Writing Your First Lambda Expression 你的第一个Lambda表达式


前言

In 2014, Java SE 8 saw the introduction of the concept of lambda expressions. If you remember the days before Java SE 8 was released, then you probably remember the anonymous classes concept. And maybe you have heard that lambda expressions are another, simpler way of writing instances of anonymous classes, in some precise cases.
If you do not remember those days, then you may have heard or read about anonymous classes, and are probably afraid of this obscure syntax.
Well, the good news is: you do not need to go through anonymous classes to understand how to write a lambda expression. Moreover, in many cases, thanks to the addition of lambdas to the Java language, you do not need anonymous classes anymore.

2014 年,Java SE 8 引入了 lambda 表达式的概念。如果我们还记得 Java SE 8 发布之前的日子,那么我们可能还记得anonymous classes (匿名类)的概念。可能你也听过,在某些特定的情况下,lambda 表达式在编写anonymous classes实例时更简单。

如果你已经不记得了,那么你可能听说过或读过anonymous classes,并且可能害怕这种晦涩难懂的语法。

现在,好消息是:你不需要通过anonymous classes来了解如何编写 lambda 表达式。 此外,在许多情况下,由于Java 语言添加了 lambda,我们可以不用anonymous classes了。

Writing a lambda expression breaks down to understanding three steps:
1.identifying the type of the lambda expression you want to write
2.finding the right method to implement
3.implementing this method.
This is really all there is to it. Let us see these three steps in detail.

编写 lambda 表达式只需了解下面这三步::

1.确定要写的 lambda 表达式类型
2.找到正确的实现方法
3.实现这个方法

就是这么简单。下面我们一起详细看看这三个步骤。


1.Identifying the Type of a Lambda Expression确定要写的 Lambda 表达式类型

Everything has a type in the Java language, and this type is known at
compile time. So it is always possible to find the type of a lambda
expression. It may be the type of a variable, of a field, of a method
parameter, or the returned type of a method.

Java 语言中的所有东西都有类型,并且这种类型在编译时是已知的,所以我们一定可以找到 lambda 表达式的类型。 类型可能是变量(variable),字段(field),方法参数(method parameter),或者是方法的返回类型(returned type of a method)。

There is a restriction on the type of a lambda expression: it has to
be a functional interface. So an anonymous class that does not
implement a functional interface cannot be written as a lambda
expression.

lambda 表达式的类型有限制:必须是functional interface(functional interface)。 所以没有使用functional interface(functional interface)的匿名类不能写成 lambda 表达式。

The complete definition of what functional interfaces are is a little
complex. All you need to know at this point is that a functional
interface is an interface that has only one abstract method.

“功能接口(functional interfaces)”的完整定义有点复杂。而目前,我们只需要弄清楚:functional interface只有一种抽象方法。

You should be aware that, starting with Java SE 8, concrete methods
are allowed in interfaces. They can be instance methods, in that case,
they are called default methods, and they can be static methods. These
methods do not count, since they are not abstract methods.

我们知道,从 Java SE 8 开始,接口中允许使用 concrete methods。 可以是 instance methods,(此时就称 default methods ),也可以是 static methods。因为两者都不是抽象方法,所以都不算。

“Do I need to add the annotation @FunctionalInterface on an interface
to make it functional?"

需不需要在接口上添加注释 @FunctionalInterface 以确保运作正常?

No you don’t. This annotation is here to help you to make sure that
your interface is indeed functional. If you put this annotation on a
type that is not a functional interface, then the compiler will raise
an error.”

不需要。 此注释的作用是确保接口正常运作。 如果放在不是 functional interface 的类型上,那么编译器将报错。”

Functional Interfaces 函数接口

Let us see some examples taken from the JDK API. We have just removed
the comments from the source code.

下面来看看JDK API的例子。代码来源的评注已隐藏。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

The Runnable interface is indeed functional, because it has only one
abstract method. The @FunctionalInterface annotation has been added as
a helper, but it is not needed.

这个 Runnable interface 的确可以,因为只有一个 abstract method。这里的@FunctionalInterface只起帮助理解的作用,但并不是必需。

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        // the body of this method has been removed
    }
}

The Consumer<T> interface is also functional: it has one abstract
method and one default, concrete method that does not count. Once
again, the @FunctionalInterface annotation is not needed.

这里的 Consumer<T> interface也可行:只有一个 abstract method 和一个 default, concrete method (并不算)。同上,这里的 @FunctionalInterface 也不是必需。

@FunctionalInterface public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        // the body of this method has been removed
    }

    default Predicate<T> negate() {
        // the body of this method has been removed
    }

    default Predicate<T> or(Predicate<? super T> other) {
        // the body of this method has been removed
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        // the body of this method has been removed
    }

    static <T> Predicate<T> not(Predicate<? super T> target) {
        // the body of this method has been removed
    }
}

The Predicate<T> interface is a little more complex, but it is still a functional interface:

1.It has one abstract method
2.It has three default methods that do not count
3.It has two static methods that do not count neither.

这里的 Predicate<T> interface要复杂一点,但是也可以行:

1.只有一个抽象方法
2.有三个不算的默认方法
3.还有两个也不算的静态方法

2.Finding the Right Method to Implement 找到正确的实现方法

At this point you have identified the type of the lambda expression
you need to write, and the good news is: you have done the hardest
part: the rest is very mechanical and easier to do.

现在,我们已经确定了要写的lambda表达式。恭喜,最难的部分已经完成了。接下来要的就简单得多,不用动太多脑子。

A lambda expression is an implementation of the only abstract method
in this functional interface. So finding the right method to implement
is just a matter of finding this method.

Lambda表达法是这个功能口里唯一一个抽象方法的实现。所以,找到实现的正确方法就只是找到这个方法。

You can take a minute to look for it in the three examples of the previous paragraph.

我们可以回到上面三个例子中找一找。

For the Runnable interface it is:

Runnable 接口里,是:

public abstract void run();

For the Predicate interface it is:

Predicate 接口里,是:

boolean test(T t);

And for the Consumer<T> interface it is:

Consumer<T> 接口里,是:

void accept(T t);

3.Implementing the Right Method with a Lambda Expression 利用Lambda Expression来实现正确的方法

Writing a First Lambda Expression that implements Predicate<String>

写第一个Lambda Expression来实现 Predicate<String>

Now for the last part: writing the lambda itself. What you need to
understand is that the lambda expression you are writing is an
implementation of the abstract method you found. Using the lambda
expression syntax, you can nicely inline this implementation in your
code.

最后一步:写lambda。需要注意的是,要写的lambda expression是找到的 abstract method 的实现。使用 lambda 表达式语法,就可以顺利地内嵌到你的代码了。

This syntax is made of three elements:

1.a block of parameters;
2.a little piece of ASCII art depicting an arrow: ->. Note that Java uses meager arrows (->) and not fat arrows (=>);
3.a block of code which is the body of the method.

表达式有三个元素:
1.一段参数
2.一个箭头:->。注意,Jave用的是->,不是=>
3.方法主体代码

Let us see examples of this. Suppose you need an instance of a Predicate that returns true for strings of characters that have exactly 3 characters.

The type of your lambda expression is Predicate The method you need to
implement is boolean test(String s)
Then you write the block of parameters, which is simple copy / paste of the signature of the method: (String s).

You then add a meager arrow: ->.

下面看一个例子。比如,你需要一个 Predicate 的实例,该实例对于正好有 3 个字符的字符串返回 true

Lambda expression的类型是 Predicate
要用的方法是 boolean test(String s)

这样,就可以写一段参数。复制粘贴 (String s) 的签名(Signature)即可。

然后加一个箭头: ->

And the body of the method. Your result should look like this:

写出来应该是这样:

Predicate<String> predicate =
    (String s) -> {
        return s.length() == 3;
    };

Simplifying the Syntax 对语法进行简化

This syntax can then be simplified, thanks to the compiler that can
guess many things so that you do not need to write them.

First, the compiler knows that you are implementing the abstract
method of the Predicate interface, and it knows that this method takes
a String as a argument. So (String s) can be simplified to (s). In
that case, where there is only one argument, you can even go one step
further by removing the parentheses. The block of arguments then
becomes s. You should keep the parentheses if you have more than one
argument, or no argument.

Second, there is just one line of code in the body of the method. In
that case you do not need the curly braces nor the return keyword.

这里的语法可以简化。编译器有猜测功能,就不需要你自己写了。

首先,编译器知道你要用Predicate 接口的抽象方法(abstract method),并且也知道这种方法会把 Strings 看作参数。所以 (String s) 可以简化为 (s) 。在这里,只有一个参数,我们甚至可以继续简化,去掉括号。参数就变成了s。如果有超过一个参数,或者没有,则需要保留括号。

其次,方法主体只有一行代码。这种情况下,就不需要用 {}return 关键词。

So the final syntax is in fact the following:

所以最终结果如下:

Predicate<String> predicate = s -> s.length() == 3;

And this leads us to the first good practice: keep your lambdas short,
so that they are just one line of simple, readable code.

由此,我们得出第一个好习惯:lambdas 尽量短。这样,代码才简单易读。

Implementing a Consumer<String> 实现Consumer<String>

At some point, people may be tempted to take shortcuts. You will hear
developers saying “a consumer takes an object and returns nothing”. Or
“a predicate is true when the string has exactly three characters”.
Most of the time, there is a confusion between the lambda expression,
the abstract method it implements, and the functional interface that
holds this method.

But since a functional interface, its abstract method, and a lambda
expression that implements it are so closely linked together, this way
of speaking actually makes total sense. So that’s OK, as long as it
does not lead to any ambiguity.

有时候,大家会想走捷径。有的开发员可能会说:“一个consumer需要一个对象(object),然后不返回任何东西”或者“字符串有3个字符的时候,predicate为true”。大多数情况下,大家会把 lambda expression它实现的abstract method包含这种方法的 functional interfac 搞混。

但是,因为 functional interface(它的 abstruct method )和它运用的 Lambda 表达式关联非常紧密,这种说法其实是有道理的。所以,只要意思清楚,也OK。

Let us write a lambda that consumes a String and prints on System.out. The syntax can be this one:

下面,我们写一段获取一个字符串并且打印它的Lambda,语法可能如下:

Consumer<String> print = s -> System.out.println(s);

Here we directly wrote the simplified version of the lambda expression.

这里,我们直接写简版Lambda表达式。

Implementing a Runnable 实现 Runnable

Implementing a Runnable turns out to write an implementation of void
run(). This block of arguments is empty, so it should be written with
parentheses. Remember: you can omit the parentheses only if you have
one argument, here we have zero. So let us write a runnable that tells
us that it is running:

实现 Runnable 就是写 void run()。这里的参数是空,所以要用括号。记住:如果有一个参数,可以不用括号。这里没有参数。

现在我们写一个 runnable 来表示正在运行:

Runnable runnable = () -> System.out.println("I am running");

Calling a Lambda Expression 调用Lambda表达式

Let us go back to our previous Predicate example, and suppose that
this predicate has been defined in a method. How can you use it to
test if a given string of characters is indeed of length 3?

Well, despite the syntax you used to write a lambda, you need to keep
in mind that this lambda is an instance of the interface Predicate.
This interface defines a method called test() that takes a String and
returns a boolean.

Let us write that in a method:

我们回顾一下之前的 Predicate 例子。假设它已经被定义成了一种方法,我们如何用它来测试一串字符是否确实长度为 3

先暂时不管写Lambda的语法,我们需要牢记这个Lambda是接口 Predicate 的一个例子。这里的接口定义了一种方法,叫做 test()。它使用 String ,并且返回 boolean.

下面,我们来写:

List<String> retainStringsOfLength3(List<String> strings) {

    Predicate<String> predicate = s -> s.length() == 3;
    List<String> stringsOfLength3 = new ArrayList<>();
    for (String s: strings) {
        if (predicate.test(s)) {
            stringsOfLength3.add(s);
        }
    }
    return stringsOfLength3;
}

Note how you defined the predicate, just as you did in the previous
example. Since the Predicate interface defines this method boolean test(String), it is perfectly legal to call the methods defined in
Predicate through a variable of type Predicate. This may look
confusing at first, since this predicate variable does not look like
it defines methods.

Bear with us, there are much better way to write this code, that you
will see later in this tutorial.

跟之前的例子类似,我们要注意这里是如何定义 predicate 的。因为这里 Predicate 接口把方法定义为了 boolean test(String),通过 Predicate 类型的变量调用 Predicate 中定义的方法是完全合法的第一眼看可能会比较乱,因为此处的 predicate 变量看起来不像定义了方法。

请耐心往下看。教程后半段有写这串代码更好的方式。

So everytime you write a lambda, you can call any method defined on
the interface this lamdba is implementing. Calling the abstract method
will call the code of your lambda itself, since this lambda is an
implementation of that method. Calling a default method will call the
code written in the interface. There is no way a lambda can override a
default method.

所以,当我们写Lambda时,我们可以调用此Lamdba对应接口所定义的方法。调用抽象方法会调用缩写lambda的代码,因为此lambda是该方法的应用。调用默认方法会调用接口里的代码。Lambda 不可能会重载一个默认方法。

Capturing Local Values 捕获本地值

Once you get used to them, writing lambdas becomes very natural. They
are very well integrated in the Collections Framework, in the Stream
API and in many other places in the JDK. Starting with Java SE 8,
lambdas are everywhere, for the best.

There are constraints in using lambdas and you may come across
compile-time errors that you need to understand.

只要习惯了,写Lambda就会很顺了。在 Collections Framework、Stream API、JDK的其他很多地方,都能很好地运用。从Jave SE 8开始,Lambda代码就在运用在各处。

在用Lambda时,会有一些限制。可能会碰到编译时错误,我们需要弄清楚。

Let us consider the following code:

来看这段代码:

int calculateTotalPrice(List<Product> products) {

    int totalPrice = 0;
    Consumer<Product> consumer =
        product -> totalPrice += product.getPrice();
    for (Product product: products) {
        consumer.accept(product);
    }
}

Even if this code may look nice, trying to compile it will give you
the following error on the use of totalPrice in this Consumer
implementation:

虽然这段代码看起来不错,但在编译时,在Consumer运用中,会碰到使用totalPrice的错误:

Variable used in lambda expression should be final or effectively final

Lambda中使用的变量需为 final,或 effectively final.

The reason is the following: lambdas cannot modify variables defined
outside their body. They can read them, as long as they are final,
that is, immutable. This process of accessing variable is called
capturing: lambdas cannot capture variables, they can only capture
values. A final variable is in fact a value.

原因如下:Lambda无法修改其本身以外定义的变量。只要是 final,可以读,就是说是不可变的 (immutable)。这种获取变量的过程,我们称为捕获(capture):lambda无法捕获变量,只能捕获值。final 的变量实质上是一个值。

You have noted that the error message tells us that the variable can
be final, which is a classical concept in the Java language. It also
tells us that the variable can be effectively final. This notion was
introduced in Java SE 8: even if you do not explicitly declare a
variable final, the compiler may do it for you. If it sees that this
variable is read from a lambda, and that you do not modify it, then it
will nicely add the final declaration for you. Of course this is done
in the compiled code, the compiler does not modify your source code.
Such variables are not called final; they are called effectively final
variables. This is a very useful feature.

你应该也发现了,错误信息提示变量可以是 final,这是Java语言中经典的概念。提示也说变量可以是有效 final。此概念在Java SE 8中引入:即使我们不明确地把变量申明为 final,编译器也会帮你完成这件事。如果它发现此变量是从Lambda中读出来,并且你也没修改,它会自动帮你加上final申明。当然,这体现在编译后的代码中,编译器不会动你的源代码。这种变量不叫 final ,而是叫做 effectively final 。这个功能非常实用。

Serializing Lambdas 序列化 Lambda

Lambda expressions have been built so that they can be serialized.

Why would you serialize lambda expressions? Well, a lambda expression
may be stored in a field, and this field may be accessed through a
constructor or a setter method. Then you may have a lambda expression
in the state of your object at runtime, without being aware of it.

So to preserve backward compatibility with existing serializable
classes, serializing a lambda expression is possible.

Lambda表达式可以被序列化。

为什么要序列化Lambda表达式呢?Lambda表达式可以储存在字段 (field) 里,此字段可以通过构造函数 (constructor) 或 setter 方法来访问。这样,在你项目状态运行时间中,可能会有lambda 表达式,但你可能不知道。

因此,为了保持与现有可序列化类的向后兼容性,序列化lambda表达式是可能的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值