5.7 CharSequence als Basistyp
Bisher kennen wir die Klassen String, StringBuilder und StringBuffer, um Zeichenketten zu speichern und weiterzugeben. Ein String ist ein Wertobjekt und ein wichtiges Hilfsmittel in Programmen, da durch ihn unveränderliche Zeichenkettenwerte repräsentiert werden, während StringBuilder/StringBuffer veränderliche Zeichenfolgen umfassen.
Aber wie sieht es aus, wenn eine Teilzeichenkette gefordert ist, bei der es egal sein soll, ob das Original als String-, StringBuffer- oder StringBuilder-Objekt vorliegt? Und was ist, wenn nur lesender Zugriff gestattet sein soll, sodass Veränderungen ausgeschlossen sind? Eine Lösung ist, alles als ein String-Objekt zu erwarten (und das macht die Java-Bibliothek auch). Doch dann müssen die Programmteile, die intern mit StringBuilder/StringBuffer arbeiten, erst einen neuen String konstruieren, und das kostet Ressourcen.
Zum Glück besitzen die Klassen String sowie StringBuilder/StringBuffer einen gemeinsamen Basistyp CharSequence. Dieser Typ steht für eine unveränderliche, nur lesbare Sequenz von Zeichen. (Schnittstellen und Basistypen sowie Implementierungen werden präziser in Abschnitt 7.7, »Schnittstellen«, vorgestellt.) Methoden müssen sich also nicht mehr für konkrete Klassen entscheiden, sondern können einfach ein CharSequence-Objekt als Argument akzeptieren oder als Rückgabe weitergeben. Als Rückgabe ist CharSequence eher unpraktisch, hier ist String praktischer. Ein String und ein StringBuilder/StringBuffer-Objekt können zwar mehr, als CharSequence vorschreibt, beide lassen sich aber als CharSequence einsetzen, wenn das »Mehr« an Funktionalität nicht benötigt wird.
[zB] Beispiel
Soll eine Methode eine Zeichenkette bekommen und ist der Typ egal, so implementieren wir etwa
void process( CharSequence s ) {
…
}
statt dieser beiden Methoden:
void process( String s ) { … }
void process( StringBuilder sb ) {
process( sb.toString() );
}
Basisoperationen der Schnittstelle
Die Schnittstelle CharSequence schreibt für die implementierenden Klassen zwingend drei Methoden vor:
char charAt(int index)
Liefert das Zeichen an der Stelle index.int length()
Gibt die Länge der Zeichensequenz zurück.CharSequence subSequence(int start, int end)
Liefert eine neue CharSequence von start bis end. subSequence(…) liefert ein CharSequence-Objekt als Rückgabe – die Methode macht im Prinzip nichts anderes als ein substring( begin, end) bei einem String, nur der Rückgabetyp ist anders.
Die Methode, die sowieso schon über die absolute Oberklasse java.lang.Object vorhanden ist, ist:
String toString()
Gibt einen String der Sequenz zurück. Die Länge des toString()-Strings entspricht genau der Länge der Sequenz.
Unter anderem sind String/StringBuilder/StringBuffer implementierende Klassen der Schnittstelle CharSequence und realisieren diese Methoden.
class java.lang.String implements CharSequence, ...
class java.lang.StringBuilder implements CharSequence, ...
class java.lang.StringBuffer implements CharSequence, ...
CharSequence subSequence(int beginIndex, int endIndex)
Liefert eine nur lesbare Teilzeichenkette.
Statische compare(…)-Methode in CharSequence
Seit Java 11 gibt es in CharSequence eine neue Methode compare(…), die zwei CharSequence-Objekte lexikografisch vergleicht.
interface java.lang.CharSequence
static int compare(CharSequence cs1, CharSequence cs2)
Vergleicht die beiden Zeichenketten lexikografisch.
Die statische Methode hat den Vorteil, dass nun alle Kombinationen von CharBuffer, Segment, String, StringBuffer, StringBuilder mit nur dieser einen Methode geprüft werden können. Und wenn der Vergleich 0 ergibt, so wissen wir auch, dass die Zeichenfolgen die gleichen Zeichen enthalten.
Default-Methoden in der Schnittstelle CharSequence *
Die Schnittstelle hat seit Java 9 zwei Default-Methoden:
interface java.lang.CharSequence
default IntStream chars()
default IntStream codePoints()
Die Bedeutung von default und Schnittstellen im Allgemeinen wird in Kapitel 7, »Objektorientierte Beziehungsfragen«, erörtert. An dieser Stelle wollen wir nur den Vorteil betonen, dass chars() gut dafür verwendet werden kann, über die Zeilen zu laufen. Allerdings hat das nichts mit dem erweiterten for zu tun, sondern mit einem anderen Programmieridiom.
[zB] Beispiel
Laufe über eine Zeichenkette, und gib jedes Zeichen aus:
"Drama is life with the dull bits left out. (Hitchcock)".chars().forEach( c ->
System.out.print( (char) c )
);
Das Beispiel greift hier syntaktisch schon weit voraus und nutzt sogenannte Lambda-Ausdrücke, die ausführlich in Kapitel 12, »Lambda-Ausdrücke und funktionale Programmierung«, vorgestellt werden.