Cómo negar un predicado de referencia de método


331

En Java 8, puede usar una referencia de método para filtrar una secuencia, por ejemplo:

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

¿Hay alguna manera de crear una referencia de método que sea la negación de una existente, es decir, algo como:

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

Podría crear el notmétodo como a continuación, pero me preguntaba si el JDK ofrecía algo similar.

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

66
JDK-8050818 cubre la adición de un Predicate.not(Predicate)método estático . Pero ese problema aún está abierto, así que lo veremos lo antes posible en Java 12 (si es que lo ha hecho).
Stefan Zobel

1
Parece que esta respuesta también podría ser la solución definitiva adaptada en JDK / 11.
Naman

2
Realmente me gustaría ver una sintaxis de referencia de método especial para este caso: s.filter (String ::! IsEmpty)
Mike Twain

Respuestas:


178

Predicate.not( … )

ofrece un nuevo método Predicate # not

Entonces puede negar la referencia del método:

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

214

Estoy planeando importar estáticamente lo siguiente para permitir que la referencia del método se use en línea:

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

p.ej

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

Actualización : a partir de Java-11, el JDK también ofrece una solución similar incorporada.


99
@SaintHill, pero luego debe escribirlo, dándole un nombre al parámetro
flup



150

Hay una manera de componer una referencia de método que es lo contrario de una referencia de método actual. Vea la respuesta de @ vlasec a continuación que muestra cómo al Predicateconvertir explícitamente la referencia del método a a y luego convertirla utilizando la negatefunción. Esa es una forma entre algunas otras formas no demasiado problemáticas de hacerlo.

Lo contrario de esto:

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

Es esto:

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

o esto:

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

Personalmente, prefiero la técnica posterior porque me resulta más claro de leer it -> !it.isEmpty()que un reparto explícito largo y detallado y luego negarlo.

También se podría hacer un predicado y reutilizarlo:

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

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

O, si tiene una colección o matriz, simplemente use un bucle for que sea simple, tenga menos sobrecarga y * podría ser ** más rápido:

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

* Si desea saber qué es más rápido, utilice JMH http://openjdk.java.net/projects/code-tools/jmh y evite el código de referencia manual a menos que evite todas las optimizaciones de JVM; consulte Java 8: rendimiento de Streams vs Colecciones

** Estoy recibiendo críticas por sugerir que la técnica for-loop es más rápida. Elimina la creación de una secuencia, elimina el uso de otro método de llamada (función negativa para predicado) y elimina una lista / contador de acumulador temporal. Entonces, algunas cosas que se guardan en la última construcción pueden hacerlo más rápido.

Sin embargo, creo que es más simple y agradable, incluso si no es más rápido. Si el trabajo requiere un martillo y un clavo, ¡no traiga una motosierra y pegamento! Sé que algunos de ustedes están en desacuerdo con eso.

lista de deseos: Me gustaría ver que las Streamfunciones de Java evolucionen un poco ahora que los usuarios de Java están más familiarizados con ellas. Por ejemplo, el método 'contar' en Stream podría aceptar a Predicatepara que esto se pueda hacer directamente así:

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

or

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

¿Por qué dices que es mucho más rápido ?
José Andias

@ JoséAndias (1) ¿Es más rápido o 'mucho más rápido'? (2) Si es así, ¿por qué? ¿Qué has determinado?
El coordinador del

3
Le pido que explique "mucho más rápido para correr". Las preguntas: (1) ¿Es más rápido o 'mucho más rápido'? (2) Si es así, ¿por qué? ¿Qué has determinado? son mejor respondidas por usted, el autor de la declaración. No estoy considerando que sea más rápido o más lento. Gracias
José Andias

2
Luego lo descartaré para su consideración: elimina la creación de una secuencia, elimina el uso de otro método de llamada (función negativa para predicado) y elimina una lista / contador de acumulador temporal. Entonces, algunas cosas que se guardan en la última construcción. No estoy seguro de si es más rápido o cuánto más rápido, pero supongo que es "mucho" más rápido. Pero tal vez "mucho" es subjetivo. Es más fácil codificar más tarde que hacer predicados y secuencias negativas para hacer un recuento directo. Mi preferencia .
El coordinador el

44
negate () parece una solución ideal. Lástima que no sea estático como Predicate.negate(String::isEmpty);sin el engorroso casting.
Joel Shemtov

92

Predicatetiene métodos and, orynegate .

Sin embargo, String::isEmptyno es una Predicate, es solo una String -> Booleanlambda y aún podría convertirse en cualquier cosa, por ejemplo Function<String, Boolean>. La inferencia de tipos es lo que debe suceder primero. El filtermétodo infiere tipo implícitamente . Pero si lo niegas antes de pasarlo como argumento, ya no sucede. Como @axtavt mencionó, la inferencia explícita se puede usar como una forma fea:

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

Hay otras formas recomendadas en otras respuestas, siendo el notmétodo estático y el lambda las mejores ideas. Esto concluye la sección tl; dr .


Sin embargo, si desea una comprensión más profunda de la inferencia de tipo lambda, me gustaría explicarlo un poco más en profundidad, usando ejemplos. Mire estos e intente descubrir qué sucede:

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 no se compila: las lambdas deben inferir una interfaz funcional (= con un método abstracto)
  • p1 y f1 funcionan bien, cada uno infiriendo un tipo diferente
  • obj2 lanza un PredicateaObject - tonto pero válida
  • f2 falla en tiempo de ejecución: no se puede enviar PredicateaFunction , ya no se trata de inferencia
  • f3 funciona: llamas al método del predicado test definido por su lambda
  • p2 no se compila, Integerno tieneisEmpty método
  • p3 tampoco se compila: no hay ningún String::isEmptymétodo estático con Integerargumento

Espero que esto ayude a obtener más información sobre cómo funciona la inferencia de tipos.


46

Aprovechando las respuestas y la experiencia personal de otros:

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

44
Interesante: no puede alinear la ::referencia funcional como se desearía ( String::isEmpty.negate()), pero si asigna a una variable primero (o se convierte a Predicate<String>primera), eso funciona. Creo que lambda w / !será más legible en la mayoría de los casos, pero es útil saber qué se puede y qué no se puede compilar.
Joshua Goldberg

2
@ JoshuaGoldberg Le expliqué eso en mi respuesta: la referencia del método no es un predicado por sí sola. Aquí, el casting se realiza por la variable.
Vlasec

17

Otra opción es utilizar la conversión lambda en contextos no ambiguos en una clase:

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

}

... y luego estática importar la clase de utilidad:

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

1
De hecho, me sorprende que esto funcione, pero parece que JDK favorece Predicate <T> sobre Function <T, Boolean>. Pero no conseguirá que Lambdas eche nada a la Función <T, Boolean>.
Vlasec

Funciona para String pero no para List: Error: (20, 39) java: referencia a como es ambiguo tanto el método <T> como (java.util.function.Consumer <T>) en com.strands.sbs.function. Lambdas y método <T, R> como (java.util.function.Function <T, R>) en com.strands.sbs.function.Lambdas match
Daniel Pinyol

Daniel, podría suceder si estás tratando de usar un método sobrecargado :)
Askar Kalykov

Ahora que entiendo la inferencia de tipos mucho mejor que originalmente, entiendo cómo funciona. Básicamente, solo encuentra la única opción que funciona. Parece interesante, simplemente no sé si hay algún nombre mejor que no cause repeticiones.
Vlasec

12

¿No debería Predicate#negateser lo que estás buscando?


Necesitas un Predicateprimero.
Sotirios Delimanolis

21
Tienes que elenco String::isEmpty()de Predicate<String>antes - es muy feo.
axtavt

3
@assylias Usar como Predicate<String> p = (Predicate<String>) String::isEmpty;y p.negate().
Sotirios Delimanolis

8
@SotiriosDelimanolis Lo sé, pero eso frustra el propósito: ¡prefiero escribir s -> !s.isEmpty()en ese caso!
Assylias

@assylias: Sí, creo que esa es realmente la idea; que solo escribir la lambda a mano es la alternativa prevista.
Louis Wasserman

8

En este caso, podría usar el org.apache.commons.lang3.StringUtilsy hacer

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

66
No. La pregunta es cómo negar cualquier referencia de método y se toma String::isEmptycomo ejemplo. Todavía es información relevante si tiene este caso de uso, pero si solo responde al caso de uso de String, entonces no debería aceptarse.
Anthony Drogon el

4

He escrito una clase de utilidad completa (inspirada en la propuesta de Askar) que puede tomar la expresión lambda de Java 8 y convertirla (si corresponde) en cualquier lambda Java 8 estándar escrita en el paquete java.util.function. Por ejemplo, puedes hacer:

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

Debido a que habría numerosas ambigüedades si todos los métodos estáticos se nombraran solo as(), opté por llamar al método "como" seguido del tipo devuelto. Esto nos da el control total de la interpretación lambda. A continuación se muestra la primera parte de la clase de utilidad (algo grande) que revela el patrón utilizado.

Echa un vistazo a la clase completa aquí (en esencia).

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

Puede usar predicados de colecciones de Eclipse

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

Si no puede cambiar las cadenas de List:

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

Si solo necesita una negación String.isEmpty(), también puede usarlo StringPredicates.notEmpty().

Nota: Soy colaborador de Eclipse Collections.



0

Si está utilizando Spring Boot (2.0.0+) puede usar:

import org.springframework.util.StringUtils;

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

Que hace: return (str != null && !str.isEmpty());

Por lo tanto, tendrá el efecto de negación requerido para isEmpty

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.