Java 17中新功能的详细介绍

403 阅读13分钟

Java 17是一个长期支持(LTS)版本,于2021年9月14日达到普遍可用,请在这里下载Java 17

Java 17有14个JEP项目:

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

1.JEP 306:恢复总是严格的浮点语义

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

短暂的历史

  1. 在Java 1.2之前,所有的浮点计算都是严格的;而且在基于x87的硬件上会造成过热。
  2. 从Java 1.2开始,我们需要使用关键字strictfp 来启用严格浮点计算。默认的浮点计算从严格改为微妙不同的浮点计算(避免过热问题)。
  3. 现在,由于英特尔和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 ,以获得著名的Xoshiro256PlusPlus PRNG算法来生成特定范围内的随机整数,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渲染管线

苹果在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 端口

苹果公司有一个长期计划,将其Mac从x64过渡到AArch64(例如,苹果M1处理器)。

该JEP将JDK移植到macOS的AArch64平台上运行。

进一步阅读

5.JEP 398:废弃Applet API的删除

Java Applet 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 ,切换到简单的封装,以便仍然可以访问内部的APIs。

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

试试Java 17中的--illegal-access=warn

终端

java --illegal-access=warn

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

进一步阅读

7.JEP 406: switch的模式匹配(预览)

这个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 模式匹配和空

现在我们可以直接测试switch 中的null

旧的代码

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 细化开关中的模式

回顾下面的代码片断。为了测试Triangle tt.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:switch的模式匹配(预览)

8.JEP 407:删除RMI激活

Java 15中,JEP385删除了RMI激活功能

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

进一步阅读

9.JEP 409:密封类

Java 15的JEP 360和Java 16的JEP 397引入了[密封类(cr.openjdk.java.net/~briangoetz…

这个JEP最终将密封类作为Java 17的标准特性,与Java 16相比没有任何变化。

密封类和接口控制或限制谁可以成为一个子类型。


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

进一步阅读

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

Java 9,JEP 295引入了Ahead-of-time编译(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:Foreign Function & Memory API(孵化器)

这个外来函数和内存API允许开发者访问JVM之外的代码(外来函数)、存储在JVM之外的数据(堆外数据),以及访问不由JVM管理的内存(外来内存)。

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

历史

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

请参考Java 16中以前的Foreign Linker API例子

进一步阅读

13.JEP 414:向量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为ObjectInputFilter 接口增加了allowFilterrejectFilter ,以便更快地创建反序列化过滤器。


  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引入了一个过滤器工厂的概念,即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 clonegithub.com/mkyong/core…

$ cd java-17

参考资料