Java 8 特性 – 终极手册(二)

4.2 Stream

新增加的Stream API (java.util.stream)引入了在Java里可以工作的函数式编程。这是目前为止对java库最大的一次功能添加,希望程序员通过编写有效、整洁和简明的代码,能够大大提高生产率。

Stream API让集合处理简化了很多(我们后面会看到不仅限于Java集合类)。让我们从一个简单的类Task开始来看看Stream的用法。

01 public class Streams {
02 private enum Status {
03 OPEN, CLOSED
04 };
05  
06 private static final class Task {
07 private final Status status;
08 private final Integer points;
09  
10 Task( final Status status, final Integer points ) {
11 this.status = status;
12 this.points = points;
13 }
14  
15 public Integer getPoints() {
16 return points;
17 }
18  
19 public Status getStatus() {
20 return status;
21 }
22  
23 @Override
24 public String toString() {
25 return String.format( "[%s, %d]", status, points );
26 }
27 }
28 }

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 );

第一个问题是所有的开放的Task的点数是多少?在java 8 之前,通常的做法是用foreach迭代。但是Java8里头我们会用Stream。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 );

控制台的输出将会是:

Total points: 18
上面代码执行的流程是这样的,首先Task集合会被转化为Stream表示,然后filter操作会过滤掉所有关闭的Task,接下来使用Task::getPoints 方法取得每个Task实例的点数,mapToInt方法会把Task Stream转换成Integer Stream,最后使用Sum方法将所有的点数加起来得到最终的结果。

在我们看下一个例子之前,我们要记住一些关于Stream的说明。Stream操作被分为中间操作和终点操作。

中间操作返回一个新的Stream。这些中间操作是延迟的,执行一个中间操作比如filter实际上不会真的做过滤操作,而是创建一个新的Stream,当这个新的Stream被遍历的时候,它里头会包含有原来Stream里符合过滤条件的元素。

终点操作比如说forEach或者sum会遍历Stream从而产生最终结果或附带结果。终点操作执行完之后,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 );

这个例子跟上面那个非常像,除了这个例子里使用了parallel()方法       并且计算最终结果的时候使用了reduce方法。

输出如下:

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 );

控制台的输出如下:

{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
让我们来计算整个集合中每个task分数(或权重)的平均值来结束task的例子。

01 // Calculate the weight of each tasks (as percent of total points)
02 final Collection< String > result = tasks
03     .stream()                                        // Stream< String >
04     .mapToInt( Task::getPoints )                     // IntStream
05     .asLongStream()                                  // LongStream
06     .mapToDouble( points -> points / totalPoints )   // DoubleStream
07     .boxed()                                         // Stream< Double >
08     .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
09     .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对软件开发的现代范式的响应。

 

4.3日期时间API(JSR310

 Java 8引入了新的日期时间API(JSR 310)改进了日期时间的管理。日期和时间管理一直是Java开发人员最痛苦的问题。java.util.Date和后来的java.util.Calendar一点也没有改变这个情况(甚至让人们更加迷茫)。

因为上面这些原因,产生了Joda-Time ,可以替换Java的日期时间API。Joda-Time深刻影响了 Java 8新的日期时间API,Java 8吸收了Joda-Time 的精华。新的java.time包包含了所有关于日期、时间、日期时间、时区、Instant(跟日期类似但精确到纳秒)、duration(持续时间)和时钟操作的类。设计这些API的时候很认真地考虑了这些类的不变性(从java.util.Calendar吸取的痛苦教训)。如果需要修改时间对象,会返回一个新的实例。

让我们看看一些关键的类和用法示例。第一个类是Clock,Clock使用时区来访问当前的instant, date和time。Clock类可以替换 System.currentTimeMillis() 和 TimeZone.getDefault().

1 // Get the system clock as UTC offset
2 final Clock clock = Clock.systemUTC();
3 System.out.println( clock.instant() );
4 System.out.println( clock.millis() );

控制台输出如下:

2014-04-12T15:19:29.282Z
1397315969360

其他类我们看看LocalTime和LocalDate。LocalDate只保存有ISO-8601日期系统的日期部分,有时区信息,相应地,LocalTime只保存ISO-8601日期系统的时间部分,没有时区信息。LocalDate和LocalTime都可以从Clock对象创建。

01 // Get the local date and local time
02 final LocalDate date = LocalDate.now();
03 final LocalDate dateFromClock = LocalDate.now( clock );
04  
05 System.out.println( date );
06 System.out.println( dateFromClock );
07  
08 // Get the local date and local time
09 final LocalTime time = LocalTime.now();
10 final LocalTime timeFromClock = LocalTime.now( clock );
11  
12 System.out.println( time );
13 System.out.println( timeFromClock );

控制台输出如下:

2014-04-12
2014-04-12
11:25:54.568
15:25:54.568

LocalDateTime类合并了LocalDate和LocalTime,它保存有ISO-8601日期系统的日期和时间,但是没有时区信息。让我们看一个简单的例子。

1 // Get the local date/time
2 final LocalDateTime datetime = LocalDateTime.now();
3 final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
4  
5 System.out.println( datetime );
6 System.out.println( datetimeFromClock );

输出如下:

2014-04-12T11:37:52.309
2014-04-12T15:37:52.309

如果您需要一个类持有日期时间和时区信息,可以使用ZonedDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。让我们看一些例子:

1 // Get the zoned date/time
2 final ZonedDateTime zonedDatetime = ZonedDateTime.now();
3 final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
4 final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );
5  
6 System.out.println( zonedDatetime );
7 System.out.println( zonedDatetimeFromClock );
8 System.out.println( zonedDatetimeFromZone );

输出如下:
2014-04-12T11:47:01.017-04:00[America/New_York]
2014-04-12T15:47:01.017Z
2014-04-12T08:47:01.017-07:00[America/Los_Angeles]

最后让我们看看Duration类,Duration持有的时间精确到纳秒。它让我们很容易计算两个日期中间的差异。让我们来看一下:

1 // Get duration between two dates
2 final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16000 );
3 final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16235959 );
4  
5 final Duration duration = Duration.between( from, to );
6 System.out.println( "Duration in days: " + duration.toDays() );
7 System.out.println( "Duration in hours: " + duration.toHours() );

上面的例子计算了两个日期(2014年4月16日和2014年5月16日)之间的持续时间(基于天数和小时)输出如下:

Duration in days: 365
Duration in hours: 8783

对于Java 8的新日期时间的总体印象还是比较积极的。一部分是因为有经历实战的Joda-Time的基础,还有一部分是因为日期时间终于被认真对待而且听取了开发人员的声音。关于更多的详细信息,请参考官方文档

 

4.4   Nashorn javascript引擎

Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。Nashorn javascript引擎只是javax.script.ScriptEngine另一个实现,而且规则也一样,允许Java和JavaScript互相操作。这里有个小例子:

1 ScriptEngineManager manager = new ScriptEngineManager();
2 ScriptEngine engine = manager.getEngineByName( "JavaScript" );
3  
4 System.out.println( engine.getClass().getName() );
5 System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );

输出如下:

jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2
4.5   Base64

对Base64的支持最终成了Java 8标准库的一部分,非常简单易用:

01 package com.javacodegeeks.java8.base64;
02  
03 import java.nio.charset.StandardCharsets;
04 import java.util.Base64;
05  
06 public class Base64s {
07 public static void main(String[] args) {
08 final String text = "Base64 finally in Java 8!";
09  
10 final String encoded = Base64
11 .getEncoder()
12 .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
13 System.out.println( encoded );
14  
15 final String decoded = new String(
16 Base64.getDecoder().decode( encoded ),
17 StandardCharsets.UTF_8 );
18 System.out.println( decoded );
19 }
20 }

控制台输出的编码和解码的字符串

QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!

新的Base64API也支持URL和MINE的编码解码。

(Base64.getUrlEncoder() / Base64.getUrlDecoder()Base64.getMimeEncoder() / Base64.getMimeDecoder()).

 

4.6   并行数组

Java 8新增加了很多方法支持并行的数组处理。最重要的大概是parallelSort()这个方法显著地使排序在多核计算机上速度加快。下面的小例子演示了这个新的方法(parallelXXX)的行为。

01 </pre>
02 <pre class="brush:java">package com.javacodegeeks.java8.parallel.arrays;
03  
04 import java.util.Arrays;
05 import java.util.concurrent.ThreadLocalRandom;
06  
07 public class ParallelArrays {
08     public static void main( String[] args ) {
09         long[] arrayOfLong = new long 20000 ];       
10  
11         Arrays.parallelSetAll( arrayOfLong,
12             index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
13         Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
14             i -> System.out.print( i + " " ) );
15         System.out.println();
16  
17         Arrays.parallelSort( arrayOfLong );
18         Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
19             i -> System.out.print( i + " " ) );
20         System.out.println();
21     }
22 }</pre>
23 <pre>

这一小段代码使用parallelSetAll() t方法填充这个长度是2000的数组,然后使用parallelSort() 排序。这个程序输出了排序前和排序后的10个数字来验证数组真的已经被排序了。示例可能的输出如下(请注意这些数字是随机产生的)

Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793

4.7   并发

在新增Stream机制与lambda的基础之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法来支持聚集操作。同时也在java.util.concurrent.ForkJoinPool类中加入了一些新方法来支持共有资源池(common pool)(请查看我们关于Java 并发的免费课程)。

新增的java.util.concurrent.locks.StampedLock类提供一直基于容量的锁,这种锁有三个模型来控制读写操作(它被认为是不太有名的java.util.concurrent.locks.ReadWriteLock类的替代者)。

在java.util.concurrent.atomic包中还增加了下面这些类:

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

5.  新的工具

Java 8 提供了一些新的命令行工具,在这节里我们将会介绍它们中最有趣的部分。

5.1  Nashorn引擎:jjs

jjs是个基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。例如,我们创建一个具有如下内容的func.js文件:

 

1 </span>
2 <pre>
3 function f() {
4 return 1;
5 };
6  
7 print( f() + 1 );

我们可以把这个文件作为参数传递给jjs使得这个文件可以在命令行中执行

1 <span style="font-size: 13px;">jjs func.js</span>

输出结果如下

2

更多的详细信息请参考官方文档

 

5.2 类依赖分析工具:jdeps

Jdeps是一个功能强大的命令行工具,它可以帮我们显示出包层级或者类层级java类文件的依赖关系。它接受class文件、目录、jar文件作为输入,默认情况下,jdeps会输出到控制台。

作为例子,让我们看看现在很流行的Spring框架的库的依赖关系报告。为了让报告短一些,我们只分析一个jar:org.springframework.core-3.0.5.RELEASE.jar.

jdeps org.springframework.core-3.0.5.RELEASE.jar 这个命令输出内容很多,我们只看其中的一部分,这些依赖关系根绝包来分组,如果依赖关系在classpath里找不到,就会显示not found.

01 </pre>
02 <pre class="brush:java">org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
03    org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
04       -> java.io
05       -> java.lang
06       -> java.lang.annotation
07       -> java.lang.ref
08       -> java.lang.reflect
09       -> java.util
10       -> java.util.concurrent
11       -> org.apache.commons.logging                         not found
12       -> org.springframework.asm                            not found
13       -> org.springframework.asm.commons                    not found
14    org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
15       -> java.lang
16       -> java.lang.annotation
17       -> java.lang.reflect
18       -> java.util</pre>
19 <pre>

更多的详细信息请参考官方文档

6. JVM的新特性

JVM内存永久区已经被metaspace替换(JEP 122)。JVM参数 -XX:PermSize 和 –XX:MaxPermSizeXX:MetaSpaceSize 和 -XX:MaxMetaspaceSize代替

7. 结论

更多展望:Java 8通过发布一些可以增加程序员生产力的特性来推进这个伟大的平台的进步。现在把生产环境迁移到Java 8还为时尚早,但是在接下来的几个月里,它会被大众慢慢的接受。毫无疑问,现在是时候让你的代码与Java 8兼容,并且在Java 8足够安全稳定的时候迁移到Java 8。

作为社区对Java 8的认可,最近Pivotal发布了可在生产环境下支持Java 8的Spring Framework 4.0.3

如果你喜欢这篇文章,请订阅我们的邮件列表来查看每周的更新以及免费赠送的白皮书。对于更高级的教程,请查看我们的[JCG学院][JCG]。

我们欢迎你对Java 8中激动人心的特性进行评论!

 8. 资源

下面一些文章从不同层面上深度讨论了Java 8的特性:

时间: 2024-10-31 14:56:29

Java 8 特性 – 终极手册(二)的相关文章

Java 8 特性 – 终极手册

原文链接,原文作者:Andrey Redko ,译者:Justin,校对:郭蕾 1.简介 毫无疑问,Java 8是自Java  5(2004年)发布以来Java语言最大的一次版本升级,Java 8带来了很多的新特性,比如编译器.类库.开发工具和JVM(Java虚拟机).在这篇教程中我们将会学习这些新特性,并通过真实例子演示说明它们适用的场景. 本教程由下面几部分组成,它们分别涉及到Java平台某一特定方面的内容: 语言 编译器 类库 开发工具 运行时(Java虚拟机) 2.Java的新特性 总体

Java 8 特性 – 终极手册(一)

1.简介 毫无疑问,Java 8是自Java  5(2004年)发布以来Java语言最大的一次版本升级,Java 8带来了很多的新特性,比如编译器.类库.开发工具和JVM(Java虚拟机).在这篇教程中我们将会学习这些新特性,并通过真实例子演示说明它们适用的场景. 本教程由下面几部分组成,它们分别涉及到Java平台某一特定方面的内容: 语言 编译器 类库 开发工具 运行时(Java虚拟机) 2.Java的新特性 总体来说,Java 8是一个大的版本升级.有人可能会说,Java 8的新特性非常令人

【译】Java 8的新特性—终极版

文/杜琪(简书作者) 原文链接:http://www.jianshu.com/p/5b800057f2d8 著作权归作者所有,转载请联系作者获得授权,并标注"简书作者". 声明:本文翻译自Java 8 Features Tutorial – The ULTIMATE Guide,翻译过程中发现并发编程网已经有同学翻译过了:Java 8 特性 – 终极手册,我还是坚持自己翻译了一版(写作驱动学习,加深印象),有些地方参考了该同学的. Java 8 前言: Java 8 已经发布很久了,很

Java 8 新特性终极版指南详解_java

前言: Java 8已经公布有一段时间了,种种迹象表明Java 8是一个有重大改变的发行版.在Java Code Geeks上已经有很多介绍Java 8新特性的文章,例如Playing with Java 8 – Lambdas and Concurrency.Java 8 Date Time API Tutorial : LocalDateTime和Abstract Class Versus Interface in the JDK 8 Era.本文还参考了一些其他资料,例如:15 Must

利用Java注解特性加载属性文件(properties)的值到Java类

在此之前我都是写个PropertyUtil类来加载配置文件,然后通过get方法,把key对应的值取出来. Spring提供一个PropertyPlaceholderConfigurer类,可以读取配置文件,然后在Spring配置文件通过${hibernate.dialect}这种方式注入到JavaBean中,有个不好的地方就是,要在代码中取的时候不是很方便. 然后在接触到Java注解特注解技术以后,感觉这个东东很好,hibernate映射,WebService都可以通过注解来完成,方便的很多,然

JAVA之旅(十二)——Thread,run和start的特点,线程运行状态,获取线程对象和名称,多线程实例演示,使用Runnable接口

JAVA之旅(十二)--Thread,run和start的特点,线程运行状态,获取线程对象和名称,多线程实例演示,使用Runnable接口 开始挑战一些难度了,线程和I/O方面的操作了,继续坚持 一.Thread 如何在自定义的代码中,自定义一个线程呢? 我们查看API文档,我们要启动一个线程,先实现一个子类, package com.lgl.hellojava; public class MyThread extends Thread { @Override public void run()

微信公众平台java开发关于微信扫描二维码的问题

问题描述 微信公众平台java开发关于微信扫描二维码的问题 某微信用户已经关注了我的公众号,然后他扫描了我公众平台的一个二维码,我想获取他的用户名,要怎么做?扫描二维码,是一个触发事件吗? 感谢各位了! 解决方案 他如果仅仅只是扫描的话,你是看不到他的用户名的.但是如果他关注了你的公众号,你在公众平台的用户管理里,是可以看到他的用户名信息的.希望能够帮到你. 解决方案二: 这个问题我自己研究的差不多了.用微信自己的扫一扫功能是不可能将事件推送给自己的公众平台的,我想它是推送事件给微信的公众平台了

无人使用的Java语言特性

JavaLobby上的一位读者兼作者读了Java十大最无用特性之后,也列出了自己心中无人使用的Java语言 特性,大家也可以过来评评 之前,我在JavaLobby上读了Anthony Goubard的"Java10大最无用的特性".我同意他的一些选择,但我 认为他忽略了一些无人使用的关键特性.我仅将自己限制在语言级特性上(API 太庞大了),下面是另外 4个未被使用的Java特性. 1. 严格的浮点数 也许在某个地方,Java的strictfp关键字对某个程序员十分重要,但我还没遇到过

在java中一维数组和二维数组有什么区别吗?

问题描述 在java中一维数组和二维数组有什么区别吗? 求大神指点java中一维数组和二维数组的区别,为什么一位数组是一行 二维数组可以定义多行 解决方案 如图黑色的是一维数组红色的是二维数组一维数组用来存数据二维数组用来存一维数组 解决方案二: JAVA中一维数组和二维数组的定义一维数组及二维数组的用法java中arraylist和一维数组二维数组的转换 解决方案三: 一楼正解,一维和二维的区别就是线和面的区别,一维的数组你就可以通过一个下标来准确定位,而二维的就需要有两个就像坐标系一样,一维