Expressão Lambda e método genérico

111

Suponha que eu tenha uma interface genérica:

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

E um método sort:

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

Posso invocar este método e passar uma expressão lambda como argumento:

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

Isso vai funcionar bem.

Mas agora, se eu tornar a interface não genérica e o 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) {
}

E então invoque isso como:

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

Não compila. Ele mostra um erro na expressão lambda dizendo:

"O método alvo é genérico"

Ok, quando compilei usando javac, ele mostra o seguinte erro:

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 dessa mensagem de erro, parece que o compilador não é capaz de inferir os argumentos de tipo. É esse o caso? Se sim, então por que está acontecendo assim?

Tentei de várias formas, pesquisei na internet. Então eu encontrei este artigo JavaCodeGeeks , que mostra um caminho, então tentei:

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

que novamente não funciona, ao contrário do que aquele artigo afirma que funciona. Pode ser que funcionasse em algumas compilações iniciais.

Portanto, minha pergunta é: Existe alguma maneira de criar uma expressão lambda para um método genérico? Posso fazer isso usando uma referência de método, porém, criando um método:

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

em alguma aula diga SOe passe como:

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

Respostas:

117

Você não pode usar uma expressão lambda para uma interface funcional , se o método na interface funcional tiver parâmetros de tipo . Consulte a seção §15.27.3 em JLS8 :

Uma expressão lambda é compatível [..] com um tipo de destino T se T for um tipo de interface funcional (§9.8) e a expressão for congruente com o tipo de função de [..] T. [..] Uma expressão lambda é congruente com um tipo de função se todas as opções a seguir forem verdadeiras:

  • O tipo de função não possui parâmetros de tipo .
  • [..]
nosid
fonte
47
No entanto, essa restrição não se aplica a referências de método a métodos genéricos. Você pode usar uma referência de método para um método genérico com uma interface funcional genérica.
Brian Goetz de
17
Tenho certeza de que há um bom motivo para essa restrição. O que é isso?
Sandro
6
@Sandro: simplesmente não há sintaxe para declarar parâmetros de tipo para uma expressão lambda. E essa sintaxe seria muito complicada. Lembre-se de que o analisador ainda deve ser capaz de diferenciar essa expressão lambda com parâmetros de tipo de outras construções Java legais. Portanto, você deve recorrer a referências de método. O método de destino pode declarar parâmetros de tipo usando uma sintaxe estabelecida.
Holger
2
@Holger ainda, onde os parâmetros de tipo podem ser auto-deduzidos, o compilador pode decifrar um tipo de captura como faz quando você declara, por exemplo, Set <?> E faz verificações de tipo com esses tipos capturados. Claro, isso torna impossível fornecê-los como parâmetros de tipo no corpo, mas se você precisar disso, recorrer a referências de método é uma boa alternativa
WorldSEnder
17

Usando a referência de método, encontrei outra maneira de passar o argumento:

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

Basta apontar para o compilador a versão adequada do Comparator genérico com (Comparator<String>)

Então a resposta será

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

Aleч
fonte
2
incompatible types: java.util.Comparator<java.lang.String> cannot be converted to MyComparablee MyComparablenão é genérico (nenhum tipo), portanto (MyComparable<String>)também não funcionaria
usuário85421
1
Não sei como você está digitando o código @CarlosHeuberger, mas funciona muito bem para mim, é isso que eu estava procurando.
Ivan Perales M.
@IvanPeralesM. após 2 meses ... copiei e colei seu código e copiei e colei acima da linha sobre sua linha de classificação - apenas isso, como aqui: ideone.com/YNwBbF ! Tem certeza de que digitou o código acima exatamente? usando Compartor?
user85421
Não, eu não uso a idéia por trás da resposta, para lançar o funcional para dizer ao compilador que tipo é e funcionou.
Ivan Perales M.
@IvanPeralesM. bem, então qual é o seu problema com a minha "digitação"? A resposta, conforme postada, não funciona.
user85421
0

Você quer dizer algo assim ?:

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

De que tipo é este lambda? Você não pode expressar isso em Java e, portanto, não pode compor essa expressão em um aplicativo de função e as expressões precisam ser compostas.

Para que isso funcione, você precisa de suporte para Tipos Rank2 em Java.

Os métodos podem ser genéricos, mas, portanto, você não pode usá-los como expressões. No entanto, eles podem ser reduzidos à expressão lambda, especializando todos os tipos genéricos necessários antes que você possa passá-los:ClassName::<TypeName>methodName

iconfly
fonte
1
"Of what type is this lambda? You couldn't express that in Java..."O tipo seria inferido usando o contexto, assim como com qualquer outro lambda. O tipo de lambda não é explicitamente expresso no próprio lambda.
Kröw