Java中lambda表达式详解
原文地址:http://blog.laofu.online/2018/04/20/java-lambda/
为什么使用lambda
在java中我们很容易将一个变量赋值,比如int a =0;int b=a;
但是我们如何将一段代码和一个函数赋值给一个变量?这个变量应该是什么的类型?
在javascript中,可以用一个对象来存储。
var t=function() { int a=1; a=a+1; alert(a); }
在java中,直到java8的lambda的特性问世,才有办法解决这个问题
什么是lambda
什么是lambda? lambda在程序中到底是怎样的一个存在? 首先看代码:
interface eat { void eatFood(); } public static void main(String[] args) { eat e = () -> System.out.printf("hello\n"); e.eatFood(); eat e1 = new eat() { @Override public void eatFood() { System.out.printf("anoymous class\n"); } }; e1.eatFood(); }
上面的代码中,e是一个lambda的对象,根据java的继承的特性,我们可以说e对象的类型是继承自eat接口。而e1是一个正常的匿名类的对象.
通过对比, 可以说 lambda的表达式其实是接口的实现的“另一种方式”。这种方式更加简洁,更容易阅读。除了代码层面的简洁外,在编译的结果时候lambda也不会产生一个多余的匿名类。
对于eat这个特殊的接口,称之为:函数式接口
lamda的优点
代码缩减
Option的使用简化代码
假如我们有个方法,能够产生一个Option对象std
Option<Person> std=getStudent();
1、是否为空的判断
2、返回不为空的对象
3、多重if else的简化
函数式接口
什么是函数式接口?这个是我们理解Lambda表达式的重点,也是产生lambda表达式的“母体”,这里我们引用一个比较容易理解的说法:
函数式接口是 一个只有一个抽象方法(不包含object中的方法)的接口。
这个需要说明一点,就是在Java中任何一个对象都来自Object 所有接口中自然会继承自Object中的方法,但在判断是否是函数式接口的时候要排除Object中的方法,下面举几个例子如下:
//这个是函数式接口 interface eat { void eatFood(); } //这个不是是函数式接口 interface eat { default void eatFood() { System.out.println("hello"); }; } //这个是一个函数式接口 interface eat { void eatFood(); String toString(); }
对于是否是函数式接口,java8中也提供了一个专用的注解:@FunctionalInterface。通过这个注解,可以确定是否是函数式接口:
//此处会报编译错误 @FunctionalInterface interface eat { default void eatFood() { System.out.println("hello"); }; }
下面我们写一段lambda简单的代码,找出指定的身份证号码,并打印出来。
最终的调用:
对于上面的代码实现,在我们调用excutor方法前,并不知道findName的实现方法,直到在最后把一个方法作为参数传入到excutor方法中。
反思:函数式接口NameCheckInterface,是不是可以用来表示所有返回值为bool类型的,有两个形参(类型是passager 和String类型)的lambda表达式?
如果我们再配合泛型的话,是不是我们只需要定义一个通用的函数式接口?下面我们改写下代码:
@FunctionalInterface public interface NameCheckInterface<T,T1,T2> { T2 findName(T passager,T1 name); } @FunctionalInterface public interface PrintInterface<T> { void printName(T name); } private void excutor(List<passager> passagerList, NameCheckInterface<Boolean,passager,String> checker, PrintInterface<String> printer) { for (passager p : passagerList) { if (checker.findName(p,"李四")){ printer.printName(p.getPassagerNo()); } } }
对应的调用方法
@Test public void simpTest() { List<passager> passagerList = new ArrayList<>(); passagerList.add(new passager("李四", "123456789")); passagerList.add(new passager("张三", "123456789")); passagerList.add(new passager("王二", "123456789")); excutor(passagerList,(p,str)->p.getName().equals(str),str-> System.out.println(str)); }
对于这段代码,可以得出lambda中的函数式接口是可以公用的,而jdk中也已经定义了很多通用的函数式接口。
常用的函数式接口
在jdk中通用的函数式接口如下(都在java.util.function包中):
Runnable r = () -> System.out.printf("say hello");//没有输入参数,也没有输出 Supplier<String> sp = () -> "hello";//只有输出消息,没有输入参数 Consumer<String> cp = r -> System.out.printf(r);//有一个输入参数,没有输出 Function<Integer, String> func = r -> String.valueOf(r);//有一个输入参数 有一个输出参数 BiFunction<Integer, Integer, String> biFunc = (a, b) -> String.valueOf(a + b);//有两个输入参数 有一个输出参数 BiConsumer<Integer, Integer> biCp = (a, b) -> System.out.printf(String.valueOf(a + b));//有两个输入参数 没有输出参数
PS:上面是基本的方法,其他的都是基于这几个扩展而来
如果上面的代码使用jdk中的函数式接口的话,就不用额外的定义NameCheckInterface和PrintInterface 接口了。根据上面的参数和返回值的形式,可以使用BiFunction和Consumer直接改写excutor方法:
private void excutor(List<passager> passagerList, BiFunction<passager,String,Boolean> checker, Consumer<String> printer) { for (passager p : passagerList) { if (checker.apply(p,"李四")){ printer.accept(p.getPassagerNo()); } } }
函数的引用
从上面的demo中,使用通用的函数表达式能够减少自定义函数式接口,为了进一步简化代码,lambda表达式可以改写成函数的引用的形式
函数的引用是lambda表达式的更简洁的一种写法,也是更能体现出函数式编程的一种形式,让我们更能理解lambda终归也是一个“函数的对象”。 下面我们改写一个例子:
Consumer<String> c1 = r -> System.out.printf(r); c1.accept("1"); Consumer<String> c2 =System.out::printf; c1.accept("2");
在上面的demo中lambda表达式被我们改写成System.out::printf这个形式,等于我们把一个函数直接赋值给了一个c2对象,这里我们可以俗称(非官方)c2为java函数的一个对象,这个也结局填补了java中一个空白。
函数引用的规则
对于Java中lambda改成函数的引用要遵循一定的规则,具体可以分为下面的四种形式:
- 静态方法的引用
如果函数式接口的实现恰好可以通过调用一个静态方法来实现,那么就可以使用静态方法引用
Consumer<String> c1 = r -> Integer.parseInt(r); c1.accept("1"); Consumer<String> c2 =Integer::parseInt; c1.accept("2");
2.实例方法引用
如果函数式接口的实现恰好可以通过调用一个实例方法来实现,那么就可以使用实例方法引用
Consumer<String> ins1 = r -> System.out.print(r); c1.accept("1"); Consumer<String> ins2 =System.out::print; c1.accept("2");
3.对象方法引用
抽象方法的第一个参数类型刚好是实例方法的类型,抽象方法剩余的参数恰好可以当做实例方法的参数。如果函数式接口的实现能由上面说的实例方法调用来实现的话,那么就可以使用对象方法的引用。
Function<BigDecimal,Double> fuc1=t->t.doubleValue(); fuc1.apply(new BigDecimal("1.025")); Function<BigDecimal,Double> fuc2=BigDecimal::doubleValue; fuc2.apply(new BigDecimal("1.025")); BiFunction<BigDecimal, BigDecimal, BigDecimal> func3 = (x, y) -> x.add(y); func3.apply(new BigDecimal("1.025"), new BigDecimal("1.254")); BiFunction<BigDecimal, BigDecimal, BigDecimal> func4 = BigDecimal::add; func4.apply(new BigDecimal("1.025"), new BigDecimal("1.254"));
4.构造方法引用
如果函数式接口的实现恰好可以通过调用一个类的构造方法来实现,那么就可以使用构造方法引用。
Consumer<String> n1 = r ->new BigDecimal(r); c1.accept("1"); Consumer<String> n2 =BigDecimal::new; c1.accept("2");
stream API的引用
Stream是处理数组和集合的API,Stream具有以下特点:
- 不是数据结构,没有内部存储
- 不支持索引访问
- 延迟计算
- 支持过滤,查找,转换,汇总等操作
对于StreamAPI的学习,首先需要弄清楚lambda的两个操作类型:中间操作和终止操作。 下面通过一个demo来认识下这个过程。
Stream st=Arrays.asList(1,2,3,4,5).stream().filter(x->{ System.out.print(x); return x>3; });
当我们执行这段代码的时候,发现并没有任何输出,这是因为lambda表达式需要一个终止操作来完成最后的动作。 我们修改代码:
Stream st=Arrays.asList(1,2,3,4,5).stream().filter(x->{ System.out.print(x); return x>3; }); st.forEach(t-> System.out.print(t));
对应的输出结果是:
1234455
为什么会有这个输出呢?因为在filter函数的时候并没有真正的执行,在forEach的时候才开始执行整个lambda表达式,所以当执行到4的时候,filter输出之后,forEach也执行了,最终结果是1234455
对于Java中的lambda表达式的操作,可以归类和整理如下:
中间操作:
- 过滤 filter
- 去重 distinct
- 排序 sorted
- 截取 limit、skip
- 转换 map/flatMap
- 其他 peek
终止操作
- 循环 forEach
- 计算 min、max、count、 average
- 匹配 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny
- 汇聚 reduce
- 收集器 toArray collect
常用的几个lambda
下面我们对这几个常用的lambda表达式写几个demo,首先定义公共的Student类:
public class Student { public Student(String stuName, int age, BigDecimal score, int clazz) { this.stuName = stuName; this.age = age; this.score = score; this.clazz = clazz; } private String stuName; private int age; private BigDecimal score; private int clazz; public String getStuName() { return stuName; } public void setStuName(String stuName) { this.stuName = stuName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public BigDecimal getScore() { return score; } public void setScore(BigDecimal score) { this.score = score; } public int getClazz() { return clazz; } public void setClazz(int clazz) { this.clazz = clazz; } } List<Student> studentList = new ArrayList<>(); studentList.add(new Student("黎 明", 20, new BigDecimal(80), 1)); studentList.add(new Student("郭敬明", 22, new BigDecimal(90), 2)); studentList.add(new Student("明 道", 21, new BigDecimal(65.5), 3)); studentList.add(new Student("郭富城", 30, new BigDecimal(90.5), 4)); studentList.add(new Student("刘诗诗", 20, new BigDecimal(75), 1)); studentList.add(new Student("成 龙", 60, new BigDecimal(88), 5)); studentList.add(new Student("郑伊健", 60, new BigDecimal(86), 1)); studentList.add(new Student("刘德华", 40, new BigDecimal(81), 1)); studentList.add(new Student("古天乐", 50, new BigDecimal(83), 2)); studentList.add(new Student("赵文卓", 40, new BigDecimal(84), 2)); studentList.add(new Student("吴奇隆", 30, new BigDecimal(86), 4)); studentList.add(new Student("言承旭", 50, new BigDecimal(68), 1)); studentList.add(new Student("郑伊健", 60, new BigDecimal(86), 1)); studentList.add(new Student("黎 明", 20, new BigDecimal(80), 1)); studentList.add(new Student("李连杰", 65, new BigDecimal(86), 4)); studentList.add(new Student("周润发", 69, new BigDecimal(58), 1)); studentList.add(new Student("徐若萱", 28, new BigDecimal(88), 6)); studentList.add(new Student("许慧欣", 26, new BigDecimal(86), 8)); studentList.add(new Student("陈慧琳", 35, new BigDecimal(64), 1)); studentList.add(new Student("关之琳", 45, new BigDecimal(50), 9)); studentList.add(new Student("温碧霞", 67, new BigDecimal(53), 2)); studentList.add(new Student("林青霞", 22, new BigDecimal(56), 3)); studentList.add(new Student("李嘉欣", 25, new BigDecimal(84), 1)); studentList.add(new Student("彭佳慧", 26, new BigDecimal(82), 5)); studentList.add(new Student("陈紫涵", 39, new BigDecimal(88), 1)); studentList.add(new Student("张韶涵", 41, new BigDecimal(90), 6)); studentList.add(new Student("梁朝伟", 58, new BigDecimal(74), 1)); studentList.add(new Student("梁咏琪", 65, new BigDecimal(82), 7)); studentList.add(new Student("范玮琪", 22, new BigDecimal(83), 1));
forEach
forEach:代表循环当前的list ,下面的例子是循环打印出student的名字
studentList.stream().forEach(x -> System.out.println(x.getStuName()));
filter
根据条件过滤当前的数据,获得分数大于80的学生名称
studentList.stream().filter(t -> t.getScore().compareTo(new BigDecimal(80)) > 0).forEach(x -> System.out.println(x.getStuName()));
distinct、sorted 、group
1.去除重复数据
studentList.stream().distinct().forEach(x -> System.out.println(x.getStuName()));
2.单条件排序和多条件排序
studentList.stream().sorted(Comparator.comparing(Student::getScore)).forEach(x -> System.out.println(x.getStuName())); //多条件排序 studentList.stream().sorted(Comparator.comparing(Student::getScore).thenComparing(Student::getStuName)).forEach(x -> System.out.println(x.getStuName()));
3.group 的使用
System.out.println(studentList.stream().collect(Collectors.groupingBy(x->x.getAge(),Collectors.counting())));
limit、skip
跳过多少,取多少个元素,可以根据当前的数据进行分页
studentList.stream().skip(10).limit(5).forEach(x -> System.out.println(x.getStuName())); //具体的分页 int pageIndex=1; int pageSize=5; studentList.stream().skip((pageIndex-1)*pageSize).limit(pageSize).forEach(x -> System.out.println(x.getStuName()));
map/flatMap
map是一个转换的工具,提供很多转换的方法,mapToInt,mapToDouble
studentList.stream().map(Student::getScore).forEach(x -> System.out.println(x));
上面的结果是输出当前的所有同学的得分。
flatMap是一个可以把子数组的值放到数组里面, 下面的实例是把所有的名字都拆开成一个新的数组
studentList.stream().flatMap(x-> Arrays.stream(x.getStuName().split(""))).forEach(x -> System.out.println(x));
min、max、count、 average
一组常用的统计函数:
studentList.stream().max(Comparator.comparing(x -> x.getAge())).ifPresent(x-> System.out.println(x.getAge())); studentList.stream().min(Comparator.comparing(x -> x.getAge())).ifPresent(x-> System.out.println(x.getAge())); System.out.println(studentList.stream().count()); studentList.stream().mapToDouble(x -> x.getScore().doubleValue()).average().ifPresent(x-> System.out.println(x));
anyMatch、noneMatch、 allMatch、 findFirst、 findAny
anyMatch: 操作用于判断Stream中的是否有满足指定条件的元素。如果最少有一个满足条件返回true,否则返回false。
noneMatch: 与anyMatch相反。allMatch是判断所有元素是不是都满足表达式。
findFirst: 操作用于获取含有Stream中的第一个元素的Optional,如果Stream为空,则返回一个空的Optional。若Stream并未排序,可能返回含有Stream中任意元素的Optional。
findAny: 操作用于获取含有Stream中的某个元素的Optional,如果Stream为空,则返回一个空的Optional。由于此操作的行动是不确定的,其会自由的选择Stream中的任何元素。在并行操作中,在同一个Stram中多次调用,可能会不同的结果。在串行调用时,都是获取的第一个元素, 默认的是获取第一个元素,并行是随机的返回。
System.out.println(studentList.stream().anyMatch(r -> r.getStuName().contains("伟"))); System.out.println(studentList.stream().allMatch(r -> r.getStuName().contains("伟"))); System.out.println(studentList.stream().noneMatch(r -> r.getStuName().contains("伟"))); System.out.println(studentList.stream().findFirst()); System.out.println(studentList.stream().findAny()); for (int i=0;i<10;i++) { System.out.println(studentList.stream().parallel().findAny().get().getStuName()); }
reduce
对于reduce的使用,应该在js中也有接触到,但也是比较小众的功能,但使用起来功能却非常的强大,先看一个正常的demo:
Stream.of(1, 5, 10, 8).reduce((x, y) -> { System.out.println("x : " + x); System.out.println("y : " + y); System.out.println("x+y : " +x); System.out.println("--------"); return x + y; });
打印结果:
x : 1 y : 5 x+y : 1 -------- x : 6 y : 10 x+y : 6 -------- x : 16 y : 8 x+y : 16 --------
可以看出:
- reduce是一个循环,有两个参数
- 第一次执行的时候x是第一个值,y是第二个值。
- 在第二次执行的时候,x是上次返回的值,y是第三个值
…. 直到循环结束为止。
再修改代码如下:
//指定了初始值 Stream.of(1, 5, 10, 8).reduce(100,(x, y) -> { System.out.println("x : " + x); System.out.println("y : " + y); System.out.println("x+y : " +x); System.out.println("--------"); return x + y; });
x : 100 y : 1 x+y : 100 -------- x : 101 y : 5 x+y : 101 -------- x : 106 y : 10 x+y : 106 -------- x : 116 y : 8 x+y : 116 --------
toArray、collect
toArray和collect是两个收集器,toArray是把数据转换成数组,collect是转成其他的类型。这里就不在讨论了。
System.out.println(studentList.stream().collect(Collectors.groupingBy(x->x.getAge(),Collectors.counting())));
Java中lambda表达式详解的更多相关文章
- Java 8 Lambda 表达式详解
一.Java 8 Lambda 表达式了解 参考:Java 8 Lambda 表达式 | 菜鸟教程 1.1 介绍: Lambda 表达式,也可称为闭包,是推动 Java 8 发布的最重要新特性. La ...
- JAVA8之lambda表达式详解
原文:http://blog.csdn.net/jinzhencs/article/details/50748202 lambda表达式详解 一.问题 1.什么是lambda表达式? 2.lambda ...
- Java8 Lambda表达式详解手册及实例
先贩卖一下焦虑,Java8发于2014年3月18日,距离现在已经快6年了,如果你对Java8的新特性还没有应用,甚至还一无所知,那你真得关注公众号"程序新视界",好好系列的学习一下 ...
- Lambda表达式详解(例子详解)(转自:http://blog.csdn.net/damon316/article/details/51734661)
Lambda表达式详解(例子详解) lambda简介 lambda运算符:所有的lambda表达式都是用新的lambda运算符 " => ",可以叫他,“转到”或者 ...
- 类型:.net;问题:C#lambda表达式;结果:Lambda表达式详解
Lambda表达式详解 前言 1.天真热,程序员活着不易,星期天,也要顶着火辣辣的太阳,总结这些东西. 2.夸夸lambda吧:简化了匿名委托的使用,让你让代码更加简洁,优雅.据说它是微软自c#1 ...
- java中的注解详解和自定义注解
一.java中的注解详解 1.什么是注解 用一个词就可以描述注解,那就是元数据,即一种描述数据的数据.所以,可以说注解就是源代码的元数据.比如,下面这段代码: @Override public Str ...
- Java中dimension类详解
Java中dimension类详解 https://blog.csdn.net/hrw1234567890/article/details/81217788
- [转载]java中import作用详解
[转载]java中import作用详解 来源: https://blog.csdn.net/qq_25665807/article/details/74747868 这篇博客讲的真的很清楚,这个作者很 ...
- Java中日志组件详解
avalon-logkit Java中日志组件详解 lanhy 发布于 2020-9-1 11:35 224浏览 0收藏 作为开发人员,我相信您对日志记录工具并不陌生. Java还具有功能强大且功能强 ...
随机推荐
- Mysql下载(on windows-noinstall zip archive)
所有内容,都是针对Mysql5.7.18介绍. 1.首先你需要下载一个完整的包,Mysql目前有两个版本可以使用: a. MySql Enterprise Edition:企业版 b. MySql C ...
- 使用Microsoft Power BI进行基本的数据分析
Power BI是微软开发的一款简单易用的数据可视化软件. 导入数据 使用Power BI的第一步是将数据导入到软件中.获取数据->更多,可以看到可使用多种数据源,甚至微软提供了一些联机的数据源 ...
- Window文件目录遍历 和 WIN32_FIND_DATA 结构(非常详细的中文注释)
第一部分 *百度百科提供的内容总结:WIN32_FIND_DAT 第二部分 *程序实例 第三部分 *一篇使用FindFirstFile和FindNextFile函数的博文 第一部分 ...
- Node EE方案 -- Rockerjs在微店的建设与发展
本文是根据2019.4.13日参加 "Node-Party"论坛使用的PPT,加上笔者新的思考与沉淀而来.在此再次感谢贝贝网前端部门和芋头君以及相关与会人员的支持! -- 微店杨力 ...
- GTest翻译词汇表
版本号:v_0.1 词汇表 Assertion: 断言. Bug: 不翻译. Caveat: 警告. Error bound: 误差范围. Exception: 异常. Flag: 标志位. Floa ...
- 为了考PMP,我做了一个刷题小程序
一.背景 1.我是一名软件工程师,技术出身,担任开发组长,对项目管理不是很熟,所以决定系统学习下项目管理. 2.全球最适合的项目管理学习课程就是PMP,每年有4次PMP考试,证书还是很有含金量的. 3 ...
- 玩转Java多线程(乒乓球比赛)
转载请标明博客的地址 本人博客和github账号,如果对你有帮助请在本人github项目AioSocket上点个star,激励作者对社区贡献 个人博客:https://www.cnblogs.com/ ...
- Redis 学习笔记(篇三):跳表
跳表 跳表(skiplist)是一种有序的数据结构,是在有序链表的基础上发展起来的. 在 Redis 中跳表是有序集合(sort set)的底层实现之一. 说到 Redis 中的有序集合,是不是和 J ...
- Docker环境下的前后端分离项目部署与运维(十一)Docker Swarm技术
Docker Swarm技术 docker swarm技术(之前的docker集群都是在 一个虚拟主机上的,但是如果这个主机挂掉了over了,docker技术就是多个虚拟主机形成一个集群) Swarm ...
- VMware克隆CentOS7,解决网络配置问题
问题: 安装CentOS7 mini版,静态IP配置完毕后,关闭虚机CentOS7-1,克隆虚拟机为CentOS-2.克隆出来的虚拟机使用ifconfig命令,无法发现网卡,只有一个lo设备.虚机无法 ...