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)

目录
相关文章
|
Kubernetes Java 测试技术
无忧微服务:如何实现大流量下新版本的发布自由
本文讨论了微服务上云过程中的稳定性挑战,特别是变更引起的生产故障。阿里云MSE(微服务引擎)提供了一种全链路无损发布方案,旨在消除变更风险,实现白天流量高峰时的安全发布。
1167 115
|
机器学习/深度学习 人工智能 自然语言处理
机器学习之线性回归与逻辑回归【完整房价预测和鸢尾花分类代码解释】
机器学习之线性回归与逻辑回归【完整房价预测和鸢尾花分类代码解释】
|
关系型数据库 MySQL Linux
centos7安装openssh9.0p1
centos7安装openssh9.0p1
1610 0
|
4月前
|
存储 机器学习/深度学习 安全
阿里云服务器4核8G价格参考:最新收费标准、可选实例规格与活动价格参考
阿里云服务器4核8G配置目前有计算型 c6、AMD 计算型 c6a、计算平衡增强型 c6e等多种实例规格可选,目前在阿里云的活动中4核8G配置的云服务器经济型e、通用算力型u1、计算型c8i、计算型c9i和计算型c8y实例可选,选择不同实例规格和带宽价格不一样,本文为大家介绍阿里云服务器4核8G配置的最新月付及年付活动价格,以及选择参考。
|
6月前
计算网络号的直接方法
子网掩码用于区分IP地址中的网络部分和主机部分,连续的“1”表示网络位,“0”表示主机位。例如,255.255.255.0 的二进制为 11111111.11111111.11111111.00000000,前24位是网络部分。通过子网掩码可提取网络号,如 IP 192.168.1.10 与子网掩码 255.255.255.0 的网络号为 192.168.1.0。此外,文档还介绍了十进制与二进制间的转换方法,帮助理解IP地址的组成与计算。
446 11
|
Devops 持续交付 开发者
传统软件开发与 DevOps 方法之间的差异
【8月更文挑战第24天】
347 0
|
11月前
|
数据采集 安全 定位技术
使用代理IP爬虫时数据不完整的原因探讨
在信息化时代,互联网成为生活的重要部分。使用HTTP代理爬取数据时,可能会遇到失败情况,如代理IP失效、速度慢、目标网站策略、请求频率过高、地理位置不当、网络连接问题、代理配置错误和目标网站内容变化等。解决方法包括更换代理IP、调整请求频率、检查配置及目标网站变化。
235 11
|
SQL 关系型数据库 MySQL
MySQL查询(万字超详细版)
本文详细介绍了数据库中的单表和多表查询方法。首先,单表查询包括全列查询、指定列查询及去重查询,其中应避免使用`*`以提高效率。接着,文章讲解了排序查询,包括升序和降序,并展示了如何通过多个字段进行排序。在多表查询部分,本文解释了内连接、外连接(左外连接和右外连接)以及自连接的概念和用法,提供了丰富的代码示例
494 2
MySQL查询(万字超详细版)
|
存储
ceph集群存储池的资源配额
这篇文章讲解了如何在Ceph集群中为存储池设置资源配额,包括创建存储池、查看和设置存储池的最大对象数量和最大存储容量的限制。
288 2