Java 8功能教程– ULTIMATE指南(PDF下载)

编者注:在本文中,我们提供了全面的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.简介

毫无疑问, 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.Runnablejava.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_USEElementType。 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版本(请查看此下载说明 )支持提供了有用的配置选项来控制此编译器设置,如下图所示。

Java 8-配置Eclipse项目
图1.配置Eclipse项目以支持新的Java 8编译器–parameters参数

另外,为了验证参数名称的可用性, 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 APIjava.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具有一些点(或伪复杂性)概念,可以为OPENCLOSED 。 然后,让我们介绍一些要处理的任务。

 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之类的中间操作实际上并不执行任何过滤,而是创建一个新的流,该新流在遍历时将包含与给定谓词匹配的初始流的元素。

终端操作(例如forEachsum )可能会遍历流以产生结果或副作用。 执行终端操作后,流管道被视为已消耗,无法再使用。 在几乎所有情况下,终端操作人员都很渴望完成对基础数据源的遍历。

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

 // 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 

我们将要查看的其他新类是LocaleDateLocalTime 。 在ISO- 8601日历系统中,LocaleDate仅保存日期部分,没有时区。 分别地, LocaleTime在ISO- 8601日历系统中仅保存没有时区的时间部分LocaleDateLocaleTime都可以从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 

LocalDateTimeLocaleDateLocalTime结合在一起,并在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 Features Tutorial was last updated on Oct. 3, 2016

翻译自: https://www.javacodegeeks.com/java-8-features-tutorial.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值