Discussion:
Generics: Comparable Interface
(zu alt für eine Antwort)
Uwe Seimet
2005-08-04 17:39:55 UTC
Permalink
Hallo!

Mir gelingt es nicht, Comparable.compareTo() innerhalb einer
parametrisierten Methode ohne Fehlermeldung des Compilers zu verwenden.
Ein Beispiel, das hoffentlich nicht zu weit runtergestrippt ist, um mein
Problem zu demonstrieren:

----

import java.util.*;

class Test<T> implements Comparator<T>
{
public int compare(T t1, T t2)
{
return getComparable(t1).compareTo(getComparable(t2));
}

Comparable getComparable(T t1)
{
return (Comparable)null;
}
}

----

javac (und ähnlich auch Eclipse) meldet:

Test.java:7: warning: [unchecked] unchecked call to compareTo(T) as a
member of the raw type java.lang.Comparable
return getComparable(t1).compareTo(getComparable(t2));

Ich verstehe zwar, warum der Compiler mecker, aber trotzdem ist es mir
bisher nicht gelungen, Typsicherheit ohne Fehlermeldung zu erreichen.
Wie wäre es richtig?

Grüße Uwe
--
-----------------------------------------------------------------------
Dr. Uwe Seimet http://www.seimet.de
Sebastian Scheid
2005-08-04 20:48:47 UTC
Permalink
Post by Uwe Seimet
Hallo!
Mir gelingt es nicht, Comparable.compareTo() innerhalb einer
parametrisierten Methode ohne Fehlermeldung des Compilers zu verwenden.
Ein Beispiel, das hoffentlich nicht zu weit runtergestrippt ist, um mein
----
import java.util.*;
class Test<T> implements Comparator<T>
{
public int compare(T t1, T t2)
{
return getComparable(t1).compareTo(getComparable(t2));
}
Comparable getComparable(T t1)
{
return (Comparable)null;
}
}
public int compare(T t1, T t2) {
return getComparable(t1).compareTo(t2);
}

Comparable<T> getComparable(T t1) {
return Comparable<T> t1;
}

erledigt die Warnung in compare().

Allerdings hast Du dann eine Warnung beim casten in getComparable().
Das was Du hier machen willst, macht IMHO auch nicht so viel Sinn und Du
bekommst deshalb die Warnung nicht weg. Comparable<T> wird von Klassen
implementiert, um deren "natürliche Ordnung" mit sich selbst oder anderen
Typen T auszudrücken. T gibt dabei an, mit welchem Typ verglichen werden
kann:

Vergleich mit sich selbst:
-------------------------------
class A implements Comparable<A> {

private Integer i;

public int compareTo(A o) {
return i.compareTo(o.i);
}

}
-------------------------------


Vergleich mit anderem Typ (macht das in der Praxis eigentlich Sinn?),
symmetrisch wegen API-Vertrag:
-------------------------------
class B implements Comparable<C> {

Integer i;

public int compareTo(C o) {
return i.compareTo(o.j);
}

}

class C implements Comparable<B> {

Integer j;

public int compareTo(B o) {
return j.compareTo(o.i);
}

}

-------------------------------

Ein Comparator<T> dagegen vergleicht einen beliebigen Typen mit sich selbst,
nie mit einem anderen Typen. Dabei sollte der Vergleich nicht auf der
natürlichen Ordnung basieren, denn diese kann ja durch Implementieren von
Comparable<T> des Typs T beschrieben werden.
Das bedeutet aber, dass Du in compare(T, T) von Comparator nicht an der
natürlichen Ordnung, die durch Comparable gegeben ist, interessiert bist. Du
musst also aufgrund irgendwelcher Kriterien vergleichen. Und an die Daten
für den Vergleich kommst Du nicht über den unbekannten Typ T, sondern nur
über einen konreketen Typ, z.B. A:

class Test implements Comparator<A> {

public int compare(A a1, A a2) {
return a1.i - a2.i; // oder irgendwas passendes

}

}

Und dann musst Du auch nicht nach Comparable casten und bekommst keine
Warnung.
Was im Prinzip in der API fehlt, um Deinen ursprünglichen Code ans Laufen zu
bekommen ist ein Comparator<S, T>
-------------------------------
interface Comparator2<S, T> {
public int compare(S s, T t);
}

class Test2<T> implements Comparator2<Comparable<T>, T> {

public int compare(Comparable<T> t1, T t2) {
return t1.compareTo(t2);
}

}

Und dieses Konstrukt brauchst Du aus o.g. Gründen eigentlich nicht.

Gruß
Sebastian
Uwe Seimet
2005-08-04 21:30:10 UTC
Permalink
Post by Sebastian Scheid
musst also aufgrund irgendwelcher Kriterien vergleichen. Und an die Daten
für den Vergleich kommst Du nicht über den unbekannten Typ T, sondern nur
In meinem Fall ist der Typ A entweder java.lang.String oder
java.lang.Integer, die beide Comparable implementieren, wobei sicher
gestellt ist, dass a1 und a2 entweder nur String oder nur Integer sind.

Der Comparator erhält zwei T. T besitzt mehrere String- und
Integer-Felder, und der Comparator "weiß", welche dieser Felder (Paare
von Strings oder Integern) im jeweiligen Kontext verglichen werden
müssen. Genauer: Er "weiß", wie er an die richtige Comparable-Referenzen
kommt, wobei sicher gestellt ist, dass es immer zwei Strings oder zwei
Integers sind.
Ich hatte eigentlich gehofft, das mit compareTo lösen zu können, da
dieses Interface sowohl von String als auch von Integer implementiert
wird, und genau das tut, was ich brauche.

Danke jedenfalls für die ausführliche Antwort!
--
-----------------------------------------------------------------------
Dr. Uwe Seimet http://www.seimet.de
Ingo R. Homann
2005-08-05 07:48:36 UTC
Permalink
Hi,
Post by Uwe Seimet
In meinem Fall ist der Typ A entweder java.lang.String oder
java.lang.Integer, die beide Comparable implementieren, wobei sicher
gestellt ist, dass a1 und a2 entweder nur String oder nur Integer sind.
...
Ich hatte eigentlich gehofft, das mit compareTo lösen zu können, da
dieses Interface sowohl von String als auch von Integer implementiert
wird, und genau das tut, was ich brauche.
Genau das dachte ich auch. Wieso machst Du es nicht wie folgt:

class C implements Comparator<Comparable> {
public int compare(Comparable o1, Comparable o2) {
return o1.compareTo(o2);
}
}

Wobei sich dann fast die Frage stellt, wozu Du überhaupt noch einen
Comparator benötigst...

Ciao,
Ingo
Sebastian Scheid
2005-08-05 09:06:04 UTC
Permalink
Post by Ingo R. Homann
Hi,
Post by Uwe Seimet
In meinem Fall ist der Typ A entweder java.lang.String oder
java.lang.Integer, die beide Comparable implementieren, wobei sicher
gestellt ist, dass a1 und a2 entweder nur String oder nur Integer sind.
...
Ich hatte eigentlich gehofft, das mit compareTo lösen zu können, da
dieses Interface sowohl von String als auch von Integer implementiert
wird, und genau das tut, was ich brauche.
class C implements Comparator<Comparable> {
public int compare(Comparable o1, Comparable o2) {
return o1.compareTo(o2);
}
}
Auch dabei gekommst Du eine Warnung, weil der Compiler wissen will, mit
welchem Typ das Comparable vergleichen kann, also
class C implements Comparator<Comparable<T>> {

In der Comparator#compare() Methode kannst Du aber nur gleiche Typen als
Parameter habe. Comparable#compareTo() wird aber auf Comparable<T>
aufgerugen und erwartet T als Parameter.
Also nochmal:
1. Comparator<T>#compare(T, T)
2. Comparable<T>#compare(T)
sieht zwar ähnlich aus, verglichen wird aber
1. T mit T
2. Comparable<T> mit T

Das Comparable<T> muss man so lesen: beliebiger Typ, vergleichbar mit T.

Das Problem liegt IMHO im Gebrauch des Comparators. Der OP will doch glaube
ich eine Art Hilfsmethode haben, um Comparable Objekte zu sortieren, sei es
ein String oder ein Integer. Dazu braucht es aber keinen Comparator:

class Comparer<T> {

public int compare(Comparable<T> c1, T c2) {
return c1.compareTo(c2);
}

}

Schöne Grüße
Sebastian
Ingo R. Homann
2005-08-05 10:02:54 UTC
Permalink
Hi,
Post by Sebastian Scheid
Post by Ingo R. Homann
class C implements Comparator<Comparable> {
public int compare(Comparable o1, Comparable o2) {
return o1.compareTo(o2);
}
}
Auch dabei gekommst Du eine Warnung, weil der Compiler wissen will, mit
welchem Typ das Comparable vergleichen kann,...
Auch gerade erst gesehen. Folgendes funktioniert aber ohne Warnung:

class C implements Comparator<Comparable<Comparable>> // ROTFLOL!
{
public int compare(Comparable<Comparable> o1,
Comparable<Comparable> o2)
{
return o1.compareTo(o2);
}
}

Ob's schön ist, sei mal dahingestellt. Ausserdem wage ich immernoch den
Sinn dieses Comparators in Frage zu stellen.

Ciao,
Ingo
Uwe Seimet
2005-08-05 13:20:01 UTC
Permalink
Post by Ingo R. Homann
class C implements Comparator<Comparable<Comparable>> // ROTFLOL!
{
public int compare(Comparable<Comparable> o1,
Comparable<Comparable> o2)
{
return o1.compareTo(o2);
}
}
Ob's schön ist, sei mal dahingestellt. Ausserdem wage ich immernoch den
Sinn dieses Comparators in Frage zu stellen.
Den brauche ich für Collections.sort().
--
-----------------------------------------------------------------------
Dr. Uwe Seimet http://www.seimet.de
Sebastian Scheid
2005-08-05 14:51:06 UTC
Permalink
Post by Uwe Seimet
Post by Ingo R. Homann
class C implements Comparator<Comparable<Comparable>> // ROTFLOL!
{
public int compare(Comparable<Comparable> o1,
Comparable<Comparable> o2)
{
return o1.compareTo(o2);
}
}
Ob's schön ist, sei mal dahingestellt. Ausserdem wage ich immernoch den
Sinn dieses Comparators in Frage zu stellen.
Den brauche ich für Collections.sort().
Erklär doch nochmal was Du da übergeben willst. Eine List mit Strings und
Integers? In einem früheren Post hattest Du das mal erklärt, aber ich habs
nicht verstanden. Zeig doch mal ein Codeschnipsel. Vielleicht können wir Dir
dann eher helfen.

Sebastian
Uwe Seimet
2005-08-05 15:43:23 UTC
Permalink
Post by Sebastian Scheid
Erklär doch nochmal was Du da übergeben willst. Eine List mit Strings und
Integers? In einem früheren Post hattest Du das mal erklärt, aber ich habs
nicht verstanden. Zeig doch mal ein Codeschnipsel. Vielleicht können wir Dir
dann eher helfen.
OK, ich versuch' mal genauer zu beschreiben, was ich eigentlich will.
Ich habe Instanzen der Klassen A:

class A
{
String b;
Integer c;
// weitere Strings bzw. Integers folgen
}

Eine Liste mit Instanzen dieser Klasse dient als Daten für den Inhalt
einer Tabelle, wobei in einer Spalte des Views die Werte von b, in einer
anderen die Werte von c stehen. Pro Zeile des Views existiert ein Objekt
A. b und c sind somit quasi die Zellen der Tabelle.

Diese Tabelle soll aufsteigend oder absteigend sortiert werden, wobei
der Anwender entscheidet, ob zuerst nach b oder zuerst nach c sortiert
wird. Bei der Sortierung ist für c die nummerische Reihenfolge relevant,
für b der Stringvergleich.

Sortiert wird mit Collections.sort(Liste, Comparator), wobei ein
Comparator benutzt wird, der so aussieht:

final private class DataComparator implements Comparator<T>
{
public int compare(final T data1, final T data2)
{
for (int i = 0; i < nrOfcolumns.length; i++)
{
final int result = getComparableForColumn(data1, ...)
.compareTo(getComparableForColumn(data2, ...));
if (result != 0)
{
return sortedDescending_ * result;
// sortedDescending_ ist -1 oder 1
}
}

return 0;
}
}

T wird durch Instanzen von A ersetzt. getObjectForColumn() liefert in
Abhängigkeit von der Einstellung des Benutzer das Comparable-Objekt für
den Spalten-Index, der als nächstes zu vergleichen ist. Für die
Beispiel-Klasse A wird also beim Durchlaufen der Schleife zunächst
sowohl für data1 als auch für data2 das Comparable für die Spalte 0
(String-Vergleich), im nächsten Durchlauf dann für die Spalte 1
(Integer-Vergleich) geliefert, oder umgekehrt. (Es kann n Spalten geben
kann, je nachdem wie A bzw. T aussieht.)

Ursprünglich gab es in der Tabelle keine Integers, und da passte eine
einfache Lösung gut, da nur Strings verglichen werden mussten. Aber nun
gibt es auch Integers in der Tabelle (bzw. in A), und da sowohl Integer
als auch String Comparable implementieren, und sicher gestellt ist (zur
Laufzeit), dass die richtigen Werte-Paare verglichen werden, dachte ich
an Comparable/compareTo als Lösung, um je nach Spalte entweder Strings
oder Integers vergleichen zu lassen, ohne wissen zu müssen, welcher Typ
es tatsächlich ist.

Vielleicht gibt es ja eine grundsätzlich anderen/besseren Ansatz,
mehrspaltige Tabellen mit unterschiedlichen Datentypen pro Spalte zu
sortieren. Das wäre natürlich interessant.
--
-----------------------------------------------------------------------
Dr. Uwe Seimet http://www.seimet.de
Sebastian Scheid
2005-08-05 17:54:33 UTC
Permalink
Post by Sebastian Scheid
Erklär doch nochmal was Du da übergeben willst. Eine List mit Strings und
Integers? In einem früheren Post hattest Du das mal erklärt, aber ich
habs nicht verstanden. Zeig doch mal ein Codeschnipsel. Vielleicht können
wir Dir dann eher helfen.
OK, ich versuch' mal genauer zu beschreiben, was ich eigentlich will. Ich
class A
{
String b;
Integer c;
// weitere Strings bzw. Integers folgen
}
Eine Liste mit Instanzen dieser Klasse dient als Daten für den Inhalt
einer Tabelle, wobei in einer Spalte des Views die Werte von b, in einer
anderen die Werte von c stehen. Pro Zeile des Views existiert ein Objekt
A. b und c sind somit quasi die Zellen der Tabelle.
Diese Tabelle soll aufsteigend oder absteigend sortiert werden, wobei der
Anwender entscheidet, ob zuerst nach b oder zuerst nach c sortiert wird.
Bei der Sortierung ist für c die nummerische Reihenfolge relevant, für b
der Stringvergleich.
Sortiert wird mit Collections.sort(Liste, Comparator), wobei ein
final private class DataComparator implements Comparator<T>
{
public int compare(final T data1, final T data2)
{
for (int i = 0; i < nrOfcolumns.length; i++)
{
final int result = getComparableForColumn(data1, ...)
.compareTo(getComparableForColumn(data2, ...));
if (result != 0)
{
return sortedDescending_ * result;
// sortedDescending_ ist -1 oder 1
}
}
return 0;
}
}
T wird durch Instanzen von A ersetzt. getObjectForColumn() liefert in
Abhängigkeit von der Einstellung des Benutzer das Comparable-Objekt für
den Spalten-Index, der als nächstes zu vergleichen ist. Für die
Beispiel-Klasse A wird also beim Durchlaufen der Schleife zunächst sowohl
für data1 als auch für data2 das Comparable für die Spalte 0
(String-Vergleich), im nächsten Durchlauf dann für die Spalte 1
(Integer-Vergleich) geliefert, oder umgekehrt. (Es kann n Spalten geben
kann, je nachdem wie A bzw. T aussieht.)
Ursprünglich gab es in der Tabelle keine Integers, und da passte eine
einfache Lösung gut, da nur Strings verglichen werden mussten. Aber nun
gibt es auch Integers in der Tabelle (bzw. in A), und da sowohl Integer
als auch String Comparable implementieren, und sicher gestellt ist (zur
Laufzeit), dass die richtigen Werte-Paare verglichen werden, dachte ich an
Comparable/compareTo als Lösung, um je nach Spalte entweder Strings oder
Integers vergleichen zu lassen, ohne wissen zu müssen, welcher Typ es
tatsächlich ist.
Ok, das verstehe ich jetzt :-)

Ich hab jetzt auch lange daran rumprobiert, aber IMHO bekommst Du
compiletime Sicherheit und die Anforderung alle Comparables automatisch zu
unterstützen nicht unter einen Hut. Denn irgendwann kommst Du ja an eine
solche Stelle:
-------------------------------------------
class A {
String b;

Integer c;

Comparable<???> getComparableForColumn(int column) {
switch (column) {
case 0:
return b;
case 1:
return c;
}
throw new IllegalArgumentException();
}
}

-------------------------------------------

Diese Methode liefert ja zwei verschiedene Typen, die nicht mireinander
vergleichbar sind. Der Compiler kann ja nicht wissen, dass Du immer nur zwei
Comparables miteinander vergleichst, die die gleiche column haben.

Wenn Dir die compiletime Sicherheit wichtig ist, bleibt Dir IMHO nichts
anderes übrig als für jede Spalte einen eigenen Comparator zu schreiben:

-------------------------------------------
class Row {
String a;
Integer b;
}


class Column1Comparator implements Comparator<Row> {

public int compare(Row r1, Row r2) {
return r1.a.compareTo(r2.a);
}
}


class Column2Comparator implements Comparator<Row> {

public int compare(Row r1, Row r2) {
return r1.b.compareTo(r2.b);
}
}


class RowComparator implements Comparator<Row> {

// hier kannst Du beliebige Spalten in beliebiger Reihenfolge eintragen
// Wichtigste Spalte zuerst
List<Comparator<Row>> columnsComparators;

public int compare(Row r1, Row r2) {
for (Comparator<Row> eachComparator : columnsComparators) {
int compare = eachComparator.compare(r1, r2);
if (compare != 0)
return compare;
}
return 0;
}

}
-------------------------------------------

Mir fällt leider auch nichts besseres ein, obwohl so ein feature sicherlich
wünschenswert ist.

Schöne Grüße
Sebastian
Ralf Ullrich
2005-08-05 19:04:31 UTC
Permalink
OK, dann versuch ich's nochmal.

Wie wär's damit?

cu

package de.com.lang.java;

import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class ComparatorDemo2 {

static enum Order {
ASCENDING, DESCENDING
}

interface DataVector {

Object getColumn(int col);
}

static class DataObject implements DataVector {

String someString;

// hier ist schonmal das Problem #1: int ist kein Comparable
int someInteger;

public DataObject(String someString, int someInteger) {
this.someString = someString;
this.someInteger = someInteger;
}

public Object getColumn(int col) {
switch (col) {
case 0:
return someString;
case 1:
return new Integer(someInteger); // Problem #1 gelöst
}
throw new IllegalArgumentException("invalid column index: "
+ col);
}

@Override
public String toString() {
return someString + "(age: " + someInteger + ")";
}
}

static class DataSortInfo {

int column;

Order order;

public DataSortInfo(int column, Order order) {
this.column = column;
this.order = order;
}
}

static class DataComparator<T extends DataVector> implements
Comparator<T> {

private DataSortInfo[] sortSpec;

public DataComparator(DataSortInfo... sortSpec) {
this.sortSpec = new DataSortInfo[sortSpec.length];
System.arraycopy(sortSpec, 0, this.sortSpec, 0,
sortSpec.length);
}

public int compare(T t1, T t2) {
int cmp = 0;
for (int i = 0; i < sortSpec.length; i++) {
DataSortInfo sortInfo = sortSpec[i];
if (sortInfo.order == Order.ASCENDING) {
cmp = getComparable(t1, sortInfo.column).compareTo(
getComparable(t2, sortInfo.column));
} else {
cmp = getComparable(t2, sortInfo.column).compareTo(
getComparable(t1, sortInfo.column));
}
if (cmp != 0)
break;
}
return cmp;
}

@SuppressWarnings("unchecked")
public <S extends Object> Comparable<S> getComparable(T vector,
int index) {
try {
// hier gibt es nun einen Type-Safety check,
// den wir aber ignorieren können, da
// 1. ClassCastException für non-Comparables abgefangen
wird
// 2. _alle_ Comparable<?> letztlich zu Comparable<S
extends
// Object>
// kompatibel sind
return (Comparable<S>) vector.getColumn(index);
} catch (ClassCastException e) {
// Wenn wir hierher kommen, enthielt der DataVector ein
// Object das Comparable nicht implementiert
throw (ClassCastException) new ClassCastException(
"getComparable() must be overridden for
DataVectors "
+ "containing non-Comparable Data")
.initCause(e);
}
}

}

public static void main(String[] args) {

List<DataObject> list = new LinkedList<DataObject>();
list.add(new DataObject("Kirk", 32));
list.add(new DataObject("Spock", 124));
list.add(new DataObject("Pille", 46));
list.add(new DataObject("Scotty", 42));
list.add(new DataObject("Checkov", 20));
list.add(new DataObject("Uhura", 32));
list.add(new DataObject("Sulu", 20));

DataSortInfo colOneAscending = new DataSortInfo(0,
Order.ASCENDING);
DataSortInfo colOneDescending = new DataSortInfo(0,
Order.DESCENDING);
DataSortInfo colTwoAscending = new DataSortInfo(1,
Order.ASCENDING);
DataSortInfo colTwoDescending = new DataSortInfo(1,
Order.DESCENDING);

System.out.println("Sort String up, int down");
Collections.sort(list, new
DataComparator<DataObject>(colOneAscending,
colTwoDescending));
for (Iterator iter = list.iterator(); iter.hasNext();) {
DataObject data = (DataObject) iter.next();
System.out.println(data.toString());
}

System.out.println("Sort int up, String down");
Collections.sort(list, new
DataComparator<DataObject>(colTwoAscending,
colOneDescending));
for (Iterator iter = list.iterator(); iter.hasNext();) {
DataObject data = (DataObject) iter.next();
System.out.println(data.toString());
}

}
}

Ralf Ullrich
2005-08-04 21:52:05 UTC
Permalink
Ist jetzt nur geraten, weil ich selbst noch nicht viel mit Generics
gemacht habe und gerade zu faul bin das ganze in Eclipse zu kopieren,
aber ich denke mit einem "<?>" an den richtigen Stellen solltest du die
Post by Uwe Seimet
import java.util.*;
class Test<T> implements Comparator<T>
{
public int compare(T t1, T t2)
{
return getComparable(t1).compareTo(getComparable(t2));
}
Comparable<?> getComparable(T t1)
{
return (Comparable<?>)null;
}
}
cu
Uwe Seimet
2005-08-05 06:10:55 UTC
Permalink
Post by Ralf Ullrich
Ist jetzt nur geraten, weil ich selbst noch nicht viel mit Generics
gemacht habe und gerade zu faul bin das ganze in Eclipse zu kopieren,
aber ich denke mit einem "<?>" an den richtigen Stellen solltest du die
Nein, das hatte ich alles schon versucht. Mag vieleicht in meinem
kleinem Beispiel funktionieren, aber nicht in der eigentlichen Anwendung.
--
-----------------------------------------------------------------------
Dr. Uwe Seimet http://www.seimet.de
Ralf Ullrich
2005-08-05 14:48:22 UTC
Permalink
Uwe Seimet wrote:
--
Post by Uwe Seimet
Nein, das hatte ich alles schon versucht. Mag vieleicht in meinem
kleinem Beispiel funktionieren, aber nicht in der eigentlichen Anwendung.
OK, jetzt abe ich's doch nach Eclipse kopiert und bin auf folgende
Lösung gekommen:

package de.com.lang.java;

import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;

public class ComparatorDemo {

static class ComparableComparator<T extends Comparable<T>>
implements Comparator<T> {

public int compare(T t1, T t2) {
return t1.compareTo(t2);
}

}

public static void main(String[] args) {

List<String> list = new LinkedList<String>();
list.add("Spock");
list.add("Kirk");
list.add("Pile");
list.add("Scotty");
list.add("Checkov");
list.add("Uhura");
list.add("Sulu");

Collections.sort(list, new ComparableComparator<String>());

}
}


Wenn das nicht tut, musstt du mir sagen was in deiner Anwendung anders
ist. (Am besten auch mit einem kleinen main()-TestCase)

cu
Lesen Sie weiter auf narkive:
Loading...