it-swarm.com.ru

Как отрицать предикат ссылки на метод

В Java 8 вы можете использовать ссылку на метод для фильтрации потока, например:

Stream<String> s = ...;
long emptyStrings = s.filter(String::isEmpty).count();

Есть ли способ создать ссылку на метод, который является отрицанием существующего, то есть что-то вроде:

long nonEmptyStrings = s.filter(not(String::isEmpty)).count();

Я мог бы создать метод not, как показано ниже, но мне было интересно, предлагает ли JDK нечто подобное.

static <T> Predicate<T> not(Predicate<T> p) { return o -> !p.test(o); }
248
assylias

Java-11 предлагает новый метод Predicate # not

Таким образом, вы можете отменить ссылку на метод:

Stream<String> s = ...;
long nonEmptyStrings = s.filter(Predicate.not(String::isEmpty)).count();
52
Anton Balaniuc

Я планирую статический импорт следующего, чтобы позволить использование ссылки на метод inline: 

public static <T> Predicate<T> not(Predicate<T> t) {
    return t.negate();
}

например.

Stream<String> s = ...;
long nonEmptyStrings = s.filter(not(String::isEmpty)).count();

Обновление: - Ну, JDK/11 может предложить аналогичное решение .

176
davidillsley

Есть способ составить ссылку на метод, противоположную текущей ссылке на метод. См. Ответ @ vlasec ниже, который показывает, как явным образом привести ссылку на метод к Predicate, а затем преобразовать его с помощью функции negate. Это один из нескольких других не слишком хлопотных способов сделать это.

Противоположность этому:

Stream<String> s = ...;
int emptyStrings = s.filter(String::isEmpty).count();

это:

Stream<String> s = ...;
int notEmptyStrings = s.filter(((Predicate<String>) String::isEmpty).negate()).count()

или это:

Stream<String> s = ...;
int notEmptyStrings = s.filter( it -> !it.isEmpty() ).count();

Лично я предпочитаю более позднюю технику, потому что нахожу более понятным чтение it -> !it.isEmpty(), чем длинное подробное явное приведение, а затем отрицание.

Можно также сделать предикат и использовать его повторно:

Predicate<String> notEmpty = (String it) -> !it.isEmpty();

Stream<String> s = ...;
int notEmptyStrings = s.filter(notEmpty).count();

Или, если есть коллекция или массив, просто используйте цикл for, который прост, имеет меньше накладных расходов, а * может быть ** быстрее:

int notEmpty = 0;
for(String s : list) if(!s.isEmpty()) notEmpty++;

* Если вы хотите знать, что быстрее, то используйте JMH http://openjdk.Java.net/projects/code-tools/jmh и избегайте ручного кода тестирования, если только он не избегает всех оптимизаций JVM - см. Java 8: производительность потоков против коллекций

** Я получаю злость за предположение, что техника цикла for работает быстрее. Это исключает создание потока, исключает использование другого вызова метода (отрицательная функция для предиката) и устраняет временный список/счетчик аккумулятора. Итак, несколько вещей, которые сохраняются последней конструкцией, могут сделать ее быстрее. 

Я думаю, что это проще и приятнее, хотя и не быстрее. Если работа требует молотка и гвоздя, не вводите бензопилу и клей! Я знаю, что некоторые из вас не согласны с этим.

список пожеланий: я хотел бы видеть, что функции Java Stream теперь немного развиваются, поскольку пользователи Java более знакомы с ними. Например, метод 'count' в Stream может принимать Predicate, чтобы это можно было сделать прямо так:

Stream<String> s = ...;
int notEmptyStrings = s.count(it -> !it.isEmpty());

or

List<String> list = ...;
int notEmptyStrings = lists.count(it -> !it.isEmpty());
134
The Coordinator

Predicate имеет методы and, or и negate.

Однако String::isEmpty не является Predicate, это просто String -> Boolean лямбда, и он все равно может стать чем угодно, например, Function<String, Boolean>. Вывод типа это то, что должно произойти в первую очередь. Метод filter выводит тип неявно . Но если вы отрицаете это, прежде чем передать его в качестве аргумента, это больше не происходит. Как упоминалось в @axtavt, явный вывод можно использовать как уродливый способ:

s.filter(((Predicate<String>) String::isEmpty).negate()).count()

В других ответах рекомендуются другие способы, среди которых статический метод not и лямбда, скорее всего, являются лучшими идеями. На этом заканчивается раздел tl; dr .


Однако, если вы хотите получить более глубокое понимание вывода типа лямбда-выражений, я хотел бы объяснить его более подробно, используя примеры. Посмотрите на них и попытайтесь выяснить, что происходит:

Object obj1                  = String::isEmpty;
Predicate<String> p1         = s -> s.isEmpty();
Function<String, Boolean> f1 = String::isEmpty;
Object obj2                  = p1;
Function<String, Boolean> f2 = (Function<String, Boolean>) obj2;
Function<String, Boolean> f3 = p1::test;
Predicate<Integer> p2        = s -> s.isEmpty();
Predicate<Integer> p3        = String::isEmpty;
  • obj1 не компилируется - лямбды должны выводить функциональный интерфейс (= с одним абстрактным методом)
  • p1 и f1 работают просто отлично, каждый выводит свой тип
  • obj2 приводит Predicate к Object - глупо, но верно
  • f2 терпит неудачу во время выполнения - вы не можете привести Predicate к Function, это больше не вывод
  • f3 работает - вы вызываете метод предиката test, который определяется его лямбда
  • p2 не компилируется - Integer не имеет метода isEmpty
  • p3 тоже не компилируется - нет статического метода String::isEmpty с аргументом Integer

Я надеюсь, что это поможет лучше понять, как работает вывод типа.

75
Vlasec

Опираясь на ответы других и личный опыт:

Predicate<String> blank = String::isEmpty;
content.stream()
       .filter(blank.negate())
43
Jose Alban

Другой вариант заключается в использовании лямбда-приведения в однозначных контекстах в одном классе:

public static class Lambdas {
    public static <T> Predicate<T> as(Predicate<T> predicate){
        return predicate;
    }

    public static <T> Consumer<T> as(Consumer<T> consumer){
        return consumer;
    }

    public static <T> Supplier<T> as(Supplier<T> supplier){
        return supplier;
    }

    public static <T, R> Function<T, R> as(Function<T, R> function){
        return function;
    }

}

... а затем статический импорт служебного класса:

stream.filter(as(String::isEmpty).negate())
16
Askar Kalykov

Не должно Predicate#negate быть тем, что вы ищете?

11
Marco13

В этом случае вы можете использовать org.Apache.commons.lang3.StringUtils и сделать

int nonEmptyStrings = s.filter(StringUtils::isNotEmpty).count();
8
outofBounds

Вы можете использовать Предикаты из Коллекции Eclipse

MutableList<String> strings = Lists.mutable.empty();
int nonEmptyStrings = strings.count(Predicates.not(String::isEmpty));

Если вы не можете изменить строки из List:

List<String> strings = new ArrayList<>();
int nonEmptyStrings = ListAdapter.adapt(strings).count(Predicates.not(String::isEmpty));

Если вам нужно только отрицание String.isEmpty(), вы также можете использовать StringPredicates.notEmpty().

Примечание: я участвую в коллекциях Eclipse.

4
Nikhil Nanivadekar

Я написал полный служебный класс (вдохновленный предложением Аскара), который может взять лямбда-выражение Java 8 и превратить их (если применимо) в любой типизированный стандартный лямбда-код Java 8, определенный в пакете Java.util.function. Вы можете, например, сделать:

  • asPredicate(String::isEmpty).negate()
  • asBiPredicate(String::equals).negate()

Поскольку было бы много неясностей, если бы все статические методы были бы названы просто as(), я решил вызвать метод «as», за которым следует возвращаемый тип. Это дает нам полный контроль над лямбда-интерпретацией. Ниже приведена первая часть (несколько большого) служебного класса, раскрывающая используемый шаблон. 

Посмотрите на полный класс здесь (в Gist).

public class FunctionCastUtil {

    public static <T, U> BiConsumer<T, U> asBiConsumer(BiConsumer<T, U> biConsumer) {
        return biConsumer;
    }

    public static <T, U, R> BiFunction<T, U, R> asBiFunction(BiFunction<T, U, R> biFunction) {
        return biFunction;
    }

     public static <T> BinaryOperator<T> asBinaryOperator(BinaryOperator<T> binaryOperator) {
        return binaryOperator;
    }

    ... and so on...
}
3
Per-Åke Minborg

Вы можете сделать это, пока emptyStrings = s.filter(s->!s.isEmpty()).count();

0
Narsi Reddy Nallamilli

Если вы используете Spring Boot (2.0.0+), вы можете использовать:

import org.springframework.util.StringUtils;

...
.filter(StringUtils::hasLength)
...

Что делает: return (str != null && !str.isEmpty());

Таким образом, он будет иметь необходимый эффект отрицания для isEmpty

0
Gilad Peleg