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

#1 热门回答(109 赞)

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

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

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

此外,constructnew Type() { … }保证产生一个新的不同实例(asnew总是如此)。如果在非staticcontext中创建,则匿名内部类实例始终保持对其外部实例的引用。相反,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


#2 热门回答(45 赞)

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

它们是为程序员提供的。

语言是与编译器的对话,可以将我们的想法变成机器可以执行的东西。从其他语言(orleaveitforother languages)来到它的人们对Java的主要抱怨之一曾经是它在程序员身上的某种心理模型(即一切都是一个类)。

我不打算判断这是好还是坏:一切都是权衡取舍。但Java 8 lambdas允许程序员使用术语断点,这是你以前在Java中无法做到的。

这与程序性程序员在学习Java时学习tothinkin课程是一回事:你会看到他们逐渐从具有美化结构的类中移动,并使用一堆静态方法获得"帮助"类,然后转向更紧密的东西类似于理性的OO设计(mea culpa)。

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


#3 热门回答(33 赞)

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

如果没有lambda表达式(和/或方法引用),Streampipelines的可读性会低得多。

例如,考虑如果用匿名类实例替换每个lambda表达式,followStreampipeline将如何显示。

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