编者注:在本文中,我们提供了全面的Java 8功能教程。 自Java 8公开发布以来已经有一段时间了,所有迹象都表明这是一个非常重要的版本。
我们在Java Code Geeks处提供了大量教程,例如“ 玩Java 8 – Lambda和并发” ,“ Java 8 Date Time API教程: JDK 8时代的 LocalDateTime和抽象类与接口” 。
我们还引用了其他来源的15篇必读Java 8教程 。 当然,我们还研究了一些不足之处,例如Java 8的黑暗面 。
现在,是时候将所有Java 8的主要功能集中到一本参考文章中,以使您阅读愉快。 请享用!
目录
- 1.简介 2. Java语言的新功能
- 2.1。 Lambda和功能接口 2.2。 接口默认方法和静态方法 2.3。 方法参考 2.4。 重复注释 2.5。 更好的类型推断 2.6。 扩展注释支持
3. Java编译器的新功能 - 3.1。 参数名称
4. Java库中的新功能 - 4.1。 可选的 4.2。 流 4.3。 日期/时间API(JSR 310) 4.4。 Nashorn JavaScript引擎 4.5。 Base64 4.6。 平行阵列 4.7。 并发
5.新的Java工具 - 5.1。 Nashorn引擎:jjs 5.2。 类依赖分析器:jdeps
6. Java运行时(JVM)的新功能 7.结论 8.资源
1.简介
毫无疑问, Java 8发行版是Java 5以来(Java发行版早在2004年发布)以来最伟大的事情。 它为Java带来了许多新功能,包括一种语言,其编译器,库,工具以及JVM(Java虚拟机)本身。 在本教程中,我们将研究所有这些更改,并在实际示例中演示不同的使用场景。
本教程由几个部分组成,每个部分都涉及平台的特定方面:
- 语言
- 编译器
- 图书馆
- 工具
- 运行时(JVM)
2. Java语言的新功能
Java 8无论如何都是主要版本。 可能有人说,要实现每个Java开发人员正在寻找的功能,最终确定都花了很长时间。 在本节中,我们将介绍其中的大部分内容。
Lambda和功能接口
Lambda(也称为闭包)是整个Java 8版本中最大,最期待的语言更改。 它们使我们可以将功能视为方法参数(传递函数),或将代码视为数据:每个功能开发人员都非常熟悉的概念。 从第一天开始,JVM平台上的许多语言(Groovy, Scala等等)都具有lambda,但是Java开发人员别无选择,只能用样板匿名类来破坏lambda。
Lambdas设计讨论花费了大量时间和社区精力。 但是最后,我们找到了折衷方案,从而导致了新的简洁紧凑的语言结构。 以最简单的形式,lambda可以表示为以逗号分隔的参数列表,即–>符号和主体。 例如:
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可以引用类成员和局部变量(如果没有,则暗指使它们有效地成为最终变量)。 例如,这两个片段是等效的:
String separator = "," ; Arrays.asList( "a" , "b" , "d" ).forEach( ( String e ) -> System.out.print( e + separator ) );
和:
final String separator = "," ; Arrays.asList( "a" , "b" , "d" ).forEach( ( String e ) -> System.out.print( e + separator ) );
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; } );
语言设计师对如何使已经存在的功能实现lambda友好性进行了大量思考。 结果,出现了功能接口的概念。 函数接口是只有一种方法的接口。 这样,它可以隐式转换为lambda表达式。 java.lang.Runnable和java.util.concurrent.Callable是功能接口的两个很好的例子。 实际上,功能接口是易碎的:如果有人在接口定义中仅添加了另一种方法,它将不再起作用,并且编译过程将失败。 为了克服这种脆弱性并明确声明接口的作用,Java 8添加了特殊的注解@FunctionalInterface(Java库中所有现有的接口也已使用@FunctionalInterface进行注解)。 让我们看一下这个简单的功能接口定义:
@FunctionalInterface public interface Functional { void method(); }
要记住的一件事: 默认方法和静态方法不会破坏功能接口协定,可以声明为:
@FunctionalInterface public interface FunctionalDefaultMethods { void method(); default void defaultMethod() { } }
Lambda是Java 8的最大卖点。它有潜力吸引越来越多的开发人员使用这个强大的平台,并为纯Java中的函数式编程概念提供最新的支持。 有关更多详细信息,请参阅官方文档 。
接口的默认方法和静态方法
Java 8用两个新概念扩展了接口声明:默认方法和静态方法。 默认方法使接口在某种程度上类似于特征,但目标有所不同。 它们允许向现有接口添加新方法,而不会破坏与为这些接口的较早版本编写的代码的二进制兼容性。
默认方法和抽象方法之间的区别在于,需要实现抽象方法。 但是默认方法不是。 相反,每个接口都必须提供所谓的默认实现,并且所有实现者都将默认继承它(可以在需要时覆盖此默认实现)。 让我们看下面的例子。
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() 。 其中一个类DefaultableImpl实现了此接口,而保留了默认方法的实现。 另一个OverridableImpl重写默认实现并提供自己的实现。
Java 8提供的另一个有趣的功能是接口可以声明(并提供实现)静态方法。 这是一个例子。
private interface DefaulableFactory { // Interfaces now allow static methods static Defaulable create( Supplier< Defaulable > supplier ) { return supplier.get(); } }
下面的小代码段将上面示例中的默认方法和静态方法粘合在一起。
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() ); }
该程序的控制台输出如下所示:
Default implementation Overridden implementation
JVM上的默认方法实现非常有效,并且方法调用的字节码指令支持该方法。 默认方法允许现有的Java接口发展而不会中断编译过程。 很好的例子是添加到java.util.Collection接口的大量方法: stream() , parallelStream() , forEach() , removeIf() ……
尽管功能强大,但应谨慎使用默认方法:在将方法声明为默认方法之前,最好三思,如果确实需要,则可能会导致复杂层次结构中的歧义和编译错误。 有关更多详细信息,请参阅官方文档 。
方法参考
方法引用提供了有用的语法,可以直接引用现有的方法或Java类或对象(实例)的构造函数。 与Lambdas表达式结合使用时,方法引用使语言构造看起来紧凑而简洁,从而简化了样板。
下面,将Car类作为不同方法定义的示例,让我们区分四种受支持的方法引用类型。
public static Car { 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 。 请注意,构造函数没有参数。
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 );
将所有这些示例作为Java程序运行会在控制台上产生以下输出(实际的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
有关方法引用的更多示例和详细信息,请参考官方文档。
重复注释
自从Java 5引入了注释支持以来,此功能就非常流行并且得到了广泛的使用。 但是,注释使用的局限性之一是不能在同一位置多次声明同一注释。 Java 8违反了此规则,并引入了重复注释。 它允许相同的注释在声明的位置重复多次。
重复的注释本身应使用@Repeatable注释进行注释。 实际上,这不是语言更改,而是更多的编译器技巧,因为该技术的原理保持不变。 让我们看一个简单的例子:
package com.javacodegeeks.java8.repeatable.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; public class RepeatingAnnotations { @Target ( ElementType.TYPE ) @Retention ( RetentionPolicy.RUNTIME ) public @interface Filters { Filter[] value(); } @Target ( ElementType.TYPE ) @Retention ( RetentionPolicy.RUNTIME ) @Repeatable ( Filters. class ) public @interface Filter { String value(); }; @Filter ( "filter1" ) @Filter ( "filter2" ) public interface Filterable { } public static void main(String[] args) { for ( Filter filter: Filterable. class .getAnnotationsByType( Filter. class ) ) { System.out.println( filter.value() ); } } }
正如我们所看到的,是一个注释类过滤器与@Repeatable(过滤器。 类 )注解。 过滤器只是过滤器注释的持有者,但是Java编译器试图向开发人员隐藏其存在。 因此, Filterable接口具有两次定义的Filter注释(没有提及Filters )。
此外,反射API提供了新的方法getAnnotationsByType()返回某种类型的重复注释(请注意,筛选。 类 .getAnnotation(过滤器。 类 )将返回过滤器的编译器注入的情况下)。
程序输出如下所示:
filter1 filter2
有关更多详细信息,请参阅官方文档 。
更好的类型推断
Java 8编译器在类型推断方面进行了很多改进。 在许多情况下,可以通过使代码保持整洁的编译器来推断显式类型参数。 让我们看一个例子。
package com.javacodegeeks.java8.type.inference; public class Value< T > { public static < T > T defaultValue() { return null ; } public T getOrDefault( T value, T defaultValue ) { return ( value != null ) ? value : defaultValue; ) ? value : defaultValue; } }
这是Value <String>类型的用法。
package com.javacodegeeks.java8.type.inference; public class TypeInference { public static void main(String[] args) { final Value< String > value = new Value<>(); value.getOrDefault( "22" , Value.defaultValue() ); } }
Value的类型参数。 推断出defaultValue () ,不需要提供该值。 在Java 7中,相同的示例将无法编译,应将其重写为Value。<String> defaultValue () 。
扩展注释支持
Java 8扩展了可以使用注释的上下文。 现在,几乎可以注释所有内容:局部变量,泛型类型,超类和实现接口,甚至是方法的异常声明。 下面是几个示例。
package com.javacodegeeks.java8.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Collection; public class Annotations { @Retention ( RetentionPolicy.RUNTIME ) @Target ( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } ) public @interface NonEmpty { } public static class Holder< @NonEmpty T > extends @NonEmpty Object { public void method() throws @NonEmpty Exception { } } @SuppressWarnings ( "unused" ) public static void main(String[] args) { final Holder< String > holder = new @NonEmpty Holder< String >(); @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>(); } }
ElementType。 TYPE_USE和ElementType。 TYPE_PARAMETER是两种新的元素类型,用于描述适用的注释上下文。 Annotation Processing API也进行了一些小的更改,以识别Java编程语言中的那些新类型的注释。
3. Java编译器的新功能
参数名称
从字面上看,Java开发人员已经发明了各种方法来将方法参数名称保留为Java字节码 ,并使它们在运行时可用(例如Paranamer库 )。 最后,Java 8将此要求苛刻的功能烘焙到语言中(使用Reflection API和Parameter.getName()方法)和字节码(使用新的javac编译器参数–parameters )。
package com.javacodegeeks.java8.parameter.names; import java.lang.reflect.Method; import java.lang.reflect.Parameter; public class ParameterNames { public static void main(String[] args) throws Exception { Method method = ParameterNames. class .getMethod( "main" , String[]. class ); for ( final Parameter parameter: method.getParameters() ) { System.out.println( "Parameter: " + parameter.getName() ); } } }
如果在不使用–parameters参数的情况下编译此类,然后运行该程序,则会看到类似以下内容:
Parameter: arg0
将–parameters参数传递给编译器后,程序输出将有所不同(将显示参数的实际名称):
Parameter: args
对于有经验的Maven用户 ,可以使用maven-compiler-plugin的 configuration部分将–parameters参数添加到编译器中 :
< plugin > < groupId >org.apache.maven.plugins</ groupId > < artifactId >maven-compiler-plugin</ artifactId > < version >3.1</ version > < configuration > < compilerArgument >-parameters</ compilerArgument > < source >1.8</ source > < target >1.8</ target > </ configuration > </ plugin >
带有Java 8的最新Eclipse Kepler SR2版本(请查看此下载说明 )支持提供了有用的配置选项来控制此编译器设置,如下图所示。
另外,为了验证参数名称的可用性, Parameter类提供了一种方便的方法isNamePresent() 。
4. Java库中的新功能
Java 8添加了许多新类并扩展了现有类,以便为现代并发,函数式编程,日期/时间等提供更好的支持。
可选的
迄今为止, 著名的NullPointerException是Java应用程序失败的最常见原因。 很久以前,伟大的Google Guava项目引入了Optional作为NullPointerException的解决方案,通过空检查防止代码库污染,并鼓励开发人员编写更干净的代码。 受Google Guava的启发, Optional现在是Java 8库的一部分。
可选的只是一个容器:它可以保存某个T类型的值 ,或者可以为null 。 它提供了许多有用的方法,因此显式null检查不再有任何借口。 请参阅Java 8官方文档以获取更多详细信息。
我们将看一下Optional用法的两个小例子:具有可为空的值和不允许为null的值。
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的此实例具有非null值,则isPresent()方法返回true,否则返回false 。 如果Optional通过接受生成默认值的函数为null ,则orElseGet()方法提供了后备机制。 map()方法转换当前Optional的值,并返回新的Optional实例。 orElse()方法类似于orElseGet(),但它接受函数默认值而不是函数。 这是该程序的输出:
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!
有关更多详细信息,请参阅官方文档。
流
新添加的Stream API ( java.util.stream )在Java 中引入了现实世界中的函数式编程。 到目前为止,这是Java库中最全面的扩展,旨在通过允许Java开发人员编写有效,简洁,简洁的代码来提高他们的生产力。
Stream API大大简化了集合的处理(但不仅限于Java集合,我们将在后面看到)。 让我们从名为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 。 然后,让我们介绍一些要处理的任务。
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中,答案是流:支持顺序和并行聚合操作的一系列元素。
// 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
这里发生了几件事。 首先,将任务集合转换为其流表示形式。 然后,对流的过滤操作将所有已关闭的任务过滤掉。 上下一步骤中,mapToInt操作任务 S的整数 s使用每个任务实例的任务:: getPoints方法的流的流转换。 最后,使用求和方法对所有点求和,得出最终结果。
在继续下一个示例之前,需要注意一些有关流的注意事项( 此处有更多详细信息 )。 流操作分为中间操作和终端操作。
中间操作返回一个新的流。 它们总是很懒惰,执行诸如filter之类的中间操作实际上并不执行任何过滤,而是创建一个新的流,该新流在遍历时将包含与给定谓词匹配的初始流的元素。
终端操作(例如forEach或sum )可能会遍历流以产生结果或副作用。 执行终端操作后,流管道被视为已消耗,无法再使用。 在几乎所有情况下,终端操作人员都很渴望完成对基础数据源的遍历。
流的另一个价值主张是开箱即用的并行处理支持。 让我们看一下这个示例,该示例确实总结了所有任务的要点。
// 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
通常,需要根据某些标准对收集元素进行分组。 流可以帮助实现这一点,下面的示例也将对此进行说明。
// 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 ]]}
为了完成任务示例,让我们基于任务集的点来计算每个任务在整个集合中的总体百分比(或权重)。
// 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 * // LongStream )( 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操作非常适合从流处理中受益。 这是一个确认这一点的小例子。
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 ); }
在流上调用的onClose方法返回具有附加关闭处理程序的等效流。 在流上调用close()方法时,将运行关闭处理程序。
通过接口的默认方法和静态方法生成的 Stream API以及Lambda和方法引用是Java 8对软件开发中现代范例的响应。 有关更多详细信息,请参阅官方文档 。
日期/时间API(JSR 310)
Java 8通过提供新的Date-Time API(JSR 310),进一步进行了日期和时间管理。 对于Java开发人员而言,日期和时间操纵是最糟糕的痛苦之一。 紧随其后的java.util.Calendar标准java.util.Date根本没有改善这种情况(可以说,使情况更加混乱)。
这就是Joda-Time的诞生方式:Java的绝佳替代日期/时间API。 Java 8的新Date-Time API(JSR 310)受Joda-Time的影响很大,并充分利用了它的优势。 新的java.time包包含日期,时间,日期/时间,时区,瞬间,持续时间和时钟操作的所有类 。 在API的设计中,非常认真地考虑了不变性:不允许进行任何更改(从java.util.Calendar中吸取的艰巨教训)。 如果需要修改,将返回相应类的新实例。
让我们看一下关键类及其用法示例。 第一类是时钟 ,它使用时区提供对当前时刻,日期和时间的访问。 可以使用Clock代替System.currentTimeMillis()和TimeZone.getDefault() 。
// Get the system clock as UTC offset final Clock clock = Clock.systemUTC(); System.out.println( clock.instant() ); System.out.println( clock.millis() );
控制台上的示例输出:
2014 - 04 2014 -12T15: 19 : 29 .282Z 1397315969360
我们将要查看的其他新类是LocaleDate和LocalTime 。 在ISO- 8601日历系统中,LocaleDate仅保存日期部分,没有时区。 分别地, LocaleTime在ISO- 8601日历系统中仅保存没有时区的时间部分 。 LocaleDate和LocaleTime都可以从Clock创建。
// Get the local date and local time final LocalDate date = LocalDate.now(); final LocalDate dateFromClock = LocalDate.now( clock ); System.out.println( date ); System.out.println( dateFromClock ); // Get the local date and local time final LocalTime time = LocalTime.now(); final LocalTime timeFromClock = LocalTime.now( clock ); System.out.println( time ); System.out.println( timeFromClock );
控制台上的示例输出:
2014 - 04 - 12 2014 - 04 - 12 11 : 25 : 54.568 15 : 25 : 54.568
LocalDateTime将LocaleDate和LocalTime结合在一起,并在ISO-8601日历系统中保存带时间的日期,但没有时区。 快速示例如下所示。
// Get the local date/time final LocalDateTime datetime = LocalDateTime.now(); final LocalDateTime datetimeFromClock = LocalDateTime.now( clock ); System.out.println( datetime ); System.out.println( datetimeFromClock );
控制台上的示例输出:
2014 - 04 2014 -12T11: 37 : 52.309 2014 - 04 2014 -12T15: 37 : 52.309
如果您需要特定时区的日期/时间,则可以使用ZonedDateTime来提供帮助。 在ISO-8601日历系统中,它保存带有日期和时区的日期。 这是不同时区的几个示例。
// Get the zoned date/time final ZonedDateTime zonedDatetime = ZonedDateTime.now(); final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock ); final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) ); System.out.println( zonedDatetime ); System.out.println( zonedDatetimeFromClock ); System.out.println( zonedDatetimeFromZone );
控制台上的示例输出:
2014 - 04 -12T11: 47 : 01.017 - 04 : 00 [America/New_York] 2014 - 04 2014 -12T15: 47 : 01 .017Z 2014 - 04 -12T08: 47 : 01.017 - 07 : 00 [America/Los_Angeles]
最后,让我们看一下Duration类:以秒和纳秒为单位的时间量。 计算两个日期之间的差异非常容易。 让我们来看看。
// Get duration between two dates final LocalDateTime from = LocalDateTime.of( 2014 , Month.APRIL, 16 , 0 , 0 , 0 ); final LocalDateTime to = LocalDateTime.of( 2015 , Month.APRIL, 16 , 23 , 59 , 59 ); final Duration duration = Duration.between( from, to ); System.out.println( "Duration in days: " + duration.toDays() ); System.out.println( "Duration in hours: " + duration.toHours() );
上面的示例计算了2014 年4月16日至2015年 4月 16日这两个日期之间的持续时间(以天和小时为单位)。 这是控制台上的示例输出:
Duration in days: 365 Duration in hours: 8783
关于Java 8的新日期/时间API的总体印象是非常非常积极的。 部分是由于它经过了久经考验的基础( Joda-Time ),部分是因为这一次最终得到了认真解决,并听到了开发人员的声音。 有关更多详细信息,请参阅官方文档 。
Nashorn JavaScript引擎
Java 8附带了新的Nashorn JavaScript引擎 ,该引擎允许在JVM上开发和运行某些类型JavaScript应用程序。 Nashorn JavaScript引擎只是javax.script.ScriptEngine的另一种实现,并且遵循相同的规则集,从而允许Java和JavaScript互操作。 这是一个小例子。
ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName( "JavaScript" ); System.out.println( engine.getClass().getName() ); System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
控制台上的示例输出:
jdk.nashorn.api.scripting.NashornScriptEngine Result: 2
我们将在后面专门讨论新Java工具的部分中回到Nashorn。
Base64
最后,随着Java 8版本的发布, 对Base64编码的支持已进入Java标准库。 如以下示例所示,它非常易于使用。
package com.javacodegeeks.java8.base64; import java.nio.charset.StandardCharsets; import java.util.Base64; public class Base64s { public static void main(String[] args) { final String text = "Base64 finally in Java 8!" ; final String encoded = Base64 .getEncoder() .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) ); System.out.println( encoded ); final String decoded = new String( Base64.getDecoder().decode( encoded ), StandardCharsets.UTF_8 ); System.out.println( decoded ); } }
程序运行的控制台输出显示编码和解码的文本:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ== Base64 finally in Java 8 !
Base64类( Base64。getUrlEncoder () / Base64。getUrlDecoder () , Base64。getMimeEncoder () / Base64。getMimeDecoder () )还提供URL友好的编码器/解码器和MIME友好的编码器/解码器。
平行阵列
Java 8版本增加了许多新方法来允许并行数组处理。 可以说,最重要的一个是parallelSort() ,它可以显着加快多核计算机上的排序速度。 下面的小示例演示了该新方法家族( parallelXxx )的实际作用。
package com.javacodegeeks.java8.parallel.arrays; import java.util.Arrays; import java.util.concurrent.ThreadLocalRandom; public class ParallelArrays { public static void main( String[] args ) { long [] arrayOfLong = new long [ 20000 ]; Arrays.parallelSetAll( arrayOfLong, index -> ThreadLocalRandom.current().nextInt( 1000000 ) ); Arrays.stream( arrayOfLong ).limit( 10 ).forEach( i -> System.out.print( i + " " ) ); System.out.println(); Arrays.parallelSort( arrayOfLong ); Arrays.stream( arrayOfLong ).limit( 10 ).forEach( i -> System.out.print( i + " " ) ); System.out.println(); } }
这个小代码段使用方法parallelSetAll()填充具有20000个随机值的数组。 之后,将应用parallelSort() 。 程序在排序前后会输出前10个元素,以确保数组真正有序。 示例程序输出看起来像这样(请注意,数组元素是随机生成的):
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 Sorted: 39 220 263 268 325 607 655 678 723 793
并发
新方法已添加到java.util.concurrent.ConcurrentHashMap类中,以支持基于新添加的流功能和lambda表达式的聚合操作。 另外,已经将新方法添加到java.util.concurrent.ForkJoinPool类中,以支持公用池(另请参见有关Java并发性的免费课程 )。
添加了新的java.util.concurrent.locks.StampedLock类,以提供具有三种模式的基于功能的锁,用于控制读/写访问(它可能被认为是臭名昭著的java.util.concurrent.locks.ReadWriteLock的替代方法。 )。
新类已添加到java.util.concurrent.atomic包中:
- 双累加器
- 双重加法器
- 长累积器
- 长加法器
5.新的Java工具
Java 8附带了一组新的命令行工具。 在本节中,我们将研究其中最有趣的部分。
Nashorn引擎:jjs
jjs是基于命令行的独立Nashorn引擎。 它接受JavaScript源代码文件列表作为参数并运行它们。 例如,让我们创建一个具有以下内容的文件func.js :
function f() { return 1 ; }; print( f() + 1 );
要从命令执行此fie,让我们将其作为参数传递给jjs :
jjs func.js
控制台上的输出将是:
2
有关更多详细信息,请参阅官方文档 。
类依赖分析器:jdeps
jdeps是一个非常出色的命令行工具。 它显示了Java类文件的包级别或类级别的依赖关系。 它接受.class文件, 目录或JAR文件作为输入。 默认情况下, jdeps将依赖项输出到系统输出(控制台)。
例如,让我们看一下流行的Spring Framework库的依赖项报告。 为了使示例简短,让我们仅分析一个JAR文件: org.springframework.core-3.0.5.RELEASE.jar 。
jdeps org.springframework.core- 3.0 . 5 .RELEASE.jar
该命令输出很多,因此我们将对其进行研究。 依赖项按程序包分组。 如果依赖项在类路径上不可用,则显示为not found 。
org.springframework.core- 3.0 . 5 .RELEASE.jar -> C:\Program Files\Java\jdk1. 8.0 \jre\lib\rt.jar org.springframework.core (org.springframework.core- 3.0 . 5 .RELEASE.jar) -> java.io -> java.lang -> java.lang.annotation -> java.lang.ref -> java.lang.reflect -> java.util -> java.util.concurrent -> org.apache.commons.logging not found -> org.springframework.asm not found -> org.springframework.asm.commons not found org.springframework.core.annotation (org.springframework.core- 3.0 . 5 .RELEASE.jar) -> java.lang -> java.lang.annotation -> java.lang.reflect -> java.util
For more details please refer to official documentation .
6. New Features in Java runtime (JVM)
The PermGen space is gone and has been replaced with Metaspace ( JEP 122 ). The JVM options -XX:PermSize and – XX:MaxPermSize have been replaced by -XX:MetaSpaceSize and -XX:MaxMetaspaceSize respectively.
7.结论
The future is here: Java 8 moves this great platform forward by delivering the features to make developers much more productive. It is too early to move the production systems to Java 8 but in the next couples of months its adoption should slowly start growing. Nevertheless the time is right to start preparing your code bases to be compatible with Java 8 and to be ready to turn the switch once Java 8 proves to be safe and stable enough.
As a confirmation of community Java 8 acceptance, recently Pivotal released Spring Framework 4.0.3 with production-ready Java 8 support .
如果您喜欢此功能,请订阅我们的时事通讯,以享受每周更新和免费白皮书! 另外,请查看我们的课程以获得更高级的培训!
You are welcome to contribute with your comments about the exciting new Java 8 features!
8. Resources
Some additional resources which discuss in depth different aspects of Java 8 features:
- Java 8 Tutorials on JCG Examples: https://examples.javacodegeeks.com/?s=java+8
- What's New in JDK 8: http://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html
- The Java Tutorials: http://docs.oracle.com/javase/tutorial/
- WildFly 8, JDK 8, NetBeans 8, Java EE 7: http://blog.arungupta.me/2014/03/wildfly8-jdk8-netbeans8-javaee7-excellent-combo-enterprise-java/
- Java 8 Tutorial: http://winterbe.com/posts/2014/03/16/java-8-tutorial/
- JDK 8 Command-line Static Dependency Checker: http://marxsoftware.blogspot.ca/2014/03/jdeps.html
- The Illuminating Javadoc of JDK 8: http://marxsoftware.blogspot.ca/2014/03/illuminating-javadoc-of-jdk-8.html
- The Dark Side of Java 8: http://blog.jooq.org/2014/04/04/java-8-friday-the-dark-side-of-java-8/
- Installing Java™ 8 Support in Eclipse Kepler SR2: http://www.eclipse.org/downloads/java8/
- Java 8: http://www.baeldung.com/java8
- Oracle Nashorn. A Next-Generation JavaScript Engine for the JVM: http://www.oracle.com/technetwork/articles/java/jf14-nashorn-2126515.html
Java 8 Features Tutorial was last updated on Oct. 3, 2016
翻译自: https://www.javacodegeeks.com/java-8-features-tutorial.html