除了保存代码行之外,lambda表达式还有什么用处吗?

时间:2017-11-21 07:08:56

标签: java lambda java-8

除了保存代码行之外,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());

10 个答案:

答案 0 :(得分:112)

Lambda表达式不会改变您通常可以使用Java解决的问题集,但肯定会使解决某些问题变得更容易,只是出于同样的原因我们不再使用汇编语言进行编程。从程序员的工作中删除冗余任务可以使生活更轻松,并允许做一些你甚至不会触摸的事情,只是为了你必须生成的代码量(手动)。

但lambda表达式不只是保存代码行。 Lambda表达式允许您定义函数,您之前可以使用匿名内部类作为解决方法,这就是为什么您可以在这些情况下替换匿名内部类,但不是一般。

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

此外,构造new Type() { … }保证生成新的不同实例(如new总是这样)。如果在非static上下文中创建,匿名内部类实例始终保持对其外部实例的引用。相比之下,lambda表达式仅在需要时捕获对this的引用,即,如果它们访问this或非static成员。并且它们生成有意未指定标识的实例,这允许实现在运行时决定是否重用现有实例(另请参阅“Does a lambda expression create an object on the heap every time it's executed?”)。

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

鉴于语义和性能的差异,lambda表达式可能会改变程序员将来解决某些问题的方式,当然,也是由于新的API包含了利用新语言功能的函数式编程思想。另请参阅Java 8 lambda expression and first-class values

答案 1 :(得分:48)

编程语言不适用于机器执行。

它们适合程序员思考

语言是与编译器的对话,可以将我们的想法变成机器可以执行的东西。关于Java的主要抱怨之一来自其他语言的人(或者离开 其他语言)曾经是强制程序员的某种心理模型(即一切都是一个阶级)。

我不会考虑这是好还是坏:一切都是权衡取舍。但是Java 8 lambdas允许程序员根据函数 思考,这是你以前在Java中无法做到的。

与程序性程序员学习思考在课堂上学习时的情况是一样的:你看到他们逐渐从那些美化结构的类中移动,并拥有一个带有“辅助”类的程序一堆静态方法,并转向更接近于理性OO设计的东西(mea culpa)。

如果您只是将它们视为表达匿名内部类的一种较短的方式,那么您可能不会发现它们非常令人印象深刻,就像上面的程序程序员可能认为类没有任何重大改进一样。 / p>

答案 2 :(得分:35)

如果能够以更短更清晰的方式编写大量逻辑,则可以将保存代码行视为一项新功能,从而缩短其他人阅读和理解的时间。

如果没有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表达式和方法引用的情况下使其可读,您必须定义包含此处使用的各种功能接口实例的变量,这将拆分管道的逻辑,使其更难理解。

答案 3 :(得分:8)

内部迭代

在迭代Java集合时,大多数开发人员倾向于获取元素,然后处理它。这就是,取出该项目,然后使用它,或重新插入它等。使用8版之前的Java,您可以实现内部类并执行以下操作:

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

现在使用Java 8,你可以做得更好,更简洁:

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;
}

使用Java 8 Predicate interface,您可以做得更好:

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

答案 4 :(得分:4)

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

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

  • 通过使用lambdas,您很乐意使用元素流上的函数式操作进行编程,例如对集合进行map-reduce转换。见java.util.function&amp; java.util.stream打包文档。

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

  • 如果lambda不访问其范围之外的变量,编译器将优化lambda创建,这意味着lambda实例仅由JVM创建一次。有关详细信息,您可以查看@ Holger对问题Is method reference caching a good idea in Java 8? 的回答。

  • 除了功能接口之外,Lambdas还可以实现多标记接口,但匿名内部类不能实现更多接口,例如:

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

答案 5 :(得分:2)

要回答你的问题,事实上是lambdas 不要让你做任何你在java-8之前做不到的事情,而是让你能够写更多简洁的代码。这样做的好处是,您的代码将更清晰,更灵活。

答案 6 :(得分:2)

我还没有提到的一件事是lambda 允许你定义使用它的功能

因此,如果您有一些简单的选择函数,您不需要将它放在一个带有一堆样板的单独位置,您只需编写一个简洁且与本地相关的lambda。

答案 7 :(得分:2)

函数组合和高阶函数。

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))

示例函数组合

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);

答案 8 :(得分:1)

是的,有许多优点。

  • 无需定义整个类我们可以通过它自己作为参考传递函数的实现。
    • 类的内部创建将创建.class文件,而如果使用lambda,则编译器会避免创建类,因为在lambda中,您传递的是函数实现而不是类。
  • 代码可重用性高于之前
  • 正如你所说的,代码比正常实现更短。

答案 9 :(得分:1)

Lambdas只是匿名类的语法糖。

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

如果您使用的是IntelliJ IDEA,它可以为您进行转换:

  • 将光标放在lambda
  • 按alt /选项+输入

enter image description here