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


331

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

6
JDK-8050818 описывает добавление статического Predicate.not(Predicate)метода. Но эта проблема все еще открыта, поэтому мы увидим это в ближайшее время в Java 12 (если вообще когда-либо).
Стефан Зобель

1
Похоже, что этот ответ может быть лучшим решением, адаптированным и в JDK / 11.
Наман

2
Я действительно хотел бы видеть специальный синтаксис ссылки на метод для этого случая: s.filter (String ::! IsEmpty)
Майк Твен

Ответы:


179

Predicate.not( … )

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

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

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

214

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

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

например

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

Обновление : начиная с Java-11, JDK также предлагает аналогичное встроенное решение .


9
@SaintHill, но затем вы должны записать его, присвоив параметру имя
flup



150

Существует способ составить ссылку на метод, противоположную текущей ссылке на метод. См. Ответ @ vlasec ниже, который показывает, как явным образом привести ссылку на метод к a, 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 работает быстрее. Это исключает создание потока, исключает использование другого вызова метода (отрицательная функция для предиката) и устраняет временный список / счетчик аккумулятора. Итак, несколько вещей, которые сохраняются последней конструкцией, могут сделать ее быстрее.

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

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

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

or

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

Почему вы говорите, что это намного быстрее ?
Хосе Андиас

@ JoséAndias (1) Это быстрее или «намного быстрее»? (2) Если так, то почему? Что вы определили?
Координатор

3
Я прошу вас уточнить «намного быстрее бежать». Вопросы: (1) Это быстрее или «намного быстрее»? (2) Если так, то почему? Что вы определили? лучше ответят вы, автор заявления. Я не считаю, что это будет быстрее или медленнее. Спасибо
Хосе Андиас

2
Затем я расскажу об этом на ваше рассмотрение. Это исключает создание потока, исключает использование другого вызова метода (отрицательная функция для предиката) и устраняет временный список / счетчик аккумулятора. Итак, несколько вещей, которые сохраняются последней конструкцией. Я не уверен, что это быстрее или насколько быстрее, но я предполагаю, что это «намного» быстрее. Но, возможно, «много» субъективно. Проще написать код позже, чем создавать отрицательные предикаты и потоки, чтобы выполнить прямой подсчет. Мои предпочтения.
Координатор

4
Negate () кажется идеальным решением. Жаль, что это не статично, как Predicate.negate(String::isEmpty);без громоздкого кастинга.
Джоэл Шемтов

92

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аргументом

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


46

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

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

4
Интересно - вы не можете встроить функциональную ::ссылку, как хотелось бы ( String::isEmpty.negate()), но если вы присваиваете переменную первым (или приводите к Predicate<String>первому), это работает. Я думаю, что лямбда-ж / !будет наиболее читаемой в большинстве случаев, но полезно знать, что можно, а что нельзя компилировать.
Джошуа Голдберг

2
@JoshuaGoldberg Я объяснил это в своем ответе: ссылка на метод не является предикатом сама по себе. Здесь приведение производится переменной.
Власек

17

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

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

1
Я на самом деле удивлен, что это работает - но, похоже, JDK предпочитает Predicate <T> перед Function <T, Boolean>. Но вы не получите Lambdas, чтобы привести что-либо к Function <T, Boolean>.
Власек

Это работает для String, но не для List: Error: (20, 39) java: ссылка на as неоднозначна для обоих методов <T> as (java.util.function.Consumer <T>) в com.strands.sbs.function. Лямбды и метод <T, R> as (java.util.function.Function <T, R>) в сопоставлении com.strands.sbs.function.Lambdas
Даниэль Пиньоль,

Даниэль, это может произойти, если ты пытаешься использовать перегруженный метод :)
Аскар Калыков

Теперь, когда я понимаю вывод типов гораздо лучше, чем изначально, я понимаю, как он работает. По сути, он просто находит единственный вариант, который работает. Выглядит интересно, я просто не знаю, есть ли какое-нибудь лучшее имя, которое не вызывает суть.
Власек,

12

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


Вы должны получить Predicateпервый.
Сотириос Делиманолис

21
Вы должны гипс , String::isEmpty()чтобы Predicate<String>раньше - это очень некрасиво.
axtavt

3
@assylias Используйте как Predicate<String> p = (Predicate<String>) String::isEmpty;и p.negate().
Сотириос Делиманолис

8
@ SotiriosDelimanolis Я знаю, но это побеждает цель - я бы лучше написал s -> !s.isEmpty()в этом случае!
assylias

@assylias: Да, я считаю, что это на самом деле идея; то, что просто выписывание лямбда-лаунджа - это предполагаемый запасной вариант.
Луи Вассерман

8

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

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

6
Нет. Вопрос в том, как отрицать любую ссылку на метод, и String::isEmptyв качестве примера. Это все еще важная информация, если у вас есть этот вариант использования, но если он отвечает только на вариант использования String, то он не должен быть принят.
Энтони Дрогон,

4

Я написал полный служебный класс (вдохновленный предложением Аскара), который может взять лямбда-выражение Java 8 и превратить их (если применимо) в любой типизированный стандартный лямбда-код 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...
}

4

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



0

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

import org.springframework.util.StringUtils;

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

Который делает: return (str != null && !str.isEmpty());

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.