Zurück   Weiter

5 Objektorientiertes Programmieren mit Java

5.1 Klassen

Java ist eine rein objektorientierte Programmiersprache. Dies bedeutet, dass alle Elemente eines Programm in Klassen definiert sein müssen. Auch das Programm selber muss als Klasse definiert sein. Bei unseren bisherigen kleinen Programmen war der Klassenname gleich dem Namen des Programms.

5.1.1 Klassendefinition

Die Definition einer Klasse haben wir schon im Kapitel Das Javaprogramm kennengelernt.

  class KlassenName {
    ...
    ...
  }

Bis jetzt haben wir in die Klassendefinition lediglich die Methode public static void main(String[] args) eingefügt um ein Programm starten zu können. In erster Linie wird eine Klassendefinition dazu verwendet um die Definition eines Objektes zu implementieren. Dazu werden in einer Klasse Member Variablen, Konstruktoren und Methoden definiert.

5.1.2 Membervariablen

Membervariablen dienen dazu den Zustand eines Objekts zu speichern. Diese Variablen sind im ganzen Bereich der Klasse gültig. Dazu werden sie direkt im Anweisungsblock der Klasse definiert.

  class KlassenName {
    variablenTyp memberVariable1;
    variablenTyp memberVariable2;

    ...
  }

Üblicherweise stehen die Vereinbarungen von Membervariablen am Anfang einer Klassendefinition.

class Point1 {
  int xPosition;
  int yPosition;
}

Sourcefile

5.1.3 Konstruktoren

In Konstruktoren werden alle nötigen Initialisierungen einer Klasse durchgeführt, d.h. den Membervariablen werden die ersten Werte zugewiesen. Sie werden benötigt um Instanzen von Klassen zu erzeugen, mit denen man 'arbeiten' kann. Konstruktoren werden in einer Klassendefinition nach den Membervariablen definiert.

  class KlassenName {
    variablenTyp memberVariable1;
    variablenTyp memberVariable2;

    KlassenName(Parameterliste) {
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    ...
  }

Die Parameterliste hat folgenden Aufbau:

  variablenTyp1 variablenName1, variablenTyp2 variablenName2, ..., ...

In dieser Liste werden die Variablen festgelegt, welche der Konstruktor zur Ausführung braucht. In der Regel sind dies Variablen, welche den Anfangszustand eines Objekts, d.h. die Anfangswerte der Membervariablen bestimmen oder zumindest beeinflussen. Eine Parameterliste kann aus beliebig vielen Variablen bestehen. Die runden Klammern in der Konstruktordefinition müssen aber auch bei leerer Liste angegeben werden. Wenn ein in der Parameterliste vereinbarter Variablennamen mit dem Namen einer Membervariablen übereinstimmt, so kann auf die Membervariable in diesem Konstruktor nicht direkt zugegriffen werden.

class Point2 {
  int xPosition;
  int yPosition;

  Point2(int xPos, int yPos) {
    xPosition = xPos;
    yPosition = yPos;
  }
}

Sourcefile

In vielen Fällen ist es sinnvoll mehrere verschiedene Konstruktoren zu definieren um unterschiedliche Initialisierungsmöglichkeiten zu besitzen. So kann man z.B. einen zusätzlichen Konstruktor ohne Parameter zu definieren, in welchem man Defaulteinstellungen vorgenommen werden.

class Point3 {
  int xPosition;
  int yPosition;

  Point3() {
    xPosition = 0;
    yPosition = 0;
  }

  Point3(int xPos, int yPos) {
    xPosition = xPos;
    yPosition = yPos;
  }
}

Sourcefile

In einer Klasse können beliebig viele Konstruktoren definiert werden. Sie müssen sich in der Parameterliste unterscheiden. Das bedeutet es müssen andere Variablentypen gewählt werden und/oder die Anzahl der Parameter muss sich geändert haben.

5.1.4 Erzeugen einer Instanz einer Klasse

Um mit einer Klasse arbeiten zu können, benötigt man eine Instanz dieser Klasse. Diese Instanz wird mit dem new-Operator erzeugt:

  class KlassenName {
    variablenTyp memberVariable1;
    variablenTyp memberVariable2;

    KlassenName(Parameterliste1) {
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    KlassenName(Parameterliste2) {
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    ...

    public static void main(String[] args) {
      KlassenName variablenName = new KlassenName(ParameterListe1);
      KlassenName variablenName = new KlassenName(ParameterListe2);
    }
  }

Mit dem new-Operator wird der Konstruktor der Klasse aufgerufen, bei dem die Parameterliste

  variablenWert1, variablenWert2, variablenWert3, ...

in Anzahl, Typ und Reihenfolge mit der im Konstruktor definierten Parameterliste übereinstimmt.

class Point4 {
  int xPosition;
  int yPosition;
  
  Point4() {
    System.out.println("entering Point4.<init>()");
    xPosition = 0;
    yPosition = 0;
  }

  Point4(int xPos, int yPos) {
    System.out.println("entering Point4.<init>(xPos, yPos)");
    xPosition = xPos;
    yPosition = yPos;
  }

  public static void main(String[] args) {
    Point4 p1 = new Point4();
    Point4 p2 = new Point4(5, 8);
  }
}
Sourcefile

Aufgabe 10

Es spielt keine Rolle ob der Aufruf des Konstruktors aus der eigenen oder einer fremden Klasse geschieht. Der Compiler durchsucht den ihm angegebenen classpath nach Klassendefinitionen, welche sich nicht in der gleichen Datei befinden.
Line1.java:

class Line1 {
  Point4 start;
  Point4 end;

  Line1(Point4 p1, Point4 p2) {
    System.out.println("entering Line1.<init>(p1, p2)");
    start = p1;
    end = p2;
  }
}

Sourcefile

Paint1.java:

class Paint1 {
  public static void main(String[] args) {
    Point4 p1 = new Point4(1, 3);
    Point4 p2 = new Point4(8, 10);
    Line1 graph = new Line1(p1, p2);
  }
}
Sourcefile

Aufgabe 11

5.1.5 Default-Konstruktor

Um eine Instanz eines Objektes zu erzeugen, wird immer ein Konstruktor aufgerufen. Aus diesem Grund besitzt jede in java definierte Klasse einen Default-Konstruktor. Dieser Default-Konstruktor besitzt eine leere Parameterliste und einen leeren Anweisungsblock.

  class KlassenName {
    ...

    // Dieser Konstruktor ist als Default in jedem Java-Objekt definiert.
    // Er kann deshalb auch weggelassen werden.
    KlassenName() {
    }

    ...
  }

Die nächste Klassendefinition beschreibt die gleiche Klasse:

  class KlassenName {
    ...

    ...
  }

Sobald man einen eigenen Konstruktor in einer Klasse definiert, existiert der Default-Konstruktor nicht mehr.

  class KlassenName {
    ...

    // In dieser Klasse existiert nun kein Konstruktor mit
    // leerer Parameterliste mehr.
    KlassenName(ParameterListe) {
      ...
    }

    ...
  }

Möchte man in einer Klasse, neben anderen Konstruktoren, einen Konstruktor mit leerer Parameterliste haben, so muss man diesen definieren, auch wenn er einen leeren Anweisungblock besitzt.

  class KlassenName {
    ...

    KlassenName(ParameterListe) {
      ...
    }

    KlassenName() {
    }

    ...
  }

5.1.6 Zugriff auf Membervariablen

Mit einer Instanz einer Klasse kann man arbeiten. Das bedeutet man kann auf die Membervariablen einer Klasse zugreifen und diese verändern.

  class KlassenName {
    variablenTyp memberVariable1;
    variablenTyp memberVariable2;

    KlassenName(Parameterliste) {
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    public static void main(String[] args) {
      KlassenName name = new KlassenName(ParameterListe);
      name.memberVariable1 = wert3;
      name.memberVariable2 = wert4;
    }

    ...
  }

Normalerweise ist es in der Objektorientierten Programmierung aber nicht üblich direkt auf die Membervariablen einer Klasse zuzugreifen. Mit den Zugriffsklassen ist es möglich einen Zugriff auf Membervariablen zu verbieten.

class Paint2 {
  public static void main(String[] args) {
    Point4 p1 = new Point4(1, 3);
    Point4 p2 = new Point4(8, 10);
    p1.xPosition = -86;
    p2.xPosition = 99;
    Line1 graph = new Line1(p1, p2);
    System.out.println("start.x = " + graph.start.xPosition);
    System.out.println("end.x = " + graph.end.xPosition);
  }
}
Sourcefile

Aufgabe 12

5.2 Methoden

Methoden werden verwendet, um Funktionalitäten einer Klasse nach aussen sichtbar zu machen oder den Sourcecode in kleine logische Einheiten zu unterteilen. Es wird damit versucht den Sourcecode übersichtlicher zu gestalten um Fehlersuche und Wartung zu vereinfachen. Eine Methode kann auch Berechnungen oder Arbeitsschritte durchführen und das Ergebnis an den Aufrufer zurückliefern.

5.2.1 Methodendefinition

Zu einer Methodendefinition gehört die Angabe eines Returntyps, der Methodenname und eine Parameterliste.

  class KlassenName {
    variablenTyp memberVariable1;
    variablenTyp memberVariable2;

    KlassenName(Parameterliste) {
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    returnTyp methodenName1(ParameterListe1) {
      ...
      return berechneterWert;
    }

    void methodenName2(ParameterListe2)  {
      ...
    }
    ...
  }

Der Returntyp gibt an von welchem Datentyp das Ergebnis ist, welches an die aufrufende Methode zurückgegeben wird. Gibt eine Methode keinen Wert zurück, so muss als Returntyp void angeben werden. Der Name der Methode besteht wie Klassennamen und Variablennamen aus alphanumerischen Zeichen und es gelten die selben Bedingungen. In der Namenskonvention wird empfohlen, dass Methodennamen mit kleinen Buchstaben beginnen und bei zusammengesetzten Wörtern jedes eigenständige Wort mit einem Grossbuchstaben beginnt. Die Parameterliste hat den gleichen Aufbau wie bei den Konstruktoren.

  variablenTyp1 variablenName1, variablenTyp2 variablenName2, ..., ...

Der Anweisungsblock enthält alle Anweisungen, welche von der Methode ausgeführt werden sollen. Um den Sourcecode übersichtlich und gut lesbar zu gestalten, sollte man versuchen nicht zu viele Anweisungen in eine Methode zu packen. Bei grösseren Methoden ist es sinnvoll diese in mehrere Methoden mit sprechenden Namen aufzuteilen. Der Sourcecode wird so leichter lesbar und auch besser wartbar.
Ist ein Returntyp ungleich void definiert, so muss von der Methode ein Wert, ein Ergebnis zurückgegeben werden. Dieses Ergebnis wird mit dem Schlüsselwort return zurückgegeben.

  class KlassenName {
    ...
    returnTyp methodenName(ParameterListe) {
      returnTyp variableName = variablenWert;
      ...
      return variablenName;
    }
    ...
  }

Vergisst man die Angabe der return-Anweisung, so löst dies einen Compiler-Fehler aus. Verwendet man in einer Methode bedingte Anweisungen, so ist darauf zu achten, dass wirklich alle möglichen Fälle mit einer return-Anweisung beendet werden.

  class KlassenName {
    ...
    returnTyp methodenName(ParameterListe) {
      if(bedingung) {
        returnTyp variableName1 = variablenWert1;
        return variablenName1;
      } else {
        returnTyp variableName2 = variablenWert2;
        return variablenName2;
      }
    }
    ...
  }
Bei einer Methode mit dem Returntyp void kann return; angegeben werden um die Ausführung einer Methode vorzeitig zu beenden. Dies wird vor allem dazu verwendet Methoden vorzeitig abzubrechen, wenn ein ungewollter Zustand eingetreten ist.
  class KlassenName {
    ...
    void methodenName(ParameterListe) {
      ...
      if(unerwünschteBedingung) {
        return;
      }
      ...
    }
    ...
  }
Es ist möglich mehrere Methoden mit dem gleichem Namen aber unterschiedlichen Parameterlisten anzugeben. Der Compiler entscheidet bei einem Methodenaufruf anhand der Liste der übergebenen Parameter, welche der Methoden er aufzurufen hat. Es ist aber nicht möglich gleichnamige Methoden mit gleichen Parameterlisten aber unterschiedlichen Returntypen zu definieren. Dies kann vom Compiler nicht aufgelöst werden.

class Point5 {
  int xPosition;
  int yPosition;

  Point5() {
    xPosition = 0;
    yPosition = 0;
  }

  Point5(int xPos, int yPos) {
    xPosition = xPos;
    yPosition = yPos;
  }

  int getXPosition() {
    return xPosition;
  }

  int getYPosition() {
    return yPosition;
  }

  void moveTo(int xPos, int yPos) {
    xPosition = xPos;
    yPosition = yPos;
  }
}

Sourcefile

5.2.2 Methodenaufruf

Eine Methode wird durch die Angabe ihres Names und einer der Definition entsprechenden Parameterliste aufgerufen. Hat die Methode einen Returntyp ungleich void, so kann der Methodenaufruf direkt einer Variablen des Datentyps 'Returntyp' zugewiesen werden.

  class KlassenName {
    returnTyp memberVariable1;
    variablenTyp memberVariable2;

    KlassenName(Parameterliste) {
      memberVariable1 = methodenName1(ParameterListe1);
      memberVariable2 = wert2;
      methodenName2(ParameterListe2);
      ...
    }

    returnTyp methodenName1(ParameterListe1) {
      ...
      return berechneterWert;
    }
    
    void methodenName2(ParameterListe2) {
      ...
    }

    ...
  }
class Point6 {
  int xPosition;
  int yPosition;

  Point6() {
    moveTo(0, 0);
    System.out.println("xPosition = " + getXPosition() + ", yPosition = " + getYPosition());
  }

  Point6(int xPos, int yPos) {
    moveTo(xPos, yPos);
    System.out.println("xPosition = " + getXPosition() + ", yPosition = " + getYPosition());
 }

  int getXPosition() {
    return xPosition;
  }

  int getYPosition() {
    return yPosition;
  }

  void moveTo(int xPos, int yPos) {
    xPosition = xPos;
    yPosition = yPos;
  }

  public static void main(String[] args) {
    Point6 p1 = new Point6();
    Point6 p2 = new Point6(10, 13);
  }
}
Sourcefile

Aufgabe 13

5.2.2.1 Das pre- und Post-Increment/Decrement

Bei der Incrementierung/Decrementierung von ganzen Zahlen mit ++/-- werden, wenn die Incrementierung/Decrementierung in einem Methoden- oder Konstruktorenaufruf steht, zwei Fälle unterschieden.

5.2.2.1.1 Das pre-Increment/Decrement

Beim pre-Increment/Decrement stehen die Operatoren vor dem Argument. Die Incrementierung/Decrementierung wird durchgeführt bevor das Argument verwendet wird.

class Point7 {
  int xPosition;
  int yPosition;

  Point7() {
    moveTo(0, 0);
  }

  Point7(int xPos, int yPos) {
    moveTo(xPos, yPos);
    System.out.println("xPosition = " + getXPosition() + ", yPosition = " + getYPosition());
  }

  int getXPosition() {
    return xPosition;
  }

  int getYPosition() {
    return yPosition;
  }

  void moveTo(int xPos, int yPos) {
    xPosition = xPos;
    yPosition = yPos;
  }

  public static void main(String[] args) {
    int x = 10;
    new Point7(++x, 13);
    System.out.println("x = " + x);
  }
}
Sourcefile

5.2.2.1.2 Das Post-Increment/Decrement

Beim Post-Increment/Decrement stehen die Operatoren hinter dem Argument. Die Incrementierung/Decrementierung wird durchgeführt nachdem das Argument verwendet wurde.

class Point8 {
  int xPosition;
  int yPosition;

  Point8() {
    moveTo(0, 0);
  }

  Point8(int xPos, int yPos) {
    moveTo(xPos, yPos);
    System.out.println("xPosition = " + getXPosition() + ", yPosition = " + getYPosition());
  }

  int getXPosition() {
    return xPosition;
  }

  int getYPosition() {
    return yPosition;
  }

  void moveTo(int xPos, int yPos) {
    xPosition = xPos;
    yPosition = yPos;
  }

  public static void main(String[] args) {
    int x = 10;
    new Point8(x++, 13);
    System.out.println("x = " + x);
  }
}
Sourcefile

5.2.3 Aufruf von Methoden in Objektinstanzen

Um eine Methode in einer fremden Klasse aufzurufen, benötigt man eine Instanz dieses Objektes. Über diese Instanz der Klasse kann man auf die Methoden zugreifen.

  class KlassenName1 {
    returnTyp memberVariable1;
    variablenTyp memberVariable2;

    KlassenName(Parameterliste1) {
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    returnTyp methodenName1(ParameterListe2) {
      ...
      return memberVariable1;
    }

    void methodenName2(ParameterListe3)  {
      ...
    }

    ...
  }

  class KlassenName2 {
    ...

    public static void main(String[] args) {
      KlassenName1 name = new KlassenName1(ParameterListe1);
      returnTyp variable1 = name.methodenName1(ParameterListe2);
      name.methodenName2(ParameterListe3);
    }

    ...
  }

Dabei müssen die Parameterwerte in Typ und Anzahl mit den in der Methodendefinition vereinbarten Parametern übereinstimmen.
Point9.java:

class Point9 {
  int xPosition;
  int yPosition;

  Point9() {
    moveTo(0, 0);
  }

  Point9(int xPos, int yPos) {
    moveTo(xPos, yPos);
  }

  void moveTo(int xPos, int yPos) {
    xPosition = xPos;
    yPosition = yPos;
  }

  int getXPosition() {
    return xPosition;
  }

  int getYPosition() {
    return yPosition;
  }
}

Sourcefile

Line2.java:

class Line2 {
  Point9 start;
  Point9 end;

  Line2(Point9 startPoint, Point9 endPoint) {
    start = startPoint;
    end = endPoint;
  }

  void paint() {
    int minY;
    int maxY;
    if(start.getYPosition() > end .getYPosition()) {
      minY = end.getYPosition();
      maxY = start.getYPosition();
    } else {
      minY = start.getYPosition();
      maxY = end.getYPosition();
    }
    double m = (end.getYPosition() - start.getYPosition()) /
               (double)(end.getXPosition() - start.getXPosition());
    double b = -m * end.getXPosition() + end.getYPosition();
    for(int i = 0; i < minY; i++) {
      System.out.println();
    }
    for(int i = minY; i <= maxY; i++) {
      int xPos = (int)Math.round(1/m * (i - b));
      for(int j = 0; j < xPos; j++) {
        System.out.print(" ");
      }
      System.out.println("x");
    }
  }
}

Sourcefile

Paint3.java:

class Paint3 {
  public static void main(String[] args) {
    Point9 p1 = new Point9(1, 3);
    Point9 p2 = new Point9(8, 10);
    Line2 graph = new Line2(p1, p2);
    graph.paint();
    p2.moveTo(7, 1);
    graph.paint();
  }
}
Sourcefile

Aufgabe 14

5.3 Der Verweisoperator 'this'

Mit dem Operator this ist es möglich, auf die Instanz des Objekts zu verweisen, in dem dieser Operator aufgerufen wird. Anders formuliert: Der this-Operator zeigt immer auf die Instanz der Klasse, in der er aufgerufen wird. Mit this ist es möglich innerhalb einer Methode oder eines Konstruktors auf Variablennamen der Klassendefinition zuzugreifen, welche durch Variablennamen innerhalb der Methoden- oder Konstruktordefinition verdeckt sind.

  class KlassenName {
    variablenTyp variablenName1;
    variablenTyp variablenName2;

    KlassenName(variablenTyp variablenName1, variablenTyp variablenName2) {
      this.variablenName1 = variablenName1;
      this.variablenName2 = variablenName2;
      ...
    }

    ...
  }
class Point10 {
  int xPosition = 0;
  int yPosition = 0;

  Point10() {
    moveTo(0, 0);
  }

  Point10(int xPosition, int yPosition) {
    moveTo(xPosition, yPosition);
  }

  int getXPosition() {
    return xPosition;
  }

  int getYPosition() {
    return yPosition;
  }

  void moveTo(int xPosition, int yPosition) {
    System.out.println("xPosition = " + xPosition + ", this.xPosition = " + this.xPosition);
    System.out.println("yPosition = " + yPosition + ", this.yPosition = " + this.yPosition);
    this.xPosition = xPosition;
    this.yPosition = yPosition;
  }

  public static void main(String[] args) {
    Point10 p = new Point10(10, 13);
  }
}
Sourcefile

Mit this ist es auch möglich Konstruktoren innerhalb der eigenen Klasse aufzurufen. Dies wird häufig verwendet um z.B. einen leeren Konstruktor einzufügen und die Daten mit Defaultwerten zu füllen. Der Aufruf des eigenen Konstruktors muss in der ersten Programmzeile des Anweisungsblockes des Konstruktors stehen.

class Point11 {
  int xPosition = 0;
  int yPosition = 0;

  Point11() {
    this(0, 0);
    System.out.println("Im Konstruktor ohne Parameter");
  }

  Point11(int xPos, int yPos) {
    System.out.println("Im Konstruktor mit Parametern");
    moveTo(xPos, yPos);
  }

  int getXPosition() {
    return xPosition;
  }

  int getYPosition() {
    return yPosition;
  }

  void moveTo(int xPosition, int yPosition) {
    this.xPosition = xPosition;
    this.yPosition = yPosition;
  }

  public static void main(String[] args) {
    Point11 p1 = new Point11();
    Point11 p2 = new Point11(10, 13);
  }
}
Sourcefile

5.4 Vererbung

Die Vererbung wird in Java mit dem Schlüsselwort extends angezeigt. Die Elternklasse wird auch Superklasse genannt, die vererbte Klasse Subklasse. Jede Klasse kann nur von einer einzigen Superklasse erben. Durch die Vererbung erbt die Subklasse alle Eigenschaften, d.h. alle Methoden und Membervariablen, der Superklasse. Der neuen Klasse können weitere Eigenschaften hinzugefügt werden. Konstruktoren werden nicht vererbt.

  class KlassenName extends KlassenName_der_SuperKlasse {
    variablenTyp memberVariable1;
    variablenTyp memberVariable2;

    KlassenName(Parameterliste) {
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    ...
  }

So soll zum Beispiel die Klasse BasePoint den Ursprung eines Koordinatensystems darstellen und hat damit die Eigenschaft, sowohl als x-, als auch als y-Wert immer den Wert '0' zu besitzen.

class BasePoint1 extends Point10 {
  BasePoint1() {
    moveTo(0, 0);
  }

  public static void main(String[] args) {
    BasePoint1 base = new BasePoint1();
  }
}
Sourcefile

5.4.1 Überschreiben von Methoden

Geerbte Eigenschaften von Klassen, also Methoden und Membervariablen, können überschrieben, geändert werden. So muss in der Klasse BasePoint die Methode moveTo überschrieben werden, damit es nicht möglich ist, den Ursprung auf eine andere Position zu verschieben.
Point12.java

class Point12 {
  int xPosition = 0;
  int yPosition = 0;

  Point12() {
    this(0, 0);
  }

  Point12(int xPos, int yPos) {
    moveTo(xPos, yPos);
  }

  int getXPosition() {
    return xPosition;
  }

  int getYPosition() {
    return yPosition;
  }

  void moveTo(int xPosition, int yPosition) {
    System.out.println("entering Point12.moveTo");
    this.xPosition = xPosition;
    this.yPosition = yPosition;
  }
}

Sourcefile

BasePoint2.java

class BasePoint2 extends Point12 {
  BasePoint2() {
    moveTo(0, 0);
  }

  void moveTo(int xPosition, int yPosition) {
    System.out.println("entering BasePoint2.moveTo");
    this.xPosition = 0;
    this.yPosition = 0;
  }

  public static void main(String[] args) {
    BasePoint2 base = new BasePoint2();
  }
}
Sourcefile

5.4.2 Der Verweisoperator 'super'

Manchmal ist es nützlich oder nötig in Methoden oder Konstruktoren auf die Originaleigenschaften, die Methoden und Membervariablen der Superklasse, zugreifen zu können. Dies ist mit dem Schlüsselwort super möglich.

5.4.2.1 Aufruf von Konstruktoren der Superklasse

Ein beliebiger Konstruktor der Superklasse kann mit super in der ersten Zeile des Konstruktors der Subklasse aufgerufen werden.

  class KlassenName extends KlassenName_der_SuperKlasse {
    variablenTyp memberVariable1;
    variablenTyp memberVariable2;

    KlassenName(Parameterliste1) {
      super(ParameterListe2);
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    ...
  }

Beim Aufruf eines Konstruktors einer Klasse wird immer auch der Konstruktor der Superklasse aufgerufen, auch wenn dies nicht explizit angegeben wird.

class MeineSuperKlasse1 {
  MeineSuperKlasse1() {
    System.out.println("MeineSuperKlasse1.Konstruktor");
  }
}

Sourcefile

class MeineSubKlasse1 extends MeineSuperKlasse1 {
  MeineSubKlasse1() {
    System.out.println("MeineSubKlasse1.Konstruktor");
  }

  public static void main(String[] args) {
    MeineSubKlasse1 subKlasse = new MeineSubKlasse1();
  }
}
Sourcefile

Ruft man nicht explizit einen Konstruktor der Superklasse auf, so wird der Konstruktor ohne Parameter der Superklasse aufgerufen. Dies bedeutet aber auch, dass die Superklasse einen Konstruktor ohne Parameter besitzen muss Ansonsten muss einer der vorhandenen Konstruktoren der Superklasse mit super aufgerufen werden.

class MeineSuperKlasse2 {
  MeineSuperKlasse2(int dummyVariable) {
    System.out.println("MeineSuperKlasse2.Konstruktor");
  }
}

Sourcefile

class MeineSubKlasse2 extends MeineSuperKlasse2 {
  MeineSubKlasse2() {
    // hier muss der Konstruktor der Superklasse explizit aufgerufen
    // werden, da die Superklasse keinen Konstruktor mit leerer
    // Parameterliste besitzt.
    super(0);
    System.out.println("MeineSubKlasse2.Konstruktor");
  }

  public static void main(String[] args) {
    MeineSubKlasse2 subKlasse = new MeineSubKlasse2();
  }
}
Sourcefile

5.4.2.2 Aufruf von Methoden und Membervariablen der Superklasse

Mit super kann auch auf Methoden und Membervariablen der Superklasse zugegriffen werden.

  class KlassenName extends KlassenName_der_SuperKlasse {
    variablenTyp memberVariable1;
    variablenTyp memberVariable2;

    KlassenName(Parameterliste1) {
      super(ParameterListe2);
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    returnTyp methodenName1(ParameterListe1) {
      ...
      return super.methodenName1(ParameterListe1);
    }

    void methodenName2(ParameterListe2) {
      ...
      super.methodenName2(ParameterListe2);
    }

    ...
  }

Der Aufruf von Methoden und Membervariablen der Superklasse mit Hilfe von super wird benötigt, wenn diese in der Subklasse überschrieben wurden. Durch das Überschreiben sind die Methoden und Membervariablen der Superklasse verdeckt und können nicht direkt angesprochen werden.

class BasePoint4 extends Point12 {
  BasePoint4() {
    super(0, 0);
  }

  void moveTo(int xPosition, int yPosition) {
    System.out.println("entering BasePoint4.moveTo");
    super.moveTo(0, 0);
  }

  public static void main(String[] args) {
    BasePoint4 base = new BasePoint4();
  }
}
Sourcefile

Aufgabe 15

5.5 Die Klasse Object

Alle Klassen in Java haben als oberste Eltenklasse die Klasse Object. Auch Klassen bei denen keine Superklasse angegeben ist, erben implizit von der Klasse Object. Die Klassendefinition

  class KlassenName {
    ...
    ...
  }

ist gleichbedeutend mit

  class KlassenName extends Object {
    ...
    ...
  }

Wie wir im Laufe des Kurses noch sehen werden, ist es mit dieser allgemeinen Superklasse möglich allgemeingültige Klassen z.B. für Listen und ähnliches zu erzeugen. Ausserdem besitzt die Klasse Object einige nützliche Methodendefinitionen die bei Bedarf geeignet überschrieben werden können. Auch davon werden wir im Laufe des Kurses ein paar kennenlernen.

5.6 Die Java Packages

Klassen werden in Java in Packages eingeteilt. Der Name eines Packages stellt gleichtzeitig den Namen des Verzeichnisses dar, in dem sich die compilierte Klasse befindet. Package Namen werden im allgemeinen ausschliesslich in Kleinbuchstaben geschrieben. Man benutzt Packages vor allem um die Sourcecode-Dateien in logische Einheiten zusammenzufassen. Die Zuordnung einer Klasse zu einem Package, geschieht mit der package-Anweisung. Diese Anweisung muss in der ersten (nicht auskommentierten) Zeile einer Datei stehen. Wird kein Package angegeben, so befindet sich die Klasse im Default-package, dem aktuellen Verzeichnis im Verzeichnisbaum. Der Name einer Klasse, über den auf sie zugegriffen werden kann, setzt sich aus den Namen der Packages und ihrem eigenen Klassennamen zusammen. Wichtig ist, dass sich die compilierten Dateien auch im richtig bennanten Verzeichnis befinden, da sowohl der Compiler, als auch die JVM den Klassennamen auflösen und die Dateien in den entsprechenden Verzeichnissen suchen.

  package packagename;

  class KlassenName extends KlassenName_der_SuperKlasse {
    variablenTyp memberVariable1;
    variablenTyp memberVariable2;

    KlassenName(Parameterliste) {
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    ...
  }

Unsere Beispieldateien Point, BasePoint und Line werden wir jetzt in ein package zusammenfassen, dass wir graphic nennen. Das Zeichenprogramm Paint verschieben wir in das package programs.

Point13.java

package graphic;

class Point13 {
  int xPosition = 0;
  int yPosition = 0;

  Point13() {
    this(0, 0);
  }

  Point13(int xPos, int yPos) {
    moveTo(xPos, yPos);
  }

  int getXPosition() {
    return xPosition;
  }

  int getYPosition() {
    return yPosition;
  }

  void moveTo(int xPosition, int yPosition) {
    this.xPosition = xPosition;
    this.yPosition = yPosition;
  }
}

Sourcefile

BasePoint5.java

package graphic;

class BasePoint5 extends Point13 {
  BasePoint5() {
    super(0, 0);
  }

  void moveTo(int xPosition, int yPosition) {
    super.moveTo(0, 0);
  }
}

Sourcefile

Line3.java:

package graphic;

class Line3 {
  Point13 start;
  Point13 end;

  Line3(Point13 startPoint, Point13 endPoint) {
    start = startPoint;
    end = endPoint;
  }

  void paint() {
    int minY;
    int maxY;
    if(start.getYPosition() > end .getYPosition()) {
      minY = end.getYPosition();
      maxY = start.getYPosition();
    } else {
      minY = start.getYPosition();
      maxY = end.getYPosition();
    }
    double m = (end.getYPosition() - start.getYPosition()) /
               (double)(end.getXPosition() - start.getXPosition());
    double b = -m * end.getXPosition() + end.getYPosition();
    for(int i = 0; i < minY; i++) {
      System.out.println();
    }
    for(int i = minY; i <= maxY; i++) {
      int xPos = (int)Math.round(1/m * (i - b));
      for(int j = 0; j < xPos; j++) {
        System.out.print(" ");
      }
      System.out.println("x");
    }
  }
}

Sourcefile

Paint4.java:

package programs;

class Paint4 {
  public static void main(String[] args) {
    Point13 p1 = new Point13(1, 3);
    Point13 p2 = new Point13(8, 10);
    Line3 graph = new Line3(p1, p2);
    graph.paint();
    p2.moveTo(7, 1);
    graph.paint();
  }
}

Sourcefile


Um diese Dateien korrekt abzuspeichern, müssen wir neue Verzeichnisse anlegen. Ich gehe bei den folgenden Angaben davon aus, dass auf dem Laufwerk 'C' unter Windows, im Root-Verzeichnis unter Unix/Linux das Verzeichnis JavaKurs existiert. Um die folgenden Schritte auszuprobieren, musst du dieses Verzeichnis anlegen oder die Angabe dieses Verzeichnis in den folgenden Schritten durch die eigenen Pfad Angaben ersetzen.
In dem Verzeichnis JavaKurs legen wir das Unterverzeichnis graphic an und kopieren die Dateien Point13, BasePoint5 und Line3 hinein. Dann erstellen wir das Verzeichnis programs und kopieren die Datei Paint4 hinein. In der Console können wir nun von dem Verzeichnis JavaKurs aus versuchen die Dateien zu compilieren. Bitte achte darauf, dass im CLASSPATH das aktuelle Verzeichnis enthalten ist ('.;'(Windows) bzw. '.:'(Unix)). Man kann sich den CLASSPATH anzeigen lassen mit dem Befehl:

  Windows: echo %CLASSPATH% + Return-Taste
  Unix/Linux: echo $CLASSPATH + Return-Taste

Wir compilieren zuerst die Dateien im Verzeichnis graphic:

  Windows: javac graphic\*.java + Return-Taste
  Unix/Linux: javac graphic/*.java + Return-Taste

Dabei sollten keine Compiler-Fehler auftreten. Nun versuchen wir die Datei Paint4.java zu compilieren:

  Windows: javac programs\Paint4.java + Return-Taste
  Unix/Linux: javac programs/Paint4.java + Return-Taste

Bei diesem Versuch treten die Fehlermeldungen auf, dass die Klassen Point13 und Line3 nicht gefunden werden. Dies liegt daran, dass zum Namen einer Klasse auch der Name des Packages gehört, in dem sie sich befindet.

package programs;

class Paint5 {
  public static void main(String[] args) {
    graphic.Point13 p1 = new graphic.Point13(1, 3);
    graphic.Point13 p2 = new graphic.Point13(8, 10);
    graphic.Line3 graph = new graphic.Line3(p1, p2);
    graph.paint();
    p2.moveTo(7, 1);
    graph.paint();
  }
}

Sourcefile

Der Versuch diese Klasse zu compilieren

  Windows: javac programs\Paint5.java + Return-Taste
  Unix/Linux: javac programs/Paint5.java + Return-Taste

schlägt wieder fehl. Der Compiler bringt Fehlermeldungen, dass wir keine Berechtigung haben auf die Klassen, ihre Konstruktoren und Methoden zuzugreifen. Damit wir diese Berechtigung haben, müssen wir vor jeder Klassen-, Konstruktor- und Methodendefinition der Point- und Line-Klasse das Schlüsselwort public einfügen. Auch bei der Definition der Klasse Paint fügen wir public ein.

Point14.java:

package graphic;

public class Point14 {
  int xPosition = 0;
  int yPosition = 0;

  public Point14() {
    this(0, 0);
  }

  public Point14(int xPos, int yPos) {
    moveTo(xPos, yPos);
  }

  public int getXPosition() {
    return xPosition;
  }

  public int getYPosition() {
    return yPosition;
  }

  public void moveTo(int xPosition, int yPosition) {
    this.xPosition = xPosition;
    this.yPosition = yPosition;
  }
}

Sourcefile

BasePoint5.java

package graphic;

public class BasePoint6 extends Point14 {
  public BasePoint6() {
    super(0, 0);
  }

  public void moveTo(int xPosition, int yPosition) {
    super.moveTo(0, 0);
  }
}

Sourcefile

Line4.java:

package graphic;

public class Line4 {
  Point14 start;
  Point14 end;

  public Line4(Point14 startPoint, Point14 endPoint) {
    start = startPoint;
    end = endPoint;
  }

  public void paint() {
    int minY;
    int maxY;
    if(start.getYPosition() > end .getYPosition()) {
      minY = end.getYPosition();
      maxY = start.getYPosition();
    } else {
      minY = start.getYPosition();
      maxY = end.getYPosition();
    }
    double m = (end.getYPosition() - start.getYPosition()) /
               (double)(end.getXPosition() - start.getXPosition());
    double b = -m * end.getXPosition() + end.getYPosition();
    for(int i = 0; i < minY; i++) {
      System.out.println();
    }
    for(int i = minY; i <= maxY; i++) {
      int xPos = (int)Math.round(1/m * (i - b));
      for(int j = 0; j < xPos; j++) {
        System.out.print(" ");
      }
      System.out.println("x");
    }
  }
}

Sourcefile

Paint5.java:

package programs;

class Paint6 {
  public static void main(String[] args) {
    graphic.Point14 p1 = new graphic.Point14(1, 3);
    graphic.Point14 p2 = new graphic.Point14(8, 10);
    graphic.Line4 graph = new graphic.Line4(p1, p2);
    graph.paint();
    p2.moveTo(7, 1);
    graph.paint();
  }
}
Sourcefile

Jetzt ist die Compilation erfolgreich. Auf die Bedeutung von public wird im Kapitel Zugriffsklassen eingegangen. Das Programm kann jetzt auch gestartet werden. Aber auch hier ist zu beachten, dass sich der Name aus dem Packagenamen und dem Klassennamen zusammensetzt.

  java program.Paint6 + Return-Taste

5.6.1 Die import-Anweisung

Will man in einem anderen package nicht immer den vollen Namen einer Klasse angeben, so kann eine Klasse auch mit der import-Anweisung bekanntgegeben werden. Die import-Anweisungen müssen die nächsten Anweisungen nach der package-Anweisung sein. Sie dürfen nicht innerhalb der Klassendefinition stehen.

  package packageName;

  import packageName1.KlassenName1;
  import packageName2.KlassenName2;

  class KlassenName extends KlassenName_der_SuperKlasse {
    variablenTyp memberVariable1;
    variablenTyp memberVariable2;

    KlassenName(Parameterliste) {
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    ...
  }

Nach dem Einfügen der import-Anweisung kann ein Klassenname, ohne zusätzliche Angabe des Packages in dem sich die Klasse befindet, verwendet werden.

Paint7.java:

package programs;

import graphic.Point14;
import graphic.Line4;

class Paint7 {
  public static void main(String[] args) {
    Point14 p1 = new Point14(1, 3);
    Point14 p2 = new Point14(8, 10);
    Line4 graph = new Line4(p1, p2);
    graph.paint();
    p2.moveTo(7, 1);
    graph.paint();
  }
}

Sourcefile

Mit der import-Anweisung können einzelne Klassen mit ihrem Namen oder auch komplette packages eingebunden werden. Ein komplettes Verzeichnis fügt man mit einem Stern anstelle des Klassennamens ein.

Paint8.java:

package programs;

import graphic.*;

class Paint8 {
  public static void main(String[] args) {
    Point14 p1 = new Point14(1, 3);
    Point14 p2 = new Point14(8, 10);
    Line4 graph = new Line4(p1, p2);
    graph.paint();
    p2.moveTo(7, 1);
    graph.paint();
  }
}

Sourcefile

Das package java.lang wird automatisch eingebunden, es braucht nicht angegeben zu werden. In diesem package befinden sich Klassen wie System und Math, die wir schon verwendet haben, ohne sie explizit importiert zu haben.

5.7 Zugriffsklassen

Eine Zugriffsklasse beschreibt die Sichtbarkeit einer Klasse, einer Methode oder einer Membervariablen über die Klasse und das package hinaus, in dem sie definiert sind.

5.7.1 Zugriffsklassen für Klassen

Klassen können zwei Zugriffsklassen besitzen.

5.7.1.1 public

Die Zugriffsklasse public erlaubt 'weltweiten' Zugriff. Es kann von allen Klassen, egal in welchem package sie sich befinden zugegriffen werden. Der Sourcecode einer als public definierten Klasse, muss in einer Datei stehen, die den selben Namen besitzt wie die Klasse selber. Dabei ist auch auf Gross-/Kleinschreibung zu achten.
KlassenName.java

  package packageName;

  import packageName1.KlassenName1;
  import packageName2.KlassenName2;

  public class KlassenName extends KlassenName_der_SuperKlasse {
    variablenTyp memberVariable1;
    variablenTyp memberVariable2;

    KlassenName(Parameterliste) {
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    ...
  }

5.7.1.2 default

Ohne die Angabe einer Zugriffsklasse ist eine Klasse nur in dem package sichtbar, in dem sie definiert wurde. Aus einem anderen package heraus, kann auf die Klasse nicht zugegriffen werden. Bis zum letzten Kapitel haben wir in unseren bisherigen Beispielen kein package angegeben. Somit befanden sich alle Klassen im gleichen (Default-)package und wir konnten problemlos darauf zugreifen.

5.7.2 Zugriffsklassen für Konstruktoren, Methoden und Membervariablen

Die Zugriffsklassen sind ein wichtiges Hilfsmittel um die im objekorientierten Paradigma geforderte Kapselung umzusetzen. Mit ihnen gelingt es den Zugriff auf Konstruktoren, Methoden und Variablen zu erlauben und zu verbieten. Das bedeutet man kann mit Hilfe der Zugriffsklassen festlegen ob eine Variable von einer anderen Klasse aus geändert werden darf oder ob eine Methode oder ein Konstruktor aufgerufen werden darf.

5.7.2.1 public

Die Zugriffsklasse public erlaubt 'weltweiten' Zugriff. Es kann aus jeder beliebigen Klasse heraus auf den Konstruktor, die Membervariable oder die Methode zugegriffen werden.

  package packageName;

  import packageName1.KlassenName1;
  import packageName2.KlassenName2;

  public class KlassenName extends KlassenName_der_SuperKlasse {
    public variablenTyp memberVariable1;
    public variablenTyp memberVariable2;
    // Diese Membervariablen dürfen von jeder beliebigen anderen
    // Klasse aus geändert werden.

    public KlassenName(Parameterliste) {
      // Dieser Konstruktor kann aus jeder beliebigen anderen Klasse heraus
      // aufgerufen werden
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    public methodenName(ParameterListe) {
      // Diese Methode kann aus jeder beliebigen anderen Klasse heraus
      // aufgerufen werden
      ...
    }
    ...
  }

5.7.2.2 protected

Bei Methoden und Membervariablen die als protected deklariert sind, ist ein Zugriff nur von den Klassen möglich, die entweder eine Subklasse sind oder im gleichen package definiert sind, wie die Klasse, in der die Methode oder die Membervariable implementiert wurde.

  package packageName;

  import packageName1.KlassenName1;
  import packageName2.KlassenName2;

  public class KlassenName extends KlassenName_der_SuperKlasse {
    public variablenTyp memberVariable1;
    public variablenTyp memberVariable2;

    public KlassenName(Parameterliste) {
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    public methodenName1(ParameterListe1) {
      ...
    }
    
    protected methodenName2(ParameterListe2) {
      // Diese Methode ist nur für Klassen sichtbar, welche
      // 1. im package packageName definiert sind oder
      // 2. die Subklasse von KlassenName sind.
      ...
    }
    ...
  }

5.7.2.3 default

Ohne die Angabe einer expliziten Zugriffsklasse ist eine Methode oder ein Datenfeld nur innerhalb des packages zugänglich, in dem die Klasse definiert ist. Ausserhalb ist auch Subklassen dieser Klasse der Zugriff nicht erlaubt.

5.7.2.4 private

Als private deklarierte Methoden und Elemente sind nur der Klasse zugänglich, in der sie definiert wurden. Auch Subklassen haben keine Zugriffsrechte auf diese Methoden und Elemente.

  package packageName;

  import packageName1.KlassenName1;
  import packageName2.KlassenName2;

  public class KlassenName extends KlassenName_der_SuperKlasse {
    private variablenTyp memberVariable1;
    private variablenTyp memberVariable2;
    // Diese Variablen sind nur innerhalb dieser Klassendefinition sichtbar.
    // Von anderen Klasse, auch Subklassen, aus kann icht zugegriffen werden.

    public KlassenName(Parameterliste) {
      memberVariable1 = wert1;
      memberVariable2 = wert2;
      ...
    }

    public methodenName1(ParameterListe1) {
      ...
    }
    
    private methodenName2(ParameterListe2) {
      // Diese Methode ist nur innerhalb dieser Klasse sichtbar.
      ...
    }
    ...
  }

In den meisten Fällen erlaubt man den Zugriff auf Membervariablen nur über Methoden. D.h. man weist den Membervariablen die Zugriffsklasse private zu und definiert jeweils eine public Methode zum setzen und auslesen der Membervariablen.

Aufgabe 16

5.7.3 Zugriffsklassen bei überschriebenen Konstruktoren, Methoden und Membervariablen

Überschreibt man Konstruktoren, Methoden und Membervariablen, so ist zu beachten, dass die Zugriffsklasse nur in Richtung mehr Öffentlichkeit geändert werden darf. Dies bedeutet, dass z.B. eine public-Methode nicht mit einer protected oder private-Methode überschrieben werden darf. Anders herum darf eine protected-Methode mit einer public-Methode überschrieben werden. private-Methoden können nicht überschrieben werden, da sie für die Subklasse vollkommen unsichtbar sind. Es können aber Methoden mit gleichem Namen und gleichen Parametern definiert werden. In einer Superklasse wird aber immer die eigene private-Methode aufgerufen, auch wenn die Subklasse eine Methode mit gleichem Namen und gleichen Parametern besitzt.

5.8 Modifier

Modifier sind Schlüsselwörter, die einer Vereinbarung einer Klasse eines oder eines Elements vorangestellt werden können. Sie verändern die Eigenschaften des Gegenstands der Vereinbarung. So kann beispielsweise ein Element als konstant definiert werden oder eine Klasse als nicht ableitbar gekennzeichnet werden.

5.8.1 Modifier für Klassen

Eine Klasse kann in Java mit den Modifiern abstract und final gekennzeichnet werden.

5.8.1.1 abstract

Abstrakte Klassen dienen dazu, lediglich eine Klassenstruktur festzulegen, ohne nähere Implementierungen vorzunehmen. Eine abstrakte Klasse darf nicht selbst instantiiert werden. Von ihr muss erst eine Subklasse abgeleitet werden, welche die mit abstract gekennzeichneten Methoden der Klasse implementiert. Sobald eine Klasse auch nur eine Methode besitzt, die als abstract vereinbart ist, muss sie ebenfalls als abstract vereinbart werden. Weitere Erklärungen zur Benutzung von abstrakten Methoden und Klassen finden sich im Kapitel Interfaces.

5.8.1.2 final

Von Klassen, die mit final gekennzeichnet sind, können keine Subklassen abgeleitet werden. Dadurch sind auch alle Methoden einer Klasse, die als final gekennzeichnet ist, nicht überschreibbar alsofinal.

5.8.1.3 Kombination der Modifier

Eine Kombination von abstract und final ist nicht möglich. final verbietet eine Subklasse der Klasse zu bilden und bei als abstract gekennzeichneten Klassen können nur Instanzen von Subklassen erzeugt werden.

5.8.2 Modifier für Variablen

5.8.2.1 final

Mit final können Konstanten deklariert werden. Ist einer final-Variablen einmal ein Wert zugewiesen, so kann dieser nicht mehr geändert werden. Vor dem ersten Gebrauch einer final-Variablen muss ihr ein Wert zugewiesen worden sein. final-Deklarationen sind auch innerhalb von Methoden oder der Parameterliste von Methoden möglich. Auf diese Spezialfälle wird im Kapitel über innere Klassen näher eingegangen.

5.8.2.2 static

Nicht als static vereinbarte Variablen sind einer Instanz einer Klasse zugeordnet. Mit jeder neuen Instanz wird ein eigener Speicherbereich für jede Variable angelegt. Ein mit static deklariertes Element dagegen ist allen Instanzen einer Klasse physikalisch gemeinsam. Es gibt für diese Variable nur einen Speicherbereich, der von allen Instanzen gemeinsam benutzt wird. Ändert eine Instanz den Wert eines static-Elements, dann macht sich diese Änderung auch bei allen anderen Instanzen bemerkbar. Als static werden häufig klassenübergreifende Konstanten deklariert.

5.8.2.3 volatile

Bei mit volatile gekennzeichneten Variablen, geht der Compiler davon aus, dass sie durch fremde Prozesse verändert werden und trifft entsprechende Vorkehrungen, damit stets Konsistenz gewährleistet ist. Als volatile deklarierte Variablen werden bei Programmen mit mehreren Threads benötigt.

5.8.2.4 transient

Der Modifier transient signalisiert, dass eine Variable nicht zur Zustandsbeschreibung einer Instanz gehört. Variablen die als transient deklariert sind, werden beim Abspeichern des Zustandes eines Objekts (siehe auch Serialization) nicht mit abgespeichert.

5.8.2.5 Kombination der Modifier

final und static können auch kombiniert benutzt werden. Häufig deklariert man final-Konstanten auch als static, da die Konstanten von allen Instanzen genutzt werden und nicht für jede Instanz neuer Speicherplatz belegt werden soll.

5.5.9 Modifier für Methoden

5.5.9.1 final

Eine mit final gekennzeichnete Methode kann nicht überschrieben werden. Methoden sind implizit final, wenn sie in einer als final vereinbarten Klasse stehen.

5.5.9.2 static

static-Methoden stehen in völliger Analogie zu den static-Elementen. Sie werden ebenfalls der Klasse und nicht einer Instanz zugeordnet. Eine static-Methode kann auch ohne eine Instanz der zugehörigen Klasse aufgerufen werden. Es gibt auch Klassen die nicht instantiiert werden dürfen und ausschliesslich statische Methoden enthalten. Ein Beispiel hierfür ist die Klasse java.lang.Math. Beim Aufruf muss der Methode statt des Namens der Instanz, der Klassenbezeichner vorangestellt werden.

5.5.9.3 native

Der native-Modifier erlaubt das Einbinden von Programmcode, der in einer anderen Sprache geschrieben wurde. Eine native-Deklaration besteht nur aus dem Methodenkopf, der von einem Semikolon abgeschlossen wird.

5.5.9.4 synchronized

synchronized stellt sicher, dass eine Methode nie von zwei verschiedenen Threads gleichzeitig ausgeführt werden kann.

5.5.9.5 abstract

Mit dem Schlüsselwort abstract werden Methoden deklariert, die überschrieben werden müssen. Abstrakte Methoden dürfen nur in Klassen deklariert werden, die ebenfalls als abstract gekennzeichnet sind. Eine abstract-Deklaration besteht wie eine native-Deklaration nur aus dem Methodenkopf.

5.5.9.6 Kombination der Modifier

Bis auf die Kombination von static und abstract sind alle Kombinationen der Modifier erlaubt. Allerdings macht die Kombination von final und abstract wenig Sinn, da final Methoden nicht überschrieben werden dürfen, abstract Methoden aber überschrieben werden müssen.

5.9 Arrays

Arrays sind indizierte Listen von einer festgelegten Anzahl n von Elementen.

index Element
0 1. Element
1 2. Element
2 3. Element
... ...
n-1 n. Element

Die Elemente können sowohl primitive Datentypen, als auch Objekte sein. Die Arrays selber werden in Java als Objekte betrachtet. Der Elementtyp wir bei der Definition eines Arrays festgelegt.

  elementTyp[] variablenName;

5.9.1 Initialisierung eines Arrays

Auch von Arrays muss, bevor man etwas zuweisen oder aufrufen kann, eine Initialisierung durchgeführt werden. Man muss eine neue Instanz des Arrays erzeugen. Bei der Initialisierung des Arrays wird die Anzahl n der möglichen Elemente des Arrays festgelegt.

  variablenName = new elementTyp[n];

5.9.2 Zugriff auf Arrayelemente

Nach der Initialisierung des Arrays kann mit dem Index auf die einzelnen Elemente des Arrays zugegriffen werden. Nach der Initialisierung sind die einzelnen Elemente noch unbestimmt. Es muss ihnen zuerst ein Wert zugewiesen werden.

  variablenName[0] = new Element();
  variablenName[1] = new Element();
  variablenName[2] = new Element();
  ...
  variablenName[n] = new Element();

Nach der Initialisierung der einzelnen Elemente, können diese durch Angabe des Index wie einzelne Variablen verwendet werden.

  for(int i = 0; i < n; i++) {
    System.out.println(variablenName[i]);
  }
public class StringArray1 {
  public static void main(String[] args) {
    String[] myStringList;
    myStringList = new String[5];
    myStringList[0] = "abc";
    myStringList[1] = "bcd";
    myStringList[2] = "cde";
    myStringList[3] = "def";
    myStringList[4] = "efg";
    for(int i = 0; i < 5; i++) {
      System.out.println("myStringList[" + i + "] = " + myStringList[i]);
    }
  }
}
Sourcefile

5.9.3 Arrays als Parameter in Konstruktoren und Methoden

Array können auch als Parameter an Konstruktoren und Methoden übergeben werden. In den meisten Fällen ist in dem Konstruktor oder der Methode die Anzahl der Elemente des übergebenen Arrays unbekannt. Mit Hilfe des Datenfeldes length de Arrays, kann die Anzahl ermittelt werden.

  returnTyp methodenName(int[] variablenName) {
    System.out.println("Es wurden " + variablenName.length + " int-Variablen an die Methode übergeben.");
  }

Ein einfaches Beispiel für eine solche Methode ist die public static void main(String[] args). Dieser Methode werden alle eingegebenen Kommandozeilen-Parameter des Programmaufrufs übergeben.

class BringWas {
  public static void main(String[] args) {
    if(args.length > 2) {
      System.out.println(args[0] + " bringt " + args[1]);
      for(int i = 2; i < args.length; i++) {
        if(i < (args.length - 1)) {
          System.out.println(args[i] + " und");
        } else {
          System.out.println(args[i]);
        }
      }
    }
  }
}
Sourcefile

Aufgabe 17

5.9.4 Weitere Initialisierungsmöglichkeiten

Es ist auch möglich einem Array direkt bei der Initialisierung Werte zuzuweisen:

public class StringArray2 {
  public static void main(String[] args) {
    String[] myStringList = {
      "abc",
      "bcd",
      "cde",
      "def",
      "efg"
    };
    for(int i = 0; i < myStringList.length; i++) {
      System.out.println(myStringList[i]);
    }
  }
}
Sourcefile

Eine weitere ausführlicher geschriebene Möglichkeit ist:

public class StringArray {
  public static void main(String[] args) {
    String[] myStringList = new String[] {
      "abc",
      "bcd",
      "cde",
      "def",
      "efg"
    };
    for(int i = 0; i < myStringList.length; i++) {
      System.out.println(myStringList[i]);
    }
  }
}
Sourcefile

5.9.5 Mehrdimensionale Arrays

Auch mehrdimensionale Arrays kann man in Java vereinbaren. Für jede Definition des Arrays wird ein Paar eckige Klammern angeben.

  elementTyp[][] variablenName;

Ein zweidimensionales Array lässt sich noch recht einfach veranschaulichen. Man kann sich ein zweidimensionales Array als eine Tabelle vorstellen, bei der die erste Dimension die Anzahl n der Zeilen angibt, die zweite die Anzahl m der Spalten.

  elementTyp[][] variablenName;
  variablenName = new elementTyp[n][m];

Diese Initialisierung erzeugt folgende Tabelle:

  0 1 2 ... m
0 element[0][0] element[0][1] element[0][2] ... element[0][m]
1 element[1][0] element[1][1] element[1][2] ... element[1][m]
2 element[2][0] element[2][1] element[2][2] ... element[2][m]
... ... ... ... ... ...
n element[n][0] element[n][1] element[n][2] ... element[n][m]


Es ist nicht zwingend erforderlich, dass beide Dimensionen bei der Initialisierung gelichzeitig angegeben werden. Die zweite Dimension kann erst in einem späteren Schritt angegeben werden. So ist es möglich die einelnen Zeilen unterschiedlich lang zu gestalten.

  elementTyp[][] variablenName;
  variablenName = new elementTyp[n][];
  variablenName = new elementTyp[0][k];
  variablenName = new elementTyp[1][l];
  ...
  variablenName = new elementTyp[n][m];

In einer Tabelle dargestellt:

  0 1 ... k ... m ... l
0 element[0][0] element[0][1] ... element[0][k]
1 element[1][0] element[1][1] ... element[1][k] ... element[1][m] ... element[1][l]
... ... ... ... ... ...
n element[n][0] element[n][1] ... element[n][k] ... element[n][m]


Aufgabe 18

Created with Vim Zurück   Weiter