web-dev-qa-db-fra.com

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

En 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 est la négation d’un existant, c’est-à-dire quelque chose comme:

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

Je pourrais créer la méthode not 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); }
248
assylias

Java-11 propose une nouvelle méthode Predicate # not

Vous pouvez donc nier la référence à la méthode:

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

J'envisage d'importer de manière statique les éléments suivants pour permettre à la référence de la 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: - Le JDK/11 pourrait également proposer un solution similaire .

176
davidillsley

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 convertissant explicitement la référence de la méthode en une variable Predicate, puis en la convertissant à l'aide de la fonction negate. C’est un moyen parmi d’autres qui ne sont pas trop gênants.

Le contraire de ceci:

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 postérieure parce que je trouve plus facile de lire it -> !it.isEmpty() qu’une longue distribution explicite et verbeuse, puis de 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 * peut ê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 de la main, à moins que cela n’évite toutes les optimisations JVM - voir Java 8: performances de Streams vs Collections

** On me reproche d'avoir suggéré que la technique de la boucle for est plus rapide. Il élimine la création d'un flux, élimine l'utilisation d'un autre appel de méthode (fonction négative pour le prédicat) et élimine une liste/un compteur d'accumulateur temporaire. Donc, quelques éléments sauvegardés par la dernière construction 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 exige un marteau et un clou, n'apportez pas de scie à chaîne et de colle! Je sais que certains d’entre vous sont en désaccord avec cela.

wish-list: j'aimerais voir les fonctions Java Stream évoluer un peu maintenant que les utilisateurs de Java les connaissent mieux. Par exemple, la méthode 'count' dans Stream pourrait accepter une Predicate afin que cela puisse se faire directement comme ceci:

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 a les méthodes and, or et negate.

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

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

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


Cependant, si vous souhaitez mieux comprendre l'inférence de type lambda, j'aimerais 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 parfaitement, chacun induisant un type différent
  • obj2 lance une Predicate à Object - idiot mais valide
  • f2 échoue au moment de l'exécution - vous ne pouvez pas convertir Predicate en Function, 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 - Integer n'a pas de méthode isEmpty
  • p3 ne compile pas non plus - il n'y a pas de méthode statique String::isEmpty avec l'argument Integer

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

75
Vlasec

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

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

Une autre option consiste à utiliser le casting lambda dans une classe dans des contextes non ambigus:

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 de manière statique la classe utilitaire:

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

Ne devrait pas Predicate#negate être ce que vous cherchez?

11
Marco13

Dans ce cas, vous pouvez utiliser le org.Apache.commons.lang3.StringUtilset faire

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

Vous pouvez utiliser Prédicats from Collections Eclipse

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

Si vous ne pouvez pas changer 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 de String.isEmpty(), vous pouvez également utiliser StringPredicates.notEmpty().

Remarque: je contribue aux collections Eclipse.

4
Nikhil Nanivadekar

Vous pouvez accomplir cela aussi longtemps emptyStrings = s.filter(s->!s.isEmpty()).count();

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

Donc, cela aura l'effet de négation requis pour isEmpty

0
Gilad Peleg