我不理解Java中的Lambda表达式是如何工作的。

huangapple go评论54阅读模式
英文:

I am failing to understand how lambda expressions work in Java

问题

我在一个初学者和中级水平的Java程序员之间,几天前,函数式编程 这个神秘的概念引起了我的注意。我习惯于面向对象的实践,关于在Java中使用lambda表达式的内容对我来说一点也不清晰。以下是我困惑的一个例子:

Runnable r1 = new Runnable() {
  @Override
  public void run () {
     System.out.println("在Java 8之前");
   }
};

// 使用lambda表达式的Runnable
Runnable r2 = () -> {
    System.out.println("从Java 8开始");
};

我不知道将r2分配给lambda表达式是什么意思。如何将整个接口对象分配给只是输出一条消息的代码块?函数式编程的抽象层次对我来说几乎没有任何意义,如果有任何回复,我将不胜感激。

英文:

I am somewhere between a beginner and intermediate-level java programmer and the mystifying concept of Functional programming caught my eyes a few days ago. I am accustomed to OOP practices and nothing about the use of lambda expressions in Java is clicking to me. Below is an example of my confusion

Runnable r1 = new Runnable() {
  @Override
  public void run () {
     System.out.println("Prior to Java 8");
   }
};

//Runnable using lambda expression
Runnable r2 = () - > {
    System.out.println("From Java 8");
};

I have no idea what the assignment of r2 to the lambda expression is supposed to mean. How is the entire object of an interface assigned to a block of code that simply prints out a message? The levels of abstraction for functional programming make little to no sense at all to me, and any replies would be greatly appreciated.

答案1

得分: 1

基本上,你的第二个块(r2)是语法糖。注意一下r2中的内容要少得多。如果你比较一下,会更加清楚:

Comparator stringLength = new Comparator<String>() {
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
};

与:

Comparator stringLength = (a, b) -> a.length() - b.length();

但是让我们暂时专注于r1

r1本身也是语法糖!它是下面代码的简写:

public void methodWeAreIn() {
    class MethodLocalClassWithoutAnImportantName implements Runnable {
        public void run() { System.out.println("boo"); }
    }

    Runnable r1 = new MethodLocalClassWithoutAnImportantName();
}

重点是:首先定义了一个类,然后创建了该类的单个实例,然后丢弃了它的具体类型(将它赋值给了类型为Runnable的变量。就像在List<String> x = new ArrayList<String>();中一样,你失去了调用trimToSizeensureCapacity的能力,这些方法只存在于ArrayList中,而不在List本身中)。在这个特定的代码片段中,这并不是什么坏事。

而且这就是要说明的:你在这里定义的类型(MethodLocalClassWithoutAnImportantName)在这个上下文中是__完全无关紧要的类型__ - 你的代码库中没有任何方法的参数是这个类型,也没有任何方法返回它。这有点像反接口:接口存在的唯一目的就是作为实现的类型,或者放在方法签名中(作为返回类型或参数)。这个类的目的正好相反。这个类的目的不是类的结构,而是__仅仅__其中的实现部分。这也是面向对象的一部分 - 有时你只是实现一个接口,几乎不定义任何新的方法或字段,只是实现接口,可能还会覆盖超类中的一些内容。

重新考虑这一切的含义更有意义。你不仅仅是在定义一个新类然后创建一个实例。实际上,你只是将一些代码放进了一个信封里,而不是运行它。

现在你可以把这个信封交给另一个方法(该方法也可以选择继续传递它,或者打开信封并运行其中的代码),或者重复运行它,或者稍后在另一个线程中运行它等等。是的,在r1(以及我的方法局部类示例)中,这个‘信封’是一个对象,但这只是一种方便的方式,这个练习的重点不是创建新类型,而是创建可以在代码之间传递的代码。

Lambda 表达式只是正式地实现了这一点。

注意:r2并不是精确的语法糖;但除非你做了一些不应该做的事情,否则你无法区分出差异:尝试将r2视为具有标识的对象。如果你synchronized (r2),或者尝试类似System.identityHashCode(r2);的操作,会发生奇怪的事情。但重点不是这样做,你为什么要这样做呢?

英文:

Essentially, your second block (r2) is syntax sugar. Note how the stuff in r2 is a lot less text. It gets even better if you compare, say:

Comparator stringLength = new Comparator&lt;String&gt;() {
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
};

versus:

Comparator stringLength = (a, b) -&gt; a.length() - b.length();

But let's focus on r1 for a moment.

That, itself, is syntax sugar too! It's short for:

public void methodWeAreIn() {
    class MethodLocalClassWithoutAnImportantName implements Runnable {
        public void run() { System.out.println(&quot;boo&quot;); }
    }

    Runnable r1 = new MethodLocalClassWithoutAnImportantName();
}

The point is: You're first defining a class, then making a single instance of this class, then discarding the specific type nature of it (you assign it to a variable of type Runnable. Just like in List&lt;String&gt; x = new ArrayList&lt;String&gt;();, you lose the ability to invoke trimToSize and ensureCapacity, which are methods that are only in ArrayList and not in List itself.) That's not a bad thing: In this particular code segment it doesn't matter.

And that's the point: Your type here (MethodLocalClassWithoutAnImportantName) is utterly irrelevant as a type - no method anywhere in your codebase has one of these as a parameter and no method returns it. It's like an anti-interface: Interfaces exist solely to have a type to implement or put in a method signature (as return type or parameter). The point of this class is the opposite. The point isn't the class structure. The point of this class is solely the implementations inside it. This is part of OO after all - sometimes you make an implementation of an interface and define no new methods or fields whatsoever, you're just implementing interfaces and perhaps overriding stuff from a superclass.

It's more sensible to reconsider what all this means. You're not so much defining a new class and then creating an instance. You're really just taking some code, and instead of running it, you stuff it in an envelope.

Now you can take this envelope and hand it off to another method (which can choose to hand it off too, or open it up and run it), or run it repeatedly, or run it later in another thread, etc. Yes, in r1 (and my method local class example), the 'envelope' is an object, but that's just a convenient way to do it, the point of the exercise is not to make new types, it's to make code that can travel around.

Lambdas just make that official.

NB: r2 is not exact syntax sugar; but you can't tell the difference unless you do things you should not do: Try to treat r2 as an object with identity. if you synchronized (r2), or try things like System.identityHashCOde(r2);, wonky stuff happens. The point is not to do that. Why would you want to?

huangapple
  • 本文由 发表于 2020年8月8日 10:05:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/63311143.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定