文章目录:
Java 8 新特性Java 9 新特性Java 10 新特性Java 11 新特性Java 12 新特性Java 13 新特性Java 14 新特性Java 15 新特性Java 16 新特性Java 17 新特性Java 18 新特性? 文章较长,建议点赞、收藏、评论后慢慢看,合理利用 “只看目录功能”
前言
当我们大部分Javaer还沉浸在Java 8 的特性中时,Java 19 预计在2022年9月20号发布,现在半年发布一次的节奏真让人应接不暇,况且Spring Boot 3.0开始最低版本为Java 17,Spring Security、KafKa等也都宣布在后期版本最低需要Java 17 ,所以我们恶补一下Java 8-18的特性很有必要。
Java 8 新特性
Java 8 带来了大量的新特性。主要分为以下几个方面:语言、它的编译器、库、工具和 JVM(Java 虚拟机)。
这个教程包含Java开发者经常面对的几类问题:
感兴趣的话,可以看下官网的描述:
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.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。
在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。
为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解 @FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:
@FunctionalInterfacepublic 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 implementationOverridden 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@7a81197dRepaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197dFollowing 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实例,输出到控制台的内容如下所示:
filter1filter2
如果你希望了解更多内容,可以参考官方文档。
更好的类型推断
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_USER 和 ElementType.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? falseFull 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? trueFirst Name: TomHey 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 API(java.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 tasksfinal 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 statusfinal 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.282Z1397315969360
第二,关注下 LocalDate 和 LocalTime 类。LocalDate 仅仅包含ISO-8601日历系统中的日期部分;LocalTime则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用Clock对象构建得到。
// Get the local date and local timefinal LocalDate date = LocalDate.now();final LocalDate dateFromClock = LocalDate.now( clock ); System.out.println( date );System.out.println( dateFromClock ); // Get the local date and local timefinal LocalTime time = LocalTime.now();final LocalTime timeFromClock = LocalTime.now( clock ); System.out.println( time );System.out.println( timeFromClock );
上述例子的输出结果如下:
2014-04-122014-04-1211:25:54.56815:25:54.568
LocalDateTime 类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息。这里有一些关于LocalDate和LocalTime的例子:
// Get the local date/timefinal LocalDateTime datetime = LocalDateTime.now();final LocalDateTime datetimeFromClock = LocalDateTime.now( clock ); System.out.println( datetime );System.out.println( datetimeFromClock );
上述这个例子的输出结果如下:
2014-04-12T11:37:52.3092014-04-12T15:37:52.309
如果你需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。下面是一些使用不同时区的例子:
// Get the zoned date/timefinal 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.017Z2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
最后看下Duration类,它持有的时间精确到秒和纳秒。这使得我们可以很容易得计算两个日期之间的不同,例子代码如下:
// Get duration between two datesfinal 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: 365Duration 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.NashornScriptEngineResult: 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 **包中也新增了不少工具类,列举如下:
新的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
增强流(Stream)API
Stream 流操作自从 Java 8 引入以来,一直广受好评。
当然,学习 Stream 之前要先学习 Lambda ,也是Java 8的内容。
在 Java 9 中,又对 Stream 进行了增强,主要增加了 4 个新的操作方法:dropWhile,takeWhile,ofNullable,iterate。
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 introjshell> /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 ==> 10jshell> for(int i=0;i<a;i++){System.out.println(i);}0123456789
3、定义一个集合,赋值 1,2,3,4,5。然后输出集合
jshell> List list = List.of(1,2,3,4,5);list ==> [1, 2, 3, 4, 5]jshell> listlist ==> [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.javajshell> /exit 再见
在 D 盘看到的保存的代码片段。
JVM 调优的新特性
第一个:删除 JDK 8 中已弃用的垃圾收集器 (GC) 组合
这意味着以下 GC 组合不再存在:
DefNew + CMSParNew + 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
查看自己的 Java 10 版本。
$ java -versionjava version "10.0.1" 2018-04-17Java(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 只能用于下面的几种情况。
仅限带有初始化的程序的局部变量。for 循环或者增强for 循环中。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
这里面有一点我觉得很有意思,看这个图。
这就很有意思了,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::ofjava.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek}java.util.Currency::getInstancejava.util.Locale::getDisplayNamejava.util.spi.LocaleNameProviderjava.text.DateFormat::get*Instancejava.text.DateFormatSymbols::getInstancejava.text.DecimalFormatSymbols::getInstancejava.text.NumberFormat::get*Instancejava.time.format.DateTimeFormatter::localizedByjava.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePatternjava.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);
结果:
本地货币:CNYUS.货币: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 312:Thread-Local Handshakes,JVM 内部功能,可以提高 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 类带来了一系列的好用操作。
isBlank()判空// 判空,blank里我放入了全角空格,半角空格,TABString blank = " ";System.out.println(blank.isBlank());// 输出// true
lines()分割获取字符串流 // lines 返回一个 StreamString line = "a\nb\nc";Stream<String> lines = line.lines();// 使用 lambda 遍历lines.forEach(System.out::println);// 输出// a// b// c
repeat()复制字符串 // 复制字符串String repeat = "CSDN/WX-阿提说说";String repeat3 = repeat.repeat(3);System.out.println(repeat3);// 输出// CSDN/WX-阿提说说CSDN/WX-阿提说说CSDN/WX-阿提说说
strip()去除前后空白字符 // 去除前后空白String strip = " https://www.baidu.com ";System.out.println("==" + strip.trim() + "==");// 去除前后空白字符,如全角空格,TABSystem.out.println("==" + strip.strip() + "==");// 去前面空白字符,如全角空格,TABSystem.out.println("==" + strip.stripLeading() + "==");// 去后面空白字符,如全角空格,TABSystem.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 和其他变量类型混用。
单命令运行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 中将开源免费。所有人都可以使用这个功能了。
其他更新
JEP 309 - 添加动态文件常量。JEP 318 - 添加 Epsilon 垃圾收集器。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)。JEP 329 - 增加加密算法 chacha20,poly1305 的实现。JEP 333 - 引入实验性的 ZGC 垃圾收集器,保证停摆时间不会超过 10ms。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 -> 1K,1000000 -> 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));
输出:
1001K10K100K1M1.2K123.5K12.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 第一次改进 CDS(Class 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
常见平台格式如下:
Linux: deb and rpmmacOS: pkg and dmgWindows: 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.NullPointerExceptionat 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 中对其进行了增强。文本块依旧是预览功能,这次更新增加了两个转义符。
\ 结尾不换行\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 中正式发布。
其他更新
JEP 362:废弃对 Solaris 和 SPARC 端口支持从 Java 14 开始,放弃对 Solaris/SPARC, Solaris/x64, 和 Linux/SPARC 端口的支持,放弃一部分开发这势必会加快 Java 整体的开发节奏。相关阅读:https://openjdk.java.net/jeps/362
JEP 363: 移除 CMS 垃圾收集器移除对 CMS(Concurrent Mark Sweep) 垃圾收集器的支持,其实早在 Java 9 就开始移除 CMS 垃圾收集器了,只不过在 Java 14 中被正式删除。
JEP 364:macOS 上的 ZGC(实验性)Java 11 在 Linux 上引入了 Z 垃圾收集器 (ZGC),现在它可以移植到 macOS。
JEP 365:Windows 上的 ZGC(实验性)Java 11 在 Linux 上引入了 Z 垃圾收集器 (ZGC),现在它可以移植到 Windows 上(版本大于 1803)。
JEP 366:弃用 ParallelScavenge + SerialOld GC 组合由于使用场景不多,维护工作太大,废弃之。相关阅读:https://openjdk.java.net/jeps/366
JEP 367:删除 Pack200 工具和 APIJava 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/0x0000000800c01800www.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 类信息:
RecordComponent[] getRecordComponents()boolean isRecord()其他更新
JEP 381:删除 Solaris 和 SPARC 端口Java 14 JEP 362 (opens new window)弃用了 Solaris/SPARC、Solaris/x64 和 Linux/SPARC 端口,现在它在 Java 15 中被正式删除。
JEP 383:外部内存访问 API(第二个孵化器)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/
恢复严格的浮点语义
既然是恢复严格的浮点语义,那么说明在某个时间点之前,是始终严格的浮点语义的。其实在 Java SE 1.2 之前,所有的浮点计算都是严格的,但是以当初的情况来看,过于严格的浮点计算在当初流行的 x86 架构和 x87 浮点协议处理器上运行,需要大量的额外的指令开销,所以在 Java SE 1.2 开始,需要手动使用关键字 strictfp(strict 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);}
增强的伪随机数生成器
为伪随机数生成器 RPNG(pseudorandom 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)); } }}
输出:
73446
你也可以遍历出所有的 PRNG 算法。
RandomGeneratorFactory.all().forEach(factory -> { System.out.println(factory.group() + ":" + factory.name());});
输出:
LXM:L32X64MixRandomLXM:L128X128MixRandomLXM:L64X128MixRandomLegacy:SecureRandomLXM:L128X1024MixRandomLXM:L64X128StarStarRandomXoshiro:Xoshiro256PlusPlusLXM:L64X256MixRandomLegacy:RandomXoroshiro:Xoroshiro128PlusPlusLXM:L128X256MixRandomLegacy:SplittableRandomLXM:L64X1024MixRandom
可以看到 Legacy:Random 也在其中,新的 API 兼容了老的 Random 方式,所以你也可以使用新的 API 调用 Random 类生成随机数。
// 使用 RandomRandomGeneratorFactory<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 -versionopenjdk version "17" 2021-09-14OpenJDK Runtime Environment (build 17+35-2724)OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)➜ bin ./java --illegal-access=warnOpenJDK 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 17static 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 中被标记废除的 RMI(Remote Method Invocation)Activation,但是 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 codeAdditional 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 8000URL http://127.0.0.1:8000/
通过 help 参数可以查看 jwebserver 支持的参数。
➜ bin ./jwebserver --helpUsage: 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" * } * } * } * */
显示效果:
使用方法句柄重新实现反射核心功能
Java 18 改进了** java.lang.reflect.Method**、**Constructor **的实现逻辑,使之性能更好,速度更快。这项改动不会改动相关 API ,这意味着开发中不需要改动反射相关代码,就可以体验到性能更好反射。
OpenJDK 官方给出了新老实现的反射性能基准测试结果。
Java 18 之前:
Benchmark Mode Cnt Score Error UnitsReflectionSpeedBenchmark.constructorConst avgt 10 68.049 ± 0.872 ns/opReflectionSpeedBenchmark.constructorPoly avgt 10 94.132 ± 1.805 ns/opReflectionSpeedBenchmark.constructorVar avgt 10 64.543 ± 0.799 ns/opReflectionSpeedBenchmark.instanceFieldConst avgt 10 35.361 ± 0.492 ns/opReflectionSpeedBenchmark.instanceFieldPoly avgt 10 67.089 ± 3.288 ns/opReflectionSpeedBenchmark.instanceFieldVar avgt 10 35.745 ± 0.554 ns/opReflectionSpeedBenchmark.instanceMethodConst avgt 10 77.925 ± 2.026 ns/opReflectionSpeedBenchmark.instanceMethodPoly avgt 10 96.094 ± 2.269 ns/opReflectionSpeedBenchmark.instanceMethodVar avgt 10 80.002 ± 4.267 ns/opReflectionSpeedBenchmark.staticFieldConst avgt 10 33.442 ± 2.659 ns/opReflectionSpeedBenchmark.staticFieldPoly avgt 10 51.918 ± 1.522 ns/opReflectionSpeedBenchmark.staticFieldVar avgt 10 33.967 ± 0.451 ns/opReflectionSpeedBenchmark.staticMethodConst avgt 10 75.380 ± 1.660 ns/opReflectionSpeedBenchmark.staticMethodPoly avgt 10 93.553 ± 1.037 ns/opReflectionSpeedBenchmark.staticMethodVar avgt 10 76.728 ± 1.614 ns/op
Java 18 的新实现:
Benchmark Mode Cnt Score Error UnitsReflectionSpeedBenchmark.constructorConst avgt 10 32.392 ± 0.473 ns/opReflectionSpeedBenchmark.constructorPoly avgt 10 113.947 ± 1.205 ns/opReflectionSpeedBenchmark.constructorVar avgt 10 76.885 ± 1.128 ns/opReflectionSpeedBenchmark.instanceFieldConst avgt 10 18.569 ± 0.161 ns/opReflectionSpeedBenchmark.instanceFieldPoly avgt 10 98.671 ± 2.015 ns/opReflectionSpeedBenchmark.instanceFieldVar avgt 10 54.193 ± 3.510 ns/opReflectionSpeedBenchmark.instanceMethodConst avgt 10 33.421 ± 0.406 ns/opReflectionSpeedBenchmark.instanceMethodPoly avgt 10 109.129 ± 1.959 ns/opReflectionSpeedBenchmark.instanceMethodVar avgt 10 90.420 ± 2.187 ns/opReflectionSpeedBenchmark.staticFieldConst avgt 10 19.080 ± 0.179 ns/opReflectionSpeedBenchmark.staticFieldPoly avgt 10 92.130 ± 2.729 ns/opReflectionSpeedBenchmark.staticFieldVar avgt 10 53.899 ± 1.051 ns/opReflectionSpeedBenchmark.staticMethodConst avgt 10 35.907 ± 0.456 ns/opReflectionSpeedBenchmark.staticMethodPoly avgt 10 102.895 ± 1.604 ns/opReflectionSpeedBenchmark.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