String, [ ]
Zeichenketten und Felder (engl. arrays) sind wichtige
Datentypen, die in Java viele Eigenschaften einer Klasse haben. Variable dieses
Typs haben sehr viel mit Objekten gemeinsam. Dadurch touchieren wir in diesem
Abschnitt einige Aspekte des OOP.
Durch
String s="Java macht Freude";
System.out.println(s);
wird eine Variable namens s vom Typ einer Zeichenkette deklariert und
alloziert und danach ausgegeben. Wir wissen schon, dass wir Zeichenketten mit dem
Operator + verketten können. Es gibt viele Methoden
der Klasse java.lang.String, von denen wir - stellvertretend - die
Methode equals() herausgreifen:
//equals()
static boolean vergleiche(String s, String t){
return s.equals(t);
}
Die Punktnotation in der return-Zeile, die die Methode
equals() an die Variable s anheftet, deutet darauf hin,
dass equals()eine Methode der Klasse String
ist. Sie hat einen Parameter des Typs String und einen
Rückgabewert vom Typ boolean. Jedes Objekt der
Klasse String kann diese Methode einsetzen - mit Hilfe der Punktnotation.
Natürlich hätte man auch die String-Variable t
bitten können, mittels t.equals(s) zu klären, ob sie mit
s identisch ist.
Will man Zahlen formatiert ausgeben, muss man die Zahlen als Zeichenketten "behandeln":
Mit double x=Math.sin(1.3456); wird aus dieser durch String
s=x+""; eine Zeichenkette. Jetzt kann man z.B. mit
int j=s.indexOf("."); die Position des Punktes
herausfinden. Eine weitere String-Methode ist
String substring(int m, int n). So bewirkt z.B.
String s="Java macht Freude";
System.out.println(s.substring(5,10));
die Ausgabe "macht". Jetzt ahnt man, dass Zeichenketten Felder von
Zeichen sein könnten, die bei 0 beginnend durchnummeriert (indiziert) sind -
ein guter Übergang zum nächsten Abschnitt.
Bemerkung: Es gibt eine Klasse StringBuffer,
die in jeder Beziehung den Charakter einer Klasse hat, weil Variable von diesem Typ reine
Referenzvariable sind, während bei Variablen vom Typ
String der "Inhalt" der Variablen untrennbar mit der
Variablen verbunden ist.
double[], int[], String[], new, ...
Stellen Sie sich vor, Sie haben einen "Satz" von Zahlen vor sich, den Sie weiterverarbeiten
wollen, z.B. 20 Temperaturwerte eines Patienten im Krankenhaus, gemessen an aufeinanderfolgenden
Zeitpunkten. In Java würde man diese Daten zu einem double-Feld
der Länge 20 zusammenfassen, indem man die Zahlen durchnummeriert, aber nicht
etwa von 1 bis 20, sondern von 0 bis 19!! Die Nummerierung bei
Feldern beginnt stets bei Null. Abstrakt kann man sagen, dass Felder Datentypen
sind, deren Variable indizierte Variable eines bestimmten Datentyps sind.
Wenn man die HörerInnen dieser LV durchnummeriert, so kann man ein Feld von Namen,
genauer ein Feld von Zeichenketten einführen. Nennen wir dieses Feld HoererIn,
so kann man mit HoererIn[j] auf den Namen der HörerIn Nr. j
zugreifen. Felder in Java erkennt man an den eckigen Klammern.
Das ganze kann so (in einem main()-Block) aussehen:
String[] hoererIn={
"Betty", "Frank", "Rafaela", "Torsten"};
System.out.println(
"HoererIn Nr. 2 heisst "+hoererIn[1]);
In Zeile 1 wird das Feld zugleich deklariert und alloziert. Das Feld hat die Länge 4,
seine Komponenten sind hoererIn[0], hoererIn[1], hoererIn[2], hoererIn[3].
Die Länge des Feldes kann auch mit Hilfe von hoererIn.length
ermittelt werden. An dieser Punktnotation erkennt man, dass Felder Objekte sind. Dies
wird erst recht deutlich, wenn man Felder alloziert, ohne deren Komponenten schon
festzulegen. Dies geschieht mit dem anfangs kryptischen und gewöhnungsbedürftigen
new-Operator:
String[] hoererIn;
hoererIn = new String[4];
hoererIn[0]="Betty";
...
Die erste Zeile ist die Deklaration, die zweite die Allokation des
Feldes. Dies kann auch wie bei den Grunddatentypen simultan geschehen:
String[] hoererIn = new String[4];
hoererIn[0]="Betty";
...
In der (Numerischen) Mathematik wimmelt es von double-Feldern, die
auch Vektoren bei eindimensionalen Feldern oder Matrizen
bei zweidimensionalen Feldern heißen.
Will man die Komponenten eines Vektors aufaddieren, kann dies mit
folgender Methode geschehen:
double summe(double[] x){
double s=0;
int n=x.length;
for (int i=0;i<n;i++) s+=x[i];
return s;
}//Ende summe()
Etwas komplizierter ist das folgende Beispiel: Eine kubische Funktion
hat vier Koeffizienten, allgemein lautet ihre Abbildungsvorschrift
f(x)=a0+a1*x+a2*x2+a3*x3. Es ist also naheliegend, die
Koeffzienten durch ein Feld reeller Zahlen der Länge 4
innerhalb einer Methode zu erfassen:
class Feld1{
static double kubischeF(double[] a, double x){//Methode
double y=0; double p=1;
for (int i=0;i<4;i++) {
y+=a[i]*p;//p=xi
p*=x; //p=x{i+1}
}//Ende for
return y;
}//Ende kubischeF()
public static void main(String[] args){
double[] a = {0.1, 2.3, -2, 4};
double x=0;
while (x<2) {
System.out.println(kubischeF(a,x));
x+=0.2;
}//Ende while
}//Ende main()
}//Ende Feld1
Achten Sie auf die rekursive Berechnung der Potenz
xi innerhalb der Methode
kubischeF(). Letztere besitzt
zwei Parameter (das Koeffizientenfeld und der Punkt, an dem
ausgewertet werden soll) und den Rückgabewert double.
Die Länge des Feldes a muss der Methode
kubischeF nicht bekannt sein. Die Länge sollte aber
mindestens 4 sein! Ist sie kleiner, so kann das Programm erfolgreich
kompiliert werden, es gibt aber einen Laufzeitfehler mit der
Fehlermeldung java.lang.ArrayIndexOutOfBoundsException.
Zweidimensionale Felder sind eindimensionale Felder,
deren Komponenten selbst wieder eindimensionale Felder sind (die nicht
die gleiche Länge haben müssen!). Man kann sich (zur besseren Vorstellung)
eine rechteckige Tabelle vorstellen, bei denen man sich die Zeilen
indiziert vorstellt: a[j] sei z.B. die j-te Zeile. Dann
ist a[j][k] die k-te Komponente der j-ten Zeile. In der
Mathematik redet man von Matrizen. So kann eine Matrix mit 3
Zeilen und 4 Spalten folgendermaßen in einem Java-Programm
dargestellt werden:
double[][] a={{0.8, -0.7, 0.1, 2.1},
{9,0, -1, 2.2},
{-2, 1.1, 0, -8}};
System.out.println( "In Zeile 2 und Spalte 1 steht"+a[1][0]);
Wir wissen schon, dass wir jede Variable deklarieren müssen: der
Bezeichner und der Typ der Variable werden festgelegt. Ist
//BspA
double x;
System.out.println("x="+x);
Teil eines main()-Blocks, so gibt es die
Compilerfehlermeldung x may not have been
initialized. Hier wurde eine lokale Variable der Methode
main() zwar deklariert, aber nicht initialisiert,
d.h. mit einem Inhalt versehen. Ganz entsprechende Fehlermeldungen
gibt es bei der Deklaration von Variablen anderer Typen wie
String s; oder double[] x;. In
//BspB
double[] x = new double[4];
System.out.println("x[0]="+x[0]);
jedoch wird das Feld gleichzeitig deklariert und initialisiert und
den 4 Komponenten der Wert 0.0 zugewiesen. Die Verwendung des new-Operators
dient also der Initialisierung.
Ein bisschen anders sieht dies bei globalen Variablen aus. Fangen wir mit
//BspC
class BspC{
static double x;
public static void main(String[] args){
System.out.println("x="+x);
}
}
an. Hier wird keine fehlende Initialisierung angemahnt. Diese wurde
automatisch vorgenommen, wie man an der Ausgabe x=0.0 erkennt.
Die Verwirrung wird größer in
//BspD
class BspD{
static String s;
public static void main(String[] args){
System.out.println("s="+s);
}
}
Ausgabe ist s=null. Es scheint eine automatische
Initialisierung durch den "Null-String" gegeben zu haben - was auch
immer das ist.
Wir steigern uns:
//BspE
class BspE{
static double[] x;
public static void main(String[] args){
System.out.println("x[0]="+x[0]);
}
}
Das Programm kann compiliert werden, erst das Laufen ergibt den Laufzeitfehler
java.lang.NullpointerException. Um dies zu verstehen,
muss man das Pointer-Konzept bzw. das Konzept der Zeigervariablen
( Referenzvariable ) verstanden haben. Durch
die Zeile static double[] x; wird das double-Feld
x nur deklariert, aber nicht initialisiert. Der Laufzeitfehler hätte
etwa mit gleichzeitiger Initialisierung durch
static double[] x = new double[4]; vermieden
werden können. Initialisierung heißt, dass ein Speicherplatz für den Inhalt
der Variablen geschaffen wird. Bei der reinen Deklaration ist die Länge des Feldes
noch unbekannt, es gibt noch keinen Sinn Speicherplatz zu reservieren. Nun ist das double-Feld
x in dem Sinn eine Zeigervariable,
dass diese auf den Speicherplatz des Inhalts zeigt. Solange die Initialisierung noch
nicht erfolgt ist, weiß die Zeigervariable nicht, wohin sie zeigen soll, sie
ist ein Nullpointer. Wird nun der Inhalt des
Speicherplatzes eines Nullpointers erfragt (wie in BspE), so gibt es
den obigen Laufzeitfehler.
Alle Variablen der Grunddatentypen sind keine Zeigervariablen. Bei
ihrer Deklaration wird gleichzeitig Speicherplatz geschaffen und ihr
Inhalt vorbesetzt (z.B. durch null). Dennoch gibt es auch bei diesen
zuweilen (z.B. bei Verwendung als lokale Variable) Compilerfehler, die
eine Initialisierung anmahnen (s. BspA). Feldvariable und alle Objekte
(Variable von Klassen) sind Zeigervariable. Das zu verstehen, ist
ausgesprochen hilfreich. Mehr dazu im nächsten Abschnitt ( Referenzvariable ).
Übrigens spricht man auch von Allokation statt von Initialisierung.
Ein letztes Wort zu den String-Variablen. Auch diese sind
Zeigervariable, der Null-String ist ein Null-Pointer -
warum, können Sie herausfinden, indem Sie etwa versuchen mit
Hilfe von s.length() die Länge
eines deklarierten, aber nicht initialisierten Strings zu
erfragen. Allerdings ist ihr "Inhalt" unlösbar verbunden mit
ihrer Deklaration: später werden Sie sehen, dass
eine String-Variable als Parameter einer Methode vieles mit einer
Variablen vom Grunddatentyp gemein hat. Das ist anders bei der
Klasse StringBuffer, die in diesem
Kurs nicht behandelt wird.
|