Last Updated:

Java 17 中的新功能

银狐
Java 17 徽标

 

Java 17是一个长期支持 (LTS) 版本,已于 2021 年 9 月 14 日正式发布,请在此处下载 Java 17

Java 17 有 14 个 JEP 项。

Java 17 开发人员功能。
伪随机数生成器、开关模式匹配(预览)、密封类(标准功能)、外部函数和内存 API(孵化器)、动态反序列化过滤器。

1. JEP 306:恢复始终严格的浮点语义

此 JEP 用于数字敏感程序,主要是科学目的;它再次严格默认浮点运算,或Strictfp确保每个平台上的浮点计算结果相同。

简短的历史

  1. 在 Java 1.2 之前,所有的浮点计算都是严格的;它导致基于 x87 的硬件过热。
  2. 从 Java 1.2 开始,我们需要关键字strictfp来启用严格的浮点计算。默认浮点计算已从严格的浮点计算更改为略有不同的浮点计算(避免过热问题)。
  3. 现在,由于 Intel 和 AMD 都支持SSE2(Streaming SIMD Extensions 2)扩展,可以支持严格的 JVM 浮点运算而不会过热,因此,之前(Java 1.2 之前)基于 x87 的硬件上的过热问题是在当今的硬件中不敬。
  4. Java 17 将 Java 1.2 之前的严格浮点计算恢复为默认值,这意味着关键字strictfp现在是可选的。
 

2. JEP 356:增强型伪随机数生成器

该 JEP 引入了一个新接口,称为RandomGenerator使未来的伪随机数生成器 (PRNG)算法更易于实现或使用。

RandomGenerator.java

package java.util.random;

public interface RandomGenerator {
  //...
}

下面的示例使用新的 Java 17RandomGeneratorFactory来获得著名的Xoshiro256PlusPlusPRNG 算法,以生成特定范围 0 – 10 内的随机整数。

JEP356.java

package com.mkyong.java17.jep356;

import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;

public class JEP356 {

  public static void main(String[] args) {

      // legacy
      // RandomGeneratorFactory.of("Random").create(42);

      // default L32X64MixRandom
      // RandomGenerator randomGenerator = RandomGeneratorFactory.getDefault().create();

      // Passing the same seed to random, and then calling it will give you the same set of numbers
      // for example, seed = 999
      RandomGenerator randomGenerator = RandomGeneratorFactory.of("Xoshiro256PlusPlus").create(999);

      System.out.println(randomGenerator.getClass());

      int counter = 0;
      while(counter<=10){
          // 0-10
          int result = randomGenerator.nextInt(11);
          System.out.println(result);
          counter++;
      }

  }
}

输出

终端

class jdk.random.Xoshiro256PlusPlus
4
6
9
5
7
6
5
0
6
10
4

以下代码生成所有 Java 17 PRNG 算法。


  RandomGeneratorFactory.all()
              .map(fac -> fac.group()+ " : " +fac.name())
              .sorted()
              .forEach(System.out::println);

输出

终端

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

Java 17 还重构了遗留的随机类,如java.util.Random,SplittableRandomSecureRandom扩展新RandomGenerator接口。

3. JEP 382:新的 macOS 渲染管线

Apple 在macOS 10.14 版本(2018 年 9 月)中弃用了 OpenGL 渲染库,转而支持新的Metal 框架以获得更好的性能。

此 JEP 将 macOS 的 Java 2D(如 Swing GUI)内部渲染管道从 Apple OpenGL API 更改为 Apple Metal API;这是一个内部变化;没有新的 Java 2D API,也没有更改任何现有的 API。

4. JEP 391:macOS/AArch64 端口

Apple 有一个将其 Mac 从 x64 过渡到 AArch64(例如 Apple M1 处理器)的长期计划。

此 JEP 端口 JDK 可在 macOS 上的 AArch64 平台上运行。

5. JEP 398:弃用 Applet API 以进行删除

Java小程序API,因为大多数的网页浏览器的移除了用于Java的浏览器插件的支持是无关紧要的。

Java 9 弃用了 Applet API。


@Deprecated(since = "9")
public class Applet extends Panel {
  //...
}

此 JEP 将 Applet API 标记为要删除。


@Deprecated(since = "9", forRemoval = true)
@SuppressWarnings("removal")
public class Applet extends Panel {
  //...
}

6. JEP 403:强封装JDK内部

许多第三方库、框架和工具正在访问 JDK 的内部 API 和包。Java 16、JEP 396默认进行了强封装(我们不允许轻易访问内部 API)。但是,我们仍然可以使用--illegal-access切换到简单封装来仍然访问内部 API。

这个 JEP 是上述 Java 16 JEP 396 的后继者,它通过删除--illegal-access选项又进了一步,这意味着我们无法访问内部 API,除了关键的内部 API,如sun.misc.Unsafe.

--illegal-access=warn在 Java 17 中尝试。

终端

java --illegal-access=warn

OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=warn; support was removed in 17.0  

7. JEP 406:开关模式匹配(预览)

此 JEP 为switch语句和表达式添加了模式匹配。由于这是一个预览功能,我们需要使用--enable-preview选项来启用它。

7.1 if...else 链

在 Java 17 之前,我们通常使用一系列if...else测试来测试几种可能性。

JEP406.java

package com.mkyong.java17.jep406;

public class JEP406 {

  public static void main(String[] args) {

      System.out.println(formatter("Java 17"));
      System.out.println(formatter(17));

  }

  static String formatter(Object o) {
      String formatted = "unknown";
      if (o instanceof Integer i) {
          formatted = String.format("int %d", i);
      } else if (o instanceof Long l) {
          formatted = String.format("long %d", l);
      } else if (o instanceof Double d) {
          formatted = String.format("double %f", d);
      } else if (o instanceof String s) {
          formatted = String.format("String %s", s);
      }
      return formatted;
  }

}

在 Java 17 中,我们可以像这样重写上面的代码:

JEP406.java

package com.mkyong.java17.jep406;

public class JEP406 {

    public static void main(String[] args) {

        System.out.println(formatterJava17("Java 17"));
        System.out.println(formatterJava17(17));

    }

    static String formatterJava17(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();
        };
    }

}

7.2 模式匹配和空值

现在,我们可以测试nullswitch直接。

旧代码。

JEP406.java

package com.mkyong.java17.jep406;

public class JEP406 {

  public static void main(String[] args) {

      testString("Java 16");  // Ok
      testString("Java 11");  // LTS
      testString("");         // Ok
      testString(null);       // Unknown!
  }

  static void testString(String s) {
      if (s == null) {
          System.out.println("Unknown!");
          return;
      }
      switch (s) {
          case "Java 11", "Java 17"   -> System.out.println("LTS");
          default                     -> System.out.println("Ok");
      }
  }

}

新代码。

JEP406.java

package com.mkyong.java17.jep406;

public class JEP406 {

    public static void main(String[] args) {

        testStringJava17("Java 16");  // Ok
        testStringJava17("Java 11");  // LTS
        testStringJava17("");         // Ok
        testStringJava17(null);       // Unknown!
    }

    static void testStringJava17(String s) {
        switch (s) {
            case null                   -> System.out.println("Unknown!");
            case "Java 11", "Java 17"   -> System.out.println("LTS");
            default                     -> System.out.println("Ok");
        }
    }

}

7.3 在 switch 中细化模式

查看以下代码片段。为了测试Triangle tand t.calculateArea(),我们需要创建一个额外的if条件。


class Shape {}
  class Rectangle extends Shape {}
  class Triangle  extends Shape {
      int calculateArea(){
          //...
      } }

  static void testTriangle(Shape s) {
      switch (s) {
          case null:
              break;
          case Triangle t:
              if (t.calculateArea() > 100) {
                  System.out.println("Large triangle");
                  break;
              }else{
                  System.out.println("Triangle");
              }
          default:
              System.out.println("Unknown!");
      }
  }

Java 17 允许所谓的重新定义模式或guarded patterns如下所示:


  static void testTriangle2(Shape s) {
      switch (s) {
          case null ->
                  {}
          case Triangle t && (t.calculateArea() > 100) ->
                  System.out.println("Large triangle");
          case Triangle t ->
                  System.out.println("Triangle");
          default ->
                  System.out.println("Unknown!");
      }
  }

进一步阅读

有关更多示例和解释,请访问此JEP 406:开关模式匹配(预览版)

8. JEP 407:删除 RMI 激活

Java 15,JEP385弃用了RMI 激活以进行删除。

此 JEP 删除了 RMI 激活或java.rmi.activation包。

9. JEP 409:密封类

Java 15、JEP 360和 Java 16、JEP 397引入了 [密封类 (https://cr.openjdk.java.net/~briangoetz/amber/datum.html) 作为预览功能。

该 JEP 将密封类最终确定为 Java 17 中的标准特性,与 Java 16 没有任何变化。

密封的类和接口控制或限制谁可以是子类型。


public sealed interface Command
    permits LoginCommand, LogoutCommand, PluginCommand{
    //...
}

进一步阅读

10. JEP 410:删除实验性 AOT 和 JIT 编译器

Java 9、JEP 295引入了提前编译(jaotc工具)作为实验性功能。在 Java 10 之后,JEP 317再次提出它作为实验性 JIT 编译器。

然而,这个特性自从被引入以来几乎没有什么用,并且需要付出大量的努力来维护它,所以这个 JEP 删除了实验性的基于 Java 的提前 (AOT)即时 (JIT)编译器

删除了以下 AOT 包、类、工具和代码:

  • jdk.aot — jaotc 工具
  • jdk.internal.vm.compiler — Graal 编译器
  • jdk.internal.vm.compiler.management - Graal 的 MBean
  • src/hotspot/share/aot — 转储和加载 AOT 代码
  • 由保护的附加代码 #if INCLUDE_AOT

11. JEP 411:弃用安全管理器以进行删除

Java 1.0 引入了安全管理器来保护客户端 Java 代码,现在已经不相关了。

此 JEP 弃用要删除的安全管理器。

SecurityManager.java

package java.lang;

 * @since   1.0
 * @deprecated The Security Manager is deprecated and subject to removal in a
 *       future release. There is no replacement for the Security Manager.
 *       See <a href="https://openjdk.java.net/jeps/411">JEP 411</a> for
 *       discussion and alternatives.
 */
@Deprecated(since="17", forRemoval=true)
public class SecurityManager {
  //...
}

12. JEP 412:外部函数和内存 API(孵化器)

这个Foreign Function & Memory API允许开发者访问JVM外的代码(外来函数)、存储在JVM外的数据(堆外数据),以及访问非JVM管理的内存(外来内存)。

PS 这是一个孵化功能;需要添加--add-modules jdk.incubator.foreign来编译和运行 Java 代码。

历史

  • Java 14 JEP 370引入了外部内存访问 API(孵化器)。
  • Java 15 JEP 383引入了外部内存访问 API(第二孵化器)。
  • Java 16 JEP 389引入了外部链接器 API(孵化器)。
  • Java 16 JEP 393引入了外部内存访问 API(第三孵化器)。
  • Java 17 JEP 412引入了外部函数和内存 API(孵化器)。

请参阅Java 16 中以前的外部链接器 API 示例

13. JEP 414:Vector API(第二孵化器)

Java 16、JEP 414引入了新的 Vector API 作为孵化 API

此 JEP 改进了 Vector API 性能和其他增强功能,例如对字符的支持操作、字节向量与布尔数组之间的相互转换等。

14. JEP 415:特定于上下文的反序列化过滤器

在 Java 中,反序列化不受信任的数据是危险的,请阅读OWASP – 不受信任数据的反序列化Brian Goetz – Towards Better Serialization

Java 9、JEP 290引入了序列化过滤来帮助防止反序列化漏洞。

14.1 以下示例使用模式创建自定义过滤器。

DdosExample.java

package com.mkyong.java17.jep415;

import java.io.Serializable;

public class DdosExample implements Serializable {
  @Override
  public String toString() {
      return "running ddos...!";
  }
}
JEP290.java

package com.mkyong.java17.jep415;

import java.io.*;

public class JEP290 {

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

      byte[] bytes = convertObjectToStream(new DdosExample());
      InputStream is = new ByteArrayInputStream(bytes);
      ObjectInputStream ois = new ObjectInputStream(is);

      // Setting a Custom Filter Using a Pattern
      // need full package path
      // the maximum number of bytes in the input stream = 1024
      // allows classes in com.mkyong.java17.jep415.*
      // allows classes in the java.base module
      // rejects all other classes !*
      ObjectInputFilter filter1 =
              ObjectInputFilter.Config.createFilter(
                  "maxbytes=1024;com.mkyong.java17.jep415.*;java.base/*;!*");
      ois.setObjectInputFilter(filter1);

      try {
          Object obj = ois.readObject();
          System.out.println("Read obj: " + obj);
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
  }

  private static byte[] convertObjectToStream(Object obj) {
      ByteArrayOutputStream boas = new ByteArrayOutputStream();
      try (ObjectOutputStream ois = new ObjectOutputStream(boas)) {
          ois.writeObject(obj);
          return boas.toByteArray();
      } catch (IOException ioe) {
          ioe.printStackTrace();
      }
      throw new RuntimeException();
  }

}

输出

终端

  Read obj: running ddos...!

下面的示例将拒绝包中的所有类com.mkyong.java17.jep415.*


  byte[] bytes = convertObjectToStream(new DdosExample());

  ObjectInputFilter filter1 =
        ObjectInputFilter.Config.createFilter(
          "!com.mkyong.java17.jep415.*;java.base/*;!*");  

重新运行它;这一次,我们将无法反序列化对象。

终端

  Exception in thread "main" java.io.InvalidClassException: filter status: REJECTED
    at java.base/java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1412)

14.2 下面的例子创建了一个反序列化过滤器来拒绝所有扩展JComponent.

JComponentExample.java

package com.mkyong.java17.jep415;

import javax.swing.*;
import java.io.Serializable;

public class JComponentExample extends JComponent implements Serializable {
}
JEP290_B.java

package com.mkyong.java17.jep415;

import javax.swing.*;
import java.io.*;

public class JEP290_B {

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

        byte[] bytes = convertObjectToStream(new JComponentExample());
        InputStream is = new ByteArrayInputStream(bytes);
        ObjectInputStream ois = new ObjectInputStream(is);

        ois.setObjectInputFilter(createObjectFilter());

        try {
            Object obj = ois.readObject();
            System.out.println("Read obj: " + obj);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    // reject all JComponent classes
    private static ObjectInputFilter createObjectFilter() {
        return filterInfo -> {
            Class<?> clazz = filterInfo.serialClass();
            if (clazz != null) {
                return (JComponent.class.isAssignableFrom(clazz))
                        ? ObjectInputFilter.Status.REJECTED
                        : ObjectInputFilter.Status.ALLOWED;
            }
            return ObjectInputFilter.Status.UNDECIDED;
        };
    }

    private static byte[] convertObjectToStream(Object obj) {
        ByteArrayOutputStream boas = new ByteArrayOutputStream();
        try (ObjectOutputStream ois = new ObjectOutputStream(boas)) {
            ois.writeObject(obj);
            return boas.toByteArray();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        throw new RuntimeException();
    }

}

输出

终端

Exception in thread "main" java.io.InvalidClassException: filter status: REJECTED
at java.base/java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1412)
at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2053)
at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1907)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2209)
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1742)
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:514)
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:472)
at com.mkyong.java17.jep415.JEP290_B.main(JEP290_B.java:17)

14.3 Java的17加allowFilterrejectFilterObjectInputFilter界面来创建反序列化过滤器更快。


  allowFilter(Predicate<Class<?>>, ObjectInputFilter.Status)

  rejectFilter(Predicate<Class<?>>, ObjectInputFilter.Status)

对于 14.2 中的上述示例,现在我们可以重构如下代码:


    // Java 9
    private static ObjectInputFilter createObjectFilter() {
        return filterInfo -> {
            Class<?> clazz = filterInfo.serialClass();
            if (clazz != null) {
                return (JComponent.class.isAssignableFrom(clazz))
                        ? ObjectInputFilter.Status.REJECTED
                        : ObjectInputFilter.Status.ALLOWED;
            }
            return ObjectInputFilter.Status.UNDECIDED;
        };
    }

    // Java 17
    // reject all JComponent classes
    ObjectInputFilter jComponentFilter = ObjectInputFilter.rejectFilter(
            JComponent.class::isAssignableFrom,
            ObjectInputFilter.Status.UNDECIDED);
    ois.setObjectInputFilter(jComponentFilter);

14.4 回到 Java 17,此 JEP 415 引入了过滤器工厂的概念 a BinaryOperator,以动态或特定于上下文选择不同的反序列化过滤器。工厂决定如何组合两个过滤器或更换过滤器。

下面是结合两个反序列化过滤器的 Java 17 过滤器工厂示例。

JEP415_B.java

package com.mkyong.java17.jep415;

import java.io.*;
import java.util.function.BinaryOperator;

public class JEP415_B {

  static class PrintFilterFactory implements BinaryOperator<ObjectInputFilter> {

      @Override
      public ObjectInputFilter apply(
              ObjectInputFilter currentFilter, ObjectInputFilter nextFilter) {

          System.out.println("Current filter: " + currentFilter);
          System.out.println("Requested filter: " + nextFilter);

          // Returns a filter that merges the status of a filter and another filter
          return ObjectInputFilter.merge(nextFilter, currentFilter);

          // some logic and return other filters
          // reject all JComponent classes
          /*return filterInfo -> {
              Class<?> clazz = filterInfo.serialClass();
              if (clazz != null) {
                  if(JComponent.class.isAssignableFrom(clazz)){
                      return ObjectInputFilter.Status.REJECTED;
                  }
              }
              return ObjectInputFilter.Status.ALLOWED;
          };*/

      }
  }

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

      // Set a filter factory
      PrintFilterFactory filterFactory = new PrintFilterFactory();
      ObjectInputFilter.Config.setSerialFilterFactory(filterFactory);

      // create a maxdepth and package filter
      ObjectInputFilter filter1 =
              ObjectInputFilter.Config.createFilter(
                  "com.mkyong.java17.jep415.*;java.base/*;!*");
      ObjectInputFilter.Config.setSerialFilter(filter1);

      // Create a filter to allow String.class only
      ObjectInputFilter intFilter = ObjectInputFilter.allowFilter(
              cl -> cl.equals(String.class), ObjectInputFilter.Status.REJECTED);

      // if pass anything other than String.class, hits filter status: REJECTED
      //byte[] byteStream =convertObjectToStream(99);

      // Create input stream
      byte[] byteStream =convertObjectToStream("hello");
      InputStream is = new ByteArrayInputStream(byteStream);
      ObjectInputStream ois = new ObjectInputStream(is);

      ois.setObjectInputFilter(intFilter);

      try {
          Object obj = ois.readObject();
          System.out.println("Read obj: " + obj);
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
  }

  private static byte[] convertObjectToStream(Object obj) {
      ByteArrayOutputStream boas = new ByteArrayOutputStream();
      try (ObjectOutputStream ois = new ObjectOutputStream(boas)) {
          ois.writeObject(obj);
          return boas.toByteArray();
      } catch (IOException ioe) {
          ioe.printStackTrace();
      }
      throw new RuntimeException();
  }

}

输出

终端

Current filter: null
Requested filter: com.mkyong.java17.jep415.*;java.base/*;!*
Current filter: com.mkyong.java17.jep415.*;java.base/*;!*
Requested filter: predicate(
    com.mkyong.java17.jep415.JEP415_B$$Lambda$22/0x0000000800c01460@15aeb7ab,
      ifTrue: ALLOWED, ifFalse:REJECTED)
Read obj: hello

进一步阅读

请阅读以下链接以获取更多反序列化过滤器示例:

下载源代码

$ git clone https://github.com/mkyong/core-java

$ cd java-17

参考