Java8新特性教程 - 终极指南

194 篇文章 3 订阅
14 篇文章 0 订阅

转载来源: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和ConcurrencyJava 8 Date Time API教程:LocalDateTimeAbstract 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虚拟机)本身带来了大量新功能。在本教程中,我们将了解所有这些更改,并演示实际示例中的不同使用方案。

本教程由几个部分组成,每个部分都涉及平台的特定方面:

  • 语言
  • 编译器
  • 图书馆
  • 工具
  • 运行时(JVM)

 

2. Java语言的新功能

Java 8无论如何都是主要版本。有人可能会说,为了实现每个Java开发人员都在寻找的功能,需要花费很长时间才能完成。在本节中,我们将介绍其中的大部分内容。

2.1。Lambdas和功能接口

Lambdas(也称为闭包)是整个Java 8版本中最大和最期待的语言变化。它们允许我们将功能视为方法参数(传递函数),或将代码视为数据:每个功能开发人员都非常熟悉的概念。JVM平台上的许多语言(Groovy,Scala,......)从第一天起就开始使用lambdas,但Java开发人员别无选择,只能使用样板匿名类来攻击lambda。

Lambdas的设计讨论花了很多时间和社区的努力。但最后,已经找到了权衡取舍,从而产生了新的简洁和紧凑的语言结构。在最简单的形式中,lambda可以表示为逗号分隔的参数列表,- >符号和正文。例如:

1Arrays.asList( "a""b""d" ).forEach( e -> System.out.println( e ) );

请注意编译器推断的参数类型e。或者,您可以显式提供参数的类型,将定义包含在括号中。例如:

1Arrays.asList( "a""b""d" ).forEach( ( String e ) -> System.out.println( e ) );

如果lambda的主体更复杂,它可能被包装成方括号,就像Java中常用的函数定义一样。例如:

1Arrays.asList( "a""b""d" ).forEach( e -> {<font></font>
2    System.out.print( e );<font></font>
3    System.out.print( e );<font></font>
4} );<font></font>

Lambdas可以引用类成员和局部变量(如果不是,则隐式地使它们成为最终的)。例如,这两个片段是等效的:

1String separator = ",";<font></font>
2Arrays.asList( "a""b""d" ).forEach( <font></font>
3    ( String e ) -> System.out.print( e + separator ) );<font></font>

和:

1final String separator = ",";<font></font>
2Arrays.asList( "a""b""d" ).forEach( <font></font>
3    ( String e ) -> System.out.print( e + separator ) );<font></font>

Lambdas可能会返回一个值。返回值的类型将由编译器推断。在返回如果拉姆达身体只是一个班轮不需要声明。下面的两个代码段是等效的:

1Arrays.asList( "a""b""d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

和:

1Arrays.asList( "a""b""d" ).sort( ( e1, e2 ) -> {<font></font>
2    int result = e1.compareTo( e2 );<font></font>
3    return result;<font></font>
4} );<font></font>

语言设计者对如何使现有的lambda友好功能进行了大量的思考。结果,出现了功能接口的概念。功能界面是一个只有一个方法的界面。因此,它可以隐式转换为lambda表达式。在一个java.lang.Runnablejava.util.concurrent.Callable是功能接口的两个很好的例子。在实践中,功能接口是脆弱的:如果有人只在接口定义中添加另一个方法,它将不再起作用,编译过程将失败。为了克服这种脆弱性并明确声明接口的意图是正常的,Java 8添加了特殊的注释@FunctionalInterface(Java库中的所有现有接口也都使用@FunctionalInterface注释)。我们来看看这个简单的功能接口定义:

1@FunctionalInterface<font></font>
2public interface Functional {<font></font>
3    void method();<font></font>
4}

要记住一件事:默认和静态方法不会破坏功能接口合同,可能会声明:

1@FunctionalInterface<font></font>
2public interface FunctionalDefaultMethods {<font></font>
3    void method();<font></font>
4        <font></font>
5    default void defaultMethod() {            <font></font>
6    }        <font></font>
7}

Lambda是Java 8的最大卖点。它有可能吸引越来越多的开发人员加入这个伟大的平台,并为纯Java中的函数式编程概念提供最先进的支持。有关详细信息,请参阅官方文档

2.2。接口的默认和静态方法

Java 8使用两个新概念扩展了接口声明:default和static方法。默认方法使接口有点类似于特征,但服务有点不同的目标。它们允许向现有接口添加新方法,而不会破坏与为这些接口的旧版本编写的代码的二进制兼容性。

默认方法和抽象方法之间的区别在于需要实现抽象方法。但默认方法不是。相反,每个接口必须提供所谓的默认实现,并且所有实现者都将默认继承它(如果需要,可以覆盖此默认实现)。我们来看看下面的例子。

01private interface Defaulable {<font></font>
02    // Interfaces now allow default methods, the implementer may or <font></font>
03    // may not implement (override) them.<font></font>
04    default String notRequired() { <font></font>
05        return "Default implementation"; <font></font>
06    }        <font></font>
07}<font></font>
08        <font></font>
09private static class DefaultableImpl implements Defaulable {<font></font>
10}<font></font>
11    <font></font>
12private static class OverridableImpl implements Defaulable {<font></font>
13    @Override<font></font>
14    public String notRequired() {<font></font>
15        return "Overridden implementation";<font></font>
16    }<font></font>
17}

接口Defaulable使用关键字default作为方法定义的一部分声明默认方法notRequired()。其中一个类DefaultableImpl实现了此接口,使默认方法实现保持原样。另一个是OverridableImpl,它会覆盖默认实现并提供自己的实现。

Java 8提供的另一个有趣特性是接口可以声明(并提供实现)静态方法。这是一个例子。

1private interface DefaulableFactory {<font></font>
2    // Interfaces now allow static methods<font></font>
3    static Defaulable create( Supplier< Defaulable > supplier ) {<font></font>
4        return supplier.get();<font></font>
5    }<font></font>
6}

下面的小代码片段将上面示例中的默认方法和静态方法粘合在一起。

1public static void main( String[] args ) {<font></font>
2    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );<font></font>
3    System.out.println( defaulable.notRequired() );<font></font>
4        <font></font>
5    defaulable = DefaulableFactory.create( OverridableImpl::new );<font></font>
6    System.out.println( defaulable.notRequired() );<font></font>
7}

该程序的控制台输出如下所示:

1Default implementation<font></font>
2Overridden implementation

JVM上的默认方法实现非常高效,并且由方法调用的字节代码指令支持。默认方法允许现有Java接口在不破坏编译过程的情况下发展。好的例子是添加到java.util.Collection接口的过多方法:stream()parallelStream()forEach()removeIf(),...

虽然功能强大,但应谨慎使用默认方法:在将方法声明为默认方法之前,如果确实需要它,最好三思而后行,因为它可能会导致复杂层次结构中出现歧义和编译错误。有关详细信息,请参阅官方文档

2.3。方法参考

方法引用提供了有用的语法,可直接引用Java类或对象(实例)的现有方法或构造函数。通过Lambdas表达式的结合,方法引用使语言结构看起来紧凑而简洁,不会出现样板。

下面,将Car类作为不同方法定义的示例,让我们区分四种支持的方法引用类型。

01public static class Car {<font></font>
02    public static Car create( final Supplier< Car > supplier ) {<font></font>
03        return supplier.get();<font></font>
04    }              <font></font>
05        <font></font>
06    public static void collide( final Car car ) {<font></font>
07        System.out.println( "Collided " + car.toString() );<font></font>
08    }<font></font>
09        <font></font>
10    public void follow( final Car another ) {<font></font>
11        System.out.println( "Following the " + another.toString() );<font></font>
12    }<font></font>
13        <font></font>
14    public void repair() {   <font></font>
15        System.out.println( "Repaired " this.toString() );<font></font>
16    }<font></font>
17}

第一种类型的方法引用是使用语法Class :: new的构造函数引用,或者对于泛型,Class <T> :: new。请注意,构造函数没有参数。

1final Car car = Car.create( Car::new );<font></font>
2final List< Car > cars = Arrays.asList( car );

第二种类型是使用语法Class :: static_method引用静态方法。请注意,该方法只接受一个Car类型的参数。

1cars.forEach( Car::collide );

第三种类型是使用语法Class :: method引用特定类型的任意对象的实例方法。请注意,该方法不接受任何参数。

1cars.forEach( Car::repair );

最后,第四种类型是引用特定类实例的语法实例::方法的实例方法。请注意,该方法只接受Car类型的一个参数。

1final Car police = Car.create( Car::new );<font></font>
2cars.forEach( police::follow );

将所有这些示例作为Java程序运行会在控制台上生成以下输出(实际的Car实例可能不同):

1Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d<font></font>
2Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d<font></font>
3Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

有关方法参考的更多示例和详细信息,请参阅官方文档。

2.4。重复注释

由于Java 5引入了注释支持,因此该功能变得非常流行并且被广泛使用。但是,注释使用的一个限制是,相同的注释不能在同一位置多次声明。Java 8打破了这个规则并引入了重复注释。它允许相同的注释在声明的位置重复多次。

重复注释本身应该使用@Repeatable注释进行注释。实际上,它不是语言变化,而是更多的编译器技巧,因为技术下面保持不变。我们来看看快速示例:

01package com.javacodegeeks.java8.repeatable.annotations;<font></font>
02<font></font>
03import java.lang.annotation.ElementType;<font></font>
04import java.lang.annotation.Repeatable;<font></font>
05import java.lang.annotation.Retention;<font></font>
06import java.lang.annotation.RetentionPolicy;<font></font>
07import java.lang.annotation.Target;<font></font>
08<font></font>
09public class RepeatingAnnotations {<font></font>
10    @Target( ElementType.TYPE )<font></font>
11    @Retention( RetentionPolicy.RUNTIME )<font></font>
12    public @interface Filters {<font></font>
13        Filter[] value();<font></font>
14    }<font></font>
15    <font></font>
16    @Target( ElementType.TYPE )<font></font>
17    @Retention( RetentionPolicy.RUNTIME )<font></font>
18    @Repeatable( Filters.class )<font></font>
19    public @interface Filter {<font></font>
20        String value();<font></font>
21    };<font></font>
22    <font></font>
23    @Filter"filter1" )<font></font>
24    @Filter"filter2" )<font></font>
25    public interface Filterable {        <font></font>
26    }<font></font>
27    <font></font>
28    public static void main(String[] args) {<font></font>
29        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {<font></font>
30            System.out.println( filter.value() );<font></font>
31        }<font></font>
32    }<font></font>
33}

我们可以看到,有一个注释类Filter使用@Repeatable(Filters。)注释。该过滤器仅仅是一个持有人筛选注释,但Java编译器力图隐藏从开发它的存在。因此,Filterable接口具有两次定义的Filter注释(没有提到过滤器)。

此外,反射API提供了新的方法getAnnotationsByType()返回某种类型的重复注释(请注意筛选。.getAnnotation(过滤器。)将返回的实例过滤器由编译器注入)。

程序输出看起来像这样:

1filter1<font></font>
2filter2

有关详细信息,请参阅官方文档

2.5。更好的类型推断

Java 8编译器在类型推断上有了很大的改进。在许多情况下,显式类型参数可以由编译器推断,使代码更清晰。让我们看看其中一个例子。

01package com.javacodegeeks.java8.type.inference;<font></font>
02<font></font>
03public class Value< T > {<font></font>
04    public static< T > T defaultValue() { <font></font>
05        return null; <font></font>
06    }<font></font>
07    <font></font>
08    public T getOrDefault( T value, T defaultValue ) {<font></font>
09        return ( value != null ) ? value : defaultValue;<font></font>
10    }<font></font>
11}

这是Value <String>类型的用法。

1package com.javacodegeeks.java8.type.inference;<font></font>
2<font></font>
3public class TypeInference {<font></font>
4    public static void main(String[] args) {<font></font>
5        final Value< String > value = new Value<>();<font></font>
6        value.getOrDefault( "22", Value.defaultValue() );<font></font>
7    }<font></font>
8}

Value的类型参数defaultValue()是推断的,不需要提供。在Java 7中,相同的示例将不会编译,应该重写为Value。<String> defaultValue()

2.6。扩展注释支持

Java 8扩展了可能使用注释的上下文。现在,可以对大多数内容进行注释:局部变量,泛型类型,超类和实现接口,甚至是方法的异常声明。几个例子如下所示。

01package com.javacodegeeks.java8.annotations;<font></font>
02<font></font>
03import java.lang.annotation.ElementType;<font></font>
04import java.lang.annotation.Retention;<font></font>
05import java.lang.annotation.RetentionPolicy;<font></font>
06import java.lang.annotation.Target;<font></font>
07import java.util.ArrayList;<font></font>
08import java.util.Collection;<font></font>
09<font></font>
10public class Annotations {<font></font>
11    @Retention( RetentionPolicy.RUNTIME )<font></font>
12    @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )<font></font>
13    public @interface NonEmpty {        <font></font>
14    }<font></font>
15        <font></font>
16    public static class Holder< @NonEmpty T > extends @NonEmpty Object {<font></font>
17        public void method() throws @NonEmpty Exception {           <font></font>
18        }<font></font>
19    }<font></font>
20        <font></font>
21    @SuppressWarnings"unused" )<font></font>
22    public static void main(String[] args) {<font></font>
23        final Holder< String > holder = new @NonEmpty Holder< String >();       <font></font>
24        @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();       <font></font>
25    }<font></font>
26}

的ElementType。TYPE_USEElementType。TYPE_PARAMETER是两种新的元素类型,用于描述适用的注释上下文。该注释处理API也经历了一些小的改动,以识别Java编程语言中那些新型的注解。

 

3. Java编译器的新功能

3.1 参数名称

从字面上看,Java开发人员正在发明不同的方法来保存Java字节码中的方法参数名称,并使它们在运行时可用(例如,Paranamer库)。最后,Java 8将这一要求强大的功能融入语言(使用Reflection API和Parameter.getName()方法)和字节代码(使用新的javac编译器参数 - 参数)。

01package com.javacodegeeks.java8.parameter.names;<font></font>
02<font></font>
03import java.lang.reflect.Method;<font></font>
04import java.lang.reflect.Parameter;<font></font>
05<font></font>
06public class ParameterNames {<font></font>
07    public static void main(String[] args) throws Exception {<font></font>
08        Method method = ParameterNames.class.getMethod( "main", String[].class );<font></font>
09        forfinal Parameter parameter: method.getParameters() ) {<font></font>
10            System.out.println( "Parameter: " + parameter.getName() );<font></font>
11        }<font></font>
12    }<font></font>
13}

如果你在不使用-parameters参数的情况下编译这个类,然后运行这个程序,你会看到类似的东西:

1Parameter: arg0

使用-parameters参数传递给编译器时,程序输出将不同(将显示参数的实际名称):

1Parameter: args

对于有经验的Maven用户,可以使用maven-compiler-plugin的配置部分将-parameters参数添加到编译器中

01<plugin><font></font>
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>
10</plugin>

使用Java 8的最新Eclipse Kepler SR2版本(请查看此下载说明)支持提供了有用的配置选项来控制此编译器设置,如下图所示。

图1.配置Eclipse项目以支持新的Java 8 compiler -parameters参数。

图片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的值。

1Optional< String > fullName = Optional.ofNullable( null );<font></font>
2System.out.println( "Full Name is set? " + fullName.isPresent() );        <font></font>
3System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); <font></font>
4System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

isPresent()方法返回如果这种情况下可选择具有非空值和虚假,否则。所述orElseGet()方法提供的情况下,回退机制可选具有通过接受函数生成默认一个值。的地图()方法变换的当前可选的值,并返回新可选实例。的OrElse运算()方法类似于orElseGet() ,但代替函数它接受默认值。这是该程序的输出:

1Full Name is set? false<font></font>
2Full Name: [none]<font></font>
3Hey Stranger!

让我们简要介绍另一个例子:

1Optional< String > firstName = Optional.of( "Tom" );<font></font>
2System.out.println( "First Name is set? " + firstName.isPresent() );        <font></font>
3System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); <font></font>
4System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );<font></font>
5System.out.println();

这是输出:

1First Name is set? true<font></font>
2First Name: Tom<font></font>
3Hey Tom!

有关详细信息,请参阅官方文档。

4.2 流

添加的Stream APIjava.util.stream)将实际的功能样式编程引入Java。这是迄今为止Java库中最全面的补充,旨在通过允许Java开发人员编写有效,干净和简洁的代码来提高Java开发人员的工作效率。

Stream API使集合处理大大简化(但它不仅限于Java集合,我们稍后会看到)。让我们从名为Task的简单类开始。

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

任务有一些点(或伪复杂度)的概念,可以是OPENCLOSED。然后让我们介绍一小部分任务。

1final Collection< Task > tasks = Arrays.asList(<font></font>
2    new Task( Status.OPEN, 5 ),<font></font>
3    new Task( Status.OPEN, 13 ),<font></font>
4    new Task( Status.CLOSED, 8 ) <font></font>
5);

我们要解决的第一个问题是所有OPEN任务总共有多少点?在Java 8之前,通常的解决方案是某种foreach迭代。但在Java 8中,答案是流:支持顺序和并行聚合操作的一系列元素。

1// Calculate total points of all active tasks using sum()<font></font>
2final long totalPointsOfOpenTasks = tasks<font></font>
3    .stream()<font></font>
4    .filter( task -> task.getStatus() == Status.OPEN )<font></font>
5    .mapToInt( Task::getPoints )<font></font>
6    .sum();<font></font>
7        <font></font>
8System.out.println( "Total points: " + totalPointsOfOpenTasks );

控制台上的输出看起来像这样:

1Total points: 18

这里有几件事情。首先,将任务集合转换为其流表示。然后,流上的过滤操作过滤掉所有CLOSED任务。上下一步骤中,mapToInt操作的流并将其转换任务 s到的流整数 S使用任务:: getPoints的每个任务实例的方法。最后,使用求和方法对所有点进行求和,得出最终结果。

在继续下一个示例之前,有一些关于流的注意事项(这里有更多细节)。流操作分为中间操作和终端操作。

中间操作返回一个新流。它们总是很懒,执行像filter这样的中间操作实际上并不执行任何过滤,而是创建一个新的流,当遍历时,它包含与给定谓词匹配的初始流的元素

终端操作(例如forEachsum)可以遍历流以产生结果或副作用。在执行终端操作之后,流管道被认为已消耗,并且不能再使用。在几乎所有情况下,终端操作都很渴望,完成对底层数据源的遍历。

流的另一个价值主张是并行处理的开箱即用支持。让我们看看这个例子,它总结了所有任务的要点。

1// Calculate total points of all tasks<font></font>
2final double totalPoints = tasks<font></font>
3   .stream()<font></font>
4   .parallel()<font></font>
5   .map( task -> task.getPoints() ) // or map( Task::getPoints ) <font></font>
6   .reduce( 0, Integer::sum );<font></font>
7    <font></font>
8System.out.println( "Total points (all tasks): " + totalPoints );

它与第一个示例非常相似,只是我们尝试并行处理所有任务并使用reduce方法计算最终结果。

这是控制台输出:

1Total points (all tasks): 26.0

通常,需要通过某些标准对集合元素进行分组。Streams可以帮助解决这个问题,以及下面的示例演示。

1// Group tasks by their status<font></font>
2final Map< Status, List< Task > > map = tasks<font></font>
3    .stream()<font></font>
4    .collect( Collectors.groupingBy( Task::getStatus ) );<font></font>
5System.out.println( map );

此示例的控制台输出如下所示:

1{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}

为了完成任务示例,让我们根据点数计算整个集合中每个任务的总体百分比(或权重)。

01// Calculate the weight of each tasks (as percent of total points) <font></font>
02final Collection< String > result = tasks<font></font>
03    .stream()                                        // Stream< String ><font></font>
04    .mapToInt( Task::getPoints )                     // IntStream<font></font>
05    .asLongStream()                                  // LongStream<font></font>
06    .mapToDouble( points -> points / totalPoints )   // DoubleStream<font></font>
07    .boxed()                                         // Stream< Double ><font></font>
08    .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream<font></font>
09    .mapToObj( percentage -> percentage + "%" )      // Stream< String> <font></font>
10    .collect( Collectors.toList() );                 // List< String > <font></font>
11        <font></font>
12System.out.println( result );

控制台输出就在这里:

1[19%, 50%, 30%]

最后,正如我们之前提到的,Stream API不仅仅是关于Java集合。逐行读取文本文件等典型的I / O操作是从流处理中受益的非常好的候选者。这是一个确认这一点的小例子。

1final Path path = new File( filename ).toPath();<font></font>
2try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {<font></font>
3    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );<font></font>
4}

在流上调用的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 <font></font>
2final Clock clock = Clock.systemUTC();<font></font>
3System.out.println( clock.instant() );<font></font>
4System.out.println( clock.millis() );

The sample output on a console:

12014-04-12T15:19:29.282Z<font></font>
21397315969360

我们将要看的其他新类是LocaleDateLocalTimeLocaleDate仅保留ISO-8601日历系统中没有时区的日期部分。LocaleTime分别保留ISO- 8601日历系统中的时间 Zone。无论LocaleDateLocaleTime可以从创建时钟

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

控制台上的示例输出:

12014-04-12<font></font>
22014-04-12<font></font>
311:25:54.568<font></font>
415:25:54.568

LocalDateTime结合在一起LocaleDate本地时间,并拥有一个日期时间,但没有在ISO-8601日历系统的时区。一个简单的例子如下所示。

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

控制台上的示例输出:

12014-04-12T11:37:52.309<font></font>
22014-04-12T15:37:52.309

如果您需要特定时区的日期/时间,ZonedDateTime可以提供帮助。它在ISO-8601日历系统中保存带有时间和时区的日期。以下是不同时区的几个示例。

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

控制台上的示例输出:

12014-04-12T11:47:01.017-04:00[America/New_York]<font></font>
22014-04-12T15:47:01.017Z<font></font>
32014-04-12T08:47:01.017-07:00[America/Los_Angeles]

最后,让我们看一下持续时间类:以秒和纳秒为单位的时间量。它可以很容易地计算两个日期之间的差异。让我们来看看。

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

上面的示例计算2014 年4月16日2015年4月16日两个日期之间的持续时间(以天和小时为单位)。以下是控制台上的示例输出:

1Duration in days: 365<font></font>
2Duration 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互操作性。这是一个小例子。

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

控制台上的示例输出:

1jdk.nashorn.api.scripting.NashornScriptEngine<font></font>
2Result: 2

稍后我们将在专用于新Java工具的部分中回到Nashorn 。

4.5。Base64编码

最后,Base64编码支持已经进入Java 8标准库,并发布了Java 8。它非常容易使用,如以下示例所示。

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

程序运行的控制台输出显示编码和解码文本:

1QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==<font></font>
2Base64 finally in Java 8!

也有由类的Base64提供URL友好编码器/解码器和MIME型编码器/解码器(Base64的。getUrlEncoder() / Base64的。getUrlDecoder() Base64的。getMimeEncoder() / Base64的。getMimeDecoder() )。

4.6。并行数组

Java 8版本添加了许多新方法以允许并行数组处理。可以说,最重要的是parallelSort(),它可以显着加速多核机器上的排序。以下小例子演示了这个新方法系列(parallelXxx)的实际应用。

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

这个小代码片段使用方法parallelSetAll()来填充具有20000个随机值的数组。之后,正在应用parallelSort()。程序在排序之前和之后输出前10个元素,以确保数组真正有序。示例程序输出可能看起来像那样(请注意,数组元素是随机生成的):

1Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378<font></font>
2Sorted: 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

1function f() { <font></font>
2     return 1; <font></font>
3}; <font></font>
4<font></font>
5print( f() + 1 );

要从命令执行此命令,让我们将其作为参数传递给jjs

1jjs func.js

控制台上的输出将是:

12

有关详细信息,请参阅官方文档

5.2 类依赖性分析器:jdeps

jdeps是一个非常棒的命令行工具。它显示了Java类文件的包级别或类级别依赖性。它接受.class文件,目录JAR文件作为输入。默认情况下,jdeps将依赖输出到系统输出(控制台)。

作为示例,让我们看一下流行的Spring Framework库的依赖关系报告。简而言之,让我们只分析一个JAR文件:org.springframework.core-3.0.5.RELEASE.jar

1jdeps org.springframework.core-3.0.5.RELEASE.jar

这个命令输出了很多,所以我们将看看它的一部分。依赖项按包分组。如果类路径上没有依赖项,则显示为未找到

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

有关详细信息,请参阅官方文档

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功能的不同方面:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值