一、前言
毫无疑问,Java 8的发布是自从Java5以来Java世界中最重大的事件,它在编译器、工具类和Java虚拟机等方面为Java语言带来的很多新特性。在本文中我们將一起关注下这些新变化,使用实际的例子展示它们的使用场景。
本教程涉及到一下几个部分的内容:
- 语法规范
- 编译器
- 类库
- 工具
- Java虚拟机
二、Java语言中的新特性
Java 8 是一个主发行版本,为了实现每一位Java开发人员所期待的特性而花费了很长时间,本节將会涉及到Java 8 中的大部分新特性。
1.Lambda表达式和函数式编程接口
Lambda表达式是整个Java 8体系中最让人期待的特性,它允许我们將函数作为一个方法的参数传递到方法体中或者將一段代码作为数据,这些概念有过函数式编程经验的会比较熟悉,很多基于Java虚拟机平台的语言(Groovy、Scala…)都引入了Lambda表达式。
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表达式可以引用类的成员和局部变量(隐式的使用final关键字修饰它们),例如下面两段代码是等价的:
String separator = ",";
Arrays.asList("a", "b", "d").forEach((String e) -> System.out.print( e + separator ));
和
final String separator = ",";
Arrays.asList("a", "b", "d").forEach((String e) -> System.out.print( e + separator ));
Lambda表达式还可以返回一个值,返回值的类型由编译器推断,如果Lambda表达式体只有一行,return语句是不需要的,下面两段代码片段是等价的:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
和
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
语言设计者们在现存的功能中如何更友好的支持Lambda表达式上做出了大量的思考。因此出现了函数式接口的概念,一个函数接口中只能声明一个单独的函数。因此,它可以隐式的转换为Lambda表达式。java.lang.Runnable和java.util.concurrent.Callable就是两个很好的函数式接口的例子。在编程实践中,函数式接口是相当容易被破坏的,如果有人在接口的定义中添加了另外一个方法,它將不再是函数式接口而且编译过程可能会失败。为了解决这种易破坏性并且显式的声明该接口属于函数式接口,Java 8新增了一个特殊的注解@FunctionalInterface,我们来看一下一个简单的函数式接口的定义:
@FunctionalInterface
public interface Functional {
void method();
}
有一件事需要注意:默认和静态方法并不破坏函数式接口的约定,可以声明如下:
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
Lambda表达式是Java 8最大的卖点,它潜在的吸引越来越多的开发者使用这个伟大的平台并且提供了先进的纯Java支持的函数式编程的概念。更多细节请参考官方文档,也可参考另一篇浅显易懂的文章。
2.接口的默认和静态方法
Java 8 扩展了接口的声明,引入了两个新的概念default和static方法,default 方法使得接口有些相似的特征但又服务于不同的目标。它允许在不打破旧版本接口二进制兼容性的前提下添加新的方法。
default方法和abstract修饰的方法的不同在于,abstract方法在子类中必须实现它,但是default方法不需要。相反,每个接口中必须提供default方法默认的实现并且所有接口的实现类都会默认继承它(如果需要可以覆盖这个默认实现)。让我们看看下面的例子:
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声明了一个default方法notRequired()。类DefaultableImpl实现了该接口,保留了notRequired()方法默认的实现,另外一个类OverridableImpl重写了默认的实现。
Java 8中提供了另外一个有趣的特性,接口中可以声明(并实现)static关键字修饰的方法。例如:
private interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}
下面一小段代码展示了上例中static方法和default方法的使用:
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
default方法在JVM中的实现是非常高效的,并且提供了方法调用的字节码指令。default方法允许对现有的Java接口进行演变而不打断编译过程。在java.util.Collection接口中新增stream(), parallelStream(), forEach(), removeIf(), …方法就是很好的例子。
注:虽然功能很强大,default方法在使用时也是需要谨慎的,把一个方法声明为default时最好再三考虑是否真的有必要,否则可能会导致含义模糊,在错综复杂的继承关系中可能导致编译出错。更多细节请参考官方文档。
3.方法引用(Method References)
方法引用提供了一种非常有用的语法去直接引用类或对象的方法(或构造函数)。与Lambda表达式结合使用,方法引用使语言结构看起来简洁紧凑。
下面的例子中,Car类中定义了一些不同的方法,我们一起看一下Java8支持的四种不同类型的方法引用。
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 );
运行这些案例,控制台输出:
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
更多关于方法引用的细节,请参考官方文档。
4.重复注解(Repeating annotations)
自从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() );
}
}
}
我们可以看到,Filter是使用@Repeatable修饰的注解类。Filters注解是Filter注解的持有者,但是Java编译器试图向开发人员掩饰它的存在。在Filterable接口中定义了两次Filter。
Java的反射API提供了一个新的方法getAnnotationsByType() ,我们使用它返回Filter类型的重复注解。运行程序输出:
filter1
filter2
更多细节请查看官方文档。
5.更好的类型推测机制
Java 8编译器在类型引用上做了很大的改进。在很多情况下,,显式的类型参数可以由编译器推断以保持代码的简洁,我们来看一下下面的例子。
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 >类型。
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()。
6.扩展注解支持
Java 8 扩展了注解的使用场景。现在,它几乎可以对Java中任何元素进行注解:局部变量、泛型、超类、甚至是函数的异常声明。一些使用案例如下:
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编译器新特性
1.参数名
资深的Java开发人员一直在发明不同的方式使得方法参数的名字能保留在Java字节码中,并且能够在运行时获取它们(比如,Paranamer类库)
最终,Java 8在语言(使用反射API和Parameter.getName()方法)和字节码(使用新的javac编译器的-parameters参数)中增加了这一特性
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使用人员,为编译器增加-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发行版Eclipse Kepler SR2中,如下图所示,勾选相关选项:
注:验证参数名称的可用性,可以使用Parameter类的isNamePresent()方法。
四、Java库的新特性
为了更好的支持现代并发、函数式编程、时间日期等,Java 8新增一些新的类,并且扩展了一些现有的类。
1.Optional
著名的空指针异常(NullPointerException)是迄今为止最流行的导致Java应用程序失败的原因。很久以前,伟大的Google Guava项目提供了Optional去解决空指针异常,冗余的检查null的代码,鼓励着程序员写更加简洁的代码。受到Google Guava的启示,Optional成为Java 8 库的一部分。
Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。更多详情请参考官方文档。
我们下面用两个小例子来演示如何使用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!" ) );
如果Optional类的实例为非空值的话,isPresent()返回true,否则返回false。为了防止Optional为空值,orElseGet()方法通过回调函数来产生一个默认值。map()函数对当前Optional的值进行转化,然后返回一个新的Optional实例。orElse()方法和orElseGet()方法类似,但是orElse接受一个默认值而不是一个回调函数。下面是这个程序的输出:
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!
更多细节请查看官方文档。
2.流(Streams)
新增的Stream API( java.util.stream)將实际的函数式编程引入到Java中。这是迄今为止最全面的新增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中持有Status类型和Integer类型的引用points,然后我们向集合中添加Task对象:
final Collection< Task > tasks = Arrays.asList(
new Task( Status.OPEN, 5 ),
new Task( Status.OPEN, 13 ),
new Task( Status.CLOSED, 8 )
);
第一个问题是我们需要知道所有对象中Task的status属性值为OPEN的points之和是多少?在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流转换为Integer流,使用每个实例的Task::getPoints方法
最后使用sum方法,计算出最终结果
在开始下一个案例之前,需要了解一下stream的一些细节(更多细节)。
Stream操作分为中间操作和最终操作。中间操作返回一个新的stream。执行一个中间操作(例如过滤)实际上并没有发生过滤操作,而是生成一个新的stream。最终操作,例如forEach或sum,可以遍历stream来产生结果。最终操作执行后,流管道(pipelines)被认为被消耗,將不能再被使用。在几乎所有情况下,最终操作是希望完成底层数据源的遍历。
Stream的另一个价值是并行操作,我们来看下面一个计算所有Task对象points之和的例子:
// 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 );
这个例子和上一个比较类似,不同的是使用parallel方法处理所有的Task对象,使用reduce方法计算最终结果。
控制台输出:
Total points (all tasks): 26.0
通常,我们需要执行一个集合元素的分组操作,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对象的points值占整个集合中所有Task对象points属性之和的百分比:
// 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集合使用,典型的IO操作,像一行一行的从文本文件中读取数据,使用steam 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 );
}
onClose方法的调用返回一个等价的流和一个额外的关闭处理程序。Stream的close()方法调用时,关闭处理程序会被执行。
Stream API、Lambda表达式与方法引用在接口的默认和静态方法的配合下是Java 8对现代软件开发范式的回应,更多细节请查看官方文档。
3.日期/时间 API(JSR310)
Java 8提供新的时间-日期API(JSR310),来进一步加强对日期与时间的处理。对日期与时间的操作一直是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() );
控制台输出:
2014-04-12T15:19:29.282Z
1397315969360
我们需要关注的其他类是LocalDate与LocalTime。LocalDate只持有ISO-8601格式且无时区信息的日期部分。相应的,LocalTime只持有ISO-8601格式且无时区信息的时间部分。LocalDate与LocalTime都可以从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
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 );
控制台输出:
2014-04-12T11:37:52.309
2014-04-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-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() );
上面的例子中我们使用Duration类计算出两个日期相差的间隔,控制台输出:
Duration in days: 365
Duration in hours: 8783
程序员对Java 8在日期/时间API的改进整体印象是非常非常好的。一部分原因是因为它建立在“久战杀场”的Joda-Time基础上,另一方面是因为用来大量的时间来设计它,并且这次程序员的声音得到了认可。更多详情请参考官方文档。
4.JavaScript引擎Nashorn
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 tools章节中将会再对Nashorn引擎进行介绍。
5.Base64编码
最后,Java 8发行版的标准库中新增了Base64编码支持,它的使用较为简单,例如:
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也提供了URL和MIME编码解码器(Base64.getUrlEncoder()/Base64.getUrlDecoder(),Base64.getMimeEncoder()/Base64.getMimeDecoder())。
6.并行的数组工具类Arrays(Parallel Arrays)
Java 8发行版新增了一些新的方法,允许并行的数组处理,其中最重要的一个是parallelSort()方法,在多核处理器上能显著的提高数组排序速度。下面的例子演示了新的parallelXxx系列方法的使用:
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()方法在数组中填充2000个随机值,然后调用parallelSort()方法,程序输出排序后的前10个元素以查看是否是有序的。
运行程序控制台输出如下:
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793
7.并发(Concurrency)
在新增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包中还增加了下面这些类:
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
五、新的Java工具
Java 8中新增了一些命令行工具,在本节中我们来看一下它们的有趣之处。
1.Nashorn引擎:jjs
jjs是一个基于Nashorn引擎的命令行工具,它接收一个JavaScript源文件作为参数,然后运行它们。例如,我们创建一个func.js文件,内容如下:
function f() {
return 1;
};
print( f() + 1 );
在控制台中运行以下命令:
jjs func.js
控制台输出:
2
更多细节请查看相关官方文档。
2.类依赖关系分析工具:jdeps
jdeps是一个非常优秀的控制台工具,能够显示Java class文件类层级和包层级的依赖关系。它接收.class文件、目录、jar文件作为参数,输出依赖关系到控制台。
例如,我们使用它分析spring框架中core模块,在控制台输入:
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
六、JVM新特性
垃圾回收机制中的永久代(PermGen)被元空间(Metaspace)取代。JVM参数-XX:PermSize和-XX:MaxPermSize被替换为-XX:MetaSpaceSize和-XX:MaxMetaspaceSize。
七、相关资源
下面一些文章从不同层面上深度讨论了Java 8的特性:
- JDK 8 新特性
- Java教程
- WildFly 8, JDK 8, NetBeans 8, Java EE 7
- Java 8教程
- JDK 8命令行工具——静态依赖分析器
- 探究JDK 8的Javadoc
- Java 8的“黑暗面”
- Java 8
- Oracle Nashorn——JVM平台上的下一代JavaScript引擎
原文链接: javacodegeeks
译文链接: http://www.importnew.com/11908.html
http://blog.csdn.net/rongbo_j/article/details/49644689