Мы в Grubhub почти во всём бэкенде используем Java. Это проверенный язык, который за последние 20 лет доказал свою скорость и надёжность. Но с годами возраст «старичка» всё-таки начал сказываться.
Java — один из самых популярных языков JVM, но не единственный. В последние годы конкуренцию ему составляют Scala, Clojure и Kotlin, которые обеспечивают новую функциональность и оптимизированные функции языка. Короче говоря, они позволяют делать больше с более лаконичным кодом.
Эти инновации в экосистеме JVM очень интересные. Из-за конкуренции Java вынуждена меняться, чтобы сохранить конкурентоспособность. Новый шестимесячный график выпуска и несколько JEP (JDK enhancement proposals) в Java 8 (Valhalla, local-Variable Type Inference, Loom) — доказательство того, что Java долгие годы останется конкурентоспособным языком.
Тем не менее, размер и масштаб Java означают, что разработка продвигается медленнее, чем мы хотели бы, не говоря уже о сильном желании любой ценой поддерживать обратную совместимость. В любой разработке первым приоритетом должны быть функции, однако здесь необходимые функции слишком долго разрабатываются, если вообще попадают в язык. Поэтому мы в Grubhub используем Project Lombok, чтобы прямо сейчас иметь в своём распоряжении оптимизированную и улучшенную Java. Проект Lombok — это плагин компилятора, который добавляет в Java новые «ключевые слова» и превращает аннотации в Java-код, уменьшая усилия на разработку и обеспечивая некоторую дополнительную функциональность.
Настройка Lombok
Grubhub всегда стремится улучшить жизненный цикл программного обеспечения, но каждый новый инструмент и процесс имеет стоимость, которую следует учесть. К счастью, для подключения Lombok достаточно добавить всего пару строк в файл gradle.
Lombok преобразует аннотации в исходном коде в Java-операторы до того, как компилятор их обработает: зависимость
lombok
отсутствует в рантайме, поэтому использование плагина не увеличит размер сборки. Чтобы настроить Lombok с Gradle (он также работает с Maven), просто добавьте в файл build.gradle такие строки:plugins {
id 'io.franzbecker.gradle-lombok' version '1.14'
id 'java'
}
repositories {
jcenter() // or Maven central, required for Lombok dependency
}
lombok {
version = '1.18.4'
sha256 = ""
}
При использовании Lombok наш исходный код не будет валидным кодом Java. Поэтому потребуется установить плагин для IDE, иначе среда разработки не поймёт, с чем имеет дело. Lombok поддерживает все основные Java IDE. Интеграция бесшовная. Все функции вроде «показать использования» и «перейти к реализации» продолжают работать как и раньше, перемещая вас к соответствующему полю/классу.
Lombok в действии
Лучший способ познакомиться с Lombok — увидеть его в действии. Рассмотрим несколько типичных примеров.
Оживить объект POJO
При помощи «старых добрых объектов Java» (POJO) мы отделяем данные от обработки, чтобы сделать код проще для чтения и упростить сетевые передачи. В простом POJO есть несколько приватных полей, а также соответствующие геттеры и сеттеры. Они справляются с работой, но требуют большого количества шаблонного кода.
Lombok помогает использовать POJO более гибким и структурированным образом без дополнительного кода. Вот так с помощью аннотации
@Data
мы упрощаем базовый POJO:@Data
public class User {
private UUID userId;
private String email;
}
@Data
— просто удобная аннотация, которая применяет сразу несколько аннотаций Lombok.@ToString
генерирует реализацию для методаtoString()
, которая состоит из аккуратного представления объекта: имя класса, все поля и их значения.
@EqualsAndHashCode
генерирует реализацииequals
иhashCode
, которые по умолчанию используют нестатические и нестационарные поля, но настраиваются.
@Getter / @Setter
генерирует геттеры и сеттеры для частных полей.
@RequiredArgsConstructor
создаёт конструктор с требуемыми аргументами, где обязательными являются окончательные поля и поля с аннотацией@NonNull
(подробнее об этом ниже).
Одна эта аннотация просто и элегантно охватывает многие типичные случаи использования. Но POJO не всегда покрывает необходимую функциональность.
@Data
— полностью изменяемый класс, злоупотребление которым может повысить сложность и ограничить параллелизм, что негативно отражается на живучести приложения.Есть другое решение. Вернёмся к нашему классу
User
, сделаем его неизменяемым и добавим несколько других полезных аннотаций.@Value
@Builder(toBuilder = true)
public class User {
@NonNull
UUID userId;
@NonNull
String email;
@Singular
Set<String> favoriteFoods;
@NonNull
@Builder.Default
String avatar = “default.png”;
}
Аннотация
@Value
аналогична @Data
за исключением того, что все поля по умолчанию являются закрытыми и окончательными, а сеттеры не создаются. Благодаря этому объекты @Value
сразу становятся неизменяемыми. Поскольку все поля являются окончательными, конструктора аргументов нет. Вместо этого Lombok использует @AllArgsConstructor
. В результате получается полностью функциональный, неизменяемый объект.Но неизменяемость не очень полезна, если вам нужно всего лишь создать объект с помощью конструктора all-args. Как объясняет Джошуа Блох в книге «Эффективное программирование на Java», при наличии большого количества параметров конструктора следует использовать билдеры. Тут вступает в действие класс
@Builder
, автоматически генерируя внутренний класс билдера:User user = User.builder()
.userId(UUID.random())
.email(“grubhub@grubhub.com”)
.favoriteFood(“burritos”)
.favoriteFood(“dosas”)
.build()
Генерация билдера упрощает создание объектов с большим количеством аргументов и добавлением новых полей в будущем. Статический метод возвращает экземпляр билдера для задания всех свойств объекта. После этого вызов
build()
возвращает инстанс.Аннотацию
@NonNull
можно использовать для утверждения, что эти поля не являются нулевыми при создании экземпляра объекта, иначе выбрасывается исключение NullPointerException
. Обратите внимание, что поле аватара аннотировано @NonNull
, но не задано. Дело в том, что аннотация @Builder.Default
по умолчанию указывает на default.png.Также обратите внимание, как билдер использует
favoriteFood
, единственное название свойства в нашем объекте. При размещении аннотации @Singular
на свойстве коллекции Lombok создаёт специальные методы билдера для индивидуального добавления элементов в коллекцию, а не для одновременного добавления всей коллекции. Это особенно хорошо для тестов, потому что способы создания маленьких коллекций в Java нельзя назвать простыми и быстрыми.Наконец, параметр
toBuilder = true
добавляет метод экземпляра toBuilder()
, который создаёт объект билдера, заполненный всеми значениями этого экземпляра. Так легко создаётся новый инстанс, предварительно заполненный всеми значениями из исходного, так что остаётся изменить лишь необходимые поля. Это особенно полезно для классов @Value
, поскольку поля неизменяемы.Несколько примечаний дополнительно настраивают специальные функции сеттера.
@Wither
создаёт методы withX
для каждого свойства. На входе — значение, на выходе — клон экземпляра с обновлённым значением одного поля. @Accessors
позволяет настраивать автоматически созданные сеттеры. Параметр fluent=true
отключает конвенцию “get” и “set” для геттеров и сеттеров. В определённых ситуациях это может быть полезной заменой @Builder
.Если реализация Lombok не подходит для вашей задачи (и вы посмотрели на модификаторы аннотаций), то всегда можно просто взять и написать собственную реализацию. Например, если у вас класс
@Data
, но один геттер нуждается в пользовательской логике, просто реализуйте этот геттер. Lombok увидит, что реализация уже предоставлена, и не перезапишет её автоматически созданной реализацией.С помощью всего нескольких простых аннотаций базовый POJO получил так много богатых функций, которые упрощают его использование, не загружая работой нас, инженеров, не отнимая время и не увеличивая затраты на разработку.
Удаление шаблонного кода
Lombok полезен не только для POJO: его можно применить на любом уровне приложения. Следующие способы использования Lombok особенно полезны в классах компонентов, таких как контроллеры, службы и DAO (объекты доступа к данным).
Ведение журнала — базовое требование для всех частей программы. Любой класс, выполняющий значимую работу, должен записывать лог. Таким образом, стандартный логгер становится шаблоном для каждого класса. Lombok упрощает этот шаблон до одной аннотации, которая автоматически определяет и создаёт экземпляр логгера с правильным именем класса. Существует несколько различных аннотаций в зависимости от структуры журнала.
@Slf4j // also: @CommonsLog @Flogger @JBossLog @Log @Log4j @Log4j2 @XSlf4j
public class UserService {
// created automatically
// private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserService.class);
}
После объявления логгера добавляем наши зависимости:
@Slf4j
@RequiredArgsConstructor
@FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE)
public class UserService {
@NonNull UserDao userDao;
}
Аннотация
@FieldDefaults
добавляет ко всем полям окончательный и приватный модификаторы. @RequiredArgsConstructor
создаёт конструктор, который устанавливает экземпляр UserDao
. Аннотация @NonNull
добавляет проверку в конструкторе и создаёт исключение NullPointerException
, если экземпляр UserDao
равен нулю.Но подождите, это ещё не всё!
Есть ещё много ситуаций, где Lombok проявляет себя с лучшей стороны. Предыдущие разделы показывали конкретные примеры, но Lombok может облегчить разработку во многих областях. Вот несколько небольших примеров, как эффективнее его использовать.
Хотя в Java 9 появилось ключевое слово
var
, но переменную всё равно можно переназначить. В Lombok есть ключевое слово val
, которое выводит окончательный тип локальной переменной.
// final Map map = new HashMap<Integer, String>();
val map = new HashMap<Integer, String>();
Некоторые классы c чисто статическими функциями не предназначены для инициализации. Один из способов предотвратить создание экземпляра — объявить приватный конструктор, который выбрасывает исключение. Lombok кодифицировал этот шаблон в аннотации
@UtilityClass
. Она генерирует приватный конструктор, который создаёт исключение, окончательно выводит класс и делает все методы статическими.@UtilityClass
// will be made final
public class UtilityClass {
// will be made static
private final int GRUBHUB = “ GRUBHUB”;
// autogenerated by Lombok
// private UtilityClass() {
// throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated");
//}
// will be made static
public void append(String input) {
return input + GRUBHUB;
}
}
Java часто критикуют за многословность из-за проверяемых исключений. Отдельная аннотация Lombok устраняет их:
@SneakyThrows
. Как и следовало ожидать, реализация довольно хитрая. Она не перехватывает исключения и даже не оборачивает исключения в RuntimeException
. Вместо этого она полагается на тот факт, что во время выполнения JVM не проверяет согласованность проверяемых исключений. Так делает только javac. Поэтому Lombok с помощью преобразования байт-кода во время компиляции отключает эту проверку. В результате получается запускаемый код.
public class SneakyThrows {
@SneakyThrows
public void sneakyThrow() {
throw new Exception();
}
}
Сравнение бок о бок
Прямое сравнение лучше всего демонстрирует, сколько кода экономит Lombok. В плагине IDE есть функция “de-lombok”, которая приблизительно преобразует большинство аннотаций Lombok в нативный Java-код (аннотации
@NonNull
не конвертируются). Таким образом, любая IDE с установленным плагином сможет конвертировать большинство аннотаций в собственный код Java и обратно. Вернёмся к нашему классу User
.
@Value
@Builder(toBuilder = true)
public class User {
@NonNull
UUID userId;
@NonNull
String email;
@Singular
Set<String> favoriteFoods;
@NonNull
@Builder.Default
String avatar = “default.png”;
}
Класс Lombok — всего лишь 13 простых, читаемых, понятных строк. Но после запуска de-lombok, класс превращается более чем в сто строк шаблонного кода!
public class User {
@NonNull
UUID userId;
@NonNull
String email;
Set<String> favoriteFoods;
@NonNull
@Builder.Default
String avatar = "default.png";
@java.beans.ConstructorProperties({"userId", "email", "favoriteFoods", "avatar"})
User(UUID userId, String email, Set<String> favoriteFoods, String avatar) {
this.userId = userId;
this.email = email;
this.favoriteFoods = favoriteFoods;
this.avatar = avatar;
}
public static UserBuilder builder() {
return new UserBuilder();
}
@NonNull
public UUID getUserId() {
return this.userId;
}
@NonNull
public String getEmail() {
return this.email;
}
public Set<String> getFavoriteFoods() {
return this.favoriteFoods;
}
@NonNull
public String getAvatar() {
return this.avatar;
}
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof User)) return false;
final User other = (User) o;
final Object this$userId = this.getUserId();
final Object other$userId = other.getUserId();
if (this$userId == null ? other$userId != null : !this$userId.equals(other$userId)) return false;
final Object this$email = this.getEmail();
final Object other$email = other.getEmail();
if (this$email == null ? other$email != null : !this$email.equals(other$email)) return false;
final Object this$favoriteFoods = this.getFavoriteFoods();
final Object other$favoriteFoods = other.getFavoriteFoods();
if (this$favoriteFoods == null ? other$favoriteFoods != null : !this$favoriteFoods.equals(other$favoriteFoods))
return false;
final Object this$avatar = this.getAvatar();
final Object other$avatar = other.getAvatar();
if (this$avatar == null ? other$avatar != null : !this$avatar.equals(other$avatar)) return false;
return true;
}
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $userId = this.getUserId();
result = result * PRIME + ($userId == null ? 43 : $userId.hashCode());
final Object $email = this.getEmail();
result = result * PRIME + ($email == null ? 43 : $email.hashCode());
final Object $favoriteFoods = this.getFavoriteFoods();
result = result * PRIME + ($favoriteFoods == null ? 43 : $favoriteFoods.hashCode());
final Object $avatar = this.getAvatar();
result = result * PRIME + ($avatar == null ? 43 : $avatar.hashCode());
return result;
}
public String toString() {
return "User(userId=" + this.getUserId() + ", email=" + this.getEmail() + ", favoriteFoods=" + this.getFavoriteFoods() + ", avatar=" + this.getAvatar() + ")";
}
public UserBuilder toBuilder() {
return new UserBuilder().userId(this.userId).email(this.email).favoriteFoods(this.favoriteFoods).avatar(this.avatar);
}
public static class UserBuilder {
private UUID userId;
private String email;
private ArrayList<String> favoriteFoods;
private String avatar;
UserBuilder() {
}
public User.UserBuilder userId(UUID userId) {
this.userId = userId;
return this;
}
public User.UserBuilder email(String email) {
this.email = email;
return this;
}
public User.UserBuilder favoriteFood(String favoriteFood) {
if (this.favoriteFoods == null) this.favoriteFoods = new ArrayList<String>();
this.favoriteFoods.add(favoriteFood);
return this;
}
public User.UserBuilder favoriteFoods(Collection<? extends String> favoriteFoods) {
if (this.favoriteFoods == null) this.favoriteFoods = new ArrayList<String>();
this.favoriteFoods.addAll(favoriteFoods);
return this;
}
public User.UserBuilder clearFavoriteFoods() {
if (this.favoriteFoods != null)
this.favoriteFoods.clear();
return this;
}
public User.UserBuilder avatar(String avatar) {
this.avatar = avatar;
return this;
}
public User build() {
Set<String> favoriteFoods;
switch (this.favoriteFoods == null ? 0 : this.favoriteFoods.size()) {
case 0:
favoriteFoods = java.util.Collections.emptySet();
break;
case 1:
favoriteFoods = java.util.Collections.singleton(this.favoriteFoods.get(0));
break;
default:
favoriteFoods = new java.util.LinkedHashSet<String>(this.favoriteFoods.size() < 1073741824 ? 1 + this.favoriteFoods.size() + (this.favoriteFoods.size() - 3) / 3 : Integer.MAX_VALUE);
favoriteFoods.addAll(this.favoriteFoods);
favoriteFoods = java.util.Collections.unmodifiableSet(favoriteFoods);
}
return new User(userId, email, favoriteFoods, avatar);
}
public String toString() {
return "User.UserBuilder(userId=" + this.userId + ", email=" + this.email + ", favoriteFoods=" + this.favoriteFoods + ", avatar=" + this.avatar + ")";
}
}
}
То же самое сделаем для класса
UserService
.@Slf4j
@RequiredArgsConstructor
@FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE)
public class UserService {
@NonNull UserDao userDao;
}
Вот примерный аналог в стандартном Java-коде.
public class UserService {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserService.class);
private final UserDao userDao;
@java.beans.ConstructorProperties({"userDao"})
public UserService(UserDao userDao) {
if (userDao == null) {
throw new NullPointerException("userDao is marked @NonNull but is null")
}
this.userDao = userDao;
}
}
Оценка эффекта
На портале Grubhub более ста бизнес-сервисов, связанных с доставкой еды. Мы взяли один из них и запустили функцию “de-lombok” в плагине Lombok IntelliJ. В результате изменилось около 180 файлов, а кодовая база выросла примерно на 18 000 строк кода после удаления 800 случаев использований Lombok. В среднем, каждая строка Lombok экономит 23 строки Java. С таким эффектом трудно представить Java без Lombok.
Резюме
Lombok — отличный помощник, который реализует новые функции языка, не требуя особых усилий со стороны разработчика. Конечно, проще установить плагин, чем обучить всех инженеров новому языку и портировать существующий код. Lombok не всесилен, но уже из коробки достаточно мощный, чтобы реально помочь в работе.
Ещё одно преимущество Lombok в том, что он сохраняет согласованность кодовых баз. У нас более ста различных сервисов и распределённая команда по всему миру, так что согласованность кодовых баз облегчает масштабирование команд и снижает нагрузку на переключение контекста при запуске нового проекта. Lombok работает для любой версии начиная с Java 6, поэтому мы можем рассчитывать на его доступность во всех проектах.
Для Grubhub это больше, чем просто новые функции. В конце концов, весь этот код можно написать вручную. Но Lombok упрощает скучные части кодовой базы, не влияя на бизнес-логику. Это позволяет сфокусироваться на вещах, действительно важных для бизнеса и наиболее интересных для наших разработчиков. Монтонный шаблонный код — это пустая трата времени программистов, рецензентов и мейнтейнеров. Кроме того, поскольку этот код больше не пишется вручную, то устраняет целые классы опечаток. Преимущества автогенерации в сочетании с мощью
@NonNull
уменьшают вероятность ошибок и помогают нашей разработке, которая направлена на доставку еды к вашему столу! Комментарии (26)
BUY33
04.02.2019 20:11+4Может несколько параллельный к теме статьи вопрос. Однако, у java-господ нет усталости от обилия аннотаций в коде?
Казалось бы, делает жизнь проще, столько работы за собой скрывают. Но вот приходишь в какой-нибудь типовой проект с Lombok, Spring, Hibernate и что-нибудь ещё. Весь код просто увешен как ёлка с ног до головы аннотациями.
А молодые неокрепшие умы восхищаются, для них кругом магия, понять которую они не спешат. Зачем? Поставь аннотацию, всё сразу заработает. Не работает? Не хватает какой-то аннотации.
А мне всё больше кажется, что есть в этом что-то костыльное. Повышается ментальная нагрузка — глядя на аннотацию, надо вспоминать, что она добавляет или какую аннотацию ещё надо добавить. Как будто ключевых слов в языке прибавилось раза в два. Аннотации не нарушают принцип «явное лучше неявного»?
К Lombok, наверно, тут меньше всего претензий — он порождает довольно простой код, но тоже вносит свою лепту в общую копилку. Хотя можно же просто жмакнуть пару клавиш в IDEA для генерации геттеров/сеттеров и проч.
Всё чаще начинает казаться, что уж лучше я руками.tuxi
04.02.2019 22:33+1Java-господа разные бывают :) Поэтому аннотации не везде властвуют. Насчет «кодогенерации» — поддержу. Бездумное использование не повышает уровень разработки. Ускоряет — да. Но не повышает. Плюс, рано или поздно встанет вопрос: а правильно ли реализованы методы equals/compare/hashCode и почему на проде наш компаратор кидает эксепшены.
qasta
05.02.2019 20:13Есть такая усталость. Когда-то аннотации пришли на замену монструозным xml-описаниям, которые клалис рядом с классами, и свою задачу они решили (тем более, что в C# «атрибуты» появились очень быстро). Сейчас периодически на эти аннотации навешивают функционал, который там не нужен, в результате получаем слишком развесистые файлы .java, где на 3 строки с полями класса 23 строки с аннотациями :)
Всё чаще начинает казаться, что уж лучше я руками.
Полностью согласен. Меня в этом вопросе вообще далеко занесло: в своё время отказался от spring-boot (условно раз и навсегда), так как там есть ConditionalOnClass и ему подобные механизмы, в результате которых анализ нестандартных проблем и реализация нестандартного функционала ох как сложно даётся. Конкретный пример — в версии 1.4 (или более ранней?) нельзя было НИКАК добраться до ObjectMapper из FasterXML (который из JAX-RS — не путать с MVC), чтобы немного его «поднастроить». На раскопки тогда ушло пол-дня или день. Ну не предусмотрели на тот момент «магической аннотации», а создаваемый ObjectMapper в контекст (ни в Spring Context, ни в атрибуты сервлетов/сессий/запросов) не клался. И всё — птица «обломинго» заставила заменить фреймворк.
К чему это я всё написал: сам подход «всё сделает магия» приводит к тому, что за нестандартным поведением приходится ходить либо очень далеко, либо вообще отказываться. И если разработчики фреймворка этот сценарий предусмотрели — то вы добавляете «вторую магическую аннотацию или файлик», а если нет — то никак. Это всё хорошо для pet-проектов и «ща я за 5 минут сделаю докер-образ с годным приложением из 3-х сущностей», а в сложных проектах (где таблиц хотя бы 100 и бизнес-атрибутов на некоторых их них столько же) — увы…
panchmp
04.02.2019 20:17+3Lombok неплохая тулза, которая занимает свою нишу и имеет своих фанатов
но «возвращает величие Java», «реализует новые функции языка» — явный перебор, эта тулза лишь синтаксический сахар
все «проблемы» которые решает тулза спокойно решаются через автогенерацию в idea, но без диких хаков и ужаса при дебаге
antonpv
04.02.2019 20:26Кстати вот да, недавно как раз читал эту статью в оригинале, а тут перевод! Спасибо, материал весьма дискуссионный. Не удержусь и оставлю коммент под оригинальной статьей:
Lombok sucks a butt. Kotlin data classes are way better and don’t require a ghetto compiler extension.
Borz
04.02.2019 22:29+1пыль решили сдуть с книжных полок? Тогда надо сразу автору и про новые фишки в виде стримов и лямбд в Java 8 рассказать — с ними Java ещё величавее будет
vladimir_dolzhenko
На мой скромный взгляд, ломбок — как костыль сбоку — если уж добавлять вкусняшек, но при этом оставаясь в java экосистеме — то стоит смотреть больше в сторону kotlin. IMHO.
darkit
а в чем костыль? код укорачивает, время экономит. в рантайме ничего не трогает. наоборот какая джава отличная, что можно её так расширять.
vladimir_dolzhenko
Пожалуй та же беда, что и с другими annotation processor-ами времени компиляции:
Если вдруг приходиться отладкой заниматься — с ломбоком начинает напоминать ад (хотя может сейчас уже плагины стали получше). Не совсем очевидно, что там он за код нагенерировал (прежде всего equals/hashcode).
Т.е если говорить о синтаксическом сахаре лет 10 назад — то ломбок был вполне себе ответом, но сейчас, когда есть лучшие решения… зачем делать такой шаг назад?
darkit
у меня много кода с ломбоком и обычно дебажить надо логику приложения, а не банальные геттеры. те моя практика показывает, что никакой боли нет.
я не думаю, что бизнес согласится выкинуть весь код написанный сотней разработчиков и начать все с нуля в Котлине. причем я то аргументов не смогу привести зачем надо тратить на это деньги. Лучше уж тогда все на Rust переписать :)
vladimir_dolzhenko
Никто не говорить о том, что переписать всё на kotlin/scala/whatever. Я говорю о том, что если в 2019 начинать что-то писать — то вряд ли стоит выбирать Lombok.
И про Rust вы наверно пошутили…
darkit
ломбок лишь сахар, поэтому он лишь дополнение к джава коду. и чем плохо писать на джава в 2019 я не особо понимаю :(((
fRoStBiT
А зачем переписывать? На Lombok (это фактически диалект Java) тоже никто бы не стал переписывать всё с нуля.
И там, и там можно писать новый код «в новом стиле» (без шаблонных геттеров и прочего), не трогая старый.
darkit
Ломбок легко встраивается в проект и не выглядит чужеродным. ну добавилось ещё пару джава аннотаций, делов то. те его можно всунуть в любой проект на любой стадии написания. а Котлин так легко не получится.
fRoStBiT
Точнее добавился ещё один annotation processor, который часть своих функций выполняет через дикие хаки.
И без поддержки в IDE c ним никак.
Kotlin точно так же всовывается в любой проект на любой стадии без проблем.
sshikov
Не скажу про котлин, а скажем всунуть в проект груви вам будет стоить добавления одного плагина в сборку maven (для gradle примерно тоже самое). IDEA это понимает, и поддерживает. И все. Вы можете писать классы на груви, получая большую кучу приятных плюшек. Нет, это не замена ломбоку 1 к одному, конечно. Это просто другой вариант упростить себе работу.
Подозреваю, что с котлином все точно также, плюс-минус детали.
oldd
Так и есть, в kotline хорошая поддержка groovy, включая запуск сгенерированных скриптов и передача объектов в обе стороны
sshikov
Нет конечно. Котлин и груви — это две параллельные возможности. Друг друга они не знают и не поддерживают (ну разве что через JSR-233).
NickWinter
Kotlin не собирается под jdk > 8. Соответственно не получает фишек новых версий.
fRoStBiT
А каких фишек вам не хватает?
Все новые фичи рантайма (например, сжатые строки и G1) не зависят от языка. А в языке Java пока не появилось ничего нового, чего могло бы не хватать тем, кто перешёл на Kotlin.
NickWinter
С ходу нашел вот:
openjdk.java.net/jeps/280
bugs.openjdk.java.net/browse/JDK-8175883
Тут даже вопрос не в конкретных поинтах, а в том, что при развитии jdk kotlin не будет подхватывать оптимизации и улучшения.
osiraben
Порой нельзя так просто взять и убедить заказчика использовать котлин.