В таких языках программирования, как C#, Kotlin, Groovy, Scala есть возможность расширять класс путем добавления нового функционала, при этом не требуется наследование или изменение самого изначального класса. Это реализовано с помощью специальных выражений, называемых расширения. Java, в отличие от этих языков, не имеет такой возможности из коробки и даже не планирует в ближайших релизах. Благодаря Lombok это стало возможным. Методы расширения были реализованы в Lombok еще 8 лет назад (с поддержкой Eclipse), но для многих все упиралось в поддержку плагином в IDEA (код компилировался, но IDE его не распознавала как валидный). Lombok плагин теперь предустановлен в IDEA 2021.1 EAP, и теперь он поддерживает методы расширения lombok (спасибо akozlova, NekoCaffeine и mplushnikov).
Рассмотрим пример классического статического импорта:
import static org.apache.commons.lang3.StringUtils.capitalize;
public class ExtensionMethods {
public static void main(String[] args) {
String str = "test";
String capitalized = capitalize(str);
// "Test"
System.out.println(capitalized);
}
}
при переходе на метод расширения код станет выглядеть так:
import lombok.experimental.ExtensionMethod;
import org.apache.commons.lang3.StringUtils;
@ExtensionMethod(StringUtils.class)
public class ExtensionMethods {
public static void main(String[] args) {
String str = "test";
String capitalized = str.capitalize();
// "Test"
System.out.println(capitalized);
}
}
Заворачивания аргументов в скобки заменяются на цепочки вызовов, т.е. код вида call3(call2(call1(arg)))
превратится в
arg.call1()
.call2()
.call3();
Во многих ситуациях это может облегчить чтение кода, особенно когда цепочки длинные, здесь есть некая аналогия со Stream Api
или преобразования значения java.util.Optional
.
Фактически это просто синтаксический сахар. Код при компиляции будет заменен на вызов статического метода. Первый аргумент статического метода и станет объектом "this
".
null-значения
В отличие от обычных instance-методов, методы расширения могут работать и с null-значениями, т.е. подобный вызов вполне допустим:
import org.apache.commons.lang3.StringUtils;
@ExtensionMethod(StringUtils.class)
public class MethodExtensions {
public static void main(String[] args) throws Exception {
String nullStr = null;
// "isEmpty=true"
System.out.println("isEmpty=" + nullStr.trimToEmpty().isEmpty());
}
}
Еще примеры
Можно добавить в проект на JDK 8 метод, который появится только в JDK 11:
@UtilityClass
public class CollectionExtensions {
public static <T> T[] toArray(Collection<T> list, IntFunction<T[]> generator) {
return list.stream().toArray(generator);
}
}
@ExtensionMethod(CollectionExtensions.class)
public class MethodExtensions {
public static void main(String[] args) throws Exception {
List<Integer> list = Arrays.asList(1, 2, 3);
// toArray(IntFunction<T[]>) добавлен только в Java 11
Integer[] array = list.toArray(Integer[]::new);
// "[1, 2, 3]"
System.out.println(Arrays.toString(array));
}
}
Или добавить более лаконичный вызов Stream.collect(toList()):
@UtilityClass
public class StreamExtensions {
public static <T> List<T> toList(Stream<T> stream) {
return stream.collect(Collectors.toList());
}
}
@ExtensionMethod(CollectionExtensions.class)
public class MethodExtensions {
public static void main(String[] args) throws Exception {
List<Integer> list = Arrays.asList(3, 1, 2);
List<Integer> sorted = list.stream()
.sorted()
.toList();
// "[1, 2, 3]"
System.out.println(sorted);
}
}
Настройка проекта
- Установите последнюю версию IDEA EAP, важно: EAP версии не стабильны, зато бесплатны. Плагин доступен и в Ultimate, и в Community Edition.
- Добавьте зависимость lombok: maven
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
либо для gradle:
compileOnly 'org.projectlombok:lombok:1.18.16'
annotationProcessor 'org.projectlombok:lombok:1.18.16'
testCompileOnly 'org.projectlombok:lombok:1.18.16'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.16'
- Убедитесь, что включена опция проекта
Build, Execution, Deployment
->Compiler
->Annotations processor
->Enable annotation processing
- Добавьте аннотацию
@ExtensionMethod
на класс (откуда будет вызов), перечисляя все утилитные классы, из которых необходимо импортировать вызовы.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Что думаете про методы расширения?
-
33,3%Уже использую в Kotlin, Groovy, Scala, etc.3
-
55,6%Уже использую в Lombok / обязательно попробую5
-
0,0%Использую Lombok, но идея расширений не нравится0
-
11,1%Lombok — зло1
-
0,0%Другое (напишите в комментарии)0