Comment annuler un prédicat de référence de méthode


331

Dans Java 8, vous pouvez utiliser une référence de méthode pour filtrer un flux, par exemple:

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

Existe-t-il un moyen de créer une référence de méthode qui soit la négation d'une référence existante, c'est-à-dire quelque chose comme:

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

Je pouvais créer la notméthode comme ci-dessous mais je me demandais si le JDK offrait quelque chose de similaire.

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

6
JDK-8050818 couvre l'ajout d'une Predicate.not(Predicate)méthode statique . Mais ce problème est toujours ouvert, nous le verrons au plus tôt dans Java 12 (si jamais).
Stefan Zobel

1
Il semble que cette réponse pourrait également être la solution ultime adaptée dans JDK / 11.
Naman

2
Je voudrais vraiment voir une syntaxe de référence de méthode spéciale pour ce cas: s.filter (String ::! IsEmpty)
Mike Twain

Réponses:


179

Predicate.not( … )

propose une nouvelle méthode Predicate # not

Vous pouvez donc annuler la référence de la méthode:

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

214

Je prévois d'importer de manière statique les éléments suivants pour permettre à la référence de méthode d'être utilisée en ligne:

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

par exemple

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

Mise à jour : à partir de Java-11, le JDK propose également une solution similaire intégrée .


9
@SaintHill mais ensuite vous devez l'écrire, en donnant un nom au paramètre
flup



150

Il existe un moyen de composer une référence de méthode qui est l'opposé d'une référence de méthode actuelle. Voir la réponse de @ vlasec ci-dessous qui montre comment en exprimant explicitement la référence de méthode en a Predicatepuis en la convertissant à l'aide de la negatefonction. C'est une façon parmi quelques autres façons pas trop gênantes de le faire.

Le contraire:

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

est-ce:

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

ou ca:

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

Personnellement, je préfère la technique ultérieure parce que je trouve plus clair à lire it -> !it.isEmpty()qu'un long casting explicite verbeux et ensuite à nier.

On pourrait aussi faire un prédicat et le réutiliser:

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

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

Ou, si vous avez une collection ou un tableau, utilisez simplement une boucle for qui est simple, a moins de surcharge et * pourrait être ** plus rapide:

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

* Si vous voulez savoir ce qui est plus rapide, utilisez JMH http://openjdk.java.net/projects/code-tools/jmh et évitez le code de référence manuel à moins qu'il n'évite toutes les optimisations JVM - voir Java 8: performances des flux vs Collections

** Je reçois un flak pour avoir suggéré que la technique for-loop est plus rapide. Il élimine la création d'un flux, il élimine l'utilisation d'un autre appel de méthode (fonction négative pour le prédicat) et il élimine une liste / compteur d'accumulateurs temporaires. Donc, quelques choses qui sont enregistrées par la dernière construction qui pourraient le rendre plus rapide.

Je pense que c'est plus simple et plus agréable, même si ce n'est pas plus rapide. Si le travail nécessite un marteau et un clou, n'apportez pas de tronçonneuse et de colle! Je sais que certains d'entre vous s'y opposent.

wish-list: j'aimerais voir les Streamfonctions Java évoluer un peu maintenant que les utilisateurs Java les connaissent mieux. Par exemple, la méthode 'count' dans Stream pourrait accepter un Predicateafin que cela puisse être fait directement comme ceci:

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

or

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

Pourquoi dites-vous que c'est beaucoup plus rapide ?
José Andias

@ JoséAndias (1) Est-ce plus rapide ou 'beaucoup plus rapide'? (2) Si oui, pourquoi? Qu'avez-vous déterminé?
Le coordinateur du

3
Je vous demande de préciser "beaucoup plus vite à exécuter". Les questions: (1) Est-ce plus rapide ou «beaucoup plus rapide»? (2) Si oui, pourquoi? Qu'avez-vous déterminé? sont mieux répondu par vous, l'auteur de la déclaration. Je ne pense pas que ce soit plus rapide ou plus lent. Merci
José Andias

2
Ensuite, je vais jeter ceci à votre attention - Il élimine la création d'un flux, il élimine l'utilisation d'un autre appel de méthode (fonction négative pour le prédicat) et il élimine une liste / compteur d'accumulateurs temporaires. Donc, quelques choses qui sont enregistrées par la dernière construction. Je ne sais pas si c'est plus rapide ou combien plus rapide, mais je suppose que c'est 'beaucoup' plus rapide. Mais «beaucoup» est peut-être subjectif. Il est plus simple de coder plus tard que de faire des prédicats et des flux négatifs pour effectuer un comptage direct. Ma préférence .
Le coordinateur

4
negate () semble être une solution idéale. Dommage que ce ne soit pas statique comme Predicate.negate(String::isEmpty);sans le casting encombrant.
Joel Shemtov

92

Predicatea des méthodes and, oret negate.

Cependant, ce String::isEmptyn'est pas un Predicate, c'est juste un String -> Booleanlambda et il pourrait encore devenir n'importe quoi, par exemple Function<String, Boolean>. L'inférence de type est ce qui doit se produire en premier. La filterméthode déduit implicitement le type . Mais si vous le niez avant de le passer en argument, cela ne se produit plus. Comme @axtavt l'a mentionné, l' inférence explicite peut être utilisée comme une méthode laide:

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

Il existe d'autres moyens conseillés dans d'autres réponses, avec not méthode et lambda étant probablement les meilleures idées. Ceci conclut la section tl; dr .


Cependant, si vous souhaitez une compréhension plus approfondie de l'inférence de type lambda, je voudrais l'expliquer un peu plus en profondeur, à l'aide d'exemples. Regardez-les et essayez de comprendre ce qui se passe:

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 ne compile pas - les lambdas doivent déduire une interface fonctionnelle (= avec une méthode abstraite)
  • p1 et f1 fonctionnent très bien, chacun inférant un type différent
  • obj2 jette un PredicateàObject - idiot mais valide
  • f2 échoue au moment de l'exécution - vous ne pouvez pas lancer Predicatede conversion versFunction , il ne s'agit plus d'inférence
  • f3 fonctionne - vous appelez la méthode du prédicat test qui est définie par son lambda
  • p2 ne compile pas - Integern'a pasisEmpty méthode
  • p3 ne compile pas non plus - il n'y a pas de String::isEmptyméthode statique avec Integerargument

J'espère que cela vous aidera à mieux comprendre comment fonctionne l'inférence de type.


46

S'appuyant sur les réponses et l'expérience personnelle des autres:

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

4
Intéressant - vous ne pouvez pas aligner la ::référence fonctionnelle comme on pourrait le souhaiter ( String::isEmpty.negate()), mais si vous attribuez d'abord à une variable (ou transtypez en Predicate<String>premier), cela fonctionne. Je pense que lambda w / !sera le plus lisible dans la plupart des cas, mais il est utile de savoir ce qui peut et ne peut pas être compilé.
Joshua Goldberg

2
@JoshuaGoldberg J'ai expliqué que dans ma réponse: La référence de méthode n'est pas un prédicat en soi. Ici, le casting est effectué par la variable.
Vlasec

17

Une autre option consiste à utiliser la conversion lambda dans des contextes non ambigus en une seule classe:

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

}

... puis importez statiquement la classe d'utilité:

stream.filter(as(String::isEmpty).negate())

1
Je suis en fait surpris que cela fonctionne - mais il semble que JDK favorise le prédicat <T> sur la fonction <T, Boolean>. Mais vous n'obtiendrez pas Lambdas pour lancer quoi que ce soit à Function <T, Boolean>.
Vlasec

Cela fonctionne pour String mais pas pour List: Error: (20, 39) java: la référence à as est ambiguë à la fois la méthode <T> as (java.util.function.Consumer <T>) dans com.strands.sbs.function. Lambdas et méthode <T, R> as (java.util.function.Function <T, R>) dans com.strands.sbs.function.Lambdas match
Daniel Pinyol

Daniel, cela peut arriver si vous essayez d'utiliser une méthode surchargée :)
Askar Kalykov

Maintenant que je comprends l'inférence de type beaucoup mieux qu'initialement, je comprends comment cela fonctionne. Fondamentalement, il trouve simplement la seule option qui fonctionne. Ça a l'air intéressant, je ne sais pas s'il y a un meilleur nom qui ne cause pas de passe-partout.
Vlasec

12

Ça ne devrait pas Predicate#negateêtre ce que vous cherchez?


Vous devez obtenir une Predicatepremière.
Sotirios Delimanolis

21
Vous devez casting String::isEmpty()à Predicate<String>avant - il est très laid.
axtavt le

3
@assylias Utilisez comme Predicate<String> p = (Predicate<String>) String::isEmpty;et p.negate().
Sotirios Delimanolis

8
@SotiriosDelimanolis Je sais, mais cela va à l'encontre du but - je préfère écrire s -> !s.isEmpty()dans ce cas!
assylias

@assylias: Oui, je crois que c'est en fait l'idée; que le simple fait d'écrire la longue lambda est le repli prévu.
Louis Wasserman

8

Dans ce cas , u pourrait utiliser org.apache.commons.lang3.StringUtilset faire

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

6
Non. La question est de savoir comment annuler toute référence de méthode et prend String::isEmptycomme exemple. Ce sont toujours des informations pertinentes si vous avez ce cas d'utilisation, mais s'il ne répond qu'au cas d'utilisation de String, il ne doit pas être accepté.
Anthony Drogon

4

J'ai écrit une classe utilitaire complète (inspirée de la proposition d'Askar) qui peut prendre l'expression lambda Java 8 et les transformer (le cas échéant) en n'importe quel lambda Java 8 standard défini défini dans le package java.util.function. Vous pouvez par exemple faire:

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

Parce qu'il y aurait de nombreuses ambiguïtés si toutes les méthodes statiques étaient nommées juste as() , j'ai choisi d'appeler la méthode "as" suivie du type retourné. Cela nous donne un contrôle total sur l'interprétation lambda. Ci-dessous se trouve la première partie de la classe d'utilité (assez large) révélant le modèle utilisé.

Jetez un oeil à la classe complète ici (à l'essentiel).

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

Vous pouvez utiliser les prédicats des collections Eclipse

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

Si vous ne pouvez pas modifier les chaînes de List:

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

Si vous avez seulement besoin d'une négation, String.isEmpty()vous pouvez également utiliser StringPredicates.notEmpty().

Remarque: je suis un contributeur aux collections Eclipse.



0

Si vous utilisez Spring Boot (2.0.0+), vous pouvez utiliser:

import org.springframework.util.StringUtils;

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

Qui fait: return (str != null && !str.isEmpty());

Il aura donc l'effet de négation requis pour isEmpty

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.