Expresión Lambda y método genérico

111

Supongamos que tengo una interfaz genérica:

interface MyComparable<T extends Comparable<T>>  {
    public int compare(T obj1, T obj2);
}

Y un método sort:

public static <T extends Comparable<T>> 
       void sort(List<T> list, MyComparable<T> comp) {
    // sort the list
}

Puedo invocar este método y pasar una expresión lambda como argumento:

List<String> list = Arrays.asList("a", "b", "c");
sort(list, (a, b) -> a.compareTo(b));

Eso funcionará bien.

Pero ahora, si hago la interfaz no genérica y el método genérico:

interface MyComparable {
    public <T extends Comparable<T>> int compare(T obj1, T obj2);
}

public static <T extends Comparable<T>> 
       void sort(List<T> list, MyComparable comp) {
}

Y luego invoca esto como:

List<String> list = Arrays.asList("a", "b", "c");
sort(list, (a, b) -> a.compareTo(b));

No se compila. Muestra un error en la expresión lambda que dice:

"El método de destino es genérico"

OK, cuando lo compilé usando javac, muestra el siguiente error:

SO.java:20: error: incompatible types: cannot infer type-variable(s) T#1
        sort(list, (a, b) -> a.compareTo(b));
            ^
    (argument mismatch; invalid functional descriptor for lambda expression
      method <T#2>(T#2,T#2)int in interface MyComparable is generic)
  where T#1,T#2 are type-variables:
    T#1 extends Comparable<T#1> declared in method <T#1>sort(List<T#1>,MyComparable)
    T#2 extends Comparable<T#2> declared in method <T#2>compare(T#2,T#2)
1 error

A partir de este mensaje de error, parece que el compilador no puede inferir los argumentos de tipo. ¿Es ese el caso? Si es así, ¿por qué está sucediendo así?

Probé de varias formas, busqué en Internet. Luego encontré este artículo de JavaCodeGeeks , que muestra una forma, así que probé:

sort(list, <T extends Comparable<T>>(a, b) -> a.compareTo(b));

que de nuevo no funciona, al contrario de lo que ese artículo afirma que funciona. Podría ser posible que solía funcionar en algunas versiones iniciales.

Entonces mi pregunta es: ¿hay alguna forma de crear una expresión lambda para un método genérico? Sin embargo, puedo hacer esto usando una referencia de método, creando un método:

public static <T extends Comparable<T>> int compare(T obj1, T obj2) {
    return obj1.compareTo(obj2);
}

en alguna clase decir SO, y pasarlo como:

sort(list, SO::compare);
Rohit Jain
fuente

Respuestas:

117

No puede usar una expresión lambda para una interfaz funcional , si el método en la interfaz funcional tiene parámetros de tipo . Consulte la sección §15.27.3 en JLS8 :

Una expresión lambda es compatible [..] con un tipo de destino T si T es un tipo de interfaz funcional (§9.8) y la expresión es congruente con el tipo de función de [..] T. [..] Una expresión lambda es congruente con un tipo de función si se cumplen todas las condiciones siguientes:

  • El tipo de función no tiene parámetros de tipo .
  • [..]
nosid
fuente
47
Sin embargo, esta restricción no se aplica a las referencias de métodos a métodos genéricos. Puede utilizar una referencia de método a un método genérico con una interfaz funcional genérica.
Brian Goetz
17
Estoy seguro de que hay una buena razón para esta restricción. ¿Qué es?
Sandro
6
@Sandro: simplemente no hay sintaxis para declarar parámetros de tipo para una expresión lambda. Y esa sintaxis sería muy complicada. Tenga en cuenta que el analizador aún debe poder diferenciar una expresión lambda con parámetros de tipo de otras construcciones legales de Java. Por lo tanto, debe recurrir a referencias de métodos. El método de destino puede declarar parámetros de tipo utilizando una sintaxis establecida.
Holger
2
@Holger aún, donde los parámetros de tipo se pueden deducir automáticamente, el compilador podría descifrar un tipo de captura como lo hace cuando declara, por ejemplo, Set <?> Y realiza comprobaciones de tipo con esos tipos capturados. Claro, eso hace que sea imposible darlos como parámetros de tipo en el cuerpo, pero si lo necesita, recurrir a referencias de método es una buena alternativa
WorldSEnder
17

Usando la referencia del método, encontré otra forma de pasar el argumento:

List<String> list = Arrays.asList("a", "b", "c");        
sort(list, Comparable::<String>compareTo);
Andrey
fuente
3

Simplemente señale al compilador la versión adecuada del Comparador genérico con (Comparator<String>)

Entonces la respuesta será

sort(list, (Comparator<String>)(a, b) -> a.compareTo(b));

Aleч
fuente
2
incompatible types: java.util.Comparator<java.lang.String> cannot be converted to MyComparabley MyComparableno es genérico (sin tipo), por (MyComparable<String>)lo que tampoco funcionaría
user85421
1
No sé cómo estás escribiendo el código @CarlosHeuberger, pero me funciona muy bien, esto es lo que estaba buscando.
Ivan Perales M.
@IvanPeralesM. después de 2 meses ... copié y pegué su código y copié y pegué la línea de arriba sobre su línea de clasificación, solo eso, como aquí: ideone.com/YNwBbF ! ¿Estás seguro de que escribiste el código anterior exactamente? usando Compartor?
user85421
No, no lo hago, utilizo la idea detrás de la respuesta, para emitir el funcional para decirle a la compilación de qué tipo es y funcionó.
Ivan Perales M.
@IvanPeralesM. bueno, entonces ¿cuál es tu problema con mi "mecanografía"? La respuesta, tal como está publicada, no funciona.
user85421
0

¿Te refieres a algo como esto ?:

<T,S>(T t, S s)->...

¿De qué tipo es esta lambda? No podría expresar eso en Java y, por lo tanto, no puede componer esta expresión en una aplicación de función y las expresiones deben ser componibles.

Para que esto funcione, necesitaría soporte para los tipos Rank2 en Java.

Se permite que los métodos sean genéricos pero, por lo tanto, no se pueden usar como expresiones. Sin embargo, se pueden reducir a una expresión lambda especializando todos los tipos genéricos necesarios antes de poder pasarlos:ClassName::<TypeName>methodName

mosca
fuente
1
"Of what type is this lambda? You couldn't express that in Java..."El tipo se inferiría utilizando el contexto, al igual que con cualquier otro lambda. El tipo de lambda no se expresa explícitamente en el propio lambda.
Kröw