Замена связанного вызова метода с использованием ссылки на метод

"Java 8 Lambdas: прагматическое функциональное программирование" имеет пример использования peek метод в Stream API. Этот фрагмент кода печатает национальности артистов, чье имя начинается с "The":

Set<Nationality> nationalities = album.getMusician()
                                 .filter(artist -> artist.getName().startsWith("The"))
                                 .map(artist -> artist.getNationality())
                                 .peek(nation -> System.out.println(nation))
                                 .collect(Collectors.toList());

Я хочу переписать этот код со ссылками на методы:

Set<Nationality> nationalities = album.getMusician()
                                 .filter(artist -> artist.getName().startsWith("The"))
                                 .map(Artist::getNationality)
                                 .peek(System.out::println)
                                 .collect(Collectors.toList());

Есть ли решение переписать filter(artist -> artist.getName().startsWith("The"))?

3 ответа

Решение

Вам нужно создать отдельный метод, который принимает Artist и возвращает логическое значение:

private boolean nameStartsWithThe(Artist a) {
    return a.getName().startsWith("The");
}

Set<Nationality> nationalities = album.getMusician()
                                 .filter(this::nameStartsWithThe)

или статическим методом:

private static boolean nameStartsWithThe(Artist a) {
    return a.getName().startsWith("The");
}

Set<Nationality> nationalities = album.getMusician()
                                 .filter(MyClass::nameStartsWithThe)

Вам нужно что-то, что составляет два метода. Есть несколько методов для составления методов (IntUnaryOperator имеет compose а также andThen методы, которые могут составить два IntUnaryOperatorс новым IntUnaryOperator). Но те, которые я нашел, кажутся специализированными для определенных типов функциональных интерфейсов; определяющий compose методы для каждой возможной пары функциональных типов интерфейса были бы слишком громоздкими.

Я получил что-то на работу, которая будет составлять Function и Predicate чтобы получить новый Predicate:

static <T,U> Predicate<T> functionPredicate(Function<T,U> func, Predicate<U> pred) {
    return obj -> pred.test(func.apply(obj));
}

То есть он может составлять предикат, который оперирует T от функции, которая принимает T и возвращается Uи предикат, который действует на U, Это будет почти работать на вашем примере, за исключением того, что startsWith нужен другой параметр. Но это работает:

static boolean startsWithThe(String s) {
    return s.startsWith("The");
}

Predicate<Artist> pred = functionPredicate(Artist::getName, ThisClass::startsWithThe);

где ThisClass какой класс содержит startsWithThe, Это работает. Если вы хотите избежать написания нового метода (например, startsWithThe), вы могли бы написать обобщенный метод "параметризованного предиката", чтобы вы

Predicate<Artist> pred = functionPredicate(Artist::getName, parameterizedPredicate(String::startsWith, "The"));

но я не пробовал это.

Поэтому кажется, что можно придумать что-то, что позволит вам использовать ссылки на методы вместо лямбда-выражений. Я спрашиваю, стоит ли это того. Для меня ссылка на метод - это просто сокращение для некоторых видов лямбд; и если вы не можете делать то, что вы хотите, с помощью простой ссылки на метод, я думаю, что использование лямбды является кратким и достаточно ясным, и вам не нужно добавлять всю дополнительную ригмаролу, как мой functionPredicate метод. Я видел несколько вопросов, которые задают что-то вроде: "Как я могу использовать для этого ссылку на метод вместо лямбды?", И я, честно говоря, не понимаю, почему.

Невозможно заменить эту строку ссылкой на метод.


Ссылка на метод работает с использованием того факта, что во всем лямбда-выражении используется только один объект, и компилятор может вывести его (ссылка не имеет значения, а тип может быть выведен) с помощью целевой типизации.

Так,

artist -> artist.getNationality()

заменяется на

Artist::getNationality

Вот Artist::getNationality Метод соответствует типу цели, не требуя дополнительной информации.


В случае artist -> artist.getName().startsWith("The")в лямбда-выражении есть два вызова метода. Порядок, параметры важны, и должны быть указаны.

Похоже, что artist ссылка должна быть выведена, но компилятор не будет знать, какой объект должен startsWith("The") метод будет вызван.

Надеюсь это поможет.

Другие вопросы по тегам