2016-01-05 9 views
17

Mi chiedo se ci sia mai un caso d'uso valida per il seguente:Dovrebbe comparabile mai comparare con un altro tipo?

class Base {} 

class A implements Comparable<Base> { 
    //... 
} 

sembra essere un modello comune (vedi Collections per un certo numero di esempi) di accettare una collezione di tipo T, dove T extends Comparable<? super T> .

Ma sembra tecnicamente impossibile soddisfare il contratto di compareTo() quando si confronta con una classe base, perché non c'è modo di garantire che un'altra classe non estenda la base con un confronto contraddittorio. Si consideri il seguente esempio:

class Base { 
    final int foo; 
    Base(int foo) { 
     this.foo = foo; 
    } 
} 

class A extends Base implements Comparable<Base> { 
    A(int foo) { 
     super(foo); 
    } 
    public int compareTo(Base that) { 
     return Integer.compare(this.foo, that.foo); // sort by foo ascending 
    } 
} 

class B extends Base implements Comparable<Base> { 
    B(int foo) { 
     super(foo); 
    } 
    public int compareTo(Base that) { 
     return -Integer.compare(this.foo, that.foo); // sort by foo descending 
    } 
} 

Abbiamo due classi estendono Base utilizzando paragoni che non seguono una regola comune (se ci fosse una regola comune, che sarebbe quasi certamente essere implementato in Base). Eppure il seguente tipo rotta verrà compilato:

Collections.sort(Arrays.asList(new A(0), new B(1))); 

Non sarebbe più sicuro per accettare soltanto T extends Comparable<T>? O c'è qualche caso d'uso che convalida il carattere jolly?

+0

Il mio istinto dice no (mele e arance e tutto il resto), ma l'unica ragione per cui "potresti" considerare questo è se non hai mai voluto confrontare le classi figlie in un modo diverso allora fai la classe genitore. Certo, potresti sempre chiamare semplicemente 'super.compareTo' invece – MadProgrammer

+0

Penso che potresti voler chiarire un po 'il tuo titolo. Penso che la domanda più interessante sollevata nel tuo post sia "Perché i metodi in Collezioni dichiarati per funzionare su" T estende Comparable "invece di" T estende Comparable "?" Ovviamente non è un gran bel titolo, ma penso che un titolo orientato come questo possa essere migliore. – Others

+0

So che Java 'enum's usa la loro classe base per un' Comparable'. Tutti gli 'enum' di Java derivano dalla classe di base' java.lang.Enum' che implementa 'Comparable ', dove 'E' è la classe figlia (ad esempio, l'enumerazione che si utilizza). – markspace

risposta

10

Questa è una buona domanda. In primo luogo, cominciamo con il motivo per cui Collections utilizzare metodi come

binarySearch(List<? extends Comparable<? super T>> list, T key) 

Anzi, perché non basta

binarySearch(List<? extends Comparable<T>> list, T key) 

La ragione di questo è il principio PECS: Produttore estende, Super consumatori. Cosa fa binarySearch? Lo legge gli elementi dall'elenco e li confronta per passando i valori alla funzione compareTo. Dal momento che legge elementi, la lista funge da produttore e quindi la prima parte, Producer Extends. Questo è ovvio, quindi per quanto riguarda la parte Consumer Super?

Consumer Super significa fondamentalmente che se passi solo dei valori ad una funzione, non ti importa se accetta il tipo esatto del tuo oggetto o qualche superclasse. Quindi, quello che sta dicendo la dichiarazione binarySearch è: posso cercare qualsiasi cosa purché tutto ciò che può essere passato al metodo compareTo degli elementi della lista.

In caso di ordinamento non è così ovvio, perché gli elementi sono solo l'uno rispetto all'altro. Ma anche in questo caso, seimplementa effettivamente Comparable<Base> (e fa l'intero confronto) e A e B, basta estendere Base senza toccare il confronto in alcun modo? Quindi non sarà possibile ordinare gli elenchi di A e B perché non implementano rispettivamente Comparable<A> e Comparable<B>. Dovresti reimplementare l'intera interfaccia ogni volta che esegui la sottoclasse!

Un altro esempio: cosa accadrebbe se qualcuno volesse effettuare una ricerca binaria su un elenco contenente istanze di qualche classe che non estenda nemmeno il proprio Base?

class BaseComparable implements Comparable<Base> { 
    private final Base key; 
    // other fields 

    BaseComparable(Base key, ...) { 
     this.key = key; 
     // other fields initialization 
    } 

    @Override 
    public int compareTo(Base otherKey) { 
     return this.key.compareTo(otherKey); 
    } 
}; 

Ed ora vogliono utilizzare un'istanza di A come chiave per la ricerca binaria.Possono farlo solo a causa della parte ? super T. Si noti che questa classe non ha idea se la chiave sia A o B, quindi non è possibile implementarla Comparable<A/B>.

Come per il tuo esempio, immagino sia solo un esempio di design scadente. Sfortunatamente, non vedo alcun modo di prevenire tali cose senza infrangere il principio PECS e/o limitare le già limitate funzionalità dei generici Java.

0

Il vincolo

T extends Comparable<? super T> 

occorre essere intesi come

T extends S, S extends Comparable<S> 

Un tipo Comparable è sempre auto-confronto.

Se X <: Comparable<Y>, deve essere il caso che X <: Y <: Comparable<Y>.

Potremmo anche "provarlo" dal contratto di compareTo(). Poiché è necessario definire il contrario y.compareTo(x), deve essere vero che Y <: Comparable<A>, X<:A. Seguendo lo stesso argomento, abbiamo A <: Comparable<Y>. Quindi la transitività porterà a A=Y.

+0

Il contratto non richiede tecnicamente che 'y.compareTo (x)' sia definito, sebbene sia implicito. – shmosel

+0

non è in grado di soddisfare il contratto se 'y.compareTo (x)' non è definito :) – ZhongYu

Problemi correlati