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

Сегодня я хотел бы поиграться с этим новым оператором и рассмотреть особенности его работы более детально. Так как паттерн-матчинг по типу ещё не вошёл в главный репозиторий JDK, мне пришлось скачать репозиторий проекта Amber, в котором ведётся разработка новых синтаксических конструкций Java, и собрать JDK из этого репозитория.

Итак, первое, что мы сделаем — проверим версию Java, чтобы убедиться, что мы действительно используем JDK 14:

> java -version
openjdk version "14-internal" 2020-03-17
OpenJDK Runtime Environment (build 14-internal+0-adhoc.osboxes.amber-amber)
OpenJDK 64-Bit Server VM (build 14-internal+0-adhoc.osboxes.amber-amber, mixed mode, sharing)

Всё верно.

Теперь напишем небольшой кусок кода со «старым» оператором instanceof и запустим его:

public class A {
    public static void main(String[] args) {
        new A().f("Hello, world!");
    }

    public void f(Object obj) {
        if (obj instanceof String) {
            String str = (String) obj;
            System.out.println(str.toLowerCase());
        }
    }
}

> java A.java
hello, world!

Работает. Это стандартная проверка на тип с последующим приведением. Подобные конструкции мы пишем изо дня в день, какую бы версию Java мы бы не использовали, хоть 1.0, хоть 13.
Но теперь у нас в руках Java 14, и давайте перепишем код с использованием улучшенного оператора instanceof (повторяющиеся строки кода в дальнейшем буду опускать):

if (obj instanceof String str) {
    System.out.println(str.toLowerCase());
}

> java --enable-preview --source 14 A.java
hello, world!

Прекрасно. Код стал чище, короче, безопаснее и читабельнее. Было три повторения слова String, стало одно. Заметьте, что мы не забыли указать аргументы --enable-preview --source 14, т.к. новый оператор является preview feature. Кроме того, внимательный читатель, наверное, заметил, что мы запустили исходный файл A.java напрямую, без компиляции. Такая возможность появилась в Java 11.

Давайте попробуем написать что-нибудь более навороченное и добавим второе условие, которое использует только что объявленную переменную:

if (obj instanceof String str && str.length() > 5) {
    System.out.println(str.toLowerCase());
}

Компилируется и работает. А что если поменять условия местами?

if (str.length() > 5 && obj instanceof String str) {
    System.out.println(str.toLowerCase());
}

A.java:7: error: cannot find symbol
        if (str.length() > 5 && obj instanceof String str) {
            ^

Ошибка компиляции. Чего и следовало ожидать: переменная str ещё не объявлена, а значит не может быть использована.

Кстати, что с мутабельностью? Переменная final или нет? Пробуем:

if (obj instanceof String str) {
    str = "World, hello!";
    System.out.println(str.toLowerCase());
}

A.java:8: error: pattern binding str may not be assigned
    str = "World, hello!";
    ^

Ага, переменная final. Это значит, что слово «переменная» здесь вообще не совсем корректно. Да и компилятор использует специальный термин «pattern binding». Поэтому предлагаю отныне говорить не «переменная», а «биндинг паттерна» (к сожалению, слово «binding» не очень хорошо переводится на русский).

С мутабельностью и терминологией разобрались. Поехали экспериментировать дальше. Вдруг у нас получится «сломать» компилятор?

Что если назвать переменную и биндинг паттерна одним и тем же именем?

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

A.java:7: error: variable obj is already defined in method f(Object)
if (obj instanceof String obj) {
                          ^

Логично. Перекрытие переменной из внешней области видимости не работает. Это эквивалентно тому, как если бы мы просто завели переменную obj второй раз в той же области видимости.

А если так:

if (obj instanceof String str && obj instanceof String str) {
    System.out.println(str.toLowerCase());
}

A.java:7: error: illegal attempt to redefine an existing match binding
if (obj instanceof String str && obj instanceof String str) {
                              ^

Компилятор надёжен как бетон.

Что ещё можно попробовать? Давайте поиграемся с областями видимости. Если в ветке if определён биндинг, то будет ли он определён в ветке else, если инвертировать условие?

if (!(obj instanceof String str)) {
    System.out.println("not a string");
} else {
    System.out.println(str.toLowerCase());
}

Сработало. Компилятор не только надёжен, но ещё и умён.

А если так?

if (obj instanceof String str && true) {
    System.out.println(str.toLowerCase());
}

Опять сработало. Компилятор корректно понимает, что условие сводится к простому obj instanceof String str.

Неужели не удастся «сломать» компилятор?

Может, так?

if (obj instanceof String str || false) {
    System.out.println(str.toLowerCase());
}

A.java:8: error: cannot find symbol
    System.out.println(str.toLowerCase());
                       ^

Ага! Вот это уже похоже на баг. Ведь все три условия абсолютно эквивалентны:

  • obj instanceof String str
  • obj instanceof String str && true
  • obj instanceof String str || false

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

Но да ладно, давайте попробуем ещё что-нибудь. Будет ли работать такое:

if (!(obj instanceof String str)) {
    throw new RuntimeException();
}
System.out.println(str.toLowerCase());

Скомпилировалось. Это хорошо, поскольку этот код эквивалентен следующему:
if (!(obj instanceof String str)) {
    throw new RuntimeException();
} else {
    System.out.println(str.toLowerCase());
}

А так как оба варианта эквивалентны, то и программист ожидает, что они будут работать одинаково.

Что насчёт перекрытия полей?

public class A {
    private String str;

    public void f(Object obj) {
        if (obj instanceof String str) {
            System.out.println(str.toLowerCase());
        } else {
            System.out.println(str.toLowerCase());
        }
    }
}

Компилятор не заругался. Это вполне логично, потому что локальные переменные всегда могли перекрывать поля. Для биндингов паттернов, видимо, тоже решили не делать исключения. С другой стороны, такой код довольно хрупок. Одно неосторожное движение, и вы можете не заметить, как ваша ветка if сломалась:

private boolean isOK() {
    return false;
}

public void f(Object obj) {
    if (obj instanceof String str || isOK()) {
        System.out.println(str.toLowerCase());
    } else {
        System.out.println(str.toLowerCase());
    }
}

В обеих ветвях теперь используется поле str, чего может не ожидать невнимательный программист. Чтобы как можно раньше обнаруживать подобные ошибки, используйте инспекции в IDE и разную подсветку синтаксиса для полей и переменных. А ещё я рекомендую всегда использовать квалификатор this для полей. Это добавит ещё больше надёжности.

Что ещё интересного? Как и «старый» instanceof, новый никогда не матчит null. Это значит, что можно всегда полагаться на то, что биндинги паттернов никогда не могут быть null:

if (obj instanceof String str) {
    System.out.println(str.toLowerCase()); // Никогда не выбросит NullPointerException
}

Кстати, используя это свойство, можно укоротить подобные цепочки:

if (a != null) {
    B b = a.getB();
    if (b != null) {
        C c = b.getC();
        if (c != null) {
            System.out.println(c.getSize());
        }
    }
}

Если использовать instanceof, то код выше можно переписать так:

if (a != null && a.getB() instanceof B b && b.getC() instanceof C c) {
    System.out.println(c.getSize());
}

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

Что насчёт дженериков?

import java.util.List;

public class A {
    public static void main(String[] args) {
        new A().f(List.of(1, 2, 3));
    }

    public void f(Object obj) {
        if (obj instanceof List<Integer> list) {
            System.out.println(list.size());
        }
    }
}

> java --enable-preview --source 14 A.java
Note: A.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
3

Очень интересно. Если «старый» instanceof поддерживает только instanceof List или instanceof List<?>, то новый работает с любым конкретным типом. Ждём первого человека, который попадётся вот в такую ловушку:

if (obj instanceof List<Integer> list) {
    System.out.println("Int list of size " + list.size());
} else if (obj instanceof List<String> list) {
    System.out.println("String list of size " + list.size());
}

Почему это не работает?
Ответ: отсутствие reified generics в Java.

ИМХО, это довольно серьёзная проблема. С другой стороны, я не знаю, как можно было бы её исправить. Похоже, опять придётся полагаться на инспекции в IDE.

Выводы


В целом, новый паттерн-матчинг по типу работает очень круто. Улучшенный оператор instanceof позволяет делать не только тест на тип, но ещё и объявлять готовый биндинг этого типа, избавляя от необходимости ручного приведения. Это означает, что в коде будет меньше шума, и читателю будет гораздо проще разглядеть полезную логику. Например, большинство реализаций equals() можно будет писать в одну строчку:

public class Point {
    private final int x, y;
    …

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof Point p && p.x == this.x && p.y == this.y;
    }
}

Код выше можно написать ещё короче. Как?
С помощью записей, которые также войдут в Java 14. О них мы поговорим в следующий раз.

С другой стороны, вызывают небольшие вопросы несколько спорных моментов:

  • Не полностью прозрачные правила области видимости (пример с instanceof || false).
  • Перекрытие полей.
  • instanceof и дженерики.

Однако это скорее мелкие придирки, нежели серьёзные претензии. В целом, огромные преимущества нового оператора instanceof определённо стоят его добавления язык. А если он ещё выйдет из состояния preview и станет стабильной синтаксической конструкцией, то это будет большой мотивацией наконец-то уйти с Java 8 на новую версию Java.

P.S. У меня есть канал в Telegram, где я пишу о новостях Java. Призываю вас на него подписаться.

Комментарии (66)


  1. romankh3
    27.11.2019 10:49
    +2

    Интересный подход к изучению нового функционала!
    Продолжай в том же духе


  1. slavap
    27.11.2019 10:50

    биндинг паттерна — говорить точно не нужно. Pattern binding — вполне нормально. И какие проблемы с переводом binding на русский? Связанный, привязанный, прикреплённый,… — уйма вариантов.


    1. nile1
      27.11.2019 14:31

      Тем более что он "байндинг", а не "биндинг"


  1. xdenser
    27.11.2019 11:03

    Не понятно это привязка или все же присваивание? Не рассмотрен случай, когда obj изменился после instanceof.


  1. bugy
    27.11.2019 11:10

    Хм, непонятно, почему решили сделать с объявлением дополнительной переменной (myVar instanceof String str)
    В котлине, насколько я знаю, можно просто с изначальной переменной дальше работать, будто она этого типа


    В JEP про это написано: мы не хотим ad hoc а хотим pattern matching. Но имхо это странно — усложнять имеющийся синтакс ради потенциальной новой фичи, которая может быть ещё совершенно иначе будет реализована
    Да ещё и на ровном месте добавлять смесь проблем с перекрытием переменных и умных условий


    1. igormich88
      27.11.2019 11:31

      Насколько я понимаю подход описанный в статье один в один скопирован из C#.
      PS: мне тоже вариант из Котлина кажется удобнее.


      1. Deosis
        27.11.2019 12:17

        В C# это объяснили тем, что вместо myVar можно подставить любое выражение, и оно будет вычислено один раз.


    1. mayorovp
      27.11.2019 11:40

      Потому что введение в старый язык смарткастов может изменить работу существующего кода.


      Например, вот в этом случае:


              if (foo instanceof Bar) {
                  foo.new Baz().run();
              }

      Если в классе Bar переопределен внутренний класс Baz — поведение кода после введения смарткастов изменится.


      Кроме внутреннего класса, можно неудачно переопределить поле.


      1. bugy
        27.11.2019 13:22

        Логично, спасибо


    1. orionll Автор
      27.11.2019 12:17

      Лично мне не нравятся смарткасты, потому что они могут работать только с final полями и final/effectively final переменными:

      private Object field;
      ...
      if (this.field instanceof String) {
           // Можем использовать this.field как String здесь или нет?
      }


      В Java-подходе такой проблемы нет:
      if (this.field instanceof String str) {
           // Используем str
      }


      1. bugy
        27.11.2019 13:23

        Интересный пример, но "такой проблемы нет" только в случае атомарности оператора?
        Я, к сожалению, не нашел ничего про concurrency в pattern matching


        1. mayorovp
          27.11.2019 13:38

          Тут речь идёт не о многопотоке, а о реентерабельности и внезапных связях.


          Вот вам пример к чему это приводит в Typescript:


          switch (derivation.dependenciesState) {
              // ...
              case IDerivationState.POSSIBLY_STALE: {
                  // ...
                  obj.get();
          
                  // ...
                  // https://github.com/mobxjs/mobx/blob/master/src/core/derivation.ts#L117
                  if ((derivation.dependenciesState as any) === IDerivationState.STALE) {
                      // ...
                  }
              }
          }

          Зачем тут каст к any? А чтобы заткнуть компилятор, который уверен, что derivation.dependenciesState никак не может быть равен IDerivationState.STALE внутри блока case IDerivationState.POSSIBLY_STALE. Даже несмотря на то, что obj.get() этот самый dependenciesState обязательно изменяет.


  1. zagayevskiy
    27.11.2019 11:18
    -2

    Компилятор надёжен как бетон.

    Компилятор просто бетон /sarcasm
    Джаву пора закапывать, кажется. Каждый релиз вызывает фейспалм.
    Это называется smart cast и должно работать без всякой дополнительной фигни.


    1. mayorovp
      27.11.2019 11:42

      Не должно. Обратная совместимость же! Вот если обратную совместимость нарушат — тогда джаву и правда будет пора закапывать...


      1. zagayevskiy
        27.11.2019 11:53

        А что «обратная совместимость»? Чем это лучше, чем введение нового оператора?


      1. DieSlogan
        28.11.2019 10:07

        У одной нашей команды есть куча проектов, которые работают на Java8, максимум. Совместимость уже частично нарушена.


      1. Xobotun
        28.11.2019 12:03

        В мире майнкрафта не получается запускать сервера с плагинами на версиях выше 8: почему-то код сервера, на который можно ставить плагины, требует какую-то дикую интроспекцию, которую Jigsaw запрещает. По крайней мере, мне это так объяснили. :)


  1. tsypanov
    27.11.2019 11:40
    +3

    Спасибо за хорошую статью, однозначно жирный плюс!


    Смущает пара моментов:


    1)


    До этого всё в Java по умолчанию являлось non-final: поля, классы, методы, параметры методов и даже параметры лямбд.

    Параметры лямбд действительно можно переприсваивать, только это не работает для переменных, захваченых лямбдами:


    public void foo(String name) {
      String str = "";
    
      cache.computeIfAbsent(name, beanName -> {
        str = beanName;                                    // <--------- ошибка компиляции
        return beanName;
      });
    }

    2)
    Вместо "биндинг" можно писать "связывание", ИМХО, это как раз тот момент, когда вместо заимствования лучше сделать дословный перевод.


    3)


    Ага! Вот это уже похоже на баг.

    Писали ли вы в указанную в начале статьи рассылку? Что говорят разработчики?


    1. Xobotun
      27.11.2019 14:26

      1) У лямбд, как мне кажется, всё же чуть иной случай: чтобы переменная могла быть захвачена, она должна быть либо явно final, либо effectively final. У новых «связываний» финальность появляется от рождения, что чуть отличается от захваченных переменных в лямбдах.

      Но это исключительно к слову пришлось. :D


    1. lany
      27.11.2019 19:24
      +1

      Не туда копаешь. Неявный final внутри метода появился в multi-catch в седьмой джаве. Правда про это мало кто знает кроме разработчиков IDE.


      Если же рассматривать объявление класса, то поля, объявленные в интерфейсе, неявно final (и static) с первой джавы. Ну и константы enum туда же.


      1. orionll Автор
        28.11.2019 06:26

        Да, что-то я в торопях даже не заметил, что про final откровенный бред написал. Я написал, что методы и классы по умолчанию являются final, что очевидно не так.


      1. tsypanov
        28.11.2019 12:34

        Ну, поля интерфейсов и перечисления я не упомянул как сами собой разумеющиеся вещи.


        Неявный final внутри метода появился в multi-catch в седьмой джаве. Правда про это мало кто знает кроме разработчиков IDE.

        Истину глаголешь, до твоего комментария я об этом не знал :)


  1. Enverest
    27.11.2019 13:16
    +1

    Очень хороший доклад Тагира Валеева на эту тему.


  1. Xobotun
    27.11.2019 14:19

    А что произошло с областями видимости вот в этом примере: v?

    if (!(obj instanceof String str)) {
        throw new RuntimeException();
    }
    System.out.println(str.toLowerCase());

    Скомпилировалось. <...>

    "Биндинг Связывание паттерна" str вышло за пределы фигурных скобок условного оператора?
    То есть он работает иначе, чем try-with-resources, у которого Closeable-переменные, объявленные в круглых скобках, видны только внутри. Это тоже может доставить проблем, чувствую.


    1. orionll Автор
      27.11.2019 14:27

      Связывание паттерна" str вышло за пределы фигурных скобок условного оператора?

      Да, это называется flow scoping.


      1. Xobotun
        27.11.2019 15:06

        А, это не баг, это фича. Ок.

        Спасибо за статью. :)


  1. MaximChistov
    27.11.2019 14:59
    +1

    Имхо не хватает еще вот такого случая в примерах:

    Object obj = "a";
    if (obj instanceof String s) {
        obj = "b";
        System.out.println(s.toLowerCase());
    }

    Как сработает этот код? Не скомпилируется? Выдаст runtime error? Просто выведет «a»? Просто выведет «b»? Если выведет «a», то поведение уже не как у instnceof + cast, которое вывело бы значение obj на момент выполнения println


    1. mayorovp
      27.11.2019 15:17
      +1

      Конечно же, поведение именно такое, как у instanceof + cast:


      Object obj = "a";
      if (obj instanceof String) {
          String s = (String)obj;
          obj = "b";
          System.out.println(s.toLowerCase());
      }


  1. goodvin1709
    27.11.2019 15:37

    Привет Kotlin'у и его smartcast.


  1. dim2r
    27.11.2019 16:44

    Попробуйте еще тренарный оператор помучить. Что-то типа

    int a = AA instanceof Integer aa? 1: null;


    1. orionll Автор
      29.11.2019 10:16

      Это не очень интересно, потому что всё работает аналогично if. А вот что-нибудь другое помучить можно, например:

      for (Object obj = ...; obj instanceof String str; obj = ...) {
          // Используем str
      }

      Или вот такое:
      final boolean ok = obj instanceof String str;
      if (ok) {
          System.out.println(str.toLowerCase());
      }

      Последнее, кстати, не работает.


      1. dim2r
        29.11.2019 15:13

        работает аналогично if.

        В тренарном есть такой финт, который компилируется, но вызывает ошибку исполнения:

        int a=false?1:null;


        если преобразовать в if, то не скомпируется
        int a;
        if(false){a=1;}else{a=null;}


        error: incompatible types


        1. lany
          30.11.2019 04:11

          В плане скоупинга переменных паттерна работает аналогично. Про вывод типов никто ничего не говорил.


  1. fRoStBiT
    27.11.2019 16:59

    Note: A.java uses unchecked or unsafe operations.
    Похоже, опять придётся полагаться на инспекции в IDE.

    Поведение идентично unchecked cast, и реакция компилятора соответствующая, так что никаких проблем нет. Предупреждения компилятора надо всё-таки читать.


    1. orionll Автор
      27.11.2019 17:39

      Проблема есть. Она заключается во внешней обманчивости конструкции instanceof List<Integer>. Когда новичок смотрит на такое, то у него может возникнуть ложное впечатление, будто он на самом деле проверяет список на наличие только целых чисел в нём. Да, раньше было то же самое, но оно по крайней мере всё было явно: сначала проверяем instanceof List, а потом приводим объект к List<Integer>. Сейчас всё это запрятано внутрь нового instanceof'а и разобраться в деталях стало немного сложнее.


      1. fRoStBiT
        28.11.2019 10:25

        ложное впечатление, будто он на самом деле проверяет список на наличие только целых чисел в нём

        Но ведь это ничем не отличается от явного приведения:


        final var intList = (List<Integer>) list;

        Тоже создаёт "ложное впечатление, будто он на самом деле проверяет список на наличие только целых чисел в нём", ведь обычно приведение типа это делает (с массивами например).


  1. Beholder
    27.11.2019 21:00
    +1

    Эх, чего только не придумают, лишь бы на Kotlin не переходить...


    if (a != null) {
        B b = a.getB();
        if (b != null) {
            C c = b.getC();
            if (c != null) {
                System.out.println(c.getSize());
            }
        }
    }

    Эквивалент:


    a?.b?.c?.let { println(it.size) }

    Ну или если с проверкой типов


    (((a as? A)?.b as? B)?.c as? C)?.let { println(it.size) }

    Не очень красиво, но можно ещё так:


    inline fun <reified T> Any?.cast(): T? = this as? T
    
    a.cast<A>()?.b.cast<B>()?.c.cast<C>()?.let { println(it.size) }


    1. prs123
      28.11.2019 11:52

      Ну потому что куча больших проектов уже написано на Java и на переписывание этого добра на Kotlin нет ни сил, не средств


      1. mayorovp
        28.11.2019 13:47

        Ну, на Котлин не обязательно переносить сразу весь проект, можно начинать с отдельных файлов же.


  1. koowaah
    27.11.2019 22:22

    Спасибо за статью. Вот паттерн патчинг и в Java завозят. Хотелось также чтобы type test pattern также работал и с switch. Не хватает также deconstruction pattern и других паттернов. Но думаю, должны со временем завести.
    Хотя многие паттерны можно реализовать и в виде библиотеки, но поддержка на уровне языка это круто.


    1. tsypanov
      29.11.2019 12:20

      Вот паттерн патчинг и в Java завозят

      Всё-таки матчинг :)


  1. snuk182
    28.11.2019 01:25

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


    1. lany
      28.11.2019 03:17

      Нет, пока не обсуждается. Честно говоря, мне кажется, что это плохая фича для джавы.


      1. v2kxyz
        28.11.2019 09:04
        +1

        А не расскажете кратко — почему это плохая идея? В Scala и Kotlin перегрузка операторов вроде нормально живет. На ум приходит только, то что появится новое поле для ошибок, но это почти с любой фичей так.


        1. lany
          29.11.2019 05:14
          -1

          Начнём с того, что ни у кого нет цели сделать из Джавы новый Котлин или Скалу. Какой смысл, если Котлин и Скала уже существуют? Джава — это отдельный язык со своей философией. У Котлина и Скалы другая философия. Это прекрасно, что у программистов есть выбор, какой философии следовать. Если сделать из Джавы второй Котлин, выбор пропадёт.


          В Джаве есть одна хорошая вещь: в большинстве случаев локального контекста понятно, чтобы выяснить, что делает данная строчка кода. Скажем, если вы видите a[b] = c * d, вы знаете точно, что здесь у вас происходит запись произведения в элемент массива в куче, вне зависимости от того, что такое a, b, c и d, вне зависимости от того, какие у вас есть импорты и библиотеки, в каком классе вы выполняете этот код. Вы точно знаете, что в этой строчке у вас нет сетевого запроса или доступа к базе данных. В Котлине или Скале вы не уверены, эта строчка может делать абсолютно всё что угодно. Я считаю, что ясность — важная отличительная черта Джавы, и Джава перестанет быть Джавой, если потеряет ясность. Причём это усложнит не только чтение кода человеком, но и средства автоматического анализа кода. Некоторые вещи могут вообще перестать работать.


          1. v2kxyz
            29.11.2019 15:37
            +3

            Причём это усложнит не только чтение кода человеком, но и средства автоматического анализа кода. Некоторые вещи могут вообще перестать работать.

            Если честно, мне вот эту мысль в чуть более подробной форме ожидал увидеть. От кого же еще, как ни от вас.
            Про философию я и раньше читал, но на то она и философия — очень субъективно.
            Мне вот кажется, что код
            a.set(b, c.multiply(b))
            читается хуже, чем
            a[b] = c * d
            Особенно если формула вычислений подлиннее. И это стоит рисков по появлению нежелательных действий в перегруженных операторах. К тому же оператор + уже в каком то смысле перегружен, он означает и сложение и конкатенацию, а при помощи var можно вообще содомию JavaScript устроить
            int a = 2;
            String b = "1";
            var c = a + b; // c = "21", wtf?
            Кстати var же ввели, на тему него тоже была куча холиваров, но нежелание писать boilerplate code победило желание иметь тотальную ясность, точнее ее отдали на откуп писателям.

            P.S. Всякие мимокрокодилы, типа меня, про перегрузку операторов начали спрашивать еще до появления Scala и Kotlin, ибо приходили из С++.
            P.P.S. Приду домой — загляну в IDEA с Kotlin, там нет фичи подсветки перегруженного оператора?


    1. kacetal
      28.11.2019 14:32

      Уже есть и давно, в восьмой джаве появилось.


      1. mayorovp
        28.11.2019 15:03

        Разве?


        1. kacetal
          28.11.2019 15:33

          Да, появилась возможность писать плагины к jvm. Например можно взять два BigDecimal a + b, а во время компилации это будет заменено на вызов метода add. Поищите, в интернете есть и видео и статьи как это делается, в том числе и на русском.


          1. snuk182
            28.11.2019 16:42

            А оно с OpenJDK дружит?
            Теоретически должно, так как описано в виде JSR-199, но там куча com.sun.tools.* классов.


            1. tsypanov
              29.11.2019 12:24

              Основная разработка явы ведётся как раз в OpenJDK, так что дружит.


          1. tsypanov
            29.11.2019 12:27
            +1

            во время компилации

            Не совсем понял вас: будет изменён байт-код, выданный javac-ом, или уже выход оптимизирующего компилятора?


            1. kacetal
              29.11.2019 20:33
              +1

              Во время компиляции компилятор проходит через 5-7 этапов (не помню точно) начиная с parse где происходит чтение исходников и заканчивая на generate где он генерирует class файлы, дак вот, можно вмешатся в некоторые стадии этого процесса и получить нужны нам class файл. Чтобы сделать это, необходимо имплементировать интерфейс Plugin.


              https://docs.oracle.com/javase/8/docs/jdk/api/javac/tree/com/sun/source/util/Plugin.html


  1. noktigula
    28.11.2019 02:07

    Смотрите, в Джаву Котлин завезли!


  1. lany
    28.11.2019 06:41

    Про перекрытие полей спросил. А про || false — это в amber-dev можно спросить, там открытый мейлинг-лист.


    1. orionll Автор
      28.11.2019 07:23

      Про || false я подумал и похоже понял. С первого взгляда может показаться, что они эквиваленты с && true, однако это не так:

      if (obj instanceof String str && <любое условие>) {
          // Можем всегда использовать str
      }

      Но в случае с || уже совсем другая картина:
      if (obj instanceof String str || <любое условие>) {
          // Не можем использовать str
      }

      Допустим, мы могли бы сделать по-умному, и во втором случае разрешить использовать str, если <любое условие> могло бы быть вычислено в false во время компиляции, но это было бы очень хрупко и делало бы опасным рефакторинг:
      if (obj instanceof String str || false) {
          // str работает
      }

      private boolean isFalse() {
          return false;
      }
      ...
      
      if (obj instanceof String str || isFalse()) {
          // str уже не работает
      }

      Если программист захочет заинлайнить isFalse(), то код ломается.


  1. dyadyaSerezha
    28.11.2019 08:34

    А сдается мне, что это улучшит одну десятитысячеую часть кода, если не одну милионную. Приколько, но…


    1. orionll Автор
      28.11.2019 13:39

      Посмотрел наш проект (320k строк кода). instanceof используется 2290 раз. Почти все из них можно переписать на паттерн-матчинг.


      1. dyadyaSerezha
        29.11.2019 02:57

        Один instanceof на каждые 150 строчек кода? А может, "что-то в консерватории подправить"?


        1. lany
          29.11.2019 05:09

          Зависит от проекта. В исходниках IntelliJ IDEA тоже многие тысячи instanceof. По сути дела многие инспекции — это поиск паттернов по дереву, паттерн-матчинг в чистом виде. Ну и в целом нет ничего плохого в instanceof, если правильно его использовать. Часто это существенно лучше, чем visitor pattern, который в джаве выглядит откровенно по-уродски, занимает больше места в исходниках и имеет больше накладных расходов при исполнении.


          1. dyadyaSerezha
            29.11.2019 06:04

            Все это так, но сколько таких проектов, как IntelliJ? Одна сотая? Вот и получается та самая одна десятитысячная.


            1. tsypanov
              29.11.2019 12:29

              На этой одной сотой держится вся прикладная разработка (и не только проектов на яве, кстати). Вы готовы пересесть на какой-нибудь "Саблайм" и запускать сборку/тесты каждый раз из командной строки? Я — нет. Посему, имхо, именно в этом месте лучше оставить как есть.


              1. dyadyaSerezha
                30.11.2019 02:14

                1. IntelliJ уже написана.)
                2. Как я понимаю, все большая ее часть становится написана на Kotlin.


  1. kacetal
    28.11.2019 14:45

    Интересно, а если объявить такую переменную просто в теле метода, или можно только в условных операторах?


    1. orionll Автор
      29.11.2019 10:23

      Можно просто объявить:

      boolean x = obj instanceof String str;

      В IDE добавят предупреждение о том, что str не используется.


      1. lany
        29.11.2019 13:58

        Но boolean x = obj instanceof String str && !str.isEmpty(); — уже вполне полезный код.