java8新特性

一.函数式接口

函数式接口,就是一个接口里面只有一个接口,接口上都用@FunctionalInterface注释修饰(编译级的注解,不符合规范就会报错,但不加该注释对函数式接口是没有影响的);一般用在Lambda表达式和方法引用中,下面例如:

@FunctionalInterface

interface GreetingService

{

void sayMessage(String message);

}

那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):

GreetingService greetService = message -> System.out.println("Hello " + message);

扩展方面,函数接口可以在里面写静态方法和默认方法(也是Java8特性之一,而且静态方法和默认方法不属于抽象方法),可以在接口中处理一些逻辑等

二.lambda表达式

Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。

Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

在上面这个代码中的参数e的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如:

Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如:

Arrays.asList( "a", "b", "d" ).forEach( e -> {

System.out.print( e );

System.out.print( e );

} );

Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {

int result = e1.compareTo( e2 );

return result;

} );


三.Optional的使用

Optional用于检查空值,解决空指针异常的情况;Optional提供了很多有用的方法,所以使得代码变得更整洁。

1.of():为非空的值创建一个optional实例,其中参数不能为空;

OptionalexampleOptional = Optional.of(example);

2.ofNullable():创建Optional实例,可以为空

3.isPresent():如果实例中的值非空,返回true,否则返回false;

4.get()返回该对象;

5.ifPresent(Consumer consumer):如果值不为空,执行方法体,可做对象判断后的操作;

exampleOptional.ifPresent((value) -> value ="hello"+ value);

6.orElse(T other):如果实例有值,则返回实例的值,反之返回参数的值;

7.orElseGet(Supplier other):跟orElse(T other)一样,只是参数不一样,orElseGet方法可以接受Supplier接口的实现用来生成默认值;

String temp = exampleOptional.orElseGet(() ->newDate().toString());

8.orElseThrow(Supplier exceptionSupplier):如果为空则抛出异常;

9.filter(Predicate):判断Optional对象中保存的值是否满足Predicate,并返回新的Optional;

Optional filter = optional.filter((a) -> a ==null);

10.map(Function):对Optional中保存的值进行函数运算,并返回新的Optional(可以是任何类型);

Optional str1Optional = optional1.map((a) ->"key"+ a);

11.flatMap():功能与map()相似,差别请看如下代码。flatMap方法与map方法类似,区别在于mapping函数的返回值不同。map方法的mapping函数返回值可以是任何类型T,而flatMap方法的mapping函数必须是Optional。

Optional> str1Optional = optional1.map((a) -> {

returnOptional.of("key"+ a);

});

四.方法引用

方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。

Car类是不同方法引用的例子,可以帮助读者区分四种类型的方法引用。

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::new。注意:这个构造器没有参数。

final Car car = Car.create( Car::new );

final List< Car > cars = Arrays.asList( car );

第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。

cars.forEach( Car::collide );

第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:

cars.forEach( Car::repair );

第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:

final Car police = Car.create( Car::new );

cars.forEach( police::follow );

运行上述例子,可以在控制台看到如下输出(Car实例可能不同):

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

五.Streams

新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。

Stream API极大得简化了集合操作(后面我们会看到不止是集合),首先看下这个叫Task的类:

public class Streams  {

private enum Status {

OPEN, CLOSED

};

private static final class Task {

private final Status status;

private final Integer points;

Task( final Status status, final Integer points ) {

this.status = status;

this.points = points;

}

public Integer getPoints() {

return points;

}

public Status getStatus() {

return status;

}

@Override

public String toString() {

return String.format( "[%s, %d]", status, points );

}

}

}

Task类有一个分数(或伪复杂度)的概念,另外还有两种状态:OPEN或者CLOSED。现在假设有一个task集合:

final Collection< Task > tasks = Arrays.asList(

new Task( Status.OPEN, 5 ),

new Task( Status.OPEN, 13 ),

new Task( Status.CLOSED, 8 )

);

首先看一个问题:在这个task集合中一共有多少个OPEN状态的点?在Java 8之前,要解决这个问题,则需要使用foreach循环遍历task集合;但是在Java 8中可以利用steams解决:包括一系列元素的列表,并且支持顺序和并行处理。

// Calculate total points of all active tasks using sum()

final long totalPointsOfOpenTasks = tasks

.stream()

.filter( task -> task.getStatus() == Status.OPEN )

.mapToInt( Task::getPoints )

.sum();

System.out.println( "Total points: " + totalPointsOfOpenTasks );

运行这个方法的控制台输出是:

Total points: 18

这里有很多知识点值得说。首先,tasks集合被转换成steam表示;其次,在steam上的filter操作会过滤掉所有CLOSED的task;第三,mapToInt操作基于每个task实例的Task::getPoints方法将task流转换成Integer集合;最后,通过sum方法计算总和,得出最后的结果。

在学习下一个例子之前,还需要记住一些steams(点此更多细节)的知识点。Steam之上的操作可分为中间操作和晚期操作。

中间操作会返回一个新的steam——执行一个中间操作(例如filter)并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam。

晚期操作(例如forEach或者sum),会遍历steam并得出结果或者附带结果;在执行晚期操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,晚期操作都是立刻对steam进行遍历。

steam的另一个价值是创造性地支持并行处理(parallel processing)。对于上述的tasks集合,我们可以用下面的代码计算所有任务的点数之和:

// Calculate total points of all tasks

final double totalPoints = tasks

.stream()

.parallel()

.map( task -> task.getPoints() ) // or map( Task::getPoints )

.reduce( 0, Integer::sum );

System.out.println( "Total points (all tasks): " + totalPoints );

这里我们使用parallel方法并行处理所有的task,并使用reduce方法计算最终的结果。控制台输出如下:

Total points(all tasks): 26.0

对于一个集合,经常需要根据某些条件对其中的元素分组。利用steam提供的API可以很快完成这类任务,代码如下:

// Group tasks by their status

final Map< Status, List< Task > > map = tasks

.stream()

.collect( Collectors.groupingBy( Task::getStatus ) );

System.out.println( map );

控制台的输出如下:

{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}

最后一个关于tasks集合的例子问题是:如何计算集合中每个任务的点数在集合中所占的比重,具体处理的代码如下:

// Calculate the weight of each tasks (as percent of total points)

final Collection< String > result = tasks

.stream()                                        // Stream< String >

.mapToInt( Task::getPoints )                    // IntStream

.asLongStream()                                  // LongStream

.mapToDouble( points -> points / totalPoints )  // DoubleStream

.boxed()                                        // Stream< Double >

.mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream

.mapToObj( percentage -> percentage + "%" )      // Stream< String>

.collect( Collectors.toList() );                // List< String >

System.out.println( result );

控制台输出结果如下:

[19%, 50%, 30%]

最后,正如之前所说,Steam API不仅可以作用于Java集合,传统的IO操作(从文件或者网络一行一行得读取数据)可以受益于steam处理,这里有一个小例子:

final Path path = new File( filename ).toPath();

try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {

lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );

}

Stream的方法onClose返回一个等价的有额外句柄的Stream,当Stream的close()方法被调用的时候这个句柄会被执行。Stream API、Lambda表达式还有接口默认方法和静态方法支持的方法引用,是Java 8对软件开发的现代范式的响应。

推荐阅读更多精彩内容