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

В 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); }

Ответ 1

Predicate.not( … )

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

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

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

Ответ 2

Я планирую статический импорт следующего, чтобы позволить использование ссылки на метод 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 может предлагать аналогичное решение.

Ответ 3

Существует способ составить ссылку на метод, которая противоположна текущей ссылке метода. См. Ниже приведенный ниже ответ @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: производительность потоков и коллекций

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

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

Список пожеланий: Я хотел бы, чтобы функции 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());

Ответ 4

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

Однако String::isEmpty не является Predicate, это просто String → Boolean лямбда- 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 использует Object Predicate - глупо, но верно
  • f2 не выполняется во время выполнения - вы не можете привести Predicate к Function, это больше не вывод
  • f3 работает - вы вызываете метод предиката test который определяется его лямбда-выражением
  • p2 не компилируется - у Integer нет метода isEmpty
  • p3 тоже не компилируется - нет статического метода String::isEmpty с аргументом Integer

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

Ответ 5

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

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

Ответ 6

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

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())

Ответ 7

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

Ответ 8

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

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

Ответ 9

Вы можете использовать Predicates из Коллекции 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.

Ответ 10

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

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

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

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

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...
}

Ответ 11

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

import org.springframework.util.StringUtils;

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

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

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

Ответ 12

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