一门编程语言要辅助函数式编程,难道我们将要因为客户每指出一个须求

表现参数化

为了应对多变的须要,难道大家就要因为客户每提议一个急需,我们将要写一个主意去落实啊?

引人注目那样做很冗余,而且维护性大大下落,那表达清码的规划不够好。好在早就有前人帮我们指出了作为参数化考虑(即将一段代码逻辑作为参数,使之可以在不相同对象间传递)。

java1.8从前使用匿名类来促成行为参数化,尽管用匿名类去贯彻一个函数式接口中的方法。java1.8随后,推出了Lambda表明式来代表原先匿名类完结行为参数化的扑朔迷离进度,使代码更简单、更优雅。

Lambda初体验

先从不难的例证初始:创立一个thread,必要在Thread()构造方法中传唱一个Runnable接口的落实类对象,但貌似不会为了那一个完结类对象去创设一个兑现类,java1.8以前更简洁的更便民维护的方式是在构造方法中成立一个落到实处了Runnable接口的匿名类对象,只行使三遍,代码如下:

new Thread(new Runnable() {

@Override

public void run() {

System.out.println(“使用匿名类完毕Runnable接口,落成效益需要6行代码”);

}

}).start();

可以观察,通过匿名类完结Runnable接口,须要编制6行代码,但骨子里真正落实了我们须要的听从的代码只有一行(蟹灰加粗),从代码量上来看,那就突显很冗余,“高度难题(height
problem)”。

java1.8公告的新特色,lambda表明式,就足以很好的化解那一个标题,上面的代码等价上边的代码:

new Thread(() ->
{System.out.println(“使用拉姆da表明式,只需求一行代码”);}
).start();

留意上面代码中的淡红字体部分,那就是Lambda表明式的一个简单易行演示,lambda表达式充当了这一个接口中的抽象方法的切切实实贯彻。

lambda表明式是java8中最要害的特性之一,它让代码变得容易并且同意你传递行为。什么日期,Java总是因为代码冗长和缺乏函数式编程的能力而面临批评。随着函数式编程变得尤为受欢迎,Java也被迫开首拥抱函数式编程。否则,Java会被大家逐步撤消。

拉姆da表明式的语法结构

下边我们就来看一下lambda表明式的两种选拔语法:

(params) -> expression

(params) -> statement

(params) -> { statement; }

左侧第四个括号中的params参数列表依据须要充实;中间是一个箭头,英文半角的-与过量号>组成,那八个记号之间无法有空格,箭头两边可以有空格;箭头的出手是表明式或许语句块。即使是相近“return
a+b”那种结构的方法体,可以一贯写成(int a, int b) -> a+b
,expresion能够回到该表明式的结果,可以看看lambda表达式把return那种艺术退出语句都简化省略掉了。借使只是想经过操纵台出口语句打印一段话,可以写成()
-> System.out.println(“Hello”)
语句末尾的分集团都得以概括不写。若是是落到实处方式的逻辑比较复杂,就可以用花括号将一段逻辑代码括起来,比如
() -> { 语句块 }

Java8是驱动那一个世界上最盛行的编程语言应用函数式编程的一次大的超越。一门编程语言要支持函数式编程,就必须把函数作为这几个等老百姓。在Java8事先,只可以通过匿名内部类来写出函数式编程的代码。而随着lambda表达式的引入,函数变成了一等老百姓,并且可以像任何变量一样传递。

函数式接口

在越来越表达lambda表明式从前,先做一个学问储备,什么是函数式接口?

只持有一个措施的接口,称为函数式接口。在原先的本子中,人们常称那体系型为SAM类型,即单抽象方法类型(SAM,Single
Abstract Method)

java1.8以后,设计者们对JDK做了周到的变更,为契合函数式接口规范的接口,都抬高了@FunctionalInterface注脚,文告编译器那几个接口是符合函数式接口的标准,固然或者部分接口中有七个主意,但是方法的签字方可各有差距。

看似仍旧不太领会?大家找多少个JDK的事例来探视,比如:

(1)Callable接口

@FunctionalInterface

public interface Callable {

V call() throws Exception;

}

(2)Runnable接口

@FunctionalInterface

public interface Runnable {

public abstract void run();

}

(3)java.util.Comparator接口

@FunctionalInterface

public interface Comparator {

int compare(T o1, T o2);

boolean equals(Object obj);

// java1.8从此还增添了一些default方法,那里就不列出

}

可以发现,Callable和Runnable那五个接口的共性,接口中都只表明了一个主意。符合那种布局正式的interface,java中就称为函数式接口。而在(3)Comparator接口中有八个格局,为啥吧?因为boolean
equals(Object
obj)是Object类的public方法,函数式接口中允许定义Object的public方法,像clone()方法就不可能定义因为是protected方法,加上了@FunctionalInterface评释告诉编译器,那一个接口必须符合函数式接口规范的,若是不符合就会编译报凑。

lambda表明式允许开发者定义一个不囿于于定界符的匿名函数,你可以像使用编程语言的别样程序结构一样来使用它,比如变量评释。假若一门编程语言要求扶助高阶函数,lambda表达式就派上用场了。高阶函数是指把函数作为参数只怕重回结果是一个函数那么些函数。

拉姆da表达式的结果类型,目的项目(Target Typing)

在初体验的例子中,好像lambda表明式没有结果值类型,但不代表lambda就没有结果类型,只是我们不要求指定lambda表达式的结果类型。

那lambda表明式的结果类型是如何呢?答案是:它的序列是由其上下文推导而来。相当于说,同一段lambda表明式在不相同的上下文环境中,或者会有分歧的结果类型,比如:

Callable c =() -> “done.”;

PrivilegedAction p =() -> “done.”;

纵然c和p等号左侧的lambda表达式一样,不过多少个lambda表达式的结果却不雷同,第二个是Callable类型,第四个是PrivilegedAction类型。

由编译器完毕对拉姆da表达式的结果类型推导,编译器依据Lambda表达式的上下文推导出一个预期的品种,这么些预期的花色就是对象项目。lambda表达式对目的项目也有需要,编译器会检查lambda表明式的推理类型和目的项目标方法签名是还是不是一致。须要满足下列全部标准,lambda表明式才得以被赋给目的类型T:

·T 是一个函数式接口

·lambda表明式的参数与 T 中的方法的形参列表在数量、类型上完全一致

·lambda表明式的重临值与 T
中的方法的再次回到值相合作,lambda表达式的回到值类型应该是 T 的落到实处类或子类

·lambda表达式内所抛出的那些与 T 中的方法throws的这一个类型相协作,同上一条

自个儿个人对目标项目标知情:

对象项目差别于再次来到值类型,它是对要兑现的办法所属的函数式接口的一种参考,待已毕方式有重返值类型,也有其所属的接口或类,而以此办法所属的接口或类,就是目标项目。

java设计者须求,lambda表明式只好出将来目的项目为函数式接口的内外文中

其一章节的代码如下ch02
package
.

代码中度下落了,宽度呢?

lambda表明式将多行代码浓缩到一行,是化解了“中度难点”,不过过多的新闻在一行表述,显明会追加lambda表明式一行的代码量,那就暴发了“宽度难题”,java设计者在设计lambda表达式时考虑到那点,做了优化的统筹:

乘胜Java8中lambda表明式的引入,Java也支撑高阶函数。接下来让我们来分析那几个经典的lambda表达式示例–Java中Collections类的一个sort函数。sort函数有两种调用方式,一种必要一个List作为参数,另一种需要一个List参数和一个Comparator。第三种sort函数是一个接收lambda表明式的高阶函数的实例,如下:

(1)省略形参类型

由于目的项目(函数式接口)已经“知道”lambda表明式的款式参数(Formal
parameter)类型,所以没有须要把已知类型再重复写几回。约等于说,lambda表达式的参数类型可以从目的项目中汲取。

举个例证:

Comparator c = (s1, s2) -> s1.compareToIgnoreCase(s2);

其中s1和s2大家即便没有显然指定其参数类型,可是编译器可以透过上下文推导出其形参类型,Comparator接口中有多少个主意,int
compare(T o1, T o2)、boolean equals(Object
obj),按照lambda表明式的参数列表(2个形参),可以推导出要促成的接口方法是compare(T
o1, T
o2),又依照目标项目Comparator指定了不畏,就可以推导出s1和s2的参数类型就是String。

List<String> names = Arrays.asList("shekhar", "rahul", "sameer");
Collections.sort(names, (first, second) -> first.length() - second.length());

(2)当lambda参数唯有一个且其系列能够被演绎出时,参数列表的()括号也可以简容易单

举个例证:

FileFilter java = f -> f.getName().endsWith(“.java”);

java.io.FileFilter接口中仅有一个办法,boolean accept(File
pathname),可以推导出该lambda表明式的参数列表应该是File类型,约等于说参数f的品种也可以概括了,而且唯有那些参数,那么括号()也得以简不难单了。

上边的代码是基于names的长短来展开排序,运行的结果如下:

上下文

上边提到多次lambda表明式只能够出现拥有目的项目标左右文中,上面列出含有目的项目的上下文:

·变量注脚

·赋值

·重回语句

·数组先河化器

·方法和构造方法的参数

·lambda表达式函数体

·条件表明式(? :)

·转型(Cast)表达式

[rahul, sameer, shekhar]

主意引用

通过下面的例子和验证,我们领略了lambda表明式允许大家自定义一个匿名方式((params)
-> {…}
那看起来就如一个从未名字的法子定义),并能以函数式接口的办法利用这一个匿名格局。那未来我们也足以不用自定义方法,直接引用已有的艺术也是可以的,那种引用我们称为艺术引用

办法引用和lambda表明式拥有一致的特征(例如,都急需一个目的项目,并且需求被转移为函数式接口的实例),只但是不必要为已有点子提供方法体,大家直接通过该措施的名字就足以引用这些已有艺术。

举个例子:

class Person {

private final String name;

public String getName(){

return this.name;

}

….

}

Person[] people = …

Comparator byName = Comparator.comparing(p – > p.getName());

Arrays.sort(people, byName);


加粗部分可以用措施引用lambda表明式来取代:

Comparator byName = Comparator.coparint(Person::getName);

是或不是看起来表义就更清楚了吧?方法引用Person::getName就足以视作是lambda表明式p
->
p.getName()的一种简写方式,就算看起来好像代码量没有滑坡多少,可是富有了更醒目标语义——若是大家想调用的措施拥有一个名字,这大家就径直用那几个名字来调用它呢。

地方代码片段中的(first,second) -> first.length() - second.length()表明式是一个Comparator<String>的lambda表达式。

艺术引用的门类

上边列出方法引用的两种语法:

·静态方法引用ClassName::staticMethodName

·实例中的实例方法引用instanceReferenceName::methodName

·父类上的实例方法引用super::methodName

·本类上的实例方法引用ClassName::methodName

·构造方法引用Class::new

·数组构造方法引用TypeName[]::new

在类型和艺术名以内,加上分隔符“::”

  • (first,second)Comparatorcompare办法的参数。

  • first.length() - second.length()相比较name字符串长度的函数体。

  • -> 是用来把参数从函数体中分离出来的操作符。

用一个例子融会贯通

先是看实例代码:

List people = … Collections.sort(people,newComparator()
{publicintcompare(Person x, Person y)
{returnx.getLastName().compareTo(y.getLastName()); } });

看了lambda表明式的用法之后,是否感到冗余代码太多呢?

大家先用lambda表达式去掉冗余的匿名类,精简成一行代码:

Collections.sort(people,

(Person x, Person y) -> x.getLastName().compareTo(y.getLastName()));

至今看起来代码是不难了诸多,然则觉得抽象程度还相比差,开发人员依旧需求展开实际的相比较操作,我们得以借助java.util.Comparator接口中静态方法comparing()
(那也是Java1.8新增的):

Collections.sort(people,

Comparator.comparing((Person p) -> p.getLastName()));

编译器可以帮忙大家做项目推导,同时还足以凭借静态导入,进一步精简:

Collections.sort(people,comparing(p-> p.getLastName()));

明日看起来,就发现可以将lambda表达式用艺术引用来替换:

Collections.sort(people, comparing(Person::getLastName));

应用Collections.sort()的帮衬方法也不太妥当,它使代码冗余,也不能针对List接口的数据结构提供一定的高效落实,而且因为Collections.sort()方法不属于List接口,用户在翻阅List接口文档的时候或然不会意识到Collections类中有提供一个针对List接口的排序方法sort(),那里可以做一步优化,大家得以为List接口添加一个default方法sort(),然后径直通过List对象调用该sort()方法:

people.sort(comparing(Person::getLastName));

如此那般即方便调用,也惠及代码的阅读和前期维护。将最终结出相比较一最先的匿名类的兑现格局,是还是不是要更不难,但语义却更清楚了啊?那就是lambda表明式的利益。

在大家深入钻研Java8中的lambda表达式以前,大家先来追溯一下他们的野史,通晓它们为什么会存在。

lambda表明式的历史

lambda表明式源自于λ演算.λ演算源点于用函数式来制订表明式计算概念的研商Alonzo
Church
λ演算是图灵完整的。图灵完整意味着你可以用lambda表明式来发挥任何数学算式。

λ演算后来变成了函数式编程语言强有力的冲突功底。诸如
Hashkell、Lisp等名牌的函数式编程语言都以根据λ演算.高阶函数的定义就出自于λ演算

λ演算中最根本的概念就是表达式,一个表达式可以用如下方式来表示:

<expression> := <variable> | <function>| <application>
  • variable
    一个variable就是一个类似用x、y、z来代表1、2、n等数值大概lambda函数式的占位符。

  • function
    它是一个匿名函数定义,须求一个变量,并且转变另一个lambda表明式。例如,λx.x*x是一个求平方的函数。

  • application
    把一个函数当成一个参数的作为。假如你想求10的平方,那么用λ演算的章程的话你须要写一个求平方的函数λx.x*x并把10运用到那么些函数中去,那么些函数程序就会回到(λx.x*x) 10 = 10*10 = 100。可是你不仅可以求10的平方,你可以把一个函数传给另一个函数然后生成另一个函数。比如,(λx.x*x) (λz.z+10)
    会生成那样一个新的函数
    λz.(z+10)*(z+10)。今后,你可以用那一个函数来生成一个数增进10的平方。这就是一个高阶函数的实例。

当今,你已经精通了λ演算和它对函数式编程语言的震慑。上面我们后续深造它们在java8中的完结。

在java8事先传递行为

Java8事先,传递行为的唯一方法就是透过匿名内部类。如若你在用户已毕注册后,要求在其余一个线程中发送一封邮件。在Java8事先,可以经过如下方式:

sendEmail(new Runnable() {
            @Override
            public void run() {
                System.out.println("Sending email...");
            }
        });

sendEmail方法定义如下:

public static void sendEmail(Runnable runnable)

地点的代码的难题不仅仅在于我们须要把作为封装进去,比如run格局在一个对象里面;更不佳的是,它不难模糊开发者真正的企图,比如把作为传递给sendEmail函数。如若您用过部分像样Guava的库,那么你就会切身感受到写匿名内部类的伤痛。上边是一个大致的例证,过滤所有标题中富含lambda字符串的task。

Iterable<Task> lambdaTasks = Iterables.filter(tasks, new Predicate<Task>() {
            @Override
            public boolean apply(Task task) {
                return input.getTitle().contains("lambda");
            }
});

选用Java8的Stream
API,开发者不用太第三方库就可以写出地点的代码,大家将在下一章chapter
3
讲述streams相关的知识。所以,继续往下阅读!

Java 8 Lambda表达式

在Java8中,大家得以用lambda表明式写出如下代码,那段代码和下面提到的是同一个例证。

sendEmail(() -> System.out.println("Sending email..."));

地点的代码卓殊简单,并且可以清晰的传递编码者的意向。()用来代表无参函数,比如Runnable接口的中run主意不含任何参数,直接就可以用()来代替。->是将参数和函数体分开的lambda操作符,上例中,->末端是打印Sending email的连带代码。

上面再次经过Collections.sort这几个例子来通晓带参数的lambda表达式如何运用。要将names列表中的name依据字符串的尺寸排序,必要传递一个Comparator给sort函数。Comparator的概念如下

Comparator<String> comparator = (first, second) -> first.length() - second.length();

上边写的lambda表明式约等于Comparator接口中的compare方法。compare办法的概念如下:

int compare(T o1, T o2);

T是传递给Comparator接口的参数类型,在本例中names列表是由String组成,所以T表示的是String

在lambda表明式中,大家不须要明确提出参数类型,javac编译器会通过上下文自动测算参数的类型音信。由于大家是在对一个由String类型组成的List举行排序并且compare艺术唯有用一个T类型,所以Java编译器自动测算出八个参数都以String品类。依照上下文猜想类型的行事称为项目测度。Java8升任了Java中早就存在的花色预计系统,使得对lambda表明式的匡助变得更加强劲。javac会寻找紧邻lambda表达式的一对音信透过这个新闻来测算出参数的不易类型。

在超过一半景象下,javac会基于上下文自动测算类型。假诺因为丢失了上下文消息或然上下文音信不完整而致使不可以测算出类型,代码就不会编译通过。例如,上面的代码中大家将String类型从Comparator中移除,代码就会编译退步。

Comparator comparator = (first, second) -> first.length() - second.length(); // compilation error - Cannot resolve method 'length()'

Lambda表明式在Java8中的运行机制

您大概早就意识lambda表明式的项目是有的近似上例中Comparator的接口。但并不是每一种接口都得以利用lambda表明式,唯有这些单纯包涵一个非实例化抽象方法的接口才能动用lambda表明式。那样的接口被称着函数式接口同时它们可以被@FunctionalInterface诠释注释。Runnable接口就是函数式接口的一个事例。

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

@FunctionalInterface诠释不是必须的,可是它可以让工具知道这么些接口是一个函数式接口并显现有意义的行事。例如,若是你试着那编译一个用@FunctionalInterface注明本身同时包涵八个抽象方法的接口,编译就会报出那样一个错Multiple
non-overriding abstract methods
found
。同样的,要是你给一个不含有其余措施的接口添加@FunctionalInterface诠释,会获取如下错误音讯,No
target method found
.

下边来应对一个您大脑里一个老大首要的疑难,Java8的lambda表明式是不是只是一个匿名内部类的语法糖或然函数式接口是什么被转换成字节码的?

答案是NO,Java8不选择匿名内部类的由来重要有两点:

  1. 属性影响:
    假使lambda表明式是运用匿名内部类完结的,那么各个lambda表明式都会在磁盘上生成一个class文件。当JVM启动时,那个class文件会被加载进来,因为具备的class文件都亟需在启动时加载并且在利用前确认,从而会造成JVM的启航变慢。

  2. 向后的扩大性:
    假如Java8的设计者从一伊始就接纳匿名内部类的点子,那么那将限制lambda表明式现在的使发展范围。

运用动态启用

Java8的设计者决定动用在Java7中新增的动态启用来耽误在运行时的加载策略。当javac编译代码时,它会捕获代码中的lambda表达式并且生成一个动态启用的调用地址(称为lambda工厂)。当动态启用被调用时,就会向lambda表达式暴发转换的地方再次回到一个函数式接口的实例。比如,在Collections.sort那么些例子中,它的字节码如下:

public static void main(java.lang.String[]);
    Code:
       0: iconst_3
       1: anewarray     #2                  // class java/lang/String
       4: dup
       5: iconst_0
       6: ldc           #3                  // String shekhar
       8: aastore
       9: dup
      10: iconst_1
      11: ldc           #4                  // String rahul
      13: aastore
      14: dup
      15: iconst_2
      16: ldc           #5                  // String sameer
      18: aastore
      19: invokestatic  #6                  // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
      22: astore_1
      23: invokedynamic #7,  0              // InvokeDynamic #0:compare:()Ljava/util/Comparator;
      28: astore_2
      29: aload_1
      30: aload_2
      31: invokestatic  #8                  // Method java/util/Collections.sort:(Ljava/util/List;Ljava/util/Comparator;)V
      34: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      37: aload_1
      38: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      41: return
}

地点代码的要紧部分位于第23行23: invokedynamic #7, 0 // InvokeDynamic #0:compare:()Ljava/util/Comparator;此间开创了一个动态启用的调用。

接下去是将lambda表明式的内容转换来一个将会因而动态启用来调用的不二法门中。在这一步中,JVM完毕者有自由选用策略的义务。

那边自身仅简单的牢笼一下,具体的中间规范见那里
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html.

匿名类 vs lambda表达式

下边大家对匿名类和lambda表达式做一个相对而言,以此来分别它们的两样。

  1. 在匿名类中,this
    指代的是匿名类本身;而在lambda表明式中,this代替的是lambda表达式所在的那个类。

  2. You can shadow variables in the enclosing class inside the anonymous
    class,
    而在lambda表明式中就会报编译错误。(英文部分不会翻译,希望大家一同商讨下,感谢)

  3. lambda表达式的项目是由上下文决定的,而匿名类中必须在创制实例的时候肯定指定。

自家索要团结去写函数式接口吗?

Java8暗许带有众多可以直接在代码中运用的函数式接口。它们放在java.util.function包中,下边简单介绍多少个:

java.util.function.Predicate<T>

此函数式接口是用来定义对部分口径的反省,比如一个predicate。Predicate接口有一个叫test的法门,它需要一个T项目标值,再次回到值为布尔类型。例如,在一个names列表中找出富有以s千帆竞发的name就可以像如下代码那样使用predicate。

Predicate<String> namesStartingWithS = name -> name.startsWith("s");

java.util.function.Consumer<T>

本条函数式接口用于表现那多少个不要求发出其余输出的行为。Consumer接口中有一个名叫accept的艺术,它须要一个T花色的参数并且没有再次来到值。例如,用指定音信发送一封邮件:

Consumer<String> messageConsumer = message -> System.out.println(message);

java.util.function.Function<T,R>

其一函数式接口必要一个值并赶回一个结实。例如,假设须要将具备names列表中的name转换为大写,可以像上边那样写一个Function:

Function<String, String> toUpperCase = name -> name.toUpperCase();

java.util.function.Supplier<T>

这些函数式接口不要求传值,可是会回来一个值。它可以像上面那样,用来变化唯一的标识符

Supplier<String> uuidGenerator= () -> UUID.randomUUID().toString();

在接下去的章节中,大家会学习更加多的函数式接口。

Method references

偶然,你须求为一个一定措施创立lambda表明式,比如Function<String, Integer> strToLength = str -> str.length();,这一个表明式仅仅在String目的上调用length()办法。可以这么来简化它,Function<String, Integer> strToLength = String::length;。仅调用一个艺术的lambda表明式,可以用缩写符号来代表。在String::length中,String是目的引用,::是定界符,length是目标引用要调用的法子。静态方法和实例方法都足以应用形式引用。

Static method references

假如大家须要从一个数字列表中找出最大的一个数字,那大家可以像那样写一个格局引用Function<List<Integer>, Integer> maxFn = Collections::maxmax是一Collections里的一个静态方法,它须求传入一个List品种的参数。接下来你就足以如此调用它,maxFn.apply(Arrays.asList(1, 10, 3, 5))。上边的lambda表明式等价于Function<List<Integer>, Integer> maxFn = (numbers) -> Collections.max(numbers);

Instance method references

在那样的气象下,方法引用用于一个实例方法,比如String::toUpperCase是在一个String引用上调用
toUpperCase措施。还足以拔取带参数的点子引用,比如:BiFunction<String, String, String> concatFn = String::concatconcatFn能够如此调用:concatFn.apply("shekhar", "gulati")String``concat办法在一个String对象上调用并且传递一个近乎"shekhar".concat("gulati")的参数。

Exercise >> Lambdify me

上边通过一段代码,来采用所学到的。

public class Exercise_Lambdas {

    public static void main(String[] args) {
        List<Task> tasks = getTasks();
        List<String> titles = taskTitles(tasks);
        for (String title : titles) {
            System.out.println(title);
        }
    }

    public static List<String> taskTitles(List<Task> tasks) {
        List<String> readingTitles = new ArrayList<>();
        for (Task task : tasks) {
            if (task.getType() == TaskType.READING) {
                readingTitles.add(task.getTitle());
            }
        }
        return readingTitles;
    }

}

位置那段代码首先通过工具方法getTasks获取具有的Task,那里大家不去关爱getTasks方法的具体贯彻,getTasks可以透过webservice大概数据库恐怕内存获取task。一旦得到了tasks,咱们就过滤所有处于reading状态的task,并且从task中领取他们的标题,最后回到所有处于reading状态task的标题。

下边大家大致的重构下–在一个list上利用foreach和方法引用。

public class Exercise_Lambdas {

    public static void main(String[] args) {
        List<Task> tasks = getTasks();
        List<String> titles = taskTitles(tasks);
        titles.forEach(System.out::println);
    }

    public static List<String> taskTitles(List<Task> tasks) {
        List<String> readingTitles = new ArrayList<>();
        for (Task task : tasks) {
            if (task.getType() == TaskType.READING) {
                readingTitles.add(task.getTitle());
            }
        }
        return readingTitles;
    }

}

使用Predicate<T>来过滤tasks

public class Exercise_Lambdas {

    public static void main(String[] args) {
        List<Task> tasks = getTasks();
        List<String> titles = taskTitles(tasks, task -> task.getType() == TaskType.READING);
        titles.forEach(System.out::println);
    }

    public static List<String> taskTitles(List<Task> tasks, Predicate<Task> filterTasks) {
        List<String> readingTitles = new ArrayList<>();
        for (Task task : tasks) {
            if (filterTasks.test(task)) {
                readingTitles.add(task.getTitle());
            }
        }
        return readingTitles;
    }

}

使用Function<T,R>来将task中的title提取出来。

public class Exercise_Lambdas {

    public static void main(String[] args) {
        List<Task> tasks = getTasks();
        List<String> titles = taskTitles(tasks, task -> task.getType() == TaskType.READING, task -> task.getTitle());
        titles.forEach(System.out::println);
    }

    public static <R> List<R> taskTitles(List<Task> tasks, Predicate<Task> filterTasks, Function<Task, R> extractor) {
        List<R> readingTitles = new ArrayList<>();
        for (Task task : tasks) {
            if (filterTasks.test(task)) {
                readingTitles.add(extractor.apply(task));
            }
        }
        return readingTitles;
    }
}

把措施引用当着提取器来选取。

public static void main(String[] args) {
    List<Task> tasks = getTasks();
    List<String> titles = filterAndExtract(tasks, task -> task.getType() == TaskType.READING, Task::getTitle);
    titles.forEach(System.out::println);
    List<LocalDate> createdOnDates = filterAndExtract(tasks, task -> task.getType() == TaskType.READING, Task::getCreatedOn);
    createdOnDates.forEach(System.out::println);
    List<Task> filteredTasks = filterAndExtract(tasks, task -> task.getType() == TaskType.READING, Function.identity());
    filteredTasks.forEach(System.out::println);
}

大家也得以友善编排函数式接口,这样可以清晰的把开发者的来意传递给读者。我们可以写一个后续自Function接口的TaskExtractor接口。这么些接口的输入类型是稳定的Task品种,输出类型由落成的lambda表明式来控制。这样开发者就只须求关心输出结果的门类,因为输入的项目永远都以Task。

public class Exercise_Lambdas {

    public static void main(String[] args) {
        List<Task> tasks = getTasks();
        List<Task> filteredTasks = filterAndExtract(tasks, task -> task.getType() == TaskType.READING, TaskExtractor.identityOp());
        filteredTasks.forEach(System.out::println);
    }

    public static <R> List<R> filterAndExtract(List<Task> tasks, Predicate<Task> filterTasks, TaskExtractor<R> extractor) {
        List<R> readingTitles = new ArrayList<>();
        for (Task task : tasks) {
            if (filterTasks.test(task)) {
                readingTitles.add(extractor.apply(task));
            }
        }
        return readingTitles;
    }

}


interface TaskExtractor<R> extends Function<Task, R> {

    static TaskExtractor<Task> identityOp() {
        return t -> t;
    }
}

相关文章