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

参考地址: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表达式。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值