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.