wiki:Skript/alt: Java Grundlagen

Java Grundlagen

ACHTUNG! Diese Seite ist obsolet und wird demnächst entfernt.

Klassen

Definition
Eine Klasse ist eine Schablone für das Erzeugen einer beliebigen Anzahl gleichartiger Objekte und umfasst die gemeinsamen Attribute und Methoden der Objekte.
  • selbst-definierte Datentypen.
  • können verschiedene bestehende Datentypen zu einem neuen, übergeordnetem Datentyp zusammenfassen.
  • können nicht nur Daten, sondern insbesondere Daten und die darauf arbeitenden Operationen zusammenfassen.

Syntax der Klassendefinition

Allgemeine Form
sichtbarkeitsmodifikator class Klassenname {...}
public class Bruch {
  int n; // Nenner
  int z; // Zaehler
  void addieren(Bruch b) {
    n = n * b.d + b.n * d;
    d = d * b.d;
  }
  void teilen(Bruch b) {
    int n0 = n * b.d;
    d = d * b.n;
    n = n0;
  }
}


Objekte

Definition
Ein Objekt ist eine Instanz (ein Exemplar) einer Klasse. Es wird bei Bedarf aus der Klasse abgeleitet.

Ein Objekt besteht aus Attributen und Methoden.

Attribut

Attribute sind "Behälter" für Werte, die den internen Objektzustand mitbestimmen.

Methoden

Eine Methode ist eine Funktion eines Objektes, die nicht unbedingt von außen sichtbar sein muss. Die Menge aller Methoden bildet die Schnittstelle eines Objektes.

Beispiel
Bücher in einer Bibliothek können sehr verschieden sein.
Dennoch haben alle Bücher gleiche Eigenschaften (Attribute) und Methoden.
  • Attribute: Autoren, Titel, Verlag, Erscheinungsjahr, Seitenanzahl, ...
  • Methoden: ausleihen, zurückgeben, vormerken, verlängern, ...

Erzeugen und Verwenden von Objekten

  • Erzeugung von Objekten (Instanziierung) mit Schlüsselwort new
Buch javaLehrbuch = new Buch("Müller, Peter", "Java Grundlagen", 1999);
Konto konto1 = new Konto("12345", "Meier, Fred");
  • Zugriff auf Objektmethoden und öffentliche Attribute mit Punktnotation
javaLehrbuch.setJahr(2000);
konto1.setGuthaben(42000);
kontonummer = konto1.getKontonr();

Ein explizites Zerstören eines Objektes nach der Verwendung ist nicht notwendig.

Getter-/Setter-Methoden

Der direkte Zugriff auf Variablen eines Objektes ist nicht so elegant. Es ist besser, über Methoden auf Variablen zuzugreifen. Diese Methoden müssen die Objekt-Eigenschaften formulieren und die Werte der Variablen zurückliefern.

Allgemeine Form
rueckgabetyp getAttributname(
  return this.attributname;
}

datentyp setAttributname(datentyp wert) {
  this.attributname = wert;
}
Beispiel
class Rechteck {
  private double laenge, breite;

void setLaenge(double l) {
  this.laenge = l;
}
double getLaenge() {
  return this.laenge;
}

void setBreite(double b) {
  this.breite = b;
}
double getBreite() {
  return this.breite;
}

Zurück zum Inhaltsverzeichnis


Referenzvariable

In primitiven Datentypen werden in den Variablen unmittelbar die Werte (Zahlen, Zeichen,...) gespeichert.

Strings, Arrays und Objekte sind hingegen Referenztypen. Sie werden nicht als Ganzen in einer Variablen gespeichert. In der Variablen steht vielmehr die Speicheradresse, unter der die Objektdaten zugreifbar sind. Daher spricht man auf von Referenzvariablen.

  • In Java ist die Speicheradresse nicht auslesbar.
  • Der Default-Wert von Referenzvariablen ist null.

Objektzugriffe

Allgemeine Form
Typ referenzvariable;
referenzvariable = new Typ();
Beispiele
Rechteck r1;          // Referenzvariable r1 ist vom Typ Rechteck
r1 = new Rechteck();  // r1 verweist auf ein Objekt vom Typ Rechteck
r1.laenge = 5;
r1.breite = 2;
r1 = null;            // Objekt nicht mehr benutzbar
   // Direkte Instanziierung
Kreis k1 = new Kreis();
k1.radius = 1,42;
Dreieck d1 = new Dreieck();
Dreieck d2 = new Dreieck();
Dreieck d3 = new Dreieck();
d1.a = 5,1;
d1.b = 12,5;
d1.c = 2;
d2 = d1;
d3.b = d1.a;
d2 = null;

Zurück zum Inhaltsverzeichnis


Methoden und Parameter

Um Daten in eine Methode zu übergeben, werden diese in die Parameterliste des Methodenaufrufs geschrieben.

Inhalt der Parameterliste:

  • Typen der Parameter, die der Methode übergeben werden müssen.
  • Namen, unter denen sie in der Methode angesprochen werden.
Ablauf eines Methodenaufrufs
  1. Parameter auswerten
    Paramterübergabe: übergebenen Werte werden den Methodenparametern automatisch zugewiesen.
  2. Rumpf ausführen
    Der Rumpf der aufgerufenen Methode wird ausgeführt.
  3. Ergebnis zurückgeben
    Die Ausführung des Methodenrumpfs liefert einen Ergebniswert zurück, der dem Rückgabedatentyp entspricht. Eine return-Anweisung beendet Methodenausführungen und gibt den Wert zurück. Es können auch mehrere return-Anweisungen verwendet werden.
Beispiel

Parameterübergabe per Wert

aufrufender Teil methode(int innen) aussen innen
1. int aussen = 42; 42
2. methode(aussen); 42 42
3. innen = 84; 42 84
4. 42
  • beim Aufrufen einer Methode werden die Parameterwerte vom aufrufenden Programmteil in die Methode kopiert.
  • Wertänderungen der Parameter in der Methode wirken sich nicht auf Werte außerhalb der Methode aus!

Parameterübergabe per Referenz

  • beim Aufrufen einer Methode werden die Verweise auf die Objekte vom aufrufenden Teil in die Methode kopiert, nicht die Objekte selbst.
  • Veränderungen der Objekte in der Methode wirken sich außerhalb der Methode aus!

Überladen von Methoden

Die Implementierung von Methoden mit gleichem Rückgabetyp und Methodenname, aber unterschiedlicher Parameterliste, nennt man Überladen. Dadurch ergibt sich die Möglichkeit, unterschiedliche Methodenverhalten in Abhängigkeit von den gegebenen Parametern zu definieren.

Beispiel
public int verlaengern(int tage) {
  ...
  leihFrist = leihFrist + tage;
  ...
}
public int verlaengern() {
  ...
  leihFrist = leihFrist + Standartwert;
  ...
}

Zurück zum Inhaltsverzeichnis

Konstruktoren

Zur Objekterzeugung gibt es besondere Methoden, die sogenannten Konstruktoren. Dabei wird zwischen dem Default-Konstruktor und parametrisierbaren Konstruktoren unterschieden.
Es müssen jedoch keine Objekte explizit gelöscht werden. Das Java-System erkennt automatisch, dass ein Objekt nicht mehr von einer Variable direkt oder indirekt erreichbar ist und der Garbage-Collector sammelt diese auf.

Mit dem Schlüsselwort new und dem Aufruf eines Konstruktors werden Objekte erzeugt.

Allgemeine Form
Klassenname variablenamen = new Klassenname();
Klassenname variablenamen = new Klassenname(datentyp name, ... );
Klassenname variablenamen = new Klassenname( Klassenname name );

Default-Konstruktor

Jede Klasse hat immer einen Konstruktor, auch wenn er nicht explizit implementiert wurde. (Default-Konstruktur)

Allgemeine Form

public class Klassenname {

  datentyp attribut;
  public Klassenname() {
  }
}

Parametrisierte Konstruktoren

Parametrisierte Konstruktoren werden häufig für die Initialisierung von Attributen beim Erzeugen eines Objektes genutzt und können, wie Methoden, überladen werden.
Wenn ein parametrisierter Konstruktor definiert wurde, muss der Konstruktor ohne Parameter explizit definiert werden!

Allgemeine Form

public class Klassenname {
  datentyp attribut;
  public Klassenname() {
  }
// Parametrisierte Konstruktor
  public Klassenname(datentyp d) {
  this.attribut = d;
  }
}

Copy-Konstruktor

Der Copy-Konstruktor erlaubt es, typgleiche Objekte als Parameter zu übergeben. So lässt sich ein Objekt mit den Werten des Übergebenen instanziieren.

Allgemeine Form
public class Klassenname {
  datentyp attribut;
  public Klassenname() {
  }
// Copy-Konstruktor
  public Klassenname(Klassenname k) {
  this.attribut = k.attribut;
  }
}

Zurück zum Inhaltsverzeichnis


Gültigkeitsbereich von Bezeichnern

Es besteht bei großen Programmen ein Problem, da verschiedene Ebenen erkennbar sind und überall Bezeichner benötigt werden.

Die Ebenen eines Java-Programms sind hierarchisch angeordnet:

  1. Dateien xxx.java
  2. Klassendeklarationen (Klassennamen, Konstruktoren)
  3. Methodendeklarationen (Namen von Methoden, formalen Parametern, lokalen Variablen)
  4. Schleifen (Variablen zur Steuerung von Schleifen, Namen von Anweisungen)

Die Gültigkeitsbereiche orientieren sich an syntaktischen Kontext/Struktur:

  • Alle Bezeichner müssen vereinbart werden.
    • Jede Vereinbarung wird dem Block zugeordnet, in der sie geschrieben ist.
    • Bei mehreren verschachtelten Blöcken, gehört die Vereinbarung zum innersten Block.
  • Vereinbarung vor Benutzung.
    • Ein Bezeichner kann erst nach seiner Vereinbarung verwendet werden.
    • Ausnahmen: Klassen und Methoden innerhalb des gleichen Blocks.
  • Gültigkeit bis an das Ende des Blocks, welcher die Vereinbarung enthält.
    • Ein Bezeichner gilt nur bis zu der schließenden geschweiften Klammer des betreffenden Blocks.
  • Namensgleiche Bezeichner in einem inneren Block überdecken die aus einem äußeren Block.
  • In einem Block müssen alle Bezeichner verschieden sein.
    • Bezeichner im selben Block müssen verschiedene Namen aufweisen, damit man sie unterscheiden kann.
    • Ausnahmen: Überladen von Methoden und verschiedenartige Programmelemente (Klassen, Methoden,Variablen)

Zurück zum Inhaltsverzeichnis


Erweiterung einer Klasse

Die ursprüngliche Idee für die Klassenbildung war, eine Einheit aus Daten und Methoden zu bilden und dies in einer Informationsstruktur mit einem engen Bezug zur Problemstellung darzustellen.
In Java lassen sich allgemeine und spezielle Eigenschaften durch Ober- und Unterklassen darstellen, wobei gemeinsame/allgemeine Eigenschaften (Variablen/Methoden) in Oberklassen definiert werden.

Begriffe
Unterklasse: Eine Klasse, die erweitert.
Oberklasse: Eine Klasse, die erweitert wird.
Extension einer Klasse: Die Menge aller Objekte einer Klasse und all ihrer Unterklassen.
Vererbung: Das Konzept der Erweiterung von Klassen.
Beispiel
Eine Reise, Bahnreise, Flugreise
public class Reise {
  DatumsTyp datum;
  Ort start, ziel;
}
public class Bahnreise extends Reise {
  ZugTyp zug;
}
public class Flugreise extends Reise {
  FlugzeugTyp flugzeug;
}
  • Die Klasse Reise ist die Oberklasse von Flugreise und Bahnreise.
  • Die Klassen Flugreise und Bahnreise heißen Erweiterung der Klasse Reise.

Zuweisungskompatibilität

Der Zugriff auf Objekte erfolgt über Verweis-Variablen.

  • Objekte der Unterklasse sind auch Objekte der Oberklasse:
    Bahnreise extends Reise
  • Variable haben aber einen deklarierten Typ und verweisen auf Objekte ihres Typs:
    Bahnreise b; Reise r;
    Bahnreise b = new Bahnreise();
  • Es ist jedoch auch erlaubt, dass Variablen auf Objekte ihrer jeweiligen Unterklassen verweisen:
    r = b;

Welche Attribute stehen dann in dieser Situation zur Verfügung?

Widening

Ein Objekt von einer Oberklasse her zu betrachten, wird Widening genannt.
Dadruch sind die speziellen Eigenschaften der Unterklassen nicht mehr sichtbar (implizite Typ-Anpassung):

Bahnreise b = new Bahnreise();
Flugreise f = new Flugreise();
Reise r = f;
... r.flugzeugtyp ...   // unzulässig.

Narrowing

Ein Objekt von einer Unterklasse her zu betrachten, wird Narrowing genannt.
Dabei muss die Typanpassung explizit angegeben werden:

Bahnreise b;
b = (Bahnreise) r;

Der Effekt ist eine Typüberprüfung zur Laufzeit:
Bevor die Zuweisung vollendet wird, geschieht die Überprüfung, ob das aktuelle Objekt r vom Typ Bahnreise oder eine Unterklasse davon ist.

Mit instanceof kann ein expliziter Test auf Klassenzugehörigkeit formuliert werden:

if (r instanceof Flugreise) {
  f = (Flugreise) r; ...
} else if (r instanceof Bahnreise) {
  b = (Bahnreise) r; ...
} else {
  System.out.println("r verweist auf ein Objekt" + " unbekannten Typs.");
}


Beispiele
  • Bahnreise bahn1 = new Bahnreise();
    legal: Variable vom Typ Bahnreise erhält Referenz auf Bahnreise Instanz
  • Bahnreise bahn2 = new Flugreise();
    illegal: Bahnreise hat keine Vererbungsbeziehung zu Flugreise.
  • Reise reise = new Bahnreise();
    legal: reise hat aber keinen Zugriff auf Bahnreise-spezifische Elemente. (Widening)
  • Bahnreise bahn3 = new Reise();
    illegal: In Reise sind nicht alle Elemente deklariert, die für Bahnreise benötigt werden.
  • Bahnreise bahn4 = reise;
    illegal: zur Compile-Zeit nicht erkennbar, ob es zur Laufzeit noch passt.
  • Bahnreise bahn5 = (Bahnreise) reise;
    legal: Narrowing.

Zurück zum Inhaltsverzeichnis


Erzeugung von Objekten

Welche Rolle spielen Ober- und Unterklassen bei der Erzeugung von Objekten?
new liefert ein neues Objekt der jeweiligen Klasse, dabei werden die Konstruktoren aller Oberklassen aufgerufen.

Konstruktor-Aufrufreihenfolge
  1. Konstruktor der Oberklasse
    • rekursive Wiederholung bis zum leeren Konstruktor von Object, der zuerst ausgeführt wird.
  2. Konstruktoren der Attribute in der Reihenfolge ihrer Deklaration.
  3. Rumpf des Konstruktors.

Begründung: Bei Instanziierung eines Objektes muss es möglich sein, auf Attribute der Oberklasse und auf die eigenen Attribute zuzugreifen.

this(...) & super(...)

Mit this(...) kann ein Konstruktor innerhalb der Klasse benutzt werden.
Mit super(...) können Konstruktoren der Oberklassen benutzt werden.

Bedingungen:

  • this(...) oder super(...) müssen als erste ausführbare Anweisung in einem Konstruktor erscheinen.
  • this(...) und super(...) schließen sich gegenseitig aus.
Beispiel
class Punkt2D {
  int x, y;
  Punkt2D() {
    // this(0,0) ruft den zweiten Konstruktor mit den Werten 0 und 0 auf
    this(0, 0);
  }
  Punkt2D(int x, int y) {
    this.x = x;
    this.y = y;
  }
}
class Punkt3D extends Punkt2D {
  int z;
  Punkt3D() {
    // this(0,0,0) ruft den zweiten Konstruktor mit den Werten 0 und 0 auf
    this(0, 0, 0);
  }
  Punkt3D(int x, int y, int z) {
    // super(x,y) ruft den zweiten Konstruktor aus der Klasse Punkt2D auf
    super(x, y);
    this.z = z;
  }
}

Zurück zum Inhaltsverzeichnis


Verdecken und Überschreiben

Die Verwendung von identischen Namen in einer Erweiterung muss geregelt sein. Deshalb gilt:

  • Bei Variablen:
    Deklaration in Unterklasse verdeckt die Deklaration in der Oberklasse.

    Beim Verdecken von Variablen ist (zunächst) nur die aktuelle Unterklassen-Variable sichtbar.
    class Ober {
      // int abc ist durch boolean abc verdeckt.
      int abc;
    }
    
    class Unter extends Ober {
      boolean abc;
    }
    
    Die verdeckten Variablen einer Oberklasse können mittels einer Variable des Typs der Oberklasse sichtbar gemacht werden.
    Unter U = new Unter();
    Ober O = U;
    O.abc = 2;
    U.abc = true;
    


  • Bei Methoden:
    Deklaration in Unterklasse überschreibt die Deklaration in der Oberklasse.

    Methoden sind identisch, wenn der Methodenkopf identisch ist. Es wird immer die speziellste Version einer Methode aufgerufen. Dieses Prinzip nennt man Späte Bindung.
    class Ober {
      int b(boolean x) {
        if (x) {
          return 0;
        }
        return 42;
      }
    }
    
    class Ober {
      int b(boolean y) {
        if (y) {
          return 22;
        }
        return 1;
      }
    }
    
    Die überschriebene Methode lässt sich durch den Selbstverweis super. aus Sicht der Oberklasse aufrufen.
    class Ober {
      int b(boolean y) {
        return super.b(y);
      }
    }
    

Zurück zum Inhaltsverzeichnis


Verwendung einer Erweiterung

Die Erweiterungskonzepte für Methoden in Java: statisch und dynamisch

  • Statische Erweiterung von Methoden (Überschreiben):
    -Methodenkopf identisch in Ober- und Unterklasse.
    -Späte Bindung.
  • Dynamische Erweiterung:
    -Es wird einer Methode der Oberklasse aus dem Rumpf einer Methode der Unterklasse aufgerufen.
    -Benutzung des Selbstverweises super.xx(yy).

Anonyme Erweiterung

Wenn von einer Unterklasse nur ein Objekt benötigt wird, kann man einer sogenannte einmalige (anonyme) Erweiterung benutzen.

Beispiel
class Ober {
  int tuWas(int a) {
    return a * a;
  }
}
...
Ober o = new Ober() {
  int tuwas(int a) {
// Spezielle, einmalige Erweiterung:
    return a + 42;
  }
};
...

Zurück zum Inhaltsverzeichnis


Beziehungen zwischen Klassen

Es gibt verschiedene Formen der Beziehung zwischen Klassen.

  1. Statische Erweiterung (benannt oder anonym)
    Methode der Oberklasse wird überschrieben.
  1. Dynamische Erweiterung
    Rückgriff auf Methode der Oberklasse mit super..
  1. Benutzt-Beziehung
    Ein Objekt vom Typ X benutzt ein Objekt vom Typ Y:
    public class X {
    // Referenzvariable ermöglicht Benutzt-Beziehung
      Y y;
      public void def() {
    // Benutzt-Beziehung zu einem neuen Objekt Y
        y = new Y();
        y.abc();
        y.test();
      }
    }
    
  1. Erweiterung durch Delegation
    Die Eigenschaften einer Klasse werden von einer anderen Klasse importiert und dann an spezifischen Stellen speziell erweitert:
    class Speziell {
      Allgemein temp;
    
      public Speziell() {
        temp = new Allgemein();
      }
      void abc() {
        ...
    // Delegation an Allgemein.
        temp.abc();
        ...
      }
    }
    
    Vorteil: Eigenschaften können währen der Laufzeit geändert und allgemeine Strukturen können spezialisiert werden.

Zurück zum Inhaltsverzeichnis


Last modified 6 years ago Last modified on Aug 31, 2018, 10:10:24 PM