转载来源:https://www.javacodegeeks.com/2014/05/java-8-features-tutorial.html
Java 8 新特性教程 - 终极指南
编者按:在这篇文章中,我们提供了一个全面的Java 8功能教程。自从Java 8出现在公众面前已经有一段时间了,所有事情都表明这是一个非常重要的版本。
我们在Java Code Geeks上提供了大量的教程,比如在Java 8中使用Java 8 - Lambdas和Concurrency,Java 8 Date Time API教程:LocalDateTime和Abstract Class Versus接口。
我们还从其他来源引用了15 Must Read Java 8 Tutorials。当然,我们也研究了一些不足之处,比如Java 8的黑暗面。
现在,是时候在一个参考文章下收集所有主要的Java 8功能,以获得阅读乐趣。请享用!
想成为Java 8 Ninja?
订阅我们的新闻通讯并立即下载Java 8功能终极指南!
为了让您快速掌握主要的Java 8版本,我们编写了一个带有所有新功能和好东西的kick-ass指南!除了在线学习,您可以下载PDF格式的电子书!
目录
1.简介
2. Java语言的新功能
2.1。Lambdas和功能接口
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年发布,早在2004年)。它为Java作为一种语言,它的编译器,库,工具和JVM(Java虚拟机)本身带来了大量新功能。在本教程中,我们将了解所有这些更改,并演示实际示例中的不同使用方案。
本教程由几个部分组成,每个部分都涉及平台的特定方面:
2. Java语言的新功能
Java 8无论如何都是主要版本。有人可能会说,为了实现每个Java开发人员都在寻找的功能,需要花费很长时间才能完成。在本节中,我们将介绍其中的大部分内容。
2.1。Lambdas和功能接口
Lambdas(也称为闭包)是整个Java 8版本中最大和最期待的语言变化。它们允许我们将功能视为方法参数(传递函数),或将代码视为数据:每个功能开发人员都非常熟悉的概念。JVM平台上的许多语言(Groovy,Scala,......)从第一天起就开始使用lambdas,但Java开发人员别无选择,只能使用样板匿名类来攻击lambda。
Lambdas的设计讨论花了很多时间和社区的努力。但最后,已经找到了权衡取舍,从而产生了新的简洁和紧凑的语言结构。在最简单的形式中,lambda可以表示为逗号分隔的参数列表,- >符号和正文。例如:
1 |
Arrays.asList( "a" , "b" , "d" ).forEach( e -> System.out.println( e ) ); |
请注意编译器推断的参数类型e。或者,您可以显式提供参数的类型,将定义包含在括号中。例如:
1 |
Arrays.asList( "a" , "b" , "d" ).forEach( ( String e ) -> System.out.println( e ) ); |
如果lambda的主体更复杂,它可能被包装成方括号,就像Java中常用的函数定义一样。例如:
1 |
Arrays.asList( "a" , "b" , "d" ).forEach( e -> { |
Lambdas可以引用类成员和局部变量(如果不是,则隐式地使它们成为最终的)。例如,这两个片段是等效的:
1 |
String separator = "," ; |
2 |
Arrays.asList( "a" , "b" , "d" ).forEach( |
3 |
( String e ) -> System.out.print( e + separator ) ); |
和:
1 |
final String separator = "," ; |
2 |
Arrays.asList( "a" , "b" , "d" ).forEach( |
3 |
( String e ) -> System.out.print( e + separator ) ); |
Lambdas可能会返回一个值。返回值的类型将由编译器推断。在返回如果拉姆达身体只是一个班轮不需要声明。下面的两个代码段是等效的:
1 |
Arrays.asList( "a" , "b" , "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) ); |
和:
1 |
Arrays.asList( "a" , "b" , "d" ).sort( ( e1, e2 ) -> { |
2 |
int result = e1.compareTo( e2 ); |
语言设计者对如何使现有的lambda友好功能进行了大量的思考。结果,出现了功能接口的概念。功能界面是一个只有一个方法的界面。因此,它可以隐式转换为lambda表达式。在一个java.lang.Runnable和java.util.concurrent.Callable是功能接口的两个很好的例子。在实践中,功能接口是脆弱的:如果有人只在接口定义中添加另一个方法,它将不再起作用,编译过程将失败。为了克服这种脆弱性并明确声明接口的意图是正常的,Java 8添加了特殊的注释@FunctionalInterface(Java库中的所有现有接口也都使用@FunctionalInterface注释)。我们来看看这个简单的功能接口定义:
2 |
public interface Functional { |
要记住一件事:默认和静态方法不会破坏功能接口合同,可能会声明:
2 |
public interface FunctionalDefaultMethods { |
5 |
default void defaultMethod() { |
Lambda是Java 8的最大卖点。它有可能吸引越来越多的开发人员加入这个伟大的平台,并为纯Java中的函数式编程概念提供最先进的支持。有关详细信息,请参阅官方文档。
2.2。接口的默认和静态方法
Java 8使用两个新概念扩展了接口声明:default和static方法。默认方法使接口有点类似于特征,但服务有点不同的目标。它们允许向现有接口添加新方法,而不会破坏与为这些接口的旧版本编写的代码的二进制兼容性。
默认方法和抽象方法之间的区别在于需要实现抽象方法。但默认方法不是。相反,每个接口必须提供所谓的默认实现,并且所有实现者都将默认继承它(如果需要,可以覆盖此默认实现)。我们来看看下面的例子。
01 |
private interface Defaulable { |
02 |
// Interfaces now allow default methods, the implementer may or |
03 |
// may not implement (override) them. |
04 |
default String notRequired() { |
05 |
return "Default implementation" ; |
09 |
private static class DefaultableImpl implements Defaulable { |
12 |
private static class OverridableImpl implements Defaulable { |
14 |
public String notRequired() { |
15 |
return "Overridden implementation" ; |
接口Defaulable使用关键字default作为方法定义的一部分声明默认方法notRequired()。其中一个类DefaultableImpl实现了此接口,使默认方法实现保持原样。另一个是OverridableImpl,它会覆盖默认实现并提供自己的实现。
Java 8提供的另一个有趣特性是接口可以声明(并提供实现)静态方法。这是一个例子。
1 |
private interface DefaulableFactory { |
2 |
// Interfaces now allow static methods |
3 |
static Defaulable create( Supplier< Defaulable > supplier ) { |
下面的小代码片段将上面示例中的默认方法和静态方法粘合在一起。
1 |
public static void main( String[] args ) { |
2 |
Defaulable defaulable = DefaulableFactory.create( DefaultableImpl:: new ); |
3 |
System.out.println( defaulable.notRequired() ); |
5 |
defaulable = DefaulableFactory.create( OverridableImpl:: new ); |
6 |
System.out.println( defaulable.notRequired() ); |
该程序的控制台输出如下所示:
2 |
Overridden implementation |
JVM上的默认方法实现非常高效,并且由方法调用的字节代码指令支持。默认方法允许现有Java接口在不破坏编译过程的情况下发展。好的例子是添加到java.util.Collection接口的过多方法:stream(),parallelStream(),forEach(),removeIf(),...
虽然功能强大,但应谨慎使用默认方法:在将方法声明为默认方法之前,如果确实需要它,最好三思而后行,因为它可能会导致复杂层次结构中出现歧义和编译错误。有关详细信息,请参阅官方文档。
2.3。方法参考
方法引用提供了有用的语法,可直接引用Java类或对象(实例)的现有方法或构造函数。通过Lambdas表达式的结合,方法引用使语言结构看起来紧凑而简洁,不会出现样板。
下面,将Car类作为不同方法定义的示例,让我们区分四种支持的方法引用类型。
01 |
public static class Car { |
02 |
public static Car create( final Supplier< Car > supplier ) { |
03 |
return supplier.get(); |
06 |
public static void collide( final Car car ) { |
07 |
System.out.println( "Collided " + car.toString() ); |
10 |
public void follow( final Car another ) { |
11 |
System.out.println( "Following the " + another.toString() ); |
14 |
public void repair() { |
15 |
System.out.println( "Repaired " + this .toString() ); |
第一种类型的方法引用是使用语法Class :: new的构造函数引用,或者对于泛型,Class :: new。请注意,构造函数没有参数。
1 |
final Car car = Car.create( Car:: new ); |
2 |
final List< Car > cars = Arrays.asList( car ); |
第二种类型是使用语法Class :: static_method引用静态方法。请注意,该方法只接受一个Car类型的参数。
1 |
cars.forEach( Car::collide ); |
第三种类型是使用语法Class :: method引用特定类型的任意对象的实例方法。请注意,该方法不接受任何参数。
1 |
cars.forEach( Car::repair ); |
最后,第四种类型是引用特定类实例的语法实例::方法的实例方法。请注意,该方法只接受Car类型的一个参数。
1 |
final Car police = Car.create( Car:: new ); |
2 |
cars.forEach( police::follow ); |
将所有这些示例作为Java程序运行会在控制台上生成以下输出(实际的Car实例可能不同):
1 |
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car @7a81197d
|
2 |
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car @7a81197d
|
3 |
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car @7a81197d |
有关方法参考的更多示例和详细信息,请参阅官方文档。
2.4。重复注释
由于Java 5引入了注释支持,因此该功能变得非常流行并且被广泛使用。但是,注释使用的一个限制是,相同的注释不能在同一位置多次声明。Java 8打破了这个规则并引入了重复注释。它允许相同的注释在声明的位置重复多次。
重复注释本身应该使用@Repeatable注释进行注释。实际上,它不是语言变化,而是更多的编译器技巧,因为技术下面保持不变。我们来看看快速示例:
01 |
package com.javacodegeeks.java8.repeatable.annotations; |
03 |
import java.lang.annotation.ElementType; |
04 |
import java.lang.annotation.Repeatable; |
05 |
import java.lang.annotation.Retention; |
06 |
import java.lang.annotation.RetentionPolicy; |
07 |
import java.lang.annotation.Target; |
09 |
public class RepeatingAnnotations { |
10 |
@Target ( ElementType.TYPE ) |
11 |
@Retention ( RetentionPolicy.RUNTIME ) |
12 |
public @interface Filters { |
16 |
@Target ( ElementType.TYPE ) |
17 |
@Retention ( RetentionPolicy.RUNTIME ) |
18 |
@Repeatable ( Filters. class ) |
19 |
public @interface Filter { |
25 |
public interface Filterable { |
28 |
public static void main(String[] args) { |
29 |
for ( Filter filter: Filterable. class .getAnnotationsByType( Filter. class ) ) { |
30 |
System.out.println( filter.value() ); |
我们可以看到,有一个注释类Filter使用@Repeatable(Filters。类)注释。该过滤器仅仅是一个持有人筛选注释,但Java编译器力图隐藏从开发它的存在。因此,Filterable接口具有两次定义的Filter注释(没有提到过滤器)。
此外,反射API提供了新的方法getAnnotationsByType()返回某种类型的重复注释(请注意筛选。类.getAnnotation(过滤器。类)将返回的实例过滤器由编译器注入)。
程序输出看起来像这样:
有关详细信息,请参阅官方文档。
2.5。更好的类型推断
Java 8编译器在类型推断上有了很大的改进。在许多情况下,显式类型参数可以由编译器推断,使代码更清晰。让我们看看其中一个例子。
01 |
package com.javacodegeeks.java8.type.inference; |
03 |
public class Value< T > { |
04 |
public static < T > T defaultValue() { |
08 |
public T getOrDefault( T value, T defaultValue ) { |
09 |
return ( value != null ) ? value : defaultValue; |
这是Value 类型的用法。
1 |
package com.javacodegeeks.java8.type.inference; |
3 |
public class TypeInference { |
4 |
public static void main(String[] args) { |
5 |
final Value< String > value = new Value<>(); |
6 |
value.getOrDefault( "22" , Value.defaultValue() ); |
Value的类型参数。defaultValue()是推断的,不需要提供。在Java 7中,相同的示例将不会编译,应该重写为Value。 defaultValue()。
2.6。扩展注释支持
Java 8扩展了可能使用注释的上下文。现在,可以对大多数内容进行注释:局部变量,泛型类型,超类和实现接口,甚至是方法的异常声明。几个例子如下所示。
01 |
package com.javacodegeeks.java8.annotations; |
03 |
import java.lang.annotation.ElementType; |
04 |
import java.lang.annotation.Retention; |
05 |
import java.lang.annotation.RetentionPolicy; |
06 |
import java.lang.annotation.Target; |
07 |
import java.util.ArrayList; |
08 |
import java.util.Collection; |
10 |
public class Annotations { |
11 |
@Retention ( RetentionPolicy.RUNTIME ) |
12 |
@Target ( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } ) |
13 |
public @interface NonEmpty { |
16 |
public static class Holder< @NonEmpty T > extends @NonEmpty Object { |
17 |
public void method() throws @NonEmpty Exception { |
21 |
@SuppressWarnings ( "unused" ) |
22 |
public static void main(String[] args) { |
23 |
final Holder< String > holder = new @NonEmpty Holder< String >(); |
24 |
@NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>(); |
该的ElementType。TYPE_USE和ElementType。TYPE_PARAMETER是两种新的元素类型,用于描述适用的注释上下文。该注释处理API也经历了一些小的改动,以识别Java编程语言中那些新型的注解。
3. Java编译器的新功能
3.1 参数名称
从字面上看,Java开发人员正在发明不同的方法来保存Java字节码中的方法参数名称,并使它们在运行时可用(例如,Paranamer库)。最后,Java 8将这一要求强大的功能融入语言(使用Reflection API和Parameter.getName()方法)和字节代码(使用新的javac编译器参数 - 参数)。
01 |
package com.javacodegeeks.java8.parameter.names; |
03 |
import java.lang.reflect.Method; |
04 |
import java.lang.reflect.Parameter; |
06 |
public class ParameterNames { |
07 |
public static void main(String[] args) throws Exception { |
08 |
Method method = ParameterNames. class .getMethod( "main" , String[]. class ); |
09 |
for ( final Parameter parameter: method.getParameters() ) { |
10 |
System.out.println( "Parameter: " + parameter.getName() ); |
如果你在不使用-parameters参数的情况下编译这个类,然后运行这个程序,你会看到类似的东西:
使用-parameters参数传递给编译器时,程序输出将不同(将显示参数的实际名称):
对于有经验的Maven用户,可以使用maven-compiler-plugin的配置部分将-parameters参数添加到编译器中:
02 |
< groupId >org.apache.maven.plugins groupId >< font > font > |
03 |
< artifactId >maven-compiler-plugin artifactId >< font > font > |
04 |
< version >3.1 version >< font > font > |
05 |
< configuration >< font > font > |
06 |
< compilerArgument >-parameters compilerArgument >< font > font > |
07 |
< source >1.8 source >< font > font > |
08 |
< target >1.8 target >< font > font > |
09 |
configuration >< font > font > |
使用Java 8的最新Eclipse Kepler SR2版本(请查看此下载说明)支持提供了有用的配置选项来控制此编译器设置,如下图所示。
图片1。配置Eclipse项目以支持新的Java 8 compiler -parameters参数。
此外,为了验证参数名称的可用性,Parameter类提供了一个方便的方法isNamePresent()。
4. Java库中的新功能
Java 8添加了许多新类并扩展了现有的类,以便为现代并发,函数式编程,日期/时间等提供更好的支持。
4.1 可选的
在著名的NullPointerException是迄今为止Java应用程序故障的最流行的原因。很久以前,伟大的Google Guava项目引入了Optional作为NullPointerException的解决方案,通过空检查阻止代码库污染,并鼓励开发人员编写更清晰的代码。受Google Guava的启发,Optional现在是Java 8库的一部分。
可选只是一个容器:它可以保存某些类型的值T或只是null。它提供了许多有用的方法,因此显式空检查不再有任何借口。有关更多详细信息,请参阅Java 8官方文档。
我们将看一下Optional usages的两个小例子:可空值和不允许null的值。
1 |
Optional< String > fullName = Optional.ofNullable( null );< font > font > |
2 |
System.out.println( "Full Name is set? " + fullName.isPresent() ); < font > font > |
3 |
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); < font > font > |
4 |
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) ); |
该isPresent()方法返回真如果这种情况下可选择具有非空值和虚假,否则。所述orElseGet()方法提供的情况下,回退机制可选具有空通过接受函数生成默认一个值。的地图()方法变换的当前可选的值,并返回新可选实例。的OrElse运算()方法类似于orElseGet() ,但代替函数它接受默认值。这是该程序的输出:
1 |
Full Name is set? false
|
让我们简要介绍另一个例子:
1 |
Optional< String > firstName = Optional.of( "Tom" );< font > font > |
2 |
System.out.println( "First Name is set? " + firstName.isPresent() ); < font > font > |
3 |
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); < font > font > |
4 |
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );< font > font > |
这是输出:
1 |
First Name is set? true
|
有关详细信息,请参阅官方文档。
4.2 流
新添加的Stream API(java.util.stream)将实际的功能样式编程引入Java。这是迄今为止Java库中最全面的补充,旨在通过允许Java开发人员编写有效,干净和简洁的代码来提高Java开发人员的工作效率。
Stream API使集合处理大大简化(但它不仅限于Java集合,我们稍后会看到)。让我们从名为Task的简单类开始。
01 |
public class Streams { |
06 |
private static final class Task { |
07 |
private final Status status; |
08 |
private final Integer points; |
10 |
Task( final Status status, final Integer points ) { |
15 |
public Integer getPoints() { |
19 |
public Status getStatus() { |
24 |
public String toString() { |
25 |
return String.format( "[%s, %d]" , status, points ); |
任务有一些点(或伪复杂度)的概念,可以是OPEN或CLOSED。然后让我们介绍一小部分任务。
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 ) |
我们要解决的第一个问题是所有OPEN任务总共有多少点?在Java 8之前,通常的解决方案是某种foreach迭代。但在Java 8中,答案是流:支持顺序和并行聚合操作的一系列元素。
1 |
// Calculate total points of all active tasks using sum() |
2 |
final long totalPointsOfOpenTasks = tasks |
4 |
.filter( task -> task.getStatus() == Status.OPEN ) |
5 |
.mapToInt( Task::getPoints ) |
8 |
System.out.println( "Total points: " + totalPointsOfOpenTasks ); |
控制台上的输出看起来像这样:
这里有几件事情。首先,将任务集合转换为其流表示。然后,流上的过滤器操作过滤掉所有CLOSED任务。上下一步骤中,mapToInt操作的流并将其转换任务 s到的流整数 S使用任务:: getPoints的每个任务实例的方法。最后,使用求和方法对所有点进行求和,得出最终结果。
在继续下一个示例之前,有一些关于流的注意事项(这里有更多细节)。流操作分为中间操作和终端操作。
中间操作返回一个新流。它们总是很懒,执行像filter这样的中间操作实际上并不执行任何过滤,而是创建一个新的流,当遍历时,它包含与给定谓词匹配的初始流的元素
终端操作(例如forEach或sum)可以遍历流以产生结果或副作用。在执行终端操作之后,流管道被认为已消耗,并且不能再使用。在几乎所有情况下,终端操作都很渴望,完成对底层数据源的遍历。
流的另一个价值主张是并行处理的开箱即用支持。让我们看看这个例子,它总结了所有任务的要点。
1 |
// Calculate total points of all tasks |
2 |
final double totalPoints = tasks |
5 |
.map( task -> task.getPoints() ) // or map( Task::getPoints ) |
6 |
.reduce( 0 , Integer::sum ); |
8 |
System.out.println( "Total points (all tasks): " + totalPoints ); |
它与第一个示例非常相似,只是我们尝试并行处理所有任务并使用reduce方法计算最终结果。
这是控制台输出:
1 |
Total points (all tasks): 26.0 |
通常,需要通过某些标准对集合元素进行分组。Streams可以帮助解决这个问题,以及下面的示例演示。
1 |
// Group tasks by their status |
2 |
final Map< Status, List< Task > > map = tasks |
4 |
.collect( Collectors.groupingBy( Task::getStatus ) ); |
5 |
System.out.println( map ); |
此示例的控制台输出如下所示:
1 |
{CLOSED=[[CLOSED, 8 ]], OPEN=[[OPEN, 5 ], [OPEN, 13 ]]} |
为了完成任务示例,让我们根据点数计算整个集合中每个任务的总体百分比(或权重)。
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 > |
12 |
System.out.println( result ); |
控制台输出就在这里:
最后,正如我们之前提到的,Stream API不仅仅是关于Java集合。逐行读取文本文件等典型的I / O操作是从流处理中受益的非常好的候选者。这是一个确认这一点的小例子。
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 ); |
在流上调用的onClose方法返回一个带有附加关闭处理程序的等效流。在流上调用close()方法时,将运行关闭处理程序。
流API连同lambda表达式和方法引用的出炉接口的默认和静态方法是在软件开发了Java 8应对现代范式。有关更多详细信息,请参阅官方文档。
4.3 日期/时间API(JSR 310)
Java 8通过提供New Date-Time API(JSR 310)进行了更多的日期和时间管理。日期和时间操作是Java开发人员最痛苦的问题之一。标准的java.util.Date后跟java.util.Calendar并没有改善这种情况(可以说,使它更令人困惑)。
That is how Joda-Time was born: the great alternative date/time API for Java. The Java 8’s New Date-Time API (JSR 310)was heavily influenced by Joda-Time and took the best of it. The new java.time package contains all the classes for date, time, date/time, time zones, instants, duration, and clocks manipulation. In the design of the API the immutability has been taken into account very seriously: no change allowed (the tough lesson learnt from java.util.Calendar). If the modification is required, the new instance of respective class will be returned.
Let us take a look on key classes and examples of their usages. The first class is Clock which provides access to the current instant, date and time using a time-zone. Clock can be used instead of System.currentTimeMillis() and 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() ); |
The sample output on a console:
1 |
2014 - 04 -12T15: 19 : 29 .282Z |
我们将要看的其他新类是LocaleDate和LocalTime。LocaleDate仅保留ISO-8601日历系统中没有时区的日期部分。LocaleTime分别仅保留ISO- 8601日历系统中的时间 Zone。无论LocaleDate和LocaleTime可以从创建时钟。
01 |
// Get the local date and local time |
02 |
final LocalDate date = LocalDate.now(); |
03 |
final LocalDate dateFromClock = LocalDate.now( clock ); |
05 |
System.out.println( date ); |
06 |
System.out.println( dateFromClock ); |
08 |
// Get the local date and local time |
09 |
final LocalTime time = LocalTime.now(); |
10 |
final LocalTime timeFromClock = LocalTime.now( clock ); |
12 |
System.out.println( time ); |
13 |
System.out.println( timeFromClock ); |
控制台上的示例输出:
该LocalDateTime结合在一起LocaleDate和本地时间,并拥有一个日期时间,但没有在ISO-8601日历系统的时区。一个简单的例子如下所示。
1 |
// Get the local date/time |
2 |
final LocalDateTime datetime = LocalDateTime.now(); |
3 |
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock ); |
5 |
System.out.println( datetime ); |
6 |
System.out.println( datetimeFromClock ); |
控制台上的示例输出:
1 |
2014 - 04 -12T11: 37 : 52.309
|
2 |
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" ) ); |
6 |
System.out.println( zonedDatetime ); |
7 |
System.out.println( zonedDatetimeFromClock ); |
8 |
System.out.println( zonedDatetimeFromZone ); |
控制台上的示例输出:
1 |
2014 - 04 -12T11: 47 : 01.017 - 04 : 00 [America/New_York] |
2 |
2014 - 04 -12T15: 47 : 01 .017Z |
3 |
2014 - 04 -12T08: 47 : 01.017 - 07 : 00 [America/Los_Angeles] |
最后,让我们看一下持续时间类:以秒和纳秒为单位的时间量。它可以很容易地计算两个日期之间的差异。让我们来看看。
1 |
// Get duration between two dates |
2 |
final LocalDateTime from = LocalDateTime.of( 2014 , Month.APRIL, 16 , 0 , 0 , 0 ); |
3 |
final LocalDateTime to = LocalDateTime.of( 2015 , Month.APRIL, 16 , 23 , 59 , 59 ); |
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日和2015年4月16日两个日期之间的持续时间(以天和小时为单位)。以下是控制台上的示例输出:
2 |
Duration in hours: 8783 |
关于Java 8的新日期/时间API的整体印象是非常非常积极的。部分原因是,由于经过实战证明的基础(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" ); |
4 |
System.out.println( engine.getClass().getName() ); |
5 |
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) ); |
控制台上的示例输出:
1 |
jdk.nashorn.api.scripting.NashornScriptEngine |
稍后我们将在专用于新Java工具的部分中回到Nashorn 。
4.5。Base64编码
最后,Base64编码的支持已经进入Java 8标准库,并发布了Java 8。它非常容易使用,如以下示例所示。
01 |
package com.javacodegeeks.java8.base64; |
03 |
import java.nio.charset.StandardCharsets; |
04 |
import java.util.Base64; |
06 |
public class Base64s { |
07 |
public static void main(String[] args) { |
08 |
final String text = "Base64 finally in Java 8!" ; |
10 |
final String encoded = Base64 |
12 |
.encodeToString( text.getBytes( StandardCharsets.UTF_8 ) ); |
13 |
System.out.println( encoded ); |
15 |
final String decoded = new String( |
16 |
Base64.getDecoder().decode( encoded ), |
17 |
StandardCharsets.UTF_8 ); |
18 |
System.out.println( decoded ); |
程序运行的控制台输出显示编码和解码文本:
1 |
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ== |
2 |
Base64 finally in Java 8 ! |
也有由类的Base64提供URL友好编码器/解码器和MIME型编码器/解码器(Base64的。getUrlEncoder() / Base64的。getUrlDecoder() ,Base64的。getMimeEncoder() / Base64的。getMimeDecoder() )。
4.6。并行数组
Java 8版本添加了许多新方法以允许并行数组处理。可以说,最重要的是parallelSort(),它可以显着加速多核机器上的排序。以下小例子演示了这个新方法系列(parallelXxx)的实际应用。
01 |
package com.javacodegeeks.java8.parallel.arrays; |
03 |
import java.util.Arrays; |
04 |
import java.util.concurrent.ThreadLocalRandom; |
06 |
public class ParallelArrays { |
07 |
public static void main( String[] args ) { |
08 |
long [] arrayOfLong = new long [ 20000 ]; |
10 |
Arrays.parallelSetAll( arrayOfLong, |
11 |
index -> ThreadLocalRandom.current().nextInt( 1000000 ) ); |
12 |
Arrays.stream( arrayOfLong ).limit( 10 ).forEach( |
13 |
i -> System.out.print( i + " " ) ); |
16 |
Arrays.parallelSort( arrayOfLong ); |
17 |
Arrays.stream( arrayOfLong ).limit( 10 ).forEach( |
18 |
i -> System.out.print( i + " " ) ); |
这个小代码片段使用方法parallelSetAll()来填充具有20000个随机值的数组。之后,正在应用parallelSort()。程序在排序之前和之后输出前10个元素,以确保数组真正有序。示例程序输出可能看起来像那样(请注意,数组元素是随机生成的):
1 |
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
|
2 |
Sorted: 39 220 263 268 325 607 655 678 723 793 |
4.7 并发
java.util.concurrent.ConcurrentHashMap类中添加了新方法,以支持基于新添加的流工具和lambda表达式的聚合操作。此外,java.util.concurrent.ForkJoinPool类中添加了新方法以支持公共池(另请参阅我们关于Java并发的免费课程)。
添加了新的java.util.concurrent.locks.StampedLock类,以提供基于功能的锁,其中包含三种控制读/写访问的模式(对于臭名昭着的java.util.concurrent.locks.ReadWriteLock,它可能被认为是更好的选择。)。
java.util.concurrent.atomic包中添加了新类:
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
5.新的Java工具
Java 8附带了一组新的命令行工具。在本节中,我们将查看其中最有趣的内容。
5.1 Nashorn引擎:jjs
jjs是一个基于命令行的独立Nashorn引擎。它接受JavaScript源代码文件列表作为参数并运行它们。例如,让我们创建一个包含以下内容的文件func.js:
要从命令执行此命令,让我们将其作为参数传递给jjs:
控制台上的输出将是:
有关详细信息,请参阅官方文档。
5.2 类依赖性分析器:jdeps
jdeps是一个非常棒的命令行工具。它显示了Java类文件的包级别或类级别依赖性。它接受.class文件,目录或JAR文件作为输入。默认情况下,jdeps将依赖项输出到系统输出(控制台)。
作为示例,让我们看一下流行的Spring Framework库的依赖关系报告。简而言之,让我们只分析一个JAR文件:org.springframework.core-3.0.5.RELEASE.jar。
1 |
jdeps org.springframework.core- 3.0 . 5 .RELEASE.jar |
这个命令输出了很多,所以我们将看看它的一部分。依赖项按包分组。如果类路径上没有依赖项,则显示为未找到。
01 |
org.springframework.core- 3.0 . 5 .RELEASE.jar -> C:\Program Files\Java\jdk1. 8.0 \jre\lib\rt.jar |
02 |
org.springframework.core (org.springframework.core- 3.0 . 5 .RELEASE.jar) |
05 |
-> java.lang.annotation |
09 |
-> java.util.concurrent |
10 |
-> org.apache.commons.logging not found |
11 |
-> org.springframework.asm not found |
12 |
-> org.springframework.asm.commons not found |
13 |
org.springframework.core.annotation (org.springframework.core- 3.0 . 5 .RELEASE.jar) |
15 |
-> java.lang.annotation |
有关详细信息,请参阅官方文档。
6. Java运行时(JVM)中的新功能
所述的PermGen空间不见了,已被替换为元空间(JEP 122)。JVM选项-XX:PermSize和-XX:MaxPermSize已分别替换为-XX:MetaSpaceSize和-XX:MaxMetaspaceSize。
7.结论
未来就在这里:Java 8通过提供功能来提升开发人员的工作效率,从而推动这一伟大的平台向前发展。现在将生产系统转移到Java 8还为时过早,但在接下来的几个月里,它的采用应该慢慢开始增长。尽管如此,现在是时候开始准备你的代码库以便与Java 8兼容,并准备好在Java 8被证明足够安全和稳定时转换开关。
作为社区Java 8接受的确认,最近Pivotal发布了Spring Framework 4.0.3,支持生产就绪Java 8。
如果您喜欢这个,那么订阅我们的时事通讯,享受每周更新和免费白皮书!另外,查看我们的课程以获得更高级的培训!
欢迎您对有关令人兴奋的新Java 8功能的评论做出贡献!
8.资源
一些额外的资源深入讨论了Java 8功能的不同方面:
- 关于JCG的Java 8教程示例:https://examples.javacodegeeks.com/ ? s = java + 8
- JDK 8中的新功能:http://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html
- Java教程: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教程:http://winterbe.com/posts/2014/03/16/java-8-tutorial/
- JDK 8命令行静态依赖项检查程序:http://marxsoftware.blogspot.ca/2014/03/jdeps.html
- JDK 8的Illogating Javadoc:http://marxsoftware.blogspot.ca/2014/03/illuminating-javadoc-of-jdk-8.html
- Java 8的黑暗面:http://blog.jooq.org/2014/04/04/java-8-friday-the-dark-side-of-java-8/
- 在Eclipse Kepler SR2中安装Java™8支持:http://www.eclipse.org/downloads/java8/
- Java 8:http://www.baeldung.com/java8
- Oracle Nashorn。JVM的下一代JavaScript引擎:http://www.oracle.com/technetwork/articles/java/jf14-nashorn-2126515.html