写在前面

这是一道真实的面试题,一个读者朋友出去面试,面试官竟然问他这样一个问题:你说说Java8中为什么引入Lambda表达式?引入Lambda表达式后有哪些好处呢?还好这个朋友对Java8早有准备。不过,如果是看文章的你出去面试,面试官问你这样的问题,你是否也能轻松回答呢?

什么是Lambda表达式?

Lambda表达式是一个匿名函数,我们可以这样理解Lambda表达式:Lambda是一段可以传递的代码(能够做到将代码像数据一样进行传递)。使用Lambda表达式能够写出更加简洁、灵活的代码。并且,使用Lambda表达式能够使Java的语言表达能力得到提升。

匿名内部类

在介绍如何使用Lambda表达式之前,我们先来看看匿名内部类,例如,我们使用匿名内部类比较两个Integer类型数据的大小。

Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};

在上述代码中,我们使用匿名内部类实现了比较两个Integer类型数据的大小。

接下来,我们就可以将上述匿名内部类的实例作为参数,传递到其他方法中了,如下所示。

 TreeSet<Integer> treeSet = new TreeSet<>(com);

完整的代码如下所示。

@Test
public void test1(){
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
TreeSet<Integer> treeSet = new TreeSet<>(com);
}

我们分析下上述代码,在整个匿名内部类中,实际上真正有用的就是下面一行代码。

 return Integer.compare(o1, o2);

其他的代码本质上都是“冗余”的。但是为了书写上面的一行代码,我们不得不在匿名内部类中书写更多的代码。

Lambda表达式

如果使用Lambda表达式完成两个Integer类型数据的比较,我们该如何实现呢?

Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

看到没,使用Lambda表达式,我们只需要使用一行代码就能够实现两个Integer类型数据的比较。

我们也可以将Lambda表达式传递到TreeSet的构造方法中,如下所示。

 TreeSet<Integer> treeSet = new TreeSet<>((x, y) -> Integer.compare(x, y));

直观的感受就是使用Lambda表达式一行代码就能搞定匿名内部类多行代码的功能。

看到这,不少读者会问:我使用匿名内部类的方式实现比较两个整数类型的数据大小并不复杂啊!我为啥还要学习一种新的语法呢?

其实,我想说的是:上面咱们只是简单的列举了一个示例,接下来,咱们写一个稍微复杂一点的例子,来对比下使用匿名内部类与Lambda表达式哪种方式更加简洁。

对比常规方法和Lambda表达式

例如,现在有这样一个需求:获取当前公司中员工年龄大于30岁的员工信息。

首先,我们需要创建一个Employee实体类来存储员工的信息。

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
private static final long serialVersionUID = -9079722457749166858L;
private String name;
private Integer age;
private Double salary;
}

在Employee中,我们简单存储了员工的姓名、年龄和薪资。

接下来,我们创建一个存储多个员工的List集合,如下所示。

protected List<Employee> employees = Arrays.asList(
new Employee("张三", 18, 9999.99),
new Employee("李四", 38, 5555.55),
new Employee("王五", 60, 6666.66),
new Employee("赵六", 16, 7777.77),
new Employee("田七", 18, 3333.33)
);

1.常规遍历集合

我们先使用常规遍历集合的方式来查找年龄大于等于30的员工信息。

public List<Employee> filterEmployeesByAge(List<Employee> list){
List<Employee> employees = new ArrayList<>();
for(Employee e : list){
if(e.getAge() >= 30){
employees.add(e);
}
}
return employees;
}

接下来,我们测试一下上面的方法。

@Test
public void test3(){
List<Employee> employeeList = filterEmployeesByAge(this.employees);
for (Employee e : employeeList){
System.out.println(e);
}
}

运行test3方法,输出信息如下所示。

Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)

总体来说,查找年龄大于或者等于30的员工信息,使用常规遍历集合的方式稍显复杂了。

例如,需求发生了变化:获取当前公司中员工工资大于或者等于5000的员工信息。

此时,我们不得不再次创建一个按照工资过滤的方法。

public List<Employee> filterEmployeesBySalary(List<Employee> list){
List<Employee> employees = new ArrayList<>();
for(Employee e : list){
if(e.getSalary() >= 5000){
employees.add(e);
}
}
return employees;
}

对比filterEmployeesByAge()方法和filterEmployeesBySalary方法后,我们发现,大部分的方法体是相同的,只是for循环中对于条件的判断不同。

如果此时我们再来一个需求,查找当前公司中年龄小于或者等于20的员工信息,那我们又要创建一个过滤方法了。 看来使用常规方法是真的不方便啊!

这里,问大家一个问题:对于这种常规方法最好的优化方式是啥?相信有不少小伙伴会说:将公用的方法抽取出来。没错,将公用的方法抽取出来是一种优化方式,但它不是最好的方式。最好的方式是啥?那就是使用 设计模式 啊!设计模式可是无数前辈不断实践而总结出的设计原则和设计模式。大家可以查看《设计模式汇总——你需要掌握的23种设计模式都在这儿了!》一文来学习设计模式专题。

2.使用设计模式优化代码

如何使用设计模式来优化上面的方法呢,大家继续往下看,对于设计模式不熟悉的同学可以先根据《设计模式汇总——你需要掌握的23种设计模式都在这儿了!》来学习。

我们先定义一个泛型接口MyPredicate,对传递过来的数据进行过滤,符合规则返回true,不符合规则返回false。

public interface MyPredicate<T> {

    /**
* 对传递过来的T类型的数据进行过滤
* 符合规则返回true,不符合规则返回false
*/
boolean filter(T t);
}

接下来,我们创建MyPredicate接口的实现类FilterEmployeeByAge来过滤年龄大于或者等于30的员工信息。

public class FilterEmployeeByAge implements MyPredicate<Employee> {
@Override
public boolean filter(Employee employee) {
return employee.getAge() >= 30;
}
}

我们定义一个过滤员工信息的方法,此时传递的参数不仅有员工的信息集合,同时还有一个我们定义的接口实例,在遍历员工集合时将符合过滤条件的员工信息返回。

//优化方式一
public List<Employee> filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate){
List<Employee> employees = new ArrayList<>();
for(Employee e : list){
if(myPredicate.filter(e)){
employees.add(e);
}
}
return employees;
}

接下来,我们写一个测试方法来测试优化后的代码。

@Test
public void test4(){
List<Employee> employeeList = this.filterEmployee(this.employees, new FilterEmployeeByAge());
for (Employee e : employeeList){
System.out.println(e);
}
}

运行test4()方法,输出的结果信息如下所示。

Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)

写到这里,大家是否有一种豁然开朗的感觉呢?

没错,这就是设计模式的魅力,对于设计模式不熟悉的小伙伴,一定要参照《设计模式汇总——你需要掌握的23种设计模式都在这儿了!》来学习。

我们继续获取当前公司中工资大于或者等于5000的员工信息,此时,我们只需要创建一个FilterEmployeeBySalary类实现MyPredicate接口,如下所示。

public class FilterEmployeeBySalary implements MyPredicate<Employee>{
@Override
public boolean filter(Employee employee) {
return employee.getSalary() >= 5000;
}
}

接下来,就可以直接写测试方法了,在测试方法中继续调用filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate)方法。

@Test
public void test5(){
List<Employee> employeeList = this.filterEmployee(this.employees, new FilterEmployeeBySalary());
for (Employee e : employeeList){
System.out.println(e);
}
}

运行test5方法,输出的结果信息如下所示。

Employee(name=张三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
Employee(name=赵六, age=16, salary=7777.77)

可以看到,使用设计模式对代码进行优化后,无论过滤员工信息的需求如何变化,我们只需要创建MyPredicate接口的实现类来实现具体的过滤逻辑,然后在测试方法中调用filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate)方法将员工集合和过滤规则传入即可。

这里,问大家一个问题:上面优化代码使用的设计模式是哪种设计模式呢?如果是你,你会想到使用设计模式来优化自己的代码吗?小伙伴们自己先思考一下到底使用的设计模式是什么?文末我会给出答案!

使用设计模式优化代码也有不好的地方:每次定义一个过滤策略的时候,我们都要单独创建一个过滤类!!

3.匿名内部类

那使用匿名内部类是不是能够优化我们书写的代码呢,接下来,我们就使用匿名内部类来实现对员工信息的过滤。先来看过滤年龄大于或者等于30的员工信息。

@Test
public void test6(){
List<Employee> employeeList = this.filterEmployee(this.employees, new MyPredicate<Employee>() {
@Override
public boolean filter(Employee employee) {
return employee.getAge() >= 30;
}
});
for (Employee e : employeeList){
System.out.println(e);
}
}

运行test6方法,输出如下结果信息。

Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)

再实现过滤工资大于或者等于5000的员工信息,如下所示。

@Test
public void test7(){
List<Employee> employeeList = this.filterEmployee(this.employees, new MyPredicate<Employee>() {
@Override
public boolean filter(Employee employee) {
return employee.getSalary() >= 5000;
}
});
for (Employee e : employeeList){
System.out.println(e);
}
}

运行test7方法,输出如下结果信息。

Employee(name=张三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
Employee(name=赵六, age=16, salary=7777.77)

匿名内部类看起来比常规遍历集合的方法要简单些,并且将使用设计模式优化代码时,每次创建一个类来实现过滤规则写到了匿名内部类中,使得代码进一步简化了。

但是,使用匿名内部类代码的可读性不高,并且冗余代码也比较多!!

那还有没有更加简化的方式呢?

4.重头戏:Lambda表达式

在使用Lambda表达式时,我们还是要调用之前写的filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate)方法。

注意看,获取年龄大于或者等于30的员工信息。

@Test
public void test8(){
filterEmployee(this.employees, (e) -> e.getAge() >= 30).forEach(System.out::println);
}

看到没,使用Lambda表达式只需要一行代码就完成了员工信息的过滤和输出。是不是很6呢。

运行test8方法,输出如下的结果信息。

Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)

再来看使用Lambda表达式来获取工资大于或者等于5000的员工信息,如下所示。

@Test
public void test9(){
filterEmployee(this.employees, (e) -> e.getSalary() >= 5000).forEach(System.out::println);
}

没错,使用Lambda表达式,又是一行代码就搞定了!!

运行test9方法,输出如下的结果信息。

Employee(name=张三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
Employee(name=赵六, age=16, salary=7777.77)

另外,使用Lambda表达式时,只需要给出需要过滤的集合,我们就能够实现从集合中过滤指定规则的元素,并输出结果信息。

5.重头戏:Stream API

使用Lambda表达式结合Stream API,只要给出相应的集合,我们就可以完成对集合的各种过滤并输出结果信息。

例如,此时只要有一个employees集合,我们使用Lambda表达式来获取工资大于或者等于5000的员工信息。

@Test
public void test10(){
employees.stream().filter((e) -> e.getSalary() >= 5000).forEach(System.out::println);
}

没错,只给出一个集合,使用Lambda表达式和Stream API,一行代码就能够过滤出想要的元素并进行输出。

运行test10方法,输出如下的结果信息。

Employee(name=张三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)
Employee(name=王五, age=60, salary=6666.66)
Employee(name=赵六, age=16, salary=7777.77)

如果我们只想要获取前两个员工的信息呢?其实也很简单,如下所示。

@Test
public void test11(){
employees.stream().filter((e) -> e.getSalary() >= 5000).limit(2).forEach(System.out::println);
}

可以看到,我们在代码中添加了limit(2)来限制只获取两个员工信息。运行test11方法,输出如下的结果信息。

Employee(name=张三, age=18, salary=9999.99)
Employee(name=李四, age=38, salary=5555.55)

使用Lambda表达式和Stream API也可以获取指定的字段信息,例如获取工资大于或者等于5000的员工姓名。

@Test
public void test12(){
employees.stream().filter((e) -> e.getSalary() >= 5000).map(Employee::getName).forEach(System.out::println);
}

可以看到,使用map过滤出了工资大于或者等于5000的员工姓名。运行test12方法,输出如下的结果信息。

张三
李四
王五
赵六

是不是很简单呢?

最后,给出文中使用的设计模式:策略模式。

写在最后

如果觉得文章对你有点帮助,请微信搜索并关注「 冰河技术 」微信公众号,跟冰河学习Java8新特性。

最后,附上Java8新特性核心知识图,祝大家在学习Java8新特性时少走弯路。

【Java8新特性】你知道Java8为什么要引入Lambda表达式吗?的更多相关文章

  1. 【Java8新特性】关于Java8的Stream API,看这一篇就够了!!

    写在前面 Java8中有两大最为重要的改变.第一个是 Lambda 表达式:另外一个则是 Stream API(java.util.stream.*) ,那什么是Stream API呢?Java8中的 ...

  2. 【Java8新特性】关于Java8中的日期时间API,你需要掌握这些!!

    写在前面 Java8之前的日期和时间API,存在一些问题,比如:线程安全的问题,跨年的问题等等.这些问题都在Hava8中的日期和时间API中得到了解决,而且Java8中的日期和时间API更加强大.立志 ...

  3. JAVA8初探-让方法参数具备行为能力并引入Lambda表达式

    关于JAVA8学习的意义先来贴一下某网站上的对它的简单介绍:“Java 8可谓Java语言历史上变化最大的一个版本,其承诺要调整Java编程向着函数式风格迈进,这有助于编写出更为简洁.表达力更强,并且 ...

  4. 【java8新特性】01:函数式编程及Lambda入门

    我们首先需要先了解什么是函数式编程.函数式编程是一种结构化编程范式.类似于数学函数.它关注的重点在于数据操作.或者说它所提倡的思想是做什么,而不是如何去做. 自Jdk8中开始.它也支持函数式编程.函数 ...

  5. java8新特性全面解析

    在Java Code Geeks上有大量的关于Java 8 的教程了,像玩转Java 8--lambda与并发,Java 8 Date Time API 教程: LocalDateTime和在Java ...

  6. Java8新特性 1——利用流和Lambda表达式操作集合

    Java8中可以用简洁的代码来操作集合,比如List,Map,他们的实现ArrayList.以此来实现Java8的充分利用CPU的目标. 流和Lambda表达式都是Java8中的新特性.流可以实现对集 ...

  7. Java8新特性

    Java8新特性 Java8主要的新特性涵盖:函数式接口.Lambda 表达式.集合的流式操作.注解的更新.安全性的增强.IO\NIO 的改进.完善的全球化功能等. 1.函数式接口 Java 8 引入 ...

  8. Java系列 - 用Java8新特性进行Java开发太爽了

    本人博客文章网址:https://www.peretang.com/using-java8s-new-features-to-coding-is-awesome/ 前言 从开始写博客到现在已经过去3个 ...

  9. Java8 新特性之Stream----java.util.stream

    这个包主要提供元素的streams函数操作,比如对collections的map,reduce. 例如: int sum = widgets.stream() .filter(b -> b.ge ...

  10. 这可能是史上最好的 Java8 新特性 Stream 流教程

    本文翻译自 https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ 作者: @Winterbe 欢迎关注个人微信公众 ...

随机推荐

  1. 使用Maven Archetype创建Java项目模板

    1.over view 简而言之,Archetype是一个Maven项目模板工具包.原型被定义为一种原始的模式或模型,所有其他同类的东西都是从中产生的.当我们试图提供一个提供生成Maven项目的一致方 ...

  2. 八、路由详细介绍之动态路由OSPF(重点)

    一.OSPF介绍 OSPF优点:无环路.收敛快.扩展性好.支持认证 二.工作原理: 图中RTA.RTB.RTC每个路由器都会生成一个LSA, 通过LSA泛洪进行互相发送相互学习,形成LSDB (链路状 ...

  3. hadoop(七)集群配置同步(hadoop完全分布式四)|9

    前置配置:rsync远程同步|xsync集群分发(hadoop完全分布式准备三)|9 1. 分布式集群分配原则 部署分配原则 说明Namenode和secondarynamenode占用内存较大,建议 ...

  4. 利用 Github 网络钩子实现自动化部署

    GitHub 的网络钩子(webhook)功能,可以很方便的实现自动化部署.本文记录了使用 Node.js 的开发部署过程,当项目的 master 分支被推时,将在服务器进行自动部署 添加网路钩子 在 ...

  5. R语言kohonen包主要函数介绍

    最近准备写一篇关于自组织映射 (Self-organizing map)的文章.SOM的代码很多,研究了一圈之后目前使用最顺手的是R语言的kohonen包. 这个kohonen包功能很丰富,但是接口不 ...

  6. Java匹马行天下之JavaSE核心技术——异常处理

    Java匹马行天下之JavaSE核心技术——异常处理 异常的简介 在Java中,异常就是Java在编译.运行或运行过程中出现的错误. 程序错误分为三种:编译错误.运行时错误和逻辑错误 编译错误是因为程 ...

  7. B2. Character Swap (Hard Version)

    链接: http://codeforces.com/contest/1243/problem/B2 题目大意: 两个字符串,判断能否通过交换为从而使得这两个字符串完全一致,如不可以的话,直接输出NO, ...

  8. Largest Rectangle in a Histogram 杭电1506

    题目链接 :http://acm.hdu.edu.cn/showproblem.php?pid=1506 Problem Description A histogram is a polygon co ...

  9. Laravel 分页 数据丢失问题解决

    问题: to do list 中有32条数据,每页10条,共3页. 做完了一个事项之后,准备打卡,发现找不到这个事项. 数据库查询正常,有这一条数据. 原因: 发现是分页出了问题,第1页的数据和第2页 ...

  10. JAVA快速排序代码实现

    通过一趟排序将要排序的数据分割成独立的两部分:分割点左边都是比它小的数,右边都是比它大的数.然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列. 快速 ...