译-- Lambda Expressions >=java SE8 (Lambda表达式详解)

来源:互联网 发布:王师就剩一个连 知乎 编辑:程序博客网 时间:2024/04/27 22:25

原文地址:http://stackoverflow.com/documentation/java/91/lambda-expressions#t=201701170111285810613

Introduction

Lambda表达式用一个表达式提供了一个实现单个接口方法(函数式接口)的简洁明了的方式。他允许你减少你必须创建和维护的代码数量,它经常被用作匿名内部类的替代。

Examples


Introduction to Java lambdas (介绍java lambda)

Functional Interfaces (函数式接口)

Lambdas 只能在一个仅包含一个抽象方法的函数式接口上操作。函数式接口可以有任意的default 或着static 方法。(为此, 函数式接口有时候是说具有单个抽象方法的接口, 或着SAM interfaces)。

interface Foo1 {    void bar();}interface Foo2 {    int bar(boolean baz);}interface Foo3 {    String bar(Object baz, int mink);}interface Foo4 {    default String bar() { // default so not counted        return "baz";    }    void quux();}

当声明 函数式接口时@FunctionalInterface注解可以被加上,虽然它没有明确的作用, 但是如果一个注解被用于非函数式接口一个compiler error 将会产生,因此充当一个接口不应该被改变的提醒者。

@FunctionalInterfaceinterface Foo5 {    void bar();}@FunctionalInterfaceinterface BlankFoo1 extends Foo3 { // inherits abstract method from Foo3}@FunctionalInterfaceinterface Foo6 {    void bar();    boolean equals(Object obj); // overrides one of Object's method so not counted}

相反的, 它不是一个函数式接口, 因为它不止有一个抽象方法。

interface BadFoo {    void bar();    void quux(); // <-- Second method prevents lambda: which one should be considered as lambda?}

它也不是一个函数式接口, 因为它没有任何方法。

interface BlankFoo2 { }

java 8 也在java.util.function 中提供了很多基本的模版函数式接口, 例如, 内置的接口 Predicate<T> 包含了一个单个方法, 输入一个值T 并且放回一个boolean

Lambda Expressions

Lambda表达式的基本结构是:
这里写图片描述
fi 将会持有一个实现了FunctionalInterface 接口的匿名类的实例,匿名类中一个方法的定义为{System.out.println("Hello"); }。 换句话说,等价于:

FunctionalInterface fi = new FunctionalInterface() {    @Override    public void theOneMethod() {        System.out.println("Hello");    }};

你不能在使用lambda时明确一个方法名,反而根本不需要,因为函数式接口必须有一个抽象方法, 所以java重写了它。

一旦lambda的类型不确定,(e.g. 重写方法)你可以给lambda添加一个强转型告诉编译器它的类型,就像:

Object fooHolder = (Foo1) () -> System.out.println("Hello");System.out.println(fooHolder instanceof Foo1); // returns true

如果函数式接口的单个方法包含参数,它们的本地变量名应该出现在lambda的方括号中。没有必要去声明参数的类型或着返回值的类型,因为他们能从接口定义中推理得出(当然如果你想声明参数类型, 这也不是一个错误)。因此,如下两个样例是等价的:

Foo2 longFoo = new Foo2() {    @Override    public int bar(boolean baz) {        return baz ? 1 : 0;    }};Foo2 shortFoo = (x) -> { return x ? 1 : 0; };

如果函数只有一个参数,参数两边的圆括号可以省略:

Foo2 np = x -> { return x ? 1 : 0; }; // okayFoo3 np2 = x, y -> x.toString() + y // not okay

Implicit Returns

(隐式返回)
如果被放在lambda中的代码是一个java 表达式而不是一个声明,它就会被当作一个返回这个表达式值的方法,因此,下面这两个是等价的:

IntUnaryOperator addOneShort = (x) -> (x + 1);IntUnaryOperator addOneLong = (x) -> { return (x + 1); };

Accessing Local Variables (value closures)

(访问本地变量(值闭包))
因为lambdas 是匿名内部类的简化写法,它们遵循在一个闭合的域中访问本地变量相同的规则;变量必须被当作final 并且在lambda表达式中不能够被修改。

IntUnaryOperator makeAdder(int amount) {    return (x) -> (x + amount); // Legal even though amount will go out of scope                                // because amount is not modified}IntUnaryOperator makeAccumulator(int value) {    return (x) -> { value += x; return value; }; // Will not compile}

如果以这种方式包含一个可改变的变量是必要的, 一个包含此变量的拷贝的合法对象应该被使用, Read more in\[Java Closures with lambda expressions.]

Accepting Lambdas

(接收lambdas)
因为lambda是一个接口的实现,去使一个方法接收lambda并没有什么特别的要做:任何函数只要是函数式接口都能够接收一个lambda。

public void passMeALambda(Foo1 f) {    f.bar();}passMeALambda(() -> System.out.println("Lambda called"));

Using Lambda Expressions to Sort a Collection

(使用lambda表达式去排序一个集合)

在java 8 之前, 当排序一个集合的时候, 用一个匿名(或着 有名字)类去实现java.util.Comparator 接口是必要的:

Java SE 1.2Collections.sort(    personList,    new Comparator<Person>() {        public int compare(Person p1, Person p2){            return p1.getFirstName().compareTo(p2.getFirstName());        }    });

从java 8 开始, 匿名内部类能够被lambda表达式替代, 注意到p1p2 参数能够被忽略, 因为编译器能够自动的推断出它们。

Collections.sort(    personList,     (p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName()));

这个例子能够被简化通过使用Comparator.comparingmethod references (方法引用), 用:: (双冒号)符号来表达:

Collections.sort(    personList,    Comparator.comparing(Person::getFirstName));

静态导入允许我们更加简明的去表达它, 但是对于是否能够提高整体可读性是备受争论的。

import static java.util.Collections.sort;import static java.util.Comparator.comparing;//...sort(personList, comparing(Person::getFirstName));

Comparators构建这种方式可以用来链式调用。例如, 通过名字比较之后, 如果有一些人具有相同的名字, 那么thenComparing 方法将会根据性别来接着比较

sort(personList, comparing(Person::getFirstName).thenComparing(Person::getLastName));

Method References

(方法引用)

方法引用允许提前定义的静态或着实例方法去绑定到一个合适的函数式接口来当作参数传递,而不是用一个匿名的lambda表达式。

假设我们有一个模型:

class Person {    private final String name;    private final String surname;    public Person(String name, String surname){        this.name = name;        this.surname = surname;    }    public String getName(){ return name; }    public String getSurname(){ return surname; }}List<Person> people = getSomePeople();

Instance method reference (to an arbitrary instance)

实例方法引用(对于一个任意的实例)

people.stream().map(Person::getName)

等价的lambda:

people.stream().map(person -> person.getName())

在这个例子中, 对于一个Person 类的实例方法getName() 的一个方法引用被传递。因为它被当作一个集合类型, 实例上的方法(之后被察觉)将会被调用 。


Instance method reference (to a specific instance)

实例方法引用(对于一个特定类型)

people.forEach(System.out::println);

因为System.out 是一个PrintStream 的实例,对这个特定的实例的一个方法引用被当作一个参数传递。
等价的lambda表达式:

people.forEach(person -> System.out.println(person));

Static method reference

(静态的方法引用)

对于转换流,我们能够使用静态方法引用

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);numbers.stream().map(String::valueOf)

这个例子传递了一个String类型的valueOf() 静态方法引用, 因此valueOf() 在集合中的实例对象中被当做一个参数传递。
等价的lambda:

 numbers.stream().map(num -> String.valueOf(num))

Reference to a constructor

(构造器引用)

List<String> strings = Arrays.asList("1", "2", "3");strings.stream().map(Integer::new)

读Collect Elements of a Stream into a Collection看看如何收集元素到集合中。
唯一的一个Integer 的String参数构造器在这里被使用, 通过一个被当作参数提供的String来构造一个整数,只要这个string代表一个数字, 流将会被转化为整数。等价的lambda:

Collect Elements of a Stream into a Collection

Cheat-Sheet

备忘单

Method Reference Format Code Equivalent Lambda Static method TypeName::method (args) -> TypeName.method(args) Non-static method (from instance) instance::method (args) -> instance.method(args) Non-static method (no instance) TypeName::method (instance, args) -> instance.method(args) Constructor TypeName::new (args) -> new TypeName(args)

Implementing multiple interfaces

(实现多个接口)

有时候你想使lambda表达式实现多个接口,使用标记式接口(例如java.io.Serializable)是很有用的, 因为他们不添加任何抽象方法。

例如你想使用一个客户自定义Comparator 创建一个TreeSet, 接着序列化它, 并通过网路发送它 。一般方法:

TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));

并不生效, 因为对Comparator的lambda没有实现Serialization, 你能够修正它通过使用交叉类型, 并且显式的明确这个lambda是需要序列化的:

TreeSet<Long> ts = new TreeSet<>(    (Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));

如果你平凡的使用交叉类型(例如, 你正在使用一个譬如几乎所有东西都必须序列化的 Apache Spark 框架), 你能够创建一个空的接口并在你的代码中使用它们。

public interface SerializableComparator extends Comparator<Long>, Serializable {}public class CustomTreeSet {  public CustomTreeSet(SerializableComparator comparator) {}}

这样你就保证了传递的comparator接口将会被序列化。


Java Closures with lambda expressions.

(java lambda表达式闭包)


当一个lambda表达式引用一个封闭域(全局或局部)内的变量, 一个lambda闭包被创建。这样做的规则和内联方法以及匿名类是相同的。

来自一个闭合域中的本地变量在一个lambda内部被使用时必须是final。在java 8 (最早的支持lambdas的版本)中不需要在外部上下文中声明final , 但是必须(当作final)来对待。例如:

int n = 0; // With Java 8 there is no need to explicit finalRunnable r = () -> { // Using lambda    int i = n;    // do something};

只要值n 变量没有被改变,它就是合法的。如果你尝试去在lambda的外部或内部去改变这个变量, 你将会得到下面的编译错误:

“local variables referenced from a lambda expression must be final or
effectively final”.

例如:

int n = 0;Runnable r = () -> { // Using lambda    int i = n;    // do something};n++; // Will generate an error.

如果在lambda里面必须使用一个可改变的变量, 正常的方法是声明一个对此变量的final 拷贝,然后使用这个拷贝。例如:

int n = 0;final int k = n; // With Java 8 there is no need to explicit finalRunnable r = () -> { // Using lambda    int i = k;    // do something};n++;      // Now will not generate an errorr.run();  // Will run with i = 0 because k was 0 when the lambda was created

自然地, lambda的body体里面对原始变量的改变是不可见的。

注意到java 不支持真正的闭包, 一个java lambda不能够以一种能够看到在它所被实例化的环境中的变量的改变的方式被创建 。如果你想实现一个能够对它的环境进行观察或做出改变的闭包, 你应该使用一个合法的类“聚集”它。例如:

// Does not compile ...public IntUnaryOperator createAccumulator() {    int value = 0;    IntUnaryOperator accumulate = (x) -> { value += x; return value; };    return accumulate;}

以上将不会被编译,由于之前讨论的原因。我们能够绕过编译错误,如下:

// Does not compile ...public IntUnaryOperator createAccumulator() {    int value = 0;    IntUnaryOperator accumulate = (x) -> { value += x; return value; };    return accumulate;}

这个问题是IntUnaryOperator 接口设计契约的打破, 它声明实例应该是函数式的并且无状态的。如果一个闭包被传递进一个可以接收函数式对象的内置函数式接口, 这是很容易造成冲突和错误的行为。解封装的易变状态的闭包应当被实现为一个合法类。例如:

// Correct ...public class Accumulator {   private int value = 0;   public int accumulate(int x) {      value += x;      return value;   }}

Lambda - Listener Example

Lambda - Listener 示例

匿名类listener

JButton btn = new JButton("My Button");btn.addActionListener(new ActionListener() {    @Override    public void actionPerformed(ActionEvent e) {        System.out.println("Button was pressed");    }});

Lambda listener

JButton btn = new JButton("My Button");btn.addActionListener(e -> {    System.out.println("Button was pressed");});

Using lambda expression with your own functional interface

在你的函数式接口中使用Lambda

Lambda 意味着为单个方法的接口提供一个内联实现代码和用一种常规变量的方式来传递它们的能力,正如我们所曾做的。我们把它叫做函数式接口。

例如, 用一个匿名类来写一个Runnable , 然后启动一个线程,就像这样:

//Old waynew Thread(        new Runnable(){            public void run(){                System.out.println("run logic...");            }        }).start();//lambdas, from Java 8new Thread(        ()-> System.out.println("run logic...")).start();

现在, 和上面一致, 你们有一些客户端接口:

interface TwoArgInterface {    int operate(int a, int b);}

在你的代码中你怎么使用Lambda去给出这个接口的实现? 和上述Runnable接口示例一样, 看下面的驱动程序:

public class CustomLambda {    public static void main(String[] args) {        TwoArgInterface plusOperation = (a, b) -> a + b;        TwoArgInterface divideOperation = (a,b)->{            if (b==0) throw new IllegalArgumentException("Divisor can not be 0");            return a/b;        };        System.out.println("Plus operation of 3 and 5 is: " + plusOperation.operate(3, 5));        System.out.println("Divide operation 50 by 25 is: " + divideOperation.operate(50, 25));    }}

return only returns from the lambda, not the outer method

return 仅仅从Lambda中返回, 而不是外部方法)

return 仅仅从Lambda中返回, 而不是外部方法)
当心这不同于ScalaKotlin!

void threeTimes(IntConsumer r) {  for (int i = 0; i < 3; i++) {    r.accept(i);  }}void demo() {  threeTimes(i -> {    System.out.println(i);    return; // Return from lambda to threeTimes only!  });}

当尝试用特有语言结构, 这会导致无法预期的异常, 譬如内置的结构:for 循环 return 表现的不同:

void demo2() {  for (int i = 0; i < 3; i++) {    System.out.println(i);    return; // Return from 'demo2' entirely  }}

在Scala 和 Kotlin , demodemo2 都仅仅打印0, 但这并不是始终如一的, java方法和refactoring和 类的使用是一致的 - return 在代码的顶部和底部表现相同:

void demo3() {  threeTimes(new MyIntConsumer());}class MyIntConsumer implements IntConsumer {  public void accept(int i) {    System.out.println(i);    return;  }}

因此, java 的return 和类方法和refactoring更为一致, 但和内置的 forwhile 不具一致性, 保留了它们的特殊性。
由此, 解析来两个案例在java 中是等价的:

IntStream.range(1, 4)    .map(x -> x * x)    .forEach(System.out::println);IntStream.range(1, 4)    .map(x -> { return x * x; })    .forEach(System.out::println);

此外, try-with-resources 的使用在java中是安全的:

class Resource implements AutoCloseable {  public void close() { System.out.println("close()"); }}void executeAround(Consumer<Resource> f) {  try (Resource r = new Resource()) {    System.out.print("before ");    f.accept(r);    System.out.print("after ");  }}void demo4() {  executeAround(r -> {    System.out.print("accept() ");    return; // Does not return from demo4, but frees the resource.  });}

将会打印before accept() after close() 。 在Scala 和Kotlin 语义中try-with-resources 将不会被关闭, 将仅仅打印出before accept()


Lambdas and Execute-around Pattern

Lambdas 和 执行-环绕模式

在一些简单的场景中, 作为函数式接口, 有一些使用lambdas好的样例, 一个相对常见的能够被lambdas所增强的用例是被称为Execute-Around模式, 在这个模式中, 你有一组标准的setup/teardown 代码, 很多场景需要被用例特定的代码去环绕, 一些通用的示例就是file io , database io , try / catch 代码块。

interface DataProcessor {    void process( Connection connection ) throws SQLException;;}public void doProcessing( DataProcessor processor ) throws SQLException{    try (Connection connection = DBUtil.getDatabaseConnection();) {        processor.process(connection);        connection.commit();    } }

接着用lambda 来调用这个方法,看起来像下面这样:

public static void updateMyDAO(MyVO vo) throws DatabaseException {    doProcessing((Connection conn) -> MyDAO.update(conn, ObjectMapper.map(vo)));}

它并不限于I/O操作, 它能够应用于和setup/tear down类似且变量较少的任务的任何场景。这种模式的主要好处是代码重用 和 强制DRY(Don’t Repeat Yourself)。


Traditional style to Lambda style

传统的Lambda风格

Traditional way

interface MathOperation{    boolean unaryOperation(int num);}public class LambdaTry {    public static void main(String[] args) {        MathOperation isEven = new MathOperation() {            @Override            public boolean unaryOperation(int num) {                return num%2 == 0;            }        };        System.out.println(isEven.unaryOperation(25));        System.out.println(isEven.unaryOperation(20));    }}

Lambda style

1、移除类名和函数式接口体

public class LambdaTry {    public static void main(String[] args) {        MathOperation isEven = (int num) -> {            return num%2 == 0;        };        System.out.println(isEven.unaryOperation(25));        System.out.println(isEven.unaryOperation(20));    }}

2、可选的类型的声明

MathOperation isEven = (num) -> {    return num%2 == 0;};

3、可选的参数两边括弧, 如果是一个参数

MathOperation isEven = num -> {    return num%2 == 0;};

4、可选的花括号, 如果在函数体中只有一行
5、可选的返回值, 如果在函数体中只有一行

MathOperation isEven = num -> num%2 == 0;

Lambdas and memory utilization

Lambdas 和 内存利用

因为Java lambda是闭包的, 它们能够 “捕获” 在闭合作用域中变量的值, 然而并不是所有的lambda都能捕获 – 简单的lambdas 就像 s -> s.length() 什么都没有捕获, 被称作 无状态的 – 捕获形lambdas 要求一个临时的对象去持有这个被捕获的变量, 在这个代码片中, 这个lambda () -> j 是一个捕获型lambda, 并且在被使用时可能造成一个对象被分配内存。

public static void main(String[] args) throws Exception {    for (int i = 0; i < 1000000000; i++) {        int j = i;        doSomethingWithLambda(() -> j);    }}

虽然并不会很快的变得显而易见, 因为new 关键字并没有在任何地方出现, 但是这个代码创建了1000000000 个独立的() -> j lambda实例。


Using lambda expressions & predicates to get a certain value(s) from a list

使用lambda条件表达式来从列表中获取某些值

从java 8 开始, 你能够使用lambda 表达式 & predicates。
例如: 使用lambda 表达式 & predicates 从列表中获取某个值, 在这个样例中, 如果他们具有大于18岁的事实就会被打印出来, 反之不会。
Person Class:

public class Person {    private String name;    private int age;    public Person(String name, int age) {        this.name = name;        this.age = age;    }    public int getAge() { return age; }    public String getName() { return name; }}

内置的来自java.util.function.Predicate 包中的接口Predicate是一个函数式接口,并有一个boolean test(T t) 方法。
示例用法:

import java.util.ArrayList;import java.util.List;import java.util.function.Predicate;public class LambdaExample {    public static void main(String[] args) {        List<Person> personList = new ArrayList<Person>();        personList.add(new Person("Jeroen", 20));        personList.add(new Person("Jack", 5));        personList.add(new Person("Lisa", 19));        print(personList, p -> p.getAge() >= 18);    }    private static void print(List<Person> personList, Predicate<Person> checker) {        for (Person person : personList) {            if (checker.test(person)) {                System.out.print(person + " matches your expression.");            } else {                System.out.println(person  + " doesn't match your expression.");            }        }    }}

这个print(personList, p -> p.getAge() >= 18); 方法采用一个lambda表达式(因为Predicate 被用于作为一个参数), 你能定义自己所需要的表达式, checker的test方法检查表达式正确与否:checker.test(person) 。你可以轻易的去把它变成其他的, 例如print(personList, p -> p.getName().startsWith("J")); 它会检查人的名字是否以字母“J”开头。


Syntax

() -> { return expression; } // Zero-arity with function body to return a value.() -> expression // Shorthand for the above declaration; there is no semicolon for expressions.() -> { function-body } // Side-effect in the lambda expression to perform operations.parameterName -> expression // One-arity lambda expression. In lambda expressions with only one argument, the parenthesis can be removed.(Type parameterName, Type secondParameterName, ...) -> expression // lambda evaluating an expression with parameters listed to the left(parameterName, secondParameterName, ...) -> expression // Shorthand that removes the parameter types for the parameter names. Can only be used in contexts that can be inferred by the compiler where the given parameter list size matches one (and only one) of the size of the functional interfaces expected.
0 0
原创粉丝点击