Как стать автором
Обновить

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

Ну в общем, они взяли весь сахар из Котлина. И это замечательно!

Data/Case классы как по мне наиболее ожидаемое.
С Null правда ещё сложно, хоть и есть optional.
Чуть синтаксиса со стримами и make Java great again.

Не совсем. Скорее из Скалы (оттуда же Котлин фичи надергал). Скала тоже многое из других языков тянет, но в классе языков под JVM на мой взгляд она самая продвинутая. И все равно Java пока сильно отстает и от Котлина, и от Скалы. И, в связи с ее спецификой, догонять будет еще очень долго. У меня даже ощущение, что JVM переживет саму Java. :)

Да ну нет же. Надергали фичей, но не весь сахар.

На мой взгляд, это слишком легкомысленное замечание. Во-первых, речь не только о сахаре. Например, рекорды — это модельная сущность, nominal tuple, тип-произведение, если хотите. Это штука с сильной семантикой, а не просто синтаксический шорткат. Во-вторых, "взяли из Котлина" — это просто неверно. Синтаксически эти штуки непохожи на Котлин, а, например, instanceof patterns вообще в Котлине прямого аналога не имеют. У Котлина иной путь — flow typing, который в Java никто тащить не собирается. Sealed types — это больше не фича языка Java, а фича виртуальной машины: проверка иерархии делается на уровне VM, и там надо было аккуратно поработать с раздельной компиляцией и т. д. И теперь как раз котлиновские sealed-типы адаптируют, чтобы они были sealed-типами на уровне JVM. И, кстати, sealed-интерфейсов в Котлине не было, и теперь их затаскивают в Котлин из Java. Тоже берут сахар? Даже текстовые блоки синтаксически похожи совсем не на Котлин (где они плохо сделаны), а на Swift. За каждой из этих фич годы исследований и дизайна, а не просто "давайте сделаем как в языке X".

а, например, instanceof patterns вообще в Котлине прямого аналога не имеют.

Шта? Вообще-то это одна из ключевых фишек языка — вывод типов где только возможно


    fun test(any: Any): String = when(any){
        is String -> any.replace('a', 'b')
        is Double -> sin(any).toString()
        is Thread -> any.isAlive.toString()
        else -> "rrrrrr"
    }

Это и называется flow typing. И в Java он не реализован и не будет. Об этом я и говорю. Вместо этого в джаве вводится новая переменная — такого синтаксиса в Котлине нет.

С одной стороны становится удобнее, а с другой стороны имеем повышение порога вхождения из-за увеличения количества лексем.

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

А лучше по новым. Например, изучать только новый синтаксис switch, который удобнее и логичнее. А старый с двоеточием не изучать вообще или оставить как дополнительный материал.

Извините, но читать другой код это ключевое.

Однако, мне так и не удалось заставить такой код работать

Как минимум не хватает “else if (shape == null) ...”
Пробовали добавить?

Попробовал — ошибка та же (missing return statement). Начинает работать только после добавления ветки «else».

Никто нигде никогда не обещал, что в цепочках if-else будет анализ на exhaustiveness. Он планируется в switch, когда там прикрутят паттерны. Но пока этого нет.

Меня натолкнул на эту мысль вот этот абзац из JEP:
Sealing a class restricts its subclasses. User code can inspect an instance of a sealed class with an if-else chain of instanceof tests, one test per subclass; no catch-all else clause is needed. For example, the following code looks for the three permitted subclasses of Shape:

Shape rotate(Shape shape, double angle) {
if (shape instanceof Circle) return shape;
else if (shape instanceof Rectangle) return shape.rotate(angle);
else if (shape instanceof Square) return shape.rotate(angle);
// no else needed!
}


Пересмотрел JEP еще раз — в нем есть и такое утверждение:
For example, consider this code from earlier:
Shape rotate(Shape shape, double angle) {
if (shape instanceof Circle) return shape;
else if (shape instanceof Rectangle) return shape.rotate(angle);
else if (shape instanceof Square) return shape.rotate(angle);
// no else needed!
}
The Java compiler cannot ensure that the instanceof tests cover all the permitted subclasses of Shape. For example, no compile-time error message would be issued if the instanceof Rectangle test was omitted.


Немного запутанное описание, но, похоже, вы правы.

Согласен, написано немного запутано.

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

Мне кажется, вы тут упускаете ещё один возможный исход – значению параметра shape ничего ведь не мешает быть null… или я не прав? В этом случае ни одна проверка на instanceof не вернёт true.

З.ы. в любом случае, спасибо за статью, хорошее изложение.

Ещё здесь вызывается javac [...] --release 16 [...], а в другом примере поста - javac [...] --source 16 [...]

Мне очень понравилось вот это — Moving ZGC (Z Garbage Collector) thread-stack processing from safepoints to a concurrent phase Обещают, что затраты на пропускную способность улучшенной задержки должны быть незначительными, а время, проведенное в ZGC safepoints на типичных машинах, должно быть меньше одной миллисекунды.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Всё более острый (sharpen)
Да нет, именно скалистый (Scala)
Область видимости переменной s может быть как внутри блока if (как в примере выше), так и за его пределами

Так и не понял, всё-таки область видимости внутри или и за пределами тоже? Если область видимости может быть переменной, то от чего зависит?
Она может быть и внутри и за пределами — зависит от условия if.
Например, тут она будет внутри:
if (obj instanceof String s) {
    System.out.println(s);
}

А тут за пределами:
if (!(obj instanceof String s)) {
  throw new Exception();
}
System.out.println(s);
Не поверил, запустил у себя. Впервые вижу динамическую область видимости, еще и зависящую от условия if'а. Мир больше не будет прежним.

Тогда вам совсем вынесет мозг такой пример:



Здесь область видимости переменной паттерна состоит из двух несвязанных кусков, разделённых телом if'а.

Здесь, как раз, нет. Фактически это одна область, если определять её не в исходниках, а в байткоде. Меня удивила именно вариативность области видимости.

Байткоду-то какая разница? В байткоде это тоже может быть две области. Байткод тут ни при чём.

Я всего лишь пытаюсь понять логику. Если за таким поведением кроется более простая техническая реализация — для меня это понятно и логично. Недоумение вызвала бы только попытка вынуть гланды через неподходящие отверстия только ради самого процесса)
Я тут Dart пишу, скоро в 2.12 будет null safety (AKA non-nullable by default), и для этого прикрутили в язык flow analysis, которому например не нужно объявлять новую переменную, просто instance of делает type promotion существующей переменной. Возможно в Java решили, что type promotion слишком радикально вводить, и вместо этого добавили сахар экономии type cast(а).
Жесть какая-то)
java  --enable-preview --source 16 Outer.java 
Inner_1
jdk16.Outer$Inner$StaticClass@6b67034
Point[x=1, y=2]

А зачем здесь preview? В Java 16 это стандартная фича.

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

Хм. String.length(), Collection.size(), Process.pid(), Process.exitValue(), Runtime.availableProcessors(), Runtime.freeMemory(), ThreadGroup.activeCount() и т. д. Я бы не сказал, что префикс get — это прямо "стандарт".

Скорее всего, имелся ввиду стандарт именования JavaBeans, на который завязаны многие фреймворки и утилиты.

Это не стандарт, а соглашение. Стандарт говорит "Accessor methods can have arbitrary names" (JavaBeans 1.01a, 7.1). В разделе 8.3 действительно описывается именование на get/is, но в разделе 8.2 ещё раз подчёркивается "However, within Java Beans the use of method and type names that match design patterns is entirely optional".


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

Нет, jackson адаптирован начиная с версии 2.12 которая релизнулась всего несколько недель назад.

Методы (геттеры) с префиксом количественно доминируют, а значит они — стандарт де-факто.

lany, именно это я и имел ввиду, говоря о стандарте JavaBeans.
Ну почему нельзя было сделать record'ы как @Value-классы в Lombok? Невозможность наследовать сводит все преимущества record'ов на нет: если есть Point(x, y), то очень хотелось бы отнаследовать от него Point3D(x, y, z).

К сожалению, старая школа ООП вбила этот ужасный паттерн наследования реализаций. Наследование — это специализация. Совершенно очевидно, что точка в трёхмерном пространстве не является специальным случаем точки в двумерном пространстве. В таком наследовании нет смысла.


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

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

Эх, так и знал, что нужно было приводить другой пример. ОК, давайте тогда возьмем классы Shape, Square, Cirle, у которых есть поле center типа Point и метод getArea().
совершенно непонятно как правильно автоматически сгенерировать equals для рекорда, если его можно наследовать

Ну вот IDEA же как-то с этим справляется (если проставить флажок «Accept subclasses as parameter to equals() method», а если проставить флажок «Use getters during code generation», так вообще красота получается):
Пример кода, который сгенерировала IDEA
public abstract class Shape {
    private final int x;
    private final int y;

    public Shape(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }
    
    public abstract double getArea();

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Shape)) return false;
        Shape shape = (Shape) o;
        return getX() == shape.getX() && getY() == shape.getY();
    }

    @Override
    public int hashCode() {
        return Objects.hash(getX(), getY());
    }
}

public class Circle extends Shape {
    private final int radius;

    public Circle(int x, int y, int radius) {
        super(x, y);
        this.radius = radius;
    }

    public int getRadius() {
        return radius;
    }
    
    @Override
    public double getArea() {
        return Math.PI * getRadius() * getRadius();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Circle)) return false;
        if (!super.equals(o)) return false;
        Circle circle = (Circle) o;
        return getRadius() == circle.getRadius();
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), getRadius());
    }
}

Всё просто. Shape — это интерфейс. Рекорды могут реализовывать интерфейсы. Есть у него точка в виде поля или нету — никого волновать не должно. Квадрат может быть удобно задать центром и длиной стороны, а может быть углом и длиной стороны. В одном из случаев метод, который возвращает центральную точку, не будет тривиальным геттером, а будет вычислять значение. Должна быть возможность тривиальным образом сменить одну реализацию на другую, не сломав ни одного клиента. Абстракция! Рекорд по определению имеет прозрачную структуру, он не обеспечивает абстракции.


Ну вот IDEA же как-то с этим справляется

Тут, например, можно почитать на эту тему.

Например, совершенно непонятно как правильно автоматически сгенерировать equals для рекорда, если его можно наследовать.

В C# это сделали, добавив в сравнение виртуальное свойство EqualityContract, которое возвращает реальный тип:


var b = new Base("Hello");
var d = new Derived("Hello", "World");
Console.WriteLine(b.Equals(d));

Base.Equals сравнит не только два Hello, но и сами типы, а потому вернёт false.

А нет подобной статьи для Java 8 — Java 11 ?))
благодарю!
non-sealed

Служебное слово с дефисом это, конечно, образцово-показательный ХХВП. Из сотен инженеров никто не потрудился нормальный антоним подобрать.

Чего ж ты не написал свои гениальные идеи в мейлинг-лист? :-) Кстати, ещё не поздно: фича не финализирована, ключевое слово вполне можно поменять. Иди в amber-spec-comments и предлагай! На Хабре-то тебя Брайан не прочитает :-)

Формальных антонимов там не так уж много:


  • open
    — агащаз, никто такое ключевое слово делать не станет, иначе накроет весь язык, и первой будет стандартная библиотека
  • unsealed
    — ну понятно, да?
  • unrestricted
    — семантически не подходит, потому что подразумевает отсутствие любых препятствий, а никто не хочет настолько сильные требования вводить с налёта.
  • non-sealed
    — Слово составное и страшноватое, зато абсолютно безопасно в качестве ключевого слова — потому что единственный случай, когда из-за этого что-то перестанет компилироваться — это если у вас есть две переменные, non и sealed, и вы из первой вычитаете вторую, а форматировать с пробелами почему-то не захотели.

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

Ну теоретически можно сделать контекстно-зависимое ключевое слово open, которое является ключевым только в списке модификаторов. Собственно, слово sealed так и добавлено: вы спокойно можете иметь метод sealed или переменную sealed.

Со словом open другая проблема — это уже ключевое слово! Да-да, только оно используется исключительно в файлах module-info.java, означая, что в данном модуле все классы доступны в рантайме (к примеру, для грязного рефлекшна). Кажется, что это может привнести некоторую путаницу: если класс объявлен open, это может быть воспринято в том же контексте (другим модулям разрешено лазить в потроха класса).

Ну вот да. Я, кстати, не стал специально писать про смысл open в module-info, но и Реми, и Брайан сами про это упомянули. Maccimo, иди ворвись и скажи им, что они не потрудились :-)

У вас там элитарный экспертный клуб с фейс-контролем при подписке, ворваться не получится.

Я же тебе сказал, в amber-spec-comments, даже ссылку приложил.

Пока что не понятно зачем отдельный модификатор non-sealed вообще нужен, кроме как для «The goal is to make it Java-ish, by adopting this convention». Если класс не объявлен явно как final или sealed, то по логике вещей он как раз non-sealed.


Особенно забавно это буйство новых ключевых слов сочетается с фразой «It is a common (and often deserved) complaint that "Java is too verbose"» из Data Classes and Sealed Types for Java.


А то Maccimo вряд ли сам напишет.

Не в *-experts точно, ибо "You probably want amber-spec-observers".

Если класс не объявлен явно как final или sealed, то по логике вещей он как раз non-sealed.

Этот вопрос обсуждали, естественно. Смысл в том, что хорошего дефолта нет, а если сделать, что дефолт = non-sealed, то люди будут просто по забывчивости или лени открывать свои иерархии. Поэтому надо явно указывать.

Руководствуясь такой логикой можно начать требовать non-final, non-volatile, non-strictfp и так далее. Вдруг программист забыл?

Не забываем про обратную совместимость.

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

Я бы предпочёл для этого аннотацию, по аналогии с @Override. К ключевым словам-маркерам у меня идиосинкразия со времён Delphi.

Аннотации — это фичи уровня компилятора, и вряд ли будут поддерживаться на уровне JVM, в отличие от ключевых слов.

Ну кастомные так-то поддерживаются, ничто не мешает технически поддержать и другие.

Кастомные что? Кастомные аннотации — это просто, там фреймворки есть. А вот о кастомных ключевых словах JVM ничего знать не может.

Кастомные аннотации. При чем тут фреймворки? Аннотации, хоть и не всякие, фиксируются в class-файле. Например.

Вы о чём?
JVM не знает ни о каких ключевых словах. Ни о кастомных, ни о каких-либо других. Ключевые слова заканчиваются на уровне javac.

Если бы они действительно заканчивались на уровне javac, не было бы надёжных понятий sealed type и final field в Java. С другой стороны, JVM прекрасно обходится без @Override, она ей вообще не нужна для определения shadowing.

На уровне class-файла final field это взведённый бит в соответствующем поле, а sealed type — аттрибут PermittedSubclasses.


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

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


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


Кроме того, вернувшись к исходному сообщению, хочу возразить — sealed и non-sealed это не просто маркеры. Это @Override и @FunctionalInterface — маркеры, а эти ключевые слова именно приводят в действие дополнительные механизмы в JVM, так же, как это делают final, private и volatile.

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

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

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


Maccimo


По вашей логике в JVM с самой первой версии была поддержка ключевого слова open из тогда ещё не существовавшего языка программирования.

Нет, не так. Kotlin использует механизмы JVM, которые были туда добавлены для поддержки ключевого слова final, но не так, как это делает основной хост-язык. Если бы такой механизм в Java не был добавлен вообще, то Kotlin нечего бы было там использовать. То, что первым тут было именно яйцо, видно и по тому, что в документации
к Kotlin классы описываются именно в семантике ключевых слов Java, а не в семантике внутренних механизмов JVM. Они легко могли написать "by default, Kotlin classes are non-extensible", но вместо этого написали детали имплементации, через которые эта фича была сделана.


Нет никаких технических причин, по которым javac не мог бы обрабатывать определённые аннотации особым образом

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

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

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


Верно, эти причины философские.

Вновь отсылаю вас к исходникам, которые бесцеремонно попрают ваши философские воззрения.

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

Так вы сюда в демагогии поупражняться пришли?
Так бы сразу и сказали. Демагогия у вас пока на троечку.

Моя основная мысль была про значение аннотаций и про то, что, согласно философии языка, они не являются заменой ключевых слов, и таковыми не станут. Case in point — несколько новых ключевых слов, которые добавлены или будут добавлены(sealed, record), и ни одной compile-bounding аннотации.
На что вы заявили, будто я демагог, потому что вы нашли аж одну (на самом деле их две) аннотации, которые влияют на атрибуты нодов AST в понимании javac. Причём, этот же атрибут вполне мог быть вычислимым на основании эвристик, как это уже происходит для @Override и @FunctionalInterface.
В сухом остатке, вы меня не убедите (одна аннотация против нескольких десятков ключевых слов, причём @Sealed точно могли сделать аннотацией, но сделали ключевым словом, что показательно), и у меня нет больше никаких инструментов, чтобы убедить вас, потому что мои соображения вы уже назвали демагогией, что указывает на желаемый уровень дискуссии.
Посему я и предлагаю разойтись пока с миром — если вы правы, все новые фичи будут добавляться через аннотации, достаточно только подождать, пока очистится пайплайн от текущих проектов, чтобы "новый свежий ветер перемен" наконец задул в полную силу. Если прав я, то будут в основном добавляться ключевые слова. Время рассудит.


PS. Хотя что вам мешает написать в рассылку и предложить перестать добавлять новые ключевые слова в язык? Я-то всего лишь тут в демагогии упражняюсь, а там есть прямой доступ в уши Брайана Гетца, и он довольно активно отвечает на вопросы и даёт разъяснения.

А как это ещё назвать, если не поддержкой ключевых слов на уровне JVM?

Так и назвать, поддержкой final/volatile/etc. полей в классе.


Ключевые слова это свойство языка программирования. JVM не работает на уровне языков программирования, она работает на уровне класс-файлов. Вы смешиваете уровни абстракции.


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


By default, Kotlin classes are final: they can’t be inherited. To make a class inheritable, mark it with the open keyword.

https://kotlinlang.org/docs/reference/classes.html#inheritance

По вашей логике в JVM с самой первой версии была поддержка ключевого слова open из тогда ещё не существовавшего языка программирования.


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

Процессор аннотаций может перекорёжить весь класс как ему вздумается начиная с Java 6 как минимум (см. The Hacker’s Guide to Javac). В качестве живого примера см. Lombok.


Про использование аннотаций:


Нет никаких технических причин, по которым javac не мог бы обрабатывать определённые аннотации особым образом, устанавливать любые флаги и создавать любые аттрибуты какие разработчики сочтут нужными.


Пример: https://hg.openjdk.java.net/jdk8/jdk8/langtools/file/e9f118c2bd3c/src/share/classes/com/sun/tools/javac/comp/MemberEnter.java#l788


И комментарий оттуда:


// Internally to java.lang.invoke, a @PolymorphicSignature annotation
// acts like a classfile attribute
Every signature-polymorphic method must be declared with the following properties:

It must be native.
It must take a single varargs parameter of the form Object....
It must produce a return value of type Object.
It must be contained within the java.dyn package.
— source https://wiki.openjdk.java.net/display/mlvm/InterfaceDynamic

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


Аннотация здесь помогает скорее не компилятору, а человеку, так как во-первых, ему точно понятно, что данный метод или тип выполняет в тексте определённую роль, а во-вторых, после проставления аннотаций эвристики можно проверить и сгенерировать подсказки если ожидание человека отличается от ожидания javac.


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


@FunctionalInterface
public interface TriFunction <X, Y, Z, R> {

  R apply(X arg0, Y arg1, Z arg2);

  BiFunction<Y, X, R> curry(X arg0);
}
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории