深度分析:java8的新特性lambda和stream流,看完你学会了吗?
1. lambda表达式
1.1 什么是lambda
以java为例,可以对一个java变量赋一个值,比如int a = 1,而对于一个方法,一块代码也是赋予给一个变量的,对于这块代码,或者说被赋给变量的函数,就是一个lambda表达式
//为变量赋值
int a = 1; //将代码块赋值给变量
var = public void fun(int x){
x+1;
} //可以简化
var = (x)->x+1;
1.2 java为什么要引入lambda
lambda是为函数式编程服务的
编程语言共性之------什么是函数式编程?
函数式编程是一种编程范式,也就是如何编写程序的方法论,主要思想是把运算过程尽量编写成一系列嵌套的函数调用,FP强调“everything is lambda",并且强调在逻辑处理中不变性的重要性
OOP强调“everything is object”,以及object之间的消息传递。通过消息传递改变每个Object的内部状态,但是很多情况代码的编写实际上是用不到对象的,比如,对一组数据做加工,先查询,然后聚合,聚合后排序,再join,再排序,再聚合,再转换(map)得到最终的结果。这个过程,用FP的函数就很自然
result = func1(func2(func3...funcN(x))))
java为了在原先oop的思想上增加函数式编程的使用,在java8上增加了lambda函数的新特性
除此之外,lambda表达式的引入还使得代码更为简洁,可以避免生成过多的污染环境的无用实现类(下面说)
1.3 如何使用lambda表达式
lambda表达式的引入可以避免生成过多的污染环境的实现类;
lambda表达式可以被赋值给一个变量,那么这个变量的类型是什么?
在java中,所有的Lambda的类型都是一个接口,而Lambda表达式本身,需要是这个接口的实现,这个接口需要具备三个特征,具备这些特征的接口叫做函数式接口
函数式接口只有一个抽象方法
default方法为默认实现,不计入抽象方法
如果接口声明了一个覆盖java.lang.Object的全局方法之一的抽象方法,那么它不会计入接口的抽象方法数量中,因为接口的任何实现都将具有java.lang.Object或其他地方的实现
如何使用lambda表达式
比如Comparator接口就是一个函数式接口,所以他可以使用lambda表达式,在之前使用comparator对一个list排序是下面这样的
List<Integer> list = new ArrayList<>();
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1-o2;
}
});
可以看到上面实际真正有用的是return o1 - o2,上面的代码使用lambda表达式写如下
Collections.sort(list, ((o1, o2) -> o1-o2));
Lambda 表达式的基础语法:Lambda 操作符->将 Lambda 表达式拆分成两部分:
左侧:Lambda 表达式的参数列表;
右侧:Lambda 表达式中所需执行的功能, 即 Lambda 体;
语法格式一:无参数,无返回值
() -> System.out.println("Hello Lambda!"); 语法格式二:有一个参数,并且无返回值
(x) -> System.out.println(x) 语法格式三:若只有一个参数,小括号可以省略不写
x -> System.out.println(x) 语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator<Integer> com = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
}; 语法格式五:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
1.4 lambda表达式方法引用,构造器引用和数组引用
方法引用
若 Lambda 体中的功能,已经有方法提供了实现,可以使用方法引用
对象的引用 :: 实例方法名
类名 :: 静态方法名
类名 :: 实例方法名
①方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName
//对象的引用 :: 实例方法名
@Test
public void test1(){
// 之前我们是这样写的
Employee emp = new Employee(101, "张三", 18, 9999);
Supplier<String> sup = () -> emp.getName();
System.out.println(sup.get()); System.out.println("----------------------------------");
// 现在我们是这样写的
Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
} //类名 :: 静态方法名
@Test
public void test2(){
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
System.out.println("-------------------------------------");
Comparator<Integer> com2 = Integer::compare;
} //类名 :: 实例方法名
@Test
public void test3(){
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System.out.println(bp.test("abcde", "abcde")); System.out.println("-----------------------------------------"); BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("abc", "abc")); }
构造器引用
对于person类,有两个构造器
class Person {
String firstName;
String lastName; Person() {} Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
现在有一个工厂接口用来生成person类
// Person 工厂
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}
我们可以通过 :: 关键字来引用 Person 类的构造器,来代替手动去实现这个工厂接口:
// 直接引用 Person 构造器
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
Person::new 这段代码,能够直接引用 Person 类的构造器。然后 Java 编译器能够根据上下文选中正确的构造器去实现 PersonFactory.create 方法
2.1 什么是Stream
Java 8引入了全新的Stream API,这里的Stream和I/O流不同,Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作,Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返
List<String> myList =
Arrays.asList("a1", "a2", "b1", "c2", "c1"); myList
.stream() // 创建流
.filter(s -> s.startsWith("c")) // 执行过滤,过滤出以 c 为前缀的字符串
.map(String::toUpperCase) // 转换成大写
.sorted() // 排序
.forEach(System.out::println); // for 循环打印 ①:中间操作会再次返回一个流,所以,我们可以链接多个中间操作,注意这里是不用加分号的。上图中的filter 过滤,map 对象转换,sorted 排序,就属于中间操作。
②:终端操作是对流操作的一个结束动作,一般返回 void 或者一个非流的结果。上图中的 forEach循环 就是一个终止操作
上面是Stream的简单实用,可以看出它也是函数式编程,更多的表达了业务逻辑
2.2 常用api
创建Stream
1. Arrays.stream()
当在日常编程中面对的是一个数组,可以使用Arrays.stream()方法来使用Stream
Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96};
long count = Arrays.stream(array).filter(i->i>20).count();
2. Stream.of()
当面对数组时除了可以使用Arrays.stream()方法外,还可以使用Stream将需要的数组转成Stream。这个方法不但支持传入数组,将数组转成Stream,也支持传入多个参数,将参数最终转成Stream
Integer[] array = new Integer[]{3,4,8,16,19,27,23,99,76,232,33,96};
long count = Stream.of(array).filter(i->i>20).count();
long sum = Stream.of(12,77,59,3,654).filter(i->i>20).mapToInt(Integer::intValue).sum();
System.out.println("count:"+count+",sum:"+sum);
3. Collection.stream()
这个就是最常见的Stream了。因为Collection是Java中集合接口的父接口,Java中的集合都继承或实现了此接口。所以Java中的集合都可以使用此方法来创建一个Stream
List<Integer> numbers = new ArrayList<>();
numbers.add(3);
numbers.add(4);
numbers.add(8);
numbers.add(16);
numbers.stream().forEach(number->{
System.out.println(number);
});
4.filter
这是一个Stream的过滤转换,此方法会生成一个新的流,其中包含符合某个特定条件的所有元素,filter接受一个函数作为参数,该函数用Lambda表达式表示
List<Integer> integerList = Lists.newArrayList();
integerList.add(15);
integerList.add(32);
integerList.add(5);
integerList.add(232);
integerList.add(56);
List<Integer> after = integerList.stream()
.filter(i->i>50)
.collect(Collectors.toList());
System.out.println(after);//232,56
5.map
map方法指对一个流中的值进行某种形式的转换。需要传递给它一个转换的函数作为参数
List<Integer> integerList = Lists.newArrayList();
integerList.add(15);
integerList.add(32);
integerList.add(5);
integerList.add(232);
integerList.add(56);
//将Integer类型转换成String类型
List<String> afterString = integerList.stream()
.map(i->String.valueOf(i)).collect(Collectors.toList());
6.flatMap
将多个Stream连接成一个Stream,这时候不是用新值取代Stream的值,与map有所区别,这是重新生成一个Stream对象取而代之
List<String> words = new ArrayList<String>();
words.add("your");
words.add("name"); public static Stream<Character> characterStream(String s){
List<Character> result = new ArrayList<>();
for (char c : s.toCharArray())
result.add(c);
return result.stream();
} Stream<Stream<Character>> result = words.map(w -> characterStream(w));
//[['y', 'o', 'u', 'r'], ['n', 'a', 'm', 'e']]
Stream<Character> letters = words.flatMap(w -> characterStream(w));
//['y', 'o', 'u', 'r', 'n', 'a', 'm', 'e']
7.limit方法和skip方法
limit(n)方法会返回一个包含n个元素的新的流(若总长小于n则返回原始流)
skip(n)方法正好相反,它会丢弃掉前面的n个元素
用limit和skip方法一起使用就可以实现日常的分页功能:
List<Integer> pageList = myList.stream()
.skip(pageNumber*pageSize)
.limit(pageSize).collect(Collectors.toList());
8.distinct方法和sorted方法
distinct方法会根据原始流中的元素返回一个具有相同顺序、去除了重复元素的流,这个操作显然是需要记住之前读取的元素。
List<Integer> myTestList = Lists.newArrayList();
myTestList.add(10);
myTestList.add(39);
myTestList.add(10);
myTestList.add(78);
myTestList.add(10);
List<Integer> distinctList = myTestList.stream()
.distinct().collect(Collectors.toList());
System.out.println("distinctList:"+distinctList);
运行结果:
distinctList:[10, 39, 78]
sorted方法是需要遍历整个流的,并在产生任何元素之前对它进行排序。因为有可能排序后集合的第一个元素会在未排序集合的最后一位。
List<Integer> myTestList = Lists.newArrayList();
myTestList.add(39);
myTestList.add(78);
myTestList.add(10);
myTestList.add(22);
myTestList.add(56);
List<Integer> sortList = myTestList.stream()
.sorted(Integer::compareTo).collect(Collectors.toList());
System.out.println("sortList:"+sortList);
运行结果:
sortList:[10, 22, 39, 56, 78]
9.Collect
collect在流中生成列表,map,等常用的数据结构
将一个流收集到一个List中,只需要这样写就可以。
List<Integer> thereList = hereList.stream().collect(Collectors.toList()); 收集到Set中可以这样用
Set<Integer> thereSet = hereList.stream().collect(Collectors.toSet()); 收集到Set时,控制Set的类型,可以这样。
TreeSet<Integer> treeSet = hereList.stream()
.collect(Collectors.toCollection(TreeSet::new));
10.聚合操作
聚合是指将流汇聚为一个值,以便在程序中使用。聚合方法都是终止操作,聚合方法包括sum,count,max,min
long sum = Stream.of(12,77,59,3,654).filter(i->i>20).mapToInt(Integer::intValue).sum();
findFirst方法返回非空集合中的第一个值,它通常与filter方法结合起来使用
Integer first = hearList.stream().filter(i->i>100).findFirst().get(); findAny方法可以在集合中只要找到任何一个所匹配的元素,就返回,此方法在对流并行执行时十分有效
Integer anyItem = hearList.parallelStream().filter(i->i>100).findAny().get();
11.分组
对具有相同特性的值进行分组是一个很常见的功能
将一个Room对象集合按照高度分组。 List<Room> roomList = Lists.newArrayList(
new Room(11,23,56),
new Room(11,84,48),
new Room(22,46,112),
new Room(22,75,62),
new Room(22,56,75),
new Room(33,92,224)); Map<Integer,List<Room>> groupMap = roomList.stream().collect(Collectors.groupingBy(Room::getHigh));
System.out.println("groupMap:"+groupMap);
2.3 Stream流的处理顺序
Stream流的中间操作具有延迟性,当且仅当存在终端操作时,中间操作才会被执行
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> {
System.out.println("filter: " + s);
return true;
});
执行此代码段时,不会打印任何内容,对上面的代码添加 forEach终端操作,就有打印内容了
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> {
System.out.println("filter: " + s);
return true;
})
.forEach(s -> System.out.println("forEach: " + s));
filter: d2
forEach: d2
filter: a2
forEach: a2
filter: b1
forEach: b1
filter: b3
forEach: b3
filter: c
forEach: c
但是可以看到输出结果并不是先将所有filter操作的打印语句打印出来;事实上,输出的结果却是随着链条垂直移动的,比如说,当 Stream 开始处理 d2 元素时,它实际上会在执行完 filter 操作后,再执行 forEach 操作,接着才会处理第二个元素
原因是出于性能的考虑。这样设计可以减少对每个元素的实际操作数,比如下面操作
Stream.of("d2", "a2", "b1", "b3", "c")
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase(); // 转大写
})
.anyMatch(s -> {
System.out.println("anyMatch: " + s);
return s.startsWith("A"); // 过滤出以 A 为前缀的元素
}); // map: d2
// anyMatch: D2
// map: a2
// anyMatch: A2
终端操作 anyMatch()表示任何一个元素以 A 为前缀,返回为 true,就停止循环。所以它会从 d2 开始匹配,接着循环到 a2 的时候,返回为 true ,于是停止循环。
由于数据流的链式调用是垂直执行的,map这里只需要执行两次。相对于水平执行来说,map会执行尽可能少的次数,而不是把所有元素都 map 转换一遍
stream --> filter --> map --> sorted --> collect
2.4 并行流
和迭代器不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item;
Stream具有平行处理能力,处理的过程会分而治之,也就是将一个大任务切分成多个小任务,这表示每个任务都是一个操作
//parallel方法可以将任意的串行流转换为一个并行流
Stream.of(roomList).parallel();
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.parallelStream()
.forEach(out::println);
//展示顺序不一定会是1、2、3、4、5、6、7、8、9,而可能是任意的顺序
最后
大家看完有什么不懂的欢迎在下方留言讨论
深度分析:java8的新特性lambda和stream流,看完你学会了吗?的更多相关文章
- 深度分析:SpringBoot异常捕获与封装处理,看完你学会了吗?
SpringBoot异常处理 简介 日常开发过程中,难免有的程序会因为某些原因抛出异常,而这些异常一般都是利用try ,catch的方式处理异常或者throw,throws的方式抛出异常不管.这种 ...
- java8新特性Lambda和Stream
Java8出来已经4年,但还是有很多人用上了jdk8,但并没用到里面的新东西,那不就等于没用?jdk8有许多的新特性,详细可看下面脑图 我只讲两个最重要的特性Lambda和Stram,配合起来用可以极 ...
- 深度分析:java设计模式中的原型模式,看完就没有说不懂的
前言 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的 ...
- vue3.x新特性之setup函数,看完就会用了
最近有小伙伴跟我聊起setup函数,因为习惯了vue2.x的写法导致了,setup用起来觉得奇奇怪怪的,在一些api混编的情况下,代码变得更加混乱了,个人觉得在工程化思想比较强的团队中使用setup确 ...
- 乐字节-Java8新特性-Lambda表达式
上一篇文章我们了解了Java8新特性-接口默认方法,接下来我们聊一聊Java8新特性之Lambda表达式. Lambda表达式(也称为闭包),它允许我们将函数当成参数传递给某个方法,或者把代码本身当作 ...
- Java8新特性-Lambda表达式是什么?
目录 前言 匿名内部类 函数式接口 和 Lambda表达式语法 实现函数式接口并使用Lambda表达式: 所以Lambda表达式是什么? 实战应用 总结 前言 Java8新特性-Lambda表达式,好 ...
- Java8 新特性 Lambda & Stream API
目录 Lambda & Stream API 1 Lambda表达式 1.1 为什么要使用lambda表达式 1.2 Lambda表达式语法 1.3 函数式接口 1.3.1 什么是函数式接口? ...
- Java 8 新特性 - Lambda表达式
Lambda表达式 vs 匿名类既然lambda表达式即将正式取代Java代码中的匿名内部类,那么有必要对二者做一个比较分析.一个关键的不同点就是关键字 this.匿名类的 this 关键字指向匿名类 ...
- 简单了解JAVA8的新特性
JAVA8新特性会颠覆整个JAVA程序员的编程习惯 甚至如果您坚守JAVA7之前的编程习惯,今后你看比较年轻的程序员写的JAVA代码都会无法理解 所以为了保证不脱钩,我觉得有必要学习JAVA8的新特性 ...
随机推荐
- linux mount 挂载提示 mount: you must specify the filesystem type
解决方法: mkfs.ext3 /dev/vdv mount -t ext3 /dev/vdv /usr1
- IDEA操作git的一些常用技巧
转自:https://blog.csdn.net/ck4438707/article/details/53455962 Git原理以后会分章节介绍,本次主要说一下intellij怎样操作git.int ...
- short i =1; i=i+1与short i=1; i+=1的区别
很典型的一到JAVA 基础面试题,上次面试遇到的,现在记录一下. short i =1; i=i+1;short i=1;i+=1;这两有什么区别呢 ?对两个容量不一样的数据类型的变量进行算术运算时, ...
- java实现单链表、栈、队列三种数据结构
一.单链表 1.在我们数据结构中,单链表非常重要.它里面的数据元素是以结点为单位,每个结点是由数据元素的数据和下一个结点的地址组成,在java集合框架里面 LinkedList.HashMap(数组加 ...
- Redis学习笔记(二)——Keys通用操作
1.查询所有key: keys * 2.*通用符(代表0或多),查询所有以n(*)开头的key: keys n* 3.?通用符(代表1个字符): key n? 4.del删除key [key1 key ...
- WebService安全机制的思考与实践
近来因业务需要,需要研究webservice,于是便有这篇文章:SpringBoot整合Apache-CXF实践 一.WebService是什么? WebService是一个平台独立的.低耦合的.自包 ...
- 蒲公英 · JELLY技术周刊 Vol.28: Next.js 10 发布
蒲公英 · JELLY技术周刊 Vol.28 前端应用到底该选 SSR 还是 CSR?每个项目技术栈决策的时候都会根据实际需求有自己的看法,而在不久前 React 17 发布之后,自然而然也会有同学好 ...
- 适合 C++ 新手学习的开源项目——在 GitHub 学编程
作者:HelloGitHub-小鱼干 俗话说:万事开头难,学习编程也是一样.在 HelloGitHub 的群里,经常遇到有小伙伴询问编程语言如何入门方面的问题,如: 我要学习某一门编程语言,有什么开源 ...
- Vue3教程:一个基于 Vue 3 + Vant 3 的商城项目开源啦!
之前发布过一篇文章,告诉大家我要开发一个 Vue3 的商城项目并开源到 GitHub 上,供大家练手和学习,随后也一直有收到留言和反馈,问我开发到哪里了,什么时候开源之类的问题,今天终于可以通知大家, ...
- TCP性能分析与调优策略
网络传输 传播延迟: 消息从发送端到接收端需要的时间,是信号传播距离和速度的函数 传输延迟: 把消息中的所有比特转移到链路中需要的时间,是消息长度和链路速率的函数 处理延迟: 处理分组首部.检查位错误 ...