Чего ждать от Java в 2020 году?

    2020 уже в разгаре, давайте же обсудим, какие изменения в мире Java нас ожидают в этом году. В этой статье перечислю основные тренды Java и JDK. И буду рад дополнениям от читателей в комментариях.

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

    image

    Итак, начнем. К сожалению, сразу придется разочаровать тех, кто не слишком следит за циклом Java-релизов, но ждет длинную программу поддержки (LTS). В этом году нас ждут релизы только с коротким жизненным циклом поддержки (STS).
    Первым мы рассмотрим грядущий релиз JDK 14, который должен выйти уже в середине марта. В этом релизном цикле заявлено целых 16 JEP-ов. Вот полный список:
    305: Pattern Matching for instanceof (Preview)
    343: Packaging Tool (Incubator)
    345: NUMA-Aware Memory Allocation for G1
    349: JFR Event Streaming
    352: Non-Volatile Mapped Byte Buffers
    358: Helpful NullPointerExceptions
    359: Records (Preview)
    361: Switch Expressions (Standard)
    362: Deprecate the Solaris and SPARC Ports
    363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector
    364: ZGC on macOS
    365: ZGC on Windows
    366: Deprecate the ParallelScavenge + SerialOld GC Combination
    367: Remove the Pack200 Tools and API
    368: Text Blocks (Second Preview)
    370: Foreign-Memory Access API (Incubator)

    Многие JEP-ы из этого списка широко освещались на конференции Joker 2019. Остановлюсь на тех, которые представляются мне наиболее интересными.

    Pattern Matching for instanceof (Preview)


    Долгий JEP наконец-то выходит в Preview. Думаю, если вы практикующий программист, который промышленно пишет код на Java много лет, то не раз сталкивались с этой болью:

    if (obj instanceof String) {
        String s = (String) obj;
        System.out.println(s.toUpperCase());
    }
    

    Если же вы писали или пишете код еще и на Kotlin, то боль от увиденного кода на Java немного усиливается. Участники проекта Amber представят нам своё видение Pattern Matching в Java, которое должно уменьшить эту боль. С приходом Java 14 мы сможем переписать пример следующим образом:

    if (obj instanceof String s) {
       System.out.println(s.toUpperCase());
    }
    

    Кажется, что дополнение не такое уж и ценное – экономим строчку кода. Но представим, что мы хотим сделать следующее:

    if (obj instanceof String) {
        String s = (String) obj;
        if (s.contains(“prefix_”)) {
           return s.toUpperCase();
        }
    }
    return null;
    

    Громоздко выглядит, не так ли? Попробуем тоже самое, но с Pattern Matching.

    return (obj instanceof String s) && s.contains(“prefix_”) ? s.toUpperCase() : null;
    

    Так явно будет лучше. Но помним, что статус у этой функциональности – Preview. Посмотрим, что изменится со временем. Что касается меня, это точно сделает мою жизнь лучше.

    Helpful NullPointerExceptions


    На дворе 2020 год, а вы еще пишете так, что у вас вылетают NullPointerExceptions? Не переживайте, вероятно, вы не единственный. Goetz Lindenmaier и Ralf Schmelter не предложили новый способ уйти от NullPointerExceptions (Optional все еще с нами), но предложили улучшить процесс отладки приложения, чтобы понять, где же именно лежит null. Итак, представим, что мы пишем код в пятом часу… ночи, конечно же. И написали мы вот такую функцию:

    public String getStreetFromRequest(Request request) {
       return request.getAddress().getStreet().toUpperCase();
    }
    

    Неплохо, но совсем позабыли поставить аннотации @Nullable и @Nonnull и проверить наличие адреса в переданных полях. Получили NullPointerException. Что говорит нам исключение?

    Exception in thread "main" java.lang.NullPointerException
    	at Program.getStreetFromRequest(Program.java:10)
    	at Program.main(Program.java:6)
    

    Увы, видим только строку, класс и стек. В каком именно месте вернулся null? Может, это request? Может, getAddress() вернул null? А может, и getStreet()? Что ж, цепочки вызовов иногда приносят боль.

    Авторы JEP предлагают решение: при формировании исключения предполагается обойти стек, чтобы определить место, где именно вернулся null, а затем вывести название переменных/методов. Попробуем теперь Java 14 с опцией -XX:+ShowCodeDetailsInExceptionMessages. Запускаем и получаем несколько иное:

    Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toUpperCase()" because the return value of "Address.getStreet()" is null
    	at Program.getStreetFromRequest(Program.java:10)
    	at Program.main(Program.java:6)
    

    Теперь мы знаем, что ночное программирование до добра не доводит (но доводит порой все-таки до выполнения задач в срок), а в нашей программе мы забыли, что адрес не является обязательным полем.

    Records (Preview)


    Все еще генерируете getters/setters/equals/hashCode с помощью idea? Тогда этот JEP идёт к вам!
    Data-классы занимают в жизни разработчика прикладного программного обеспечения далеко не последнее место. Каждый раз нам приходится или генерировать методы Data-классов с помощью нашей любимой IDE, или использовать различные compile-time плагины для генерации нужных методов, таких как lombok.

    В итоге у нас очень много кода, похожего на этот:

    public class Request {
        private Address address;
    
        public Address getAddress() {
            return address;
        }
    
        public void setAddress(Address address) {
           this.address = address;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Request request = (Request) o;
            return Objects.equals(address, request.address);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(address);
        }
    
        @Override
        public String toString() {
            return "Request{" +
                    "address=" + address +
                    '}';
        }
    }
    

    Или такого:
    @Data
    @AllArgsConstructor
    public class Request {
        private Address address;
    }
    

    В Java 14 участники проекта Amber предлагают новый синтаксис для создания дата-классов. Для этого нужно использовать новое ключевое слово record. Синтаксис для Record немного иной, чем для описания классов или enum, и слегка похож на Kotlin. Код, приведенный выше, можно переписать следующим образом:

    public record Request(Address address) {
    }
    

    Все поля записей по умолчанию имеют модификаторы private и final. Сам рекорд является final-классом и не может наследоваться от другого класса, но может реализовывать интерфейсы. В рекорд-классах из коробки мы получаем: getters-методы, публичный конструктор, параметры которого являются все поля record в порядке описания, equals/hashCode и toString. Из неприятного: мы не можем добавить в record поля, кроме тех, которые указаны после названия класса. Например, этот код приведет к ошибке:

    public record Request(Address address) {
       private final String anotherParameter; // compilation error
    }
    

    Text Blocks (Second Preview)


    Текстовые блоки были выпущены как превью еще в Java 13. Мы, пользователи, покрутили, повертели и дали feedback (я искренне верю, что вы уже используете Java 13 с preview). В результате создатели Java внесли небольшие изменения в textblocks. Во-первых, теперь мы можем явно указывать место окончания строки, поставив escapesequence\s в нашу строку. Вот пример:

    public static void main(String[] args) {
            final var test = """
                    This is the long text block with escape string \s
                    that is really well done            \s
                    """;
            System.out.println(test);
    }
    

    Теперь мы явно задали все пробелы до символа \s и все символы пробела будут сохранены до символа \s.

    Во-вторых, теперь мы можем переносить длинные строки текстового блока, не получая в итоговую строку \n символов. Для этого мы должны только добавить \ в месте переноса строки. Как это выглядит:

    public static void main(String[] args) {
            final var test = """
                    This is the long text block with escape string \
                    that is really well-done functional            
                    """;
    System.out.println(test);
    

    После выполнения мы получим следующую строку: “This is the long text block with escape string that is really well-done functional”.

    Неплохое, как мне кажется, дополнение. Я очень жду перевода этого функционала в standard.
    Все функции, которые мы рассмотрели, наверняка будут широко обсуждаться на ближайших конференциях. Некоторые из них уже обсуждались на Joker 2019. Обязательно посмотрите доклад Joker 2019 на эту тему “Feature evolution in Java 13 and beyond” от Cay Horstmann.

    И еще немного интересных вещей


    В JEP-списке есть два интересных пункта в инкубаторе. Для начала, у нас появится универсальный инструмент, который будет создавать инсталляторы для разных ОС (ну наконец-то, хочется сказать тем, кто танцевал вокруг установки программ на Windows). Jpacker будет способен создавать инсталляторы msi/exe для Windows, пакеты для macOS и rpm/ deb для Linux. Посмотрим, что из этого выйдет, но в тех редких случаях, когда я делал что-то для desktop, я лично страдал от того, что у меня не было штатного инструмента для сборки инсталлятора.

    Еще более перспективным выглядит новый API для доступа к “Foreign-Memory”, т.е. всякого рода нативной или персистентной памяти. В первую очередь этот API пригодится создателям баз данных на Java или создателям фреймворков, таких, как, например, Netty. Именно они, используя Unsafe и ByteBuffer, максимально оптимизируют доступ к памяти с off-heap.

    Следующий релиз. Радость и разочарование


    В сентябре нас ждет очередной short-term support release под номером 15. Список JEP, которые войдут в окончательный релиз, еще открыт. Пока можно видеть очень много различных изменений в самом языке, в стандартной библиотеке и виртуальной машине.
    Вот список кандидатов (может быстро меняться, актуально смотрим тут: bugs.openjdk.java.net/secure/Dashboard.jspa?selectPageId=19114):
    111: Additional Unicode Constructs for Regular Expressions
    116: Extended Validation SSL Certificates
    134: Intuitive Semantics for Nested Reference Objects
    152: Crypto Operations with Network HSMs
    198: Light-Weight JSON API
    218: Generics over Primitive Types
    300: Augment Use-Site Variance with Declaration-Site Defaults
    301: Enhanced Enums
    302: Lambda Leftovers
    303: Intrinsics for the LDC and INVOKEDYNAMIC Instructions
    306: Restore Always-Strict Floating-Point Semantics
    338: Vector API (Incubator)
    339: Compiler Intrinsics for Java SE APIs
    348: Compiler Intrinsics for Java SE APIs
    356: Enhanced Pseudo-Random Number Generators
    360: Sealed Types (Preview)
    371: Hidden Classes

    Как можно видеть, в списке по-прежнему нет сильно ожидаемых вещей. В первую очередь для меня это Project Loom. Идея структурного параллелизма очень популярна в последние годы. Сопрограммы позволяют сильно упростить задачу конкурентных параллельных вычислений и асинхронное исполнение задач. Отличные примеры реализации этой идеи можно увидеть, например, в языках Kotlin (coroutines) и Go (goroutines). В Java также прорабатывают идею структурного параллелизма, и уже есть первые результаты. Пока их можно посмотреть, лишь собрав JDK из репозитория проекта.

    Очень перспективный проект Valhalla также пока не порадовал нас каким-либо превью. Интересный доклад по этому проекту был представлен на Joker 2019 (“Нужны ли в Java «инлайн»-типы? Узкий взгляд инженера по производительности на проект Valhalla” от Сергея Куксенко).

    Что же представлено в списке?

    Первое, что бросается в глаза – это JSON API. Сразу напрашивается вопрос – а зачем? Однозначного ответа явно нет. В JEP-секции о мотивации сказано, что JSON стал чем-то типа стандарта для web-сервисов, и вот оно время, чтобы адаптировать Java SE для взаимодействия с JSON (даже несмотря на то, что сейчас есть тонна библиотек для парсинга JSON). Самое вероятное объяснение – возможность разработчикам ПО использовать небольшой core API, чтобы снизить размер bundle, не затаскивая тяжелый Jackson себе. Иного объяснения я не вижу, поскольку в нем даже не будет databinding.

    Также мы видим ряд улучшений, связанных с криптографическим API. Для начала, разработчики JDK хотят внести расширение в процесс валидации SSL-сертификатов, добавив поддержку EVSSL-сертификатов. С помощью этого API в Java можно будет определить, является ли SSL-соединение доверенным по сертификату EV (Extended Validation). Будет полностью поддержан EVSSL-сертификат в соответствии с guideline. Также будет добавлен новый криптографический алгоритм EdDSA и будет улучшена проверка HSM-криптографии.

    Из языковых вещей я бы выделил реализацию Generics над примитивами. Все, кто когда-либо программировал на C# и перешел на Java, наверняка могли задать вопрос, почему же нельзя делать Generic-типы на примитивах. Ответ прост – Generics работают только с объектами, а примитивы не являются объектами и, в частности, не наследуют Object-класс. Уже не первый год идет война по этому вопросу, и Brian Goetz снова возвращается к нему. Описывать особо пока нечего. Задача понятна – поддержать такие конструкции, как List. Но даже на текущий момент есть 13 открытых вопросов, которые надо решить перед внедрением этого функционала. Честно говоря, мне интересно, чем закончится этот сериал.

    И последнее, что хочется затронуть, – это sealed types. Это очередной шаг к pattern matching. Sealed Types – это расширение языка, которое внедряет ключевые слова sealed (модификатор) и permits для класса или интерфейса.

    С помощью sealed-класса мы ограничиваем число наследников только теми классами, которые указаны в permits (явное ограничение) или в той же единице компиляции (файле). Пример описания sealed class:

    // неявно
    public abstract sealed class Base {
       public static class ChildA extends Base{}
       public static class ChildB extends Base{}
    }
    
    // явно
    public sealed interface BaseInterface permits ChildC, ChildD{
    }
    
    //в другом файле
    public class ChildC implements BaseInterface {
    }
    //в другом файле
    public class ChildD implements BaseInterface {
    }
    

    Sealed-модификатор гарантирует, что только ограниченный конечный набор наследников может расширять базовый класс или реализовывать интерфейс. Это свойство можно использовать при обработки объектов данных классов. И, конечно же, это отличный кандидат на использование в switch-операторе с pattern matching.

    Заключение


    Мы посмотрели на разные нововведения JDK этого года. Какие-то из них выстрелят, какие-то нет. Но больше всего в новых JDK я жду новые маленькие (или не очень) оптимизации, которые бесплатно делают наши программы быстрее с каждым релизом. И если вы были на последнем Joker 2019 и посетили доклад Тагира Валеева Java 9-14: Маленькие оптимизации, то, скорее всего, также как и я, были впечатлены той работой, которую проделывают контрибьюторы для оптимизации JDK. Ее результаты не видны с первого взгляда и не отражены не в одном JEP, но мы используем их каждый день.

    Хороших выпусков Java нам всем. Изучайте новые возможности платформы, ходите на конференции и следите за трендами.
    DINS
    Компания

    Похожие публикации

    Комментарии 27

      0
      Спасибо за пост!
        +6
        с null pointerами конечно найс, многих избавит от боли
          +2
          Вот бы структуры завезли как в C#, которые обладали бы поведением, но хранились на стеке, и копировались по значению. И чтобы их, вместе с примитивами можно было упаковывать в списки и передавать как параметр дженерикам:
          val list = new ArrayList<int>(); // вместе с Lombok будет красота

          Исторический костыль с int, Integer, double, Double очень раздражает.
          Пожалуй самый бросающийся в глаза, если учесть что 95% работы рядового программиста, это работа со списками.
          И хотя говорят что эффективность не фича Java, но вы прикиньте как бы мы разгрузили GC, если бы переложили часть операций на стек.
          Согласен, может кому то и не понадобится, но кучу высоконагруженных проектов это спасет от жестких оптимизаций, когда все хранится в виде кучи полей-массивов из примитивов (привет процедурное программирование). Можно будет писать требующие быстродействия и экономии вещи в привычном ООП стиле.
            +1
            Для этого стоит дождаться value types от Valhalla. Value types выглядят как очень хороший шаг вперед в области эффективности при должном уровне абстракции. Но пока там есть много открытых вопросов. Это реально один из самых интересных проектов Java. Наверно Loom и Valhalla для меня самые интересные на текущий момент.
              0
              Насколько я понял концепцию value types, там все же используется куча, а не настоящий стек, просто добавляется семантика копирования как у примитивных типов. Но хотя бы так! Жду их еще со времен Java 7 :)
                0
                На последнем Joker хороший доклад Олега Куксенко на эту тему был. Если кратко, решает JVM. Она может положить как в стек, так и в кучу.
                Вот вырезка из PDF доклада
                JVM decides if:
                ● allocate on heap
                OR
                ● put on stack (locals, parameters, result)
                ● inline into container class
                ● inline into array (flattened array)

                Посмотрим, конечно, что в финале выйдет. Но тем не менее это уже шаг вперед.
                  0
                  Как вы думаете, а это может по скорости выйти даже быстрее чем в шарпе?
                    0
                    В общем у меня надежда есть, но к сожалению я не перформанс инженер Java\JVM и сказать авторитетно ничего не смогу, пока не выйдет в public финальная версия. А потом можно будет попробовать сравнить, сделав какие-нибудь объективные тесты на одинаковом окружении.
                    Мое мнение тут субъективно. Пока все последние оптимизации JVM и стандартной библиотеки очень даже радуют.
            0

            Давно бы уже включили Nullable / NotNull в стандартную библиотеку, если так сильно элвис-оператор делать не хотят.

              0

              Ну в целом мы в наших проектах активно используем javax.annotation.nonnull и nullable. Вместе с современной IDE в нашем коде nptr ex не случается, максимум на стыке со сторонними библиотеками. Но есть побочные явления:
              1) код естественно захламляется
              2) можно забыть поставить аннотацию и ide ничего не скажет (код ревью нам в помощь)
              3) в постороннем коде аннотаций может и не быть (и скорее всего и не будет)
              Добавить в jdk такую аннотацию можно было бы и в compile time проверять. Это безусловно усложнит анализ кода. Надо будет отслеживать проверки на null. Кроме того весь багаж кода как-то надо будет поддержать по обратной совместимости. Не будут же все переписывать библиотеки?


              Честно говоря мне очень нравится в этом плане Kotlin. nullable типы как отдельный класс типов очень удобен. Пока явно не указал, все notnull. Кроме того Kotlin компилятор хорошо понимает все виды java notnull аннтотации. Прям хорошая работа

              0

              Заранее извиняюсь, но любопытство не порок. Я крайне плохо разбираюсь в Java и как следствие некоторый код мне не понятен. В частности, что вот тут происходит:


              if (obj instanceof String) {
                  String s = (String) obj;
                  System.out.println(s.toUpperCase());
              }

              Зачем делать преобразование типов, если переменна obj и так является String? Или это не преобразование?

                +1
                Java является строго типизированным языком. Все классы в иерархии идут от класс Object. Зачастую методы могут принимать обобщенный тип, аж до Object и в зависимости от типа менять свое поведение. Кроме того, компилятор не запоминает контекст, в отличии, например, от Kotlin. Для него то, что мы проверили на String уже на следующей строке не имеет значения.
                public class Sample {
                   public void method(Object obj) {
                      System.out.println(obj.toUpperCase()); // compile-time error
                   }
                   public void methodB(Object obj) {
                      if (obj instanceof String) {
                         System.out.println(obj.toUpperCase()); // compile-time error
                      }
                   }
                }
                


                В этом плане Kotlin является более продвинутым в компиляции. Вот тот же пример на Kotlin
                fun sampleFun(obj: Any) {
                   if (obj is String) {
                      print(obj.toUpperCase())  // it works
                  }
                }
                
                  0

                  А если obj is String вынести в функцию и вставить ее вызов в if — сработает?

                    0
                    Нет, так не сработает. Даже если вспомогательная функция будет inline.
                      +1
                      Само по себе не сработает, но для этого в Kotlin есть специальная штука — контракты (тут важно заметить что компилятор не проверяет гарантии и можно всё сломать). И я не уверен, но у них вроде бы еще экспериментальный статус (как раз потому что можно всё сломать ими).
                      fun isNotEmptyString(obj: Any):Boolean {
                          contract {
                              returns(true) implies (obj is String)
                          }
                          return (obj is String) && obj.isNotEmpty()
                      //return (obj is Int) && obj > 0 //still valid with contract 
                      }

                      Тут подробнее
                        0

                        Спасибо! Интересная вещь, хотя и не совсем понятно, в чем профит программиста от такого описания контракта? Мы добавляем какую-то метаинфу для компилятора, чтобы он лучше понимал основной код, и, видимо, не выдавал ворнинги. Не проще ли сразу задизайнить язык так, чтобы компилятор понимал код без подсказок?

                          0
                          Как мне кажется, вопрос же сложности компилятора. До какой вложенности стоит запоминать контекст? А если мы присваиваем новой переменной ссылку внутри новой функции, а потом проверяем? Стоит ли нам в этом случае запоминать контекст? Это трейдофф между мощностью компилятора, временем компиляции и конечно же ресурсами, которые используются для компиляции
                            0

                            Я имел ввиду, что можно было бы сделать полноценный паттерн-матчинг как синтаксическую конструкцию. Да, для компилятора это было бы чуть более напряжно. Но пусть уж лучше компилятор напрягается, чем программист, которому теперь надо держать в голове все неявные преобразования, которые делают смарткасты и контракты. Целых два разных механизма, решающих довольно узкую задачу, которая по идее должна достаточно редко возникать в языке с type-safety и null-safety.

                            +1
                            С одной стороны когда допилят компилятор такие штуки станут не особо нужны. Но с другой стороны мы можем проверять тип неявно, но при этом быть уверены что тип будет именно тот который нам нужен. Например мы добавляем всем потомкам поле type, по которому можем узнать класс. (Я не уверен насколько оправдано такое решение, но вроде бы оно может быть быстрее чем «x is A»)
                            код
                            enum class FigureType {
                                CIRCLE, RECTANGLE
                            }
                            
                            sealed class Figure {
                                internal abstract val type: FigureType
                            }
                            
                            class Circle(val x: Int, val y: Int, val radius: Int) : Figure() {
                                override val type = FigureType.CIRCLE
                            
                            }
                            
                            class Rectangle(val x0: Int, val y0: Int, val x1: Int, val y1: Int) : Figure() {
                                override val type = FigureType.RECTANGLE
                            }
                            
                            @ExperimentalContracts
                            fun Figure.isCircle(): Boolean {
                                contract {
                                    returns(true) implies (this@isCircle is Circle)
                                }
                                return type == FigureType.CIRCLE
                            }
                            
                            @ExperimentalContracts
                            fun Figure.isRectangle(): Boolean {
                                contract {
                                    returns(true) implies (this@isRectangle is Rectangle)
                                }
                                return type == FigureType.RECTANGLE
                            }
                            
                            @ExperimentalContracts
                            fun test(figure: Figure) {
                                when {
                                    figure.isCircle() -> {
                                        println(figure.x)
                                        println(figure.y)
                                        println(figure.radius)
                                    }
                                    figure.isRectangle()-> {
                                        println(figure.x0)
                                        println(figure.y0)
                                        println(figure.x1)
                                        println(figure.y1)
                                    }
                                }
                            }

                              0
                              Да, достаточно интересное использование, но думаю перегруженное для такого. По производительности надо замерять конечно.
                                +1

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


                                Чисто для сравнения аналогичный вашему код на Scala:


                                sealed trait Figure
                                case class Circle(x: Int, y: Int, radius: Int) extends Figure
                                case class Rectangle(x0: Int, y0: Int, x1: Int, y1: Int) extends Figure
                                
                                val test: Figure => Unit = _ match {
                                  case Circle(x, y, radius) => println(s"$x, $y, $radius")
                                  case Rectangle(x0, y0, x1, y1) => println(s"$x0, $y0, $x1, $y1")
                                }
                                  0
                                  Насколько я понимаю конструкция
                                  _ match {
                                    case Circle(x, y, radius) => println(s"$x, $y, $radius")
                                    case Rectangle(x0, y0, x1, y1) => println(s"$x0, $y0, $x1, $y1")
                                  }
                                  внутри содержит instanceof (посмотрел декомпилированный код). А я как раз хотел показать пример где instanceof не используется и компилятор нуждается в подсказке.
                                  Код на котлине эквивалентеый вашему коду на Scala
                                  when(figure){
                                     is Circle -> with(figure) { println("$x $y $radius") }
                                     is Rectangle -> with(figure) { println("$x0 $y0 $x1 $x1") }
                                  }
                                    0
                                    А я как раз хотел показать пример где instanceof не используется и компилятор нуждается в подсказке.

                                    Хмм, а зачем это нужно? Пусть компилятор под капотом проверяет типы как хочет. Обычно все-таки люди стремятся писать код, который решает задачи, а не подсказки для компилятора, которые выглядят гораздо страшнее, чем обычный каст, который они заменяют.


                                    Код на котлине эквивалентеый вашему коду на Scala

                                    Он не совсем эквивалентный.


                                    Во-первых, в котлиновской версии одна и та же переменная figure имеет сначала один тип, а потом уже другой. Это облегчает написание кода (не надо самому писать каст), но усложняет чтение. Даже в джаве, которая гораздо сильнее ограничена в плане синтаксиса, это сделали по-нормальному.


                                    Во-вторых, насколько такой подход масштабируется на чуть более сложные случаи типа:


                                    case Vacancy(id, _, Some(Salary(from, to, Some(currency))), Nil) => ...
                                      0
                                      Как раз нашёл статью где сравниваются два подхода — instanceOf и enum, с instanceOf медленнее, хоть и красивее. habr.com/ru/post/430014
                                      По моему идея прятать не очень красивые, но более эффективные решения в глубины кода в целом неплохая. А то если совсем не заглядывать под капот, то можно получить проблемы с производительностью (но тут речь скорее про библиотеки, а не прикладные программы)

                                      Ну из за особенностей синтаксиса я не мог написать на котлине точно так же как на Scala. Со сложными случаями выглядит интересно, спасибо почитаю.
                                0
                                Не проще ли сразу задизайнить язык так, чтобы компилятор понимал код без подсказок?

                                Подозреваю это противоречит необходимости не уходить синтаксически далеко от Java. Все же для такой точной компиляции надо менять систему типов, делая её похожей на Haskell.

                                  0

                                  Ну имхо, котлиновский синтаксис when весьма далек от джавы, точно не ближе скаловского match/case.

                          0
                          (Хм, я таки забыл refresh перед комментированием. Пусть уже остаётся.)

                          К моменту выполнения кода, obj имеет тип Object, то есть, ссылки на что угодно, которое является объектом (не примитивным типом, как int). Мы тут не уверены, что это String; чтобы использовать, надо это проверить. Если мы попытаемся obj, который не String, «преобразовать» к String, получим исключение.

                          На уровне рантайма это не преобразование — это проверка типа и присвоение переменной, сама ссылка остаётся точно такой же. Но на уровне языка это метод проверить и, если тип подходит, сделать присвоение. Слово «преобразование» тут немного условно, но устоялось.

                          Ситуаций, в которых такое нужно — проверить и выполнить действие соответственно типу — очень много, хотя в основном это действия достаточно нижнего уровня (системные библиотеки, всякие универсальные фреймворки).

                          В языках, которые писались, когда грабли этой концепции были уже хоть частично пройдены, обычно есть совмещённый оператор проверки-и-конверсии, результатом которого является или ссылка уже точно нужного типа, или null. Например, C# использует для этого «as»: можно было бы написать

                          if ((String s = obj as String) != null) {
                              System.out.println(s.toUpperCase());
                          }
                          


                          но авторы Java пропустили эту возможность (и не стоит их сильно винить за это, они были почти первопроходцы), а затем решили расширить синтаксис уже существующей проверки.
                          Упомянутый pattern matching — ещё более продвинутый вариант такой проверки (обычно со множественными вариантами).

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

                        Самое читаемое