【Java】从java8到java17各版本新特性详解

在这里插入图片描述

前言

        下面这张图是 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.Runnable和java.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::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

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

long 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。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:

LocalTime now1 = LocalTime.now(zone1);

LocalTime now2 = LocalTime.now(zone2);

System.out.println(now1.isBefore(now2));  // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2);

long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween);       // -3

System.out.println(minutesBetween);     // -239

LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。

LocalTime late = LocalTime.of(23, 59, 59);

System.out.println(late);       // 23:59:59

DateTimeFormatter germanFormatter =

    DateTimeFormatter

        .ofLocalizedTime(FormatStyle.SHORT)

        .withLocale(Locale.GERMAN);

LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);

System.out.println(leetTime);   // 13:37
6.4、LocalDate 本地日期

        LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。

LocalDate today = LocalDate.now();

LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);

LocalDate yesterday = tomorrow.minusDays(2);

LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);

DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();


System.out.println(dayOfWeek);    // FRIDAY

从字符串解析一个LocalDate类型和解析LocalTime一样简单:

DateTimeFormatter germanFormatter =

    DateTimeFormatter

        .ofLocalizedDate(FormatStyle.MEDIUM)

        .withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);

System.out.println(xmas);   // 2014-12-24
6.5、LocalDateTime 本地日期时间

        LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek();

System.out.println(dayOfWeek);      // WEDNESDAY

Month month = sylvester.getMonth();

System.out.println(month);          // DECEMBER

long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);

System.out.println(minuteOfDay);    // 1439

        只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。

Instant instant = sylvester

        .atZone(ZoneId.systemDefault())

        .toInstant();

Date legacyDate = Date.from(instant);

System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014

        格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:

DateTimeFormatter formatter =

    DateTimeFormatter

        .ofPattern("MMM dd, yyyy - HH:mm");

LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);

String string = formatter.format(parsed);

System.out.println(string);     // Nov 03, 2014 - 07:13

        和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。

7、Optional类

        Optional类是用于解决空指针异常的问题。它可以用来包装一个可能为null的值,并在值不存在时提供一些默认行为。

        空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。

        Optional 类(java.util.Optional) 是一个容器类, 它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。而且Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

7.1、创建Optional类对象的方法

Optional.of(T t) : 创建一个 Optional 实例,t必须非空;

Optional.empty() : 创建一个空的 Optional 实例

Optional.ofNullable(T t):t可以为null

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

boolean isPresent() : 判断是否包含对象

void ifPresent(Consumer<? super T> consumer) :如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。

这些都是Java 8中引入的一些重要的新特性。它们使得Java编程更加简洁、灵活和高效。

二、Java 9新特性

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

        它是一种新的Java编程组件,可以用来收集Java代码(类和包)。这个项目的主要目标是轻松地将应用程序缩小到小型设备。在Java9中,JDK本身已经划分为一组模块,以使其更加轻量级。它还允许我们开发模块化应用程序。

        Java 9 引入了一个新的模块化系统,这个系统被称为 Java 平台模块系统(Java Platform Module System,JPMS),它允许开发者将 Java 应用程序和库分割为一组模块,提供了更好的封装性、可重用性和可扩展性。

        模块是一组相关的类和资源的集合,它们可以一起被打包和分发。每个模块都有自己的名称,并且可以指定依赖关系,以及公开哪些部分供其他模块使用。这样,开发者可以更好地管理应用程序的复杂性,并确保模块之间的依赖关系是明确的和可控的。

        下面是一个简单的 Java 9 模块的示例:

module com.example.myapp {

    requires org.apache.commons.lang;

    requires java.sql;

    exports com.example.myapp;

}

        这个示例中定义了一个名为 “com.example.myapp” 的模块,它依赖于 “org.apache.commons.lang” 模块和 “java.sql” 模块。它还导出了自己的 “com.example.myapp” 包,以供其他模块使用。

        使用模块化系统,开发者可以更好地管理应用程序的依赖关系,并确保应用程序只包含所需的模块,减少了不必要的依赖和冗余代码。此外,模块化还提供了更好的可重用性和可扩展性,因为开发者可以将模块作为独立的单元进行开发和维护。

        Java 9 模块化系统为开发者提供了更好的灵活性、可维护性和可组装性,从而提高了应用程序的开发效率和性能。

2.2、JShell

        JShell是一个交互式的Java shell,允许开发人员在没有编译和运行整个程序的情况下直接在控制台中执行Java代码。

        JShell的主要特点包括:

  • 即时反馈:JShell可以立即执行代码片段,并立即返回结果,无需编译和运行整个应用程序。
  • 自动完成:JShell提供自动完成功能,可以帮助开发人员快速输入Java代码,并提供可能的代码建议。
  • 内省:JShell可以查看和修改已执行代码的状态和结果,开发人员可以直接在JShell中进行实验和调试。

        下面是一个简单的JShell案例:

$ jshell

|  Welcome to JShell -- Version 9

|  For an introduction type: /help intro

 

jshell> int a = 5;

a ==> 5
jshell> int b = 10;

b ==> 10

 
jshell> int sum = a + b;

sum ==> 15

 
jshell> System.out.println("Sum: " + sum);

Sum: 15

        在这个例子中,我们首先定义了一个整数变量a和b,并用它们计算了一个和。然后,我们使用System.out.println方法打印出和的结果。

        JShell可以帮助开发人员快速验证和测试代码,它特别适用于小规模的代码片段和实验性的开发。但是,对于大型应用程序和持久化的代码,仍然建议使用传统的Java编译和运行方式。

2.3、改进的性能

        Java 9引入了一些性能改进,包括改进的垃圾回收器、优化的JIT编译器等。

2.3.1、改进的垃圾回收器

        Java 9引入了一些改进的垃圾回收器,旨在提高性能和减少延迟。这些改进主要集中在两个方面:G1垃圾回收器的改进和新的Z垃圾回收器的引入。

2.3.1.1、G1垃圾回收器的改进

        G1(Garbage-First)垃圾回收器是Java 9中的默认垃圾回收器,相比于以前的垃圾回收器,G1回收器有以下改进:

  • 更好的并发性:G1垃圾回收器在回收垃圾对象时,可以和Java应用程序并发执行,减少了垃圾回收的停顿时间。
  • 更好的内存利用率:G1垃圾回收器可以动态地将堆划分为多个小的区域,只回收包含垃圾对象的区域,从而减少了垃圾回收的范围,提高了内存利用率。
  • 更好的可预测性:G1垃圾回收器可以预测垃圾回收的停顿时间,并根据用户配置的目标时间来调整垃圾回收的策略。
2.3.1.2、Z垃圾回收器的引入

        Z垃圾回收器(Shenandoah)是Java 9中新引入的一种垃圾回收器,它的目标是最小化垃圾回收的停顿时间。与G1垃圾回收器相比,Z垃圾回收器有以下特点:

  • 低延迟:Z垃圾回收器采用了一种读写屏障的机制,使得垃圾回收过程中对应用程序的停顿时间非常短,几乎可以忽略不计。
  • 场景可用性:Z垃圾回收器可用于大型内存堆,特别适用于需要大量内存的应用程序。
  • 分布式协作:Z垃圾回收器使用了全局共享的标记过程,可以并发执行标记和复制阶段,从而提高了回收效率。

        这些改进的垃圾回收器可以在不同的场景下发挥作用,下面是一些使用这些垃圾回收器的案例:

        对于需要低延迟的实时应用程序,可以使用G1垃圾回收器,以减少垃圾回收的停顿时间。

        对于需要大内存堆的应用程序,可以使用Z垃圾回收器来提高回收效率。

        对于需要更好的内存利用率的应用程序,可以使用G1垃圾回收器来动态划分堆空间,只回收包含垃圾对象的区域。

        需要注意的是,选择合适的垃圾回收器需要根据具体的应用程序需求来进行评估和选择。

2.3.2、优化的JIT编译器

        Java 9中引入了一种名为Graal的新型JIT(Just-In-Time)编译器。Graal是一个全新的JVM即时编译器,其目标是能够更好地优化Java代码并提升性能。以下是Graal JIT编译器的一些概述和案例。

2.3.2.1、实时编译

        Graal是一种即时编译器,它在运行时将Java字节码编译为本地机器码。相比传统的解释执行,即时编译器能够更好地优化代码并提高执行速度。

2.3.2.2、高级优化技术

        Graal使用了一些高级优化技术,如实时分析、基于图的编译和动态编译等。这些技术可以更好地理解和优化代码,提高执行效率。

2.3.2.3、多语言支持

        Graal不仅支持Java语言,还支持其他语言,如JavaScript和Python等。这使得在同一运行环境中可以更方便地同时运行不同语言的代码。

2.3.2.4、AOT编译

        Graal还支持AOT(Ahead-Of-Time)编译,即在程序运行前将Java代码编译成本地机器码。这可以提高程序的启动速度和执行效率。

2.3.2.5、实例

        Graal已经在一些实际案例中得到了应用。例如,Twitter曾经在内部测试中使用Graal JIT编译器,发现其在某些场景下能够将程序的执行速度提升2到5倍。另外,Graal还被用于优化特定领域的代码,如基因组学和数据分析等。

        Java 9中引入的Graal JIT编译器是一种全新的JVM即时编译器,通过使用高级优化技术和支持多语言等特性,可以更好地优化Java代码并提升性能。其优化效果已经在一些实际案例中得到验证。

2.4、改进的集合工厂方法

        在Java 9中,引入了新的集合工厂方法,以简化创建不可变集合的过程。这些工厂方法返回的集合是不可变的,意味着它们不能被修改。这些工厂方法通过使用新的"of"方法,提供了一种更简洁的方式来创建集合对象。

        以下是一些集合工厂方法的概述及其案例:

2.4.1、List.of()

        创建一个不可变的List集合。示例代码如下:

List<String> list = List.of("apple", "banana", "orange");
2.4.2、Set.of()

        创建一个不可变的Set集合。示例代码如下:

Set<String> set = Set.of("apple", "banana", "orange");
2.4.3、Map.of()

        创建一个不可变的Map集合。示例代码如下:

Map<String, Integer> map = Map.of("apple", 1, "banana", 2, "orange", 3);

        需要注意的是,这些工厂方法的参数数量有限制。List.of()和Set.of()方法最多接受10个元素作为参数,Map.of()方法最多接受10对键值对作为参数。如果需要创建更多元素的集合,可以使用Map.ofEntries()方法。

        以上是Java 9中改进的集合工厂方法的概述及其案例。这些工厂方法的引入使得创建不可变集合变得更加简洁和易于使用。

2.5、改进的Stream API

        Java 9中改进的Stream API提供了一些新特性和改进,使其更强大和易用。下面是Java 9中改进的Stream API的概述及其案例:

2.5.1、接口改进

        takeWhile()方法:此方法允许我们提取满足给定条件的元素,一旦遇到不满足条件的元素,就会停止处理。

        示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

List<Integer> result = numbers.stream()

                             .takeWhile(n -> n < 4)

                             .collect(Collectors.toList());

System.out.println(result);  // 输出 [1, 2, 3]

        dropWhile()方法:此方法允许我们跳过满足给定条件的元素,一旦遇到不满足条件的元素,就会开始处理。

        示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

List<Integer> result = numbers.stream()

                             .dropWhile(n -> n < 4)

                             .collect(Collectors.toList());

System.out.println(result);  // 输出 [4, 5, 6]
2.5.2、改进的方法

        ofNullable()方法:此方法允许我们将一个可能为null的元素转换为一个Stream对象,如果元素为null,则返回一个空的Stream。

        示例:

List<String> strings = Arrays.asList("Java", "Stream", null, "API");

strings.stream()

       .flatMap(s -> Stream.ofNullable(s))

       .forEach(System.out::println);  // 输出 Java Stream API

        iterate()方法的重载:iterate()方法现在有一个额外的参数,用于指定终止条件,这使得操作无限流更加方便。

        示例:

Stream.iterate(0, n -> n < 10, n -> n + 2)

      .forEach(System.out::println);  // 输出 0 2 4 6 8

        toList()方法:此方法允许我们将Stream对象转换为一个List对象,这样我们就不需要使用collect(Collectors.toList())方法了。

        示例:

List<Integer> numbers = Stream.of(1, 2, 3, 4, 5)

                              .toList();

System.out.println(numbers);  // 输出 [1, 2, 3, 4, 5]

        Java 9中改进的Stream API提供了一些新特性和改进,使其更强大和易用。以上是Java 9中改进的Stream API的概述及其案例。

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

        Java 9引入了一种新的Try-With-Resources语法,允许在try语句中声明和初始化资源,使代码更加简洁和易读。

        在Java 9中,Try-With-Resources语法得到了改进,引入了一种更简洁的写法。Try-With-Resources是一种异常处理机制,用于自动关闭资源,无需手动调用close()方法。语法的改进使得代码更易读、更简洁。

        在Java 7和8中,我们可以使用Try-With-Resources语法来处理资源的关闭,例如:

try (Resource resource = new Resource()) {

// 使用资源

} catch (Exception e) {
// 处理异常
}

        在Java 9中,我们可以使用改进后的Try-With-Resources语法,将资源的声明和使用放在一起,例如:

try (Resource resource = new Resource()) {

// 使用资源

}

        这种写法的好处是,可以更直观地看到资源的使用范围,并且无需在catch块中显式地关闭资源。

        以下是一个完整的示例,展示了Java 9中改进的Try-With-Resources语法的使用:

import java.io.BufferedReader;

import java.io.FileReader;

import java.io.IOException;

 

public class TryWithResourcesExample {

    public static void main(String[] args) {

        try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {

            String line;

            while ((line = reader.readLine()) != null) {

                System.out.println(line);

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

}

        在这个示例中,我们使用Try-With-Resources语法来打开并读取一个文件。在try语句块中,我们声明了一个BufferedReader对象,并将其初始化为一个FileReader对象。在try语句块结束时,无论是否发生异常,资源都将自动关闭。这样,我们就不需要在catch块中显式地关闭资源。

        Java 9中改进的Try-With-Resources语法使得资源的关闭更简洁和直观。通过将资源的声明和使用放在一起,我们可以更容易地管理资源,并减少代码冗余。

2.7、改进的HTTP/2支持

        Java 9通过引入新的HTTP/2客户端API和服务器API,提供了对HTTP/2协议的原生支持。

        HTTP/2是一种新的协议,旨在提高Web应用程序的性能。以下是Java 9中HTTP/2支持的概述和一些案例:

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

        Java 9引入了对HTTP/2的原生支持,可以轻松地创建和使用HTTP/2客户端和服务器。此前,需要使用第三方库来实现HTTP/2功能。

2.7.2、支持多路复用

        HTTP/2支持多路复用,允许在单个TCP连接上同时进行多个请求和响应。这可以提高网络性能和资源利用率。Java 9的HTTP/2支持允许开发人员使用多路复用功能来提高应用程序的效率。

2.7.3、支持流优先级

        HTTP/2允许为每个请求分配优先级,以便服务器可以根据优先级进行请求处理。在Java 9中,开发人员可以使用新的API来设置和获取请求的优先级。

2.7.4、支持服务器推送

        HTTP/2支持服务器推送功能,服务器可以在客户端请求之前主动推送相关资源。在Java 9中,开发人员可以使用新的API来实现服务器推送功能。

2.7.5、性能改进

        HTTP/2具有更高的吞吐量和更低的延迟,这可以提升Web应用程序的性能。Java 9的HTTP/2支持旨在最大程度地改善性能,并提供更好的用户体验。

        以下是使用Java 9的HTTP/2支持的一个简单案例:

import java.net.URI;

import java.net.http.HttpClient;

import java.net.http.HttpRequest;

import java.net.http.HttpResponse;

 

public class Http2Example {

    public static void main(String[] args) throws Exception {

        HttpClient httpClient = HttpClient.newBuilder()

                .version(HttpClient.Version.HTTP_2)

                .build();

 

        HttpRequest httpRequest = HttpRequest.newBuilder()

                .uri(new URI("https://example.com"))

                .build();

 

        HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());

 

        System.out.println("Response Code: " + response.statusCode());

        System.out.println("Response Body: " + response.body());

    }

}

        以上代码演示了如何使用Java 9的HTTP/2支持发送HTTP请求并获取响应。在此示例中,我们首先创建一个HTTP/2客户端,然后使用该客户端发送对https://example.com的GET请求。最后,我们打印了服务器的响应代码和响应体。

2.8、改进的安全性

        Java 9改进了安全性,引入了一些新的功能和改进,以帮助开发者更好地保护他们的应用程序和用户数据。下面是Java 9中改进的安全性的概述和一些案例:

2.8.1、模块化安全性

        Java 9引入了模块化系统,使得可以更好地保护和隔离代码。模块系统允许开发者定义明确的依赖关系和访问权限,以确保只有授权的模块可以访问敏感代码和数据。

2.8.2、改进的SHA-3支持

        SHA-3是一种新的密码哈希算法,用于保护数据的完整性。Java 9增加了对SHA-3的支持,使开发者能够更好地保护用户密码和其他敏感信息。

2.8.3、去除过时的加密算法

        Java 9移除了一些不安全或不推荐使用的加密算法,例如RC4和MD5。这将帮助开发者更好地保护数据免受已知的攻击。

2.8.4、JEP 229: HTTP/2客户端

        Java 9引入了对HTTP/2协议的支持,这是一种更安全和高效的网络传输协议。HTTP/2客户端提供了更强大的安全功能,如服务器推送和流量控制,以提高应用程序的性能和安全性。

2.8.5、JEP 274

        加强对Java平台API的安全性:Java 9增加了对Java平台API的安全验证,以防止恶意代码利用这些API进行攻击。这将帮助开发者更好地保护他们的应用程序免受安全漏洞。

        这些是Java 9中改进的安全性的一些方面和案例。通过这些改进,开发者可以更好地保护他们的应用程序和用户数据,并提高应用程序的性能和安全性。

2.9、改进的编译工具

        Java 9中引入了许多改进的编译工具,包括一些新的命令行选项和功能增强。下面是其中一些重要的改进概述和实例案例:

2.9.1、支持模块化编译

        Java 9引入了一种新的模块化编译选项 --module-source-path,允许在一个独立的模块路径下编译模块化的Java项目。这种模块化编译能够更好地管理和维护大型项目,并且可以提高编译和运行时的性能。

        例如,可以使用以下命令编译模块化的Java项目:

javac --module-source-path src -d out $(find src -name '*.java')
2.9.2、增强的注解处理器

        Java 9中的javac命令行选项增加了一些有用的注解处理器选项,如-XprintProcessorInfo,-XprintRounds等。这些选项可以显示注解处理器的详细信息,并提供更好的调试和分析能力。

        例如,可以使用以下命令查看注解处理器的详细信息:

javac -XprintProcessorInfo -d out -processor com.example.MyProcessor $(find src -name '*.java')
2.9.3、编译时类型检查的改进

        Java 9通过改进之前的-JavaDoc选项,增加对类型检查的支持。这意味着在编译时,编译器将更严格地检查类型,以减少运行时类型转换错误。

        例如,可以使用以下命令启用更严格的类型检查:

javac -Xlint:strict $(find src -name '*.java')

        这些是Java 9中改进的编译工具的一些概述和实例案例。这些改进可以提高编译过程的效率和质量,并为开发人员提供更好的调试和分析能力。

三、Java 11新特性

3.1、动态类文件常量

        Java语言规范中引入了新的invokedynamic指令,以便支持动态类文件常量。这使得在运行时修改常量池中的内容成为可能。

3.2、Epsilon垃圾收集器

        Epsilon是一种新的垃圾收集器,它不执行任何实际的垃圾收集操作,而是简单地将所有对象分配到一块内存区域中。这使得在性能测试中可以轻松运行Java应用程序,而不会受到垃圾收集的干扰。

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

        Java 11支持Unicode 10版本,这意味着它可以在字符串中使用更多的特殊字符和表情符号。

3.4、合并Java EE和Java SE功能

        在Java 11中,Java EE已经从Java SE中分离出来,并作为一个独立的项目进行开发和维护。这使得Java EE的发展可以更加灵活和独立于Java SE的版本更新。

3.5、HTTP/2客户端 API

        Java 11引入了新的HTTP/2客户端API,以便更轻松地使用HTTP/2协议进行网络通信。这个API提供了更好的性能和效率,并且支持异步和流式处理。

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

        Java 11允许在Lambda表达式中使用"var"关键字来声明局部变量的类型。这使得代码更加简洁和易读,而无需显式地指定变量类型。

        这只是Java 11中新增的一些特性的一部分,还有其他一些更新和改进。有关完整的特性列表,请参考官方文档。

四、Java 17 新特性

4.1、Sealed Classes

        Sealed classes 允许开发人员限制继承和实现的类和接口,从而提高代码的安全性和可维护性。

4.2、Pattern Matching for switch

        Pattern Matching for switch 允许开发人员在 switch 语句中使用更强大的模式匹配功能,从而简化代码和提高可读性。

4.3、Record Classes

        Record classes 是一种新的类定义方式,它自动为类生成 equals()、hashCode() 和 toString() 方法,从而简化了对于数据类的创建和使用。

4.4、Sealed Interfaces

        Sealed interfaces 类似于 sealed classes,允许开发人员限制实现的接口,从而提高代码的安全性和可维护性。

4.5、Foreign Function & Memory API

        Foreign Function & Memory API 提供了与本地代码交互的新方式,从而提高了 Java 与其他语言(如 C、C++)的互操作性。

4.6、Strong encapsulation of JDK internals

        Java 17 对 JDK 内部的一些类和方法进行了强封装,提高了代码的安全性和可维护性。

        这些新特性对于开发人员来说,提供了更多的工具和功能,可以更轻松地开发高效、安全的 Java 应用程序。

Java 8引入了许多新特性,以下是其中一些重要的特性: 1. Lambda表达式:Lambda表达式是Java 8中最重要的特性之一。它允许我们以更简洁的方式编写匿名函数。Lambda表达式可以作为参数传递给方法,或者用于函数式接口的实现。 2. 函数式接口:函数式接口是只包含一个抽象方法的接口。Java 8引入了一些新的函数式接口,如`Predicate`、`Consumer`、`Supplier`和`Function`等。这些接口可以与Lambda表达式一起使用,使得编写函数式代码更加方便。 3. Stream API:Stream API是Java 8中处理集合数据的新方式。它提供了一种流式操作集合的方式,可以进行过滤、映射、排序等操作。Stream API可以大大简化集合数据的处理,并且可以利用多核处理器进行并行操作。 4. 默认方法:默认方法是接口中的一个新特性,它允许在接口中定义具有默认实现的方法。这样一来,当接口的实现类没有实现该方法时,就会使用默认实现。 5. 方法引用:方法引用是一种更简洁地调用已经存在的方法的方式。它可以替代Lambda表达式,使得代码更加简洁易读。 6. 新的日期和时间API:Java 8引入了全新的日期和时间API,取代了旧的`java.util.Date`和`java.util.Calendar`类。新的API提供了更好的日期和时间处理方式,并且支持时区、日历等功能。 7. Optional类:Optional类是一个容器对象,可以包含null或非null的值。它可以避免空指针异常,并且提供了一些便利的方法来处理可能为空的值。 8. 并发增强:Java 8对并发编程进行了一些增强,引入了新的并发工具类,如`CompletableFuture`和`StampedLock`,以及新的并行操作方式。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

韩悸桉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值