Як заперечувати присудок довідки про метод


330

У 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)
Майк Твен

Відповіді:



214

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

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



149

Існує спосіб скласти посилання на метод, протилежний поточному посиланню на метод. Див. Відповідь @ 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-loop швидша. Це виключає створення потоку, виключає використання іншого виклику методу (негативна функція для предиката), а також усуває тимчасовий список акумуляторів / лічильник. Отже, кілька речей, які зберігаються останньою конструкцією, які можуть зробити це швидше.

Я думаю, що це простіше і приємніше, хоча навіть і не швидше. Якщо робота вимагає молотка та цвяха, не приносьте бензопилу та клей! Я знаю, що деякі з вас вирішують це.

список побажань: Я хотів би, щоб Streamфункції Java трохи розвивалися зараз, коли користувачі Java більше знайомі з ними. Наприклад, метод 'count' в потоці може прийняти 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);без громіздкого кастингу.
Джоель Шемтов

91

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

Я сподіваюся, що це допоможе отримати більше розуміння того, як працює тип виступу.


45

Спираючись на відповіді та особистий досвід інших:

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

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

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> над функцією <T, Boolean>. Але ви не змусите Лямбда віддати щось на функцію <T, булева>.
Власек

Це працює для String, але не для List: Помилка: (20, 39) java: посилання на як є неоднозначним і методом <T> як (java.util.function.Consumer <T>) в com.strands.sbs.function. Лямбдас і метод <T, R> as (java.util.function.Function <T, R>) в com.strands.sbs.function.Lambdas match
Daniel Pinyol

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

Тепер, коли я розумію висновок про тип набагато краще, ніж спочатку, я розумію, як це працює. В основному, він просто знаходить єдиний варіант, який працює. Виглядає цікаво, я просто не знаю, чи є якась краща назва, яка не спричиняє котельну плиту.
Власек

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.