Рубрики
Без рубрики

Что нового в Java 17

Java 17 содержит 14 элементов JEP, основные функции включают генератор псевдослучайных чисел, сопоставление шаблонов для коммутатора (предварительный просмотр), запечатанный класс (стандартная функция), API внешних функций и памяти (инкубатор), фильтры динамической десериализации, также устаревшие активация RMI, API апплетов и менеджер безопасности для удаления и т. Д.

Java 17 является выпуском с долгосрочной поддержкой (LTS), который стал общедоступным 14 сентября 2021 года, загрузите Java 17 здесь .

Java 17 содержит 14 элементов JEP.

  • 1. JEP 306: Восстановить Всегда Строгую Семантику С Плавающей Запятой
  • 2. JEP 356: Усовершенствованные генераторы Псевдослучайных чисел
  • 3. JEP 382: Новый конвейер рендеринга mac OS
  • 4. JEP 391: Порт macOS/AArch64
  • 5. JEP 398: Не рекомендуется использовать API апплета для удаления
  • 6. JEP 403: Строго инкапсулировать JDKInternals
  • 7. JEP 406: Сопоставление шаблонов для переключателя (предварительный просмотр)
    • 7.1 если… другая цепочка
    • 7.2 Сопоставление с образцом и нулевое значение
    • 7.3 Уточнение шаблонов в коммутаторе
  • 8. JEP 407: Удалить активацию RMI
  • 9. JEP 409: Закрытые Классы
  • 10. JEP 410: Удалите экспериментальный компилятор AOT и JIT
  • 11. JEP 411: Откажитесь от Менеджера безопасности для удаления
  • 12. JEP 412: API внешних функций и памяти (Инкубатор)
  • 13. JEP 414: Векторный API (Второй инкубатор)
  • 14. JEP 415: Фильтры десериализации, Зависящие от контекста
  • Скачать Исходный Код
  • Рекомендации

Функции разработчика Java 17. генератор псевдослучайных чисел, сопоставление шаблонов для коммутатора (предварительный просмотр), закрытый класс (стандартная функция), API внешней функции и памяти (инкубатор), фильтры динамической десериализации.

1. JEP 306: Восстановить Всегда Строгую Семантику С Плавающей Запятой

Этот ДЖИП предназначен для программ, чувствительных к числовым данным, в основном для научных целей; Он снова сделал операции с плавающей запятой по умолчанию строгими или Strictfp , гарантируя одинаковые результаты вычислений с плавающей запятой на каждой платформе.

Краткая история

  1. До Java 1.2 все вычисления с плавающей запятой были строгими; и это приводило к перегреву оборудования на базе x87.
  2. Начиная с Java 1.2, нам нужно ключевое слово strictfp , чтобы включить строгий расчет с плавающей запятой. Расчет с плавающей запятой по умолчанию был изменен со строгого на слегка отличающиеся вычисления с плавающей запятой (избегайте проблем с перегревом).
  3. Теперь, поскольку Intel и AMD поддерживают оба SSE2 (потоковые расширения SIMD 2) расширения, которые могли бы поддерживать строгие операции с плавающей запятой JVM без перегревов, таким образом, предыдущие (до Java 1.2) проблемы с перегревом на оборудовании на базе x87 являются непочтительными в современном оборудовании.
  4. Java 17 восстанавливает строгие вычисления с плавающей запятой до Java 1.2 по умолчанию, что означает, что ключевое слово strictfp теперь необязательно.

Дальнейшее Чтение

2. JEP 356: Усовершенствованные генераторы Псевдослучайных чисел

Этот ДЖИП представил новый интерфейс под названием Генератор случайных чисел , чтобы упростить реализацию или использование будущих генераторов псевдослучайных чисел (PRNG) алгоритмов.

package java.util.random;

public interface RandomGenerator {
  //...
}

В приведенном ниже примере используется новая Java 17 randomgeneratorfactory , чтобы получить знаменитые Xoshiro256PlusPlus Алгоритмы PRNG для генерации случайных целых чисел в определенном диапазоне от 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. Случайный , Расщепляемый случай и SecureRandom для расширения нового RandomGenerator интерфейса.

Дальнейшее Чтение

3. JEP 382: Новый конвейер рендеринга mac OS

Apple отказалась от библиотеки рендеринга OpenGL в выпуске mac OS 10.14 (сентябрь 2018) в пользу нового Металлического каркаса для повышения производительности.

Этот JEEP изменяет внутренний конвейер рендеринга Java 2D (например, графический интерфейс Swing) для macOS с API Apple OpenGL на API Apple Metal; это внутреннее изменение; новых API Java 2D нет, и никакие существующие API не изменяются.

Дальнейшее Чтение

4. JEP 391: Порт macOS/AArch64

У Apple есть долгосрочный план по переводу своего Mac с x64 на AArch64 (например, процессоры Apple M1).

Этот JEP-порт JDK для запуска на платформах AArch64 в mac OS.

Дальнейшее Чтение

5. JEP 398: Не рекомендуется использовать API апплета для удаления

API Java-апплета не имеет значения, потому что большинство веб-браузеров удалили поддержку плагинов Java-браузера.

Java 9 устарел API апплета.

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

Этот JEP пометил API апплета для удаления.

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

Дальнейшее Чтение

6. JEP 403: Строго инкапсулировать JDKInternals

Многие сторонние библиотеки, платформы и инструменты получают доступ к внутренним API и пакетам JDK. Java 16, JEP 396 по умолчанию обеспечивает надежную инкапсуляцию (нам не разрешен легкий доступ к внутренним API). Тем не менее, мы все еще можем использовать --незаконный доступ для переключения на простую инкапсуляцию, чтобы по-прежнему получать доступ к внутренним API.

Этот JEP является преемником вышеупомянутого Java 16 JEP 396, и он делает еще один шаг, удаляя параметр --незаконный доступ , что означает, что у нас нет способов доступа к внутренним API, за исключением критических внутренних API, таких как sun.misc. Небезопасно .

Попробуйте --незаконный доступ =предупредить в 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 добавляет сопоставление шаблонов для переключателей операторов и выражений. Поскольку это функция предварительного просмотра, нам нужно использовать опцию --enable-предварительный просмотр , чтобы включить ее.

7.1 если… другая цепочка

До 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 вход переключатель напрямую.

Старый кодекс.

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 Уточнение шаблонов в коммутаторе

Просмотрите приведенный ниже фрагмент кода. Чтобы проверить Треугольник t и т.вычислить площадь() , нам нужно создать дополнительное если условие.

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 допускает так называемые предопределенные шаблоны или защищенные шаблоны , как показано ниже:

  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 для удаления.

Этот ДЖИП удалил активацию RMI или java.rmi.activation пакет.

Дальнейшее Чтение

9. JEP 409: Закрытые Классы

Java 15, JEEP 360 и Java 16, JPG 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, JPG 295 представила компиляцию с опережением времени (инструмент jaotc ) в качестве экспериментальной функции. Позже Java 10, JEP 317 предложил его снова в качестве экспериментального JIT-компилятора.

Однако эта функция мало используется с тех пор, как они были введены, и для ее поддержки потребовались значительные усилия, поэтому этот JEP удалил экспериментальную версию на основе Java досрочно (AOT) и как раз вовремя (JIT) компилятор

Удаляются следующие пакеты, классы, инструменты и коды AOT:

  • jdk.аот — инструмент jatc
  • jdk.внутренний.vm.компилятор — компилятор Graal
  • jdk.внутренний.vm.компилятор.управление — Мбеан Грааля
  • src/точка доступа/общий доступ/aot — сбрасывает и загружает Код AOT
  • Дополнительный код, охраняемый # если 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 JEP 411 for
 *       discussion and alternatives.
 */
@Deprecated(since="17", forRemoval=true)
public class SecurityManager {
  //...
}

Дальнейшее Чтение

12. JEP 412: API внешних функций и памяти (Инкубатор)

Этот API внешних функций и памяти позволяет разработчику получить доступ к коду за пределами JVM (внешние функции), данным, хранящимся за пределами JVM (данные вне кучи), и получить доступ к памяти, не управляемой JVM (внешняя память).

P.S Это инкубационная функция; нужно добавить — добавить модули jdk.инкубатор.иностранный для компиляции и запуска кода 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 внешних функций и памяти (Инкубатор).

Пожалуйста, обратитесь к предыдущим примерам API внешнего компоновщика в Java 16.

Дальнейшее Чтение

13. JEP 414: Векторный API (Второй инкубатор)

Java 16, JEP 414 представил новый векторный API в качестве инкубационного API .

Этот JEP улучшает производительность векторного API и другие улучшения, такие как поддержка операций с символами, перевод байтовых векторов в логические массивы и из них и т.д.

Дальнейшее Чтение

14. JEP 415: Фильтры десериализации, Зависящие от контекста

В Java десериализация ненадежных данных опасна, прочитайте OWASP – Десериализация ненадежных данных и Брайан Гетц – К лучшей сериализации .

В 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.java 17.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 В приведенном ниже примере создается фильтр десериализации для отклонения всех классов, которые расширили JКомпонент .

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(Predicate>, ObjectInputFilter.Status)

  rejectFilter(Predicate>, 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 была введена концепция фабрики фильтров, двоичного оператора , для выбора различных фильтров десериализации динамически или в зависимости от контекста. Завод определяет, как объединить два фильтра или заменить фильтр.

Ниже приведен пример фабрики фильтров Java 17 для объединения двух фильтров десериализации.

package com.mkyong.java17.jep415;

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

public class JEP415_B {

  static class PrintFilterFactory implements BinaryOperator {

      @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 $клон git

$cd java-17

Рекомендации

Оригинал: “https://mkyong.com/java/what-is-new-in-java-17/”