除了保存代码行之外,lambda 表达式还有其他用途吗?

除了保存代码行之外,lambda 表达式还有其他用途吗?

Lambda 有没有什么特别的功能可以解决不容易解决的问题?我看到的典型用法是:

Comparator<Developer> byName = new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getName().compareTo(o2.getName());
}
};

我们可以使用 lambda 表达式来缩短代码:

Comparator<Developer> byName =
(Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName());
26513 次浏览

是的,有很多优势。

  • No need to define whole class we can pass implementation of function it self as reference.
    • 类的内部创建将创建。类文件,而如果你使用 lambda,那么编译器会避免创建类,因为在 lambda 中,你传递的是函数实现而不是类。
  • 代码的可重用性比以前更高
  • 就像你说的代码比正常的实现要短。

Saving lines of code can be viewed as a new feature, if it enables you to write a substantial chunk of logic in a shorter and clearer manner, which takes less time for others to read and understand.

如果没有 lambda 表达式(和/或方法引用) ,Stream管道的可读性就会大大降低。

例如,如果用匿名类实例替换每个 lambda 表达式,那么下面的 Stream管道看起来会是什么样子。

List<String> names =
people.stream()
.filter(p -> p.getAge() > 21)
.map(p -> p.getName())
.sorted((n1,n2) -> n1.compareToIgnoreCase(n2))
.collect(Collectors.toList());

这将是:

List<String> names =
people.stream()
.filter(new Predicate<Person>() {
@Override
public boolean test(Person p) {
return p.getAge() > 21;
}
})
.map(new Function<Person,String>() {
@Override
public String apply(Person p) {
return p.getName();
}
})
.sorted(new Comparator<String>() {
@Override
public int compare(String n1, String n2) {
return n1.compareToIgnoreCase(n2);
}
})
.collect(Collectors.toList());

这比使用 lambda 表达式的版本更难编写,而且更容易出错。这也很难理解。

这是一条相对较短的管道。

为了使这些内容在没有 lambda 表达式和方法引用的情况下具有可读性,您必须定义一些变量来保存这里使用的各种函数接口实例,这将分离管道的逻辑,使得它更加难以理解。

Lambdas 只是匿名类的语法糖。

在 lambdas 之前,可以使用匿名类来实现同样的功能。每个 lambda 表达式都可以转换为一个匿名类。

If you are using IntelliJ IDEA, it can do the conversion for you:

  • 把光标放到 Lambda 里
  • 按 alt/option + enter

enter image description here

内部迭代

在迭代 Java 集合时,大多数开发人员倾向于使用 走开元素,然后使用 process元素。这就是说,把那个东西拿出来,然后使用它,或者重新插入它,等等。对于 Java 的前8个版本,您可以实现一个内部类并执行以下操作:

numbers.forEach(new Consumer<Integer>() {
public void accept(Integer value) {
System.out.println(value);
}
});

Now with Java 8 you can do better and less verbose with:

numbers.forEach((Integer value) -> System.out.println(value));

或者更好

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

行为作为论据

猜猜下面的情况:

public int sumAllEven(List<Integer> numbers) {
int total = 0;


for (int number : numbers) {
if (number % 2 == 0) {
total += number;
}
}
return total;
}

With Java 8 预测接口 you can do better like so:

public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
int total = 0;


for (int number : numbers) {
if (p.test(number)) {
total += number;
}
}
return total;
}

这么说吧:

sumAll(numbers, n -> n % 2 == 0);

资料来源: DZone-Why We Need Lambda Expressions in Java DZone-Why We Need Lambda Expressions in Java

To answer your question, the matter of fact is lambdas 不要 let you do anything that you couldn’t do prior to java-8, rather it enables you to write more concise code. The benefits of this, is that your code will be clearer and more flexible.

Lambda 表达式不会改变一般 Java 可以解决的问题集,但肯定会使解决某些问题变得更容易,这与我们不再使用汇编语言编程的原因是一样的。从程序员的工作中移除冗余的任务使生活更加容易,并且允许做一些你在其他情况下根本不会碰到的事情,只是为了你必须生成(手动)的代码量。

但是 lambda 表达式不仅仅节省了代码行。Lambda 表达式允许您定义 功能,以前您可以使用匿名内部类作为解决方案,这就是为什么您可以在这些情况下替换匿名内部类,但是一般情况下不行。

最值得注意的是,lambda 表达式是独立于它们将被转换为的函数接口定义的,因此它们不能访问继承的成员,而且它们不能访问实现函数接口的类型的实例。在 lambda 表达式中,thissuper的含义与周围上下文中的含义相同,请参阅 这个答案。此外,您不能创建新的局部变量来隐藏周围上下文的局部变量。对于定义函数的预期任务,这会删除大量错误源,但是这也意味着对于其他用例,可能存在无法转换为 lambda 表达式的匿名内部类,即使实现了函数接口。

Further, the construct new Type() { … } guarantees to produce a new distinct instance (as new always does). Anonymous inner class instances always keep a reference to their outer instance if created in a non-static context¹. In contrast, lambda expressions only capture a reference to this when needed, i.e. if they access this or a non-static member. And they produce instances of an intentionally unspecified identity, which allows the implementation to decide at runtime whether to reuse existing instances (see also “Lambda 表达式是否在每次执行时都在堆上创建一个对象?”).

这些差异适用于您的示例。您的匿名内部类构造将始终生成一个新的实例,而且它可能捕获对外部实例的引用,而您的 (Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName())是一个非捕获 lambda 表达式,在典型的实现中将计算为单例。此外,它不会在您的硬盘上生成 .class文件。

考虑到在语义和性能方面的差异,lambda 表达式可能会改变程序员将来解决某些问题的方式,当然,这也是由于新的 API 包含了利用新语言特性进行函数式编程的思想。参见 Java8 lambda 表达式和一级值


1从 JDK 1.1到 JDK 17。从 JDK 18开始,如果不使用内部类,内部类可能不会保留对外部实例的引用。出于兼容性原因,这要求内部类不可序列化。这只适用于使用目标 JDK18或更新版本重新编译 JDK18或更新版本下的内部类。参见 JDK-8271717

使用 lambdas 代替内部类有很多好处,如下所示:

  • 在不引入更多语言语法语义的情况下,使代码更紧凑、更具表现力。你在问题中已经举了一个例子。

  • 通过使用 lambdas,您可以在元素流上使用函数式操作进行编程,例如在集合上进行 map-reduce 转换。请参阅 函数 & Util.stream软件包文档。

  • 编译器没有为 lambdas 生成物理类文件。因此,它使您交付的应用程序更小

  • 如果 lambda 不访问作用域外的变量,编译器将优化 lambda 的创建,这意味着 lambda 实例只能由 JVM 创建一次。要了解更多细节,你可以查看@Holger 对这个问题的回答 < a href = “ https://stackoverflow. com/questions/23983832/Is-method-reference-cached-a-good-idea-in-Java-8/23991339 # 23991339”> 在 Java 8中方法引用缓存是一个好主意吗? .

  • 除了函数接口,Lambdas 还可以实现多个标记接口,但是匿名内部类不能实现更多的接口,例如:

    //                 v--- create the lambda locally.
    Consumer<Integer> action = (Consumer<Integer> & Serializable) it -> {/*TODO*/};
    

有一件事我还没有看到提到,是一个 lambda 允许您定义在哪里使用它的功能

所以,如果你有一些简单的选择函数,你不需要把它放在一个单独的地方与一堆样板,你只需要写一个简洁和本地相关的 lambda。

Programming languages are not for machines to execute.

他们是为程序员的 好好想想在。

语言是与编译器的对话,将我们的思想转化为机器可以执行的东西。来自其他语言(或者 离开为了其他语言)的人对 Java 的主要抱怨之一是,它过去是程序员的某种心智模型(即一切都是一个类)。

我不会去权衡这是好是坏: 一切都是权衡。但是 Java8 lambdas 允许程序员使用 功能来编写 好好想想,这是以前在 Java 中无法做到的。

这和一个过程式程序员在学习使用 Java 的类时学习 好好想想是一样的: 你看到它们逐渐从被美化的结构(structs)类转移到拥有大量静态方法的“ helper”类,然后转移到更接近于理性 OO 设计(mea culpa)的东西上。

If you just think of them as a shorter way to express anonymous inner classes then you are probably not going to find them very impressive in the same way that the procedural programmer above probably didn't think classes were any great improvement.

复合函数和高阶函数。

Lambda 函数可以作为构建“高阶函数”或执行“复合函数”的基石。在这个意义上,Lambda 函数可以看作是可重用的构建块。

通过 lambda 的高阶函数示例:

Function<IntUnaryOperator, IntUnaryOperator> twice = f -> f.andThen(f);
IntUnaryOperator plusThree = i -> i + 3;
var g = twice.apply(plusThree);
System.out.println(g.applyAsInt(7))

Example Function Composition

Predicate<String> startsWithA = (text) -> text.startsWith("A");
Predicate<String> endsWithX   = (text) -> text.endsWith("x");


Predicate<String> startsWithAAndEndsWithX =
(text) -> startsWithA.test(text) && endsWithX.test(text);


String  input  = "A hardworking person must relax";
boolean result = startsWithAAndEndsWithX.test(input);
System.out.println(result);

我还没有提到的一个好处是我最喜欢的: lambdas 使得延迟执行非常容易编写。

Log4j2使用这个例子,现在可以传递一个 lambda 来计算这个昂贵的值,而不是传递一个值给条件日志(一个可能计算起来很昂贵的值)。与以前不同的是,无论使用与否,每次都会计算这个值,而现在使用 lambdas,如果您的日志级别决定不记录这个语句,那么 lambda 就永远不会被调用,而且这种昂贵的计算也永远不会发生——性能提升!

没有 Lambdas 能做到吗?是的,通过使用 if ()检查来包围每个日志语句,或者使用冗长的匿名类语法,但代价是可怕的代码噪音。

类似的例子比比皆是。Lambdas 就像你的蛋糕一样: 多行优化代码的所有效率都被压缩到一行代码的视觉优雅中。

编辑: 按照评论者的要求,举个例子:

旧的方法是,不管这个 log 语句是否实际使用它,都会调用 expentiveCalculation() :

logger.trace("expensive value was {}", expensiveCalculation());

新的 lambda 高效方法,除非启用跟踪日志级别,否则将不会发生 ExpenseCalculation()调用:

logger.trace("expensive value was {}", () -> expensiveCalculation());