以下是Java8中的引入的部分新特性。关于Java8新特性更详细的介绍可参考 这里 。
方法引用是使用一对冒号 ::
并通过方法的名字来指向一个方法。方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
下面,我们在 Car
类中定义了 4
个方法作为例子来区分 Java 中 4
种不同方法的引用。
class Car { // Supplier是jdk1.8的接口,这里和Lamda一起使用了 public static Car create(final Supplier<Car> supplier) { return supplier.get(); } public static void collide(final Car car) { System.out.println("Collided " + car.toString()); } public void follow(final Car another) { System.out.println("Following the " + another.toString()); } public void repair() { System.out.println("Repaired " + this.toString()); } }
它的语法是 Class::new
,或者更一般的 Class<T>::new
实例如下:
final Car car = Car.create(Car::new); final List<Car> cars = Arrays.asList(car);
它的语法是 Class::static_method
,实例如下:
cars.forEach(Car::collide);
它的语法是 Class::method
实例如下:
cars.forEach(Car::repair);
它的语法是 instance::method
实例如下:
final Car police = Car.create(Car::new); cars.forEach(police::follow);
以下是方法引用的实例:
import java.util.ArrayList; import java.util.List; /** * Tester. * * @author blinkfox on 2018-01-05. */ public class Tester { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("张三"); list.add("李四"); list.add("王五"); list.forEach(System.out::println); } }
实例中我们将 System.out::println
方法作为静态方法来引用。执行以上程序,输出结果为:
张三 李四 王五
Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。与传统的接口又有些不一样,它允许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。
默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。相反,每个接口都必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)。让我们看看下面的例子:
private interface Defaulable { // Interfaces now allow default methods, the implementer may or // may not implement (override) them. default String notRequired() { return "Default implementation"; } } private static class DefaultableImpl implements Defaulable { } private static class OverridableImpl implements Defaulable { @Override public String notRequired() { return "Overridden implementation"; } }
Defaulable
接口用关键字 default
声明了一个默认方法 notRequired()
, Defaulable
接口的实现者之一 DefaultableImpl
实现了这个接口,并且让默认方法保持原样。 Defaulable
接口的另一个实现者 OverridableImpl
用自己的方法覆盖了默认方法。
Java 8带来的另一个有趣的特性是接口可以声明(并且可以提供实现)静态方法。在接口中定义静态方法,使用static关键字,例如:
private interface DefaulableFactory { // Interfaces now allow static methods static Defaulable create(Supplier<Defaulable> supplier) { return supplier.get(); } }
下面的一小段代码片段把上面的默认方法与静态方法黏合到一起。
public static void main(String[] args) { Defaulable defaulable = DefaulableFactory.create(DefaultableImpl::new); System.out.println(defaulable.notRequired()); defaulable = DefaulableFactory.create(OverridableImpl::new); System.out.println(defaulable.notRequired()); }
在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到 java.util.Collection
接口中去: stream()
, parallelStream()
, forEach()
, removeIf()
等。
尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。
Lambda
表达式(也称为闭包)是整个Java 8发行版中最受期待的在Java语言层面上的改变,Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
一个 Lambda
可以由用逗号分隔的参数列表、 –>
符号与函数体三部分表示。
首先看看在老版本的Java中是如何排列字符串的:
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对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。 在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:
Collections.sort(names, (String a, String b) -> { return b.compareTo(a); });
看到了吧,代码变得更段且更具有可读性,但是实际上还可以写得更短:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
对于函数体只有一行代码的,你可以去掉大括号 {}
以及 return
关键字,但是你还可以写得更短点:
Collections.sort(names, (a, b) -> b.compareTo(a));
Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。
Lambda
表达式是如何在Java的类型系统中表示的呢?每一个Lambda表达式都对应一个类型,通常是接口类型。而 函数式接口
是指仅仅只包含一个抽象方法的接口,每一个该类型的Lambda表达式都会被匹配到这个抽象方法。因为 默认方法
不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
我们可以将Lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface
注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。
示例如下:
@FunctionalInterface interface Converter<F, T> { T convert(F from); } Converter<String, Integer> converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123
注:如果 @FunctionalInterface
如果没有指定,上面的代码也是对的。
Java8 API包含了很多内建的函数式接口,在老Java中常用到的比如 Comparator
或者 Runnable
接口,这些接口都增加了 @FunctionalInterface
注解以便能用在 Lambda
上。
Java8 API同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自Google Guava库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。
Predicate
接口只有一个参数,返回 boolean
类型。该接口包含多种默认方法来将 Predicate
组合成其他复杂的逻辑(比如: 与
, 或
, 非
):
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();
Function
接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法( compose
, andThen
)。代码如下:
Function<String, Integer> toInteger = Integer::valueOf; Function<String, String> backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"
Supplier
接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数。代码如下:
Supplier<Person> personSupplier = Person::new; personSupplier.get(); // new Person
Consumer
接口表示执行在单个参数上的操作。代码如下:
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));
Comparator
是老Java中的经典接口, Java 8在此之上添加了多种默认方法。代码如下:
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
过滤通过一个 predicate
接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。代码如下:
stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1"
排序是一个中间操作,返回的是排序好后的 Stream
。如果你不指定一个自定义的 Comparator
则会使用默认排序。代码如下:
stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2"
需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据 stringCollection
是不会被修改的:
System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
中间操作 map
会将元素根据指定的 Function
接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。代码如下:
stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Stream
提供了多种匹配操作,允许检测指定的 Predicate
是否匹配整个 Stream
。所有的匹配操作都是最终操作,并返回一个 boolean
类型的值。代码如下:
boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // true
计数是一个最终操作,返回Stream中元素的个数,返回值类型是 long
。代码如下:
long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3
这是一个最终操作,允许通过指定的函数来将 stream
中的多个元素规约为一个元素,规越后的结果是通过 Optional
接口表示的。代码如下:
Optional<String> reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
自从Java 5引入了注解机制,这一特性就变得非常流行并且广为使用。然而,使用注解的一个限制是相同的注解在同一位置只能声明一次,不能声明多次。Java 8打破了这条规则,引入了重复注解机制,这样相同的注解可以在同一地方声明多次。
重复注解机制本身必须用 @Repeatable
注解。事实上,这并不是语言层面上的改变,更多的是编译器的技巧,底层的原理保持不变。让我们看一个快速入门的例子:
import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; public class RepeatingAnnotations { @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Filters { Filter[] value(); } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Repeatable(Filters.class) public @interface Filter { String value(); }; @Filter("filter1") @Filter("filter2") public interface Filterable { } public static void main(String[] args) { for(Filter filter: Filterable.class.getAnnotationsByType(Filter.class)) { System.out.println(filter.value()); } } }
正如我们看到的,这里有个使用 @Repeatable(Filters.class)
注解的注解类 Filter
, Filters
仅仅是 Filter
注解的数组,但Java编译器并不想让程序员意识到 Filters
的存在。这样,接口 Filterable
就拥有了两次 Filter
(并没有提到 Filter
)注解。
同时,反射相关的API提供了新的函数 getAnnotationsByType()
来返回重复注解的类型(请注意 Filterable.class.getAnnotation(Filters.class
)`经编译器处理后将会返回Filters的实例)。
Java 8扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。下面演示几个例子:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Collection; public class Annotations { @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER }) public @interface NonEmpty { } public static class Holder<@NonEmpty T> extends @NonEmpty Object { public void method() throws @NonEmpty Exception { } } @SuppressWarnings("unused") public static void main(String[] args) { final Holder<String> holder = new @NonEmpty Holder<String>(); @NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>(); } }
参考文档: