Generics nella classe Java Comparator. Qual è il tipo esatto di T e U?

Sep 09 2020

Considera i due metodi seguenti. La loro unica differenza è nella loro dichiarazione di tipo generico di Function <>

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

Diciamo che ho un List<GamingComputer> = { ASUS, MSI }, dove si GamingComputerestende Computer. Ora, voglio ordinarli.

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

Qual è il tipo di T?

La mia intuizione: T=GamingComputer. comparing()accetta keyExtractor, il cui tipo è Function<Computer super GamingComputer, Property>. Infine, comparing()ritorna Comparator<GamingComputer>.

Questo codice, a dimostrazione della mia intuizione, si compila perfettamente:

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

Ora, PECS, dal momento che c1, c2vengono aggiunti ad una collezione / costruttore / metodo, fino a quando la raccolta gestisce la loro classe genitore, in grado di gestire qualsiasi classe bambino. Questa è la ragione dietro <? super T>.

Come dimostrato in questo codice:

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

Dal momento che f2funziona con tutti Computer, dovrebbe essere in grado di funzionare anche con qualsiasi GamingComputer. Tuttavia, perché non dichiariamo tipo <? super T>, non siamo in grado di costruire un Comparatordi GamingComputers.

Ha senso. Poi...

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

La mia ipotesi: comparingT()con il tipo T=GamingComputerforza un abbattimento keyExtractor, che è Computer::getProperty. Costringe tutti Computersa usare GamingComputer::getProperty, il che probabilmente non è un problema, poiché Comparator<GamingComputer>confronta GamingComputers.

Ma perché questo NON si compila?

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

L'errore è molto particolare:

Non è possibile fare riferimento a un metodo non statico da un contesto statico, che probabilmente è un bug di Intellij

Non è possibile fare riferimento a un metodo non statico da un contesto statico nei flussi Java 8

Tuttavia, durante la compilazione:

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

Risposte

1 Sweeper Sep 09 2020 at 09:48

La mia intuizione: T=GamingComputer

La tua intuizione è corretta.


Perché Comparator<GamingComputer> c22 = comparingT(Computer::getProperty);compila?

Questo perché, a differenza delle istanze di interfacce funzionali che sono invarianti, i riferimenti ai metodi sono covarianti e controvarianti. Usando un esempio da qui , puoi fare qualcosa come:

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

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

O usando le tue classi, puoi fare:

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

È "come se" i riferimenti ai metodi hanno ? supersui loro parametri e ? extendssui tipi restituiti! I dettagli di cosa funziona e cosa no sono specificati nella sezione 15.13.2 delle specifiche del linguaggio Java.

Quindi c22, Tè ancora GamingComputer. Il riferimento al metodo Computer::getPropertypuò essere convertito in Function<GamingComputer, Property>un riferimento al metodo.

Questo non si compila, anche se f2"memorizza" Computer::getProperty:

Comparator<GamingComputer> c2 = comparingT(f2);

perché f2non è un riferimento al metodo stesso. È una variabile.

Perché Function<Computer, Property> f22 = GamingComputer::getProperty;non compila?

f22sarebbe in grado di accettare qualsiasi tipo di Computer, dal momento che accetta Computer. Se dai f22un altro tipo di computer (no GamingComputer), di GamingComputer.getPropertycerto non saresti in grado di gestirlo, vero?