Java 17 中的新功能

Java 17是一个长期支持 (LTS) 版本,已于 2021 年 9 月 14 日正式发布,请在此处下载 Java 17。
Java 17 有 14 个 JEP 项。
- 1. JEP 306:恢复始终严格的浮点语义
- 2. JEP 356:增强型伪随机数生成器
- 3. JEP 382:新的 macOS 渲染管线
- 4. JEP 391:macOS/AArch64 端口
- 5. JEP 398:弃用 Applet API 以进行删除
- 6. JEP 403:强封装JDK内部
- 7. JEP 406:开关模式匹配(预览)
- 8. JEP 407:删除 RMI 激活
- 9. JEP 409:密封类
- 10. JEP 410:删除实验性 AOT 和 JIT 编译器
- 11. JEP 411:弃用安全管理器以进行删除
- 12. JEP 412:外部函数和内存 API(孵化器)
- 13. JEP 414:Vector API(第二孵化器)
- 14. JEP 415:特定于上下文的反序列化过滤器
- 下载源代码
- 参考
Java 17 开发人员功能。
伪随机数生成器、开关模式匹配(预览)、密封类(标准功能)、外部函数和内存 API(孵化器)、动态反序列化过滤器。
1. JEP 306:恢复始终严格的浮点语义
此 JEP 用于数字敏感程序,主要是科学目的;它再次严格默认浮点运算,或Strictfp确保每个平台上的浮点计算结果相同。
简短的历史
- 在 Java 1.2 之前,所有的浮点计算都是严格的;它导致基于 x87 的硬件过热。
- 从 Java 1.2 开始,我们需要关键字strictfp来启用严格的浮点计算。默认浮点计算已从严格的浮点计算更改为略有不同的浮点计算(避免过热问题)。
- 现在,由于 Intel 和 AMD 都支持SSE2(Streaming SIMD Extensions 2)扩展,可以支持严格的 JVM 浮点运算而不会过热,因此,之前(Java 1.2 之前)基于 x87 的硬件上的过热问题是在当今的硬件中不敬。
- Java 17 将 Java 1.2 之前的严格浮点计算恢复为默认值,这意味着关键字strictfp现在是可选的。
2. JEP 356:增强型伪随机数生成器
该 JEP 引入了一个新接口,称为RandomGenerator使未来的伪随机数生成器 (PRNG)算法更易于实现或使用。
package java.util.random;
public interface RandomGenerator {
  //...
}
下面的示例使用新的 Java 17RandomGeneratorFactory来获得著名的Xoshiro256PlusPlusPRNG 算法,以生成特定范围 0 – 10 内的随机整数。
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,SplittableRandom并SecureRandom扩展新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测试来测试几种可能性。
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 中,我们可以像这样重写上面的代码:
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 模式匹配和空值
现在,我们可以测试null中switch直接。
旧代码。
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");
      }
  }
}
新代码。
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 激活
此 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 弃用要删除的安全管理器。
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 以下示例使用模式创建自定义过滤器。
package com.mkyong.java17.jep415;
import java.io.Serializable;
public class DdosExample implements Serializable {
  @Override
  public String toString() {
      return "running ddos...!";
  }
}
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.
package com.mkyong.java17.jep415;
import javax.swing.*;
import java.io.Serializable;
public class JComponentExample extends JComponent implements Serializable {
}
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加allowFilter和rejectFilter的ObjectInputFilter界面来创建反序列化过滤器更快。
  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 过滤器工厂示例。
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