毫无疑问,Java 8发行版是自Java 5(发行于2004,已通过了至关一段时间了)以来最具革命性的版本。Java 8 为Java语言、编译器、类库、开发工具与JVM(Java虚拟机)带来了大量新特性。在这篇文章中,咱们将一一探索这些变化,并用真实的例子说明它们适用的场景。主要由如下几部分组成,它们分别涉及到Java平台某一特定方面的内容:javascript
Lambda表达式(也称为闭包)是整个Java 8发行版中最受期待的在Java语言层面上的改变,Lambda容许把函数做为一个方法的参数(函数做为参数传递进方法中),或者把代码当作数据:函数式程序员对这一律念很是熟悉。在JVM平台上的不少语言(Groovy,Scala,……)从一开始就有Lambda,可是Java程序员不得不使用毫无新意的匿名类来代替lambda。html
关于Lambda设计的讨论占用了大量的时间与社区的努力。可喜的是,最终找到了一个平衡点,使得可使用一种即简洁又紧凑的新方式来构造Lambdas。java
在最简单的形式中,一个lambda能够由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如:git
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中定义普通函数同样。例如:github
Arrays.asList( "a", "b", "d" ).forEach( e -> {
System.out.print( e );
} );
Lambda能够引用类的成员变量与局部变量(若是这些变量不是final的话,它们会被隐含的转为final,这样效率更高)。例如,下面两个代码片断是等价的:spring
String separator = ","; Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.print( e + separator ) );
和:shell
final String separator = ","; Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.print( e + separator ) );
Lambda可能会返回一个值。返回值的类型也是由编译器推测出来的。若是lambda的函数体只有一行的话,那么没有必要显式使用return语句。下面两个代码片断是等价的:express
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
和:apache
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 8中全部类库的已有接口都添加了@FunctionalInterface注解)。让咱们看一下这种函数式接口的定义:
@FunctionalInterface public interface Functional { void method(); }
须要记住的一件事是:默认方法与静态方法并不影响函数式接口的契约,能够任意使用:
@FunctionalInterface public interface FunctionalDefaultMethods { void method(); default void defaultMethod() { } }
Lambda是Java 8最大的卖点。它具备吸引愈来愈多程序员到Java平台上的潜力,而且可以在纯Java语言环境中提供一种优雅的方式来支持函数式编程。更多详情能够参考官方文档。
Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。默认方法使得接口有点相似traits,不过要实现的目标不同。默认方法使得开发者能够在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。
默认方法和抽象方法之间的区别在于抽象方法须要实现,而默认方法不须要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码以下:
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.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。
尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引发歧义和编译错误。若是你想了解更多细节,能够参考官方文档。
方法引用使得开发者能够直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有不少复杂的模板代码。
看下面Car类是不一样方法引用的例子,能够帮助读者区分四种类型的方法引用。
public static 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 );
运行上述例子,能够在控制台看到以下输出(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() ); } } }
--------------
输出结果:
filter1
filter2
正如咱们看到的,这里有个使用@Repeatable( Filters.class )注解的注解类Filter,Filters仅仅是Filter注解的数组,但Java编译器并不想让程序员意识到Filters的存在。这样,接口Filterable就拥有了两次Filter(并无提到Filter)注解。
同时,反射相关的API提供了新的函数getAnnotationsByType()来返回重复注解的类型(请注意Filterable.class.getAnnotation( Filters.class )经编译器处理后将会返回Filters的实例)。更多详情请参考官方文档
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< 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是两个新添加的用于描述适当的注解上下文的元素类型。在Java语言中,注解处理API也有小的改动来识别新增的类型注解。
为了在运行时得到Java程序中方法的参数名称,老一辈的Java程序员必须使用不一样方法,例如Paranamer liberary。Java 8终于将这个特性规范化,在语言层面(使用反射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() ); } } }
在Java 8中这个特性是默认关闭的,所以若是不带-parameters参数编译上述代码并运行,则会输出以下结果:
Parameter: arg0
若是带-parameters参数,则会输出以下结果(正确的结果):
Parameter: args
若是你使用Maven进行项目管理,则能够在maven-compiler-plugin编译器的配置项中配置-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 经过增长大量新类,扩展已有类的功能的方式来改善对并发编程、函数式编程、日期/时间相关操做以及其余更多方面的支持。
到目前为止,臭名昭著的空指针异常是致使Java应用程序失败的最多见缘由。之前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava经过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
更多详情请参考官方文档。
咱们下面用两个小例子来演示如何使用Optional类:一个容许为空值,一个不容许为空值。
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!" ) );
-----
输出结果:
Full Name is set? false
Full Name: [none]
Hey Stranger!
若是Optional类的实例为非空值的话,isPresent()返回true,否从返回false。为了防止Optional为空值,orElseGet()方法经过回调函数来产生一个默认值。map()函数对当前Optional的值进行转化,而后返回一个新的Optional实例。orElse()方法和orElseGet()方法相似,可是orElse接受一个默认值而不是一个回调函数。
让咱们来看看另外一个例子: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类库最好的补充,由于Stream API能够极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
Stream API极大简化了集合框架的处理(但它的处理的范围不只仅限于集合框架的处理,这点后面咱们会看到)。让咱们以一个简单的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的状态.让咱们引入一个Task的小集合做为演示例子:
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里面咱们可使用stream:一串支持连续、并行汇集操做的元素。
// 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
这里有几个注意事项。第一,task集合被转换化为其相应的stream表示。而后,filter操做过滤掉状态为CLOSED的task。下一步,mapToInt操做经过Task::getPoints这种方式调用每一个task实例的getPoints方法把Task的stream转化为Integer的stream。最后,用sum函数把全部的分数加起来,获得最终的结果。
在继续讲解下面的例子以前,关于stream有一些须要注意的地方(详情在这里).stream操做被分红了中间操做与最终操做这两种。
中间操做返回一个新的stream对象。中间操做老是采用惰性求值方式,运行一个像filter这样的中间操做实际上没有进行任何过滤,相反它在遍历元素时会产生了一个新的stream对象,这个新的stream对象包含原始stream
中符合给定谓词的全部元素。
像forEach、sum这样的最终操做可能直接遍历stream,产生一个结果或反作用。当最终操做执行结束以后,stream管道被认为已经被消耗了,没有可能再被使用了。在大多数状况下,最终操做都是采用及早求值方式,及早完成底层数据源的遍历。
stream另外一个有价值的地方是可以原生支持并行处理。让咱们来看看这个算task分数和的例子。
// 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 );
---------
输出结果:
Total points (all tasks): 26.0
这个例子和第一个例子很类似,但这个例子的不一样之处在于这个程序是并行运行的,其次使用reduce方法来算最终的结果。
常常会有这个一个需求:咱们须要按照某种准则来对集合中的元素进行分组。Stream也能够处理这样的需求,下面是一个例子:
// 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]]}
让咱们来计算整个集合中每一个task分数(或权重)的平均值来结束task的例子。
// 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 * 100 ) ) // LongStream .mapToObj( percentage -> percentage + "%" ) // Stream< String> .collect( Collectors.toList() ); // List< String > System.out.println( result );
--------
输出结果;
[19%, 50%, 30%]
最后,就像前面提到的,Stream API不只仅处理Java集合框架。像从文本文件中逐行读取数据这样典型的I/O操做也很适合用Stream API来处理。下面用一个例子来应证这一点。
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 ); }
对一个stream对象调用onClose方法会返回一个在原有功能基础上新增了关闭功能的stream对象,当对stream对象调用close()方法时,与关闭相关的处理器就会执行。
Stream API、Lambda表达式与方法引用在接口默认方法与静态方法的配合下是Java 8对现代软件开发范式的回应。更多详情请参考官方文档。
Java 8经过发布新的Date-Time API (JSR 310)来进一步增强对日期与时间的处理。对日期与时间的操做一直是Java程序员最痛苦的地方之一,标准的 java.util.Date以及后来的java.util.Calendar一点没有改善这种状况(能够这么说,它们必定程度上更加复杂),
这种状况直接致使了Joda-Time——一个可替换标准日期/时间处理且功能很是强大的Java API的诞生。Java 8新的Date-Time API (JSR 310)在很大程度上受到Joda-Time的影响,而且吸收了其精髓。新的java.time包涵盖了全部处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操做。在设计新版API时,十分注重与旧版API的兼容性:不容许有任何的改变(从java.util.Calendar中获得的深入教训)。若是须要修改,会返回这个类的一个新实例。
让咱们用例子来看一下新版API主要类的使用方法。第一个是Clock类,它经过指定一个时区,而后就能够获取到当前的时刻,日期与时间。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() ); ------- 输出结果: 2018-09-25T10:06:53.963Z 1537870014196
咱们须要关注的其余类是LocaleDate与LocalTime。LocaleDate只持有ISO-8601格式且无时区信息的日期部分。相应的,LocaleTime只持有ISO-8601格式且无时区信息的时间部分。LocaleDate与LocalTime均可以从Clock中获得。
// Get the local date and local time final Clock clock = Clock.systemUTC(); 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 ); --------- 输出结果: 2018-09-25 2018-09-25 18:08:32.543 10:08:32.543
LocaleDateTime把LocaleDate与LocaleTime的功能合并起来,它持有的是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 ); -------- 输出结果: 2018-09-25T18:12:09.071 2018-09-25T10:12:09.071
若是你须要特定时区的日期/时间,那么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 ); -------- 输出结果: 2018-09-25T18:14:15.541+08:00[Asia/Shanghai] 2018-09-25T10:14:15.541Z 2018-09-25T03:14:15.545-07:00[America/Los_Angeles]
最后,让咱们看一下Duration类:在秒与纳秒级别上的一段时间。Duration使计算两个日期间的不一样变的十分简单。下面让咱们看一个这方面的例子。
final LocalDateTime from = LocalDateTime.of( 2017, Month.September, 25, 0, 0, 0 ); final LocalDateTime to = LocalDateTime.of( 2018, Month.September, 25, 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() ); ----------- 输出结果: Duration in days: 365 Duration in hours: 8783
上面的例子计算了两个日期2017年9月25号与2018年9月25号之间的过程。
对Java 8在日期/时间API的改进总体印象是很是很是好的。一部分缘由是由于它创建在“久战杀场”的Joda-Time基础上,另外一方面是由于用来大量的时间来设计它,而且此次程序员的声音获得了承认。更多详情请参考官方文档。
Nashorn,一个新的JavaScript引擎随着Java 8一块儿公诸于世,它容许在JVM上开发运行某些JavaScript应用。Nashorn就是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 8中,Base64编码已经成为Java类库的标准。它的使用十分简单,下面让咱们看一个例子:
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类同时还提供了对URL、MIME友好的编码器与解码器(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。
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 [ 5000 ]; Arrays.parallelSetAll( arrayOfLong, index -> ThreadLocalRandom.current().nextInt( 500000 ) ); 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(); } } ----------------- 输出结果: 43246 429105 479150 498970 478123 404198 273038 114468 488657 438767 111 347 552 581 731 852 1118 1252 1545 1735
上面的代码片断使用了parallelSetAll()方法来对一个有5000个元素的数组进行随机赋值。而后,调用parallelSort方法。这个程序首先打印出前10个元素的值,以后对整个数组排序。这个程序在控制台上的输出以下(请注意数组元素是随机生产的):
在新增Stream机制与lambda的基础之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法来支持汇集操做。同时也在java.util.concurrent.ForkJoinPool类中加入了一些新方法来支持共有资源池(common pool)。
新增的java.util.concurrent.locks.StampedLock类提供一直基于容量的锁,这种锁有三个模型来控制读写操做(它被认为是不太有名的java.util.concurrent.locks.ReadWriteLock类的替代者)。
在java.util.concurrent.atomic包中还增长了下面这些类:
更多详情请参考官方文档。
Java 8也带来了一些新的命令行工具。在这节里咱们将会介绍它们中最有趣的部分。
jjs是个基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,而且执行这些源代码。例如,咱们建立一个具备以下内容的func.js文件:
function f() { return 0 ; }; print( f() + 1 );
------
命令行中执行:jjs func.js
------
输出结果:1
使用 ScriptEngineManager, JavaScript 代码能够在 Java 中执行,实例以下:
import javax.script.ScriptEngineManager; import javax.script.ScriptEngine; import javax.script.ScriptException; public class Java8Tester { public static void main(String args[]){ ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn"); String name = "JackpotHan"; Integer result = null; try { nashorn.eval("print('" + name + "')"); result = (Integer) nashorn.eval("10 + 2"); }catch(ScriptException e){ System.out.println("执行脚本错误: "+ e.getMessage()); } System.out.println(result.toString()); } } ----------- 输出结果: $ javac Java8Tester.java $ java Java8Tester JackpotHan 12
如下实例演示了如何在 JavaScript 中引用 Java 类,建立test.js文件:
var BigDecimal = Java.type('java.math.BigDecimal'); function calculate(amount, percentage) { var result = new BigDecimal(amount).multiply( new BigDecimal(percentage)).divide(new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_EVEN); return result.toPlainString(); } var result = calculate(1537933632887,13.14); print(result);
---------
控制台执行:$ jjs test.js
输出结果:202084479361.35
更多详情请参考官方文档
jdeps是一个颇有用的命令行工具。它能够显示Java类的包级别或类级别的依赖。它接受一个.class文件,一个目录,或者一个jar文件做为输入。jdeps默认把结果输出到系统输出(控制台)上。
下面咱们查看现阶段较流行的Spring框架类库的依赖报告,为了简化这个例子,咱们只分析一个jar文件:org.springframework.core-3.0.5.RELEASE.jar
jdeps org.springframework.core-3.0.5.RELEASE.jar
这个命令输出的内容不少,因此这里咱们只选取一小部分。依赖信息按照包名进行分组。若是依赖不在classpath中,那么就会显示not found。
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
更多详情请参考官方文档
PermGen空间被移除了,取而代之的是Metaspace(JEP 122)。JVM选项-XX:PermSize与-XX:MaxPermSize分别被-XX:MetaSpaceSize与-XX:MaxMetaspaceSize所代替。
JDK8 HotSpot JVM 将移除永久区,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace),这与Oracle JRockit 和IBM JVM’s很类似,以下图所示
这意味着不会再有java.lang.OutOfMemoryError: PermGen问题,也再也不须要你进行调优及监控内存空间的使用……但请等等,这么说还为时过早。在默认状况下,这些改变是透明的,接下来咱们的展现将使你知道仍然要关注类元数据内存的占用。请必定要牢记,这个新特性也不能神奇地消除类和类加载器致使的内存泄漏。
java8中metaspace总结以下:
PermGen 空间的情况
这部份内存空间将所有移除。
JVM的参数:PermSize 和 MaxPermSize 会被忽略并给出警告(若是在启用时设置了这两个参数)。
Metaspace 内存分配模型
大部分类元数据都在本地内存中分配。
用于描述类元数据的“klasses”已经被移除。
Metaspace 容量
默认状况下,类元数据只受可用的本地内存限制(容量取决因而32位或是64位操做系统的可用虚拟内存大小)。
新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小。若是没有指定这个参数,元空间会在运行时根据须要动态调整。
Metaspace 垃圾回收
对于僵死的类及类加载器的垃圾回收将在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。
适时地监控和调整元空间对于减少垃圾回收频率和减小延时是颇有必要的。持续的元空间垃圾回收说明,可能存在类、类加载器致使的内存泄漏或是大小设置不合适。
Java 堆内存的影响
一些杂项数据已经移到Java堆空间中。升级到JDK8以后,会发现Java堆 空间有所增加。
Metaspace 监控
元空间的使用状况能够从HotSpot1.8的详细GC日志输出中获得。
Jstat 和 JVisualVM两个工具,在使用b75版本进行测试时,已经更新了,可是仍是能看到老的PermGen空间的出现。
前面已经从理论上充分说明,下面让咱们经过“泄漏”程序进行新内存空间的观察……
PermGen vs. Metaspace 运行时比较
为了更好地理解Metaspace内存空间的运行时行为,
将进行如下几种场景的测试:
首先创建了一个模拟PermGen OOM的代码
public class ClassA { public void method(String name) { // do nothing } }
上面是一个简单的ClassA,把他编译成class字节码放到D:/classes下面,测试代码中用URLClassLoader来加载此类型上面类编译成class
/** * 模拟PermGen OOM * @author benhail */ public class OOMTest { public static void main(String[] args) { try { //准备url URL url = new File("D:/classes").toURI().toURL(); URL[] urls = {url}; //获取有关类型加载的JMX接口 ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean(); //用于缓存类加载器 List<ClassLoader> classLoaders = new ArrayList<ClassLoader>(); while (true) { //加载类型并缓存类加载器实例 ClassLoader classLoader = new URLClassLoader(urls); classLoaders.add(classLoader); classLoader.loadClass("ClassA"); //显示数量信息(共加载过的类型数目,当前还有效的类型数目,已经被卸载的类型数目) System.out.println("total: " + loadingBean.getTotalLoadedClassCount()); System.out.println("active: " + loadingBean.getLoadedClassCount()); System.out.println("unloaded: " + loadingBean.getUnloadedClassCount()); } } catch (Exception e) { e.printStackTrace(); } } }
虚拟机器参数设置以下:-verbose -verbose:gc
设置-verbose参数是为了获取类型加载和卸载的信息
设置-verbose:gc是为了获取垃圾收集的相关信息
JDK 1.7 @64-bit – PermGen 耗尽测试
Java1.7的PermGen默认空间为85 MB(或者能够经过-XX:MaxPermSize=XXXm指定)
能够从上面的JVisualVM的截图看出:当加载超过6万个类以后,PermGen被耗尽。咱们也能经过程序和GC的输出观察耗尽的过程。
程序输出(摘取了部分)
...... [Loaded ClassA from file:/D:/classes/] total: 64887 active: 64887 unloaded: 0 [GC 245041K->213978K(536768K), 0.0597188 secs] [Full GC 213978K->211425K(644992K), 0.6456638 secs] [GC 211425K->211425K(656448K), 0.0086696 secs] [Full GC 211425K->211411K(731008K), 0.6924754 secs] [GC 211411K->211411K(726528K), 0.0088992 secs] ............... java.lang.OutOfMemoryError: PermGen space
JDK 1.8 @64-bit – Metaspace大小动态调整测试
Java的Metaspace空间:不受限制 (默认)
从上面的截图能够看到,JVM Metaspace进行了动态扩展,本地内存的使用由20MB增加到646MB,以知足程序中不断增加的类数据内存占用需求。咱们也能观察到JVM的垃圾回收事件—试图销毁僵死的类或类加载器对象。可是,因为咱们程序的泄漏,JVM别无选择只能动态扩展Metaspace内存空间。程序加载超过10万个类,而没有出现OOM事件。
JDK 1.8 @64-bit – Metaspace 受限测试
Java的Metaspace空间:128MB(-XX:MaxMetaspaceSize=128m)
能够从上面的JVisualVM的截图看出:当加载超过2万个类以后,Metaspace被耗尽;与JDK1.7运行时很是类似。咱们也能经过程序和GC的输出观察耗尽的过程。另外一个有趣的现象是,保留的原生内存占用量是设定的最大大小两倍之多。这可能代表,若是可能的话,可微调元空间容量大小策略,来避免本地内存的浪费。
从Java程序的输出中看到以下异常。
[Loaded ClassA from file:/D:/classes/] total: 21393 active: 21393 unloaded: 0 [GC (Metadata GC Threshold) 64306K->57010K(111616K), 0.0145502 secs] [Full GC (Metadata GC Threshold) 57010K->56810K(122368K), 0.1068084 secs] java.lang.OutOfMemoryError: Metaspace
在设置了MaxMetaspaceSize的状况下,该空间的内存仍然会耗尽,进而引起“java.lang.OutOfMemoryError: Metadata space”错误。由于类加载器的泄漏仍然存在,而一般Java又不但愿无限制地消耗本机内存,所以设置一个相似于MaxPermSize的限制看起来也是合理的。
小结:
https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html