Ссылки на методы в Java

Ссылки на методы в Java

t.me/data_analysis_ml

Лямбды — гибкие и анонимные фрагменты кода

Лямбды в Java полезны во многих направлениях. Лямбда-выражения можно использовать для более простых задач, а лямбда-утверждения — для более сложных. Лямбды могут вызывать другие методы для текущего объекта (this) и объектов, которые находятся в области видимости, таких как текущий элемент итерации и конечная локальная переменная за пределами лямбды. Лямбду всегда можно упростить, поместив код в другой метод.

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

@Test
public void filterStringsLambda()
{
    var list = Lists.mutable.with(
            "Atlanta", 
            "Atlantic City", 
            "Boston", 
            "Boca Raton");
    
    var actual = list.stream()
            .filter(string -> string.startsWith("At"))
            .collect(Collectors.toList());

    var expected = List.of("Atlanta", "Atlantic City");

    Assertions.assertEquals(expected, actual);
}

В этом коде лямбда — параметр, передаваемый методу filter в качестве предиката (Predicate). В данном примере предикат принимает параметр типа String, который назван string. Выражение после разделителя (->) будет вычисляться для каждого элемента списка и будет включать только те элементы, которые оцениваются как true.

В Stream API есть несколько методов, которые принимают предикат в качестве параметра. Например, filteranyMatchallMatch и noneMatch.

Здесь нет простого способа воспользоваться ссылкой на метод, потому что параметр “At” нужно передать методу startsWith. Параметры — криптонит для ссылок на методы. Мы можем симулировать нечто вроде ссылки на метод, написав лямбда-выражение и выделив его в отдельный метод следующим образом.

@Test
public void filterStringsLambdaInMethod()
{
    var list = Lists.mutable.with(
            "Atlanta", 
            "Atlantic City", 
            "Boston", 
            "Boca Raton");

    var actual = list.stream()
            .filter(this.stringStartsWith("At"))
            .collect(Collectors.toList());

    var expected = List.of("Atlanta", "Atlantic City");

    Assertions.assertEquals(expected, actual);
}

private Predicate<String> stringStartsWith(String prefix)
{
    return string -> string.startsWith(prefix);
}

Необходимость создавать метод в классе для генерации лямбд, которые могут использовать локальные переменные в области видимости, далека от идеала. Хотелось бы иметь более простую возможность применять метод startsWith в качестве ссылки на метод.

Как удовлетворить предпочтение ссылки на метод?

Задействовать методы With из Eclipse Collections.

Для многих методов, доступных в API Eclipse Collections, существует соответствующий дополнительный метод с суффиксом With. Каждый метод с With использует другой именованный функциональный интерфейс, который принимает два параметра (вторым будет, например, Predicate2Function2 и т.д.). Следующая схема показывает некоторые из основных методов в API Eclipse Collections вместе с соответствующими им эквивалентами и типами функциональных интерфейсов, которые они принимают в качестве параметров.

Базовые методы RichIterable и With-методы

Как эти дополнительные методы помогают использовать ссылки на методы с параметрами? Рассмотрим на примере.

Базовое использование лямбд

Посмотрим на пример фильтрации списка строк с использованием одного из основных методов Eclipse Collections с лямбдой.

@Test
public void selectStringsLambda()
{
    var list = Lists.mutable.with(
            "Atlanta", 
            "Atlantic City", 
            "Boston", 
            "Boca Raton");

    var actual = list.select(string -> string.startsWith("At"));

    var expected = List.of("Atlanta", "Atlantic City");

    Assertions.assertEquals(expected, actual);
}

Ссылка на метод с With

Теперь посмотрим, как удовлетворить предпочтение для ссылки на метод, используя эквивалент метода select c “With”.

@Test
public void selectStringsWithMethodReference()
{
    var list = Lists.mutable.with(
            "Atlanta", 
            "Atlantic City", 
            "Boston", 
            "Boca Raton");

    var actual = list.selectWith(String::startsWith, "At");

    var expected = List.of("Atlanta", "Atlantic City");

    Assertions.assertEquals(expected, actual);
}

Если у вас так и не случился момент озарения, не пугайтесь. Мы по-прежнему не можем передавать параметры ссылкам на методы напрямую. В настоящее время в Java нет синтаксиса, который это поддерживал бы. Здесь, видимо, какой-то подвох.

Попробую объяснить, как это работает. Метод selectWith принимает два параметра. Первый —  Predicate2, который будет соответствовать сигнатуре String::StartsWith. А если точнее, Predicate2<String, String> соответствует сигнатуре String::startsWith. Второй параметр, selectWith, принимает параметр любого типа, который в данном случае является строкой.

Вот как в точности выглядит сигнатура selectWith для RichIterable.

<P> RichIterable<T> selectWith(
        Predicate2<? super T, ? super P> predicate, 
        P parameter);

Пример реализации паттерна selectWith

В Eclipse Collection есть класс с именем IteratorIterate. Он включает многие базовые шаблоны итераций в Eclipse Collections, которые позволяют использовать шаблоны с любым итеративным типом Java. Я делюсь именно этим примером, потому что итератор — достаточно базовая концепция, и большинство разработчиков на Java смогут прочитать и понять такой код. Ниже показана реализация selectWith в IteratorIterate, которая сочетается со ссылками на методы с одним параметром.

public static <T, P, R extends Collection<T>> R selectWith(
        Iterator<T> iterator,
        Predicate2<? super T, ? super P> predicate,
        P injectedValue,
        R targetCollection)
{
    while (iterator.hasNext())
    {
        T item = iterator.next();
        if (predicate.accept(item, injectedValue))
        {
            targetCollection.add(item);
        }
    }
    return targetCollection;
}

Этот паттерн может использоваться с любым типом, который способен создавать Iterator.

Вот пример использования IteratorIterate.selectWith с обычным Set из JDK.

@Test
public void selectWithOnIteratorIterate()
{
    Set<String> strings = Set.of(
            "Atlanta", 
            "Atlantic City", 
            "Boston", 
            "Boca Raton");
    
    HashSet<String> actual = IteratorIterate.selectWith(
            strings.iterator(),
            String::startsWith,
            "At",
            new HashSet<>());
    var expected = Set.of("Atlanta", "Atlantic City");
    Assertions.assertEquals(expected, actual);
}

Больше ссылок на методы

Теперь, когда мы знаем, как использовать ссылку на метод с помощью With-метода, рассмотрим еще несколько примеров.

@Test
public void predicatesWithMethodReference()
{
    var list = Lists.mutable.with(
            "Atlanta", 
            "Atlantic City", 
            "Boston", 
            "Boca Raton");

    var selected1 = list.selectWith(String::startsWith, "At");

    var expected1 = List.of("Atlanta", "Atlantic City");
    Assertions.assertEquals(expected1, selected1);

    var rejected = list.rejectWith(String::startsWith, "At");

    var expected2 = List.of("Boston", "Boca Raton");
    Assertions.assertEquals(expected2, rejected);

    var selected2 = list.selectWith(String::startsWith, "Bo");

    Assertions.assertEquals(expected2, selected2);

    var detected = list.detectWith(String::endsWith, "y");

    Assertions.assertEquals("Atlantic City", detected);

    var count = list.countWith(String::contains, "c");

    Assertions.assertEquals(2, count);
    Assertions.assertTrue(
        list.anySatisfyWith(String::contains, "a"));
    Assertions.assertTrue(
        list.allSatisfyWith(String::contains, "t"));
    Assertions.assertTrue(
         list.noneSatisfyWith(String::contains, "z"));

    var partitioned = list.partitionWith(String::endsWith, "n");

    Assertions.assertEquals(expected2, partitioned.getSelected());
    Assertions.assertEquals(expected1, partitioned.getRejected());
}

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

@javatg

Источник



Report Page