翻译于 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);
|
要注意的是如果@FunctionalInterface
省略,代码也是有效的。
方法和构造函数的引用
通过使用静态方法引用,可以进一步简化上述示例代码:
1 2 3
| Converter<String, Integer> converter = Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted);
|
1 2 3 4
| Something something = new Something(); Converter<String, String> converter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted);
|
为了看::
关键字是如何工作的,我们先定义一个有不同构造函数的示例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);
|
但不同于匿名对象,变量num
可以不用申明为final
.下面的代码也是可行的:
1 2 3 4 5 6
| int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2);
|
但是 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,比如Comparator
或Runnable
。 扩展这些现有的接口,以通过@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"); predicate.negate().test("foo");
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");
|
提供者(Suppliers)
1 2
| Supplier<Person> personSupplier = Person::new; personSupplier.get();
|
消费者(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); comparator.reversed().compare(p1, p2);
|
Optionals
1 2 3 4 5 6 7
| Optional<String> optional = Optional.of("bam");
optional.isPresent(); optional.get(); optional.orElse("fallback");
optional.ifPresent((s) -> System.out.println(s.charAt(0)));
|
(未翻译完,其余的见下篇)
自己的看法
java8关于函数的几个特性与C++的STL的机制很像。