lambda表达式

简介: 什么时候使用lambda表达式lambda表达式定义lambda表达式语法lambda表达式理解lambda表达式使用lambda表达式的泛型使用什么时候使用lambda表达式如果一段代码需要延迟执行,就可以使用lambda表达式,如在另外的线程运行的一段代码、需要在某个时间点运行的代码、某个条件触发回调的代码等。lambda

什么时候使用lambda表达式

如果一段代码需要延迟执行,就可以使用lambda表达式,如在另外的线程运行的一段代码、需要在某个时间点运行的代码、某个条件触发回调的代码等。

lambda表达式定义

lambda表达式是一段可以传递的、可以执行的代码。之所以叫这个名字是因为lambda表达式是一个带参数变量的表达式,而在数学里面带参数的表达式就是“λ”;

lambda表达式语法

(参数)->表达式 (1)
参数为输入,表达式负责计算结果。如果需要进行的计算无法用一个表达式来表示,那么可以编写代码并用一对大括号“{代码段}”包裹起来。即为
(参数)->{表达式} (2)

(1)形式的lambda表达式不需要返回结果,因为它的结果可以从上下文中推到出来,如:

Comparator<String> comparator = (String first, String second)->Integer.compare(first.length(), second.length());

可以推倒出该表达式可以被使用在返回结果为int的上下文中。而Comparator接口有且仅有一个抽象方法:

int compare(T o1, T o2);

之所以要强调Comparator只包含一个抽象方法接口,是因为我们使用lambda表达式只能创建函数式接口的对象。那什么是函数式接口?对了,就是只包含一个抽象方法的接口。函数式接口的转换是我们在java中使用lambda表达式唯一能够做的事情。

lambda表达式的推导特性除了反应在对结果的推导上外,还有对变量类型的推导,如上面的代码也可以写成:

Comparator<String> comparator = ( first,  second)->Integer.compare(first.length(), second.length());

lambda表达式真的很灵活,如果要为lambda表达式指定返回结果,可以用(2)类型的lambda表达式。

Comparator<String> comparator = ( first,  second)->{
            if(first.length()<second.length()) return -1;
            else if(first.length()>second.length()) return 1;
            else return 0;
        };

但此时要注意,在lambda表达式中,只有某些分支有返回值,而其它分支没有返回值是错误的。如:

Comparator<String> comparator = ( first,  second)
->{
if(first.length()<second.length()) return -1;
};

以上讨论的lambda表达式都是自己写的实现方法,实际编程中,我们很可能需要在lambda表达式中调用其它已经实现的方法。如我们要不区分大小写的字符串进行排序,那么可以这样使用:

Arrays.sort(words,String::compareToIgnoreCase);

上面这个示例代码与下面这段是等价的

Arrays.sort(words,(String first,String second)->first.compareToIgnoreCase(second));

这就是lambda表达式的方法引用,方法引用用“::”符号做分割,方法引用有三种使用形式:

对象::实例方法
类::静态方法
类::实例方法

对于前两种形式,方法引用等同于提供方法参数的lambda表达式,第三种形式中,第一个参数会成为执行的方法对象。分别举三个例子对应上面三种形式

对象::实例方法
Button button = new Button();
button.setOnAction(System.out::println);
button.setOnAction(event -> System.out.println(event));
类::静态方法
(x,y)->Math.pow(x,y)
Math::pow
类::实例方法
String::compareToIgnoreCase
(String first,String second)->first.compareToIgnoreCase(second)

lambda表达式理解

经常有同学拿lambda表达式和java的内部类做比较,但lambda表达式的执行效率会比内部类的方式更高。如当我们使用:

Arrays.sort(words,(String first,String second)->Integer.compare(first.length(), second.length()));

Arrays.sort接收一个实现了Comparator接口的实例,会调用该实例的compare方法,然后执行lambda表达式的代码,这些对象和类的管理完全依赖于如何实现。所以建议把lambda表达式想象为一个函数,而不是一个对象,但要记住它可以转换为一个函数式接口。正是由于lambda表达式可以转换为一个函数式接口,这使得在JAVA中不能声明像(String,String)->int这样的函数类型,因为它无法转换为一个函数式接口,我们需要使用像comparator<String>这样的函数式接口来声明函数的目的。java设计者坚持使用了接口的概念,没有将函数类型引入到Java中。

lambda表达式使用

要接受一个lambda表达式需要选择一个函数式接口,这个接口的选择是根据我们的需求而定。Java8中已经定义了很多函数式接口,在java.util.function包中,函数式接口一般会用@FunctionalInterface注解,使用这个注解有两个好处,首先编译器会检查标注该注解的实体,检测该接口是否只包含一个抽象方法,因为这样的接口才是函数式接口,其次在javadoc页面也会有一条声明,说明这个接口是函数式接口。

举个例子,方法repate是反复多次执行一个action,如果这个action执行的过程中需要一些参数的传入,那我们可以选择IntConsumer函数接口,如果不需要传入参数,我们可以选择Runnable函数接口,具体代码如下

public static void repeat(int n, IntConsumer action){
for (int i = 0; i < n; i++) {
action.accept(i);
}
}

repeat(3,i->System.out.println("循环次数:"+i));

public static void repeat(int n, Runnable action){
for (int i = 0; i < n; i++) {
action.run();
}
}

repeat(3,()->System.out.println("hello world"));

输出结果:

循环次数:0
循环次数:1
循环次数:2
hello world
hello world
hello world

lambda表达式的泛型使用

在以前使用java泛型的时候,由于类型擦除,我们无法建立一个泛型数组。这使得我们在调用Stream<T>类的toArray方法时无法调用T[] result = new T[n]。这个时候我们可以使用lambda表达式传入构造函数。

List<String> list = new ArrayList<>();
list.add("a");
list.add("bc");
list.add("def");
list.add("ghij");
list.add("kmlno");
list.add("pqrstu");
String[] result = list.stream().toArray(String[]::new);
//下面这一句跟上面等价
//String[] result = list.stream().toArray(n->new String[n]);

这样在调用过程中,数组长度可以被当作参数传入,然后建立一个长度为n的String数组。

除了以上简单的应用外,泛型应用还有复杂的形式。如

 * @param <T> the type of the input to the function
 * @param <R> the type of the result of the function
 * @since 1.8
 */
@FunctionalInterface
public interface Function<T, R> {
...
}

举例来说,employee是person的子类,如果我们有Function<Person,employee>,那么可以把它传递给以Function<employee,Person>作为参数的方法。虽然我们的函数可以处理Person实例,但是它们只会用employee对象调用它,我们期望函数返回Person实例,但是它实际上返回了更好的结果(employee)。所以在函数类型中有一个一般准则,父类作为参数类型,子类作为返回类型。根据这个准则如果要实现一个接受泛型的函数式表达式的方法,只需在不是返回类型的参数类型使用<? super>,在不是参数类型的返回类型上使用<? extend>。如在Stream<String>的forEach方法。我们可以把一个Consumer<Object>传递给该方法。因为如果Consumer<Object>能够处理任何对象,那么它也一定可以处理字符串。

//Stream的forEach方法
void forEach(Consumer<? super T> action);

另外,当一个函数类型的参数和返回值都是一个类型的时候就没有必要使用泛型了,如T reduce(T identity, BinaryOperator<T> accumulator)

目录
相关文章
|
Web App开发 XML 前端开发
Selenium安装及八大元素定位方法&介绍及使用教程
Selenium是一个支持多种编程语言的自动化测试工具,用于Web应用的测试。它提供了多种元素定位策略,包括ID、Name、Class Name、Tag Name、Link Text、Partial Link Text、CSS Selector和XPath。安装Selenium需先确保Python和pip已安装,然后通过pip安装库,并下载对应浏览器的WebDriver。验证安装成功后,可通过编写简单脚本来打开网页并打印标题。注意WebDriver版本应与浏览器兼容,且可能需要额外的依赖包。文章还介绍了XPath的两种类型及其区别,推荐使用相对XPath以提高稳定性。
483 0
|
弹性计算
阿里云4核16G游戏专用服务器26元,就问你服不服?
阿里云4核16G游戏专用服务器26元,就问你服不服?
|
新零售 前端开发 JavaScript
盒马跨端设计系统 ReX Design For OS
6 个月前,在 D2 前端技术论上我们向社区分享了《盒马中后台跨端方案》,详细介绍了我们在基于盒马实体零售数字化业务场景中,面向盒马营运数字化系统构建的跨端设计系统——ReX Design For OS。6 个月来,在开发资源极度紧张的状态下,我们持续建设和打磨,今天我们很高兴的告诉大家我们已经将项目的主要代码开源到了 Github,并发布了 beta 版本。
盒马跨端设计系统 ReX Design For OS
|
Devops 持续交付 开发者
传统软件开发与 DevOps 方法之间的差异
【8月更文挑战第24天】
259 0
|
SQL 关系型数据库 MySQL
MySQL查询(万字超详细版)
本文详细介绍了数据库中的单表和多表查询方法。首先,单表查询包括全列查询、指定列查询及去重查询,其中应避免使用`*`以提高效率。接着,文章讲解了排序查询,包括升序和降序,并展示了如何通过多个字段进行排序。在多表查询部分,本文解释了内连接、外连接(左外连接和右外连接)以及自连接的概念和用法,提供了丰富的代码示例
392 2
MySQL查询(万字超详细版)
|
存储
ceph集群存储池的资源配额
这篇文章讲解了如何在Ceph集群中为存储池设置资源配额,包括创建存储池、查看和设置存储池的最大对象数量和最大存储容量的限制。
221 2
|
数据安全/隐私保护 智能硬件
智能家居系统入门指南
随着科技的飞速发展,智能家居系统已不再是遥不可及的梦想。本文将带你走进智能生活的世界,从基础概念到实用设备,再到搭建步骤和常见问题解答,全方位解析如何打造一个舒适、便捷、高效的智能居家环境。让我们一起探索,如何通过简单的操作,实现家居生活的智能化升级。
|
存储 弹性计算 对象存储
ECS快照原理
云盘快照原理包括全量和增量快照。首次快照为全量,备份所有数据块;后续快照仅备份变化部分。快照存储在OSS中,同城冗余或本地冗余根据地域不同而定。删除快照时,按数据块引用关系释放空间。快照容量基于快照链统计,全量快照加所有增量大小。快照不占用云盘空间,但产生存储费用。
|
文字识别 前端开发 JavaScript
Star33.1k!推荐一个基于网页的OCR(光学字符识别)引擎库
想要在前端解决图像识别的兄弟,可以到 Github 上下载Tesseract.js库,安装和相关学习文档都能下载到,实在获取不到的兄弟找V哥发给你,假期第二天,出去放松的同时也可以看看 V 哥的文章,祝大家玩得开心。
343 0
|
数据采集 存储 安全
登录态数据抓取:Python爬虫携带Cookie与Session的应用技巧
登录态数据抓取:Python爬虫携带Cookie与Session的应用技巧