Beuth Hochschule Objekte auf kleiner/größer vergleichen SS13, S. 1

Werbung
Beuth Hochschule
Objekte auf kleiner/größer vergleichen
SS13, S. 1
Objekte auf kleiner/größer vergleichen
1. Grundbegriffe
Def.: Ein Operator ist ein Name (für eine Operation), der typischerweise aus Sonderzeichen besteht und
auf besondere Weise notiert wird (in Präfix-, Infix-, Postfix- oder Mixfix-Notation).
Def.: Ein n-stelliger Operator erwartet n Parameter.
In Java sind alle Operatoren 1-stellig, 2-stellig oder 3-stellig.
Achtung: Es gibt einen 1-stelligen Operator - (das Vorzeichen Minus) und einen 2-stelligen Operator (das Subtraktionszeichen). Ganz entsprechend gibt es zwei +-Operatoren, die gleich aussehen.
Zweistellige Operatoren werden zwischen ihre beiden Parameter geschrieben (Infix-Notation)
z.B. a < b oder x * y oder b1 ^ b2.
Einstellige Operatoren werden vor ihren Parameter geschrieben (Präfix-Notation), z.B. -a oder ++b,
oder dahinter (Postfix-Notation), z.B. b++ oder b--.
Der Bedingungsoperator (engl. conditional operator) besteht aus den beiden Zeichen ? und : und ist der
einzige 3-stellige Operator. Er wird mixfix notiert, z.B. so: 2*a<b ? b+1 : 17 .
Def.: Eine Operation ist eine Funktion (engl.: a non-void method), deren Name ein Operator ist.
Def.: Ein Operator ist überladen, wenn er der Name von mehreren verschiedenen Operationen ist.
In Java sind alle Operatoren überladen. Z.B. bezeichet der 2-stellige Plus-Opertor + eine Additionsoperation für int-Werte, eine Additionsoperation für float-Werte, eine Additions-Operation für doubleWerte und eine Konkatenations-Operation für String-Objekte.
2. Was kann man womit auf was vergleichen?
Werte primitiver Typen kann man mit den Operatoren == und != auf gleich/ungleich und mit den Operatoren <, <=, >=, > auf kleiner/größer vergleichen.
Referenzen (d.h. die Werte von Referenzvariablen) kann man mit den Operatoren == und != auf
gleich/ungleich vergleichen. Man kann Referenzen nicht auf kleiner/größer vergleichen.
Objekte (d.h. die Zielwerte von Referenzvariablen) kann man grundsätzlich nicht mit den Operatoren
==, !=, <, <=, >=, > vergleichen. Objekte einiger Referenztypen kann man mit ihren Methoden namens
equals auf gleich/ungleich vergleichen (z.B. Objekte der Typen String, Integer, Double etc.).
Aber die equals-Methoden vieler Objekte vergleichen nur Referenzen, nicht Zielwerte (das gilt z.B.
für Objekte der Typen StringBuilder und JButton und für Objekte von Reihungstypen wie
int[] oder Integer[][] oder String[][][]).
3. Warum darf man String-Objekte in Java nicht mit < vergleichen?
Ein String-Objekt kann bis zu etwa 2,15 Milliarden char-Komponenten enthalten (und damit etwa
4,3 GB Speicher belegen). Ein Vergleich von zwei so langen String-Objekten kann ziemlich viel Rechenzeit kosten.
Ganz allgemein gilt: Wenn man zwei Variablen a und b vergleicht, sind grundsätzlich drei Ergebnisse
möglich:
- a ist kleiner als b
- a ist gleich b
- a ist größer als b
Eine Vergleichsoperation wie z.B. < liefert aber nur eines von zwei möglichen Ergebnissen, denn sie hat
den Ergebnistyp (Rückgabetyp) boolean, und dieser Typ hat nur 2 Werte. Wenn man mit solchen
boolean-Operationen alle drei möglichen Ergebnisse unterscheiden will, muss man zweimal vergleichen, wie im folgenden Beispiel.
S. 2, SS13
3. Warum darf man String-Objekte in Java nicht mit < vergleichen? Beuth-Hochschule
Beispiel-01: Mit boolean-Operationen 3 Ergebnissse unterscheiden (funktioniert in Java nicht):
1
2
3
4
5
6
7
8
9
10
String a = ... ; // Der Zielwert von a ist ein langer String
String b = ... ; // Der Zielwert von b ist ein langer String
if (a < b) {
// Ein teurer String-Vergleich
System.out.println("a ist kleiner b");
} else if (a > b) {
// Noch ein teurer String-Vergleich
System.out.println("a ist groesser b");
} else{
System.out.println("a ist gleich
b");
}
Operationen mit einem boolean-Ergebnis sind also nicht besonders gut dazu geeignet, lange StringObjekte effizient miteinander zu vergleichen.
In Java werden String-Objekte deshalb mit Methoden verglichen, die einen int-Wert als Ergebnis
liefern (und nicht "nur einen boolean-Wert").
Beispiel-02: Mit int-Funktionen 3 Ergebnisse unterscheiden (funktioniert in Java)
1
2
3
4
5
6
7
8
9
10
11
String a = ... ; // Der Zielwert von a ist ein langer String
String b = ... ; // Der Zielwert von b ist ein langer String
int erg = a.compareTo(b);
// Ein teurer String-Vergleich
if (erg < 0) {
// Ein billiger int-Vergleich
System.out.println("a ist kleiner b");
} else if (erg > 0) {
// Noch ein billiger int-Vergleich
System.out.println("a ist groesser b");
} else{
System.out.println("a ist gleich
b");
}
Das int-Ergebnis des Aufrufs a.compareTo(b) ist
- negativ,
wenn das Objekt a kleiner als das Objekt b ist
- gleich 0,
wenn das Objekt a gleich
dem Objekt b ist
- positiv,
wenn das Objekt a größer als das Objekt b ist
Ein String-Objekt a ist kleiner als ein String-Objekt b, wenn
in einem Lexikon a vor b stehen muss (z.B. ist "Althochdeutsch" kleiner als "Altona" und
"Kunst" ist kleiner als "Kunstdünger" .
4. Vergleichsfunktionen und Schnittstellen
Objekte bestimmter Klassen will man auf kleiner/gleich vergleichen, bei Objekten anderer Klassen ist
das nicht nötig oder nicht sinnvoll. Mit der Schnittstelle Comparable kann man ausdrücken, dass die
Objekte einer Klasse auf kleiner/gleich vergleichbar sind.
Die Schnittstelle Comparable ist wie folgt vereinbart (alle Kommentare wurden hier weggelassen):
1 public interface Comparable<T> {
2
public int compareTo(T ob);
3 }
Diese Schnittstelle ist generisch mit einem Typ-Parameter T. Man kann mit ihr unbegrenzt viele Typen
definieren, indem man T durch einen Referenztyp ersetzt, z.B. die folgenden Typen:
Comparable<Boolean>, Comparable<BigInteger>, Comparable<String>, ... .
Die Klasse String implementiert den Typ (oder: die Schnittstelle) Comparable<String>, etwa
so:
Beuth Hochschule
Objekte auf kleiner/größer vergleichen
SS13, S. 3
Ausschnitte aus der Vereinbarung der Klasse String:
...
110 public final class String
111
implements java.io.Serializable, Comparable<String>, CharSequence
112 {
...
1175
public int compareTo(String that) {
1176
int len1 = count;
1177
int len2 = that.count;
1178
int n = Math.min(len1, len2);
...
1204
return len1 - len2;
1205
}
...
3077 }
Weil die Klasse String die Schnittstelle Comparable<String> implementiert, sind alle StringObjekte gleichzeitig auch vom Typ Comparable<String>, und der Ausführer ist sicher, dass jedes
Comparable<String>-Objekt eine öffentliche Methode namens compareTo mit dem Ergebnistyp
int und einem (!) Parameter vom Typ String enthält.
Sind s1 und s2 String-Variablen, dann darf man diese Methode compareTo z.B. so aufrufen:
int
int
int
int
n1
n2
n3
n4
=
=
=
=
s1.compareTo(s2);
s2.compareTo(s1);
s1.compareTo("Hallo");
"Hallo".compareTo(s1);
Würde die Klasse String (statt der Schnittstelle Comparable<String>) z.B. die Schnittstelle
Comparable<BigInteger> implementieren, dann müsste die Methode compareTo (in Zeile
1175 bis 1205) einen Parameter vom Typ BigInteger haben, und man könnte dann nur ein
String-Objekt mit einem BigInteger-Objekt vergleichen, aber nicht ein String-Objekt mit einem String-Objekt.
Regel: Wenn die Objekte einer Klasse Karl mit Objekten derselben Klasse Karl auf kleiner/größer
vergleichbar sein sollen, dann sollte die Klasse Karl die Schnittstelle Comparable<Karl> implementieren (so, wie die Klasse String die Schnittstelle Comparable<String> implementiert).
Ausnahme: Die Klasse GregorianCalender implementiert nicht die Schnittstelle
Comparable<GregorianCalender>, sondern die Schnittstelle Comparable<Calender>.
Solche Ausnahmen sind aber selten und werden hier außer Acht gelassen.
5. Mehrere Vergleichsmethoden für eine Klasse
Die Methode compareTo in einem String-Objekt this vergleicht das this-Objekt mit ihrem Parameter that, und zwar lexikografisch.
Hinweis: Was lexikografisch bedeutet wird z.B. bei Wikipedia korrekt erläutert. An einigen anderen
Stellen im Netz wird die lexikografische Ordnung mit der lexikalischen Ordnung verwechselt oder auf
andere Weise falsch beschrieben.
Was kann/sollte man machen, wenn man String-Objekte nach einer anderen Ordnung als der lexikografischen vergleichen will? Oder allgemeiner: Wie kann man die Objekte einer eigenen Klasse Karl
mit mehreren verschiedenen Vergleichsmethoden ausrüsten?
Jede Klasse kann nur eine Comparable-Schnittstelle implementieren (entweder
Comparable<String> oder Comparable<Karl> oder ... aber nicht mehrere). Somit kann man in
einer Klasse nur eine compareTo-Methode vereinbaren, die das this-Objekt mit ihrem that-Parameter vergleicht. Die durch diese compareTo-Methode definierte Ordnung nennt man auch die natürliche Ordnung auf den Objekten der Klasse. Der Programmierer sollte seine compareTo Methoden so
schreiben, dass diese Bezeichnung gerechtfertigt ist.
S. 4, SS13
5. Mehrere Vergleichsmethoden für eine Klasse
Beuth-Hochschule
Weitere (Ordnungen und) Vergleichsmethoden kann man mit Hilfe der Schnittstelle
java.util.Comparator<K> vereinbaren. Diese Schnittstelle ist wie folgt vereinbart (auch hier
wurden umfangreiche Kommentare weggelassen):
1 package java.util;
2
3 public interface Comparator<T> {
4
int
compare(T ob1, T ob2);
5
boolean equals (Object ob);
6 }
Auch diese Schnittstelle ist generisch mit einem Typ-Parameter T. Deshalb kann man auch mit ihr unbegrenzt viele Typen definieren, indem man T durch einen Referenztyp ersetzt, z.B. die folgenden Typen:
Comparator<String>, Comparator<BigInteger>, Comparator<Karl>, ... .
Die equals-Methode in der Schnittstelle dient zum Vergleichen vom Comparator-Objekten und wir
können sie hier ignorieren.
Um für das Vergleichen von Karl-Objekten eine weitere kleiner/größer-Vergleichsmethode zu vereinbaren, kann man so vorgehen:
1. Man vereinbart eine neue Klasse, die die Schittstelle Comparator<Karl> implementiert, etwa so:
1 class Veronika implements Comparator<Karl> {
2
public int compare(Karl ob1, Karl ob2) {
3
... // hier kommen die Befehle hin, die ob1 und ob2 vergleichen
4
... // und (mit return) einen int-Wert als Ergebnis zurückgeben
5
}
6 }
In dieser Klasse braucht man also nur eine compare-Methode zu vereinbaren, sonst nichts.
Merke: Die compareTo-Methode der Schnittstelle Comparable hat nur einen Parameter. Die
compare-Methode der Schnittstelle Comparator hat dagegen zwei Parameter!
2. Man vereinbart an einer passenden Stelle (z.B. in der Klasse Karl oder anderswo) ein Objekt der
Klasse Veronika, etwa so:
static Veronika v01 = new Veronika();
3. Jetzt können wir zwei Karl-Variablen k1 und k2 z.B. wie folgt vergleichen:
int erg = v01.compare(k1, k2);
Dadurch sollte erg mit einer negativen Zahl, mit der Zahl 0 bzw. mit einer positiven Zahl initialisiert
werden, je nachdem ob das Objekt k1 im Vergleich zum Objekt k2 kleiner, gleich oder größer ist. Wenn
das nicht so ist, haben wir die compare-Methode in den Zeilen 2-5 nicht richtig programmiert und sollten sie verbessern.
Klassen wie Veronika (die die Schnittstelle Comparator<Karl> implementieren) können wir beliebig viele vereinbaren, jede mit einer anderen compare-Methode für Karl-Objekte.
6. Groß-/Kleinschreibung bei String-Vergleichen ignorieren
Die Klasse String enthält unter anderem
1. eine private Klasse namens CaseInsensitiveComparator, die die Schnittstelle
Comparator<String> implementiert und
2. ein Objekt dieser Klasse (genauer: Ein öffentliches Klassen-Attribut (public static field) namens
CASE_INSENSITIVE_ORDER vom Typ CaseInsensitiveComparator).
Dieses Attribut kann man z.B. so benutzen:
int n = String.CASE_INSENSITIVE_ORDER.compare("HALLO", "hallo");
Diese Vereinbarung wird n mit dem Wert 0 initialisieren, weil "HALLO" und "hallo" gleich sind,
wenn man Groß-/Kleinschreibung ignoriert.
Herunterladen