Genéricos na classe Java Comparator. Qual é o tipo exato de T e U?

Sep 09 2020

Considere os dois métodos a seguir. Sua única diferença está em sua declaração de tipo genérico de Função <>

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparingT(
        Function<T, ? extends U> keyExtractor) <-- Check here! T instead of ? super T
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

Digamos que eu tenha um List<GamingComputer> = { ASUS, MSI }, onde GamingComputerestende Computer. Agora, quero classificá-los.

List.sort( comparing( Computer::getProperty ) )

Qual é o tipo de T?

Minha intuição: T=GamingComputer. comparing()leva em keyExtractor, cujo tipo é Function<Computer super GamingComputer, Property>. Finalmente, comparing()retorna Comparator<GamingComputer>.

Este código, provando minha intuição, compila perfeitamente:

Function<Computer, Property> f1 = Computer::getProperty;
Comparator<GamingComputer> c1 = comparing(f1);

Agora, por PECS, pois c1, c2estão sendo adicionados a um / construtor / método de coleta, enquanto a coleção lida com sua classe pai, ele poderia lidar com qualquer classe criança. Essa é a razão por trás <? super T>.

Conforme demonstrado neste código:

Function<Computer, Property> f2 = Computer::getProperty;
Comparator<GamingComputer> c2 = comparingT(f2); // FAILS to compile. Required Type: Comparator<GamingComputer>, Provided Comparator<Computer>
Comparator<Computer> c2 = comparingT(f2); // compiles successfuly

Como f2funciona com todos Computer, deve ser capaz de funcionar com qualquer um GamingComputertambém. No entanto, como não declaramos o tipo como <? super T>, não podemos construir um Comparatorde GamingComputers.

Faz sentido. Então...

Comparator<GamingComputer> c22 = comparingT(Computer::getProperty); // compiles successfuly... WT, excuse mi French, heck???

Meu palpite: comparingT()com tipo T=GamingComputerforça um downcast em keyExtractor, que é Computer::getProperty. Ele força todos Computersa usar GamingComputer::getProperty, o que provavelmente não é um problema, uma vez Comparator<GamingComputer>que compara GamingComputers.

Mas, por que isso NÃO compila?

Function<Computer, Property> f22 = GamingComputer::getProperty;

O erro é muito peculiar:

O método não estático não pode ser referenciado a partir de um contexto estático, o que provavelmente é um bug do Intellij

O método não estático não pode ser referenciado a partir de um contexto estático em fluxos java 8

Ainda assim, ao compilar:

java: incompatible types: invalid method reference
    method getPart in class function.GamingComputer cannot be applied to given types
      required: no arguments
      found: function.Computer
      reason: actual and formal argument lists differ in length

Respostas

1 Sweeper Sep 09 2020 at 09:48

Minha intuição: T=GamingComputer

Sua intuição está correta.


Por que Comparator<GamingComputer> c22 = comparingT(Computer::getProperty);compila?

Isso ocorre porque, ao contrário das instâncias de interfaces funcionais que são invariantes, as referências de método são covariantes e contravariantes. Usando um exemplo aqui , você pode fazer algo como:

// in SomeClass
public static Integer function(Object o) {
    return 2;
}

// ...
Function<String, Object> function = SomeCLass::function;

Ou usando suas aulas, você pode fazer:

Function<GamingComputer, Property> f = Computer::getProperty;

É "como se" as referências de método tivessem ? superem seus parâmetros e ? extendsnos tipos de retorno! Os detalhes do que funciona e do que não funciona são especificados na seção 15.13.2 da Especificação da linguagem Java.

Então c22, para , Tainda é GamingComputer. A referência de método Computer::getPropertypode ser convertida em Function<GamingComputer, Property>uma referência de método.

Isso não compila, mesmo que f2"armazene" Computer::getProperty:

Comparator<GamingComputer> c2 = comparingT(f2);

porque f2não é uma referência de método em si. É uma variável.

Por que Function<Computer, Property> f22 = GamingComputer::getProperty;não compila?

f22seria capaz de aceitar qualquer tipo de Computer, desde que aceite Computer. Se você desse f22outro tipo de computador (não GamingComputer), GamingComputer.getPropertycertamente não seria capaz de lidar com isso, não é?