Universität Hamburg - Fachbereiche - Fachbereich Mathematik

Java-Kurs (4)

WiSe 01/02

Bodo Werner

Zurück zum Inhaltsverzeichnis.

4. Zeichenketten und Felder

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.

4.1 Zeichenketten: Der Datentyp String

Durch

String s="Java macht Freude";
System.out.println(s);
wird eine Variable namens s vom Typ einer Zeichenkette deklariert und allokiert 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), s.Aufgabe 9. 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.

4.2 Felder

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 allokiert. 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 allokiert, 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 Allokierung 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*x^2+a3*x^3. 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=x^i
	    p*=x; //p=x^{i+1}
	}//Ende for
	return y;
    }//Ende kubischeF()
    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 x^i 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. Man kann sich 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]);

4.3 Deklaration und Intitialisierung

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;
  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;
  static void main(String[] args){
    System.out.println("s="+s);
  }
}
Ausgabe ist s=null. Es scheint eine automatische Initialisierung druch den "Null-String" gegeben zu haben - was auch immer das ist. Wir steigern uns:
 
//BspE 
class BspE{ 
  static double[] x;
  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 Allokierung 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: im nächsten Abschnitt werden Sie sehen, dass eine String-Variable als Parameter einer Methode vieles mit einer Variablen vom Grunddatentyp gemein haben. Das ist anders bei der Klasse StringBuffer, die in diesem Kurs nicht behandelt wird.

Weiter mit Methoden, Teil 3.