本帖最后由 tuan2016 于 2016-5-14 23:25 编辑
4. Java 类库的新特性Java 8 通过增加大量新类,扩展已有类的功能的方式来改善对并发编程、函数式编程、日期/时间相关操作以及其他更多方面的支持。
4.1 Optional到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。 Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。更多详情请参考官方文档。 我们下面用两个小例子来演示如何使用Optional类:一个允许为空值,一个不允许为空值。 - 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接受一个默认值而不是一个回调函数。下面是这个程序的输出: - Full Name is set? false
- Full Name: [none]
- Hey Stranger!
复制代码
让我们来看看另一个例子: - 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();
复制代码
下面是程序的输出: - First Name is set? true
- First Name: Tom
- Hey Tom!
复制代码
更多详情请参考官方文档
4.2 Stream最新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供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 )
- );
复制代码
我们下面要讨论的第一个问题是所有状态为OPEN的任务一共有多少分数?在Java 8以前,一般的解决方式用foreach循环,但是在Java 8里面我们可以使用stream:一串支持连续、并行聚集操作的元素。 - // 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 );
复制代码
程序在控制台上的输出如下:
这里有几个注意事项。第一,task集合被转换化为其相应的stream表示。然后,filter操作过滤掉状态为CLOSED的task。下一步,mapToInt操作通过Task::getPoints这种方式调用每个task实例的getPoints方法把Task的stream转化为Integer的stream。最后,用sum函数把所有的分数加起来,得到最终的结果。 在继续讲解下面的例子之前,关于stream有一些需要注意的地方(详情在这里).stream操作被分成了中间操作与最终操作这两种。 中间操作返回一个新的stream对象。中间操作总是采用惰性求值方式,运行一个像filter这样的中间操作实际上没有进行任何过滤,相反它在遍历元素时会产生了一个新的stream对象,这个新的stream对象包含原始stream
中符合给定谓词的所有元素。 像forEach、sum这样的最终操作可能直接遍历stream,产生一个结果或副作用。当最终操作执行结束之后,stream管道被认为已经被消耗了,没有可能再被使用了。在大多数情况下,最终操作都是采用及早求值方式,及早完成底层数据源的遍历。 stream另一个有价值的地方是能够原生支持并行处理。让我们来看看这个算task分数和的例子。 - // 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 );
复制代码
这个例子和第一个例子很相似,但这个例子的不同之处在于这个程序是并行运行的,其次使用reduce方法来算最终的结果。
下面是这个例子在控制台的输出: - Total points (all tasks): 26.0
复制代码
经常会有这个一个需求:我们需要按照某种准则来对集合中的元素进行分组。Stream也可以处理这样的需求,下面是一个例子: - // 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]]}
复制代码
让我们来计算整个集合中每个task分数(或权重)的平均值来结束task的例子。 - // 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%]最后,就像前面提到的,Stream API不仅仅处理Java集合框架。像从文本文件中逐行读取数据这样典型的I/O操作也很适合用Stream API来处理。下面用一个例子来应证这一点。 - 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对现代软件开发范式的回应。更多详情请参考官方文档。 |