Java 8 的特性介绍 ( 1 )

翻译于 http://winterbe.com/posts/2014/03/16/java-8-tutorial/

欢迎来到我对Java 8的介绍。本教程将逐步介绍所有新的语言功能。 由简短的代码示例支持,您将学习如何使用默认接口方法,lambda表达式,方法引用和可重复注释。 在文章末尾,您将熟悉最新的API更改,如流,函数式接口,map扩展和新的Date API。

没有长段的文字,只有一些对代码片段的评论。 请享用!

接口的默认方法

Java 8使我们能够通过使用默认关键字将非抽象方法实现添加到接口。
此功能也称为扩展方法。这是我们的第一个例子:

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

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

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);
}
});

在java8中可以用更精炼的Lambda 表达式来实现

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

您可以看到代码更短,更容易阅读。 但它变得更短:

1
2
3
4
5
Collections.sort(names, (String a, String b) -> b.compareTo(a));
```java
对于一行方法,您可以跳过大括号{}和返回关键字。 但它变得更短:
```java
Collections.sort(names, (a, b) -> b.compareTo(a));

java编译器知道参数类型,所以你也可以跳过它们。

函数式接口

lambda表达式如何适配到java的类型系统当中呢?每个lambda对应于由接口指定的给定类型。

所谓的功能界面必须包含一个抽象方法声明。 该类型的每个lambda表达式将与此抽象方法匹配。 由于默认方法不是抽象的,您可以向您的功能界面添加默认方法。

示例:

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省略,代码也是有效的。

方法和构造函数的引用

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

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

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

为了看::关键字是如何工作的,我们先定义一个有不同构造函数的示例bean。

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;
}
}

下一步,我们指定一个Persion工厂接口用于创建一个新的persion

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");

我们创建一个Persion构造函数的引用如Person::new. java编译器会自动选择一个匹配的构造方法。

Lambda 作用域

从lambda表达式访问外部范围变量非常类似于匿名对象。您可以从本地外部范围以及实例字段和静态变量访问最终变量。

访问本地变量

我们可以从lambda表达式中读取外部申明的final局部变量:

1
2
3
4
5
final int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);

stringConverter.convert(2); // 3

但不同于匿名对象,变量num可以不用申明为final.下面的代码也是可行的:

1
2
3
4
5
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);

stringConverter.convert(2); // 3

但是 num必须在编译器看起来不被改变(隐式final),下面的代码是不能编译通过的:

1
2
3
4
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
num = 3;

在lambda内部改num的内容也是禁止的。

访问字段和静态变量

与局部变量相反,我们对lambda表达式中的实例字段和静态变量都有读写权限。 这种行为是从匿名对象所熟知的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
};
}
}

访问默认接口方法

还记得第一部分的Formula示例? Interface Formula定义了一个默认方法sqrt,可以从包含匿名对象的每个公式实例中访问sqrt。 这不适用于lambda表达式。
默认方法无法从lambda表达式中访问。 以下代码编译不通过:

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

内置的函数式接口

JDK 1.8 API包含许多内置的接口。其中一些是比较旧版本的Java,比如ComparatorRunnable。 扩展这些现有的接口,以通过@FunctionalInterface注解来支持Lambda。

但Java 8 API也充满了新的接口,使您的生活更轻松。 其中一些接口是从Google Guava库中可能也有。 即使你熟悉这个库,你应该密切关注这些接口如何扩展一些有用的方法扩展。

断言

1
2
3
4
5
6
7
8
9
10
Predicate<String> predicate = (s) -> s.length() > 0;

predicate.test("foo"); // true
predicate.negate().test("foo"); // false

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();

功能(Functions)

1
2
3
4
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply("123"); // "123"

提供者(Suppliers)

1
2
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person

消费者(Consumers)

1
2
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

比较器(Comparators)

1
2
3
4
5
6
7
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");

comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0

Optionals

1
2
3
4
5
6
7
Optional<String> optional = Optional.of("bam");

optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"

(未翻译完,其余的见下篇)

自己的看法

java8关于函数的几个特性与C++的STL的机制很像。