Java 8

来源:互联网 发布:淘宝客服与买家对话 编辑:程序博客网 时间:2024/05/16 01:31

本篇来自于 State of Lambda by Brian Goetz

Java 8 包括的首要的语言新特性有:

  • Lambda 表达式 (非正式式的称为”闭包”或”匿名方法”)
  • 方法和构造器引用
  • 扩展的目标类型和类型推导
  • 接口中缺省的(default) 和静态的(static)方法

一. 背景。
Java面向对象编程,有些对象仅仅只有一个方法,典型的情况是 一个Java API定义了一个接口 (所谓 回调接口), 用户通过匿名实例化这个接口(匿名内部类),然后调用这个API。 例如:

public interface Runnable{ //functional interface    public void run();}public class Foo {    public static void main(String[] args) {        Runnable runnable = new Runnable() {            public void run() {                System.out.println("new thread");            }        };        new Thread(runnable).start();    }}

匿名内部类有些缺点, 首先:

  • 代码冗余
  • 内部类的this和变量名容易误解
  • 类型载入和实例创建语义不够灵活
  • 无法访问非final的本地变量
  • 无法抽象控制流

考虑到对函数式编程的支持,Java 8增加了Lambda表达式的语言新特性,这样可以简化 实现只有一个方法的匿名内部类的代码。

二. 函数接口(Function Interface)。
Java 8 以前的版本,接口(interface)中只能定义没有实现的方法(没有方法体 body)。这有很大的弊端,一旦接口改变增加一个新的方法,所有实现此接口的类都要改变。为此Java 8 接口支持 缺省接口方法(修饰符default) 和 静态接口方法(修饰符static),这两种方法都有实现的body。

如果接口中只有一个没有实现的方法 如 Runnable, 这种类型的接口称为 函数接口。(如果我们定义一个函数接口,可以添加注解FunctionalInterface让编译器来检验是否是函数接口)。

例如 Java 7 定义的函数接口有:

java.lang.Runnablejava.util.concurrent.Callablejava.security.PrivilegedActionjava.util.Comparatorjava.io.FileFilterjava.beans.PropertyChangeListener

另外,Java 8 添加了个新的包,java.util.function, 包含一些常用的函数接口,如:

Predicate<T> -- a boolean-valued property of an objectConsumer<T> -- an action to be performed on an objectFunction<T,R> -- a function transforming a T to a RSupplier<T> -- provide an instance of a T (such as a factory)UnaryOperator<T> -- a function from T to TBinaryOperator<T> -- a function from (T, T) to T

其他常用的函数接口 如:

java.io.FileFilter

三. Lambda 表达式
匿名内部类最大的痛点是笨重,如前面的Runnable实例有5行代码。
Lambda表达式是匿名方法,针对函数接口,目的以一个轻量级的机制取代内部类的机制。
Lambda表达式的例子如:

(int x, int y) -> x + y() -> 42(String s) -> { System.out.println(s); }

第一个表达式 带两个参数,返回它们的总数;
第二个表达式 未带参数,返回证书 42;
第三个表达式 带一个String参数,将这个参数打印到控制台,不返回。

Lambda表达式由三部分组成:

  • 一个参数列表
  • 一个箭头 ->
  • 一个方法体

前面的代码用lambda表达式可以写成:

public class Foo {    public static void main(String[] args) {        new Thread(()->System.out.println("new thread")).start();    }}

使用lambda表达式,代码很紧凑,语义清晰。

四. 目标类型
一个函数接口不是lambda表达式语法的一部分,一个lambda表达式代表什么类型的对象呢?它的类型根据上下文推导(type inference)。
如上面的代码,Thread构造器的参数类型是Runnable, 所以Lambda表达式
System.out.println(“new thread”) 代表一个Runnable实例。
run()方法匿名;没有返回值;方法体是 System.out.println(“new thread”)。
编译器负责推导每个lambda表达式的类型,称之为目标类型(Target Type)。

一个lambda表达式只能出现在一个目标类型是一个函数接口的上下文。
也就是, 一个lambda表达式能被赋值于一个目标类型T应该满足下面条件:

  • T 是一个函数接口类型
  • Lambda 表达式的参数和T的参数 (数量和类型)相同
  • Lambda 表达式的body返回的类型与T的方法返回类型兼容
  • Lambda 表达式的body抛出的异常能被T的方法抛出

因为参数类型可以推导出,所有可以省略, 如下面的 参数s1和s2的类型可以推导出是String, 所以省略。

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

如果只有一个参数,可以省略包围参数的括号。如下面的lambda表达式, (f) 省略成 f。

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

五. 目标类型的上下文
下面的上下文有目标类型
- 变量声明
- 返回声明
- 数组的初始化
- 方法的参数
- Lambda 表达式的bodies
- 条件表达式 (?:)
- 造型表达式 (Cast)

Comparator<String> c;c = (String s1, String s2) -> s1.compareToIgnoreCase(s2);public Runnable toDoLater() {  return () -> {    System.out.println("later");  };}Supplier<Runnable> c = () -> () -> { System.out.println("hi"); };Callable<Integer> c = flag ? (() -> 23) : (() -> 42);Object o = (Runnable) () -> { System.out.println("hi"); };

目标类型不限于lambda 表达式, Java 8支持泛型类型的目标类型。

List<String> ls =  Collections.checkedList(new ArrayList<>(), String.class);Set<Integer> si = flag ? Collections.singleton(23)                       : Collections.emptySet();

六. 词法作用域
在内部类中使用变量名(以及this)非常容易出错。内部类中通过继承得到的成员(包括来自Object的方法)可能会把外部类的成员掩盖(shadow),此外未限定(unqualified)的this引用会指向内部类自己而非外部类。

相对于内部类,lambda表达式的语义就十分简单:它不会从超类(supertype)中继承任何变量名,也不会引入一个新的作用域。lambda表达式基于词法作用域,也就是说lambda表达式函数体里面的变量和它外部环境的变量具有相同的语义(也包括lambda表达式的形式参数)。此外,’this’关键字及其引用在lambda表达式内部和外部也拥有相同的语义。

为了进一步说明词法作用域的优点,请参考下面的代码,它会把”Hello, world!”打印两遍:

public class Hello {  Runnable r1 = () -> { System.out.println(this); }  Runnable r2 = () -> { System.out.println(toString()); }  public String toString() { return "Hello, world!"; }  public static void main(String... args) {    new Hello().r1.run();    new Hello().r2.run();  }}

与之相类似的内部类实现则会打印出类似Hello1@5b89a773Hello2@537a7706之类的字符串,这往往会使开发者大吃一惊。

基于词法作用域的理念,lambda表达式不可以掩盖任何其所在上下文中的局部变量,它的行为和那些拥有参数的控制流结构(例如for循环和catch从句)一致。

七. 变量捕获
内部类实例会一直保留一个对其外部类实例的强引用,而那些没有捕获外部类成员的lambda表达式则不会保留对外部类实例的引用。内部类的这个特性往往会造成内存泄露。

Callable<String> helloCallable(String name) {  String hello = "Hello";  return () -> (hello + ", " + name);}

Lambda表达式禁止可变的本地变量,如:

int sum = 0;list.forEach(e -> { sum += e.size(); }); // ERROR

Lambda表达式对值封闭,对变量开放。

List<Person> list = new ArrayList<>();list.forEach((x) ->x.setName("Tom")); //OK

八. 方法引用
方法引用包括:

  • 静态方法引用 (ClassName::methName)
  • 实例方法引用 (instanceRef::methName)
  • 超类方法引用 (super::methName)
  • 特殊类型的实例方法引用 (ClassName::methName)
  • 类构造器引用(ClassName::new)
  • 数组构造器引用 (TypeName[]::new)

方法前有个分隔符::

例如:

class Person {     private final String name;    private final int age;    public int getAge() { return age; }    public String getName() { return name; }   ...}Person[] people = ...Comparator<Person> byName = Comparator.comparing(p -> p.getName());Arrays.sort(people, byName);

如果将p.getName()的换成方法引用,上面的代码可以重写为:

Comparator<Person> byName = Comparator.comparing(Person::getName);

其他的例子:

Consumer<Integer> b1 = System::exit;   // void exit(int status)Consumer<String[]> b2 = Arrays::sort;  // void sort(Object[] a)Consumer<String> b3 = MyProgram::main; // void main(String... args)Runnable r = MyProgram::main;          // void main(String... args)Consumer consumer = System.out::println;等价于Consumer consumer = x->System.out.println(x);

八. 缺省方法和静态方法
接口中可以定义缺省方法和静态方法。
默认方法拥有其默认实现,实现接口的类型通过继承得到该默认实现(如果类型没有覆盖该默认实现)。此外,默认方法不是抽象方法,所以我们可以放心的向函数式接口里增加默认方法,而不用担心函数式接口的单抽象方法限制。

九. 汇总
Java 8新语言特性—lambda表达式,方法引用,默认方法和静态接口方法,以及范围更广的类型推导。开发者可以使用它们编写出更加清晰简洁的代码,类库编写者可以编写更加强大易用的并行类库。
例如:下面的代码太冗余.

List<Person> people = ...Collections.sort(people, new Comparator<Person>() {  public int compare(Person x, Person y) {    return x.getLastName().compareTo(y.getLastName());  }})

使用lambda表达式和类库,可以写成如下:

Collections.sort(people,                  (Person x, Person y) -> x.getLastName().compareTo(y.getLastName()));或者Collections.sort(people, Comparator.comparing(Person::getName));或者list.sort((Person x, Person y) -> x.getName().compareTo(y.getName()));或者list.sort(Comparator.comparing(Person::getName));
0 0