黑马程序员技术交流社区

标题: java 1.8 新特性--4 [打印本页]

作者: tuan2016    时间: 2016-5-14 23:14
标题: java 1.8 新特性--4
本帖最后由 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类:一个允许为空值,一个不允许为空值。
  1. Optional< String > fullName = Optional.ofNullable( null );
  2. System.out.println( "Full Name is set? " + fullName.isPresent() );        
  3. System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
  4. System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
复制代码

如果Optional类的实例为非空值的话,isPresent()返回true,否从返回false。为了防止Optional为空值,orElseGet()方法通过回调函数来产生一个默认值。map()函数对当前Optional的值进行转化,然后返回一个新的Optional实例。orElse()方法和orElseGet()方法类似,但是orElse接受一个默认值而不是一个回调函数。下面是这个程序的输出:
  1. Full Name is set? false
  2. Full Name: [none]
  3. Hey Stranger!
复制代码

让我们来看看另一个例子:
  1. Optional< String > firstName = Optional.of( "Tom" );
  2. System.out.println( "First Name is set? " + firstName.isPresent() );        
  3. System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
  4. System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
  5. System.out.println();
复制代码

下面是程序的输出:
  1. First Name is set? true
  2. First Name: Tom
  3. Hey Tom!
复制代码

更多详情请参考官方文档

4.2 Stream
最新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
Stream API极大简化了集合框架的处理(但它的处理的范围不仅仅限于集合框架的处理,这点后面我们会看到)。让我们以一个简单的Task类为例进行介绍:
  1. public class Streams  {
  2.     private enum Status {
  3.         OPEN, CLOSED
  4.     };
  5.    
  6.     private static final class Task {
  7.         private final Status status;
  8.         private final Integer points;

  9.         Task( final Status status, final Integer points ) {
  10.             this.status = status;
  11.             this.points = points;
  12.         }
  13.         
  14.         public Integer getPoints() {
  15.             return points;
  16.         }
  17.         
  18.         public Status getStatus() {
  19.             return status;
  20.         }
  21.         
  22.         @Override
  23.         public String toString() {
  24.             return String.format( "[%s, %d]", status, points );
  25.         }
  26.     }
  27. }
复制代码

Task类有一个分数的概念(或者说是伪复杂度),其次是还有一个值可以为OPEN或CLOSED的状态.让我们引入一个Task的小集合作为演示例子:
  1. final Collection< Task > tasks = Arrays.asList(
  2.     new Task( Status.OPEN, 5 ),
  3.     new Task( Status.OPEN, 13 ),
  4.     new Task( Status.CLOSED, 8 )
  5. );
复制代码

我们下面要讨论的第一个问题是所有状态为OPEN的任务一共有多少分数?在Java 8以前,一般的解决方式用foreach循环,但是在Java 8里面我们可以使用stream:一串支持连续、并行聚集操作的元素。
  1. // Calculate total points of all active tasks using sum()
  2. final long totalPointsOfOpenTasks = tasks
  3.     .stream()
  4.     .filter( task -> task.getStatus() == Status.OPEN )
  5.     .mapToInt( Task::getPoints )
  6.     .sum();
  7.         
  8. System.out.println( "Total points: " + totalPointsOfOpenTasks );
复制代码

程序在控制台上的输出如下:
  1. Total points: 18
复制代码

这里有几个注意事项。第一,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分数和的例子。
  1. // Calculate total points of all tasks
  2. final double totalPoints = tasks
  3.    .stream()
  4.    .parallel()
  5.    .map( task -> task.getPoints() ) // or map( Task::getPoints )
  6.    .reduce( 0, Integer::sum );
  7.    
  8. System.out.println( "Total points (all tasks): " + totalPoints );
复制代码

这个例子和第一个例子很相似,但这个例子的不同之处在于这个程序是并行运行的,其次使用reduce方法来算最终的结果。
下面是这个例子在控制台的输出:
  1. Total points (all tasks): 26.0
复制代码

经常会有这个一个需求:我们需要按照某种准则来对集合中的元素进行分组。Stream也可以处理这样的需求,下面是一个例子:
  1. // Group tasks by their status
  2. final Map< Status, List< Task > > map = tasks
  3.     .stream()
  4.     .collect( Collectors.groupingBy( Task::getStatus ) );
  5. System.out.println( map );
复制代码

这个例子的控制台输出如下:
  1. {CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
复制代码

让我们来计算整个集合中每个task分数(或权重)的平均值来结束task的例子。
  1. // Calculate the weight of each tasks (as percent of total points)
  2. final Collection< String > result = tasks
  3.     .stream()                                        // Stream< String >
  4.     .mapToInt( Task::getPoints )                     // IntStream
  5.     .asLongStream()                                  // LongStream
  6.     .mapToDouble( points -> points / totalPoints )   // DoubleStream
  7.     .boxed()                                         // Stream< Double >
  8.     .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
  9.     .mapToObj( percentage -> percentage + "%" )      // Stream< String>
  10.     .collect( Collectors.toList() );                 // List< String >
  11.         
  12. System.out.println( result );
复制代码

下面是这个例子的控制台输出:
[19%, 50%, 30%]
最后,就像前面提到的,Stream API不仅仅处理Java集合框架。像从文本文件中逐行读取数据这样典型的I/O操作也很适合用Stream API来处理。下面用一个例子来应证这一点。
  1. final Path path = new File( filename ).toPath();
  2. try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
  3.     lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
  4. }
复制代码

对一个stream对象调用onClose方法会返回一个在原有功能基础上新增了关闭功能的stream对象,当对stream对象调用close()方法时,与关闭相关的处理器就会执行。
Stream API、Lambda表达式方法引用接口默认方法与静态方法的配合下是Java 8对现代软件开发范式的回应。更多详情请参考官方文档





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2