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

20 Причин для перехода с Java 8

“Те, кто не хочет удовлетворять потребности жизни, страдают от последствий. Если ты не хочешь меняться… С тегом java, новости.

“Те, кто не хочет удовлетворять потребности жизни, страдают от последствий. Если вы не хотите меняться, вы остаетесь на заднем плане. Если вы не хотите меняться, вас вышвырнут из технологического века. Если вам не нравится меняться, вы устареваете. Вы не способны жить в быстро меняющемся мире. Жизнь требует перемен”.

Пастор Сандей Аделаджа

Обновления 25 Ноября 2019 года : исправлены ошибки в поддержке Oracle Java 8, поддержке Graal в Java 8 и добавлены предупреждения для функций предварительного просмотра.

С самого первого бета-релиза Java в 1995 году до конца 2006 года новые версии языка появлялись примерно с двухлетними интервалами. Но после того, как Java 6 была выпущена в конце 2006 года, разработчикам пришлось почти пять лет ждать новой версии языка. Java 8 и 9 были столь же вялыми, с почти тремя и тремя с половиной годами ожидания, соответственно, для каждого выпуска.

Версии Java 7, 8 и 9 внесли огромные изменения в экосистему Java в API, сборщик мусора, компилятор и многое другое. Марк Рейнхолд , главный архитектор Java в Oracle, взял на себя обязательство 6-месячного цикла быстрого выпуска будущих версий Java с чередованием версий с долгосрочной поддержкой (LTS), и до сих пор его команда выполняла это обещание – версии 10, 11, 12 и 13 выпускались (в среднем) каждые 6 месяцев, 13,5 дней (что такое две недели среди друзей?).

Самой последней версией LTS является Java 11, выпущенная в сентябре 2018 года, которая будет иметь бесплатные общедоступные обновления до выхода следующей версии LTS (сентябрь 2021 года) с расширенной поддержкой до сентября 2026 года. Reinhold new vision видит LTS-версию Java каждые три года (каждые шесть версий), поэтому следующим выпуском LTS будет Java SE 17, предварительно запланированный на сентябрь 2021 года. Это обеспечивает длительный поддерживаемый переходный период от старой версии LTS к новой версии LTS.

Обратите внимание, что существуют тонкие, но существенные различия между Oracle JDK, OpenJDK и датами выпуска и циклами поддержки OpenJDK, о которых я расскажу в следующей статье.

Хотя разработчики Java не так плохи, как пользователи Python – значительная часть из которых все еще кодировала с 8-летней * вышедшей на пенсию * версией Python в прошлом году , мы до сих пор медленно переходили от Java 8 , хотя Java 9 существует уже более двух лет. Существует множество веских причин, по которым вам следует рассмотреть возможность перехода с любой версии Java, которую вы используете сейчас, на (по крайней мере) Java 11, включая…

содержание

  1. Oracle больше не предоставляет бесплатную поддержку Java 8
  2. jshell, Java-РЕПЛИК (Java 9)
  3. модули и связывание (Java 9)
  4. улучшенный Javadoc (Java 9)
  5. Коллекция неизменяемых заводских методов (Java 9)
  6. Улучшения потока (Java 9)
  7. банки с несколькими выпусками (Java 9)
  8. методы частного интерфейса (Java 9)
  9. GraalVM, новая виртуальная машина Java (Java 9/10)
  10. вывод типа локальной переменной (Java 10)
  11. неизменяемые улучшения коллекции (Java 10)
  12. осведомленность о контейнерах (Java 10)
  13. запуск файла с одним исходным кодом (Java 11)
  14. переключение выражений – шаг к сопоставлению шаблонов (Java 12)
  15. сборщики тиков (Java 12)
  16. многострочные текстовые блоки (Java 13 (предварительный просмотр))
  17. Компилятор Java на Java с проектом Metropolis (Java 14+)
  18. потоковая типизация, анонимные переменные, классы данных и запечатанные типы в проекте Amber (Java 14+)
  19. сопрограммы, оптимизация конечных вызовов и облегченные волокна пользовательского режима с помощью Project Loom (Java 14+)
  20. типы значений, общие специализации и обобщенные обобщения в проекте Valhalla (Java 14+)

#1. Oracle больше не предоставляет бесплатную поддержку Java 8

Хотя AdoptOpenJDK будет предоставлять бесплатные общедоступные обновления для своей версии Java 8 до по крайней мере до сентября 2023 года , Oracle уже прекратила бесплатную поддержку своего JDK. “Расширенная поддержка” будет предоставляться до марта 2025 года, и ею можно воспользоваться , купив подписку Oracle Java .

Узнайте о некоторых различиях между JDK Oracle, OpenJDK и AdoptOpenJDK здесь.

[вернуться к оглавлению]

#2. jshell, Java-РЕПЛИК (Java 9)

Цикл Чтение-Оценка-печать (REPL) стал почти обязательной функцией для современных языков программирования. У Python есть один, у Ruby есть один, и – начиная с JDK 9 – у Java есть один. Java REPL, оболочка , это отличный способ опробовать некоторые небольшие фрагменты кода и получить обратную связь в режиме реального времени:

$ jshell
|  Welcome to JShell -- Version 11.0.2
|  For an introduction type: /help intro

jshell> var x = List.of(1, 2, 3, 4, 5)
x ==> [1, 2, 3, 4, 5]

jshell> x.stream().map(e -> (e + " squared is " + e*e)).forEach(System.out::println)
1 squared is 1
2 squared is 4
3 squared is 9
4 squared is 16
5 squared is 25

[вернуться к оглавлению]

#3. модули и связывание (Java 9)

Java 9 также представляет модули , которые являются уровнем организации выше пакетов . Если у вас установлена Java 9+, вы уже используете модули , даже если вы этого не знаете. Вы можете проверить, какие модули доступны с помощью команды:

$ java --list-modules
java.base@11.0.2
java.compiler@11.0.2
java.datatransfer@11.0.2
...
jdk.unsupported.desktop@11.0.2
jdk.xml.dom@11.0.2
jdk.zipfs@11.0.2

Модули имеют несколько преимуществ , в том числе уменьшение размера приложений Java в сочетании с новым компоновщиком Java (путем включения только модулей и подмодулей, необходимых для этого приложения), позволяя использовать частные пакеты (инкапсулированные в модуле) и быстрый сбой (если ваше приложение зависит от модуля, и этот модуль недоступен в вашей системе). Это предотвращает возникновение ошибок во время выполнения, когда часть кода пытается получить доступ к методу, определенному в пакете, которого нет в вашей системе.

Есть много хороших учебных пособий доступных в Интернете, которые объясняют, как создавать и настраивать модули. Идите вперед и модулируйте!

[вернуться к оглавлению]

#4. улучшенный Javadoc (Java 9)

Начиная с Java 9, поисковые запросы Google, такие как “класс строк java”, ушли в прошлое. Новый и улучшенный Javadoc доступен для поиска, совместим с HTML5 и совместим с новой иерархией модулей. Проверьте разницу между JDK 8 и JDK 9 Здесь Javadocs.

Сделайте поиск Javadoc Oracle проще, чем когда-либо, с помощью этого небольшого плагина Chrome, который я сделал для поиска нескольких версий API непосредственно из omnibox.

[вернуться к оглавлению]

#5. Коллекция неизменяемых заводских методов (Java 9)

До Java 9 самый простой способ быстро сделать небольшой, неизменяемый Набор предопределенных значений был чем-то вроде:

Set seasons = new HashSet<>();

seasons.add("winter");
seasons.add("spring");
seasons.add("summer");
seasons.add("fall");

seasons = Collections.unmofidiableSet(seasons);

Поистине смехотворный объем кода для такой небольшой задачи. Java 9 значительно упрощает эту запись путем введения неизменяемых заводских методов Map.of() , List.of() (см. выше) и Набор.() :

Set seasons = Set.of("winter", "spring", "summer", "fall")

Одно предостережение заключается в том, что Коллекция s, созданная таким образом, не может содержать нулевые значения – это включает значения в Записи карты .

В целом, это желанное изменение, которое помогает (немного) противостоять репутации Java как излишне многословного языка и уменьшает умственные затраты, необходимые для выполнения второстепенных задач, таких как создание небольшого Набора предопределенных значений.

[вернуться к оглавлению]

#6. Улучшения потока (Java 9)

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

Java 9 вносит некоторые небольшие улучшения в Stream , методы iterate() , takeWhile() , dropWhile() и ofNullable() .

Stream.iterate() позволяет нам рекурсивно применять функцию к потоку Объектов s (начиная с некоторого начального значения Объект ) и выберите, когда прекратить возвращать значения. Это в основном Java 8-х Поток.итерировать() плюс Java 8-х Поток.фильтр() :

jshell> Stream.iterate("hey", x -> x.length() < 7, x -> x + "y").forEach(System.out::println)
hey
heyy
heyyy
heyyyy

Новые методы takeWhile() и dropWhile() по существу применяют фильтр к потоку , принимая или отклоняя, соответственно, все значения, пока не будет выполнено некоторое условие:

jshell> IntStream.range(0, 5).takeWhile(i -> i < 3).forEach(System.out::println)
0
1
2

jshell> IntStream.range(0, 5).dropWhile(i -> i < 3).forEach(System.out::println)
3
4

Java 9 также принесла Необязательный и Поток ближе друг к другу, обеспечивая Поток.обнуляемого() и Необязательно.stream() методы, которые делают работу с потоком s и Необязательно значение ветерка:

jshell> Optional.ofNullable(null).stream().forEach(System.out::println)

jshell> Optional.of(1).stream().forEach(System.out::println)
1

jshell> Stream.ofNullable(null).forEach(System.out::println)

jshell> Stream.ofNullable(1).forEach(System.out::println)
1

В Java еще нет полноценного интерфейса для понимания списка, поэтому разработчики |сами работали |/в/|узлах , пытаясь придумать свои собственные решения. Надеюсь, следующая LTS-версия Java будет включать в себя понимание истинного списка, как те, которые появляются в Haskell :

List.comprehension(
  Stream input,
  Predicate... filters)
jshell> // NOT ACTUAL JAVA CODE
jshell> List.comprehension(
   ...>   Stream.iterate(1, i -> ++i), // 1, 2, 3, 4, ...
   ...>   i -> i%2 == 0,               // 2, 4, 6, 8, 10, ...
   ...>   i -> i > 7,                  // 8, 10, 12, 14, ...
   ...>   i -> i < 13                  // 8, 10, 12
   ...> ).take(3).forEach(System.out::println)
8
10
12

Java уже предоставляет бесконечный поток s с помощью метода iterate() (просто используйте Java 8 iterate() без фильтра), но как только один элемент выходит из строя, фильтр , Поток усекается. Для реализации истинного понимания списка элементы, не прошедшие фильтр, необходимо просто удалить из выходных данных, не завершая Поток .

Мы можем мечтать!

[вернуться к оглавлению]

#7. банки с несколькими выпусками (Java 9)

Еще одной интересной особенностью Java 9 является возможность создавать многоразрядные jar файлы . В двух словах это означает, что ваши пакеты (или модули) могут содержать конкретные реализации, предназначенные для каждой версии Java 9 и выше. Таким образом, у вас может быть определенная версия класса, загруженного для Java 9, если он установлен на клиентской машине.

Все, что вам нужно сделать, это указать Мульти-релиз: true в META-INF/MANIFEST.MF файле файла jar , затем включите различные версии классов в META-INF/версии каталог:

jar root /
  - Foo.class
  - Bar.class
  - META-INF
     - MANIFEST.MF
     - versions
        - 9
           - Foo.class
        - 10
           - Foo.class

В приведенном выше примере, если этот jar используется на машине с установленной Java 10, то Foo.class относится к /META-INF/versions/10/Foo.class . В противном случае, если доступна Java 9, то Foo.class относится к /META-INF/versions/9/Foo.class . Если ни одна из этих версий Java не установлена на целевой машине, то по умолчанию /Foo.class используется.

Если вы застряли с Java 8 на данный момент, но хотите быть готовыми к будущему переходу на более новую версию, вам следует использовать несколько версий jar spring. Вам нечего терять, реализуя их!

[вернуться к оглавлению]

#8. методы частного интерфейса (Java 9)

Java 8 представила методы по умолчанию в интерфейсе s , которые были благом для СУХОЙ (не повторяйтесь) разработки программного обеспечения. Вам больше не нужно было переопределять одни и те же методы в нескольких реализациях одного интерфейса . Вместо этого вы можете определить метод с телом по умолчанию в интерфейсе , которое будет унаследовано любыми классами, реализующими этот интерфейс .

interface MyInterface {

  default void printSquared (int n) {
    System.out.println(n + " squared is " + n*n);
  }

  default void printCubed (int n) {
    System.out.println(n + " cubed is " + n*n*n);
  }
}

public class MyImplementation implements MyInterface { }

Мы могли бы использовать этот класс в оболочке нравится:

jshell> var x = new MyImplementation()
x ==> MyImplementation@39c0f4a

jshell> x.printSquared(3)
3 squared is 9

jshell> x.printCubed(3)
3 cubed is 27

Java 9 дополнительно улучшает интерфейс s, разрешая частные методы внутри них. Это означает, что мы можем еще больше увеличить повторное использование кода, особенно между этими реализациями методов по умолчанию, без доступа пользователя к этим “вспомогательным” методам:

interface MyInterface {

  private void printHelper (String verb, int n, int pow) {
    System.out.printf("%d %s is %d%n", n, verb, (int) Math.pow(n, pow));
  }

  default void printSquared (int n) {
    printHelper("squared", n, 2);
  }

  default void printCubed (int n) {
    printHelper("cubed", n, 3);
  }
}

public class MyImplementation implements MyInterface { }

Мы используем методы в этой повторной реализации MyInterface точно так же, как мы использовали их выше, но повторяющийся код в телах методов теперь извлекается в небольшой “вспомогательный” метод. Сократив количество скопированного и вставленного кода, мы можем упростить обслуживание нашего интерфейса .

[вернуться к оглавлению]

#9. GraalVM, новая виртуальная машина Java (Java 9/10)

GraalVM (произносится как “сканировать” с жестким “g” вместо “c”) – это новая виртуальная машина Java и набор для разработки , созданные Oracle на основе HotspOT и OpenJDK.

Graal был разработан в попытке повысить производительность приложений Java, пытаясь соответствовать скорости, которой обладают собственные (скомпилированные в машинный код) языки. Виртуальная машина Graal отличается от других виртуальных машин Java двумя основными способами:

  1. позволяет опережать время (AOT) компиляция
  2. поддерживает многоязычное программирование

Как известно большинству разработчиков Java, Java компилируется в байт-код Java, который позже считывается виртуальной машиной Java и преобразуется в специфичный для процессора код для компьютера пользователя. Эта двухэтапная компиляция является одной из причин девиза Java “напиши один раз, работай где угодно” – программисту Java не нужно беспокоиться о конкретных реализациях для конкретных архитектур машин. Если он работает на ее машине, он будет работать на любой другой машине, при условии, что там установлена среда выполнения Java (JRE).

Обратите внимание, что эта модель просто передает детали, относящиеся к конкретной архитектуре, от программиста Java инженеру JVM. Машинный код все еще необходимо написать, но он скрыт от обычного разработчика Java. Вот почему, конечно, существуют разные версии JDK для Windows, Mac и Linux.

GraalVM объединяет эти два шага для создания машинных изображений – двоичного кода, который создается для конкретной архитектуры, на которой работает виртуальная машина. Эта заблаговременная компиляция из байт-кода на машинный язык означает, что GraalVM создает двоичные исполняемые файлы, которые можно запускать немедленно, не проходя через JVM.

Это не новая концепция, поскольку такие языки, как C, всегда компилировались в машинный двоичный код, но это ново для экосистемы Java. (Или новый- иш , поскольку среда выполнения Android использует компиляцию AOT примерно с 2013 года.) Компиляция AOT с помощью GraalVM сокращает время запуска и повышает производительность по сравнению с JIT-скомпилированным кодом.

Но что действительно отличает GraalVM от любых других виртуальных машин Java, так это то, что Graal – это полиглот виртуальная машина:

const express = require('express');
const app = express();
app.listen(3000);

app.get('/', function(req, res) {
  var text = 'Hello World!';
  const BigInteger = Java.type('java.math.BigInteger');
  text += BigInteger.valueOf(2).pow(100).toString(16);
  text += Polyglot.eval('R', 'runif(100)')[0];
  res.send(text);
})

Graal обеспечивает совместимость с нулевыми накладными расходами между Java, JavaScript, R, Python, Ruby и C благодаря Структуре реализации языка трюфелей . Код, написанный на любой из этих языков может быть запущен в программах, написанных на любой из этих языков . Вы можете скомпилировать программу Ruby, которая вызывает код Python, или программу Java, использующую библиотеки C. Это действительно огромная работа, которая была проделана для того, чтобы все эти языки правильно взаимодействовали друг с другом, и результат почти невероятный.

[вернуться к оглавлению]

#10. вывод типа локальной переменной (Java 10)

Java 10 продолжает войну с шаблоном с помощью различных ключевое слово :

jshell> var x = new ArrayList();
x ==> []

jshell> x.add(42)
$2 ==> true

jshell> x
x ==> [42]

Новый var тип позволяет вывод локального типа в Java версий 10 и выше. Этот небольшой новый фрагмент синтаксиса, такой как оператор diamond перед ним ( <> , определенный в JDK 7 ), делает определение переменных немного менее подробным.

Вывод локального типа означает, что var может использоваться только внутри тел методов или других подобных блоков кода. Его нельзя использовать для объявления переменных экземпляра или в качестве возвращаемого типа метода и т. Д.

Обратите внимание, что переменная x выше все еще имеет тип — это просто выводится из контекста. Это, конечно, означает, что мы не можем присвоить не ArrayList<Целое число> значение x :

jshell> x = "String"
|  Error:
|  incompatible types: java.lang.String cannot be converted to java.util.ArrayList
|  x = "String"
|      ^------^

Даже с выводом типов Java по-прежнему является статически типизированным языком. Как только переменная объявляется определенного типа, она всегда является этим типом. Это отличается, например, от JavaScript, где тип переменной динамический и может меняться от строки к строке.

[вернуться к оглавлению]

#11. неизменяемые улучшения коллекции (Java 10)

Работа с неизменяемыми данными в Java, как известно, чрезвычайно сложна. Примитивные значения неизменяемы при объявлении с помощью final

jshell> public class Test { public static final int x = 3; }
|  created class Test

jshell> Test.x
$3 ==> 3

jshell> Test.x = 4
|  Error:
|  cannot assign a value to final variable x
|  Test.x = 4
|  ^----^

…но даже такая простая вещь, как конечный примитивный массив , на самом деле не является неизменной:

jshell> public class Test { public static final int[] x = new int[]{1, 2, 3}; }
|  replaced class Test

jshell> Test.x[1]
$5 ==> 2

jshell> Test.x[1] = 6
$6 ==> 6

jshell> Test.x[1]
$7 ==> 6

Ключевое слово final выше означает, что объект x является неизменяемым, но не обязательно его содержимое. Неизменность, в данном случае, означает, что x может ссылаться только на определенную ячейку памяти , поэтому мы не можем сделать что-то вроде:

jshell> Test.x = new int[]{8, 9, 0}
|  Error:
|  cannot assign a value to final variable x
|  Test.x = new int[]{8, 9, 0}
|  ^----^

Если бы x не был окончательным , приведенный выше код работал бы просто отлично (попробуйте сами!). Конечно, это вызывает всевозможные проблемы, когда у программистов есть объект, который они хотят сделать неизменяемым, объявить его как окончательный и продолжить свой веселый путь. конечный объект на самом деле не является окончательным вообще.

Когда класс Collections был представлен в Java 7 , он принес с собой несколько неизменяемых...() методов, которые предлагают “неизменяемое представление” определенных коллекций. Это означает, что если у вас есть доступ только к неизменяемому представлению, у вас нет доступа к таким методам, как добавить() , удалить() , установить() , поместить() и так далее. Любой метод, который изменил бы объект или его содержимое , эффективно скрыт от вашего взгляда. С глаз долой – из сердца вон.

jshell> List lint = new ArrayList<>();
lint ==> []

jshell> lint.addAll(List.of(1, 9, 0, 1))
$22 ==> true

jshell> lint
lint ==> [1, 9, 0, 1]

jshell> List view = Collections.unmodifiableList(lint);
view ==> [1, 9, 0, 1]

jshell> view.add(8);
|  Exception java.lang.UnsupportedOperationException
|        at Collections$UnmodifiableCollection.add (Collections.java:1058)
|        at (#25:1)

Однако, если вы сохраняете доступ к базовому объекту, вы все равно можете его изменить. Любой, у кого есть доступ к неизменяемому представлению, сможет увидеть ваши изменения:

jshell> lint.addAll(List.of(1, 8, 5, 5))
$26 ==> true

jshell> lint
lint ==> [1, 9, 0, 1, 1, 8, 5, 5]

jshell> view
view ==> [1, 9, 0, 1, 1, 8, 5, 5]

Таким образом, даже “неизменяемые представления” все еще могут быть изменены, в некотором роде.

Следуя инструкциям Java 9 “Неизменяемые фабричные методы” , Java 10 представляет еще больше улучшений API, чтобы немного упростить работу с неизменяемыми данными. Первый – это новая копия() методов, добавленных в Список , Установить и Карту . Они создают действительно неизменяемые (неглубокие) копии своих соответствующих типов:

jshell> List nope = List.copyOf(lint)
nope ==> [1, 9, 0, 1, 1, 8, 5, 5]

jshell> nope.add(4)
|  Exception java.lang.UnsupportedOperationException
|        at ImmutableCollections.uoe (ImmutableCollections.java:71)
|        at ImmutableCollections$AbstractImmutableCollection.add (ImmutableCollections.java:75)
|        at (#32:1)

jshell> lint.set(3, 9)
$33 ==> 1

jshell> lint
lint ==> [1, 9, 0, 9, 1, 8, 5, 5]

jshell> nope
nope ==> [1, 9, 0, 1, 1, 8, 5, 5]

И есть новые для неизменяемых... () методы в классе Коллекторы , которые также создают действительно неизменяемые объекты:

jshell> var lmod = IntStream.range(1, 6).boxed().collect(Collectors.toList())
lmod ==> [1, 2, 3, 4, 5]

jshell> lmod.add(6)
$38 ==> true

jshell> lmod
lmod ==> [1, 2, 3, 4, 5, 6]

jshell> var lunmod = IntStream.range(1, 6).boxed().collect(Collectors.toUnmodifiableList())
lunmod ==> [1, 2, 3, 4, 5]

jshell> lunmod.add(6)
|  Exception java.lang.UnsupportedOperationException
|        at ImmutableCollections.uoe (ImmutableCollections.java:71)
|        at ImmutableCollections$AbstractImmutableCollection.add (ImmutableCollections.java:75)
|        at (#41:1)

Шаг за шагом Java движется к более всеобъемлющей модели для неизменяемых данных.

[вернуться к оглавлению]

#12. осведомленность о контейнерах (Java 10)

В период с 2006 по 2008 год инженеры Google добавили в ядро Linux новую классную функцию , известную как контрольные группы или “контрольные группы”. Эта новая функция “ограничивает, учитывает и изолирует использование ресурсов (процессор, память, дисковый ввод-вывод, сеть и т.д.) набора процессов”.

Эта концепция может показаться вам знакомой, если вы используете такие инструменты, как Hadoop, Kubernetes или Docker, которые широко используют группы управления. Без возможности ограничить ресурсы, доступные определенным группам процессов, Docker не мог бы существовать.

К сожалению, Java была создана задолго до того, как были реализованы группы, поэтому Java изначально полностью игнорировала эту функцию.

Однако, начиная с Java 10, JVM осознает, когда она запускается в контейнере и соблюдает ограничения ресурсов, установленные для него этим контейнером по умолчанию. Эта функция также была перенесена в JDK 8. Поэтому, если вы выберете последнюю версию Java 8, эта JVM также будет поддерживать контейнер.

Другими словами, с выпуском Java 10 Докер и Java наконец-то стали друзьями .

[вернуться к оглавлению]

#13. запуск файла с одним исходным кодом (Java 11)

Начиная с Java 11, вам больше не нужно компилировать один исходный файл перед его запускомjava видит метод main в основном классе и автоматически скомпилирует и запустит код при вызове его в командной строке:

// Example.java
public class Example {
  public static void main (String[] args) {

    if (args.length < 1)
      System.out.println("Hello!");

    else
      System.out.println("Hello, " + args[0] + "!");

  }
}
$ java Example.java 
Hello!

$ java Example.java Biff
Hello, Biff!

Это небольшое, но полезное изменение в java лаунчере, которое может упростить (среди прочего) обучение новичков Java, не вводя “церемонию” явной компиляции небольших вводных программ, подобных этим.

[вернуться к оглавлению]

#14. переключение выражений – шаг к сопоставлению шаблонов (Java 12)

Разве у нас уже нет выражений switch в Java? Что это такое?

jshell> int x = 2;
x ==> 2

jshell> switch(x) {
   ...>   case 1: System.out.println("one"); break;
   ...>   case 2: System.out.println("two"); break;
   ...>   case 3: System.out.println("three"); break;
   ...> }
two

…ну, это переключатель |/заявление , а не переключатель выражение . Оператор направляет поток программы но не оценивает саму ценность. (Вы не можете, например, сделать что-то l.) ike y(x) {… } Выражение, с другой стороны, приводит к результату. Поэтому выражения могут быть назначены переменным, возвращены из функций и так далее.

переключать выражения выглядят немного иначе, чем операторы переключения . Выражение, аналогичное приведенному выше утверждению, может выглядеть следующим образом:

String name = switch(x) {
    case 1 -> "one";
    case 2 -> "two";
    case 3 -> "three";
    default -> throw new IllegalArgumentException("I can only count to 3.");
};

System.out.println(name);

Вы можете увидеть несколько различий между этим и предыдущим фрагментом. Во-первых, мы используем стрелки -> вместо двоеточий : . Синтаксически это то, как оператор switch отличается компилятором от /switch выражения .

Во-вторых, нет разрыва s в коде выражения. В выражениях switch нет “провала”, как в выражениях switch , поэтому нет необходимости прерывать после дела филиала.

В-третьих, мы должны иметь значение по умолчанию если только перечисленные случаи не являются исчерпывающими . То есть компилятор может определить, есть ли – для любого возможного значения x – оператор case , который поймал бы это значение. Если нет, то у нас должен быть случай по умолчанию . На данный момент единственный реальный способ исчерпать все возможные случаи без по умолчанию – это включить логическое или перечисление ценность.

Наконец, мы можем назначить переключатель выражение переменной ! Разница между выражениями switch и операторами аналогична разнице в Java между троичным оператором ?: и если еще заявление:

jshell> int what = 0;
what ==> 0

jshell> boolean flag = false;
flag ==> false

jshell> if (flag) what = 2; else what = 3;

jshell> what
what ==> 3

jshell> what = flag ? 4 : 5
what ==> 5

/|если еще направляет поток кода, но не может быть присвоен переменной (вы не можете сделать что-то вроде y (флаг) 3 еще 4 в Java), в то время как тернарный оператор ?: определяет выражение , которое может быть присвоено переменной.

переключение выражений – это шаг на пути к полному сопоставлению шаблонов поддержке в Java. Это разрабатывается в рамках Проекта Amber, о котором я расскажу более подробно ниже . Обратите внимание, что выражения переключения являются функцией “предварительного просмотра” в Java 12 и 13, но их планируется обновить до окончательной версии для Java 14 .

[вернуться к оглавлению]

#15. Сборщики тиков (Java 12)

Обратите внимание, что Двойной поток имеет метод average() . Приведенный ниже пример приведен только в иллюстративных целях.

Если вы когда-либо пытались выполнить сложную манипуляцию с потоком значений в Java, вы знаете, как это может раздражать, что Поток s может быть повторен только один раз:

jshell> var ints = DoubleStream.of(1, 2, 3, 4, 5)
ints ==> java.util.stream.DoublePipeline$Head@12cdcf4

jshell> var avg = ints.sum()
avg ==> 15.0

jshell> avg /= ints.count()
|  Exception java.lang.IllegalStateException: stream has already been operated upon or closed
|        at AbstractPipeline.evaluate (AbstractPipeline.java:229)
|        at DoublePipeline.count (DoublePipeline.java:486)
|        at (#19:1)

Java 12 облегчает вашу боль, вводя Collectors.teeing() ((вдохновленный утилитой UNIX плата ), которая “дублирует” поток, позволяя вам выполнять две одновременные Потоковые операции перед объединением результатов.

Версия Java 12, описанная выше, может выглядеть примерно как

jshell> import static java.util.stream.Collectors.*

jshell> var ints = DoubleStream.of(1, 2, 3, 4, 5)
ints ==> java.util.stream.DoublePipeline$Head@574caa3f

jshell> ints.boxed().collect(teeing(
   ...>   summingDouble(e -> e),
   ...>   counting(),
   ...>   (a,b) -> a/b
   ...> ))
$20 ==> 3.0

Это все еще далеко от совершенства (синтаксис немного неуклюж, и вы можете видеть, что нам нужно упаковать примитивы double s с помощью boxed() , чтобы манипулировать ими позже), но это прогресс в направлении более гибкого Поток s (и, возможно, в конечном итоге, перечисление понятий ?) на Java.

[вернуться к оглавлению]

#16. многострочные текстовые блоки (Java 13 (предварительный просмотр))

Еще одна интересная функция доступна прямо сейчас в Java 13 – это многострочные текстовые блоки. Это функция предварительного просмотра (с измененным предварительным просмотром, доступным в предстоящем JDK14 ), поэтому вам нужно передать --включить-предварительный просмотр флаг при запуске java или оболочка :

Обратите внимание, что это функция предварительного просмотра и может быть изменена в будущем и усложнена. Пожалуйста, прочтите руководство, прежде чем пытаться серьезно использовать эту функцию.

$ jshell --enable-preview
|  Welcome to JShell -- Version 13.0.1
|  For an introduction type: /help intro

jshell> String greetings = """
   ...> Hello. My name is Inigo Montoya.
   ...> You killed my father.
   ...> Prepare to die.
   ...> """
greetings ==> "Hello. My name is Inigo Montoya.\nYou killed my father.\nPrepare to die.\n"

Многострочные текстовые блоки должны начинаться с трех символов двойных кавычек в строке """ , за которыми следует символ новой строки , и должны аналогичным образом заканчиваться символом новой строки, за которым следуют три символа двойных кавычек в строке """ .

Эта функция, вероятно, не очень полезна в приведенном выше случае, но она значительно улучшает читаемость при выполнении таких действий, как генерация String s с большим количеством перемежающихся переменных или попытка эмулировать код с отступом с помощью String конкатенации по нескольким строкам. То, что мы написали бы в более ранних версиях Java как

jshell> String html1 = "\n\t\n\t\t

\"I love Java and Java loves me!\"

\n\t\n\n"; html1 ==> "\n\t\n\t\t

\"I love Java and Java ... h1>\n\t

\n\n"

или

jshell> String html2 =
   ...>   "\n" +
   ...>     "\t\n" +
   ...>       "\t\t

\"I love Java and Java loves me!\"

\n" + ...> "\t\n" + ...> "\n"; html2 ==> "\n\t\n\t\t

\"I love Java and Java ... h1>\n\t

\n\n"

…теперь мы можем писать как гораздо более читабельные:

jshell> String html3 = """
   ...> 
   ...>     
   ...>         

"I love Java and Java loves me!"

...> ...> ...> """ html3 ==> "\n\t\n\t\t

\"I love Java and Java ... h1>\n\t

\n\n" jshell> html1.equals(html2); html2.equals(html1); $16 ==> true $17 ==> true

Нет необходимости избегать одинарных или двойных кавычек, никаких чрезмерных "..." + снова и снова, просто хорошо отформатированный текст с сохранением пробелов. Многострочная строка “заборы” """ также учитывает уровень отступов в вашем коде. Поэтому, если вы находитесь внутри метода или класса, вам не нужно иметь многострочный Строка выровнена по левой стороне страницы:

jshell> String bleh = """
   ...>        hello
   ...>        is it me you're looking for
   ...>        """
bleh ==> "hello\nis it me you're looking for\n"

jshell> String bleh = """
   ...>        hello
   ...>        is it me you're looking for
   ...> """
bleh ==> "       hello\n       is it me you're looking for\n"

[вернуться к оглавлению]

#17. Компилятор Java на Java с проектом Metropolis (Java 14+)

Самое захватывающее в написании Java в 2020 году – это не то, как далеко она продвинулась за последние шесть лет, а то, куда она может пойти в следующие шесть. Итак, по последним нескольким пунктам здесь я хотел бы обсудить некоторые текущие Java Проекты , которые обещают новые и захватывающие функции в не слишком отдаленном будущем.

Первым из них является проект Metropolis, целью которого является переписать часть (или все) JVM на самой Java , где в настоящее время он написан (частично) на других языках, таких как C и C++ (в зависимости от с какой JVM вы работаете ). Руководитель проекта называет этот подход “Java на Java”, и у него есть несколько преимуществ:

  1. отделите Java от зависимостей на других языках , которые усложняются новыми версиями, исправлениями ошибок, исправлениями безопасности и т.д.
  2. разрешить виртуальной машине оптимизировать себя с помощью “горячей точки” схемы оптимизации, которая в настоящее время применяется к другому скомпилированному коду Java
  3. ремонтопригодность/упрощение — если JVM может быть полностью переписана на Java, то архитекторам JVM потребуется только знать саму Java, а не знать несколько языков; это упростит обслуживание JVM

C и C++ (на которых в настоящее время написаны части JVM) предлагают преимущества по сравнению с Java благодаря их “близкой к металлу” природе. Чтобы полностью вывести JVM на основе Java на тот же уровень, что и эти текущие JVM на гибридном языке, возможно, потребуется добавить в язык Java новые функции (например, типы значений ). Это огромный проект с множеством входов и выходов (и что у вас есть ), так что не ожидайте этого в ближайшее время. Тем не менее… в мире JVM происходит много захватывающих вещей!

[вернуться к оглавлению]

#18. потоковая типизация, анонимные переменные, классы данных и запечатанные типы в проекте Amber (Java 14+)

Project Amber – это кодовое название для огромного количества улучшений Java API, которые все направлены на упрощение синтаксиса . Эти улучшения включают в себя…

ввод потока с помощью instanceof

Ключевое слово Java instanceof проверяет является ли объект экземпляром определенного класса или интерфейса и возвращает логическое значение для этого:

jshell> ArrayList alist = new ArrayList<>();
alist ==> []

jshell> alist instanceof List
$5 ==> true

jshell> alist instanceof ArrayList
$6 ==> true

jshell> alist instanceof Object
$7 ==> true

На практике, когда используется instanceof и возвращает true , рассматриваемый объект затем явно преобразуется в нужный тип и используется в качестве объекта этого типа:

jshell> void alertNChars (Object o) {
   ...>   if (o instanceof String)
   ...>     System.out.println("String contains " + ((String)o).length() + " characters");
   ...>   else System.out.println("not a String");
   ...> }
|  created method alertNChars(Object)

jshell> String s = "I am a banana";
s ==> "I am a banana"

jshell> Integer i = 1;
i ==> 1

jshell> alertNChars(s)
String contains 13 characters

jshell> alertNChars(i)
not a String

Проект Amber направлен на то, чтобы немного упростить этот синтаксис с помощью потоковой типизации (или “потоковой типизации”), где компилятор может рассуждать о блоках instanceof . В принципе, если если (x instanceof C) блок выполняется, то объект x должен быть экземпляром класса C (или подклассом C ), поэтому могут использоваться методы экземпляра C . После проекта Amber описанный выше метод должен выглядеть примерно так:

void alertNChars (Object o) {
  if (o instanceof String s)
    System.out.println("String contains " + s.length() + " characters");
  else System.out.println("not a String");
}

Небольшое изменение, но такое, которое уменьшает некоторый визуальный беспорядок и закладывает некоторые основы для сопоставления с образцом в Java.

анонимные лямбда-переменные

Некоторые языки позволяют пользователю игнорировать параметры в лямбдах (и в других местах), используя один символ подчеркивания _ вместо идентификатора параметра. Начиная с Java 9, использование символа подчеркивания само по себе в качестве идентификатора приведет к ошибке во время компиляции, поэтому он был “реабилитирован” и теперь может использоваться таким “анонимным” способом и в Java.

“Неназванные” или “анонимные” переменные и параметры используются в тех случаях, когда вас интересует часть предоставленной информации, но не вся. Примером, подобным приведенному по приведенной выше ссылке, является Бифункция для этого требуется Целое число и Удваивает в качестве двух аргументов, но просто возвращает Целое число как Строка . Второй аргумент ( Double ) не требуется для реализации функции Bi :

BiFunction bids = (i, d) -> String.valueOf(i);

Так зачем же нам вообще нужно называть этот второй аргумент ( d )? Анонимные параметры позволили бы нам просто заменить ненужные или ненужные переменные на _ и покончим с ними:

BiFunction bids = (i, _) -> String.valueOf(i);

Самое близкое к этому в Java на данный момент, вероятно, //? универсальный тип подстановочного знака . Мы используем это, когда нам нужно указать какой-то общий аргумент типа, но когда нам все равно, что это за тип на самом деле. Это означает, что мы не можем использовать этот тип в другом месте кода. Вы могли бы назвать это “неназванным” или “анонимным” типом, и использование аналогично описанному выше.

классы данных

Предлагаемый “класс данных” в проекте Amber похож на класс данных в Котлине, случай класс в Scala или (пока еще не реализованный) запись s в C#. По сути, их цель состоит в том, чтобы уменьшить многословие, которое может мешать Java-коду.

После реализации классы данных должны превратить весь этот ужасный шаблонный код…

package test;

public class Boilerplate {

  public final int    myInt;
  public final double myDouble;
  public final String myString;

  public Boilerplate (int myInt, double myDouble, String myString) {
    super();
    this.myInt = myInt;
    this.myDouble = myDouble;
    this.myString = myString;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = prime + myInt;
    long temp = Double.doubleToLongBits(myDouble);

    result = prime * result + (int) (temp ^ (temp >>> 32));
    result = prime * result + ((myString == null) ? 0 : myString.hashCode());

    return result;
  }

  @Override
  public boolean equals (Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;

    Boilerplate other = (Boilerplate) obj;
    if (myInt != other.myInt) return false;

    if (Double.doubleToLongBits(myDouble) !=
        Double.doubleToLongBits(other.myDouble))
      return false;

    if (myString == null) {
      if (other.myString != null) return false;
    } else if (!myString.equals(other.myString))
      return false;

    return true;
  }

  @Override
  public String toString() {
    return "Boilerplate [myInt=" + myInt + ", myDouble=" + myDouble + ", myString=" + myString + "]";
  }

}

…в просто

record Boilerplate (int myInt, double myDouble, String myString) { }

Ключевое слово new record сообщит компилятору, что Шаблон – это стандартный класс данных, к которому мы хотим получить простой доступ к общедоступным переменным экземпляра, и что нам нужны все стандартные методы: Хэш-код() , равно() , toString() .

Хотя это правда, что большинство современных IDE будут генерировать весь этот код для вас, есть аргумент, что на самом деле его вообще не должно быть. Существует так много концептуальных накладных расходов при чтении и понимании кода, не относящегося к записи выше, и так много мест, где можно спрятать ошибки, что лучше просто избавиться от него. Классы данных – это будущее Java.

запечатанные типы

В Java, если класс объявлен final , он никак не может быть расширен. В нем не может быть подклассов любого рода, независимо от того, написаны ли они пользователем или разработчиком API. Противоположностью этому, конечно, является класс, который не объявлен окончательным . Такой класс может быть расширен как пользователем, так и разработчиком API, с любым количеством подклассов по желанию.

Но что, если мы хотим полуфиналфинал класс? Допустим, у нас есть класс, который мы хотим разделить на подклассы, но только определенное количество раз (для краткости я буду использовать запись s ниже):

record FossilFuelCar (Make make, Model model) { }
record ElectricCar   (Make make, Model model) { }
record HybridCar     (Make make, Model model) { }
record FuelCellCar   (Make make, Model model) { }

Предположим, мы уверены, что это единственные четыре вида автомобилей, которые нам понадобятся в обозримом будущем, и мы хотим помешать людям создавать поддельные разновидности автомобилей ( Паровой автомобиль ?). запечатанные типы обеспечивают способ сделать это:

sealed interface Car (Make make, Model model) { }

record FossilFuelCar (Make make, Model model) implements Car { }
record ElectricCar   (Make make, Model model) implements Car { }
record HybridCar     (Make make, Model model) implements Car { }
record FuelCellCar   (Make make, Model model) implements Car { }

В исходном коде этого файла, предположительно, мы могли бы определить столько реализаций Car , сколько пожелаем. Но за пределами этого файла никакие новые реализации вообще не допускаются. Думайте об этом как о смешении между классами и перечислением s — у нас есть определенное количество реализаций Автомобиля и это все.

запечатанные классы и интерфейсы также будут хорошо работать с исчерпываемостью , необходимой для полноценной схемы сопоставления шаблонов в Java. Еще больше шагов к современности!

[вернуться к оглавлению]

#19. сопрограммы, оптимизация конечных вызовов и облегченные волокна пользовательского режима с помощью Project Loom (Java 14+)

У Project Loom есть один основной фокус: легкая многопоточность.

В настоящее время, если пользователь хочет реализовать параллельное/многопоточное приложение на Java, ему необходимо – на каком-то уровне – использовать Поток s, “основная абстракция параллелизма в Java” . java.util.concurrent API также предоставляет множество дополнительных абстракций, таких как Блокировки , Будущее с и Исполнитель ы, которые могут немного упростить создание этих приложений.

Но Java Thread s реализованы на уровне операционной системы. Их создание и переключение между ними может быть очень дорогостоящим. И ОС может сильно влиять на максимальное количество разрешенных параллельных потоков, ограничивая полезность этого подхода.

Цель проекта Loon – создать абстракцию уровня приложения Потока , подобную абстракции, называемой Волокном . Идея многопоточности на уровне виртуальной машины (а не на уровне операционной системы) на самом деле является старой для Java — В Java раньше были эти “зеленые потоки” в Java 1.1 , но они были постепенно отменены в пользу собственных потоков. С ткацким станком зеленые нити вернулись с удвоенной силой.

Возможность создавать на порядки больше Волокна за аналогичный промежуток времени относительно Нити с означает, что JVM сможет разместить многопоточность спереди и по центру. Ткацкий станок позволит оптимизировать и продолжить вызов хвоста (аналогично/|сопрограммам Котлина ), открывающим новые подходы к параллельному программированию на Java.

[вернуться к оглавлению]

#20. типы значений, общие специализации и овеществленные обобщения в проекте Valhalla (Java 14+)

Последний проект, который я хотел бы обсудить, – это проект Valhalla, который, я думаю, может внести наибольшие изменения в экосистему Java из всех предстоящих и предлагаемых функций, обсуждавшихся до сих пор. Проект Valhalla предлагает три основных изменения в Java:

  1. типы значений
  2. общая специализация
  3. овеществленные дженерики

Типы значений лучше всего понятны в отличие от ссылочных типов , хорошими примерами которых являются Коллекции Java. В Списке , например, элементы хранятся в памяти как непрерывный блок ссылок . Сами ссылки указывают на их ценности, которые могут храниться в совершенно разных местах памяти. Затем повторение списка требует некоторой работы, так как для получения значения необходимо перейти по каждому указанному адресу.

Массив типов значений, с другой стороны, имеет все свои значения , хранящиеся в непрерывном блоке памяти. Ссылки не нужны, потому что следующее значение просто находится в следующей позиции в памяти. Хорошим примером этого является массив в стиле C, где должен быть объявлен тип данных, хранящихся в массиве, а также длина массива:

double balance[10];

Длина необходима для того, чтобы при выполнении кода можно было выделить непрерывный блок памяти нужного размера. В Java, конечно, уже есть эта конструкция (массивы). Но типы значений позволят пользователю создавать массив, подобный примитиву, подобный приведенному выше, даже для составного, структура – как группы данных. За счет обхода ссылок/разыменования, необходимых для существующих коллекций , скорость доступа и эффективность хранения данных значительно возрастут.

Типы значений в Java будут вести себя как классы, с методами и полями, но со скоростью доступа, равной скорости примитивных типов. Типы значений также могут использоваться в качестве универсальных типов, без накладных расходов на упаковку/распаковку примитивов и классов-оболочек. Что подводит нас к следующей важной особенности проекта “Валгалла”…

общая специализация

Универсальная специализация звучит как оксюморон, но это означает (в двух словах), что типы значений (и, следовательно, примитивные типы) могут использоваться в качестве параметра типа в универсальных методах и классах. Так в дополнение к

List

мы также могли бы

List

Большинство разработчиков Java знают, что аргументы универсального типа T , E , К , V и т.д. должны быть классами. Они не могут быть примитивными типами. Но почему?

Ну, во время компиляции Java использует стирание типов для преобразования всех определенных типов в один суперкласс. В случае Java этим суперклассом является Объект . Поскольку примитивные типы не наследуются от Объект , они не могут использоваться в качестве аргументов типа в универсальных классах, методах и т. Д.

Эта статья на ДЗоне дает отличное объяснение однородного перевода (что Java использует для преобразования всех классов в Объект во время компиляции) против гетерогенного перевода (он же. универсальная специализация), которая позволила бы создавать иерархии непересекающихся типов в универсальных типах, таких как Объект по сравнению с примитивными типами в Java. По соображениям обратной совместимости вы не сможете просто передать int для параметра типа T в любом месте существующего Java API. Вместо этого методы, которые принимают примитивы, а также Объект s, должны быть определены с помощью любого ключевого слова вместе с общим типом:

public class Box {
  private T value;
  public T getValue() {
    return value;
  }
}

овеществленные дженерики

Общая специализация для типов значений означает, что во время выполнения JVM будет знать, по крайней мере, некоторые типы, передаваемые вашему приложению. Это контрастирует с обычной процедурой удаления типов, используемой для ссылочных типов ( Объект s) на сегодняшний день. Так будут ли типы овеществлены на Java после проекта Valhalla? Может быть, по крайней мере частично.

Хотя крайне маловероятно что информация о типе будет доступна во время выполнения для ссылочных типов (для поддержания обратной совместимости) это возможно (вероятно?) что они будут доступны для типов значений. Итак, каково будущее системы типов Java? Только время покажет.

[вернуться к оглавлению]

Приведенный выше список является лишь небольшим подмножеством функций, доступных в Java 9-13, и будущих функций в Java 14+. Java чрезвычайно эволюционировала как язык и как экосистема с тех пор, как Java 8 была выпущена более пяти лет назад. Если вы не обновились с тех пор, вы действительно упускаете!

Следуйте за мной: Dev. К | Twitter.com Поддержите меня: Поддержите меня:

Спасибо за чтение!

Оригинал: “https://dev.to/awwsmm/20-reasons-to-move-on-from-java-8-1dio”