2016-01-05 20 views
17

Zastanawiam się, czy jest tam kiedykolwiek ważny przypadek zastosowania w następujących przypadkach:Czy porównywalne kiedykolwiek porównać do innego typu?

class Base {} 

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

Wydaje się, że wspólny wzór (patrz Collections dla wielu przykładów), aby przyjąć zbiór typu T, gdzie T extends Comparable<? super T> .

Ale wydaje się technicznie niemożliwe, aby wypełnić kontrakt z compareTo() w porównaniu do klasy bazowej, ponieważ nie ma sposobu, aby zapewnić, że inna klasa nie rozszerza bazy o sprzeczne porównanie. Rozważmy następujący przykład:

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

Mamy dwa zajęcia rozciągające Base używając porównań, które nie podążają wspólną regułę (jeśli nie było powszechną regułą, to prawie na pewno będzie realizowany w Base). Jeszcze następujące złamany porządek zostanie skompilowany:

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

nie byłoby bezpieczniej tylko zaakceptować T extends Comparable<T>? Czy jest jakiś przypadek użycia, który sprawdzi wildcard?

+0

Moje jelito mówi "nie" (jabłka i pomarańcze), ale jedynym powodem, dla którego "mógłbyś" to rozważyć, jest to, że NIGDY nie chcesz porównywać klas dzieci w inny sposób, niż wtedy, gdy robisz klasę rodzicielską. Oczywiście zawsze możesz po prostu nazwać "super.compareTo" zamiast: – MadProgrammer

+0

Myślę, że możesz nieco wyjaśnić swój tytuł. Wydaje mi się, że bardziej interesujące pytanie pojawia się w twoim poście: "Dlaczego metody w kolekcjach zadeklarowane do pracy na" T przedłuża porównywalną "zamiast" T przedłuża porównywalną "?" Oczywiście nie jest to świetny tytuł, ale myślę, że tytułowo zorientowany w taki sposób mógłby być lepszy. – Others

+0

Wiem, że Java 'enum' s użyć ich klasy bazowej do 'porównywalne'. Cała Java 'enum's wywodzi się z klasy bazowej' java.lang.Enum', która implementuje 'Porównywalne ', gdzie 'E' jest klasą potomną (to jest wyliczenie, którego używasz). – markspace

Odpowiedz

10

To jest bardzo dobre pytanie. Po pierwsze, zacznijmy powód Collections sposoby wykorzystania takich jak

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

Rzeczywiście, dlaczego po prostu nie

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

Powodem tego jest zasada PECS: Producent Rozszerza Super Konsumentów. Do czego służy binarySearch? To czyta elementów z listy, a następnie porównuje je przez przekazując ich wartości do funkcji . Ponieważ czyta elementy, lista działa jak producent, stąd pierwsza część - Producer Extends. Ten jest oczywisty, więc co z częścią Consumer Super?

Konsument Super w gruncie rzeczy oznacza, że ​​jeśli zamierzasz przekazywać wartości tylko niektórym funkcjom, nie przejmujesz się tym, czy akceptuje on dokładny typ obiektu lub jakiejś jego nadklasy. Deklaracja binarySearch brzmi: Mogę wyszukać wszystko, o ile wszystko można przekazać do metody listy elementów.

W przypadku sortowania nie jest to takie oczywiste, ponieważ elementy są porównywane tylko ze sobą. Ale nawet wtedy, co jeśli Base faktycznie implementuje (i robi całe porównanie) i A i B po prostu rozszerza się, nie dotykając porównania w jakikolwiek sposób? Wówczas nie można sortować list A i B, ponieważ nie implementują one odpowiednio Comparable<A> i Comparable<B>. Będziesz musiał ponownie zaimplementować cały interfejs za każdym razem, gdy będziesz podklasą!

Inny przykład: co zrobić, jeśli ktoś chciałby przeszukać plik binarny na liście zawierającej instancje niektórych klas, które nawet nie rozszerzają zakresu 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); 
    } 
}; 

A teraz chcą korzystać wystąpienie A jako klucz dla tego wyszukiwania binarnego.Mogą to zrobić tylko ze względu na część ? super T. Zauważ, że ta klasa nie ma pojęcia o tym, czy klucz jest A czy B, więc nie może implementować Comparable<A/B>.

Co do twojego przykładu, to chyba tylko przykład złego projektu. Niestety, nie widzę sposobu na zapobieganie takim rzeczom bez naruszania zasady PECS i/lub ograniczania już ograniczonej funkcjonalności generycznych Java.

0

Ograniczenie

T extends Comparable<? super T> 

należy rozumieć

T extends S, S extends Comparable<S> 

Comparable typu zawsze samo porównanie.

Jeśli X <: Comparable<Y>, musi to być przypadek, że X <: Y <: Comparable<Y>.

Możemy nawet "udowodnić", że z umowy compareTo(). Ponieważ musi być zdefiniowany odwrotny y.compareTo(x), musi to być prawda: Y <: Comparable<A>, X<:A. Po tym samym argumencie mamy A <: Comparable<Y>. Następnie przechodniość doprowadzi do A=Y.

+0

Umowa nie wymaga, aby zdefiniowano "y.compareTo (x)", chociaż jest to domyślne. – shmosel

+0

nie może naprawdę spełnić umowy, jeśli 'y.compareTo (x)' nie jest zdefiniowany :) – ZhongYu

Powiązane problemy