0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Время летит и уже скоро нас ждет очередной релиз Java. Согласно полугодовому расписанию релизов, сейчас время для Java 15, который прокладывает путь к грядущей Java 17 LTS (через год).</p>
1
<p>Время летит и уже скоро нас ждет очередной релиз Java. Согласно полугодовому расписанию релизов, сейчас время для Java 15, который прокладывает путь к грядущей Java 17 LTS (через год).</p>
2
<p>В Java наблюдается постоянный поток улучшений, на многие из которых повлияли другие JVM-языки и идеи функционального программирования. Сюда входят такие фичи, как лямбды, ограниченный локальный вывод типов или switch-выражения. Scala - особенно богатый источник идей, благодаря инновационному сочетанию объектно-ориентированного и функционального программирования.</p>
2
<p>В Java наблюдается постоянный поток улучшений, на многие из которых повлияли другие JVM-языки и идеи функционального программирования. Сюда входят такие фичи, как лямбды, ограниченный локальный вывод типов или switch-выражения. Scala - особенно богатый источник идей, благодаря инновационному сочетанию объектно-ориентированного и функционального программирования.</p>
3
<p>Давайте посмотрим, как фичи, доступные в Java 15, соотносятся с конструкциями, известными в Scala. Мы сосредоточимся на языковых фичах, пропуская улучшения JVM или очистку стандартной библиотеки. Также обратите внимание, что некоторые из описанных компонентов уже доступны в более ранних версиях Java.</p>
3
<p>Давайте посмотрим, как фичи, доступные в Java 15, соотносятся с конструкциями, известными в Scala. Мы сосредоточимся на языковых фичах, пропуская улучшения JVM или очистку стандартной библиотеки. Также обратите внимание, что некоторые из описанных компонентов уже доступны в более ранних версиях Java.</p>
4
<h2>Records</h2>
4
<h2>Records</h2>
5
<p>Начнем с рекордов (records), которые доступны в качестве превью предстоящей финальной версии. Объем кода, который был необходим для создания простого класса данных, был легкой мишенью, когда речь шла о многословности Java.</p>
5
<p>Начнем с рекордов (records), которые доступны в качестве превью предстоящей финальной версии. Объем кода, который был необходим для создания простого класса данных, был легкой мишенью, когда речь шла о многословности Java.</p>
6
<p>При создании класса данных мы обычно пишем: • закрытые финальные поля, содержащие данные; • конструктор, устанавливающий поля для заданных значений; • аксессоры для получения данных; • equals, hashCode и toString.</p>
6
<p>При создании класса данных мы обычно пишем: • закрытые финальные поля, содержащие данные; • конструктор, устанавливающий поля для заданных значений; • аксессоры для получения данных; • equals, hashCode и toString.</p>
7
class Person { private final String name; private final int age; Person(String name, int age) { this.name = name; this.age = get; } String name() { return name; } int age() { return age; } public boolean equals(Object o) { if (!(o instanceof Person)) return false; Person other = (Person) o; return other.name == name && other.age = age; } public int hashCode() { return Objects.hash(name, age); } public String toString() { return String.format("Person[name=%s, age=%d]", name, age); } }<p>Существовали обходные пути, начиная от автоматического создания кода с помощью IDE до использования аннотаций и манипулирования байт-кодом во время выполнения (см.<a>Проект Lombok</a>). Но это всегда выглядело как воркэраунд, а не как правильный способ решить эту проблему. Что ж, больше нет!</p>
7
class Person { private final String name; private final int age; Person(String name, int age) { this.name = name; this.age = get; } String name() { return name; } int age() { return age; } public boolean equals(Object o) { if (!(o instanceof Person)) return false; Person other = (Person) o; return other.name == name && other.age = age; } public int hashCode() { return Objects.hash(name, age); } public String toString() { return String.format("Person[name=%s, age=%d]", name, age); } }<p>Существовали обходные пути, начиная от автоматического создания кода с помощью IDE до использования аннотаций и манипулирования байт-кодом во время выполнения (см.<a>Проект Lombok</a>). Но это всегда выглядело как воркэраунд, а не как правильный способ решить эту проблему. Что ж, больше нет!</p>
8
<p>С помощью рекордов приведенное выше сокращается до:</p>
8
<p>С помощью рекордов приведенное выше сокращается до:</p>
9
record Person(String name, int age) { }<p>Уменьшение кода в 21 раз! Байт-код, с которым оба они компилируются, будет похож. Экземпляры рекордов могут быть созданы так же, как и класс:</p>
9
record Person(String name, int age) { }<p>Уменьшение кода в 21 раз! Байт-код, с которым оба они компилируются, будет похож. Экземпляры рекордов могут быть созданы так же, как и класс:</p>
10
var john = new Person("john", 76);<p>В Scala есть очень похожая функциональность - case-классы. Приведенный выше пример будет записан как:</p>
10
var john = new Person("john", 76);<p>В Scala есть очень похожая функциональность - case-классы. Приведенный выше пример будет записан как:</p>
11
case class Person(name: String, age: Int)<p>В чем сходство между рекордами и кейс-классами?</p>
11
case class Person(name: String, age: Int)<p>В чем сходство между рекордами и кейс-классами?</p>
12
<ul><li>методы equals, hashCode и toString генерируются автоматически (если явно не переопределены);</li>
12
<ul><li>методы equals, hashCode и toString генерируются автоматически (если явно не переопределены);</li>
13
<li>поля данных неизменяемы и общедоступны: private final поля + открытые методы доступа без параметров в Java и public val в Scala;</li>
13
<li>поля данных неизменяемы и общедоступны: private final поля + открытые методы доступа без параметров в Java и public val в Scala;</li>
14
<li>доступен конструктор со всеми полями данных;</li>
14
<li>доступен конструктор со всеми полями данных;</li>
15
<li>методы могут быть определены в теле рекорда/кейс класса;</li>
15
<li>методы могут быть определены в теле рекорда/кейс класса;</li>
16
<li>рекорды могут реализовывать интерфейсы, а кейс-классы могут реализовывать трейты (trait) (которые являются более мощным эквивалентом интерфейса Java в Scala);</li>
16
<li>рекорды могут реализовывать интерфейсы, а кейс-классы могут реализовывать трейты (trait) (которые являются более мощным эквивалентом интерфейса Java в Scala);</li>
17
<li>все рекорды расширяют java.lang.Record, а все кейс классы реализуют scala.Product.</li>
17
<li>все рекорды расширяют java.lang.Record, а все кейс классы реализуют scala.Product.</li>
18
</ul><p>Однако есть и некоторые заметные отличия:</p>
18
</ul><p>Однако есть и некоторые заметные отличия:</p>
19
<ol><li>Рекорды не могут иметь дополнительного состояния: private или public полей. Это означает, что рекорды не могут иметь вычисленное внутреннее состояние; все, что доступно, является частью основной сигнатуры рекорда. В Scala кейс-классы могут иметь private или public поля экземпляров, как и любой другой класс.</li>
19
<ol><li>Рекорды не могут иметь дополнительного состояния: private или public полей. Это означает, что рекорды не могут иметь вычисленное внутреннее состояние; все, что доступно, является частью основной сигнатуры рекорда. В Scala кейс-классы могут иметь private или public поля экземпляров, как и любой другой класс.</li>
20
<li>Рекорды не могут расширять классы, поскольку они уже неявно расширяют java.lang.Record. В Scala кейс-классы могут расширять любой другой класс, поскольку они только неявно реализуют трейт (за одним исключением: кейс-класс не может расширять другой класс case).</li>
20
<li>Рекорды не могут расширять классы, поскольку они уже неявно расширяют java.lang.Record. В Scala кейс-классы могут расширять любой другой класс, поскольку они только неявно реализуют трейт (за одним исключением: кейс-класс не может расширять другой класс case).</li>
21
<li>Рекорды всегда являются final (не могут быть расширены), в то время как кейс-классы могут (хотя это имеет ограниченную полезность).</li>
21
<li>Рекорды всегда являются final (не могут быть расширены), в то время как кейс-классы могут (хотя это имеет ограниченную полезность).</li>
22
<li>Конструкторы рекордов очень ограничены, поскольку они не могут иметь вычисленное состояние, их можно использовать в основном для проверки, например:</li>
22
<li>Конструкторы рекордов очень ограничены, поскольку они не могут иметь вычисленное состояние, их можно использовать в основном для проверки, например:</li>
23
</ol>record Person(String name, int age) { Person { if (age < 0) throw new IllegalArgumentException("Too young"); } }<p>В Scala же конструкторы не ограничены.</p>
23
</ol>record Person(String name, int age) { Person { if (age < 0) throw new IllegalArgumentException("Too young"); } }<p>В Scala же конструкторы не ограничены.</p>
24
<p>Также важной особенностью кейс-классов Scala, которая отсутствует в рекордах, является метод копирования (copy). Он позволяет создать копию экземпляра (мы не можем изменять поля из-за неизменности) с некоторыми полями, установленными на новые значения. Копирование действительно является одной из самых полезных функций Scala и настолько широко распространено, что о нем легко забыть!</p>
24
<p>Также важной особенностью кейс-классов Scala, которая отсутствует в рекордах, является метод копирования (copy). Он позволяет создать копию экземпляра (мы не можем изменять поля из-за неизменности) с некоторыми полями, установленными на новые значения. Копирование действительно является одной из самых полезных функций Scala и настолько широко распространено, что о нем легко забыть!</p>
25
<p>Подводя итог, можно сказать, что в Scala case действительно ведет себя как модификатор класса: почти все, что разрешено в обычном классе, также разрешено в кейс-классе; модификатор генерирует для нас несколько методов и полей. В Java, с другой стороны, рекорды представляют собой отдельный "тип вещей", который компилируется в класс, но имеет свои собственные ограничения и синтаксис (как и перечисления).</p>
25
<p>Подводя итог, можно сказать, что в Scala case действительно ведет себя как модификатор класса: почти все, что разрешено в обычном классе, также разрешено в кейс-классе; модификатор генерирует для нас несколько методов и полей. В Java, с другой стороны, рекорды представляют собой отдельный "тип вещей", который компилируется в класс, но имеет свои собственные ограничения и синтаксис (как и перечисления).</p>
26
<h2>Sealed classes</h2>
26
<h2>Sealed classes</h2>
27
<p>Очень похожая функция, дебютировавшая в Java 15, - это поддержка sealed-классов и интерфейсов. Они позволяет ограничить возможные реализации класса или интерфейса. Затем любой код, использующий абстрактный класс или интерфейс, может безопасно сделать предположение о возможной форме значения. Довольно часто мы хотим сделать наш класс широко доступным, но не обязательно широко расширяемым.</p>
27
<p>Очень похожая функция, дебютировавшая в Java 15, - это поддержка sealed-классов и интерфейсов. Они позволяет ограничить возможные реализации класса или интерфейса. Затем любой код, использующий абстрактный класс или интерфейс, может безопасно сделать предположение о возможной форме значения. Довольно часто мы хотим сделать наш класс широко доступным, но не обязательно широко расширяемым.</p>
28
<p>Например, следующее определяет интерфейс Animal с закрытым набором реализаций:</p>
28
<p>Например, следующее определяет интерфейс Animal с закрытым набором реализаций:</p>
29
public sealed interface Animal permits Cat, Dog, Elephant {...}<p>Затем реализация определяется как обычно:</p>
29
public sealed interface Animal permits Cat, Dog, Elephant {...}<p>Затем реализация определяется как обычно:</p>
30
public class Cat implements Animal { ... } public class Dog implements Animal { ... } public class Elephant implements Animal { ... }<p>Реализации могут быть либо явно перечислены после ключевого слова permit, либо они могут быть выведены компилятором, если все реализации находятся в одном исходном файле. Однако полезность стиля вывода, вероятно, ограничена, поскольку каждый публичный класс в Java должен быть объявлен в отдельном файле верхнего уровня.</p>
30
public class Cat implements Animal { ... } public class Dog implements Animal { ... } public class Elephant implements Animal { ... }<p>Реализации могут быть либо явно перечислены после ключевого слова permit, либо они могут быть выведены компилятором, если все реализации находятся в одном исходном файле. Однако полезность стиля вывода, вероятно, ограничена, поскольку каждый публичный класс в Java должен быть объявлен в отдельном файле верхнего уровня.</p>
31
<p>Scala использует то же ключевое слово sealed и механизм очень похож. Однако ключевого слова permit нет. Предполагается, что реализации всегда должны находиться в том же исходном файле, что и базовый трейт/класс. Это менее гибко, чем Java, но классы Scala также имеют тенденцию быть короче, и несколько общедоступных классов могут быть определены в одном исходном файле (часто названном в честь базового трейта/класса):</p>
31
<p>Scala использует то же ключевое слово sealed и механизм очень похож. Однако ключевого слова permit нет. Предполагается, что реализации всегда должны находиться в том же исходном файле, что и базовый трейт/класс. Это менее гибко, чем Java, но классы Scala также имеют тенденцию быть короче, и несколько общедоступных классов могут быть определены в одном исходном файле (часто названном в честь базового трейта/класса):</p>
32
sealed trait Animal class Cat extends Animal { ... } class Dog extends Animal { ... } class Elephant extends Animal { ... }<p>(обратите внимание, что в Scala public является модификатором доступа по умолчанию, поэтому он здесь опущен, в отличие от Java package-private).</p>
32
sealed trait Animal class Cat extends Animal { ... } class Dog extends Animal { ... } class Elephant extends Animal { ... }<p>(обратите внимание, что в Scala public является модификатором доступа по умолчанию, поэтому он здесь опущен, в отличие от Java package-private).</p>
33
<p>И в Java, и в Scala модификатор sealed хорошо работает с рекордами/кейс-классами. Используя эту комбинацию, мы получаем реализацию алгебраических типов данных, одного из основных инструментов функционального программирования. Рекорд/кейс класс - это тип произведения, а sealed интерфейс/трейт - это тип суммы.</p>
33
<p>И в Java, и в Scala модификатор sealed хорошо работает с рекордами/кейс-классами. Используя эту комбинацию, мы получаем реализацию алгебраических типов данных, одного из основных инструментов функционального программирования. Рекорд/кейс класс - это тип произведения, а sealed интерфейс/трейт - это тип суммы.</p>
34
<p>Что насчет расширения реализаций sealed-типов? В Java у нас есть три возможности: • реализация может быть final, что означает невозможность дальнейшего расширения; • он может быть объявлен sealed сам, снова перечисляя возможные реализации с использованием разрешений; • или он может быть non-sealed, что делает эту конкретную реализацию открытой для расширения.</p>
34
<p>Что насчет расширения реализаций sealed-типов? В Java у нас есть три возможности: • реализация может быть final, что означает невозможность дальнейшего расширения; • он может быть объявлен sealed сам, снова перечисляя возможные реализации с использованием разрешений; • или он может быть non-sealed, что делает эту конкретную реализацию открытой для расширения.</p>
35
<p>Любая реализация sealed-типа должна содержать ровно один из упомянутых выше модификаторов; однако каждая реализация может содержать свой модификатор. Например:</p>
35
<p>Любая реализация sealed-типа должна содержать ровно один из упомянутых выше модификаторов; однако каждая реализация может содержать свой модификатор. Например:</p>
36
public sealed interface Animal permits Cat, Dog, Elephant public final class Cat implements Animal { ... } public sealed class Dog permits Chihuahua, Pug implements Animal {} public non-sealed class Elephant implements Animal { ... }<p>Обратите внимание, что даже если реализация non-sealed, код, использующий sealed-тип, все равно может делать предположения о возможных реализациях из-за подтипов.</p>
36
public sealed interface Animal permits Cat, Dog, Elephant public final class Cat implements Animal { ... } public sealed class Dog permits Chihuahua, Pug implements Animal {} public non-sealed class Elephant implements Animal { ... }<p>Обратите внимание, что даже если реализация non-sealed, код, использующий sealed-тип, все равно может делать предположения о возможных реализациях из-за подтипов.</p>
37
<p>В Scala у нас похожий уровень контроля, реализации могут быть: • final; • sealed (опять же, все реализации должны быть в одном исходном файле); • без модификатора, что делает класс открытым для расширения.</p>
37
<p>В Scala у нас похожий уровень контроля, реализации могут быть: • final; • sealed (опять же, все реализации должны быть в одном исходном файле); • без модификатора, что делает класс открытым для расширения.</p>
38
<p>Последний (и по умолчанию) вариант соответствует non-sealed:</p>
38
<p>Последний (и по умолчанию) вариант соответствует non-sealed:</p>
39
sealed trait Animal final class Cat extends Animal { ... } sealed class Dog extends Animal { ... } class Elephant extends Animal { ... }<p><em>Материал является переводом первой части статьи "<a>Java 15 through the eyes of a Scala programmer</a>".</em></p>
39
sealed trait Animal final class Cat extends Animal { ... } sealed class Dog extends Animal { ... } class Elephant extends Animal { ... }<p><em>Материал является переводом первой части статьи "<a>Java 15 through the eyes of a Scala programmer</a>".</em></p>
40
40