1 概述

Java8据说是Java诞生以来最大的一次演进,说实话,对我个人来说没有什么特别大的感受,因为我学Java也就最近一两年的事,Java8在2014年3月18日发布,新增的特性确实非常惊艳,在语言特性层面上新增了lambda,Optional,默认方法,Stream API等,在虚拟机层面上新增了G1收集器(不过在Java9之后才改为默认的垃圾收集器)......

我个人认为Java8和语言相关的几个最重要的特性是如下几个:

lambda表达式和方法引用(其实是lambda表达式的一种特例)

Stream API

接口的默认方法

Optinal

CompletableFuture

本系列文章的后面几篇文章会围绕这几个主题来展开,今天就先上个开胃菜,lambda表达式!

2 什么是lambda表达式

lambda表达式也叫做匿名函数,其基于著名的λ演算得名,关于λ演算,推荐大家去找找关于“丘奇数”相关的资料。Java一直被人诟病的一点就是“啰嗦”,通常为了实现一个小功能,就不得不编写大量的代码,而用其他的语言例如Python等,也许寥寥几行代码就解决了,但支持lambda表达式之后,这一情况得到了大大的改善,现在只要使用得当,可以大大缩减代码里,使代码的目的更加清晰,易读,纯粹。

在Java中,很多时候在使用一些API的时候,必须要给出一些接口的实现,但因为该实现其实也就用一次,专门去创建一个新的实现类并不划算,所以一般大多数人采取的措施应该是创建一个匿名实现类,比较典型就是Collections.sort(List list, Comparator super T> c)方法,该方法接受一个Comparator类型的参数,Comparator是一个接口,表示“比较器”,如果要使用该方法对集合元素进行排序,就必须提供一个Comparator接口的实现,否则无法通过编译。如下所示:

Collections.sort(numbers, new Comparator() {

@Override

public int compare(Integer o1, Integer o2) {

return o1.compareTo(o2);

}

});

其实这个实现类的核心只有一行,即return o1.compareTo(o2);但我们却不得不编写其他“啰嗦”的代码,如果使用lambda表达式,会是怎么个样子呢?

Collections.sort(numbers, (n1, n2) -> n1.compareTo(n2));

没错,就是那么简单粗暴,就是一行核心代码。其他的比如方法签名啥的统统可以省略了,不仅简洁,而且语义也更加清晰,读起来就好像是说:“sort方法,帮我吧numbers这个序列排个序,排序规则就按照n1.compareTo(n2)的返回值来决定”。现在,是不是感觉,写代码就像在和计算机对话一样简单?但(n1, n2) -> n1.compareTo(n2)这玩意是个什么鬼?还带个箭头?不用着急,下面马上介绍lambda表达式的语法。

2.1 lambda表达式的语法

第一部分是lambda的参数列表,因为Comparator.compare()方法接受两个参数,所以这里给出两个参数n1和n2,可以省略具体的类型,Java编译器会自动推断。

第二部分是箭头,没什么特殊的地方,只是Java语言觉得使用这个,各个语言的实现也不太一样,例如Python是:号,简单理解的就当是把参数列表和函数主体分开的东西吧。

第三部分就是函数主体,也就是真正执行逻辑的地方。

如果函数主体仅仅包含一行代码,可以省略花括号{}和return关键字(如果有的话)。对于我们的例子,可以改写成这样:

Collections.sort(numbers, (n1, n2) -> {return n1.compareTo(n2);});

注意分号!因为此时return n1.compareTo(n2);就是一条普通的Java语句了,必须遵守Java的语法规则。好了,尽管我们现在明白了lambda语句的语法规则,但还有一个关键的问题,就是为什么要这样写,换句话说,为什么要有俩参数,这return又是几个意思?还有到底哪里才可以使用lambda表达式?说到这,就不得不说一下和lambda息息相关的东西了:函数式接口。

3 函数式接口

函数式接口是这样的:只有一个抽象方法的接口就是函数式接口。为什么要特别强调抽象方法呢?Java接口里声明的方法不都是抽象方法吗?在Java8之前,这么说确实没有任何问题,但Java8新增了接口的默认方法,可以在接口里给出方法的具体实现,这里先不多说,后面的文章会详细讨论这个东西。

lambda表达式仅可以用在函数式接口上,我们在上面遇到的Comparator就是一个函数式接口,他只有一个抽象方法:compare(),其方法签名是这样的:

int compare(T o1, T o2);

现在来看看 (n1, n2) -> n1.compareTo(n2)这个表达式,是不是发现了什么?没错,其实lambda表达式的参数列表就是对应的函数式接口的抽象方法的参数列表,并且类型可以省略(编译器自动推断),然后n1.compareTo(n2)的返回值是int类型,也符合compare()的方法描述。这样就算是把lambda表达式和接口的抽象方法签名匹配成功了,不会出现编译错误。

除此之外,Runnable也是一个函数式接口,它只有一个抽象方法,即run(),run()方法的方法签名如下所示:

public abstract void run();

不接受任何参数,也没有返回值。那如果要编写对应的lambda表达式,该如何做呢?其实非常简单,下面是一个示例:

Runnable r = () -> {

System.out.println(Thread.currentThread().getName());

//do something

};

如果观察仔细的话,会发现,示例代码中把这个lambda表达式赋值给了Runnable类型的变量r!经过上面的讨论,我们知道,其实lambda就是一个方法实现(其实叫做函数会更加合适),这条赋值语句看起来就好像是再说:“把方法(函数)赋值给变量!”。如果没有接触过函数式编程,会觉得这样很奇怪,怎么能把方法赋值给变量呢?计算机就是这样有意思,总是有各种各样奇奇怪怪的东西冲击我们的思维!那这有什么用呢?咱先不说什么高阶函数,科里化啥的(这些是函数式编程里的概念),就说一点:意味着我们可以把方法(函数)当做变量来使用!即现在方法就是Java世界里的“一等公民”了!既可以将其作为参数传递给其他方法(函数),还可以将其作为其他方法(函数)的返回值(以后会讲到具体的案例)

4 策略模式

策略模式是著名的23种设计模式中的一种,关于它的描述,我这里就不多说了。直接来看个例子吧。

例子是这样的,现在有一个代表汽车的Car类以及一个Car列表,现在我们想要筛选列表中符合要求的汽车,为了应对多变的筛选方法,我们打算用策略模式来实现功能。

下面是Car类的代码:

public class Car {

//品牌

private String brand;

//颜色

private Color color;

//车龄

private Integer age;

//三个参数的构造函数以及setter和getter

//颜色的枚举

public enum Color {

RED,WHITE,PINK,BLACK,BLUE;

}

}

//包含Car对象的列表

List cars = Arrays.asList(

new Car("BWM",Car.Color.BLACK, 2),

new Car("Tesla", Car.Color.WHITE, 1),

new Car("BENZ", Car.Color.RED, 3),

new Car("Maserati", Car.Color.BLACK,1),

new Car("Audi", Car.Color.PINK, 5));

我们希望用一个方法来封装筛选的逻辑,其方法签名伪代码如下所示:

cars carFilter(cars, filterStrategy);

接下来实现策略模式,下面是相关的代码:

public interface CarFilterStrategy {

boolean filter(Car car);

}

public class BWMCarFilterStrategy implements CarFilterStrategy {

@Override

public boolean filter(Car car) {

return "BWM".equals(car.getBrand());

}

}

public class RedColorCarFilterStrategy implements CarFilterStrategy {

@Override

public boolean filter(Car car) {

return Car.Color.RED.equals(car.getColor());

}

}

为了简单,仅仅实现了两种筛选策略,第一种是删选出品牌是“BWM”的汽车,第二种是删选出颜色为红色的汽车。最后来实现carFilter方法,如下所示:

private static List carFilter(List cars, CarFilterStrategy strategy) {

List filteredCars = new ArrayList<>();

for (Car car : cars) {

if (strategy.filter(car)) {

filteredCars.add(car);

}

}

return filteredCars;

}

最后的最后是测试代码:

public static void main(String[] args) {

System.out.println(carFilter(cars, new BWMCarFilterStrategy()));

System.out.println("----------------------------------------");

System.out.println(carFilter(cars, new RedColorCarFilterStrategy()));

}

分别实例化两个策略,将其作为参数传递给carFilter()方法,最终的输出如下所示:

[Car{brand='BWM', color=BLACK, age=2}]

----------------------------------------

[Car{brand='BENZ', color=RED, age=3}]

确实符合预期。是不是就到此为止了呢?当然不!我们发现,其实BWMCarFilterStrategy以及RedColorCarFilterStrategy的实现代码都非常简单,仅仅寥寥几行代码,而且CarFilterStrategy接口仅仅有一个filter抽象方法,显然是一个函数式接口,那我们能不能用lambda表达式来简化呢?答案是:完全可以!而且更加推荐用lambda表达式来简化这种情况。

4.1 用lambda表达式来简化代码

只要略微做一些修改就行了:

System.out.println(carFilter(cars, car -> "BWM".equals(car.getBrand())));

System.out.println("----------------------------------------");

System.out.println(carFilter(cars, car -> Car.Color.RED.equals(car.getColor())));

这里不再使用BWMCarFilterStrategy以及RedColorCarFilterStrategy两个类了,直接用lambda表达式就行了!最后把这俩实现删除掉!是不是顿时感觉整个项目的代码清爽了许多?

4.2 需要注意的

其实本小节的例子有些过于特殊了,如果你项目中的策略模式的实现非常复杂,其策略不是简简单单的几行代码就能解决的,此时要么进一步封装代码,要么就最好不要用lambda表达式了,因为如果逻辑复杂的话,强行使用lambda不仅仅不能简化代码,反而会使得代码更加晦涩。

5 方法引用

最后简单讲一下方法引用吧,方法引用其实是lambda表达式的一种特殊情况的表示,语法规则是:

:

如果lambda表达式的主体逻辑仅仅是一个调用方法的语句的话,那么就可以将其转换为方法引用,如下所示:

//普通的lambda表达式

numbers.forEach(n -> System.out.println(n));

//转换成方法引用

numbers.forEach(System.out::println);

他俩效果是完全一样的,但显然方法引用更加简洁,语义也更加明确了,这一语法糖“真香!”。具体的我就不多说了,建议看看《Java8 实战》一书,里面有非常非常详细的介绍。

6 小结

本文简单介绍了lambda表达式的语法以及使用。lambda表达式确实能大大简化原本复杂啰嗦的Java代码,而且更加灵活,语义也更加清晰明了,写代码的时候就好像用自然语言和计算机对话一样!但也不是哪里都能使用的,一个最基本的要求就是:其放置的位置要对应着一个函数式接口。函数式接口即只有一个抽象方法的接口,例如Comparator,Runnable等。除此之外,使用lambda表达式的时候,其主体逻辑最好不要超过10行,否则最好还是换一种方式来实现,这里10行并不是那么严格,具体情况还要具体分析。方法引用是一种特殊情况下的lambda表达式的表示方法,可以理解为是lambda的一个语法糖,其语义更加明确,语法也更加简洁,用起来还是非常舒服的!

最后,作为一个补充,来简单看看JDK内置的一些通用性比较强的函数式接口,这些接口都在java.util.function包下,我没数过,咋一看估计得有40多个吧。常用的有Function,Predicate,Consumer,Supplier等。Function的抽象方法的方法签名如下所示:

R apply(T t); //T,R是泛型

简单从语义上来看,就是传入一个T类型的值,然后apply函数将其转换成R类型的值,即一对一映射。其他的接口就不做介绍了。

7 参考资料

《Java8 实战》

java se基础巩固实例,Java SE基础巩固(十五):lambda表达式相关推荐

  1. java语言程序设计基础篇课后答案_《Java语言程序设计:基础篇》课后复习题答案-第十五章.pdf...

    <Java语言程序设计:基础篇>课后复习题答案-第十五章 Chapter15Graphics 1. Theycoordinateshouldincreaseandthexcoordinat ...

  2. Java从入门到精通十四(Lambda表达式)

    Java从入门到精通十四(Lambda表达式) Lambda的引入体验 实例一(抽象方法无参无返回值) 实例二(抽线方法有参无返回值) 实例三(抽象方法带参带返回值) lambda的表达式的简化操作 ...

  3. java基础语法实例教程_Java 基础语法

    一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作 面向对象中的一些概念 下表列出了 面向对象 编程中的一些概念 名词 说明 对象 对象是类的一个实例,有状态和行为 ...

  4. java 文件路径表达式_Java基础(二十二) Lambda表达式和File类

    函数式编程思想 强调的是做什么,而不是以什么样的方式来做,它忽略了面向对象的复杂语法,只要能够获取到结果,谁去做的,怎么做的,都不重要,重要的是结果,不重视过程. 冗余的Runnable代码 传统的写 ...

  5. java写exe程序实例,java实现可安装的exe程序实例详解

    java实现可安装的exe程序实例详解 通过编写java代码,实现可安装的exe文件的一般思路: 1.在eclipse中创建java项目,然后编写java代码,将编写好的java项目导出一个.jar格 ...

  6. matlab基础与实例教程,MATLAB基础与实例教程

    系统全面,实例丰富 考虑到Matlab进行仿真和运算分析时的基础知识和实践操作,讲解从基础的变量.函数.数据类型等入手,涉及到数学分析.图形可视化.Simulink仿真.文件读写等,全面地介绍了Mat ...

  7. Java 8:在新的Nashorn JS引擎中编译Lambda表达式

    在最近的一篇文章中,我了解了Java 8和Scala如何实现Lambda表达式. 众所周知,Java 8不仅引入了对Javac编译器的改进,而且还引入了全新的解决方案-Nashorn. 这个新引擎旨在 ...

  8. Java JVM 动态方法调用指令 invokedynamic 实现分析(以 Lambda 表达式实现原理为例)...

    一.前言 对于 invokedynamic 指令的实现需要方法句柄作为前提知识点.可参考 Java JVM 动态方法调用之方法句柄 MethodHandle. 本文以 Lambda 表达式中运用 in ...

  9. lambda表达式java项目常用_一文带你彻底搞懂Lambda表达式

    1. 为什么使用Lambda表达式 Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递).可以写出更简洁.更灵活的代码.作为一种更紧凑的代码风 ...

最新文章

  1. 在Ubuntu下FFmpeg编译,支持x264和x265(HECV)
  2. 猴子请来的逗比项目流水总结
  3. 使用pdf.js预览实现读取服务器外部文件
  4. [ARM异常]-ARMV8-aarch64 异常(中断)是如何跳转到向量表的
  5. python教程:函数递归与生成器教程
  6. jvm 06-G1收集器
  7. 量化指标公式源码_通达信指标公式源码线上阴线指标公式
  8. hive2 java连接_用Java代码通过JDBC连接Hiveserver2
  9. 免费zblog mip主题aymFreeTwo
  10. 2021年CBA总决赛第二场预测
  11. Linux工作笔记022---查看Centos 内核版本号
  12. nginx开启目录浏览,解决中文乱码问题
  13. layer——极简的jquery弹出层插件
  14. iOS中的所有字体和UILabel
  15. gqview的安装与汉化
  16. XMPP即时通讯协议使用(前传)——协议详解
  17. 如何清空c盘只剩系统_怎么把C盘东西都删除只留系统东西
  18. 不使用vue-cli 搭建vue项目
  19. ENVI操作:监督分类
  20. win10 wifi图标不见了 修复办法

热门文章

  1. Hive,Hbase shell 中文变问号(??) 的解决方法
  2. linux 下执行python.py 无效解决方案
  3. 【实践】58同城本地服务推荐系统演进
  4. 飞猪信息流内容推荐探索
  5. 【报告分享】2020年上半年中国直播电商行业发展分析报告.pdf(附下载链接)...
  6. 全球首发!惯性导航导论(剑桥大学)第十一部分
  7. oracle hcm 发展,甲骨文发布Oracle HCM Cloud云服务 呈现三大亮点
  8. cdh mysql sqoop 驱动_[bigdata-003]在cdh 5.7下 用sqoop1将mysql数据库数据导入到hdfs的方式...
  9. 6大Facebook广告文案绝招提升你Shopify独立站的转化率
  10. linux shell解析1