Java 8新特性(2) Lambda 表达式

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。

Lambda语法

(parameters) -> expression
或
(parameters) ->{ statements; }
  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

Lambda 表达式实例

// 1. 不需要参数,返回值为 5
() -> 5

// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x

// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y

// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y

// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

完整的例子

public class TestLambda {

    public static void runThreadUseLambda() {
        //Runnable 是一个函数接口,只包含了有个无参数的,返回 void 的 run 方法;
        //所以 lambda 表达式左边没有参数,右边也没有 return,只是单纯的打印一句话
        new Thread(() ->System.out.println("lambda实现的线程")).start();
    }

    public static void runThreadUseInnerClass() {
        //这种方式就不多讲了,以前旧版本比较常见的做法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("内部类实现的线程");
            }
        }).start();
    }

    public static void main(String[] args) {
        TestLambda.runThreadUseLambda();
        TestLambda.runThreadUseInnerClass();
    }
}

方法和构造函数引用(::)

有时候 Lambda 表达式的代码就只是一个简单的方法调用而已,遇到这种情况,Lambda 表达式还可以进一步简化为方法引用(Method References)。一共有四种形式的方法引用:

引用静态方法

List<Integer> ints = Arrays.asList(3, 7, 2);  // [3, 7, 2]
// ints.sort((a,b) -> Integer.compare(a,b));  // [2, 3, 7]
ints.sort(Integer::compare);  // [2, 3, 7]

引用特定对象的实例方法(单个对象的)

List<Integer> ints = Arrays.asList(3, 7, 2);
// ints.forEach(x -> System.out.print(x));
ints.forEach(System.out::print);
/** output:
372
*/
@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}

class Something {
    String startsWith(String s) {
        return String.valueOf(s.charAt(0));
    }
}

Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);    // "J"

对特定类型的任意对象的实例方法的引用

String[] stringArray = {"Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda"};
Arrays.sort(stringArray, String::compareToIgnoreCase);
// Arrays.sort(stringArray, (a,b) -> a.compareToIgnoreCase(b));
System.out.println(Arrays.toString(stringArray));

构造函数

首先我们定义一个示例 bean,包含不同的构造方法:

class Person {
    String firstName;
    String lastName;

    Person() {}

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

接下来,我们定义一个 person 工厂接口,用来创建新的 person 对象:

interface PersonFactory<P extends Person> {
    P create(String firstName, String lastName);
}

然后我们通过构造函数引用来把所有东西拼到一起,而不是像以前一样,通过手动实现一个工厂来这么做。

// PersonFactory<Person> personFactory = (a,b) -> new Person(a,b);
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

我们通过 Person::new 来创建一个 Person 类构造函数的引用。Java 编译器会自动地选择合适的构造函数来匹配 PersonFactory.create 函数的签名,并选择正确的构造函数形式。

Lambda 的范围

对于 lambdab 表达式外部的变量,其访问权限的粒度与匿名对象的方式非常类似。你能够访问局部对应的外部区域的局部 final 变量,以及成员变量和静态变量。

访问局部变量

我们可以访问 lambda 表达式外部的 final 局部变量:

final int num = 1;  // final 不是必须的
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

num = 3; // 编译失败,即使 num 没有声明 final,也是 final 的,无法改变

访问成员变量和静态变量

与局部变量不同,我们在 lambda 表达式的内部能获取到对成员变量或静态变量的读写权。这种访问行为在匿名对象里是非常典型的。

class Lambda4 {
    static int outerStaticNum;
    int outerNum;

    void testScopes() {
        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
}

访问默认接口方法

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

Formula formula = new Formula() {  // 默认方法可以在匿名对象中使用
    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};

formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0

默认方法无法在lambda表达式内部被访问。因此下面的代码是无法通过编译的:

Formula formula = (a) -> sqrt( a * 100);

参考

https://blog.csdn.net/zxhoo/article/details/38349011
https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
http://www.runoob.com/java/java8-lambda-expressions.html
https://wizardforcel.gitbooks.io/modern-java/content/ch1.html


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:Java 8新特性(2) Lambda 表达式

文章字数:1.3k

本文作者:Bin

发布时间:2019-03-13, 21:07:22

最后更新:2019-08-06, 00:07:35

原始链接:http://coolview.github.io/2019/03/13/Java8/Java%208%E6%96%B0%E7%89%B9%E6%80%A7(2)%20Lambda%20%E8%A1%A8%E8%BE%BE%E5%BC%8F/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录