Методы расширения в Java

от автора


В таких языках программирования, как 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(StreamExtensions.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 на класс (откуда будет вызов), перечисляя все утилитные классы, из которых необходимо импортировать вызовы.

ссылка на оригинал статьи https://habr.com/ru/post/527688/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *