转载+更新:JDK 8 中十大新特性

前言: Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级。在Java Code Geeks上已经有很多介绍Java 8新特性的文章,例如Playing with Java 8 – Lambdas and ConcurrencyJava 8 Date Time API Tutorial : LocalDateTimeAbstract Class Versus Interface in the JDK 8 Era。本文还参考了一些其他资料,例如:15 Must Read Java 8 TutorialsThe Dark Side of Java 8。本文综合了上述资料,整理成一份关于Java 8新特性的参考教材,希望你有所收获。

1 简介

        毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和JVM等方面的十多个新特性。在本文中我们将学习这些新特性,并用实际的例子说明在什么场景下适合使用。

这个教程包含Java开发者经常面对的几类问题:

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

2 Java语言的新特性

        Java 8是Java的一个重大版本,有人认为,虽然这些新特性领Java开发人员十分期待,但同时也需要花不少精力去学习。在这一小节中,我们将介绍Java 8的大部分新特性。

2.1 Lambda表达式和函数式接口

2.1.1 Lambda表达式

        Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。

  • Lambda的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,语法格式如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
  • 例如:
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的),可以直接在 lambda 表达式中访问外层的局部变量,不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。在 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;
    } );

2.1.2 Lambda的使用

(1) 延迟执行
         所有Lambda表达式都是延迟执行的。延迟执行的原因有很多,例如:在另一个线程中运行代码、多次运行代码、在某个算法的正确时间运行代码(排序中比较操作等)、当某些情况发生时运行代码(按钮被点击、数据到达等)、只有在需要的时候才运行代码。

例如,编写以下要求的方法:接受Lambda表达式、检查它是否应该被调用、在需要时调用它。要接受Lambda表达式,需要选择一个函数式接口。以下方法提供了延迟记录日志的能力:

public static void info(Logger logger, Supplier<String> message) {
	if(logger.isLoggable(Level).INFO) {
		logger info(message.get())
	}
}

Supplier<String> 作为参数,可以传递一个lambda表达式。

(2) 其他
  •  Lambda表达式的参数 
  • 选择一个函数式接口
  • 返回函数
  • 组合
  • 延迟
  • 并行操作
  • 处理异常
  • Lambda表达式和泛型
  • 一元操作

2.1.3 函数式接口

  • Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数式接口这个概念。
  • 函数接口指的是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。这样的接口可以隐式转换为Lambda表达式。
  • JDK 1.8之前已有的函数式接口:java.lang.Runnable、java.util.concurrent.Callable、java.security.PrivilegedAction、java.util.Comparator、java.io.FileFilter、java.nio.file.PathMatcher、java.lang.reflect.InvocationHandler、java.beans.PropertyChangeListener、java.awt.event.ActionListener、javax.swing.event.ChangeListenerJDK 1.8 新增加的函数接口:java.util.function。
  • java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:参考链接
  • java.lang.Runnablejava.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:
@FunctionalInterface
public interface Functional {
    void method();
}

不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。

@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();
    default void defaultMethod() {            
    }        
}

        Lambda表达式作为Java 8的最大卖点,它有潜力吸引更多的开发者加入到JVM平台,并在纯Java编程中使用函数式编程的概念。如果你需要了解更多Lambda表达式的细节,可以参考官方文档


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

        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";
    }
}

    一个类实现了多个接口,且这些接口有相同的默认方法,以下说明解决方法:

public interface vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
}
public interface fourWheeler {
   default void print(){
      System.out.println("我是一辆四轮车!");
   }
}
  • 方法1:创建自己的默认方法,来覆盖重写接口的默认方法。
public class car implements vehicle, fourWheeler {
   default void print(){
      System.out.println("我是一辆四轮汽车!");
   }
}
  • 方法2:使用super来调用指定接口的默认方法。
public class car implements vehicle, fourWheeler {
   public void print(){
      vehicle.super.print();
   }
}

         静态默认方法:接口可以声明(并且可以提供实现)静态方法。【Java 8 的另一个特性】,例如:

public interface vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
    // 静态方法
   static void blowHorn(){
      System.out.println("按喇叭!!!");
   }
}

         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()等等。

尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档

2.3 方法引用

        方法引用通过方法的名字来指向一个方法,使得开发者可以直接引用现存的方法、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

如果想了解和学习更详细的内容,可以参考官方文档

2.4 重复注解

        自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。

在Java 8中使用@Repeatable注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。可以利用下面的代码说明:

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.class)注解修饰,而Filters是存放Filter注解的容器,编译器尽量对开发者屏蔽这些细节。这样,Filterable接口可以用两个Filter注解注释(这里并没有提到任何关于Filters的信息)。

  另外,反射API提供了一个新的方法:getAnnotationsByType(),可以返回某个类型的重复注解,例如Filterable.class.getAnnoation(Filters.class)将返回两个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拓宽了注解的应用场景。现在,注解几乎可以使用在任何元素上:局部变量接口类型超类接口实现类,甚至可以用在函数的异常定义上。下面是一些例子:

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_USERElementType.TYPE_PARAMETER是Java 8新增的两个注解,用于描述注解的使用场景。Java 语言也做了对应的改变,以识别这些新增的注解。

3 Java编译器的新特性

3.1 参数名称

    为了在运行时获得Java程序中方法的参数名称,老一辈的Java程序员必须使用不同方法,例如Paranamer liberary。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() );
        }
    }
}

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

4 Java官方库的新特性

        Java 8增加了很多新的工具类(date/time类),并扩展了现存的工具类,以支持现代的并发编程、函数式编程等。

4.1 Optional

        Java应用中最常见的bug就是空值异常。在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。

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!" ) );

        如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回false;orElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值。

上述代码的输出结果如下:

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() − 为集合创建串行流;parallelStream() − 为集合创建并行流。】

        Stream的这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

  • Stream(流)是一个来自数据源元素队列并支持聚合操作

※元素:特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
数据源:流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。

聚合操作 :类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

  • 和以前的Collection操作不同, Stream操作还有两个基础的特征:
Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

        Steam 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 ) 
);

        首先看一个问题:在这个task集合中一共有多少个OPEN状态的点?在Java 8之前,要解决这个问题,则需要使用foreach循环遍历task集合;但是在Java 8中可以利用steams解决:包括一系列元素的列表,并且支持顺序和并行处理。

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

        这里有很多知识点值得说。首先,tasks集合被转换成steam表示;其次,在steam上的filter操作会过滤掉所有CLOSED的task;第三,mapToInt操作基于每个task实例的Task::getPoints方法将task流转换成Integer集合;最后,通过sum方法计算总和,得出最后的结果。

在学习下一个例子之前,还需要记住一些steams(点此更多细节)的知识点。Steam之上的操作可分为中间操作和晚期操作。

中间操作会返回一个新的steam——执行一个中间操作(例如filter)并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam。

        晚期操作(例如forEach或者sum),会遍历steam并得出结果或者附带结果;在执行晚期操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,晚期操作都是立刻对steam进行遍历。

steam的另一个价值是创造性地支持并行处理(parallel processing)。对于上述的tasks集合,我们可以用下面的代码计算所有任务的点数之和:

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

        对于一个集合,经常需要根据某些条件对其中的元素分组。利用steam提供的API可以很快完成这类任务,代码如下:

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

最后一个关于tasks集合的例子问题是:如何计算集合中每个任务的点数在集合中所占的比重,具体处理的代码如下:

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

最后,正如之前所说,Steam API不仅可以作用于Java集合,传统的IO操作(从文件或者网络一行一行得读取数据)可以受益于steam处理,这里有一个小例子:

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对软件开发的现代范式的响应。

4.3 Date/Time API(JSR 310)

        Java 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题(甚至令开发者更加迷茫)。

  • Local(本地) − 简化了日期时间的处理,没有时区的问题。
  • Zoned(时区) − 通过制定的时区处理日期时间。

            因为上面这些原因,诞生了第三方库Joda-Time,可以替代Java的时间管理API。Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。

我们接下来看看java.time包中的关键类和各自的使用例子。首先,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

第二,关注下LocalDateLocalTime类。LocalDate仅仅包含ISO-8601日历系统中的日期部分;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

LocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息。这里有一些关于LocalDate和LocalTime的例子

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

        如果需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有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() );
用于计算2014年4月16日和2015年4月16日之间的天数和小时数,输出结果如下:
Duration in days: 365
Duration in hours: 8783
public class DurationTest {
    public static void main(String[] args) {
        Instant start = Instant.now();
        getSum(1000000);
        Instant end = Instant.now();
        Duration duration = Duration.between(start, end);
        System.out.println("毫秒差距:" + duration.toMillis() + "ms");   --> 毫秒差距:2ms
        System.out.println("纳秒差距:" + duration.toNanos() + "ns");    --> 纳秒差距:2000000ns
    }
    public static int getSum(int n) {
        int sum = 0;
        for (int i = 1; i <= n; i ++ ) {
            sum += i;
        }
        return sum;
    }
}

对于Java 8的新日期时间的总体印象还是比较积极的,一部分是因为Joda-Time的积极影响,另一部分是因为官方终于听取了开发人员的需求。如果希望了解更多细节,可以参考官方文档

(1) LocalDate


//程序员日是一年中的第256天
LocalDate programmersDay = LocalDate.of(2018, 1,1).plusDays(255);
System.out.println("2018年程序员日:" + programmersDay);
//输出结果——>2018年程序员日:2018-09-13  2016年程序员日:2018-09-12(闰年)

//下一年生日日期
LocalDate birthday = LocalDate.of(2018, 1,12);
System.out.println("明年生日日期:" + birthday.plus(Period.ofYears(1)));
//输出结果——>明年生日日期:2019-01-12

LocalDate today = LocalDate.now();
LocalDate nationalDay = LocalDate.of(2018, 10, 1);
System.out.println("离国庆节还有" + today.until(nationalDay, ChronoUnit.DAYS) + "天");
//输出结果——>离国庆节还有97天

//计算本月的第一个周二
LocalDate firstTuesday = LocalDate.of(today.getYear(), today.getMonth(), 1)
        .with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY));
System.out.println("本月的第一个周二的日期:" + firstTuesday);

//采用案例
System.out.println("本月的第一天:" + today.with(TemporalAdjusters.firstDayOfMonth()));
//输出结果——>本月的第一天:2018-06-01
System.out.println("本月的第六天:" + today.withDayOfMonth(6));
//输出结果——>本月的第六天:2018-06-06
System.out.println("本月的最后一天:" + today.with(TemporalAdjusters.lastDayOfMonth()));
//输出结果——>本月的最后一天:2018-06-30
LocalDate firstMonday = LocalDate.parse("2018-07-01")
        .with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
System.out.println("某月的第一个周一:" + firstMonday);
//输出结果——>某月的第一个周一:2018-07-02
(2) LocalTime


//当天的某一个时间
LocalTime now = LocalTime.now();
System.out.println("现在" + now + "点");
//输出结果——>现在10:28:19.616点
System.out.println("现在" + now.withNano(0) + "点");
//输出结果——>现在10:28:19点[now.withNano(0)清除毫秒数]

//等价于LocalTime.NOON
LocalTime noon = LocalTime.of(12, 0, 0);
System.out.println("中午" + noon + "点");
//输出结果——>中午12:00点(秒为0,不显示)

//等价于 DateTimeFormatter.ISO_TIME
System.out.println("中午" + noon.format(DateTimeFormatter.ISO_LOCAL_TIME) + "点");
//输出结果——>中午12:00:00点
System.out.println(LocalTime.MIDNIGHT);
//输出结果——>00:00
System.out.println(LocalTime.parse("12:00:00"));
//输出结果——>12:00

//早上6点半醒来
LocalTime bedTime = LocalTime.of(22, 30);
LocalTime wake = bedTime.plusHours(8);
System.out.println("起床时间:" + wake);
//输出结果——>起床时间:06:30

/**
 * 时间也是按照ISO格式识别,但可以识别以下3种格式:12:00  12:01:02  12:01:02.345
 * 最新JDBC映射将把数据库的日期类型和Java 8的新类型关联起来:
 *      SQL -> Java
 *      --------------------------
 *      date -> LocalDate
 *      time -> LocalTime
 *      timestamp -> LocalDateTime
 */
(3) LocalDateTime
  • 根据年、月、日、时、分、秒、纳秒等创建LocalDateTime:
LocalDateTime of(int year, Month month, int dayOfMonth, int hour, int minute)
LocalDateTime of(int year, Month month, int dayOfMonth, int hour, int minute, int second)
LocalDateTime of(int year, Month month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond)
LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute)
LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second)
LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond)
LocalDateTime of(LocalDate date, LocalTime time)
  • LocalDatetime 的所有方法:
1.	adjustInto	调整指定的Temporal和当前LocalDateTime对
2.	atOffset	结合LocalDateTime和ZoneOffset创建一个
3.	atZone	结合LocalDateTime和指定时区创建一个ZonedD
4.	compareTo	比较两个LocalDateTime
5.	format	格式化LocalDateTime生成一个字符串
6.	from	转换TemporalAccessor为LocalDateTi
7.	get	得到LocalDateTime的指定字段的值
8.	getDayOfMonth	得到LocalDateTime是月的第几天
9.	getDayOfWeek	得到LocalDateTime是星期几
10.	getDayOfYear	得到LocalDateTime是年的第几天
11.	getHour	得到LocalDateTime的小时
12.	getLong	得到LocalDateTime指定字段的值
13.	getMinute	得到LocalDateTime的分钟
14.	getMonth	得到LocalDateTime的月份
15.	getMonthValue	得到LocalDateTime的月份,从1到12
16.	getNano	得到LocalDateTime的纳秒数
17.	getSecond	得到LocalDateTime的秒数
18.	getYear	得到LocalDateTime的年份
19.	isAfter	判断LocalDateTime是否在指定LocalDateT
20.	isBefore	判断LocalDateTime是否在指定LocalDateT
21.	isEqual	判断两个LocalDateTime是否相等
22.	isSupported	判断LocalDateTime是否支持指定时间字段或单元
23.	minus	返回LocalDateTime减去指定数量的时间得到的值
24.	minusDays	返回LocalDateTime减去指定天数得到的值
25.	minusHours	返回LocalDateTime减去指定小时数得到的值
26.	minusMinutes	返回LocalDateTime减去指定分钟数得到的值
27.	minusMonths	返回LocalDateTime减去指定月数得到的值
28.	minusNanos	返回LocalDateTime减去指定纳秒数得到的值
29.	minusSeconds	返回LocalDateTime减去指定秒数得到的值
30.	minusWeeks	返回LocalDateTime减去指定星期数得到的值
31.	minusYears	返回LocalDateTime减去指定年数得到的值
32.	now	返回指定时钟的当前LocalDateTime
33.	of	根据年、月、日、时、分、秒、纳秒等创建LocalDateTi
34.	ofEpochSecond	根据秒数(从1970-01-0100:00:00开始)创建L
35.	ofInstant	根据Instant和ZoneId创建LocalDateTim
36.	parse	解析字符串得到LocalDateTime
37.	plus	返回LocalDateTime加上指定数量的时间得到的值
38.	plusDays	返回LocalDateTime加上指定天数得到的值
39.	plusHours	返回LocalDateTime加上指定小时数得到的值
40.	plusMinutes	返回LocalDateTime加上指定分钟数得到的值
41.	plusMonths	返回LocalDateTime加上指定月数得到的值
42.	plusNanos	返回LocalDateTime加上指定纳秒数得到的值
43.	plusSeconds	返回LocalDateTime加上指定秒数得到的值
44.	plusWeeks	返回LocalDateTime加上指定星期数得到的值
45.	plusYears	返回LocalDateTime加上指定年数得到的值
46.	query	查询LocalDateTime
47.	range	返回指定时间字段的范围
48.	toLocalDate	返回LocalDateTime的LocalDate部分
49.	toLocalTime	返回LocalDateTime的LocalTime部分
50.	toString	返回LocalDateTime的字符串表示
51.	truncatedTo	返回LocalDateTime截取到指定时间单位的拷贝
52.	until	计算LocalDateTime和另一个LocalDateTi
53.	with	返回LocalDateTime指定字段更改为新值后的拷贝
54.	withDayOfMonth	返回LocalDateTime月的第几天更改为新值后的拷贝
55.	withDayOfYear	返回LocalDateTime年的第几天更改为新值后的拷贝
56.	withHour	返回LocalDateTime的小时数更改为新值后的拷贝
57.	withMinute	返回LocalDateTime的分钟数更改为新值后的拷贝
58.	withMonth	返回LocalDateTime的月份更改为新值后的拷贝
59.	withNano	返回LocalDateTime的纳秒数更改为新值后的拷贝
60.	withSecond	返回LocalDateTime的秒数更改为新值后的拷贝
61.	withYear	返回LocalDateTime年份更改为新值后的拷贝
  • 例子:
LocalDateTime dateTime = LocalDateTime.now();
System.out.println(dateTime);
//输出结果——>2018-06-27T11:18:58.343
System.out.println(dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
//输出结果——>2018-06-27 11:18:58
System.out.println(dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss", Locale.FRANCE)));
//输出结果——>2018-06-27 11:18:58

//Date转换为LocalDateTime
Date now = new Date();
System.out.println(now + "转化为" + LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault()));
//输出结果——>Wed Jun 27 11:30:22 CST 2018转化为2018-06-27T11:30:22.351

//LocalDateTime转换为Date
System.out.println(dateTime + "转化为" + Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant()));
//输出结果——>2018-06-27T11:31:47.361转化为Wed Jun 27 11:31:47 CST 2018

//获取指定日期的毫秒、秒
System.out.println(dateTime + "的毫微/纳秒为" + dateTime.atZone(ZoneId.systemDefault()).toInstant().getNano());
//输出结果——>2018-06-27T11:33:50.960的毫秒为960000000
System.out.println(dateTime + "的秒为" + dateTime.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond());
//输出结果——>2018-06-27T11:33:50.960的秒为1530070430
  • 常用工具类:
import org.apache.commons.lang3.StringUtils;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Date;

/**
 * 日期时间工具类
 *
 * @author xinyu112
 * @date 2018/07/09
 */
public class DateTimeUtils {

    public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
    public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    public static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * Date转换为LocalDateTime
     *
     * @param date
     * @return
     */
    public static LocalDateTime convertToLocalDateTime(Date date) {
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }

    /**
     * LocalDateTime转换为Date
     *
     * @param dateTime
     * @return
     */
    public static Date convertToDate(LocalDateTime dateTime) {
        return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
    }


    /**
     * 返回本地当前日期
     *
     * @return
     */
    public static LocalDate getCurrentLocalDate() {
        return LocalDate.now();
    }

    /**
     * 返回本地当前时间
     *
     * @return
     */
    public static LocalTime getCurrentLocalTime() {
        return LocalTime.now();
    }

    /**
     * 返回本地当前日期时间
     *
     * @return
     */
    public static LocalDateTime getCurrentLocalDateTime() {
        return LocalDateTime.now();
    }

    /**
     * 返回本地当前日期的固定或自定义字符串
     *
     * @return
     */
    public static String getCurrentDateStr(String pattern) {
        if (StringUtils.isNotBlank(pattern)) {
            return LocalDate.now().format(DateTimeFormatter.ofPattern(pattern));
        }
        return LocalDate.now().format(DATE_FORMATTER);
    }

    /**
     * 返回本地当前时间的固定或自定义字符串
     *
     * @return
     */
    public static String getCurrentTimeStr(String pattern) {
        if (StringUtils.isNotBlank(pattern)) {
            return LocalTime.now().format(DateTimeFormatter.ofPattern(pattern));
        }
        return LocalTime.now().format(TIME_FORMATTER);
    }

    /**
     * 返回本地当前日期时间的固定或自定义字符串
     *
     * @return
     */
    public static String getCurrentDateTimeStr(String pattern) {
        if (StringUtils.isNotBlank(pattern)) {
            return LocalDateTime.now().format(DateTimeFormatter.ofPattern(pattern));
        }
        return LocalDateTime.now().format(DATETIME_FORMATTER);
    }

    /**
     * 将本地日期字符串根据pattern解析出本地日期
     *
     * @param dateStr
     * @param pattern
     * @return
     */
    public static LocalDate parseLocalDate(String dateStr, String pattern) {
        return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * 将本地时间字符串根据pattern解析出本地时间
     *
     * @param timeStr
     * @param pattern
     * @return
     */
    public static LocalTime parseLocalTime(String timeStr, String pattern) {
        return LocalTime.parse(timeStr, DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * 将本地日期字符串根据pattern解析出本地日期
     *
     * @param dateTimeStr
     * @param pattern
     * @return
     */
    public static LocalDateTime parseLocalDateTime(String dateTimeStr, String pattern) {
        return LocalDateTime.parse(dateTimeStr, DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * 将本地日期转化为固定或自定义格式的字符串
     *
     * @param date
     * @return
     */
    public static String formatLocalDate(LocalDate date, String pattern) {
        if (StringUtils.isNotBlank(pattern)) {
            return date.format(DateTimeFormatter.ofPattern(pattern));
        }
        return date.format(DATE_FORMATTER);
    }

    /**
     * 将本地时间转化为固定或自定义格式的字符串
     *
     * @param time
     * @return
     */
    public static String formatLocalTime(LocalTime time, String pattern) {
        if (StringUtils.isNotBlank(pattern)) {
            return time.format(DateTimeFormatter.ofPattern(pattern));
        }
        return time.format(TIME_FORMATTER);
    }

    /**
     * 将本地日期时间转化为固定或自定义格式的字符串
     *
     * @param datetime
     * @return
     */
    public static String formatLocalDateTime(LocalDateTime datetime, String pattern) {
        if (StringUtils.isNotBlank(pattern)) {
            return datetime.format(DateTimeFormatter.ofPattern(pattern));
        }
        return datetime.format(DATETIME_FORMATTER);
    }

    /**
     * 获取指定日期的秒
     *
     * @param dateTime
     * @return
     */
    public static Long getSeconds(LocalDateTime dateTime) {
        return dateTime.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
    }

    /**
     * 获取指定日期的毫秒
     *
     * @param dateTime
     * @return
     */
    public static Long getMillis(LocalDateTime dateTime) {
        return dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }

    /**
     * 本地日期时间加上一个数
     *
     * @param dateTime
     * @param num
     * @param field    ChronoUnit.*(年、月、周、日、分...)
     * @return
     */
    public static LocalDateTime plus(LocalDateTime dateTime, long num, TemporalUnit field) {
        return dateTime.plus(num, field);
    }

    /**
     * 本地日期时间减去一个数
     *
     * @param dateTime
     * @param num
     * @param field    ChronoUnit.*(年、月、周、日、分...)
     * @return
     */
    public static LocalDateTime minu(LocalDateTime dateTime, long num, TemporalUnit field) {
        return dateTime.plus(num, field);
    }

    /**
     * 获取本地两个日期的时间差
     *
     * @param startTime
     * @param endTime
     * @param field
     * @return
     */
    public static long betweenTwoTime(LocalDateTime startTime, LocalDateTime endTime, TemporalUnit field) {
        Period period = Period.between(LocalDate.from(startTime), LocalDate.from(endTime));
        if (field == ChronoUnit.YEARS) {
            return period.getYears();
        } else if (field == ChronoUnit.MONTHS) {
            return period.getYears() * 12 + period.getMonths();
        }
        return field.between(startTime, endTime);
    }

    /**
     * 获取一天的开始时间
     *
     * @param dateTime
     * @return
     */
    public static LocalDateTime getDateStart(LocalDateTime dateTime) {
        return dateTime.withHour(0).withMinute(0).withSecond(0).withNano(0);
    }

    /**
     * 获取一天的结束时间
     *
     * @param dateTime
     * @return
     */
    public static LocalDateTime getDateEnd(LocalDateTime dateTime) {
        return dateTime.withHour(23).withMinute(59).withSecond(59).withNano(999999999);
    }

    /**
     * 是否当天
     *
     * @param dateTime
     * @return
     */
    public static boolean isToday(LocalDateTime dateTime) {
        return getCurrentLocalDate().equals(dateTime.toLocalDate()));
    }

    public static void main(String[] args) {
        LocalDateTime current = LocalDateTime.now();
        System.out.println(isToday(current));
        // 输出结果 --> true
        System.out.println(getMillis(current));
        // 输出结果 --> 1531105260535
        System.out.println(plus(current, 7, ChronoUnit.DAYS));
        // 输出结果 --> 2018-07-16T11:01:00.535
        System.out.println(minu(current, 7, ChronoUnit.DAYS));
        // 输出结果 --> 2018-07-16T11:01:00.535
        System.out.println(betweenTwoTime(LocalDateTime.of(2018, 7, 1, 0, 0, 0, 0),
                current, ChronoUnit.DAYS));
        // 输出结果 --> 8
    }
}

(4)与遗留代码互操作

与其他类之间操作:java.util.Date、java.util.GregorianCalendar、java.sql.Date/Time/Timestamp。



//java.util.Date转化为LocalDateTime
System.out.println(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()));
//输出结果 --> Tue Jul 17 11:37:38 CST 2018

//LocalDateTime转化为java.util.Date
System.out.println(LocalDateTime.ofInstant(new Date().toInstant(), ZoneId.systemDefault()));
//输出结果 --> 2018-07-17T11:37:38.985

//ZonedDateTime转化为GregorianCalendar
System.out.println(GregorianCalendar.from(ZonedDateTime.now()));
//输出结果 --> java.util.GregorianCalendar[time=1531798658986,areFieldsSet=true,areAllFieldsSet=true,
// lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,
// useDaylight=false,transitions=19,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,
// YEAR=2018,MONTH=6,WEEK_OF_YEAR=29,WEEK_OF_MONTH=3,DAY_OF_MONTH=17,DAY_OF_YEAR=198,DAY_OF_WEEK=3,
// DAY_OF_WEEK_IN_MONTH=3,AM_PM=0,HOUR=11,HOUR_OF_DAY=11,MINUTE=37,SECOND=38,MILLISECOND=986,
// ZONE_OFFSET=28800000,DST_OFFSET=0]

//LocalDateTime转化为java.sql.Timestamp
System.out.println(Timestamp.valueOf(LocalDateTime.now()));
//输出结果 --> 2018-07-17 11:37:38.995

//LocalDate转化为java.sql.Date
System.out.println(java.sql.Date.valueOf(LocalDate.now()));
//输出结果 --> 2018-07-17

//LocalTime转化为java.sql.Time
System.out.println(Time.valueOf(LocalTime.now()));
//输出结果 --> 11:37:38

//获取本地的时区
System.out.println(TimeZone.getTimeZone(ZoneId.systemDefault()));
//输出结果 --> sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,
// dstSavings=0,useDaylight=false,transitions=19,lastRule=null]

4.4 Nashorn JavaScript引擎

        Java 8提供了新的Nashorn JavaScript引擎,使得我们可以在JVM上开发和运行JS应用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一个实现版本,这类Script引擎遵循相同的规则,允许Java和JavaScript交互使用。

  • jjs是个基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。例如:
创建一个具有如下内容的sample.js文件:
print('Hello World!');
打开控制台,输入以下命令:
$ jjs sample.js
以上程序输出结果为:
Hello World!

jjs交互式编程:

打开控制台,输入以下命令:
$ jjs
jjs> print("Hello, World!")
Hello, World!
jjs> quit()
>>

传递参数:

打开控制台,输入以下命令:
$ jjs -- a b c
jjs> print('字母: ' +arguments.join(", "))
字母: a, b, c
jjs> 

Java 中调用 JavaScript:

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 = "Runoob";
      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
Runoob
12

JavaScript中调用 Java:

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(568000000000000000023,13.9);
print(result);
使用 jjs 命令执行以上脚本,输出结果如下:
$ jjs sample.js
78952000000000002017.94

4.5 Base64

对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行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!

新的Base64API也支持URL和MINE的编码解码。

(Base64.getUrlEncoder() / Base64.getUrlDecoder()Base64.getMimeEncoder() / Base64.getMimeDecoder())。

4.6 并行数组

Java8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。下面的例子论证了parallexXxx系列的使。方法:

例如:用parallelSetAll()方法生成20000个随机数,然后使用parallelSort()方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。

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();
    }
}
输出结果是:
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 
Sorted: 39 220 263 268 325 607 655 678 723 793

4.7 并发性

基于新增的lambda表达式和steam特性,为Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操作(更多内容可以参考我们的并发编程课程)。

Java 8还添加了新的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引擎的命令行工具,可以接受js源码并执行。例如,我们写一个func.js文件,内容如下:

function f() { 
     return 1; 
}; 
print( f() + 1 );
在命令行中执行:jjs func.js,控制台输出结果是:
2

如果需要了解细节,可以参考官方文档

5.2 类依赖分析器:jdeps

    jdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。

我们可以利用jedps分析下Spring Framework库,为了让结果少一点,仅仅分析一个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

更多的细节可以参考官方文档

6 JVM的新特性

    使用MetaspaceJEP 122)代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize-XX:MaxMetaspaceSize代替原来的-XX:PermSize-XX:MaxPermSize

7 结论

    通过为开发者提供很多能够提高生产力的特性,Java 8使得Java平台前进了一大步。现在还不太适合将Java 8应用在生产系统中,但是在之后的几个月中Java 8的应用率一定会逐步提高(PS:原文时间是2014年5月9日,现在在很多公司Java 8已经成为主流,我司由于体量太大,现在也在一点点上Java 8,虽然慢但是好歹在升级了)。作为开发者,现在应该学习一些Java 8的知识,为升级做好准备。

关于Spring:对于企业级开发,我们也应该关注Spring社区对Java 8的支持,可以参考这篇文章——Spring 4支持的Java 8新特性一览

8 Java图像界面

JavaFX就是Java在编写图形界面程序的最新技术。参考资料:Oracle官网关于JavaFX的资源和文档

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class JavaFXOneTest extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        //网格布局
        GridPane grid = new GridPane();
        grid.setAlignment(Pos.CENTER);
        //网格垂直间距
        grid.setHgap(10);
        //网格水平间距
        grid.setVgap(10);
        grid.setPadding(new Insets(25, 25, 25, 25));
        //新建场景
        Scene scene = new Scene(grid, 300, 275);
        primaryStage.setScene(scene);
        //添加标题
        Text scenetitle = new Text("Welcome");
        scenetitle.setFont(Font.font("Tahoma", FontWeight.NORMAL, 20));
        grid.add(scenetitle, 0, 0, 2, 1);
        //添加标签及文本框
        Label userName = new Label("用户名:");
        grid.add(userName, 0, 1);

        TextField userTextField = new TextField();
        grid.add(userTextField, 1, 1);
        //添加标签及密码框
        Label pw = new Label("密码:");
        grid.add(pw, 0, 2);

        PasswordField pwBox = new PasswordField();
        grid.add(pwBox, 1, 2);
        //添加提交按钮
        Button btn = new Button("登录");
        HBox hbBtn = new HBox(10);
        hbBtn.setAlignment(Pos.BOTTOM_RIGHT);
        hbBtn.getChildren().add(btn);
        grid.add(hbBtn, 1, 4);
        //提交文本提示
        final Text actiontarget = new Text();
        grid.add(actiontarget, 1, 6);

        btn.setOnAction(event -> {
            actiontarget.setFill(Color.FIREBRICK);
            actiontarget.setText("已经登录");
        });

        primaryStage.setTitle("JavaFX Welcome");
        primaryStage.show();

    }
}

9 杂项改进


import sun.misc.BASE64Encoder;
import sun.misc.FloatConsts;

import java.nio.charset.StandardCharsets;
import java.time.ZoneId;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 杂项改进
 *
 * @author liumd
 * @date 2018/07/17
 */
public class OtherChangeTest {

    public static void main(String[] args) {
        //字符串
        System.out.println(String.join("/", "", "user", "local", "bin"));
        //--输出结果 --> /user/local/bin
        //-Set<String> getAvailableZoneIds
        System.out.println(String.join(", ", ZoneId.getAvailableZoneIds()));
        //--输出结果 --> Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi......

        //数字类
        //-Boolean的logicalAnd、logicalAnd、logicalOr、logicalXor
        System.out.println(Boolean.logicalXor(true, false));
        //--输出结果 --> true
        //-以往Byte范围: -128~127, 现在调用静态方法toUnsignedInt(0-255)
        //-以往Integer范围: -2147483648~2147483647, 现在调用静态方法toUnsignedInt(0-4294967295)
        //-Byte、Short 新增toUnsignedInt; Byte、Short、Integer新增toUnsignedLong
        System.out.println();
        System.out.println((byte) 0xFF + " " + Byte.toUnsignedInt((byte) 0xFF));
        //--输出结果 --> -1 255
        System.out.println(0xFFFFFFFF + " " + Integer.toUnsignedLong(0xFFFFFFFF));
        //--输出结果 --> -1 4294967295
        //-Integer和Long新增处理无符号值的compareUnsigned、divideUnsigned和remainderUnsigned方法
        System.out.println(0xFFFFFFFF / 2 + " " + Integer.divideUnsigned(0xFFFFFFFF, 2));
        //--输出结果 --> 0 2147483647
        //-Float和Double新增静态方法isFinite(不是正无穷、负无穷、非数字, 均返回true)
        //-POSITIVE_INFINITY = 1.0F / 0.0; NEGATIVE_INFINITY = -1.0F / 0.0; NaN = 0.0F / 0.0;
        System.out.println(Float.isFinite(FloatConsts.NaN));
        //--输出结果 --> false

        //新的数学函数
        //-默认情况下, 下面普通乘法是错误结果; Math.multiplyExact(100000, 100000) 抛出integer overflow异常
        // Math新增方法(add|subtract|multiply|increment|decrement|negate)Exact;
        //-方法参数为int或long, toIntExact方法可以将long值转化为等价的int值
        System.out.println(100000 * 100000);
        //--输出结果 --> 1410065408(错误结果)
        //-整数取余问题:原来 n%2, 偶1奇0负-1; 现在新增方法floorMod、floorDiv
        System.out.println(12 % 2 + " " + 11 % 2 + " " + 11 % -2 + " " + -11 % 2);
        //--输出结果 --> 0 1 1 -1
        System.out.println(Math.floorMod(12, 2) + " " + Math.floorMod(11, 2) + " " + Math.floorMod(11, -2) + " " + Math.floorMod(-11, 2));
        //--输出结果 --> 0 1 -1 1
        System.out.println(12 / 2 + " " + 11 / 2 + " " + 11 / -2 + " " + -11 / 2);
        //--输出结果 --> 6 5 -5 -5
        System.out.println(Math.floorDiv(12, 2) + " " + Math.floorDiv(11, 2) + " " + Math.floorDiv(11, -2) + " " + Math.floorDiv(-11, 2));
        //--输出结果 --> 6 5 -6 -6

        //集合
        //-新增方法: Iterable--forEach; Collection--removeIf; List--replaceAll/sort; Iterator--forEachRemaining; BitSet--stream
        //-Map--forEach/replace/replaceAll/remove(key, value)/putIfAbsent/compute/computeIf(Absent|Present)/merge
        Map map = new HashMap();
        map.put(1, "One");
        map.put(2, "Two");
        map.put(3, "One");
        System.out.println(map + " " + map.remove(3, "One") + " " + map);
        //--输出结果 --> {1=One, 2=Two, 3=One} true {1=One, 2=Two}

        //比较器
        Person[] personArr = new Person[]{new Person(16, "lucy", "smith"),
                new Person(18, "jake", "saul"),
                new Person(16, "lily", "smith"),
                new Person(20, "sandy", "acheson")};
        Arrays.sort(personArr, Comparator.comparing(Person::getFirstName)
                .thenComparing(Person::getLastName));
        System.out.println(Arrays.toString(personArr));
        //--输出结果 --> [jake saul is 18., lily smith is 16., lucy smith is 16., sandy acheson is 20.]
        Arrays.sort(personArr, Comparator.comparing(Person::getFirstName,
                (s, t) -> Integer.compare(s.length(), t.length())));
        System.out.println(Arrays.toString(personArr));
        //--输出结果 --> [lucy smith is 16., jake saul is 18., lily smith is 16., sandy acheson is 20.]

        //Collections类
        //-emptySorted(Set|Map)返回有序集合的轻量级实例
        System.out.println(Collections.emptySortedSet());
        //--输出结果 --> []

        //使用文件
        //-Java8提供官方Base64编码/解码的方法
        String password = "A628456a";
        System.out.println(Base64.getEncoder().encodeToString(password.getBytes(StandardCharsets.UTF_8)));
        //--输出结果 --> QTYyODQ1NmE=
        System.out.println(new BASE64Encoder().encode(password.getBytes()));
        //--输出结果 --> QTYyODQ1NmE=

        //可重复注解|可用于类型的注解
        //-可重复注解

        //-可用于类型的注解出现在以下地方
        /**
         * 用于泛型类型的参数: List<@NonNull String>、Comparator.<@NonNull String>reverseOrder()
         * 用于所有数组: String[][] @NonNull  words(words[i][j]不是null)、String @NonNull [][] words(words不是null)、String[] @NonNull [] words(words[i]不是null)
         * 用于父类和接口: class Image implements @Rectangular Shape
         * 用于调用构造函数: new @Path String("/usr/bin")
         * 用于强制类型转换和instanceof检查: if (input instanceof @Path String)
         * 用于定义抛出的异常: public Person read() throws @Localized IOException
         * 用于通配符和类型绑定: List<@ReadOnly ? extends Person>、List<? extends @ReadOnly> Person
         * 用于方法和构造函数引用: @Immutable Person::getName
         */

        //其他细微改进
        //-Null检查: 连个静态方法, isNull和nonNull, 在流里面常用
        //-延迟消息: java.util.Logger 类新增了log|logp|severe|warning|info|config|fine|finer|finest等
        //-正则表达式:
        Pattern pattern = Pattern.compile("(?<year>\\d{4})-(?<date>\\d{2})-(?<day>\\d\\d)");
        Matcher matcher = pattern.matcher("2017-07-18");
        if (matcher.matches()) {
            String year = matcher.group("year");
            String month = matcher.group("date");
            String day = matcher.group("day");
            System.out.println(year + "-" + month + "-" + day);
        }
        //--输出结果 --> 2017-07-18
        //-Pattern类新增一个splitAsStream方法, 可以按照正则表达式分割
        String text = "nice to meet you!";
        System.out.println(Pattern.compile("\\s+").splitAsStream(text).collect(Collectors.toList()));
        //--输出结果 --> [nice, to, meet, you!]

        //语言环境
        List<Locale.LanguageRange> ranges = Stream.of("de", "*-CH")
                .map(Locale.LanguageRange::new)
                .collect(Collectors.toList());
        List<Locale> matches = Locale.filter(ranges, Arrays.asList(Locale.getAvailableLocales()));
        System.out.println(matches.toString());
        //--输出结果 --> [de, de_CH, de_AT, de_LU, de_DE, de_GR, fr_CH, it_CH]

        //JDBC升级到4.2
        //-java.sql对应java.time: Date--LocalDate|Time--LocalTime|Timestamp--LocalDateTime
        //-Statement类新增方法executeLargeUpdate方法, 用来执行修改行数会超过Integer.MAX_VALUE的更新操作

    }
}

class Person {
    private Integer age;
    private String firstName;
    private String lastName;

    public Person(Integer age, String firstName, String lastName) {
        this.age = age;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return firstName + " " + lastName + " is " + age + ".";
    }
}


10 参考资料

  • 在上面这个代码中的参数e的类型是由编译器推理得出的,也可以显式指定该参数的类型
  • Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值