Данный пост является переводом статьи с некоторыми уточнениями.
В этой статье я хочу рассмотреть основные возможности, добавленные в Java начиная с 7 версии по 15. Я затрону как минимум одно крупное улучшение для каждой версии, вплоть до Java 15, которая, кстати, была выпущена осенью 2020 года.
Java теперь поддерживает лямбда выражения, функциональное программирование, объявление переменных через var, неизменяемые коллекции с простыми конструкторами и "многострочные" строки (Multi-line Strings). Кроме того, появились новые интересные экспериментальные возможности, такие как дата классы (record) и sealed классы. Наконец, я расскажу о Java REPL (очень удобная вещь для быстрых экспериментов). Итак, начнем.
Функциональное программирование (Java 8)
В Java 8 функциональное программирование и лямбды были добавлены в качестве новых возможностей языка. Суть простая: данные разбиваются на шаги, каждый из которых принимает некоторый входной сигнал и преобразует его в новый выходной сигнал. Функциональное программирование можно использовать с потоками данных( Stream)
и null-safe данными (Optional)
. Пример рассмотрим ниже.
Stream (Java 8)
Очень часто во время написания программы приходится работать с масивами и листами. А именно с их преобразованием, фильтрацией, агрегациями и т.д. До появления Java 8 для подобных операций приходилось использовать цикл for
, но теперь вы можете использовать потоки следующим образом:
Stream.of("hello", "great")
.map(s -> s + " world")
.forEach(System.out::println);
> hello world
> great world
Функция map
принимает на вход лямбду, которая будет применена ко всем элементам потока. Потоки могут работать с List
, Set
и Map
(через преобразование). Благодаря потокам вы можете избавиться от практически всех циклов в вашем коде!
Optional (Java 8)
Другой распространенной проблемой в Java были Null Pointer Exceptions. Для решения данной проблемы появилась Optional
. По сути является оберткой для объекта, который может быть null
. Применение обновлений к Optional
может быть выполнено функциональным способом:
Optional.of(new Random().nextInt(10))
.filter(i -> i % 2 == 0)
.map(i -> "number is even: " + i)
.ifPresent(System.out::println);
> number is even: 6
В приведенном выше фрагменте мы создаем случайное число, заворачиваем его внутрь объекта Optional
, фильтруем и оставляем только четные числа и в конце, если объект не является null, выводим его на экран.
JShell (Java 9)
Наконец у нас есть REPL для Java, и имя ему - JShell! В двух слова: JShell позволяет экспериментировать с фрагментами Java без написания и компиляции полного класса. Вместо этого вы можете выполнять по одной команде за раз и сразу видеть результат. Вот простой пример:
$ <JDK>/bin/jshell
jshell> System.out.println("hello world")
hello world
Люди, знакомые с интерпретируемыми языками (JavaScript или Python), уже давно имеют возможность пользоваться REPL, но до сих пор эта функция отсутствовала в Java. JShell позволяет определять переменные, а также более сложные сущности, такие как многострочные функции, классы и циклы. Более того, JShell поддерживает автозавершение, что очень удобно, если вы не знаете точных методов, предлагаемых данным Java классом .
Фабричные методы для неизменяемых коллекций (Java 9)
Простая инициализация списков долгое время отсутствовала в Java, но теперь эти времена прошли. Раньше вам приходилось делать что-то вроде этого:
jshell> List<Integer> list = Arrays.asList(1, 2, 3, 4)
list ==> [1, 2, 3, 4]
Теперь это упрощается следующим образом:
jshell> List<Integer> list = List.of(1, 2, 3, 4)
b ==> [1, 2, 3, 4]
Метод of(...)
существует для List
, Set
и Map
. Все они создают неизменяемый объект всего одной простой строчкой кода.
Ключевое слова var (Java 10)
В Java 10 появилось новое ключевое слово var
, которое позволяет не указывать тип переменной.
jshell> var x = new HashSet<String>()
x ==> []
jshell> x.add("apple")
$1 ==> true
В приведенном выше фрагменте компилятор может определить тип x
как HashSet
. Эта возможность помогает сократить уж очень длинные именна переменных и улучшить читабельность. Однако здесь есть некоторые ограничения: вы можете использовать var
только внутри тела метода, а компилятор определит тип во время компиляции, так что все по-прежнему статически типизировано.
Single Source File Launch (Java 11)
Раньше, при написании простой программы в один файл, приходилось сначала компилировать файл с помощью javac, а затем запускать его с помощью java. Начиная с 11 версии можно выполнить оба действия одной командой. Сначала определяетм свой единственный исходный файл Main.java
public class Main {
public static void main(String[] args) {
System.out.println("hello world");
}
}
Компилируем и запускаем
$ java ./Main.java
hello world
Для простых программ или экспериментов, состоящих всего из одного класса, эта функция запуска одиночных исходных файлов достаточно полезна.
Switch выражения (Java 12)
Java 12 принесла нам Switch
выражения . Вот краткая демонстрация того, чем выражение отличается от старого варианта.
Старый оператор switch:
jshell> var i = 3
jshell> String s;
jshell> switch(i) {
...> case 1: s = "one"; break;
...> case 2: s = "two"; break;
...> case 3: s = "three"; break;
...> default: s = "unknown number";
...> }
jshell> s
s ==> "three"
Новый же способ позволяет делать следующее:
jshell> var i = 3;
jshell> var x = switch(i) {
...> case 1 -> "one";
...> case 2 -> "two";
...> case 3 -> "three";
...> default -> "unknown number";
...> };
x ==> "three"
Аналогичный оператор уже долгое время существует в Scala, Kotlin. Java решила не отставать. Следует отметить несколько моментов:
вместо двойных точек используются стрелки
->
.нет необходимости в
break
default
может быть опущен, когда рассмотрены все возможные случаи.Чтобы включить эту возможность в Java 12, используйте
--enable-preview --source 12
Multi-line Strings (Java 13)
Приходилось ли вам когда-нибудь определять длинную многоуровневую строку, например, JSON или XML? До сих пор вы, вероятно, сжимали все в одну строку и использовали символы новой строки \n
, что делает ее гораздо менее читабельной. Начиная с 13 версии можно делать так:
public class Main {
public static void main(String [] args) {
var s = """
{
"recipe": "watermelon smoothie",
"duration": "10 mins",
"items": ["watermelon", "lemon", "parsley"]
}""";
System.out.println(s);
}
}
Запускаем и получаем:
java --enable-preview --source 13 Main.java
{
"recipe": "watermelon smoothie",
"duration": "10 mins",
"items": ["watermelon", "lemon", "parsley"]
}
Все пробелы и табуляции сохранены.
Дата классы: record (Java 14)
Из всех новых возможностей, описанных в этой статье, это, пожалуй, та, которой я рад больше всего: наконец-то в Java появились дата классы! Объявляются с помощью ключевого слова record
и имеют автоматические геттеры, конструктор, метод equals()
и т.д. Короче говоря, вы можете избавиться от огромного куска шаблонного кода!
jshell> record Employee (String name, int age, String department) {}
| created record Employee
jshell> var x = new Employee("Anne", 25, "Legal");
x ==> Employee[name=Anne, age=25, department=Legal]
jshell> x.name()
$2 ==> "Anne"
Scala имеет аналогичную возможность с case classes
, а Kotlin - с data classes
. В Java многие разработчики до сих пор использовали Lombok
, который предлагал практически все те возможности, которые сейчас вдохновляют записи для Java 14.
instanceof без приведения типа (Java 14)
Предыдущие версии Java уже содержали ключевое слово instanceof
:
Object obj = new String("hello");
if (obj instanceof String) {
System.out.println("String length: " + ((String)obj).length());
}
Но минусом является то, что даже если мы убедились в типе - нам все равно необходимо его явное приведение. Теперь, в Java 14, компилятор достаточно умен, чтобы автоматически определить тип после проверки instanceof
:
Object obj = new String("hello");
if (obj instanceof String mystr) {
System.out.println("String length: " + mystr.length());
}
Sealed classes (Java 15)
С помощью ключевого слова sealed
вы можете ограничить, какие классы могут расширять класс или интерфейс. Вот пример:
public sealed interface Fruit permits Apple, Pear {
String getName();
}
public final class Apple implements Fruit {
public String getName() { return "Apple"; }
}
public final class Pear implements Fruit {
public String getName() { return "Pear"; }
}
Бонус: Обновленные условия лицензирования, начиная с Java 8
Последняя тема для этой статьи: лицензирование. Большинство из вас слышали, что Oracle прекратила обновления для Java 8 (для бесплатной коммерческой версии). Итак, вот ваши варианты:
Использовать более новую версию Oracle JDK (Oracle предлагает бесплатные обновления безопасности только в течение 6 месяцев после каждого выпуска).
Использовать старую версию JDK на свой страх и риск
Использовать старую версию OpenJDK Java, которая все еще получает обновления безопасности от сообщества разработчиков открытого кода или сторонних производителей.
Платить Oracle за премьер-поддержку (например, Java 8: поддержка до 2030 года). Ниже приведены предварительные сроки поддержки Oracle для каждого JDK:
На новую модель лицензирования Oracle влияет новый цикл выпуска релизов: будет происходить обновление Java каждые 6 месяцев. Данный способ помогает Oracle быстрее совершенствовать язык, быстрее получать обратную связь через экспериментальные функции и догонять более современные языки, такие как Scala, Kotlin и Python.