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

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
24-Фев-2021 15:33


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

===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_java, #_java, #_lombok, #_extension_methods, #_java
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 15:57
Часовой пояс: UTC + 5