JAVA8的lambda及stream详解
目录
一、lambda学习
1. 什么是lambda
lambda是JAVA8中提供的一个语法糖,它使用代码编写更加简洁、紧凑、灵活。Lambda允许把函数当做当参数来使用,是面向函数式编辑的思想。而这种参数式函数有两种用法:
- 匿名函数:用于实现抽象方法的一块代码段,而这个抽象方法是
函数式接口
中的抽象方法 - 已有函数的引用:使用双冒号直接引用类或对象的已有方法
来一起看段代码:
List<Integer> integers= Arrays.asList(1,3,5,4,2);
integers.sort((a,b)->a-b);
integers.forEach(System.out::println);
//输出正序排列数字1 2 3 4 5
2.函数式接口介绍
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式,也就是所说lambda匿名函数的使用方式。
函数式接口具体有哪些特点呢?
- 接口有且仅有一个抽象方法
- JDK8接口中的静态方法和默认方法,都不算是抽象方法。
- 接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
- 使用FunctionalInterface注解的接口,但注解也不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响,只是加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
看一下JAVA8中的定义的Comparator函数式接口源码
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default <U extends Comparable<? super U>> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor)
{
return thenComparing(comparing(keyExtractor));
}
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
3.lambda使用方式
1)匿名函数
使用lambda特定的语法,编写接口实现代码段,代码段充当匿名函数做为参数使用。
基本语法:()->{},其中()为参数列表,{}用来描述方法体
为了使语法更加简捷、紧凑,lambda几种变种语法:
- 语法格式一:无参,无返回
Runnable r=()-> System.out.println("接口内容");
r.run();
- 语法格式二:一参,无返回
Consumer<String> c=(x)-> System.out.println(x);
c.accept("一参,无返回值");
Consumer<String> c1=x-> System.out.println(x);
c1.accept("一参,无返回值,省去括号");
- 语法格式三:两参,有返回,多语句
Comparator<Integer> c=(x,y)->{
System.out.println("多语句测试");
return Integer.compare(x,y);
};
System.out.println(c.compare(3,2));
- 语法格式四:两参,有返回
//省略大括号里和return
Comparator<Integer> c=(x,y)->Integer.compare(x,y);
System.out.println(c.compare(3,2));
语法糖的简写要旨:能省则省
- 省略匿名函数的传统语法结构
- 省略参数类型,JVM编译器会自动根据上下文推断参数类型
- 一个参数,省略lambda左侧的括号
- 一句方法体,省略lambda右侧的大括号和return
四大内置核心函数式接口
- Consumer : 消费型接口
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
//使用方法
@Test
public void consumerInvoker(String info,Consumer<String> consumer){
System.out.println("consumer接口调用方法体");
consumer.accept(info);
}
public void testConsumer() {
consumerInvoker("consumerInvoker的参数",(x)-> System.out.println("consumer的接口实现,使用参数为:"+ x));
}
//输出
consumer接口调用方法体
consumer的接口实现,使用参数为:consumerInvoker的参数
- Supplier : 供给型接口
@FunctionalInterface
public interface Supplier<T> {
T get();
}
@Test
public void supplierTest(){
List<Integer> integers = supplierInvoker(5, () -> (int) (Math.random() * 10));
for (Integer integer : integers) {
System.out.println(integer);
}
}
public List<Integer> supplierInvoker(int num,Supplier<Integer> supplier){
ArrayList<Integer> integers = new ArrayList<>();
for (int i = 0; i < num; i++) {
integers.add(supplier.get());
}
return integers;
}
- Predicate : 断言型接口
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
@Test
public void predicateTest() {
List<Integer> list=Arrays.asList(1,2,3,4,5,6,7,8,9,0);
List<Integer> list1 = predicateInvoker(list, (x) -> x % 2 == 0);
for (Integer integer : list1) {
System.out.println(integer);
}
}
public List<Integer> predicateInvoker(List<Integer> list, Predicate<Integer> pre){
ArrayList<Integer> integers = new ArrayList<>();
for (Integer integer : list) {
if (pre.test(integer)) {
integers.add(integer);
}
}
return integers;
}
- Function: 函数型接口
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
@Test
public void functionTest() {
Double v = functionInvoker(10000d, (x) -> x * (1 + 0.03));
System.out.println("理财后我有"+ v +"元钱");
System.out.println("提现后我还有"+functionInvoker(v,(x)->x-10)+"元钱");
}
public Double functionInvoker(Double money, Function<Double,Double> fun){
System.out.println("我有"+money+"元钱");
return fun.apply(money);
}
//输出
我有10000.0元钱
理财后我有10300.0元钱
我有10300.0元钱
提现后我还有10290.0元钱
四大内置核心函数式接口的扩展接口
与四大内置核心函数接口同包(java.util.function)下的其它扩展接口
接口 | 参数 | 返回值 |
---|---|---|
BiFunction<T,U,R> | T,U | R |
UnaryOperator<T> (Function子接口) | T | T |
BinaryOperator<T> (BiFunction子接口) | T,T | T |
ToDoubleBiFunction<T, U> ToLongBiFunction<T, U> ToIntBiFunction<T, U> | T,U | double long int |
ToDoubleFunction<T> …… | T | double …… |
DoubleFunction<R> LongFunction<R> IntFunction<R> | double long int | R |
BiConsumer<T, U> | T,U | void |
ObjDoubleConsumer<T> | T,double | void |
…… | …… | …… |
java.util.function包下总共43个常用函数式接口:
BiConsumer<T,U>
BiFunction<T,U,R>
BinaryOperator<T>
BiPredicate<T,U>
BooleanSupplier
Consumer<T>
DoubleBinaryOperator
DoubleConsumer
DoubleFunction<R>
DoublePredicate
DoubleSupplier
DoubleToIntFunction
DoubleToLongFunction
DoubleUnaryOperator
Function<T,R>
IntBinaryOperator
IntConsumer
IntFunction<R>
IntPredicate
IntSupplier
IntToDoubleFunction
IntToLongFunction
IntUnaryOperator
LongBinaryOperator
LongConsumer
LongFunction<R>
LongPredicate
LongSupplier
LongToDoubleFunction
LongToIntFunction
LongUnaryOperator
ObjDoubleConsumer<T>
ObjIntConsumer<T>
ObjLongConsumer<T>
Predicate<T>
Supplier<T>
ToDoubleBiFunction<T,U>
ToDoubleFunction<T>
ToIntBiFunction<T,U>
ToIntFunction<T>
ToLongBiFunction<T,U>
ToLongFunction<T>
UnaryOperator<T>
2)方法引用
如果lambda表达式的匿名函数方法体中实现的功能,已经有现成的方法时,我们可以通过方法的名称引用已有方法。
方法引用 | 等价的lambda表达式 |
---|---|
String::valueOf | x -> Sting.valueOf(x) |
Object::toString | x -> x.toString() |
x::toString | () -> x.toString() |
ArrayList::new | () -> new ArrayList<>() |
- 实例方法引用
//例1
Consumer<String> c=(x) -> System.out.println(x);
Consumer<String> c1=System.out::println;
//例2
final SysUser sysUser=new SysUser();
Supplier<String> s=() -> sysUser.getFullname();
Supplier<String> s1=sysUser::getFullname;
- 静态方法引用
Function<List<Integer>,Integer> f=(x) -> Collections.max(x);
Function<List<Integer>,Integer> f1=Collections::max;
- 类的实例方法引用
//当lambda参数列表中第一个参数为是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用类的实例方法,ClassName::method
BiPredicate<String,String> p=(x,y) -> x.equals(y);
BiPredicate<String,String> p1=String::equals;
- 构造方法引用
Supplier<SysUser> su=() -> new SysUser();
Supplier<SysUser> su1=SysUser::new;
- 数组构造方法引用
IntFunction<Integer[]> intFunction=(x) -> new Integer[x];
Integer[] apply = intFunction.apply(3);
IntFunction<Integer[]> intFunction1=Integer[]::new;
Integer[] apply1 = intFunction1.apply(3);
方法引用最明显的特点就是只有方法名,而没有参数,只能弄明白JVM是如何推导参数的,才能熟练使用方法引用。
适配方法一:引用的方法与函数式接口的抽象方法,参数列表相同,适用类型:实例方法引用
静态方法引用
构造方法引用
适配方法二:参数列表中第一个参数充当实例方法的调用者,而第二个参数充当实例方法的参数,适用类型:类的实例方法引用
适配方法三:整数型参数充当数组的构造方法的初始长度值,适用类型:数组构造方法引用
二、stream学习
1. 什么是stream
stream是把要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
你可以把它当做是是一个高级版本的Iterator,它对集合(Collection)对象功能进行了增强,它还提供并行模式进行数据操作,而无需编写多线程代码;同时它的短路操作可以在不用处理全部元素就可以返回结果,达到节省资源,提高性能的作用。
你也可以把Stream当做是一种可供流式操作的数据视图,类似数据库中视图的概念,它不改变源数据集合,如果对其有修改操作它会返回一个新的数据集合。
Stream API 借助 Lambda 表达式,极大的提高编程效率和程序可读性;它是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等。
2.流的构成
当我们使用一个流的时候,通常包括三个基本步骤:
获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
3.创建Stream
- Stream接口的静态工厂方法
- Collection对象和数组对象转换成Stream
静态工厂方法
- of
Stream<String> stringStream=Stream.of("aaa","bbb","ccc","ddd");
stringStream.forEach(System.out::println);
- generator
Stream.generate(Math::random).limit(5).forEach(System.out::println);
- iterate
Stream.iterate(1,(x)->x+1).limit(5).forEach(System.out::println);
Collection对象和数组对象转换成Stream
- Collect对象转换成Stream
List<String> arrayList=new ArrayList<>();
Stream<String> stream = arrayList.stream();
Stream<String> stringStream1 = arrayList.parallelStream();//并行stream
- 数组对象转换成Stream
String[] sArr = new String[10];
Stream<String> stream1 = Arrays.stream(sArr);
4.数据操作
流的操作类型分为两种:
-
中间操作Intermediate :一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。而中间操作又可以分为无状态操作和有状态操作
- 无状态操作
unordered()
filter()
map()
mapToInt()
mapToLong()
mapToDouble()
flatMap()
flatMapToInt()
flatMapToLong()
flatMapToDouble()
peek()
- 有状态操作
distinct()
sorted()
sorted()
limit()
skip()
无状态中间操作是指元素的处理不受前面元素的影响,而有状态的中间操作必须等到所有元素处理之后才知道最终结果,比如排序是有状态操作,在读取所有元素之前并不能确定排序结果
- 无状态操作
-
终止操作Terminal :一个流只能有一个 终止操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。终止操作的执行,才会真正开始流的遍历,并且会生成一个结果或产生数据操作效果,终止操作又可分为非短路操作和短路操作
- 非短路操作
forEach()
forEachOrdered()
toArray()
reduce()
collect()
max()
min()
count()
- 短路操作
anyMatch()
allMatch()
noneMatch()
findFirst()
findAny()
短路操作是指不用处理全部元素就可以返回结果,比如找到第一个满足条件的元素。之所以要进行如此精细的划分,是因为底层对每一种情况的处理方式不同。
- 非短路操作
在对于一个 Stream 进行N中间操作时,是不是进行了N次for 循环?其实不是这样的,转换操作都是懒操作,多个转换操作只会在 终止操作的时候才会融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 终止操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
5.Stream API接口介绍
接口名称 | 说明 |
---|---|
filter | 过滤流,过滤流中的元素,返回一个符合条件的Stream |
map | 转换流,将一种类型的流转换为另外一种流。(mapToInt、mapToLong、mapToDouble 返回int、long、double基本类型对应的Stream) |
flatMap | 简单的说,就是一个或多个流合并成一个新流。(flatMapToInt、flatMapToLong、flatMapToDouble 返回对应的IntStream、LongStream、DoubleStream流。) |
distinct | 返回去重的Stream。 |
sorted | 返回一个排序的Stream。 |
peek | 主要用来查看流中元素的数据状态。 |
limit | 返回前n个元素数据组成的Stream。属于短路操作 |
skip | 返回第n个元素后面数据组成的Stream。 |
forEach | 循环操作Stream中数据。 |
toArray | 返回流中元素对应的数组对象。 |
reduce | 聚合操作,用来做统计。 |
collect | 聚合操作,封装目标数据。 |
min、max、count | 聚合操作,最小值,最大值,总数量。 |
anyMatch | 短路操作,有一个符合条件返回true。 |
allMatch | 所有数据都符合条件返回true。 |
noneMatch | 所有数据都不符合条件返回true。 |
findFirst | 短路操作,获取第一个元素。 |
findAny | 短路操作,获取任一元素。 |
forEachOrdered | 按元素顺序执行循环操作。 |
6. 实例操作
创建一个学生对象的List对象,然后使用stream方式对学生列表对象进行聚合、过滤、排序、映射等操作。
创建学生List
List<Student> students=new ArrayList<Student>(){{
add(new Student(1,"张三","语文",88d));
add(new Student(2,"张三","数学",90d));
add(new Student(3,"张三","英语",82d));
add(new Student(4,"李四","语文",90d));
add(new Student(5,"李四","数学",80d));
add(new Student(6,"李四","英语",72d));
add(new Student(7,"王五","数学",68d));
add(new Student(8,"王五","语文",70d));
add(new Student(9,"王五","英语",92d));
}};
获得学生名单
//获取学生名单
List<String> collect2 = students.stream().map(Student::getName)
.distinct().collect(Collectors.toList());
collect2.forEach(System.out::println);
//获取学生名单
Set<String> collect = students.stream().map(Student::getName)
.collect(Collectors.toSet());
collect.stream().forEach(System.out::println);
获得张三的成绩单
//获得张三的成绩单
Map<String, Double> zsDate = students.stream()
.filter((x) -> x.getName().equals("张三"))
.collect(Collectors.toMap(Student::getCourse, Student::getScore));
zsDate.forEach((k,v)-> System.out.println(k+"成绩是:"+v));
查找每个人的总成绩,并进行排名
//查找每个人的总成绩,并进行排名
Map<String, Double> collect = students.stream().collect(Collectors.groupingBy(
Student::getName, Collectors.summingDouble(Student::getScore)
)).entrySet().stream().sorted(Entry.<String, Double>comparingByValue().reversed())
.collect(Collectors.toMap(Entry::getKey
, Entry::getValue,(x,y)->x,LinkedHashMap::new));
collect.forEach((k,v)-> System.out.println(k+"的总成绩为"+v));
查找各科成绩最高的
//查找各科成绩最高的
Map<String, Double> collect = students.stream().collect(Collectors.groupingBy(
Student::getCourse,
Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparingDouble(Student::getScore)),
(x) -> x.get().getScore()
)
));
collect.forEach((k,v)-> System.out.println(k+"的最高成绩为"+v));
查询成绩为优异的学生
//查询成绩为优异的学生
List<String> collect = students.stream().collect(Collectors.groupingBy(
Student::getName, Collectors.collectingAndThen(Collectors.toList(),
x -> x.stream().noneMatch(y -> y.getScore() < 80))))
.entrySet().stream().filter(Map.Entry::getValue)
.map(Map.Entry::getKey).collect(Collectors.toList());
collect.forEach(System.out::println);