从Java8到Java21各版本新特性详解

Java 8到Java 21各版本新特性详解
本文详细介绍了从Java 8到Java 21的各版本新特性,包括Java 8的lambda表达式、函数式接口、方法引用、Stream API、日期/时间API和Optional类;Java 9的模块化系统(JPMS)、JShell、性能改进、集合工厂方法的改进以及HTTP/2支持;Java 11的新特性如动态类文件常量、Epsilon垃圾收集器、HTTP/2客户端API等;Java 17的Sealed Classes、Pattern Matching for switch等。文章还对Java 21的新特性进行了预测,如模式匹配的进一步增强、静态变量的垃圾回收等。

        

       

目录

前言

一、Java 8新特性

        1、lambda表达式

        2、函数式接口

        3、方法引用

        4、接口默认方法和静态方法

        5、Stream API

        5.1、创建stream通过of方法

        5.2、创建stream通过generator方法

        5.3、创建stream通过iterate方法

        5.4、通过Collection子类获取Stream

        6、新的日期/时间API

        6.1、Clock 时钟

       6.2、Timezones 时区

        6.3、LocalTime 本地时间

        6.4、LocalDate 本地日期

        6.5、LocalDateTime 本地日期时间

         7、Optional类

        7.1、创建Optional类对象的方法

        7.2、判断Optional容器中是否包含对象

二、Java 9新特性

        2.1、模块化系统(Java Platform Module System,JPMS)

        2.2、JShell

        2.3、改进的性能

        2.3.1、改进的垃圾回收器

        2.3.1.1、G1垃圾回收器的改进

        2.3.1.2、Z垃圾回收器的引入

        2.3.2、优化的JIT编译器

        2.3.2.1、实时编译

        2.3.2.2、高级优化技术

        2.3.2.3、多语言支持

        2.3.2.4、AOT编译

        2.3.2.5、实例

        2.4、改进的集合工厂方法

        2.4.1、List.of()

        2.4.2、Set.of()

        2.4.3、Map.of()

        2.5、改进的Stream API

        2.5.1、接口改进

        2.5.2、改进的方法

        2.6、改进的Try-With-Resources语法

        2.7、改进的HTTP/2支持

        2.7.1、增加了对HTTP/2的原生支持

        2.7.2、支持多路复用

        2.7.3、支持流优先级

        2.7.4、支持服务器推送

        2.7.5、性能改进

        2.8、改进的安全性

        2.8.1、模块化安全性

        2.8.2、改进的SHA-3支持

        2.8.3、去除过时的加密算法

        2.8.4、JEP 229: HTTP/2客户端

        2.8.5、JEP 274

        2.9、改进的编译工具

        2.9.1、支持模块化编译

        2.9.2、增强的注解处理器

        2.9.3、编译时类型检查的改进

三、Java 11新特性

        3.1、动态类文件常量

        3.2、Epsilon垃圾收集器

        3.3、在字符串上支持Unicode10版本

        3.4、合并Java EE和Java SE功能

        3.5、HTTP/2客户端 API

        3.6、基于Lambda表达式的本地变量类型推断

四、Java 17 新特性

        4.1、Sealed Classes

        4.2、Pattern Matching for switch

        4.3、Record Classes

        4.4、Sealed Interfaces

        4.5、Foreign Function & Memory API

        4.6、Strong encapsulation of JDK internals

五、Java 21 新特性预测

        5.1、模式匹配

        5.2、增强的Switch表达式

        5.3、可垃圾回收的静态变量

        5.4、引入更多的语法糖

        5.5、静态类型检查的改进

六、参考文献


前言

        下面这张图是 Oracle 官方给出的 Oracle JDK 支持的时间线,可以看出,JDK 17的支持时间最长,可以延续到2029年9月。考虑到技术更新的速度,这次免费商用8年的时间可谓是经过精心考虑,旨在让用户放心地升级到JDK 17(不过JDK 8的支持时间更长,可以延续到2030年12月)。

        从JDK诞生到现在,仅有几个版本得到了长期支持,主要包括JDK 7、JDK 8、JDK 11以及即将发布的JDK 17,它将是继Java 8之后最重要的LTS版本,是Java社区八年努力的成果。

        一直以来,Java 8一直是Java社区的痛点,它提供了许多新特性,比如Lambda表达式、Optional类,并且由于Java 8的长期支持时间,导致了JDK 8的广泛使用。这代表着企业管理层更看重稳定性,而程序员更注重拥抱变化之间的拉锯战。不升级已成为各大公司的默契选择。然而,现在这种平衡可能会被打破,因为Java界的主导框架SpringBoot选择了最新的Java LTS版本,也就是Java 17。

        那么接下来,让我们看看,从JDK8到JDK17,Java 社区八年努力的成果有哪些?

一、Java 8新特性

        1、lambda表达式

        Lambda表达式允许以更简洁的方式编写匿名函数。它们可用于替代单个抽象方法的接口的实现。

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

        Lambda的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如:

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

        2、函数式接口

        函数式接口是只有一个抽象方法的接口。Java 8引入了一些预定义的函数式接口,如Consumer、Predicate、Function等。

        Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnablejava.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了)。

函数式接口的定义:

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

        3、方法引用

        方法引用允许直接引用已有的方法,而不是通过lambda表达式来描述行为。

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

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

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

        4、接口默认方法和静态方法

        接口可以包含默认方法的实现,这意味着可以为接口添加新的方法,而不会破坏现有的实现类。

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

        5、Stream API

        Stream API提供了一种新的对集合操作的方式,使用流可以以更简洁的方式进行过滤、映射、排序等操作。

        5.1、创建stream通过of方法
Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");
        5.2、创建stream通过generator方法

        生成一个无限长度的Stream,其元素的生成是通过给定的Supplier(这个接口可以看成一个对象的工厂,每次调用返回一个给定类型的对象)

Stream.generate(new Supplier<Double>() {
    @Override
    public Double get() {
        return Math.random();
    }
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);

        三条语句的作用都是一样的,只是使用了lambda表达式和方法引用的语法来简化代码。每条语句其实都是生成一个无限长度的Stream,其中值是随机的。这个无限长度Stream是懒加载,一般这种无限长度的Stream都会配合Stream的limit()方法来用。

        5.3、创建stream通过iterate方法

        也是生成无限长度的Stream和generator不同的是,其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的,其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环,例如:

Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);

        这段代码就是先获取一个无限长度的正整数集合的Stream,然后取出前10个打印。千万记住使用limit方法,不然会无限打印下去。

        5.4、通过Collection子类获取Stream

public interface Collection<E> extends Iterable<E> {

    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

}

        java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。

        Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。
        Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。

        下面的例子展示了是如何通过并行Stream来提升性能,首先我们创建一个没有重复元素的大表:

int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
    UUID uuid = UUID.randomUUID();
    values.add(uuid.toString());
}

        然后我们计算一下排序这个Stream要耗时多久,串行排序:

long t0 = System.nanoTime();
long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));

        

串行耗时: 899 ms


其次我们继续看看并行排序:

ong t0 = System.nanoTime();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));

并行排序耗时: 472 ms


        上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream();

        6、新的日期/时间API

        Java 8引入了新的日期/时间API,提供了更好的处理日期和时间的方式。

        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吸取的教训),如果某个实例需要修改,则返回一个新的对象。

        6.1、Clock 时钟

        Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。

        我们接下来看看java.time包中的关键类和各自的使用例子。首先,Clock类使用时区来返回当前的纳秒时间和日期。Clock可以替代System.currentTimeMillis()TimeZone.getDefault()

Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant);
       6.2、Timezones 时区
System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
        6.3、LocalTime 本地时间

        LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:


                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值