Java 8中的Lambda表达式(基于Java 8 Tutorial)

来源:互联网 发布:sai软件多大 编辑:程序博客网 时间:2024/05/17 09:16

参考地址:http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

本文档下载地址:点击打开链接

说明:主要讲解java 8中的Lambda表达式,还有一些functional interface的内容。内容完全基于java 8 tutorial,加上一些自己的注释与理解。

使用代码本身来进行解释(这是java 8 tutorial中的风格),同时去掉一些无关紧要的知识点(比如泛型等),并且增加了一些自己的必要的注释,

相信更易于大家理解。

作者:zhrb from jmu

注意:

1.页面查看效果很差,还是下载word附件进行查看。

2.在word中打开“导航窗格”或”文档结构图”更方便查看。

Lambda Expressions

应用背景:现在希望对List<Person> roster中符合条件的Person进行一些操作,比如输出姓名等..

 Approach 1: Create Methods That Search forMembers That Match One Characteristic

public static void printPersonsOlderThan(List<Person> roster, int age) {    for (Person p : roster) {        if (p.getAge() >= age) {            p.printPerson();        }    }}

Approach 2: Create More Generalized SearchMethods

public static void printPersonsWithinAgeRange(    List<Person> roster, int low, int high) {    for (Person p : roster) {        if (low <= p.getAge() && p.getAge() < high) {            p.printPerson();        }    }}


Approach 3: Specify Search Criteria Code ina Local Class

public static void printPersons(    List<Person> roster, CheckPerson tester) {    for (Person p : roster) {        if (tester.test(p)) {            p.printPerson();        }    }}interface CheckPerson {    boolean test(Person p);}class CheckPersonEligibleForSelectiveService implements CheckPerson {    public boolean test(Person p) {        return p.gender == Person.Sex.MALE &&            p.getAge() >= 18 &&            p.getAge() <= 25;    }}


使用:printPersons(roster, new CheckPersonEligibleForSelectiveService());

Approach 4: Specify Search Criteria Code inan Anonymous Class

printPersons(    roster,    new CheckPerson() {        public boolean test(Person p) {            return p.getGender() == Person.Sex.MALE                && p.getAge() >= 18                && p.getAge() <= 25;        }    });


Approach 5: Specify Search Criteria Codewith a Lambda Expression

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


Approach 6: Use Standard FunctionalInterfaces with Lambda Expressions

interface CheckPerson {    boolean test(Person p);}interface Predicate<T> {//functional interface    boolean test(T t);}


可以看出CheckPerson接口与Predicate<T>接口中定义的方法,都是传递进一个类型,返回boolean,形式上基本一致。

我们现在使用Predicate<T>接口取代CheckPerson接口(好处:少写一个接口,直接用通用接口)修改代码如下:

方法定义
public static void printPersonsWithPredicate(    List<Person> roster, Predicate<Person> tester) {    for (Person p : roster) {        if (tester.test(p)) {            p.printPerson();        }    }//使用匿名内部类}

不需要再自定义一个CheckPerson接口,直接使用Predicate<Person>即可。

方法调用:
printPersonsWithPredicate(    roster,    p -> p.getGender() == Person.Sex.MALE        && p.getAge() >= 18        && p.getAge() <= 25);
Approach 7: Use Lambda ExpressionsThroughout Your Application
<pre code_snippet_id="315621" snippet_file_name="blog_20140427_9_7239644" name="code" class="java">public static void printPersonsWithPredicate(    List<Person> roster, Predicate<Person> tester) {    for (Person p : roster) {        if (tester.test(p)) {            p.printPerson();        }    }}

上面的所有代码,对符合条件的Person只能print,如果需要进行更多的操作,怎么办?

方法定义:
public static void processPersons(    List<Person> roster,    Predicate<Person> tester,    Consumer<Person> block) {        for (Person p : roster) {            if (tester.test(p)) {                block.accept(p);//一个没有返回值的操作            }        }}
</pre>

其中Consumer<Person>也是jdk中的functional interface,有一个方法void accept(T t),这个方法的特点刚好和printPerson()一样,没有任何的返回值

(原理类似于Approach 6中讲的内容)。上述定义也适合任何没有返回值的操作,

改进的方法调用:
processPersons(     roster,     p -> p.getGender() == Person.Sex.MALE         && p.getAge() >= 18         && p.getAge() <= 25,     p -> p.printPerson()//实际上,这行还可以换成其他操作);

可以看到processPersons不仅可以printPerson还可以调用其他方法,只要该方法的形式符合void accept(T t)….即1.无返回值。2.接受一个类型的参数。

进一步的,如果不仅要对符合条件的person进行“一个没有返回值的操作”,如上面的block.accept(p),还希望对他进行一些有返回值的操作,则可以使用Function<T,R>这个functional interface,该接口包含一个R apply(T t),如定义Function<Person, String>,那么就包含一个String apply(Person p)这样的操作。也就是说可以对符合条件的用户,执行一些操作并返回一个String值。

修改的定义如下:
public static void processPersonsWithFunction(    List<Person> roster,    Predicate<Person> tester,    Function<Person, String> mapper,    Consumer<String> block) {    for (Person p : roster) {        if (tester.test(p)) {            String data = mapper.apply(p);//有返回值的操作            block.accept(data);//无返回值的操作        }    }}
调用如下:
processPersonsWithFunction(    roster,    p -> p.getGender() == Person.Sex.MALE        && p.getAge() >= 18        && p.getAge() <= 25,    p -> p.getEmailAddress(),//有返回值的操作    email -> System.out.println(email) //无返回值的操作);<span style="background-color: rgb(255, 255, 255); font-family: Arial, Helvetica, sans-serif;">Approach 8: Use Generics More Extensively</span>

更通用的写法,比如下面这个函数,可以针对所有实现Iterable接口的实现类(如列表,数组)进行Predicate、Function与Consumer操作。不过通用是通用,但我觉得一般还是不要这么写。因为要有许多前期知识(如functional interface,泛型)才能看懂这个代码,降低代码的可阅读性。

方法定义:
public static <X, Y> void processElements(    Iterable<X> source,    Predicate<X> tester,    Function <X, Y> mapper,    Consumer<Y> block) {    for (X p : source) {        if (tester.test(p)) {            Y data = mapper.apply(p);            block.accept(data);        }    }}
方法调用:
processElements(    roster,    p -> p.getGender() == Person.Sex.MALE        && p.getAge() >= 18        && p.getAge() <= 25,    p -> p.getEmailAddress(),    email -> System.out.println(email));

Approach 9: Use Aggregate Operations ThatAccept Lambda Expressions as Parameters

将Lambda当做参数…
roster.stream().filter(        p -> p.getGender() == Person.Sex.MALE            && p.getAge() >= 18            && p.getAge() <= 25)    .map(p -> p.getEmailAddress())    .forEach(email -> System.out.println(email));

Lambda Expressions in GUI Applications

btn.setOnAction(new EventHandler<ActionEvent>() {//匿名类             @Override            public void handle(ActionEvent event) {                System.out.println("Hello World!");            }        });
改进,使用Lambda
btn.setOnAction(          event -> System.out.println("Hello World!")        );

Syntax of Lambda Expressions

一个Lambda表达式由以下几部分构成:

1.A comma-separated list of formalparameters enclosed in parentheses.

一个用逗号分隔的形参列表,要用两个圆括号包起来。前面的例子,p就是形式参数,因为只有一个形参,所以不需要使用括号。不需要指定参数的类型,比如p。

 某Lambda表达式:

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

2.The arrow token, ->

右箭头

3. A body, which consists of a singleexpression or a statement block

比如上面的

p.getGender() == Person.Sex.MALE     && p.getAge() >= 18    && p.getAge() <= 25

如果指定一个表达式,Java runtime计算该表达式然后返回其值。

还有另外一种定义法,使用一个return语句。

p -> {    return p.getGender() == Person.Sex.MALE        && p.getAge() >= 18        && p.getAge() <= 25;}

return语句不是表达式,在lambda表达式中,你必须使用{}把他括起来。不过你不用将一个返回值为void的方法括起来,比如:

email -> System.out.println(email)

lambda表达式看起来很像一个方法声明。你可以把lambda表达式当做一个匿名方法(没有名字的方法)。下面这个例子Calculator中,lambda表达式看起来不仅仅是一个形式参数。

public class Calculator {    interface IntegerMath {        int operation(int a, int b);       }    public int operateBinary(int a, int b, IntegerMath op) {        return op.operation(a, b);    }    public static void main(String... args) {        Calculator myApp = new Calculator();        IntegerMath addition = (a, b) -> a + b;        IntegerMath subtraction = (a, b) -> a - b;        System.out.println("40 + 2 = " +            myApp.operateBinary(40, 2, addition));        System.out.println("20 - 10 = " +            myApp.operateBinary(20, 10, subtraction));        }}

Accessing Local Variables of the Enclosing Scope

import java.util.function.Consumer;public class LambdaScopeTest {    public int x = 0;    class FirstLevel {        public int x = 1;        void methodInFirstLevel(int x) {            // The following statement causes the compiler to generate            // the error "local variables referenced from a lambda expression            // must be final or effectively final" in statement A:            //            // x = 99;                        Consumer<Integer> myConsumer = (y) ->             {                System.out.println("x = " + x); // Statement A                System.out.println("y = " + y);                System.out.println("this.x = " + this.x);                System.out.println("LambdaScopeTest.this.x = " +                    LambdaScopeTest.this.x);            };            myConsumer.accept(x);        }    }    public static void main(String... args) {        LambdaScopeTest st = new LambdaScopeTest();        LambdaScopeTest.FirstLevel fl = st.new FirstLevel();        fl.methodInFirstLevel(23);    }}

This example generates the followingoutput:

x = 23
y = 23
this.x = 1
LambdaScopeTest.this.x = 0

1.首先看注释的那些语句,如x=99,和methodInFirstLevel(intx)相冲突,所以会引起编译错误。当然如果将x=99定义成final x =99,可能就没问题了

2.可以看到Lambda表达式中可以通过this.x访问所在类的x属性,还可以通过LambdaScopeTest.this.x访问外部类的x属性。

Target Typing

Lambda表达式类型如何确定?

比如下面的Lambda表达式

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

public staticvoid printPersons(List<Person> roster, CheckPerson tester)类型是CheckPerson

public voidprintPersonsWithPredicate(List<Person> roster, Predicate<Person> tester)类型是Predicate<Person>

也就是说所期望的类型(CheckPerson、Predicate<Person>)叫做target type。

Java编译器可以通过target type的使用上下文或lambda表达式被找到的所在地来决定lambda表达式的类型。

Target Types and Method Arguments

对于方法参数,Java编译器使用其他两种语言特性来决定target type。

这两种语言特性是:1.overload resolution 2.type argument inference

public interface Runnable {    void run();}public interface Callable<V> {    V call();}

可以看到两种方法返回值不同,假设我们定义了两个重载方法invoke方法

void invoke(Runnable r) {    r.run();}<T> T invoke(Callable<T> c) {    return c.call();}

那么,如果String s = invoke(() -> "done");调用的是哪一个invoke方法呢?

这个我就不说了,大家自己应该能猜出来。

 

Serialization

可以序列化一个lambda表达式,但是强烈建议大家不要序列化lambda表达式。

0 0
原创粉丝点击