Java8新特性
Java8新特性
一、Lambda
(一)介绍
Lambda表达式(也称为闭包)是整个Java 8发行版中最受期待的在Java语言层面上的改变,Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),或者把代码看成数据
(二)作用
1.用作替代匿名内部类
(1)资料
λ表达式主要用于替换以前广泛使用的内部匿名类,各种回调,比如事件响应器、传入Thread类的Runnable等。看下面的例子:
Thread oldSchool = new Thread( new Runnable () {
@Override
public void run() {
System.out.println("This is from an anonymous class.");
}
} );
Thread gaoDuanDaQiShangDangCi = new Thread( () -> {
System.out.println("This is from an anonymous method (lambda exp).");
} );
注意第二个线程里的λ表达式,你并不需要显式地把它转成一个Runnable,因为Java能根据上下文自动推断出来:一个Thread的构造函数接受一个Runnable参数,而传入的λ表达式正好符合其run()函数,所以Java编译器推断它为Runnable。
2.集合类的迭代
(1)资料
集合类的批处理操作。这是Java8的另一个重要特性,它与λ表达式的配合使用乃是Java8的最主要特性。Java8之前集合类的迭代(Iteration)都是外部的,即客户代码。而内部迭代意味着改由Java类库来进行迭代,而不是客户代码。例如:
for(Object o: list) { // 外部迭代
System.out.println(o);
}
可以写成:
list.forEach(o -> {System.out.println(o);}); //forEach函数实现内部迭代
集合类(包括List)现在都有一个forEach方法,对元素进行迭代(遍历),所以我们不需要再写for循环了。forEach方法接受一个函数接口Consumer做参数,所以可以使用λ表达式。
3.流(stream)
(1)资料
Java8为集合类引入了另一个重要概念:流(stream)。一个流通常以一个集合类实例为其数据源,然后在其上定义各种操作。流的API设计使用了管道(pipelines)模式。对流的一次操作会返回另一个流。如同IO的API或者StringBuffer的append方法那样,从而多个不同的操作可以在一个语句里串起来。看下面的例子:
List<Shape> shapes = shapes.stream()
.filter(s -> s.getColor() == BLUE)
.forEach(s -> s.setColor(RED));
首先调用stream方法,以集合类对象shapes里面的元素为数据源,生成一个流。然后在这个流上调用filter方法,挑出蓝色的,返回另一个流。最后调用forEach方法将这些蓝色的物体喷成红色。(forEach方法不再返回流,而是一个终端方法,类似于StringBuffer在调用若干append之后的那个toString)。filter方法的参数是Predicate类型,forEach方法的参数是Consumer类型,它们都是函数接口,所以可以使用λ表达式。
来看更多的例子。下面是典型的大数据处理方法,Filter-Map-Reduce:
//给出一个String类型的数组,找出其中所有不重复的素数
public void distinctPrimary(String... numbers) {
List<String> l = Arrays.asList(numbers);
List<Integer> r = l.stream()
.map(e -> new Integer(e))
.filter(e -> Primes.isPrime(e))
.distinct()
.collect(Collectors.toList());
System.out.println("distinctPrimary result is: " + r);
}
第一步:传入一系列String(假设都是合法的数字),转成一个List,然后调用stream()方法生成流。
第二步:调用流的map方法把每个元素由String转成Integer,得到一个新的流。map方法接受一个Function类型的参数,上面介绍了,Function是个函数接口,所以这里用λ表达式。
第三步:调用流的filter方法,过滤那些不是素数的数字,并得到一个新流。filter方法接受一个Predicate类型的参数,上面介绍了,Predicate是个函数接口,所以这里用λ表达式。
第四步:调用流的distinct方法,去掉重复,并得到一个新流。这本质上是另一个filter操作。
第五步:用collect方法将最终结果收集到一个List里面去。collect方法接受一个Collector类型的参数,这个参数指明如何收集最终结果。在这个例子中,结果简单地收集到一个List中。我们也可以用Collectors.toMap(e->e, e->e)把结果收集到一个Map中,它的意思是:把结果收到一个Map,用这些素数自身既作为键又作为值。toMap方法接受两个Function类型的参数,分别用以生成键和值,Function是个函数接口,所以这里都用λ表达式。
在对于一个Stream进行多次转换操作,每次都对Stream的每个元素进行转换,而且是执行多次,这样时间复杂度就是一个for循环里把所有操作都做掉的N(转换的次数)倍啊。其实不是这样的,转换操作都是lazy的,多个转换操作只会在汇聚操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。
(三)Lambda表达式类型
(1)资料
λ表达式可以被当做是一个Object(注意措辞)。λ表达式的类型,叫做“目标类型(target type)”。λ表达式的目标类型是“函数接口(functional interface)”,这是Java8新引入的概念。它的定义是:一个接口,如果只有一个显式声明的抽象方法,那么它就是一个函数接口。一般用@FunctionalInterface标注出来(也可以不标)
可以用一个λ表达式为一个函数接口赋值:
Runnable r1 = () -> {System.out.println("Hello Lambda!");};
然后再赋值给一个Object:
Object obj = r1;
但却不能这样干:
Object obj = () -> {System.out.println("Hello Lambda!");};
必须显式的转型成一个函数接口才可以:
Object o = (Runnable) () -> { System.out.println("hi"); }; // correct
一个λ表达式只有在转型成一个函数接口后才能被当做Object使用。所以下面这句也不能编译:
System.out.println( () -> {} ); //错误! 目标类型不明
必须先转型:
System.out.println( (Runnable)() -> {} ); // 正确
JDK预定义了很多函数接口以避免用户重复定义。最典型的是Function:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
这个接口代表一个函数,接受一个T类型的参数,并返回一个R类型的返回值。
另一个预定义函数接口叫做Consumer,跟Function的唯一不同是它没有返回值。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
还有一个Predicate,用来判断某项条件是否满足。经常用来进行筛滤操作:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
综上所述,一个λ表达式其实就是定义了一个匿名方法,只不过这个方法必须符合至少一个函数接口。
(四)demo
E:\source\_2\src\main\java\exercise\LambdaTry.java
二、接口的默认方法与静态方法
(一)接口的默认方法
1.介绍
与传统的接口又有些不一样,它允许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。
默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。相反,每个接口都必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)。
2.例子
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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用自己的方法覆盖了默认方法。
(二)接口的静态方法
1.例子
接口可以声明(并且可以提供实现)静态方法。例如:
|
1 2 3 4 5 6 |
Private interface DefaulableFactory { // Interfaces now allow static methods Static Defaulable create( Supplier< Defaulable > supplier ) { Return supplier.get(); } }
|
下面的一小段代码片段把上面的默认方法与静态方法黏合到一起。
|
1 2 3 4 5 6 7 |
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() ); }
|
这个程序的控制台输出如下:
|
1 2 |
Default implementation Overridden implementation
|
在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream(),parallelStream(),forEach(),removeIf(),……
尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。
三、方法引用
(一)资料
可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
在学习lambda表达式之后,我们通常使用lambda表达式来创建匿名方法。然而,有时候我们仅仅是调用了一个已存在的方法。如下:
Arrays.sort(stringsArray,(s1,s2)->s1.compareToIgnoreCase(s2));
在Java8中,我们可以直接通过方法引用来简写lambda表达式中已经存在的方法。
Arrays.sort(stringsArray, String::compareToIgnoreCase);
这种特性就叫做方法引用(Method Reference)。
一共有四种类型的方法引用:
|
类型 |
示例 |
|
类静态方法引用 |
ContainingClass::staticMethodName |
|
某个对象的方法引用 |
ContainingObject::instanceMethodName |
|
特定类的任意对象的方法引用 |
ContainingType::methodName |
|
构造方法引用 |
ClassName::new |
(二)例子
定义了4个方法的Car这个类作为例子,区分Java中支持的4种不同的方法引用。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Public static class Car { 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。请注意构造器没有参数。
|
1 2 |
final Car car = Car.create( Car::new); Final List< Car > cars = Arrays.asList( car );
|
第二种方法引用是静态方法引用,它的语法是Class::static_method。请注意这个方法接受一个Car类型的参数。
|
1 |
cars.forEach( Car::collide );
|
第三种方法引用是特定类的任意对象的方法引用,它的语法是Class::method。请注意,这个方法没有参数。
|
1 |
cars.forEach( Car::repair );
|
最后,第四种方法引用是特定对象的方法引用,它的语法是instance::method。请注意,这个方法接受一个Car类型的参数
|
1 2 |
Final Car police = Car.create( Car::new); cars.forEach( police::follow );
|
运行上面的Java程序在控制台上会有下面的输出(Car的实例可能不一样):
|
1 2 3 |
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
|
四、Optional类
(一)资料
1.介绍
为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
2.例子
我们下面用两个小例子来演示如何使用Optional类:一个允许为空值,一个不允许为空值。
|
1 2 3 4 |
Optional< String > fullName = Optional.ofNullable(null); System.out.println("Full Name is set? "+ fullName.isPresent() ); System.out.println("Full Name: "+ fullName.orElseGet( () ->"[none]") ); System.out.println( fullName.map( s ->"Hey "+ s +"!").orElse("Hey Stranger!") );
|
如果Optional类的实例为非空值的话,isPresent()返回true,否从返回false。为了防止Optional为空值,orElseGet()方法通过回调函数来产生一个默认值。map()函数对当前Optional的值进行转化,然后返回一个新的Optional实例。orElse()方法和orElseGet()方法类似,但是orElse接受一个默认值而不是一个回调函数。
下面是这个程序的输出:
|
1 2 3 |
Full Name isset?false Full Name: [none] Hey Stranger!
|
让我们来看看另一个例子:
|
1 2 3 4 5 |
Optional< String > firstName = Optional.of("Tom"); System.out.println("First Name is set? "+ firstName.isPresent() ); System.out.println("First Name: "+ firstName.orElseGet( () ->"[none]") ); System.out.println( firstName.map( s ->"Hey "+ s +"!").orElse("Hey Stranger!") ); System.out.println();
|
下面是程序的输出:
|
1 2 3 |
First Name isset?true First Name: Tom Hey Tom!
|

浙公网安备 33010602011771号