Java8极简手册-01

为忙碌的程序员归纳总结和翻译的Java8极简教程,有助于快速了解Java8的新特性。

系列文章链接:

Java8极简手册-01
Java8极简手册-02
Java8极简手册-03
Java8极简手册-04

接口的默认方法(Default Methods for Interfaces)

Java 8允许我们利用default关键字在接口中定义非抽象的方法实现,这个功能也被称为虚拟扩展方法( virtual extension methods)。

以下是第一个例子:

1
2
3
4
5
6
7
interface Formula {
double calculate(int a);

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

除了抽象方法(可以理解为无具体实现的方法) calculate之外,接口 Formula 里面还定了一个默认方法 sqrt。在具体的实现类里面,抽象方法 calculate需要被重新实现,而默认方法sqrt则可以被实现类直接使用。

1
2
3
4
5
6
7
8
9
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};

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

该公式是作为匿名对象实现的,代码或许过于冗长:6行代码实现的仅仅是一个简单的计算 sqrt(a * 100),下一节我们将看到在Java8中是怎么样更优雅地实现这样的单方法对象的。

Lambda表达式

让我们从一个简单的例子来说明如何在以前版本的Java中对字符串列表进行排序:

1
2
3
4
5
6
7
8
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});

静态方法Collections.sort接受一个list和一个comparator,以便对给定list的元素进行排序。 很多时候我们会发现需要先创建匿名comparator,并将它们传递给排序方法。

从Java 8开始,我们不需要整天创建匿名对象了,而是可以使用更短的语法 :lambda expressions(lambda表达式):

1
2
3
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});

你可以看见,代码更精简,而且更具可读性了(注:可读性是对于熟悉lambda的开发者来说的),而且,我们还可以把代码写的更精简:

1
Collections.sort(names, (String a, String b) -> b.compareTo(a));

对于一行方法体,您可以跳过大括号{}return关键字, 所以还可以把它变得更短:

1
names.sort((a, b) -> b.compareTo(a));

列表现在有了一个排序方法。 此外,java编译器会知道参数类型,所以你可以跳过它们。 让我们深入了解如何在更多的场景中使用lambda表达式。

函数式接口(Functional Interfaces)

Lambda表达式如何匹配识别Java的类型系统?

每个lambda对应于由接口指定的给定类型,一个所谓的函数式接口必须包含一个精确的抽象方法声明,该类型的每个lambda表达式都将与此抽象方法匹配。 由于默认方法不是抽象的,你可以自由地将默认方法添加到你的函数式接口。

只要接口满足只包含一个抽象方法的约定,我们就可以使用任意接口作为lambda表达式。 为了确保你的接口符合要求,你应该添加@FunctionalInterface注解。 编译器知道这个注释,并会在你尝试向接口添加第二个抽象方法声明后立即抛出编译器错误。

例子:

1
2
3
4
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
1
2
3
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted); // 123

需要注意的是,如果@FunctionalInterface注释被省略掉,代码也是有效的。

方法和构造器引用

The above example code can be further simplified by utilizing static method references:

上面的示例代码可以通过使用静态方法引用进一步简化:

1
2
3
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted); // 123

Java 8让您能够通过::关键字传递方法或构造函数的引用。 上面的例子展示了如何引用一个静态方法。 但是我们也可以引用对象的方法:

1
2
3
4
5
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
1
2
3
4
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted); // "J"

Let’s see how the :: keyword works for constructors. First we define an example class with different constructors:

我们来看看::关键字如何用于构造函数,首先我们用不同的构造函数定义一个示例类:

1
2
3
4
5
6
7
8
9
10
11
class Person {
String firstName;
String lastName;

Person() {}

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

接下来,我们指定一个用于创建新的人员的Person工厂接口:

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

我们不是手动实现工厂,而是通过构造函数引用将所有东西粘合在一起:

1
2
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

我们通过Person :: new创建一个对Person构造函数的引用。 Java编译器通过匹配PersonFactory.create的参数和类型自动选择正确的构造函数。