【语言】Java 8新特性总结

2018.12.02

前言

虽然Java 8早在2014年就已经发布了,但得益于它所带来的新特性,使得Java重新焕发生机。

Java 8包含如下新特性1

  • Lambda表达式
  • 方法引用
  • 默认方法
  • 新的Streams API
  • Optional
  • 新的Date/Time API
  • Narshorn,新的JS引擎
  • 移除永久代(Permanent Generation)
  • … …

Lambda表达式

定义

Lambda表达式,可以说就是为单个方法的匿名类所提供的语法糖。Lambda表达式的支持有助于简化Java代码。编译器会根据Lambda表达式的上下文来判断所使用的***函数式接口***和参数的类型。对于特定方法,还能用***方法引用***进一步地简化Lambda表达式的写法。

语法

主要的语法就是“参数 -> 方法体”。Lambda表达式还有四条较为重要的语法:

  • 参数类型的声明是可选的
  • 当只有一个参数时,参数两侧的括号是可选的
  • 方法体使用花括号是可选的(除非方法体包含多条语句)
  • 当使用单个表达式返回返回值时,return关键字是可选的
Arrays.sort(strArray, (foo, bar) -> foo.length() - bar.length());

上面的例子中,Lambda表达式实现了Comparator接口完成排序。

作用域

Lambda表达式中能引用final变量或或者实际final变量,所谓实际final变量就是变量仅被赋值一次。例如:

// 正例
String sql = "delete * from User";
getHibernateTemplate().execute(session ->
	session.createSQLQuery(sql).uniqueResult());
// 反例
String sql = "delete * from User";
getHibernateTemplate().execute(session ->
	session.createSQLQuery(~~sql~~ ).uniqueResult());
sql = "select * from User";

Lambda表达式的极简模式——方法引用

定义

方法引用,引用的是已存在的方法,可以算是Lambda表达式的一种简化写法。对于方法体中只调用某个已存在方法的Lambda表达式,就可以改写为直接引用该方法。

语法

通过::进行方法引用,以下方法可被引用2

  • 静态方法:ContainingClass::staticMethodName
  • 类实例的实例方法:containingObject::instanceMethodName
  • 类的实例方法:全称是“特定类型的任意对象的实例方法”,ContainingType::methodName
  • 类/数组构造器(ie. TreeSet::new/TreeSet[]:new):ClassName::new

“特定类型的任意对象的实例方法”,这句话容易和“类实例的实例方法”混淆。先说“类实例的实例方法”,很直观,就是通过某个类实例引用它的实例方法,比如要对一个数组Clazz[] clazzArr排序,我们可以实例化了一个Comparator<Clazz> comparator并引用它的comparator::compare方法进行排序;再说“特定类型的任意对象的实例方法”,还是对Clazz[] clazzArr排序,但此时Clazz实现了Comparable接口,定义了Clazz::compareTo方法,那对于数组里的任意对象,都可以直接地引用它们自身的compareTo实例方法比较大小,也就是引用Clazz类的任意对象的实例方法。

但从使用者的角度来说,“特定类型的任意对象的实例方法”,其实就是通过类名引用实例方法——ContainingType::methodName,所以我们可以直观地理解为“类的实例方法”。

Lambda表达式的类型——函数式接口

定义

既然有了Lambda表达式,那在Java里如果声明它们呢?于是就有了函数式接口。函数式接口是只包含一个方法的接口,每个Lambda表达式,编译器最后都会根据上下文判断它所对应的函数式接口。

语法

Java 8在java.util.function包中定义几个函数式接口:

  • Function<T, R>:接收T类型的对象,并返回R
  • Supplier<T>:返回T类型的对象
  • Predicate<T>:基于T类型的输入,返回一个布尔值
  • Consumer<T>:对T类型的对象执行一定的动作
  • BiFunction<T, U, R>
  • BiConsumer<T, U, R>

示例如下:

// Function<T, R>
Function<String, Integer> length = String::length;
System.out.println(length.apply("GHD")); // 输出3
// Consumer<T>
User user = new User("GHD");
Consumer<User> userConsumer = foo -> foo.setName("HD G.");
userConsumer.accept(user; // public interface Consumer<T> { void accept(T t);}
System.out.println(user.getName); // 输出HD G.

Lambda铁蹄踏遍Java

最适合应用Lambda表达式的场景,莫过于集合类的操作。前朝功臣Iterator利用hasNextnext也曾立下过汗马功劳,但写法不简练,并且不易于并行化。Java 8要顺利地推广Lambda表达式,就需要先从集合类下手。然而事情没有这么简单,java.util.Collection是个接口,不能实现方法,于是Java 8就引入了***默认方法***来解决这个问题。集合类的操作就通过Stream API来拓展新特性。

默认方法

定义

默认方法,就是接口方法的默认实现。Java 8之所以要引入接口的默认方法,原因是在扩展java.util.Collection的特性时,考虑到可能产生的兼容性问题,例如如果直接给java.util.Collection添加一个新的方法,那么所有实现了该接口的类,都需要实现新方法。于是在扩展类似java.util.Collection的接口时,Java 8允许在接口里定义默认方法,这样所有实现该接口的类都自动添加了新方法的实现,以此实现“向后兼容”(Backwards Compatibility / Virtual Extension Methods)。

那可否用静态方法来实现“向后兼容”呢?答案是不行3。类的静态方法不能被子类继承,因此子类都无法实现静态方法;调用静态方法时,也就不能通过子类名调用父类的静态方法。

示例如下:

public interface Example {
    default void newMethod() {
         System.out.println("new method is here");
     }
 }

多个接口的继承问题——钻石问题

如果一个类实现了两个接口,而这两个接口都包含一个相同方法签名的默认方法,这种情况就类似于“钻石问题”4。钻石问题描述的是下图所示的继承关系,B、C类都覆盖了A类的方法,而D类同时继承了B、C类,那么通过D实例在调用该方法时,调用的是B类还是C类的方法?

钻石问题

  • 情况一:如果两个父接口都包含一个相同方法签名的默认方法,那在编译过程就会直接报错。要解决这个问题,就需要在子类里显式地覆盖这个方法。覆盖时,方法体内可以同时调用父接口的super来显式地调用默认方法。

  • 情况二:如果类实现了一个包含默认方法的接口,同时继承一个包含相同方法签名的类,那么编译是可以通过的,子类会调用父类的方法,原因是“类优先”。之所以采用“类优先”的策略,原因可能也和“向后兼容”有关5,升级到Java 8后,即使被扩展的接口引入了新的默认方法,也要保证不会影响到那些实现该接口的类的正常使用。

接口的默认方法 V.S. 抽象类

接口 抽象类 备注
覆盖方法中引用子类成员变量
提供便利方法(Convenience Methods)6 例如,ArraysConllections所提供的方法,以及工厂方法
有构造器
包含成员变量

接口的默认方法,本质上还是为了实现“向后兼容”而出现的,为Java 8和Lambda编程提供桥梁,与抽象类还是有比较大的区别的。

Stream API

Stream API包含两类操作7:中间操作(Intermediate Operation)和终结操作(Terminal Operation)。Stream API是加载模式,中间操作不会立即执行,只有到遇到第一个终结操作,中间操作才会执行。中间操作包括map/filter/flatmap等。


  1. What’s New in Java 8 ↩︎

  2. Oracle Java Method References ↩︎

  3. Difference between static and default methods ↩︎

  4. Multiple Inheritance ↩︎

  5. Dealing with the diamond problem in Java ↩︎

  6. Convenience Method ↩︎

  7. Java 8 Stream API ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章