Обновить
Комментарии 59

А что нам по поводу range предлагает стандартная библиотека?

IntStream.range(a, b).forEach(i -> ...);
Интересно, как у этого варианта с бенчмарком. Боксинга здесь нет.

У forEach другие проблемы.

IntStream.range(0, this.size).map(i -> i * i * i).sum() работает так же быстро, как и простой for
Ничего, про это написано в последнем абзаце.
То, что EliminateAutoBox оптимизация здесь сработала, скорее, повезло. Она чаще не работает, чем работает :) Даже на свежайшей JDK 17-ea простой пример отсюда (setPrimitive) по-прежнему аллоцирует лишний объект.

Зато если вместо автобоксинга написать new Integer(i), то сразу всё хорошо оптимизируется. На этом фоне очень обидно, что конструкторы обёрток задеприкейтили, не предоставив сопоставимую по скорости альтернативу :(

От new Integer(i) на обычном кроваво-энтерпрайзном проекте "Сонар" кондратий хватит

То, что EliminateAutoBox оптимизация здесь сработала, скорее, повезло

Может повезло, а может и специально такие сценарии обрабатывали.


не предоставив сопоставимую по скорости альтернативу :(

Думаю, можно создать свою тривиальную обёртку, если где-то необходимо работать с боксами, а Integer.valueOf не дожимает производительность.

К сожалению, обертка не всегда подходит и иногда нужен именно тип Integer, поэтому приходится использовать new Integer(i).


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

обертка не всегда подходит и иногда нужен именно тип Integer, поэтому приходится использовать new Integer(i)

А разве есть разница между типом возвращаемым из new Integer(i) и Integer.valueOf(i)?

А на Integer.valueOf(i) заметно хуже работает escape analysis в нетривиальных случаях, с комментария про это и началась эта ветка обсуждения :)

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


обертка не всегда подходит

ведь и Integer.valueOf(i), и new Integer(i) возвращает один и тот же тип-обёртку

Я говорил про свой собственный тип-обёртку, а не Integer. Читай внимательнее.

В опросе не хватает варианта для староверов, «Не люблю синтаксический сахар».

А я вот наоборот, люблю синтаксический сахар, но при этом конкретно в такой формулировке range пользы в своём проекте не вижу. Потому для меня там тоже нет подходящего ответа — я использую Java 14+, но не пишу в таком стиле, и пока не вижу смысла начинать писать в таком стиле.

Какие недостатки вы видите?

Да не то чтобы тут были какие-то прямо общие недостатки.


Мне практически нигде и никогда за последние, скажем так, три года не было нужды писать


   for (int i = J; i < K; i+=N) { ... }

при любых J, K и N. В отличие от, к примеру for (var el : c) {...}.


Из этого следует, что такая утилита мне просто не нужна ввиду отсутствия нужды в Range как Iterable<N>. Зато есть нужда в Range как представлении [N;K) и прочих подобных формах, включая случаи когда N и K — это даты, время или вообще произвольный Comparable.

Мне практически нигде и никогда за последние, скажем так, три года не было нужды писать

Хм. Удивительно. А мне довольно часто приходится писать и читать такие циклы. Видимо, задачи сильно разные.

задачи сильно разные.

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

Интересно, появится ли в яве когда-нибудь возможность самостоятельно определять синтаксические конструкции для своего проекта, иными словами возможность написать что-то вроде


define (int x...int y) => MyUtilityClass.range(x, y);

чтобы компилятор самостоятельно мог привести


for (i : 0...10) {
 //...
}

к


for (i : range(0, 10)) {
 //...
}

?

Мое мнение: это худшее, что может произойти с java.

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

Такого не будет, не переживайте.

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

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

Разработчики языка не равны пользователям языка. Ну и насчёт большинства ещё неизвестно.

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


А что касается большинства, я конечно никогда не видел результатов опросов, но могу отослать к выступлениям таких авторитетов, как Барух Садогурский или Евгений Борисов. По ним создаётся ощущение, что Lombok повсюду уже давно и надолго.

Ломбок используют, чтобы спрятать простыни геттеров/сеттеров и конструкторы т.е. всякий бойлерплейт. В синтаксис языка он не лезет, разве что val добавили.

По смыслу Ломбок мало отличается от butterknife или di типа dagger. Просто еще одна «библиотека», которая делает какую-то полезную магию.

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

А как DI снижает качество кода?

Мнение про DI часто слышал в разных командах и особо сильно в это не вникал. Как я понял проблема не в самом DI, а в библиотеках типа dagger за их архитектуру и проблемы.

В том-то и дело, что внедрение зависимостей и управление ими сильно улучшает качество кода, т.к. очищает его от инфраструктуры.

Никто вам не запрещает написать собственный препроцессор для разворачивания таких конструкций и прочих макросов. Через Gradle или maven вопрос решается элементарно.

Но это уже будет не Java, а что-то другое. Тогда лучше перейти на другой язык, где есть уже инструментальная поддержка.

Но это уже будет не Java, а что-то другое.

Авторов и пользователей Lombok это не остановило, а скорее вдохновило ))


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

Я очень часто встречаюсь с рекомендациями не использовать Lombok, а перейти на Kotlin. Потому что Lombok для джавы чужеродный, а в Kotlin фичи уже есть.


И пока что мне ещё никто не ответил, как в Kotlin решаются проблемы с Entity или как сделать билдер, или как преобразовать неизменяемый объект в билдер, а потом из билдера получить новый объект. Или ещё 100500 юскейсов которые отрабатывает Lombok.


Но допустим Kotlin, или какой-то другой язык завтра выровняется по фичам с Lombok. Послезавтра какой-нибудь разработчик добавит в Lombok генератор equals и hashCode для Entity и Lombok опять убежит вперёд. А когда это всё появится в другом языке совершенно неизвестно. Да и вообще — появится ли? Если экстраполировать будущее исходя из текущего положения дел — то очевидно, что нет.

Это всё долгий и отдельный разговор, но


И пока что мне ещё никто не ответил, как в Kotlin решаются проблемы с Entity или как сделать билдер, или как преобразовать неизменяемый объект в билдер, а потом из билдера получить новый объект. Или ещё 100500 юскейсов которые отрабатывает Lombok.

В Котлине билдеры не нужны вообще. Для этого есть copy.

В Котлине билдеры не нужны вообще. Для этого есть copy.

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

И пока что мне ещё никто не ответил, как в Kotlin решаются проблемы с Entity


Интересно. А можно проблему с Entity описать?
  1. Entity должны быть изменяемыми
  2. Должен существовать конструктор по умолчанию
  3. equals должен быть заявзан только на @Id или только на естественный ключ
  4. Если equals завязан на id, который делается генератором, то он должен возвращать false, если @Id == null, а hashCode должен возвращать 31.

Lombok помогает с пунктами 1 и 2, а также 3.

Наверное я упускаю какой то контекст. А апелляция к чему? К тому, что дата классы не могут быть использованы в качестве Entity? Вот прям только что использовал дата классы для того чтобы работать со Spring Data — ничто не помешало.
Наверное я упускаю какой то контекст. А апелляция к чему?

К тому, что использовать дата классы в качестве Entity не проще, чем использовать в качестве Entity обычные классы. И тут врядли что-то изменится.


А сделать библиотеку, которая с помощью генерации кода решает эту проблему можно сделать в любой момент и работать будет для любой версии джавы, поддерживающей annotation processing.


Вот прям только что использовал дата классы для того чтобы работать со Spring Data — ничто не помешало.

Только пришлось обозначить поля как изменяемые, вручную написать конструктор без параметров, вручную написать hashCode и equals, а так же toString, правильно?


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

> вручную написать hashCode и equals, а так же toString, правильно?

Нет. Примерно вот так:

@Table("MESSAGES")
data class Message(
    val content: String,
    val contentType: ContentType,
    val sent: Instant,
    val username: String,
    val userAvatarImageLink: String,
    @Id var id: String? = null)


или это не по фен-шую? Не все требования выполняются, да, наверное в каких-то ситуациях надо будет писать свой equals и hashCode.
или это не по фен-шую?

Не по фен шую )). Конструктора без параметров нет. И ещё val это опечатка наверное, да? Или это вообще не Entity и к Jpa никак не относится?


Не все требования выполняются, да, наверное в каких-то ситуациях надо будет писать свой equals и hashCode.

Как будут использоваться Entity в будущем предугадать невозможно, поэтому если не сделать equals и hashCode сразу, то потом замучаешься искать причины непонятного поведения в самые неподходящие моменты.

> И ещё val это опечатка наверное, да? Или это вообще не Entity и к Jpa никак не относится?

Используется со Spring Data. JPA — так сразу и надо было сказать :)
Для JPA дата классы конечно же не очень хорошо подходят.
Используется со Spring Data. JPA — так сразу и надо было сказать :)

У меня слово Entity с большой буквы в связке с джавой ассоциируется только с Jpa. Профдеформация наверное )))

У меня слово Entity с большой буквы в связке с джавой ассоциируется только с Jpa. Профдеформация наверное )))

Обнимаю!

Ломбок — альфа версия потенциально полезного сахара.
Котлин — бета.
Джава уже релиз.

Вот только у IDE после подобных упражнений начнутся проблемы. И придется после этого писать для нее плагин. И скорее всего, это будет плагин для IDEA, который тоже нужно будет поддерживать. Кому-тот придется расширять файлы синтаксиса для vim, а синтакстическим плагинам вима тоже голову будет переодически сносить. Так что если что-то подобное иметь, то из из коробки либо не иметь вовсе.

Любителей Lombok это не особо смущает.

Я бы предпочел использовать специализированные стримы.


for (int i: IntStream.range(0, 10)) {
    System.out.println(i);
}

И еще я бы хотел опцию компилятора -XX:+TurnOffAutoBoxingUnboxingCompletely.

Добавьте пожалуйста вариант в голосование "Пишу на Kotlin/Scala/Clojure/Other-JVM и мне всё-равно что там в ванильной Java"

Это последний пункт.

(*) Подожду Вальгаллу, чтобы уж наверняка!

Я как человек с компиляторным бэкграундом и хорошо знающий, как работает JIT с его куриными мозгами, не стал бы в критическом коде писать новомодный синтаксис, а всё бы развернул руками. Ещё есть Android (а так же различные способы запустить Java-приложения на iOS) и там уж точно всё работает совсем не так. Так что если нужно писать кроссплатформенный код (например, библиотеку, которую можно использовать на бэкэнде и на Android), да ещё и критический по производительности, я бы точно воспользовался старым добрым синтаксисом. Такие мелочи не особо влияют на читаемость кода, а IDE позволяет быстро его писать. Вот что действительно плохо влияет на читаемость кода — это более глобальные вещи, вроде правильных абстракций, соблюдения принципов вроде SOLID и т.д., а не какие-то отдельно взятые циклы.

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

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

Такие преобразования выполняются на платформенно-независимом IR задолго до кодогенерации, поэтому на ARM проблем быть не должно. Но если у кого-нибудь есть ARM под рукой, можете измерить. Что касается компиляции в нативный код — если это делать с помощью GraalVM, то она щёлкает боксинг как орехи уже давно. Там эскейп-анализ и скаляризация наголову выше, чем в C2. Они даже хвастались, что Objects.hash(i, j, k) будет работать с той же скоростью как и явный аналог типа ((31+i)31+j)31+k, то есть они и от массива-варарга, и он боксов в каждом элементе избавляются на раз.

Я лично не люблю доморощенный синтаксический сахар. Может быть намерения благие, но в результате придет на проект молодое поколение spring, либо будут спотыкаться каждый раз и думать, «что за хрень», либо даже не обратят внимания и будут писать по своему или того хуже выдумают еще вариант, как удобнее. Причем может быть 100 лет тому назад это была прогрессивная идея, а сейчас страшно даже подумать, что придется поменять в коде, что бы эту краказябру (не вашу конкретно, а в общем) поменять на эквивалент из стандартной библиотеки.

Недавно видел класс, обертка вокруг jdbc из допотопных времен. Чуть ли не предтеча hibernate. Там жуть. 20 лет назад это наверное сокращало написание кода, а сейчас spring все те же проблемы решает аннотацией и парой строк. И не переделать ничего уже, так как ни бюджета ни желания, а софту еще лет 20 жизни предрекли. Переписывать никто не будет.
Ну не знаю, заменить простой и понятный for (int i = 0...) цикл на кастомный метод, по-моему так себе разумная идея. Как написали в комментариях выше — очень легко забыть об этом кастомном методе, да и понять его с первого взгляда непросто. Да я и сам через полгода не работы над проектом забыл бы о нем, а что говорить о джунах…

Вы как-то сильно недооцениваете память программистов. В любом большом проекте есть тысячи внутренних методов, и знакомый с проектом программист обычно помнит и понимает, какой из них что делает, когда читает код. Редко когда требуется заглянуть в документацию. Да и в стандартной библиотеке тысячи методов, и в используемых библиотеках тоже. Пара новых ничего не усложняют. Конечно, было бы удобнее, если бы они появились в OpenJDK, но и свой утилитный класс это не большая проблема. При написании кода можно, кстати, стандартные шаблоны IDE переопределить, чтобы использовать новые методы автоматически.

Ответ на опрос: использую range(from, to), когда мне нужно использовать i в лямбде внутри тела цикла. В остальных случаях пишу обычный for (int i = ...).
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.