拥抱变化,面向Java17,Java8-18全系列特性详解_jdk 1.8 17 18 19区别

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

文章目录

  1. Java 8 新特性
  2. Java 9 新特性
  3. Java 10 新特性
  4. Java 11 新特性
  5. Java 12 新特性
  6. Java 13 新特性
  7. Java 14 新特性
  8. Java 15 新特性
  9. Java 16 新特性
  10. Java 17 新特性
  11. Java 18 新特性

💡 文章较长建议点赞、收藏、评论后慢慢看合理利用 “只看目录功能

前言

当我们大部分Javaer还沉浸在Java 8 的特性中时Java 19 预计在2022年9月20号发布现在半年发布一次的节奏真让人应接不暇况且Spring Boot 3.0开始最低版本为Java 17Spring Security、KafKa等也都宣布在后期版本最低需要Java 17 所以我们恶补一下Java 8-18的特性很有必要。

Java 8 新特性

Java 8 带来了大量的新特性。主要分为以下几个方面语言、它的编译器、库、工具和 JVMJava 虚拟机。
这个教程包含Java开发者经常面对的几类问题

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

感兴趣的话可以看下官网的描述
https://docs.oracle.com/en/java/javase/index.html

语言新特性

Lambda表达式和函数式接口

一个概念函数式接口只有一个方法的接口。
Lambda表达式也称为闭包是Java 8中最大和最令人期待的语言改变。
它允许我们将函数当成参数传递给某个方法或者把代码本身当作数据处理函数式开发者非常熟悉这些概念。很多JVM平台上的语言Groovy、Scala等从诞生之日就支持Lambda表达式但是Java开发者没有选择只能使用匿名内部类代替Lambda表达式。
Lambda的设计耗费了很多时间和很大的社区力量最终找到一种折中的实现方案可以实现简洁而紧凑的语言结构。最简单的Lambda表达式可由逗号分隔的参数列表、-> 符号和语句块组成。

示例1

Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

在上面这个代码中的参数e的类型是由编译器推理得出的你也可以显式指定该参数的类型例如

Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

示例2
如果Lambda表达式需要更复杂的语句块则可以使用花括号将该语句块括起来类似于Java中的函数体例如

Arrays.asList( "a", "b", "d" ).forEach( e -> {
    System.out.print( e );
    System.out.print( e );
} );

示例3
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 ) );

示例4
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表达式良好兼容考虑了很多方法于是产生了 函数接口 这个概念。
函数接口指的是只有一个函数的接口这样的接口可以隐式转换为Lambda表达式。java.lang.Runnablejava.util.concurrent.Callable是函数式接口的最佳例子。
在实践中函数式接口非常脆弱只要某个开发者在该接口中添加一个函数则该接口就不再是函数式接口进而导致编译失败。
为了克服这种代码层面的脆弱性并显式说明某个接口是函数式接口Java 8 提供了一个特殊的注解 @FunctionalInterfaceJava 库中的所有相关接口都已经带有这个注解了举个简单的函数式接口的定义

@FunctionalInterface
public interface Functional {
    void method();
}

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

接口默认方法和静态方法

Java 8使用两个新概念扩展了接口的含义默认方法和静态方法。
接口中可用定义默认方法
默认方法使得接口有点类似traits不过要实现的目标不一样。默认方法使得开发者可以在不破坏二进制兼容性的前提下往现存接口中添加新的方法即不强制那些实现了该接口的类也同时实现这个新加的方法。
默认方法和抽象方法之间的区别在于抽象方法需要实现而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写例子代码如下

private interface Defaulable {
    // Interfaces now allow default methods, the implementer may or 
    // may not implement (override) them.
    default String notRequired() { 
        return "Default implementation"; 
    }        
}
 
private static class DefaultableImpl implements Defaulable {
}
 
private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}

Defaulable接口使用关键字default定义了一个默认方法notRequired()。
DefaultableImpl类实现了这个接口同时默认继承了这个接口中的默认方法
OverridableImpl类也实现了这个接口但覆写了该接口的默认方法并提供了一个不同的实现。

接口中可用定义静态方法
Java 8带来的另一个有趣的特性是在接口中可以定义静态方法例子代码如下

private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier<Defaulable> supplier ) {
        return supplier.get();
    }
}

下面的代码片段整合了默认方法和静态方法的使用场景

public static void main( String[] args ) {
    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
    System.out.println( defaulable.notRequired() );
 
    defaulable = DefaulableFactory.create( OverridableImpl::new );
    System.out.println( defaulable.notRequired() );
}

这段代码的输出结果如下

Default implementation
Overridden implementation

由于JVM上的默认方法的实现在字节码层面提供了支持因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是给java.util.Collection接口添加新方法如stream()、parallelStream()、**forEach()和removeIf()**等等。
尽管默认方法有这么多好处但在实际开发中应该谨慎使用在复杂的继承体系中默认方法可能引起歧义和编译错误。如果你想了解更多细节可以参考官方文档

方法引用

方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用使得java类的构造方法看起来紧凑而简洁没有很多复杂的模板代码。
下面的例子中Car类是不同方法引用的例子可以帮助读者区分四种类型的方法引用。

public static class Car {
    public static Car create( final Supplier<Car> supplier ) {
        return supplier.get();
    }              
 
    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }
 
    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }
 
    public void repair() {   
        System.out.println( "Repaired " + this.toString() );
    }
}

第一种方法引用的类型是构造器引用语法是Class::new或者更一般的形式Class< T>::new。注意这个构造器没有参数。

final Car car = Car.create( Car::new );
final List<Car> cars = Arrays.asList( car );

第二种方法引用的类型是静态方法引用语法是Class::static_method。注意这个方法接受一个Car类型的参数。

cars.forEach( Car::collide );

第三种方法引用的类型是某个类的成员方法的引用语法是Class::method注意这个方法没有定义入参

cars.forEach( Car::repair );

第四种方法引用的类型是某个实例对象的成员方法的引用语法是instance::method。注意这个方法接受一个Car类型的参数

final Car police = Car.create( Car::new );
cars.forEach( police::follow );

运行上述例子可以在控制台看到如下输出Car实例可能不同

Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

重复注解

自从Java 5中引入注解以来这个特性开始变得非常流行并在各个框架和项目中被广泛使用。不过注解有一个很大的限制是在同一个地方不能多次使用同一个注解。Java 8打破了这个限制引入了重复注解的概念允许在同一个地方多次使用同一个注解。
在Java 8中使用**@Repeatable**注解定义重复注解实际上这并不是语言层面的改进而是编译器做的一个trick底层的技术仍然相同。可以利用下面的代码说明

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.class)注解修饰而Filters是存放Filter注解的容器编译器尽量对开发者屏蔽这些细节。这样Filterable接口可以用两个Filter注解注释这里并没有提到任何关于Filters**的信息。
另外反射API提供了一个新的方法getAnnotationsByType()可以返回某个类型的重复注解例如Filterable.class.getAnnoation(Filters.class)将返回两个Filter实例输出到控制台的内容如下所示

filter1
filter2

如果你希望了解更多内容可以参考官方文档

更好的类型推断

Java 8编译器在类型推断方面有很大的提升在很多场景下编译器可以推导出某个参数的数据类型从而使得代码更为简洁。例子代码如下

package com.javacodegeeks.java8.type.inference;
 
public class Value<T> {
    public static<T> T defaultValue() { 
        return null; 
    }
 
    public T getOrDefault(T value, T defaultValue ) {
        return ( value != null ) ? value : defaultValue;
    }
}

下列代码是 Value< String> 类型的应用

package com.javacodegeeks.java8.type.inference;
 
public class TypeInference {
    public static void main(String[] args) {
        final Value<String> value = new Value<>();
        value.getOrDefault("22", Value.defaultValue() );
    }
}

参数 Value.defaultValue() 的类型由编译器推导得出不需要显式指明。在Java 7中这段代码会有编译错误除非使用Value.defaultValue()。

拓宽注解的应用场景

Java 8拓宽了注解的应用场景。现在注解几乎可以使用在任何元素上局部变量、接口类型、超类和接口实现类甚至可以用在函数的异常定义上。下面是一些例子

package com.javacodegeeks.java8.annotations;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
 
public class Annotations {
    @Retention( RetentionPolicy.RUNTIME )
    @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
    public @interface NonEmpty {        
    }
 
    public static class Holder< @NonEmpty T > extends @NonEmpty Object {
        public void method() throws @NonEmpty Exception {            
        }
    }
 
    @SuppressWarnings( "unused" )
    public static void main(String[] args) {
        final Holder< String > holder = new @NonEmpty Holder< String >();        
        @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();        
    }
}

ElementType.TYPE_USERElementType.TYPE_PARAMETER 是Java 8新增的两个注解用于描述注解的使用场景。Java 语言也做了对应的改变以识别这些新增的注解。

编译器的新特性

参数名称

在运行时获得Java程序中方法的参数名称
为了在运行时获得Java程序中方法的参数名称老一辈的Java程序员必须使用不同方法例如Paranamer liberary。Java 8终于将这个特性规范化在语言层面使用反射API和 Parameter.getName()方法 和字节码层面使用新的javac编译器以及 -parameters 参数提供支持。

package com.javacodegeeks.java8.parameter.names;
 
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
 
public class ParameterNames {
    public static void main(String[] args) throws Exception {
        Method method = ParameterNames.class.getMethod("main", String[].class );
        for( final Parameter parameter: method.getParameters() ) {
            System.out.println( "Parameter: " + parameter.getName() );
        }
    }
}

在Java 8中这个特性是默认关闭的因此如果不带 -parameters 参数编译上述代码并运行则会输出如下结果

Parameter: arg0

如果带 -parameters 参数则会输出如下结果正确的结果

Parameter: args

如果你使用Maven进行项目管理则可以在 maven-compiler-plugin 编译器的配置项中配置**-parameters**参数

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
        <compilerArgument>-parameters</compilerArgument>
        <source>1.8</source>
        <target>1.8</target>
    </configuration>
</plugin>

官方库的新特性

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!

如果想了解更多的细节请参考官方文档
Optional源码

package java.util;

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * A container object which may or may not contain a non-null value.
 * If a value is present, {@code isPresent()} will return {@code true} and
 * {@code get()} will return the value.
 *
 * <p>Additional methods that depend on the presence or absence of a contained
 * value are provided, such as {@link #orElse(java.lang.Object) orElse()}
 * (return a default value if value not present) and
 * {@link #ifPresent(java.util.function.Consumer) ifPresent()} (execute a block
 * of code if the value is present).
 *
 * <p>This is a <a href="../lang/doc-files/ValueBased.html">value-based</a>
 * class; use of identity-sensitive operations (including reference equality
 * ({@code ==}), identity hash code, or synchronization) on instances of
 * {@code Optional} may have unpredictable results and should be avoided.
 *
 * @since 1.8
 */
public final class Optional<T> {
    /**
     * Common instance for {@code empty()}.
     */
    private static final Optional<?> EMPTY = new Optional<>();

    /**
     * If non-null, the value; if null, indicates no value is present
     */
    private final T value;

    /**
     * Constructs an empty instance.
     *
     * @implNote Generally only one empty instance, {@link Optional#EMPTY},
     * should exist per VM.
     * 
     * 无参构造
     */
    private Optional() {
        this.value = null;
    }

    /**
     * Returns an empty {@code Optional} instance.  No value is present for this
     * Optional.
     *
     * @apiNote Though it may be tempting to do so, avoid testing if an object
     * is empty by comparing with {@code ==} against instances returned by
     * {@code Option.empty()}. There is no guarantee that it is a singleton.
     * Instead, use {@link #isPresent()}.
     *
     * @param <T> Type of the non-existent value
     * @return an empty {@code Optional}
     * 
     *  返回一个空Optional实例。
     */
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }

    /**
     * Constructs an instance with the value present.
     *
     * @param value the non-null value to be present
     * @throws NullPointerException if value is null
     *
     * 有参构造
     */
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }

    /**
     * Returns an {@code Optional} with the specified present non-null value.
     *
     * @param <T> the class of the value
     * @param value the value to be present, which must be non-null
     * @return an {@code Optional} with the value present
     * @throws NullPointerException if value is null
     *
     * 返回一个具有指定当前非空值的Optional实例。
     */
    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

    /**
     * Returns an {@code Optional} describing the specified value, if non-null,
     * otherwise returns an empty {@code Optional}.
     *
     * @param <T> the class of the value
     * @param value the possibly-null value to describe
     * @return an {@code Optional} with a present value if the specified value
     * is non-null, otherwise an empty {@code Optional}
     *
     * 如果非空返回一个指定值Optional实例否则返回一个空Optional。
     */
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

    /**
     * If a value is present in this {@code Optional}, returns the value,
     * otherwise throws {@code NoSuchElementException}.
     *
     * @return the non-null value held by this {@code Optional}
     * @throws NoSuchElementException if there is no value present
     *
     * @see Optional#isPresent()
     *
     * 如果 this 中存在Optional值则返回该值否则抛出NoSuchElementException。
     */
    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    /**
     * Return {@code true} if there is a value present, otherwise {@code false}.
     *
     * @return {@code true} if there is a value present, otherwise {@code false}
     *
     * 如果存在值不为null则返回true否则返回false。
     */
    public boolean isPresent() {
        return value != null;
    }

    /**
     * If a value is present, invoke the specified consumer with the value,
     * otherwise do nothing.
     *
     * @param consumer block to be executed if a value is present
     * @throws NullPointerException if value is present and {@code consumer} is
     * null
     *
     * 如果存在值则使用该值调用指定的使用者否则不执行任何操作。
     */
    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }

    /**
     * If a value is present, and the value matches the given predicate,
     * return an {@code Optional} describing the value, otherwise return an
     * empty {@code Optional}.
     *
     * @param predicate a predicate to apply to the value, if present
     * @return an {@code Optional} describing the value of this {@code Optional}
     * if a value is present and the value matches the given predicate,
     * otherwise an empty {@code Optional}
     * @throws NullPointerException if the predicate is null
     *
     * 如果存在一个值并且该值与给定的谓词匹配则返回一个Optional描述该值的值否则返回一个空值Optional。
     */
    public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }

    /**
     * If a value is present, apply the provided mapping function to it,
     * and if the result is non-null, return an {@code Optional} describing the
     * result.  Otherwise return an empty {@code Optional}.
     *
     * @apiNote This method supports post-processing on optional values, without
     * the need to explicitly check for a return status.  For example, the
     * following code traverses a stream of file names, selects one that has
     * not yet been processed, and then opens that file, returning an
     * {@code Optional<FileInputStream>}:
     *
     * <pre>{@code
     *     Optional<FileInputStream> fis =
     *         names.stream().filter(name -> !isProcessedYet(name))
     *                       .findFirst()
     *                       .map(name -> new FileInputStream(name));
     * }</pre>
     *
     * Here, {@code findFirst} returns an {@code Optional<String>}, and then
     * {@code map} returns an {@code Optional<FileInputStream>} for the desired
     * file if one exists.
     *
     * @param <U> The type of the result of the mapping function
     * @param mapper a mapping function to apply to the value, if present
     * @return an {@code Optional} describing the result of applying a mapping
     * function to the value of this {@code Optional}, if a value is present,
     * otherwise an empty {@code Optional}
     * @throws NullPointerException if the mapping function is null
     *
     * 如果存在值则对其应用提供的映射函数如果结果为非 null则返回Optional描述结果。
     */
    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }

    /**
     * If a value is present, apply the provided {@code Optional}-bearing
     * mapping function to it, return that result, otherwise return an empty
     * {@code Optional}.  This method is similar to {@link #map(Function)},
     * but the provided mapper is one whose result is already an {@code Optional},
     * and if invoked, {@code flatMap} does not wrap it with an additional
     * {@code Optional}.
     *
     * @param <U> The type parameter to the {@code Optional} returned by
     * @param mapper a mapping function to apply to the value, if present
     *           the mapping function
     * @return the result of applying an {@code Optional}-bearing mapping
     * function to the value of this {@code Optional}, if a value is present,
     * otherwise an empty {@code Optional}
     * @throws NullPointerException if the mapping function is null or returns
     * a null result
     *
     * 如果存在值则将提供的Optional-bearing 映射函数应用于它返回该结果否则返回空 Optional。
     */
    public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

    /**
     * Return the value if present, otherwise return {@code other}.
     *
     * @param other the value to be returned if there is no value present, may
     * be null
     * @return the value, if present, otherwise {@code other}
     *
     * 对自身判断如果存在则返回自身否则返回other。
     * 对自身判断如果不为null则返回自身否则返回一个指定的值。
     */
    public T orElse(T other) {
        return value != null ? value : other;
    }

    /**
     * Return the value if present, otherwise invoke {@code other} and return
     * the result of that invocation.
     *
     * @param other a {@code Supplier} whose result is returned if no value
     * is present
     * @return the value if present otherwise the result of {@code other.get()}
     * @throws NullPointerException if value is not present and {@code other} is
     * null
     *
     * 如果存在则返回该值否则调用other并返回该调用的结果。
     */
    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }

    /**
     * Return the contained value, if present, otherwise throw an exception
     * to be created by the provided supplier.
     *
     * @apiNote A method reference to the exception constructor with an empty
     * argument list can be used as the supplier. For example,
     * {@code IllegalStateException::new}
     *
     * @param <X> Type of the exception to be thrown
     * @param exceptionSupplier The supplier which will return the exception to
     * be thrown
     * @return the present value
     * @throws X if there is no value present
     * @throws NullPointerException if no value is present and
     * {@code exceptionSupplier} is null
     *
     * 如果存在不为null则返回该值否则抛出由提供的供应商创建的异常。
     */
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }

    /**
     * Indicates whether some other object is "equal to" this Optional. The
     * other object is considered equal if:
     * <ul>
     * <li>it is also an {@code Optional} and;
     * <li>both instances have no value present or;
     * <li>the present values are "equal to" each other via {@code equals()}.
     * </ul>
     *
     * @param obj an object to be tested for equality
     * @return {code true} if the other object is "equal to" this object
     * otherwise {@code false}
     *
     * 判断某个其他对象是否“等于”此 Optional。 相等返回true不想等返回false
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (!(obj instanceof Optional)) {
            return false;
        }

        Optional<?> other = (Optional<?>) obj;
        return Objects.equals(value, other.value);
    }

    /**
     * Returns the hash code value of the present value, if any, or 0 (zero) if
     * no value is present.
     *
     * @return hash code value of the present value or 0 if no value is present
     *
     * 返回当前值的哈希码值如果有如果不存在值则返回 0零。
     */
    @Override
    public int hashCode() {
        return Objects.hashCode(value);
    }

    /**
     * Returns a non-empty string representation of this Optional suitable for
     * debugging. The exact presentation format is unspecified and may vary
     * between implementations and versions.
     *
     * @implSpec If a value is present the result must include its string
     * representation in the result. Empty and present Optionals must be
     * unambiguously differentiable.
     *
     * @return the string representation of this instance
     *
     * 返回此 Optional 适合调试的非空字符串表示形式。
     */
    @Override
    public String toString() {
        return value != null
            ? String.format("Optional[%s]", value)
            : "Optional.empty";
    }
}

Stream

新增的Stream APIjava.util.stream将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善以便开发者能够写出更加有效、更加简洁和紧凑的代码。
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之上的操作可分为中间操作和终止操作。详情可参考Stream API
中间操作会返回一个新的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 pointsall 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对软件开发的现代范式的响应。
Stream流与Lambda表达式、方法引用等结合使用效果还是比较不错的可以多加练习。

Date/Time API(JSR 310)

Java 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题甚至令开发者更加迷茫。
因为上面这些原因诞生了第三方库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

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

Nashorn JavaScript引擎

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

Base64

对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!

新的Base64API也支持URL和MINE的编码解码。
(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。

并行数组

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

package com.javacodegeeks.java8.parallel.arrays;
 
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
 
public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];        
 
        Arrays.parallelSetAll( arrayOfLong, 
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
            i -> System.out.print( i + " " ) );
        System.out.println();
 
        Arrays.parallelSort( arrayOfLong );        
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
}

上述这些代码使用**parallelSetAll()方法生成20000个随机数然后使用parallelSort()**方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。上述例子的代码输出的结果是

Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 
Sorted: 39 220 263 268 325 607 655 678 723 793

并发性

基于新增的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

新的Java工具

Nashorn引擎jjs

jjs是一个基于标准Nashorn引擎的命令行工具可以接受js源码并执行。例如我们写一个func.js文件内容如下

function f() { 
     return 1; 
}; 
 
print( f() + 1 );

可以在命令行中执行这个命令jjs func.js控制台输出结果是

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

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

JVM的新特性

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

Java 9 新特性

Java9 是Java8后一个比较大的更新包含新特性比较多此篇文章只总结下Java 9 版本的一些重要的新特性。并不完全。
Java 9 全部的新特性请看官网Java 平台标准版 Oracle JDK 9 中的新增功能
Java各个版本的文档入口Java平台标准版文档
Java各个版本下载https://jdk.java.net/archive/

模块化

Java 9 中的模块化是对 Java 的一次重大改进。但是模块化并不是最近才提出来的我们经常使用的 maven 构建工具就是典型的模块化构建工具。模块化不仅让模块命名清晰写出高内聚低耦合的代码更可以方便处理模块之间的调用关系。
module 是新增的Java代码访问权限级别每个module可以包含多个package。
通过module-info.java文件来声明该文件夹及其子文件夹为一个模块**exports **关键字可以用来控制模块内哪些包对外暴露。

 module store.api{
   exports com.dingtalk.store.api;
 }

使用module后即使包中的类是public的如果未通过exports显式导出其程序包则外部模块是不可调用的。
如果一个模块想要使用被另一个模块导出的package包中的类可以用requires关键字在其module-info.java文件中来导入读取目标模块的package包。

 module store.service {
   requires com.dingtalk.store.api;
 }

Java9 module 与Maven module 很像但功能完全不一样后者是作为依赖构建来方便管理应用代码而Java Module是在于安全性、访问性控制通过exports/requires 控制模块内需要暴露和依赖的具体包。

接口支持定义私有方法

在 Java 8 中增加了默认方法在 Java 9 中又增加了私有方法这时开始接口中不仅仅有了定义还具有了行为。我想这是出于代码构造上的考虑如果没有私有方法那么当多个默认方法的行为一样时就要写多个相同的代码。而有了私有方法事情就变得不一样了。
举个例子

public class Jdk9Interface {
    public static void main(String[] args) {
        ChinaPeople chinaPeople = new ChinaPeople();
        chinaPeople.sleep();
        chinaPeople.eat();
        chinaPeople.doXxx();
    }

}

class ChinaPeople implements People {
    @Override
    public void sleep() {
        System.out.println("躺着睡");
    }
}

interface People {
    void sleep();

    default void eat() {
        drink();
    }

    default void doXxx() {
        drink();
    }

    private void drink() {
        System.out.println("喝水");
    }
}

例子中的接口 people 中的 eat() 和 doXxx() 默认行为一致使用私有方法可以方便的抽取一个方法出来。
输出结果

躺着睡
喝水
喝水

集合工厂方法

在 Java 9 中为集合的创建增加了静态工厂创建方式也就是 of 方法通过静态工厂 of 方法创建的集合是只读集合里面的对象不可改变。并且不能存在 null 值对于 set 和 map 集合也不能存在 key 值重复。这样不仅线程安全而且消耗的内存也更小

// 工厂方法创建集合
List<String> stringList = List.of("a", "b", "c", "d");
Set<String> stringSet = Set.of("a", "b", "c", "d");
Map<String, Integer> stringIntegerMap = Map.of("key1", 1, "key2", 2, "key3", 3);
Map<String, Integer> stringIntegerMap2 = Map.ofEntries(Map.entry("key1", 1), Map.entry("key2", 2));

// 集合输出
System.out.println(stringList);
System.out.println(stringSet);
System.out.println(stringIntegerMap);
System.out.println(stringIntegerMap2);

输出

[a, b, c, d]
[d, a, c, b]
{key2=2, key1=1, key3=3}
{key2=2, key1=1}

这种只读集合在 Java 9 之前创建是通过 Collections.unmodifiableList 修改集合操作权限实现的。

List<String> arrayList = new ArrayList<>();
arrayList.add("CSDN");
arrayList.add("阿提说说");

// 设置为只读集合
arrayList = Collections.unmodifiableList(arrayList);

静态工厂 of 方法创建的集合还有一个特性就是工厂内部会自由复用已有实例或者创建新的实例所以应该避免对 of 创建的集合进行判等或者 haseCode 比较等操作。

// 工厂可以自由创建新的实例或者复用现有实例所以 使用 of 创建的集合避免 == 或者 hashCode 判断操作
List<String> stringList = List.of("a", "b", "c", "d");
List<String> stringList2 = List.of("a", "b", "c", "d");
System.out.println(stringList.hashCode());
System.out.println(stringList2.hashCode());
// 输出结果
// 3910595
// 3910595

增强流StreamAPI

Stream 流操作自从 Java 8 引入以来一直广受好评。
当然学习 Stream 之前要先学习 Lambda 也是Java 8的内容。
在 Java 9 中又对 Stream 进行了增强主要增加了 4 个新的操作方法dropWhiletakeWhileofNullableiterate。
1、takeWhile: 从头开始筛选遇到不满足的就结束

// takeWhile ,从头开始筛选遇到不满足的就结束了
List<Integer> list1 = List.of(1, 2, 3, 4, 5);
List<Integer> listResult = list1.stream().takeWhile(x -> x < 3).collect(Collectors.toList());
System.out.println(listResult);

// takeWhile ,从头开始筛选遇到不满足的就结束
List<Integer> list2 = List.of(1, 2, 3, 4, 3, 0);
List<Integer> listResult2 = list2.stream().takeWhile(x -> x < 3).collect(Collectors.toList());
System.out.println(listResult2);

----------------------------------
输出结果
[1, 2]
[1, 2]

2、dropWhile: 从头开始删除遇到不满足的就结束

// dropWhile ,从头开始删除遇到不满足的就结束
List<Integer> list1 = List.of(1, 2, 3, 4, 5);
List<Integer> listResult = list1.stream().dropWhile(x -> x < 3).collect(Collectors.toList());
System.out.println(listResult);

// dropWhile ,从头开始删除遇到不满足的就结束
List<Integer> list2 = List.of(1, 2, 3, 4, 3, 0);
List<Integer> listResult2 = list2.stream().dropWhile(x -> x < 3).collect(Collectors.toList());
System.out.println(listResult2);

----------------------------------
输出结果
[3, 4, 5]
[3, 4, 3, 0]

3、ofNullable: 创建支持全 null 的 Stream

Stream<Integer> stream = Stream.of(1, 2, null);
stream.forEach(System.out::print);
System.out.println();

// 空指针异常
// stream = Stream.of(null);
stream = Stream.ofNullable(null);
stream.forEach(System.out::print);

----------------------------------
输出结果
12null

4、iterate: 可以重载迭代器。

IntStream.iterate(0, x -> x < 10, x -> x + 1).forEach(System.out::print);

----------------------------------
输出结果
0123456789

在 Stream 增强之外还增强了 Optional Optional 增加了可以转换成 Stream 的方法。

Stream<Integer> s = Optional.of(1).stream();
s.forEach(System.out::print);

HTTP / 2 Client

Java 9 内置了新的 HTTP/2 客户端请求更加方便。
随便访问一个不存在的网页。

HttpClient client = HttpClient.newHttpClient();
URI uri = URI.create("http://www.baidu.com");
HttpRequest req = HttpRequest.newBuilder(uri).header("User-Agent", "Java").GET().build();
HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandler.asString());
String body = resp.body();
System.out.println(body);

输出得到的结果这里是这个网站的报错信息。

There is no method xxxAction in ApiController

Java REPL - JShell

交互式的编程环境在其他语言如 Python 上早就有了而 Java 上的交互式语言只到 Java 9 才出现。交互式的编程可以让开发者在输入代码的时候就获取到程序的运行结果而不用像之前一样新建文件、创建类、导包、测试一系列流程。
JShell 中支持 tab 补全代码以及自动添加分号下面通过一个例子演示 JShell 的使用。
1、进入 JShell. 查看帮助文档

C:\Users>jshell
|  欢迎使用 JShell -- 版本 9
|  要大致了解该版本, 请键入: /help intro
jshell> /help
|  键入 Java 语言表达式, 语句或声明。
|  或者键入以下命令之一:
|  /list [<名称或 id>|-all|-start]
|       列出您键入的源
|  /edit <名称或 id>
|       编辑按名称或 id 引用的源条目
|  /drop <名称或 id>
|       删除按名称或 id 引用的源条目
|  /save [-all|-history|-start] <文件>
|       将片段源保存到文件。
|  /open <file>
|       打开文件作为源输入
|  /vars [<名称或 id>|-all|-start]
|       列出已声明变量及其值
|  /methods [<名称或 id>|-all|-start]
|       列出已声明方法及其签名
|  /types [<名称或 id>|-all|-start]
|       列出已声明的类型
|  /imports
|       列出导入的项
|  /exit
|       退出 jshell
|  /env [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>] ...
|       查看或更改评估上下文
|  /reset [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>]...
|       重启 jshell
|  /reload [-restore] [-quiet] [-class-path <路径>] [-module-path <路径>]...
|       重置和重放相关历史记录 -- 当前历史记录或上一个历史记录 (-restore)
|  /history
|       您键入的内容的历史记录
|  /help [<command>|<subject>]
|       获取 jshell 的相关信息
|  /set editor|start|feedback|mode|prompt|truncation|format ...
|       设置 jshell 配置信息
|  /? [<command>|<subject>]
|       获取 jshell 的相关信息
|  /!
|       重新运行上一个片段
|  /<id>
|       按 id 重新运行片段
|  /-<n>
|       重新运行前面的第 n 个片段
|
|  有关详细信息, 请键入 '/help', 后跟
|  命令或主题的名称。
|  例如 '/help /list' 或 '/help intro'。主题:
|
|  intro
|       jshell 工具的简介
|  shortcuts
|       片段和命令输入提示, 信息访问以及
|       自动代码生成的按键说明
|  context
|       /env /reload 和 /reset 的评估上下文选项

jshell>

2、定义一个变量a = 10遍历从 0 到 a 的数字

jshell> int a =10;
a ==> 10
jshell> for(int i=0;i<a;i++){System.out.println(i);}
0
1
2
3
4
5
6
7
8
9

3、定义一个集合赋值 1,2,3,4,5。然后输出集合

jshell> List list = List.of(1,2,3,4,5);
list ==> [1, 2, 3, 4, 5]
jshell> list
list ==> [1, 2, 3, 4, 5]

4、查看输入过的代码

jshell> /list
   1 : int a =10;
   2 : for(int i=0;i<a;i++){System.out.println(i);}
   3 : List list = List.of(1,2,3,4,5);
   4 : list

5、列出导入的包

jshell> /imports
|    import java.io.*
|    import java.math.*
|    import java.net.*
|    import java.nio.file.*
|    import java.util.*
|    import java.util.concurrent.*
|    import java.util.function.*
|    import java.util.prefs.*
|    import java.util.regex.*
|    import java.util.stream.*

6、将代码保存到文件并退出

jshell> /save d:/JShell.java
jshell> /exit
  再见

在 D 盘看到的保存的代码片段。

JVM 调优的新特性

第一个删除 JDK 8 中已弃用的垃圾收集器 (GC) 组合

  • 这意味着以下 GC 组合不再存在

    • DefNew + CMS
    • ParNew + SerialOld
    • 增量CMS
  • 并发标记扫描 (CMS) 的“前台”模式也已被删除。以下命令行标志已被删除

    • -Xincgc
    • -XX:+CMSIncrementalMode
    • -XX:+UseCMSCompactAtFullCollection
    • -XX:+CMSFullGCsBeforeCompaction
    • -XX:+UseCMSCollectionPassing
  • 命令行标志-XX:+UseParNewGC不再有效。ParNew 只能与 CMS 一起使用而 CMS 需要 ParNew。因此该-XX:+UseParNewGC标志已被弃用并且可能会在未来的版本中被删除。

第二个使 G1 成为默认垃圾收集器

  • 使垃圾优先 (G1) 成为 32 位和 64 位服务器配置上的默认垃圾收集器 (GC)。对于大多数用户来说使用低暂停收集器例如 G1比以前默认的面向吞吐量的收集器例如 Parallel GC提供更好的整体体验。
  • 增强垃圾优先 (G1) 垃圾收集器以自动确定几个重要的内存回收设置。以前必须手动设置这些设置以获得最佳结果。此外修复了 G1 垃圾收集器的可用性、确定性和性能问题。
  • 请参阅Java 平台中的 Garbage-First Garbage Collector 标准版 HotSpot 虚拟机垃圾收集调优指南

第三个弃用并发标记扫描 (CMS) 垃圾收集器

  • 弃用并发标记扫描 (CMS) 垃圾收集器。-XX:+UseConcMarkSweepGC使用该选项在命令行上请求时会发出警告消息。Garbage-First (G1) 垃圾收集器旨在替代 CMS 的大多数用途。

其他更新

Java 9 中增加或者优化的功能远不止这些上面只是列举了常用的一些新特性更多的新特性如

  • 不能使用下划线 _ 作为变量名因为它是一个关键字。
  • Javadoc 支持 HTML5 并且支持搜索功能。
  • Nashorn 引擎升级更好的支持 Javascript.
  • String 存储结构变更从 char -> byte
  • 多Jdk版本共存jar在同一个Jar包可以包含多个Java版本的class文件在不同Jdk环境下使用对应该 jdk 版本的 jar。这对算是用户很友好的功能

新特性很多感兴趣的可以自己了解下。
Java 9 全部的新特性请看官网[Java 平台标准版 Oracle JDK 9 中的新增功能](Java 平台标准版 Oracle JDK 9 中的新增功能)

Java 10 新特性

自从 Java 9 开始Oracle 调整了 Java 版本的发布策略不再是之前的 N 年一个大版本取而代之的是 6 个月一个小版本三年一个大版本这样可以让 Java 的最新改变迅速上线而小版本的维护周期缩短到下个版本发布之前大版本的维护周期则是 3 年之久。而 10 就是这么一个小版本因为 Java 的后续版本基本都会包含之前新特性所以还是把 Java 10 带来的改变单独写一写不全。
Java 10 全部的新特性请看官网JDK 10 发行说明
Java各个版本的文档入口Java平台标准版文档
Java各个版本下载https://jdk.java.net/archive/

基于时间的版本号

就像上面说的Java 调整了发布策略为了适应这种发布节奏随着改变的还有 Java 版本号的记录方式。
版本号的新模式是 F E A T U R E . FEATURE. FEATURE.INTERIM. U P D A T E . UPDATE. UPDATE.PATCH

  • $FEATURE 基于发布版本如 Java 10 的 10 。
  • $INTERIM 问题修复和功能增强时 + 1默认是 0 。
  • $UPDATE 在进行兼容更新修复新功能安全问题时 +1。
  • $PATCH 特殊问题修复时 +1。

查看自己的 Java 10 版本。

$ java -version
java version "10.0.1" 2018-04-17
Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)

局部类型推断

JEP 286 提案让 Java 增加了局部类型推断Local-Variable Type Inference功能这让 Java 可以像 Js 里的 var 或者其他语言的 auto 一样可以自动推断数据类型。这其实只是一个新的语法糖底层并没有变化在编译时就已经把 var 转化成具体的数据类型了但是这样可以减少代码的编写。
你可以像下面这样使用 var 语法。

var hashMap = new HashMap<String, String>();
hashMap.put("CSDN","阿提说说");
var string = "hello java 10";
var stream = Stream.of(1, 2, 3, 4);
var list = new ArrayList<String>();

如果你反编译编译后的这段代码你会发现还是熟悉的代码片段。

HashMap<String, String> hashMap = new HashMap();
hashMap.put("微信", "wlw");
String string = "hello java 10";
Stream<Integer> stream = Stream.of(1, 2, 3, 4);
ArrayList<String> list = new ArrayList();

var 看似好用其实也有很多限制官方介绍了 var 只能用于下面的几种情况。

  1. 仅限带有初始化的程序的局部变量。
  2. for 循环或者增强for 循环中。
  3. for 循环中的声明。
public static void testVar() {
    // 情况1没有初始化会报错
    // var list;
    var list = List.of(1, 2, 3, 4);
  
    // 情况2
    for (var integer : list) {
        System.out.println(integer);
    }
  
    // 情况3
    for (var i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }
}

尽管对 var 的使用场景增加了很多限制但在实际使用时你还是要注意就像下面的代码你可能一眼并不能看出 result 的数据类型。

var query = "xxx";
var result = dbUtil.executeQuery(query);

基于 Java 的 JIT 编译器实验性

这个功能让基于 Java 开发的 JIT 编译器 **Graal **结合 Java 10 用在 Linux /x64 平台上这是一个实验性的 JIT 编译器有人说这也是 Java 10 中最具有未来感的引入。Graal 其实在 Java 9 中就已经引入了它带来了 Java 中的 AOT Ahead Of Time编译还支持多种语言如 Js、Python、Ruby、R、以及其他基于 JVM 如 Java、Kotlin的和基于 LLVM 如 C、C++的语言。
想切换到 Graal 可以使用下面的 jvm 参数。

-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler

这里面有一点我觉得很有意思看这个图。
image.png
这就很有意思了Graal 是 Java 语言编写的用 Java 编写的编译器然后用来将 Java 字节码编译机器代码。
Graal 官网https://www.graalvm.org/(opens new window)

类数据共享

JVM 启动时有一步是需要在内存中加载类而如果有多个 jar加载第一个 jar 的速度是最慢的。这就延长了程序的启动时间为了减少这个时间Java 10 引入了应用程序类数据共享CDS机制它可以把你想共享的类共享在程序之间使不同的 Java 进程之间共享这个类来减少这个类占用的空间以及加载速度。

G1并行全GC

早在 Java 9 时就已经引入了 G1 垃圾收集器G1 的优点很多。而在 Java 10 中还是做了小小调整当 G1 的并发收集线程不能快速的完成全 GC 时就会自动切换到并行收集这可以减少在最坏情况下的 GC 速度。

Unicode 语言标签扩展

这个提案让 JDK 实现了最新的 LDML 规范 (opens new window)中指定的更多的扩展。
主要增加了下面几个扩展方法。

java.time.temporal.WeekFields::of
java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}
java.util.Currency::getInstance
java.util.Locale::getDisplayName
java.util.spi.LocaleNameProvider
java.text.DateFormat::get*Instance
java.text.DateFormatSymbols::getInstance
java.text.DecimalFormatSymbols::getInstance
java.text.NumberFormat::get*Instance
java.time.format.DateTimeFormatter::localizedBy
java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern
java.time.format.DecimalStyle::of

测试

Currency chinaCurrency = Currency.getInstance(Locale.CHINA);
Currency usCurrency = Currency.getInstance(Locale.US);
System.out.println("本地货币" + chinaCurrency);
System.out.println("US.货币" + usCurrency);

String displayName = Locale.getDefault().getDisplayName();
String displayLanguage = Locale.getDefault().getDisplayLanguage();
String displayCountry = Locale.getDefault().getDisplayCountry();
System.out.println("本地名称" + displayName);
System.out.println("本地语言" + displayLanguage);
System.out.println("本地国家" + displayCountry);
int firstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek();
System.out.println("本地每周第一天" + firstDayOfWeek);

结果

本地货币CNY
US.货币USD
本地名称中文 (中国)
本地语言中文
本地国家中国
本地每周第一天1

API更新

Java 10 删除了部分 API也增加了一些实用方法。比如可以通过 Collection.copyOf 复制得到一个不可改变集合即使原来的集合元素发生了变化也不会有影响。

var list = new ArrayList<String>();
list.add("wechat");
list.add("wlw");
List<String> copyList = List.copyOf(list);
list.add("test");
System.out.println(copyList);
// result
// [wechat, wn8398]

也为 Optional 增加了一个新的方法 orElseThrow。调用这个方法也可以获取到 optional 中的 value , 但是如果 value 为 null 就会抛出异常。
另外在 Stream 最后收集数据的时候Collectors 可以直接指定收集的集合为不可变集合像下面这样。

list.stream().collect(Collectors.toUnmodifiableList());
list.stream().collect(Collectors.toUnmodifiableSet());

其他更新

Java 10 的更新内容不止这些上面只是列举了常用的以及比较有意思的新特性。还有部分更新如
1、JEP 312Thread-Local HandshakesJVM 内部功能可以提高 JVM 性能。
2、JEP 313删除了 javah 工具说是删除其实功能已经包含在 Java 8 中的 javac 里。
3、JEP 316让 JVM 可以在备用的存储设备如 NV-DIMM上分配堆内存而不用更改程序代码。
4、JEP 319在 JDK 中提供一组默认的根证书颁发机构CA证书。

Java 10 全部的新特性请看官网JDK 10 发行说明

Java 11 新特性

Java 11 是 Java 8 之后的第一个 LTS 版本但是也自从 Java 11 开始 Oracle JDK 不再可以免费的用于商业用途当然如果你是个人使用或者是使用 Open JDK 那么还是可以免费使用的。

Java 11 全部的新特性请看官网JDK 11 发行说明
Java各个版本的文档入口Java平台标准版文档
Java各个版本下载https://jdk.java.net/archive/

String API改动

字符串绝对是 Java 中最常用的一个类了String 类的方法使用率也都非常的高在 Java 11 中又为 String 类带来了一系列的好用操作。

  1. isBlank()判空
// 判空blank里我放入了全角空格半角空格TAB
String blank = "    ";
System.out.println(blank.isBlank());

// 输出
// true
  1. lines()分割获取字符串流
// lines 返回一个 Stream
String line = "a\nb\nc";
Stream<String> lines = line.lines();

// 使用 lambda 遍历
lines.forEach(System.out::println);

// 输出
// a
// b
// c
  1. repeat()复制字符串
// 复制字符串
String repeat = "CSDN/WX-阿提说说";
String repeat3 = repeat.repeat(3);
System.out.println(repeat3);

// 输出
// CSDN/WX-阿提说说CSDN/WX-阿提说说CSDN/WX-阿提说说
  1. strip()去除前后空白字符
// 去除前后空白
String strip = "     https://www.baidu.com  ";
System.out.println("==" + strip.trim() + "==");
// 去除前后空白字符如全角空格TAB
System.out.println("==" + strip.strip() + "==");
// 去前面空白字符如全角空格TAB
System.out.println("==" + strip.stripLeading() + "==");
// 去后面空白字符如全角空格TAB
System.out.println("==" + strip.stripTrailing() + "==");

// 输出
// ==  https://www.baidu.com  ==
// ==https://www.baidu.com==
// ==https://www.baidu.com  ==
// ==     https://www.baidu.com==

File API改动

读写文件变得更加方便

// 创建临时文件
Path path = Files.writeString(Files.createTempFile("test", ".txt"),  "https://www.baidu.com");

System.out.println(path);
// 读取文件
String s = Files.readString(path);
System.out.println(s);

// 结果
//C:\Users\ADMINI~1\AppData\Local\Temp\test10960104919895436673.txt
//https://www.baidu.com

HTTP Client

在 Java 11 中 Http Client API 得到了标准化的支持。且支持 HTTP/1.1 和 HTTP/2 也支持 websockets。
你可以像这样发起一个请求:

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://www.hao123.com"))
        .build();
// 异步
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body)
        .thenAccept(System.out::println)
        .join();

// 同步
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

更多的如同步异步请求并发访问设置代理等方式可以参考 OpenJDK 官方文档
http://openjdk.java.net/groups/net/httpclient/recipes-incubating.html(opens new window)

Lambda 局部变量推断

在 Java 10 中引入了 var 语法可以自动推断变量类型。在 Java 11 中这个语法糖可以在 Lambda 表达式中使用了。

var hashMap = new HashMap<String, Object>();
hashMap.put("CSDN", "阿提说说");
hashMap.put("website", "https://blog.csdn.net/weixin_40972073");
hashMap.forEach((var k, var v) -> {
    System.out.println(k + ": " + v);
});
//输出
//website: https://blog.csdn.net/weixin_40972073
//CSDN: 阿提说说

这里需要注意的是(var k,var v) 中k 和 v 的类型要么都用 var 要么都不写要么都写正确的变量类型。而不能 var 和其他变量类型混用。
image.png

单命令运行Java

自从学习 Java 的第一天我们就知道运行一个 Java 文件要先用 javac 命令编译再用 java 命令运行而现在只要一个 java 命令就可以运行了。

public class MainTest {
    public static void main(String[] args) {
        System.out.println("CSDN: 阿提说说");
    }
}

//java -Dfile.encoding=UTF-8  MainTest.java
//CSDN: 阿提说说

免费的飞行记录器

商业版 JDK 中一直有一款低开销的事件信息收集工具也就是飞行记录器Java Flight Recorder它可以对 JVM 进行检查、分析、记录等。当出现未知异常时可以通过记录进行故障分析。这个好用的工具在 Java 11 中将开源免费。所有人都可以使用这个功能了。

其他更新

  1. JEP 309 - 添加动态文件常量。
  2. JEP 318 - 添加 Epsilon 垃圾收集器。
  3. JEP 320 - 删除 Java EE 和 corba 模块java.xml.ws, java.xml.bind, java.activation, java.xml.ws.annotation, java.corba, java.transaction, java.se.ee, jdk.xml.ws, jdk.xml.bind。
  4. JEP 329 - 增加加密算法 chacha20,poly1305 的实现。
  5. JEP 333 - 引入实验性的 ZGC 垃圾收集器保证停摆时间不会超过 10ms。
  6. JEP 335 - 废弃 Nashorn JavaScript 引擎

Java 11 全部的新特性请看官网JDK 11 发行说明

Java 12 新特性

Java 12 早在 2019 年 3 月 19 日发布它不是一个长久支持LTS版本。此篇文章写一下部分Java 12的新特性。
Java 12 全部的新特性请看官网JDK 12 发行说明
Java各个版本的文档入口Java平台标准版文档
Java各个版本下载https://jdk.java.net/archive/

Switch 表达式 改进

在 Java 12 中对 Switch 表达式的写法进行了改进虽然是一个语法糖的改进也让 Switch 的代码编写变得更加优雅。先看一下在 Java 12 之前的 Switch 的写法。
由于 Switch 表达式在 Java 12 中并不是一个正式发布的功能还处于预览测试阶段所以想要使用 Java 12 去编译运行就需要打开功能预览参数当然如果你使用的是 Java 14 以及更高版本就可以直接跳过这个部分了。

# 编译时
./bin/javac --enable-preview -source 12 ./Xxx.java
# 运行时
./bin/java --enable-preview Xxx

Java 12以前的switch写法

// 通过传入月份输出月份所属的季节
public static void switchJava12Before(String day) {
    switch (day) {
        case "march":
        case "april":
        case "may":
            System.out.println("春天");
            break;
        case "june":
        case "july":
        case "august":
            System.out.println("夏天");
            break;
        case "september":
        case "october":
        case "november":
            System.out.println("秋天");
            break;
        case "december":
        case "january":
        case "february":
            System.out.println("冬天");
            break;
    }
}

Java 12的写法

public static void switchJava12(String day) {
    switch (day) {
        case "march", "april", "may"            -> System.out.println("春天");
        case "june", "july", "august"           -> System.out.println("夏天");
        case "september", "october", "november" -> System.out.println("秋天");
        case "december", "january", "february"  -> System.out.println("冬天");
    }
}

另外还可使用返回值赋值

String season = switch (day) {
    case "march", "april", "may"            -> "春天";
    case "june", "july", "august"           -> "夏天";
    case "september", "october", "november" -> "秋天";
    case "december", "january", "february"  -> "冬天";
    default -> {
      //throw new RuntimeException("day error")
        System.out.println("day error");
        break "day error";
    }
};
System.out.println("当前季节是:" + season);

文件对比 Files.mismatch

在 Java 中对于文件的操作已经在 Java 11 中进行了一次增强这次 Java 12 又带来了文件对比功能。
对比两个文件如果内容一致会返回 -1 如果内容不同会返回不同的字节开始位置。

// 创建两个文件
Path pathA = Files.createFile(Paths.get("a.txt"));
Path pathB = Files.createFile(Paths.get("b.txt"));

// 写入相同内容
Files.write(pathA,"abc".getBytes(), StandardOpenOption.WRITE);
Files.write(pathB,"abc".getBytes(), StandardOpenOption.WRITE);
long mismatch = Files.mismatch(pathA, pathB);
System.out.println(mismatch);

// 追加不同内容
Files.write(pathA,"123".getBytes(), StandardOpenOption.APPEND);
Files.write(pathB,"321".getBytes(), StandardOpenOption.APPEND);
mismatch = Files.mismatch(pathA, pathB);
System.out.println(mismatch);

// 删除创建的文件
pathA.toFile().deleteOnExit();
pathB.toFile().deleteOnExit();

// RESULT
// -1
// 3

Compact Number

简化的数字格式可以直接转换数字显示格式比如 1000 -> 1K1000000 -> 1M 。

System.out.println("Compact Formatting is:");
NumberFormat upvotes = NumberFormat.getCompactNumberInstance(new Locale("en", "US"), Style.SHORT);

System.out.println(upvotes.format(100));
System.out.println(upvotes.format(1000));
System.out.println(upvotes.format(10000));
System.out.println(upvotes.format(100000));
System.out.println(upvotes.format(1000000));

// 设置小数位数
upvotes.setMaximumFractionDigits(1);
System.out.println(upvotes.format(1234));
System.out.println(upvotes.format(123456));
System.out.println(upvotes.format(12345678));

输出

100
1K
10K
100K
1M
1.2K
123.5K
12.3M

JVM 相关更新

Shenandoah 垃圾收集器
Java 12 增加了 Shenandoah 一个低停顿的垃圾收集器它可以和 Java 应用程序中的执行线程同时进行用来收集垃圾进行内容回收这样就可以让停顿时间更少。
更多关于 Shenandoah 垃圾收集器的介绍可以查看文档Shenandoah GC 介绍 (opens new window)。
ZGC 并发类卸载
Z 垃圾收集器现在支持类卸载通过卸载不使用的类来释放这些类相关的数据结构从而减少应用程序的总体占用空间。因为是并发执行所以不会停止 Java 应用程序线程的执行也因此对 GC 的暂停时间影响微乎其微。默认情况下启用此功能但可以使用命令行选项禁用** -XX:-ClassUnloading**。
JVM 常量 API
在包** java.lang.invoke.constant** 中定义了一系列的基于值的符号引用可以用来描述各种可加载常量。可以更容易的对关键类文件和运行时构建的名义描述进行建模特别是对那些从常量池中加载的常量也让开发者可以更简单标准的处理可加载常量。
默认使用类数据共享CDS
这已经不是 JDK 第一次改进 CDSClass Data Sharing 功能了CDS 可以让 JVM 在同一台机器或虚拟机上启动多个应用的速度速度大大增加。原理是在启动应用时共享一些类加载信息这样启动新进程时就可以使用共享的数据。在 Java 12 之前此功能需要手动开启Java 12 调整为默认开启。
微基准套件
Java 12 中添加一套新的基于 JMH 的基本的微基准测试套件。
JMH 的使用可以参考文章 JMH - Java 代码性能测试的终极利器 (opens new window)。

其他更新

在 Java 11 支持了 Unicode 10 之后 Java 12 支持了 Unicode 11支持操作更多的表情、符号。

Java 13 新特性

Java 13 早在 2019 年 9 月就已经发布虽然不是长久支持版本但是也带来了不少新功能。此篇文章写一下部分Java 13的新特性。
Java 13 全部的新特性请看官网JDK 13 发行说明
Java各个版本的文档入口Java平台标准版文档
Java各个版本下载https://jdk.java.net/archive/

Switch 表达式 (二次预览)

在Java 12 中对 Switch 进行了一次增强这次又对Switch进行了增强。
在 Java 13 中又对 switch 表达式进行了增强增加了 yield 关键词用于返回值相比 break 语义更加明确了。

public static String switchJava13(String month) {
    return switch (month) {
        case "march", "april", "may":
            yield "春天";
        case "june", "july", "august":
            yield "夏天";
        case "september", "october", "november":
            yield "秋天";
        case "december", "january", "february":
            yield "冬天";
        default:
            yield "month error";
    };
}

动态 CDS 存档

JVM 启动时有一步是需要在内存中加载类而如果有多个 jar加载第一个 jar 的速度是最慢的。这就延长了程序的启动时间为了减少这个时间Java 10 引入了应用程序类数据共享CDS机制它可以把你想共享的类共享在程序之间使不同的 Java 进程之间共享这个类来减少这个类占用的空间以及加载速度。不过 Java 10 中使用这个功能的步骤比较繁琐。
而 Java 13 中的 AppCDS允许 Java 应用在程序执行结束时如果 JVM 没有崩溃进行动态存档存储的内容包括所有加载的应用类型类和使用的类库这些存储的类库本来并不存在于默认的 CDS 存档中。使用这个功能非常简单只需要在程序启动时增加启动参数 。

# ArchiveClassesAtExit程序结束时动态存档
bin/java -XX:ArchiveClassesAtExit=hello.jsa -cp hello.jar Hello
# SharedArchiveFile使用指定存档启动
bin/java -XX:SharedArchiveFile=hello.jsa -cp hello.jar Hello

ZGC归还未使用的内存 (实验性)

在 Java 13 之前ZGC 虽然在清理内存时导致的停顿时间非常少但是即使内存已经长时间没有使用ZGC 也不会将内存返还给操作系统这对那些十分关注内存占用的应用程序非常不友好。
比如

  • 资源按使用量付费的云上容器环境。
  • 应用虽然长时间闲置但是占用了内存导致运行的其他程序内存紧张。

而新增的这个功能可以让 ZGC 归还长时间没有使用的内存给操作系统这对某些用户来说十分友好。

重新实现 Socket API

java.net.Socket 和 java.net.ServerSocket 类早在 Java 1.0 时就已经引入了它们的实现的 Java 代码和 C 语言代码的混合维护和调试都十分不易而且这个实现还存在并发问题有时候排查起来也很困难。
因此在 Java 13 中引入了新的实现方式使用了新的实现 NioSocketImpl 来代替老旧的 PlainSocketImpl 实现。虽然功能相同但是老的方式在当前以及未来几个版本内不会删除用户随时可以通过 -Djdk.net.usePlainSocketImpl 参数切换回老的实现方式以兼容意外情况。

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Test {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(8000)){
            boolean running = true;
            while(running){
                Socket clientSocket = serverSocket.accept();
                //do something with clientSocket
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用 Java 13 运行通过参数 -XX:+TraceClassLoading 追踪加载的类日志中可以看到 NioSocketImpl。

➜  develop ./jdk-13.0.2.jdk/Contents/Home/bin/java -XX:+TraceClassLoading Test.java | grep SocketImpl
[0.699s][info   ][class,load] java.net.SocketImpl source: jrt:/java.base
[0.699s][info   ][class,load] java.net.SocketImpl$$Lambda$173/0x0000000800c37440 source: java.net.SocketImpl
[0.702s][info   ][class,load] sun.net.PlatformSocketImpl source: jrt:/java.base
[0.702s][info   ][class,load] sun.nio.ch.NioSocketImpl source: jrt:/java.base
[0.713s][info   ][class,load] sun.nio.ch.NioSocketImpl$FileDescriptorCloser source: jrt:/java.base

但在 Java 12 并不是 NioSocketImpl

➜  develop ./jdk-12.0.2.jdk/Contents/Home/bin/java -XX:+TraceClassLoading Test.java | grep SocketImpl
[0.665s][info   ][class,load] java.net.SocketImpl source: jrt:/java.base
[0.665s][info   ][class,load] java.net.AbstractPlainSocketImpl source: jrt:/java.base
[0.665s][info   ][class,load] java.net.PlainSocketImpl source: jrt:/java.base
[0.665s][info   ][class,load] java.net.SocksSocketImpl source: jrt:/java.base
[0.666s][info   ][class,load] java.net.AbstractPlainSocketImpl$1 source: jrt:/java.base

文本块 (预览)

在这之前如果我们把一个 JSON 赋值给字符串

String content = "{\n"
    + "    \"upperSummary\": null,\n"
    + "    \"sensitiveTypeList\": null,\n"
    + "    \"gmtModified\": \"2022-08-23 10:50:09\",\n"
    + "    \"lowerGraph\": null,\n"
    + "    \"signature\": \"\",\n"
    + "    \"appName\": \"xxx\",\n"
    + "    \"lowerSummary\": null,\n"
    + "    \"gmtCreate\": \"2022-08-23 10:50:09\",\n"
    + "    \"type\": \"CALL\",\n"
    + "    \"name\": \"xxxx\",\n"
    + "    \"subType\": \"yyy\",\n"
    + "    \"id\": 1,\n"
    + "    \"projectId\": 1,\n"
    + "    \"status\": 1\n"
    + "}";

终于不用写丑陋的长字符串了从 Java 13 开始你可以使用文本块的方式定义字符串了。

String content2 = """
        {
        "upperSummary": null,
        "sensitiveTypeList": null,
        "gmtModified": "2022-08-23 10:50:09",
        "lowerGraph": null,
        "signature": "",
        "appName": "xxx",
        "lowerSummary": null,
        "gmtCreate": "2022-08-23 10:50:09",
        "type": "CALL",
        "name": "xxxx",
        "subType": "yyy",
        "id": 1,
        "projectId": 1,
        "status": 1
    }
                 """;

不过这是一个预览功能如果你要是在 Java 13 中使用需要手动开启预览功能这个功能在 Java 15 中正式发布。

Java 14 新特性

Java 14早在 2019 年 9 月就已经发布虽然不是长久支持版本但是也带来了不少新功能。此篇文章写一下部分Java 14的新特性。
Java 14全部的新特性请看官网JDK 14 发行说明
Java各个版本的文档入口Java平台标准版文档
Java各个版本下载https://jdk.java.net/archive/

instanceof 类型判断预览

在 Java 14 之前使用 instanceof 进行类型判断之后需要进行对象类型转换后才能使用。

public class Java14BeaforInstanceof {

    public static void main(String[] args) {
        Object obj = new ArrayList<>();
        if (obj instanceof ArrayList) {
            ArrayList list = (ArrayList)obj;
            list.add("www.baidu.com");
        }
        System.out.println(obj);
    }
}

而在 Java 14 中可以在判断类型时指定变量名称进行类型转换方便了使用。

public class Java14Instanceof {
    public static void main(String[] args) {
        Object obj = new ArrayList<>();
        if (obj instanceof ArrayList list) {
            list.add("www.baidu.com");
        }
        System.out.println(obj);
    }
}

在使用 instanceof 判断类型成立后会自动强制转换类型为指定类型。

打包工具孵化

在 Java 14 中引入了打包工具命令是 jpackage使用 jpackage 命令可以把 JAR 包打包成不同操作系统支持的软件格式。

jpackage --name myapp --input lib --main-jar main.jar --main-class myapp.Main

常见平台格式如下

  1. Linux: deb and rpm
  2. macOS: pkg and dmg
  3. Windows: msi and exe

要注意的是jpackage 不支持交叉编译也就是说在 windows 平台上是不能打包成 macOS 或者 Linux 系统的软件格式的。

G1 支持 NUMA非统一内存访问

G1 收集器现在可以感知 NUMA 内存分配方式以提高 G1 的性能可以使用 +XX:+UseNUMA 启用这项功能。
更多阅读文档https://openjdk.java.net/jeps/345

更有用的 NullPointerExceptions

NullPointerException 一直都是一个比较常见的异常但是在 Java 14 之前如果一行有多个表达式时这时报了空指针后单纯的从报错信息来看可能并不知道是哪个对象为 NULL 。

public class Java14NullPointerExceptions {

    public static void main(String[] args) {
        String content1 = "www.baidu.com";
        String content2 = null;
        int length = content1.length() + content2.length();
        System.out.println(length);
    }
}

在 Java 14 之前从下面的报错中我们只能得到错误出现的行数但是并不能确定是 conteng1 还是 content2 为 null。

java.lang.NullPointerException
	at other.Other.java14NullPointerExceptions(Other.java:88)

但是在 Java 14 中会清晰的告诉你 because “content2” is null 。

Records (预览)

record 是一种全新的类型它本质上是一个 final 类同时所有的属性都是 final 修饰它会自动编译出 public get hashcode 、equals、toString 等方法减少了代码编写量。
示例编写一个 Dog record 类定义 name 和 age 属性。

public record Dog(String name, Integer age) {
}

使用

Dog dog1 = new Dog("牧羊犬", 1);
Dog dog2 = new Dog("田园犬", 2);
Dog dog3 = new Dog("哈士奇", 3);
System.out.println(dog1);
System.out.println(dog2);
System.out.println(dog3);

输出

Dog[name=牧羊犬, age=1]
Dog[name=田园犬, age=2]
Dog[name=哈士奇, age=3]

这个功能在 Java 15 中进行二次预览在 Java 16 中正式发布。

Switch 表达式 (标准)

Switch 表达式改进从 Java 12 就已经开始了Java 12 让 switch 支持了 case L-> 语法Java 13 引入了 yield 关键词用于返回结果但是在 Java 12 和 13 中功能都是预览版的而在 Java 14 中正式转正。

// 通过传入月份输出月份所属的季节
public static String switchJava12(String month) {
     return switch (month) {
        case "march", "april", "may"            -> "春天";
        case "june", "july", "august"           -> "夏天";
        case "september", "october", "november" -> "秋天";
        case "december", "january", "february"  -> "冬天";
        default -> "month erro";
    };
}
// 通过传入月份输出月份所属的季节
public static String switchJava13(String month) {
    return switch (month) {
        case "march", "april", "may":
            yield "春天";
        case "june", "july", "august":
            yield "夏天";
        case "september", "october", "november":
            yield "秋天";
        case "december", "january", "february":
            yield "冬天";
        default:
            yield "month error";
    };
}

文本块二次预览

文本块是 Java 13 引入的语法在 Java 14 中对其进行了增强。文本块依旧是预览功能这次更新增加了两个转义符。

  1. \ 结尾不换行
  2. \s 表示一个空格
String content = """
        {
            "upperSummary": null,\
            "sensitiveTypeList": null,
            "gmtModified": "2022-08-23\s10:50:09",
        }
         """;
System.out.println(content);

输出

{
    "upperSummary": null,    "sensitiveTypeList": null,
    "gmtModified": "2022-08-23 10:50:09",
}

文本块功能在 Java 15 中正式发布。

其他更新

  1. JEP 362废弃对 Solaris 和 SPARC 端口支持从 Java 14 开始放弃对 Solaris/SPARC, Solaris/x64, 和 Linux/SPARC 端口的支持放弃一部分开发这势必会加快 Java 整体的开发节奏。

相关阅读https://openjdk.java.net/jeps/362

  1. JEP 363 移除 CMS 垃圾收集器

移除对 CMSConcurrent Mark Sweep 垃圾收集器的支持其实早在 Java 9 就开始移除 CMS 垃圾收集器了只不过在 Java 14 中被正式删除。

  1. JEP 364macOS 上的 ZGC实验性

Java 11 在 Linux 上引入了 Z 垃圾收集器 (ZGC)现在它可以移植到 macOS。

  1. JEP 365Windows 上的 ZGC实验性

Java 11 在 Linux 上引入了 Z 垃圾收集器 (ZGC)现在它可以移植到 Windows 上版本大于 1803。

  1. JEP 366弃用 ParallelScavenge + SerialOld GC 组合

由于使用场景不多维护工作太大废弃之。相关阅读https://openjdk.java.net/jeps/366

  1. JEP 367删除 Pack200 工具和 API

Java 15 新特性

Java 15 在 2020 年 9 月发布。
Java 15 全部的新特性请看官网JDK 15 发行说明
Java各个版本的文档入口Java平台标准版文档
Java各个版本下载https://jdk.java.net/archive/

爱德华曲线算法EdDSA

Java 15 中增加了一个新的密码学算法爱德华曲线算法EdDSA签名算法。它是由 Schnorr 算法发展而来在 RFC8032 中被定义实现。

import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Base64;

public class JEP339 {

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519");
        KeyPair kp = kpg.generateKeyPair();
        byte[] msg = "www.baidu.com".getBytes(StandardCharsets.UTF_8);
        Signature sig = Signature.getInstance("Ed25519");
        sig.initSign(kp.getPrivate());
        sig.update(msg);
        byte[] s = sig.sign();
        System.out.println(Base64.getEncoder().encodeToString(s));
    }
}

输出结果

VXlpxapU+LSWjVQ0QNJvdpUh6VI6PjSwOQ2pHu65bCfnLR13OyWKunlc9rc+7SMxCh2Mnqf7TmC/iOG8oimbAw==

Sealed Classes密封类预览

在 Java 中如果想让一个类不能被继承和修改这时我们应该使用 final 关键字对类进行修饰。不过这种要么可以继承要么不能继承的机制不够灵活有些时候我们可能想让某个类可以被某些类型继承但是又不能随意继承是做不到的。Java 15 尝试解决这个问题引入了 **sealed 类被 sealed 修饰的类可以指定子类。这样这个类就只能被指定的类继承。
而且 sealed 修饰的类的机制具有传递性它的子类必须使用指定的关键字进行修饰且只能是
final、sealed、non-sealed **三者之一。

示例犬类Dog只能被牧羊犬Collie和田园犬TuGou继承使用 sealed 关键字。

public sealed interface Dog permits Collie, TuGou {
    //...
}

牧羊犬Collie只能被边境牧羊犬BorderCollie继承。

public sealed class Collie implements Dog permits BorderCollie {

}

边境牧羊犬BorderCollie不能被继承使用 final 关键字。

public final class BorderCollie extends Collie{
}

田园犬ToGou可以被任意继承使用 non-sealed 关键字。

public non-sealed class TuGou implements Dog {
}

Hidden Classes隐藏类

这个特性让开发者可以引入一个无法被其他地方发现使用且类的生命周期有限的类。这对运行时动态生成类的使用方式十分有利可以减少内存占用下面是一个使用示例。

public class JEP371Test {
    public static String lookup() {
      return "www.baidu.com";
    }
}

把类 JEP371Test 编译后的 Class 转换成 Base64然后使用 Java 15 新特性加载调用类中的 lookup 方法。

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Base64;

public class JEP371 {

    private static String CLASS_INFO = "yv66vgAAADQAFAoAAgADBwAEDAAFAAYBABBqYXZhL2xhbmcvT2JqZWN0AQAGPGluaXQ+AQADKClWCAAIAQAOd3d3LndkYnl0ZS5jb20HAAoBABVjb20vd2RieXRlL0pFUDM3MVRlc3QBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAF0xjb20vd2RieXRlL0pFUDM3MVRlc3Q7AQAGbG9va3VwAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAApTb3VyY2VGaWxlAQAPSkVQMzcxVGVzdC5qYXZhACEACQACAAAAAAACAAEABQAGAAEACwAAAC8AAQABAAAABSq3AAGxAAAAAgAMAAAABgABAAAAAwANAAAADAABAAAABQAOAA8AAAAJABAAEQABAAsAAAAbAAEAAAAAAAMSB7AAAAABAAwAAAAGAAEAAAAEAAEAEgAAAAIAEw==";

    public static void main(String[] args) throws Throwable {
        byte[] classInBytes = Base64.getDecoder().decode(CLASS_INFO);
        Class<?> proxy = MethodHandles.lookup()
            .defineHiddenClass(classInBytes, true, MethodHandles.Lookup.ClassOption.NESTMATE)
            .lookupClass();

        System.out.println(proxy.getName());
        MethodHandle mh = MethodHandles.lookup().findStatic(proxy, "lookup", MethodType.methodType(String.class));
        String result = (String) mh.invokeExact();
        System.out.println(result);
    }
}

输出

com.baidu.JEP371Test/0x0000000800c01800
www.baidu.com

移除 Nashorn JavaScript 引擎

Nashorn JavaScript 引擎在 Java 8 中被引入在 Java 11 中被标记为废弃。由于 ECMAScript 语言发展很快维护 Nashorn JavaScript 的成本过于高昂在 Java 15 中被彻底删除。

阅读Nashorn JavaScript Engine (opens new window)Deprecate the Nashorn JavaScript Engine(opens new window)

重新实现 DatagramSocket API

Java 13 中重新实现了旧的 Socket API在介绍 Java 13 时还有一部分做了这方面的介绍。
现在Java 15 重新实现了遗留的 DatagramSocket

禁用和废弃偏向锁Biased Locking

在之前JVM 在处理同步操作如使用 synchronized 同步时有一套锁的升级机制其中有一个锁机制就是偏向锁。然而通过目前的 Java 开发环境来看使用这些被 synchronized 同步的类的机会并不多如开发者更喜欢使用 **HashMap **或者 **ArrayList **而非 HashTable Vector
即使换个角度当初使用偏向锁是为了提高性能如今看来性能提升的程度和使用次数都不太有用。而偏向锁的引入增加了 JVM 的复杂性。
所以现在偏向锁被默认禁用在不久的将来将会彻底删除对于 Java 15我们仍然可以使用
-XX:+UseBiasedLocking
启用偏向锁定但它会提示 这是一个已弃用的 API。

instanceof 类型匹配 (二次预览)

instanceof 类型匹配在 Java 14 中已经改进这次仅仅再次预览没有任何改动用于接受更多的使用反馈。这个特性在 Java 16 中成为正式特性。
在之前使用 instanceof 进行类型判断之后需要进行对象类型转换后才能使用。

ZGC: 可扩展低延迟垃圾收集器正式发布

ZGC 垃圾收集器在 Java 11 中被引入但是因为收集器的复杂性当初决定逐渐引入。然后不断的听取用户的反馈建议修复问题。而现在已经很久没有收到用户的问题反馈了ZGC 是时候投入正式使用阶段了。所以在 Java 15 中 ZGC 正式发布可以使用下面的参数启用 ZGC。

 java -XX:+UseZGC className

文本块

文本块在 Java 12 JEP 326 原始字符串文字 (opens new window)中引入
在 Java 13 JEP 355文本块预览 (opens new window)中开始预览
在 Java 14 JEP 368文本块第二次预览 (opens new window)
而现在在 Java 15 文本块是正式的功能特性了。

String content = """
        {
            "upperSummary": null,\
            "sensitiveTypeList": null,
            "gmtModified": "2022-08-23\s10:50:09",
        }
         """;
System.out.println(content);

Shenandoah: 低停顿时间的垃圾收集器

Shenandoah 垃圾收集器在 Java 12 中开始引入Java 15 中成为了正式功能的一部分可以使用下面的参数启用 Shenandoah 垃圾收集器。

java -XX:+UseShenandoahGC

但是 openJDK 15 中默认是没有 Shenandoah 收集器想要使用此功能可以下载 AdoptOpenJDK (opens new window)

为什么 openJDK 中没有 Shenandoah 垃圾收集器
Shenandoah 是一个高性能、低暂停时间的垃圾收集器它是 Red Hat 主导的项目。当 Red Hat 第一次提议将 Shenandoah 贡献给 OpenJDK 时Oracle 明确表示不想支持它OpenJDK 作为一个免费软件不想支持 Red Hat 的 Shenandoah 完全没有问题。
最后 Red Hat 选择和 Oracle 合作设计一个真正干净的可插拔垃圾收集器接口允许任何人轻松选择垃圾收集器以包含在他们的构建中。最终 Shenandoah 进入了 JDK 12但是没有构建进 OpenJDK。

Records二次预览

在 Java 14 中引入了 Record 类Java 15 中对 Record 进行了增强。使它可以支持密封类型、Record 注解以及相关的反射 API 等。
示例Record 支持密封sealed类型。

public sealed interface DataBase permits DataBaseSelect, DataBaseUpdate {
}

final record DataBaseSelect(@Deprecated String table, String sql) implements DataBase {
}

final record DataBaseUpdate() implements DataBase {
}

在 java.lang.Class 增加了两个公共方法用于获取 Record 类信息

  1. RecordComponent[] getRecordComponents()
  2. boolean isRecord()

其他更新

  1. JEP 381删除 Solaris 和 SPARC 端口

Java 14 JEP 362 (opens new window)弃用了 Solaris/SPARC、Solaris/x64 和 Linux/SPARC 端口现在它在 Java 15 中被正式删除。

  1. JEP 383外部内存访问 API第二个孵化器
  2. JEP 385废弃 RMI 激活机制

只是废弃 RMI 激活机制不影响 RMI 其他功能。

Java 16 新特性

Java 16 在 2021 年 3 月 16 日正式发布在语法上的新特性更新比较多主要是一些之前版本引入的预览特性正式发布。比如Java14中的打包工具、instanceof、Record类。
Java 16 全部的新特性请看官网JDK 16 发行说明
Java各个版本的文档入口Java平台标准版文档
Java各个版本下载https://jdk.java.net/archive/

instanceof 模式匹配

改进 instanceof 在 Java 14 中已经提出在 Java 15 中继续预览而现在在 Java 16 中成为正式功能。
在之前使用 instanceof 需要如下操作

if (obj instanceof String) {
    String s = (String) obj;    // grr...
    ...
}

多余的类型强制转换而现在

if (obj instanceof String s) {
    // Let pattern matching do the work!
    ...
}

Records

Record 成为 Java 16 的正式功能下面是介绍 Java 14 时关于 Record 的介绍。
record 是一种全新的类型它本质上是一个 final 类同时所有的属性都是 final 修饰它会自动编译出 public get hashcode 、equals、toString 等方法减少了代码编写量。

Dog dog1 = new Dog("牧羊犬", 1);
Dog dog2 = new Dog("田园犬", 2);
Dog dog3 = new Dog("哈士奇", 3);
System.out.println(dog1);
System.out.println(dog2);
System.out.println(dog3);

Sealed Classes密封类预览

Sealed Classes 再次预览在 Java 15 新特性介绍文章里已经介绍过相关功能并且给出了详细的使用演示这里不再重复介绍。
下面是一段引用
我们都知道在 Java 中如果想让一个类不能被继承和修改这时我们应该使用 final 关键字对类进行修饰。不过这种要么可以继承要么不能继承的机制不够灵活有些时候我们可能想让某个类可以被某些类型继承但是又不能随意继承是做不到的。Java 15 尝试解决这个问题引入了 sealed 类被 sealed 修饰的类可以指定子类。这样这个类就只能被指定的类继承。
而且 sealed 修饰的类的机制具有传递性它的子类必须使用指定的关键字进行修饰且只能是 final、sealed、non-sealed 三者之一。

Java 17 新特性

Java 17 在 2021 年 9 月 14 日正式发布Java 17 是一个长期支持LTS版本。来看几个重要的。
Java 17 全部的新特性请看官网JDK 17 发行说明
Java各个版本的文档入口Java平台标准版文档
Java各个版本下载https://jdk.java.net/archive/
image.png

恢复严格的浮点语义

既然是恢复严格的浮点语义那么说明在某个时间点之前是始终严格的浮点语义的。其实在 Java SE 1.2 之前所有的浮点计算都是严格的但是以当初的情况来看过于严格的浮点计算在当初流行的 x86 架构和 x87 浮点协议处理器上运行需要大量的额外的指令开销所以在 Java SE 1.2 开始需要手动使用关键字 strictfpstrict float point 才能启用严格的浮点计算。
但是在 2021 年的今天硬件早已发生巨变当初的问题已经不存在了所以从 Java 17 开始恢复了始终严格的浮点语义这一特性。
扩展strictfp 是 Java 中的一个关键字大多数人可能没有注意过它它可以用在类、接口或者方法上被 strictfp 修饰的部分中的 float 和 double 表达式会进行严格浮点计算。

下面是一个示例其中的 testStrictfp() 被 strictfp 修饰。

public static void main(String[] args) {
    testStrictfp();
}

public strictfp static void testStrictfp() {
    float aFloat = 0.6666666666666666666f;
    double aDouble = 0.88888888888888888d;
    double sum = aFloat + aDouble;
    System.out.println("sum: " + sum);
}

增强的伪随机数生成器

为伪随机数生成器 RPNGpseudorandom number generator增加了新的接口类型和实现让在代码中使用各种 PRNG 算法变得容易许多。
这次增加了 **RandomGenerator **接口为所有的 PRNG 算法提供统一的 API并且可以获取不同类型的 PRNG 对象流。同时也提供了一个新类 **RandomGeneratorFactory **用于构造各种 **RandomGenerator **实例在 **RandomGeneratorFactory **中使用 **ServiceLoader.provider **来加载各种 PRNG 实现。
下面是一个使用示例随便选择一个 PRNG 算法生成 5 个 10 以内的随机数。

import java.util.Date;
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
import java.util.stream.Stream;


public class JEP356 {

    public static void main(String[] args) {
        RandomGeneratorFactory<RandomGenerator> l128X256MixRandom = RandomGeneratorFactory.of("L128X256MixRandom");
        // 使用时间戳作为随机数种子
        RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis());
        for (int i = 0; i < 5; i++) {
            System.out.println(randomGenerator.nextInt(10));
        }
    }
}

输出

7
3
4
4
6

你也可以遍历出所有的 PRNG 算法。

RandomGeneratorFactory.all().forEach(factory -> {
    System.out.println(factory.group() + ":" + factory.name());
});

输出

LXM:L32X64MixRandom
LXM:L128X128MixRandom
LXM:L64X128MixRandom
Legacy:SecureRandom
LXM:L128X1024MixRandom
LXM:L64X128StarStarRandom
Xoshiro:Xoshiro256PlusPlus
LXM:L64X256MixRandom
Legacy:Random
Xoroshiro:Xoroshiro128PlusPlus
LXM:L128X256MixRandom
Legacy:SplittableRandom
LXM:L64X1024MixRandom

可以看到 Legacy:Random 也在其中新的 API 兼容了老的 Random 方式所以你也可以使用新的 API 调用 Random 类生成随机数。

// 使用 Random
RandomGeneratorFactory<RandomGenerator> l128X256MixRandom = RandomGeneratorFactory.of("Random");
// 使用时间戳作为随机数种子
RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis());
for (int i = 0; i < 5; i++) {
    System.out.println(randomGenerator.nextInt(10));
}

使用新的 macOS 渲染库

macOS 为了提高图形的渲染性能在 2018 年 9 月抛弃了之前的 OpenGL 渲染库 而使用了 Apple Metal 进行代替。Java 17 这次更新开始支持 Apple Metal不过对于 API 没有任何改变这一些都是内部修改。

支持 macOS/AArch64 架构

起因是 Apple 在 2020 年 6 月的 WWDC 演讲中宣布将开启一项长期的将 Macintosh 计算机系列从 x64 过度到 AArch64 的长期计划因此需要尽快的让 JDK 支持 macOS/AArch64 。
Linux 上的 AArch64 支持以及在 Java 16 时已经支持。

删除已弃用的 Applet API

Applet 是使用 Java 编写的可以嵌入到 HTML 中的小应用程序嵌入方式是通过普通的 HTML 标记语法由于早已过时几乎没有场景在使用了。

<applet code="Hello.class" height=200 width=200></applet>

Applet API 在 Java 9 时已经标记了废弃现在 Java 17 中将彻底删除。

更强的 JDK 内部封装

如 Java 16 的 JEP 396 中描述的一样为了提高 JDK 的安全性使 --illegal-access 选项的默认模式从允许更改为拒绝。通过此更改JDK 的内部包和 API关键内部 API (opens new window)除外将不再默认打开。
但是在 Java 17 中除了 sun.misc.Unsafe 使用 --illegal-access 命令也不能打开 JDK 内部的强封装模式了除了 sun.misc.Unsafe API 。
在 Java 17 中使用 --illegal-access 选项将会得到一个命令已经移除的警告。

➜  bin ./java -version
openjdk version "17" 2021-09-14
OpenJDK Runtime Environment (build 17+35-2724)
OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)
➜  bin ./java --illegal-access=warn
OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=warn; support was removed in 17.0

switch 的类型匹配预览

如 instanceof 一样为 switch 也增加了类型匹配自动转换功能。
在之前使用 instanceof 需要如下操作

if (obj instanceof String) {
    String s = (String) obj;    // grr...
    ...
}

多余的类型强制转换而现在

if (obj instanceof String s) {
    // Let pattern matching do the work!
    ...
}

也可以使用如下的方式

static String formatterPatternSwitch(Object o) {
    return switch (o) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> o.toString();
    };
}

null也有新的方式

// Java 17 之前
static void testFooBar(String s) {
    if (s == null) {
        System.out.println("oops!");
        return;
    }
    switch (s) {
        case "Foo", "Bar" -> System.out.println("Great");
        default           -> System.out.println("Ok");
    }
}
// Java 17
static void testFooBar(String s) {
    switch (s) {
        case null         -> System.out.println("Oops");
        case "Foo", "Bar" -> System.out.println("Great");
        default           -> System.out.println("Ok");
    }
}

移除 RMI Activation

移除了在 JEP 385 中被标记废除的 RMIRemote Method InvocationActivation但是 RMI 其他部分不会受影响。
RMI Activation 在 Java 15 中的 JEP 385 已经被标记为过时废弃至今没有收到不良反馈因此决定在 Java 17 中正式移除。

密封类Sealed Classes

Sealed Classes 在 Java 15 中的 JEP 360 中提出在 Java 16 中的 JEP 397 再次预览现在 Java 17 中成为正式的功能相比 Java 16 并没有功能变化这里不再重复介绍想了解的可以参考之前章节。

移除实验性的 AOT 和 JIT 编译器

在 Java 9 的 JEP 295 中引入了实验性的提前编译 jaotc 工具但是这个特性自从引入依赖用处都不太大而且需要大量的维护工作所以在 Java 17 中决定删除这个特性。

主要移除了三个 JDK 模块

  • jdk.aot - jaotc 工具。
  • Jdk.internal.vm.compiler - Graal 编译器。
  • jdk.internal.vm.compiler.management

同时也移除了部分与 AOT 编译相关的 HotSpot 代码

  • src/hotspot/share/aot — dumps and loads AOT code
  • Additional code guarded by #if INCLUDE_AOT

弃用 Security Manager

Security Manager 在 JDK 1.0 时就已经引入但是它一直都不是保护服务端以及客户端 Java 代码的主要手段为了 Java 的继续发展决定弃用 Security Manager在不久的未来进行删除。

@Deprecated(since="17", forRemoval=true)
public class SecurityManager {
	// ...
}

外部函数和内存 API 孵化

新的 API 允许 Java 开发者与 JVM 之外的代码和数据进行交互通过调用外部函数可以在不使用 JNI 的情况下调用本地库。
这是一个孵化功能需要添加 --add-modules jdk.incubator.foreign 来编译和运行 Java 代码。

Vector API二次孵化

在 Java 16 中引入一个新的 API 来进行向量计算它可以在运行时可靠的编译为支持的 CPU 架构从而实现更优的计算能力。
现在 Java 17 中改进了 Vector API 性能增强了例如对字符的操作、字节向量与布尔数组之间的相互转换等功能。

指定上下文的反序列化过滤器

Java 中的序列化一直都是非常重要的功能如果没有序列化功能Java 可能都不会占据开发语言的主导地位序列化让远程处理变得容易和透明同时也促进了 Java EE 的成功。
但是 Java 序列化的问题也很多它几乎会犯下所有的可以想象的错误为开发者带来持续的维护工作。但是要说明的是序列化的概念是没有错的把对象转换为可以在 JVM 之间自由传输并且可以在另一端重新构建的能力是完全合理的想法问题在于 Java 中的序列化设计存在风险以至于爆出过很多和序列化相关的漏洞。
反序列化危险的一个原因是有时候我们不好验证将要进行反序列化的内容是否存在风险而传入的数据流可以自由引用对象很有可能这个数据流就是攻击者精心构造的恶意代码。
所以JEP 415 允许在反序列化时通过一个过滤配置来告知本次反序列化允许或者禁止操作的类反序列化时碰到被禁止的类则会反序列化失败。

扩展阅读https://openjdk.org/jeps/415

Java 18 新特性

Java 18 在 2022 年 3 月 22 日正式发布Java 18 不是一个长期支持版本这次更新共带来 9 个新功能。
Java 18 全部的新特性请看官网JDK 18 发行说明
Java各个版本的文档入口Java平台标准版文档
Java各个版本下载https://jdk.java.net/archive/

默认UTF-8字符编码

JDK 一直都是支持 UTF-8 字符编码这次是把 UTF-8 设置为了默认编码也就是在不加任何指定的情况下默认所有需要用到编码的 JDK API都使用 UTF-8 编码这样就可以避免因为不同系统不同地区不同环境之间产生的编码问题。
使用下面的命令可以输出 JDK 的当前编码。

# Mac 系统默认 UTF-8~ java -XshowSettings:properties -version 2>&1 | grep file.encoding
    file.encoding = UTF-8
    file.encoding.pkg = sun.io
➜  ~

简单的 Web 服务器

在 Java 18 中提供了一个新命令 jwebserver运行这个命令可以启动一个简单的 、最小化的静态 Web 服务器它不支持 CGI 和 Servlet所以最好的使用场景是用来测试、教育、演示等需求。
其实在如 Python、Ruby、PHP、Erlang 等许多平台都提供了开箱即用的 Web 服务器可见一个简单的 Web 服务器是一个常见的需求Java 一直没有这方面的支持现在可以了。
在 Java 18 中使用 jwebserver 启动一个 Web 服务器默认发布的是当前目录。

在当前目录创建一个网页文件 index.html

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1>CSDN-阿提说说</h1>
</body>
</html>

启动 jwebserver

Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving D:\javaprojects\java18-example\src\test\java\other and subdirectories on
 127.0.0.1 port 8000
URL http://127.0.0.1:8000/

通过 help 参数可以查看 jwebserver 支持的参数。

➜  bin ./jwebserver --help
Usage: jwebserver [-b bind address] [-p port] [-d directory]
                  [-o none|info|verbose] [-h to show options]
                  [-version to show version information]
Options:
-b, --bind-address    - 绑定地址. Default: 127.0.0.1 (loopback).
                        For all interfaces use "-b 0.0.0.0" or "-b ::".
-d, --directory       - 指定目录. Default: current directory.
-o, --output          - Output format. none|info|verbose. Default: info.
-p, --port            - 绑定端口. Default: 8000.
-h, -?, --help        - Prints this help message and exits.
-version, --version   - Prints version information and exits.
To stop the server, press Ctrl + C.

Javadoc 中支持代码片段

在 Java 18 之前已经支持在 Javadoc 中引入代码片段这样可以在某些场景下更好的展示描述信息但是之前的支持功能有限比如我想高亮代码片段中的某一段代码是无能为力的。现在 Java 18 优化了这个问题增加了 @snippet 来引入更高级的代码片段。
在 Java 18 之前使用

{@code …}
来引入代码片段。

/**
 * 在 Java 18 之后可以使用新的方式
 * 下面的代码演示如何使用 {@code Optional.isPresent}:
 * {@snippet :
 * if (v.isPresent()) {
 *     System.out.println("v: " + v.get());
 * }
 * }
 *
 * 高亮显示 println
 *
 * {@snippet :
 * class HelloWorld {
 *     public static void main(String... args) {
 *         System.out.println("Hello World!");      // @highlight substring="println"
 *     }
 * }
 * }
 *
 */

显示效果
image.png

使用方法句柄重新实现反射核心功能

Java 18 改进了** java.lang.reflect.Method**、**Constructor **的实现逻辑使之性能更好速度更快。这项改动不会改动相关 API 这意味着开发中不需要改动反射相关代码就可以体验到性能更好反射。
OpenJDK 官方给出了新老实现的反射性能基准测试结果。
Java 18 之前

Benchmark                                     Mode  Cnt   Score  Error  Units
ReflectionSpeedBenchmark.constructorConst     avgt   10  68.049 ± 0.872  ns/op
ReflectionSpeedBenchmark.constructorPoly      avgt   10  94.132 ± 1.805  ns/op
ReflectionSpeedBenchmark.constructorVar       avgt   10  64.543 ± 0.799  ns/op
ReflectionSpeedBenchmark.instanceFieldConst   avgt   10  35.361 ± 0.492  ns/op
ReflectionSpeedBenchmark.instanceFieldPoly    avgt   10  67.089 ± 3.288  ns/op
ReflectionSpeedBenchmark.instanceFieldVar     avgt   10  35.745 ± 0.554  ns/op
ReflectionSpeedBenchmark.instanceMethodConst  avgt   10  77.925 ± 2.026  ns/op
ReflectionSpeedBenchmark.instanceMethodPoly   avgt   10  96.094 ± 2.269  ns/op
ReflectionSpeedBenchmark.instanceMethodVar    avgt   10  80.002 ± 4.267  ns/op
ReflectionSpeedBenchmark.staticFieldConst     avgt   10  33.442 ± 2.659  ns/op
ReflectionSpeedBenchmark.staticFieldPoly      avgt   10  51.918 ± 1.522  ns/op
ReflectionSpeedBenchmark.staticFieldVar       avgt   10  33.967 ± 0.451  ns/op
ReflectionSpeedBenchmark.staticMethodConst    avgt   10  75.380 ± 1.660  ns/op
ReflectionSpeedBenchmark.staticMethodPoly     avgt   10  93.553 ± 1.037  ns/op
ReflectionSpeedBenchmark.staticMethodVar      avgt   10  76.728 ± 1.614  ns/op

Java 18 的新实现:

Benchmark                                     Mode  Cnt    Score   Error  Units
ReflectionSpeedBenchmark.constructorConst     avgt   10   32.392 ± 0.473  ns/op
ReflectionSpeedBenchmark.constructorPoly      avgt   10  113.947 ± 1.205  ns/op
ReflectionSpeedBenchmark.constructorVar       avgt   10   76.885 ± 1.128  ns/op
ReflectionSpeedBenchmark.instanceFieldConst   avgt   10   18.569 ± 0.161  ns/op
ReflectionSpeedBenchmark.instanceFieldPoly    avgt   10   98.671 ± 2.015  ns/op
ReflectionSpeedBenchmark.instanceFieldVar     avgt   10   54.193 ± 3.510  ns/op
ReflectionSpeedBenchmark.instanceMethodConst  avgt   10   33.421 ± 0.406  ns/op
ReflectionSpeedBenchmark.instanceMethodPoly   avgt   10  109.129 ± 1.959  ns/op
ReflectionSpeedBenchmark.instanceMethodVar    avgt   10   90.420 ± 2.187  ns/op
ReflectionSpeedBenchmark.staticFieldConst     avgt   10   19.080 ± 0.179  ns/op
ReflectionSpeedBenchmark.staticFieldPoly      avgt   10   92.130 ± 2.729  ns/op
ReflectionSpeedBenchmark.staticFieldVar       avgt   10   53.899 ± 1.051  ns/op
ReflectionSpeedBenchmark.staticMethodConst    avgt   10   35.907 ± 0.456  ns/op
ReflectionSpeedBenchmark.staticMethodPoly     avgt   10  102.895 ± 1.604  ns/op
ReflectionSpeedBenchmark.staticMethodVar      avgt   10   82.123 ± 0.629  ns/op

Vector API三次孵化

在 Java 16 中引入一个新的 API 来进行向量计算它可以在运行时可靠的编译为支持的 CPU 架构从而实现更优的计算能力。
在 Java 17 中改进了 Vector API 性能增强了例如对字符的操作、字节向量与布尔数组之间的相互转换等功能。
现在在 JDK 18 中将继续优化其性能。

互联网地址解析 SPI

对于互联网地址解析 SPI为主机地址和域名地址解析定义一个 SPI以便 java.net.InetAddress 可以使用平台内置解析器以外的解析器。

InetAddress inetAddress = InetAddress.getByName("www.csdn.net");
System.out.println(inetAddress.getHostAddress());

//39.106.226.142

其他更新

  1. JEP 419 Foreign Function & Memory API (二次孵化)(opens new window)
  2. JEP 420 switch 模式匹配二次预览(opens new window)
  3. JEP 421 弃用完成删除(opens new window)
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java

“拥抱变化,面向Java17,Java8-18全系列特性详解_jdk 1.8 17 18 19区别” 的相关文章