Lambda
在介绍Stream之前,我们先来说说Java8中的另一个新特性:Lambda,直接上代码吧
在Java8之前,list自定义排序会使用匿名内部类,实现自己的排序逻辑
stuList.sort(new Comparator<Student>() {
@Override
public int compare(Student stu1, Student stu2) {
return stu1.getAge().compareTo(stu2.getAge());
}
});
下面看一下Lambda表达式的写法:
stuList.sort((s1,s2) -> s1.getAge().compareTo(s2.getAge()));
可以看到,从代码量和整洁程度来说,Lambda都显得都简洁
什么是Lambda
Lambda表达式是表示可传递匿名方法的一种简洁方式,Lambda表达式没有名称,但是有参数列表、函数主体、返回类型,还可能有一个可以抛出的异常列表
Lambda的组成
|<----------参数--------->|<-箭头->|<----------------主体---------------|
(Student stu1,Student stu2) -> stu1.getAge().compareTo(stu2.getAge())
- 参数列表:上例中的两个参数是传给匿名方法的
- 箭头:连接参数和主体,是操作符
- 主体:是方法的逻辑处理部分,它可以接收参数列表中的参数
从上面Lambda的组成可以看出来,Lambda有两种简单的语法:
- 参数列表 -> 表达式
- 参数列表 -> {多个表达式组成的语句}
方法引用
方法引用是Java8中引入的新特性,它提供了一种引用方法而不执行方法的方式,可以让我们重复使用现用方法的定义,做为某些Lambda表达式的另一种更简洁的写法
比如:
stuList.sort((s1,s2) -> s1.getAge().compareTo(s2.getAge()));
可以使用方法引用简写为:
Comparator comparator = Comparator.comparing(Student::getAge);
当你需要方法引用时,目标引用放在分隔符::前,方法的名称放在分隔符::后。比如,上面的Student::getAge,就是引用了Student中定义的getAge方法。方法名称后不需要加括号,因为我们并没有实际调用它。方法引用提高了代码的可读性,也使逻辑更加清晰
好了,Lambda就简单的介绍到这里,更多Lambda的详细用法可以直接百度自己学习一下
Stream
Java8中添加一个新的抽象概念叫做Stream,它可以使用类似SQL语句的形式来处理Java集合的运算。
Stream提供了一种“链式”编程,能把一系列的操作像链条一样串连起来,这种风格的处理方式把集合看作是一种流,流在管道中传输,每一步操作都可以看作是管道中的一个节点,所有节点处理完成形成最终的结果
举个形象的例子,Stream的操作类似于流水线生产,整个产品线从头到尾形成一条链路,每个环节就是一个处理节点,最终所有节点处理完成就形成了最终的产品
Stream API简化了集合的各种操作,提高了程序员的生产力,让程序员能写出高效率、干净、整洁的代码
流的常见创建方式
public void create() throws FileNotFoundException {
Stream<String> stringStream = Stream.of("a","b","c");
Stream<String> stringStream1 = Stream.of("d","e","f");
//通过concat将两个Stream连接成一个Stream
Stream<String> stringStream2 = Stream.concat(stringStream,stringStream1);
stringStream2.forEach(str -> System.out.println(str));
//通过迭代器创建Stream
Stream<Integer> integerStream = Stream.iterate(0,(x) -> (x + 1)).limit(10);
//generate创建Stream
Stream<Double> doubleStream = Stream.generate(Math::random).limit(5);
//通过集合创建stream
List<String> list = Lists.newArrayList("a","b","c");
Stream<String> listStream = list.stream();
Stream<String> parallelStream = list.parallelStream();//并发流
//数组转换成Stream
String[] stringArr = new String[]{"a","b","c"};
Stream<String> arrStream = Arrays.stream(stringArr);
//文件流转换成Stream
BufferedReader reader = new BufferedReader(new FileReader("D:\\test.txt"));
Stream<String> fileStream = reader.lines();
}
Stream中的操作分为两类
- 中间操作
- 终止操作
|<------Stream------>| |<-------中间操作--------->|<--终止操作-->|
+--------------------+ +------+ +------+ +---+ +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+
中间操作
中间操作就是操作之后,Stream仍然是一个Stream,可以使用其他的中间操作继续对结果进行操作
- map 传入一个函数,将list中的所有元素都套用这个函数
- flatMap 传入一个函数,将每个元素转换成另外一个流,再将所有的流连接成一个流
- filter 传入过滤的规则,对stream中的元素进行过滤
- limit 传入一个数字,取stream中前面几个元素
- skip 传入一个数字,跳过几个元素
- distinct 去掉重复元素
- sorted 对元素进行自然排序,当遇到对象排序时,可以自定义通过对象的属性来排序
- peek 传入一个Consumer,没有返回值,不改变Stream中原来的元素,注意对比map
map、flatMap用法
public void mapTest(){
List<Integer> list = Lists.newArrayList(1,2,3,4,5);
//map就是传入一个函数,将list中的所有元素都套用这个函数
Stream<Integer> stream = list.stream().map(x -> x * x);
stream.forEach(x -> System.out.println(x));
List<String> flapList = Lists.newArrayList("a#b#c","1#2#3");
//flatMap就是传入一个函数,将每个元素转换成另外一个流,再将所有的流连接成一个流
Stream<String> flagStream = flapList.stream().flatMap(x -> {
String[] strArr = x.split("#");
Stream<String> tmpStream = Arrays.stream(strArr);
return tmpStream;
});
flagStream.forEach(x -> System.out.println(x)); //a b c 1 2 3
}
filter用法
public void filterTest(){
List<Integer> list = Lists.newArrayList(1,2,3,4,5);
//filter就是传入过滤的规则,对stream中的元素进行过滤
list.stream().filter(x -> x > 3).forEach(x -> System.out.println(x));
}
limit用法
public void limitTest(){
List<String> list = Lists.newArrayList("a","b","c","x","y","z");
//limit传入一个数字,取stream中前面几个元素
list.stream().limit(3).forEach(x -> System.out.println(x));
}
skip用法
public void skipTest(){
List<Integer> list = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
//跳过前三个元素
list.stream().skip(3);
//skip配合limit可实现分页,比如每页5笔记录
list.stream().skip(5 * 0).limit(5);//第一页
list.stream().skip(5 * 1).limit(5);//第二页
list.stream().skip(5 * 2).limit(5);//第三页
}
distinct用法
public void distinctTest(){
List<String> list = Lists.newArrayList("a","b","c","a","a","c","c","e","f");
//去掉重复元素
list.stream().distinct();
}
sorted用法
public void sortedTest(){
//sorted是对元素进行自然排序,当遇到对象排序时,可以自定义通过对象的属性来排序
List<String> list = Lists.newArrayList("a","b","c","a","a","c","c","e","f");
list.stream().sorted();
List<Integer> intList = Lists.newArrayList(1,5,5,3,9,4,4,7,6);
intList.stream().sorted();
}
peek用法
public void peekTest(){
List<String> list = Lists.newArrayList("a","b","c","a","a","c","c","e","f");
//peek传入一个Consumer,没有返回值,不改变Stream中原来的元素,注意对比map
list.stream().peek(x -> x = x + "666");
}
终止操作
终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次
- matchAll 接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
- noneMatch 接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
- anyMatch 接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
- findFirst 返回流中第一个元素
- findAny 返回流中的任意元素
- collect 收集操作
- count 统计操作
- min、max 最值操作
- reduce 规约操作。所有的元素规约成一个,比如对所有元素求和,求积等
- forEach、forEachOrdered 遍历操作
- toArray 数组操作
matchAll、noneMatch、anyMatch用法
public void matchTest(){
List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
list.stream().allMatch(x -> x > 10); //所有元素满足条件才返回true
list.stream().noneMatch( x -> x > 100);//所有元素不满足条件才返回true
list.stream().anyMatch(x -> x > 50);//只有一个元素满足就返回true
}
findFirst、findAny用法
public void findTest(){
List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
list.stream().findFirst().get();//返回Stream中第一个元素
list.stream().findAny().get();//返回Steam中任意元素
}
collect用法
将序列的内容重新组织到新的容器中,比如 List 或者 Map
public void collectTest(){
Student stu1 = new Student("stu1",11);
Student stu2 = new Student("stu2",12);
Student stu3 = new Student("stu3",13);
Student stu4 = new Student("stu4",14);
Student stu5 = new Student("stu5",15);
List<Student> list = Lists.newArrayList(stu1,stu2,stu3,stu4,stu5);
//将list中所有学生的姓名收集起来,放到一个新list中
List<String> nameList = list.stream().map(Student::getName).collect(Collectors.toList());
nameList.forEach(System.out::println);
}
Collectors
Collectors提供了很多种Collector的创建方式
public void CollectorsTest(){
Student stu1 = new Student("stu1",11);
Student stu2 = new Student("stu2",12);
Student stu3 = new Student("stu3",13);
Student stu4 = new Student("stu4",14);
Student stu5 = new Student("stu5",15);
List<Student> list = Lists.newArrayList(stu1,stu2,stu3,stu4,stu5);
//将所有学生的姓名收集到新的list中
List<String> nameList = list.stream().map(Student::getName).collect(Collectors.toList());
//同样可以收集到Set中
Set<String> nameSet = list.stream().map(Student::getName).collect(Collectors.toSet());
//将每个学生的name,age转换成Map,若有名称相同的会造成key相同导致失败
Map<String,Integer> map = list.stream().collect(Collectors.toMap(Student::getName,Student::getAge));
//字符串连接
String nameJoin = list.stream().map(Student::getName).collect(Collectors.joining(";","(",")"));
//---聚合操作----
//count
Long count = list.stream().collect(Collectors.counting());
// max
Integer maxAge = list.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get();
// sum
Integer sumAge = list.stream().collect(Collectors.summingInt(Student::getAge));
// avg
Double avgAge = list.stream().collect(Collectors.averagingDouble(Student::getAge));
// 所有聚合操作的返回
DoubleSummaryStatistics summaryStatistics = list.stream().collect(Collectors.summarizingDouble(Student::getAge));
summaryStatistics.getSum();
summaryStatistics.getAverage();
summaryStatistics.getCount();
summaryStatistics.getMax();
summaryStatistics.getMin();
//group 按年龄分组
Map<Integer,List<Student>> ageGroup = list.stream().collect(Collectors.groupingBy(Student::getAge));
//分区,分成两部分,一部分大于12岁,一部分小于等于12岁
Map<Boolean,List<Student>> agePartition = list.stream().collect(Collectors.partitioningBy(x -> x.getAge() > 12));
//规约
Integer reducingAge = list.stream().map(Student::getAge).collect(Collectors.reducing(Integer::max)).get();
}
可以看到Collectors提供了很多方便的收集方式,让我们很方便的处理集合的各种操作
count用法
public void countTest(){
List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
Long count = list.stream().count();
}
min、max用法
public void maxAndminTest(){
List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
Integer max = list.stream().max(Integer::compareTo).get();
Integer min = list.stream().min(Integer::compareTo).get();
}
reduce用法
public void reduceTest(){
List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
Integer sum = list.stream().reduce(0,(x,y) -> x + y);
}
forEachOrdered用法
适用用于并行流的情况下进行迭代,能保证迭代的有序性
public void forEachOrderedTest(){
List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
list.stream().parallel().forEachOrdered(System.out::println);
}
toArray用法
public void toArrayTest(){
List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
Object[] objects = list.stream().toArray();
for(Object obj : objects){
System.out.println(obj);
}
}
总结
- Lambda表达式是表示可传递匿名方法的一种简洁方式
- 从上面Lambda的组成可以看出来,Lambda有两种简单的语法:
- 参数列表 -> 表达式
- 参数列表 -> {多个表达式组成的语句}
- 方法引用是Java8中引入的新特性,它提供了一种引用方法而不执行方法的方式,可以让我们重复使用现用方法的定义,做为某些Lambda表达式的另一种更简洁的写法
- Java8中添加一个新的抽象概念叫做Stream,它可以使用类似SQL语句的形式来处理Java集合的运算
- Stream API简化了集合的各种操作,提高了程序员的生产力,让程序员能写出高效率、干净、整洁的代码
- Stream中的操作分为两类
- 中间操作:中间操作就是操作之后,Stream仍然是一个Stream,可以使用其他的中间操作继续对结果进行操作
- map 传入一个函数,将list中的所有元素都套用这个函数
- flatMap 传入一个函数,将每个元素转换成另外一个流,再将所有的流连接成一个流
- filter 传入过滤的规则,对stream中的元素进行过滤
- limit 传入一个数字,取stream中前面几个元素
- skip 传入一个数字,跳过几个元素
- distinct 去掉重复元素
- sorted 对元素进行自然排序,当遇到对象排序时,可以自定义通过对象的属性来排序
- peek 传入一个Consumer,没有返回值,不改变Stream中原来的元素,注意对比map
- 终止操作:终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次
- matchAll 接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
- noneMatch 接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
- anyMatch 接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
- findFirst 返回流中第一个元素
- findAny 返回流中的任意元素
- collect 收集操作
- count 统计操作
- min、max 最值操作
- reduce 规约操作。所有的元素规约成一个,比如对所有元素求和,求积等
- forEach、forEachOrdered 遍历操作
- toArray 数组操作
- 中间操作:中间操作就是操作之后,Stream仍然是一个Stream,可以使用其他的中间操作继续对结果进行操作
通过这些方法的介绍,相信你对Java8 Stream API有了一定的了解,熟练掌握这些方法能使你的代码更加简洁。对于Java8这种“链式”编程的风格到底是否应该推荐使用也是见仁见智,网上很多人说Stream API如果写的很长,确实可以减少代码,但这样会导致逻辑不够清晰,写的人很爽,看的人难受。笔者所在技术团队目前还没有大量使用Stream API,大家还是习惯于用平常的语法来写业务逻辑。
你所在的团队会大量的用到Stream API来减少代码量吗?欢迎给我留言~
如果感觉对你有些帮忙,请收藏好,你的关注和点赞是对我最大的鼓励!
如果想跟我一起学习,坚信技术改变世界,请微信搜索【Java天堂】关注我,我会定期分享自己的学习成果,第一时间推送给您
参考:
https://blog.csdn.net/y_k_y/article/details/84633001
https://www.jianshu.com/p/11c925cdba50