Java 8 新特性终极指南 1.前言  2.Java语言中的新特性 3.Java编译器新特性 4.Java库的新特性 5.新的Java工具 6.JVM新特性 7.相关资源

毫无疑问,Java 8的发布是自从Java5以来Java世界中最重大的事件,它在编译器、工具类和Java虚拟机等方面为Java语言带来的很多新特性。在本文中我们將一起关注下这些新变化,使用实际的例子展示它们的使用场景。
本教程涉及到一下几个部分的内容:
  • 语法规范
  • 编译器
  • 类库
  • 工具
  • Java虚拟机

2.Java语言中的新特性

Java 8 是一个主发行版本,为了实现每一位Java开发人员所期待的特性而花费了很长时间,本节將会涉及到Java 8 中的大部分新特性。

2.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 ) );
为了防止lamdba表达式体过于复杂,可以向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();
}
有一件事需要注意:default和static方法并不破坏函数式接口的约定,可以声明如下:

@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();
        
    default void defaultMethod() {            
    }        
}
lambda表达式是Java 8最大的卖点,它潜在的吸引越来越多的开发者使用这个伟大的平台并且提供了先进的纯Java支持的函数式编程的概念。更多细节请参考官方文档

2.2.接口的default和static方法

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时最好再三考虑是否真的有必要,否则可能会导致含义模糊,在错综复杂的继承关系中可能导致编译出错。更多细节请参考官方文档

2.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
更多关于方法引用的细节,请参考官方文档


2.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
更多细节请查看官方文档

2.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()。

2.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也发生了一些细微的变化去识别那些新的注解类型。

3.Java编译器新特性

3.1.参数名

资深的Java开发人员发明使用不同的方式在Java字节码中保存方法参数名,以便在运行时使用。
例如paranamer 库,地址:https://github.com/paul-hammant/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中,如下图所示,勾选相关选项:

Java 8 新特性终极指南
1.前言 
2.Java语言中的新特性
3.Java编译器新特性
4.Java库的新特性
5.新的Java工具
6.JVM新特性
7.相关资源

此外,验证参数名称的可用性,可以使用Parameter类的isNamePresent()方法。

4.Java库的新特性

为了更好的支持现代并发、函数式编程、时间日期等,Java 8新增一些新的类,并且扩展了一些现有的类。

4.1.Optional

著名的空指针异常(NullPointerException)是迄今为止最流行的导致Java应用程序失败的原因。很久以前,伟大的Google Guava项目提供了Optional去解决空指针异常,冗余的检查null的代码,鼓励着程序员写更加简洁的代码。受到Google Guava的启示,Optional成为Java 8 库的一部分。
Optional只是一个容器而已,它能够持有某种类型T的值或者null。它提供了一些非常有用的方法明确的避开null的检查。请查看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!" ) );
isPresent()返回true,如果Optional实例持有非空的值,否则为false。orElseGet()提供一种回调机制,为了防止Optional持有值为null,接收一个函数提供一个默认的值。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!
更多细节请查看官方文档

4.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来产生结果。最终操作执行后,流管道被认为被消耗,將不能再被使用。在几乎所有情况下,最终操作是希望完成底层数据源的遍历。
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表达式和方法引用,以及接口的default和static方法是Java 8在软件开发中应对现代范式的产物,更多细节,请查看官方文档

4.3.日期/时间 API(JSR310)

Java 8提供新的时间-日期API(JSR310),使人更容易接收日期和时间的管理方式。操作时间和日期是Java开发人员最为糟糕的一个痛点。 标准的java.util.Date和java.util.Calendar没有变动。
这就是为什么Joda-Time诞生:Java中可供选择的时间/日期API。Java 8新的日期-时间API深受Joda-Time的影响并采取了最好的。新的java.time包中包含所有日期、时间、时区和时钟操作的类。不变性的API的设计已经考虑非常认真:不允许改变(从java.util.Calendar中吸取教训)。如果需要修改,將返回各自类的新实例。
让我们看一看关键类和它们的使用案例。第一个类是Clock,它能够使用根据时区获取当前时间和日期。Clock能够取代System.currentTimeMilis()和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
接下来我们要看的类是LocaleDate和LocalTime。LocalDate只持有ISO-8601日历系统中的日期部分而没有时区部分。而LocalTime只持有IOS-8601日历系统中的时间部分,没有时区部分。LocaleDate和LocaleTime都能够使用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
LocalDateTime將LocaleDate和LocalTime结合起来,持有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是非常实用的。更多细节请查看官方文档

4.4.犀牛JavaScript引擎

Java 8 新增了犀牛JavaScript引擎,运行在JVM中运行特定的JavaScript程序。犀牛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 tools章节中将会再对犀牛引擎进行介绍。

4.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())。

4.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

4.7.并发(Concurrency)

为了支持基于stream和lambda表达式的聚合操作,java.util.concurrent.ConcurrentHashMap类中增加了新的方法。java.util.concurrent.ForkJoinPool类也增加了方法以支持线程池。
增加了用于控制读写加锁操作的类java.util.concurrent.locks.StampedLock,和java.util.concurrent.locks.ReadWriteLock相比会是更好的选择。
java.util.concurrent.atomic包下新增如下几个类:
  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

5.新的Java工具

Java 8中新增了一些命令行工具,在本节中我们来看一下它们的有趣之处。

5.1.犀牛引擎:jjs

jjs是一个基于犀牛引擎的命令行工具,它接收一个JavaScript源文件作为参数,然后运行它们。例如,我们创建一个func.js文件,内容如下:
function f() { 
     return 1; 
}; 

print( f() + 1 );
在控制台中运行以下命令:
jjs func.js
控制台输出:
2
更多细节请查看相关官方文档

5.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 FilesJavajdk1.8.0jrelib
t.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

6.JVM新特性

垃圾回收机制中的永久带(PermGen)被元空间(Metaspace)取代。JVM参数-XX:PermSize和-XX:MaxPermSize被替换为-XX:MetaSpaceSize和-XX:MaxMetaspaceSize。

7.相关资源