JDK8的随笔(02)_Lambda表达式进一步探讨

时间:2020-12-06 19:13:04

Lambda表达式的运用

尼玛,噩耗啊。项目居然要用Angular js,我擦,国内看文档都要*的说。。。
对于这个也是一知半解,看来学习mode需要强势开启。。。
继续说说Lambda吧。

利用泛型

还拿回上一篇(link可点)的例子。

interface CheckPerson {
boolean test(Person p);
}

我们做一些改动:

interface Predicate<T> {
boolean test(T t);
}

之前接受的领域模型是Person,而修改之后我们的领域模型是T,可以动态绑定任何领域模型。
如果我们指定这个领域模型是Person的时候,那么这个接口其实就是下面的效果:

interface Predicate<Person> {
boolean test(Person t);
}

那么实现的class可以写成这个样子:

public class PredicateImp implements Predicate<Person>{

@Override
public boolean test(Person t) {
return false;
}

}

有个泛型我们可以随意指定领域模型来进行领域模型扩充。

看回我们上一篇的例子。

public static void printPersonsWithPredicate(
List<Person> roster, Predicate<Person> tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}

这个定义的方法printPersonsWithPredicate,我们调用的时候依然可以利用Lambda表达式:

printPersonsWithPredicate(
roster,
p -> p.getGender() == Person.Sex.MALE
&& p.getAge() >= 18
&& p.getAge() <= 25
);

来完成。并且今后如果不是Person的class的话也可以动态的在Lambda的表达式中指定。

Lambda表达式的纵向贯穿和优化

上面利用泛型后可以扩充领域模型,但是例子中还是有一个问题。

public static void printPersonsWithPredicate(
List<Person> roster, Predicate<Person> tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}

仔细看上面的代码,我们的tester.test(Person p)这个方法利用Lambda表达式拉到了方法调用的地方来具体实现,但是这个printPersonsWithPredicate方法中还调用了一个p.printPerson()的方法,也就是说,当我们如果对Person的printPerson进行调整的话,势必要影响到这个printPersonsWithPredicate方法自身的逻辑和实现。利用Lambda表达式的一个最重要的目的也是为了提取函数式接口中的唯一这个方法的逻辑使其轻巧地在调用的地方得到体现,而不去影响调用方法本身的逻辑,从代码的编程层面上进行了一个神似的解耦操作。所以上面的这个p.printPerson()并没有得到“解耦“,利用Lambda表达式还有一个优化的可能性。
我们都知道能量守恒以及时间空间的转换,任何事物都有其本质和内在的互联,我们节省了时间空间势必要有上升,反之亦然。那么上面这个问题的解决方法就是利用空间换取灵巧。
可以思考下面的这种模式:
我们首先定义一个interface:

interface Consumer<T> {
void accept(T t);
}

OK,这是一个函数式接口。不知道函数式接口的请参看:上一篇:Lambda初体验
当然,其实我们可以不用定义这个interface,java的最新的java.util.function的package中已经为我们定义了这么一个可以直接使用的函数式接口,代码拿来看看:

先来了解 java.util.function 中的Consumer<T>

 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
package java.util.function;

import java.util.Objects;

/**
* Represents an operation that accepts a single input argument and returns no
* result. Unlike most other functional interfaces, {@code Consumer} is expected
* to operate via side-effects.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #accept(Object)}.
*
* @param <T> the type of the input to the operation
*
* @since 1.8
*/

@FunctionalInterface
public interface Consumer<T> {

/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/

void accept(T t);

/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/

default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}

这是一个函数式接口,满足一个abstract方法和≧1个的default或者static方法。
那么这个interface中的void accept(T t)即我们可以利用的方法,进行逻辑的实现。
这个interface中的default方法是被所有implement这个接口的方法所。。。。嗯。。我靠,这里怎么说?继承?呵呵,终于可以说一个类继承了一个接口的方法,这在以前也算是一个业余的错误说法吧。
所有实现这个interface的class都可以默认地拥有这个addThen方法,这个方法的目的就是在我们做完accept方法后可以继续使用一个T来完成一个后处理的执行。
举个简单的例子,比如这个场景:我们想在执行p.printPerson()后再执行一下p.printPerson2()的方法,那么这个printPerson2()就是所谓的后处理。
假设Person的中的方法是:

    public void printPerson() {
System.out.println(1);
// ...
}

public void printPerson2() {
System.out.println(2);
// ...
}

调用:

    Consumer<Person> impl = (Person p) -> p.printPerson(); // 行1
impl.andThen((Person p) -> p.printPerson2()); // 行2

上面这段代码行1中利用Lambda表达式定义了Consumer中的accept方法的实现。
行2中直接在andThen的参数(也是一个Consumer接口)中利用Lambda表达式定义了后处理的执行主体Person以及其调用的printPerson2()的方法。
上面两行代码执行完以后是啥样子?
答案是,没有任何输入。
因为我们只定义了一个impl,它进行了accept方法的Lambda化定义和其andThen的后处理的Lambda定义,但是并没有调用,所以如果想调用应该是:

    Consumer<Person> impl = (Person p) -> p.printPerson(); // 行1
impl.andThen((Person p) -> p.printPerson2()); // 行2
impl.accept(new Person()); // 行3

酱的话,加入行3后,可以在控制台打出”1 2“。

Consumer<T>的链式运用

从Consumer的代码中可以看到,default方法每次返回的都是一个Consumer的实现,那么其实我们可以无限的andThen下去?
假设我们的Person中有printPerson1~printPerson10这10个方法,我们如果需要用Lambda表达式把这10个方法写入一个Consumer的实现类,How to do?
看:

        Consumer<Person> impl = ((Consumer<Person>) ((Person p) -> p.printPerson())) // 行1
.andThen((Person p) -> p.printPerson2()) // 行2
.andThen((Person p) -> p.printPerson3()) // ...
.andThen((Person p) -> p.printPerson4())
.andThen((Person p) -> p.printPerson5())
.andThen((Person p) -> p.printPerson6())
.andThen((Person p) -> p.printPerson7())
.andThen((Person p) -> p.printPerson8())
.andThen((Person p) -> p.printPerson9())
.andThen((Person p) -> p.printPerson10());
impl.accept(new Person());

代码解析:
首先,行1开始首先定义了最基本的accept方法,然后从行2以后开始进行9个andThen方法的追加,每一个andThen中都实现一个p.printPersonx()方法的注入,那么得到的impl即为一个10个p.printPersonx()的链式调用。很神奇且变态的效果。Lambda表达式的简易扩张使得本来可能需要很复杂的匿名内部类的调用方式变得简易明了。

回到我们的例子中,使用Consumer进行优化

public static void printPersonsWithPredicate(
List<Person> roster, Predicate<Person> tester) {
for (Person p : roster) {
if (tester.test(p)) {
p.printPerson();
}
}
}

利用Consumer后,我们的方法定义应该是:

public static void processPersons(
List<Person> roster,
Predicate<Person> tester,
Consumer<Person> block) { // 行1
for (Person p : roster) {
if (tester.test(p)) {
block.accept(p);// 行2
}
}
}

行1:方法中增加了一个Consumer的参数block
行2:调用接口中的accept方法
假设我们利用一个class实现Consumer接口的话应该是下面酱:

public class ConsumerImp implements Consumer<Person> {

@Override
public void accept(Person t) {

t.printPerson();

}

}

如果调用processPersons的方法,那么第3个参数我们可以指定ConsumerImp这个实例。
OK,如果采用Lambda表达式调用processPersons方法的话:

processPersons(
roster, // 行0
p -> p.getGender() == Person.Sex.MALE // 行1
&& p.getAge() >= 18 // 行2
&& p.getAge() <= 25, // 行3
p -> p.printPerson() // 行4
);

三个参数。
行0:第一个参数。
行1~行3:利用Lambda写入的实现Predicatede的函数式接口方法表达式。
行4:利用Lambda写入的实现Consumer的函数式接口方法的表达式。
也就是说本来在ConsumerImp中使用accept方法来实现的printPerson()方法我们使用Lambda表达式简化掉了这个实现类,并且节约了实现类的DI过程。
实现起来貌似Easy了不少哈,但是刚开始可能会烧脑一点,慢慢习惯吧。

つづく・・・