Espressione lambda e metodo generico


111

Supponiamo che io abbia un'interfaccia generica:

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

E un metodo sort:

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

Posso invocare questo metodo e passare un'espressione lambda come argomento:

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

Funzionerà bene.

Ma ora se rendo l'interfaccia non generica e il metodo generico:

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) {
}

E poi invoca questo come:

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

Non si compila. Mostra un errore nell'espressione lambda che dice:

"Il metodo di destinazione è generico"

OK, quando l'ho compilato usando javac, mostra il seguente errore:

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

Da questo messaggio di errore, sembra che il compilatore non sia in grado di dedurre gli argomenti del tipo. È così? Se sì, allora perché sta succedendo in questo modo?

Ho provato vari modi, cercato su Internet. Poi ho trovato questo articolo JavaCodeGeeks , che mostra un modo, quindi ho provato:

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

che ancora una volta non funziona, contrariamente a quanto l'articolo afferma che funziona. Potrebbe essere possibile che funzionasse in alcune build iniziali.

Quindi la mia domanda è: esiste un modo per creare un'espressione lambda per un metodo generico? Posso farlo usando un metodo di riferimento, creando un metodo:

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

in qualche classe dite SOe passatelo come:

sort(list, SO::compare);

Risposte:


117

Non è possibile utilizzare un'espressione lambda per un'interfaccia funzionale , se il metodo nell'interfaccia funzionale ha parametri di tipo . Vedere la sezione §15.27.3 in JLS8 :

Un'espressione lambda è compatibile [..] con un tipo di destinazione T se T è un tipo di interfaccia funzionale (§9.8) e l'espressione è congruente con il tipo di funzione di [..] T. [..] Un'espressione lambda è congruente con un tipo di funzione se tutte le seguenti condizioni sono vere:

  • Il tipo di funzione non ha parametri di tipo .
  • [..]

47
Tuttavia, questa restrizione non si applica ai riferimenti a metodi generici. È possibile utilizzare un metodo di riferimento a un metodo generico con un'interfaccia funzionale generica.
Brian Goetz

17
Sono sicuro che ci sia una buona ragione per questa restrizione. Che cos'è?
Sandro

6
@Sandro: semplicemente non esiste una sintassi per dichiarare i parametri di tipo per un'espressione lambda. E una tale sintassi sarebbe molto complicata. Tieni presente che il parser deve ancora essere in grado di distinguere tale espressione lambda con parametri di tipo oltre ad altri costrutti Java legali. Quindi devi ricorrere a riferimenti di metodo. Il metodo di destinazione può dichiarare parametri di tipo utilizzando una sintassi stabilita.
Holger

2
@Holger ancora, dove i parametri di tipo possono essere dedotti automaticamente, il compilatore potrebbe decifrare un tipo di cattura come fa quando dichiari ad esempio Set <?> Ed esegui controlli di tipo con quei tipi catturati. Certo, questo rende impossibile assegnarli come parametri di tipo nel corpo, ma se ne avessi bisogno, ricorrere a riferimenti al metodo è una buona alternativa
WorldSEnder

17

Utilizzando il metodo di riferimento, ho trovato un altro modo per passare l'argomento:

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

3

Basta indicare al compilatore la versione corretta del comparatore generico con (Comparator<String>)

Quindi la risposta sarà

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


2
incompatible types: java.util.Comparator<java.lang.String> cannot be converted to MyComparablee MyComparablenon è generico (nessun tipo), quindi (MyComparable<String>)non funzionerebbe neanche
user85421

1
Non so come stai digitando il codice @CarlosHeuberger, ma per me funziona molto bene, questo è quello che stavo cercando.
Ivan Perales M.

@IvanPeralesM. dopo 2 mesi ... ho copiato e incollato il tuo codice e copiato e incollato sopra la riga sopra la tua riga di ordinamento - proprio questo, come qui: ideone.com/YNwBbF ! Sei sicuro di aver digitato esattamente il codice sopra? usando Compartor?
user85421

No, non lo faccio, utilizzo l'idea alla base della risposta, per lanciare il funzionale per dire alla compilazione di che tipo è e ha funzionato.
Ivan Perales M.

@IvanPeralesM. bene, allora qual è il tuo problema con la mia "digitazione"? La risposta, così come viene pubblicata, non funziona.
user85421

0

Intendi qualcosa del genere?:

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

Di che tipo è questo lambda? Non è possibile esprimerlo in Java e quindi non è possibile comporre questa espressione in un'applicazione di funzione e le espressioni devono essere componibili.

Affinché questa necessità funzioni, è necessario il supporto per i tipi Rank2 in Java.

I metodi possono essere generici ma quindi non è possibile utilizzarli come espressioni. Tuttavia, possono essere ridotti all'espressione lambda specializzando tutti i tipi generici necessari prima di poterli passare:ClassName::<TypeName>methodName


1
"Of what type is this lambda? You couldn't express that in Java..."Il tipo verrebbe dedotto utilizzando il contesto, proprio come con qualsiasi altro lambda. Il tipo di lambda non è esplicitamente espresso nel lambda stesso.
Kröw
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.