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

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 .
  • [..]

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

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


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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.