Objektorientierte Programmierung in Java

Das Konzept der Objektorientierten Programmierung

Datentypen bilden eine Einschränkung, da sie nur bestimmte Operationen zulassen. Das ist sinnvoll, weil die meisten Größen nur mit begrenztem Wertebereich verwendet werden :

Auch Funktionen bzw. Prozeduren sollten eingeschränkt verwendet werden. Es macht keinen Sinn, einen Parameter an eine Funktion zu übergeben, der zwar dem richtigen Datentyp angehört, aber eine andere Bedeutung hat.

Als Beispiel sollen Funktionen zur Dateibearbeitung in C dienen. fopen() liefert eine Adresse einer für das Anwendungsprogramm unbedeutenden Datenstruktur. Diese repräsentiert dann die offene Datei und wird bei Bedarf an andere Funktionen wie fread(), fwrite() und fclose() übergeben.

Man kann nicht verhindern, daß außerhalb dieser Funktionen auf die von fopen() gelieferte Struktur zugegriffen wird, obwohl das nicht deren Sinn ist. Sie soll nur innerhalb der Funktionen verwendet werden.

Man kann ebensowenig verhindern, daß die Adresse einer im Anwendungsprogramm konstruierten Struktur an die Funktionen übergeben wird, solange deren Datentyp eingehalten wird. Das ist natürlich erst recht nicht Sinn der Struktur.

Andere Dateibearbeitungs-Funktionen open(), read(), write() und close() verwenden Integerwerte zur Identifikation offener Dateien. Dies verhindert zwar, eine selbstkonstruierte Struktur verwenden zu können, aber dafür können beliebige und damit ungültige Integerwerte angegeben werden.

Dies könnte, wenn überhaupt, nur zur Laufzeit des Programms überprüft werden.

Klassen und Objekte

Klassen vereinigen Funktionen (Prozeduren) und Variablen. Sie lassen sich instanziieren. Dadurch entstehen Objekte. Klassen schränken die Verwendung von Funktionen und Variablen derart ein, daß sie nur in Zusammenhang mit einem Objekt dieser Klasse verwendet werden können.

Eine zu einer Klasse gehörende Funktion nennt man Methode. Eine entsprechende Variable nennt man Attribut.

Eine Methode wird nicht wie eine Funktion aufgerufen, sondern sie wird auf ein Objekt angewandt.

Objekte werden immer explizit erzeugt. Dazu hat eine Klasse Konstruktoren. Diese sind Methoden ähnlich, haben jedoch keinen Namen und liefern niemals Rückgabewerte.

Objekte werden nicht explizit zerstört, wenn sie nicht mehr benötigt werden. Stattdessen «verschwinden» sie nach deren Verwendung automatisch.

Damit Methoden und Attribute, die nur innerhalb einer Klasse verwendet werden sollen, vor dem Zugriff von außen geschützt werden können, kann man sie als privat festlegen. Auch von außen verwendbare Methoden und Attribute nennt man öffentlich.

Beispiel :

Zur Dateibearbeitung gebe es eine Klasse, die zur Bearbeitung nötige Methoden bereitstellt. Um eine Datei zu bearbeiten, wird wird die Klasse instanziiert. Dabei wird einer ihrer Konstruktoren angewandt.

Auf das dabei entstehende Objekt lassen sich dann die Methoden der Klasse anwenden, um die nötige Bearbeitung durchzuführen. Diese Methoden sind öffentlich. Möglicherweise hat die Klasse private Methoden, die innerhalb der (öffentlichen) Methoden verwendet werden.

Die Klasse wird auch Attribute besitzen, die den jeweiligen Zustand der Datei darstellen. Diese werden jedoch nur innerhalb der Methoden verwendet und sind daher privat.

Dateien können ausschließlich auf die beschriebene Weise bearbeitet werden, weil die dafür notwendigen Methoden nur auf Objekte dieser Klasse angewandt werden können und diese Objekte wiederum nur mit dieser Klasse erzeugt werden können.

Klassenhierarchien

Es ist aus verschiedenen Gründen sinnvoll, einander ähnliche Klassen zu erstellen :
Unterschiedliche Implementation der selben Funktionalität :
Ein Programm (Klasse bzw. Methode) soll Daten an eine Datei ausgeben. Es wird jedoch gewünscht, daß diese Daten je nach Bedarf an verschiedene Arten von Dateien geschrieben werden, z. B. reguläre Dateien, Pipelines zu anderen Prozessen oder über eine Netzwerkverbindung zu einem anderen Computer.
Damit das Programm unabhängig davon ist, muß es mit einer einzigen Klasse arbeiten, die diese Dateiarten zusammenfaßt.
Konkretisierungen oder Erweiterungen :
In einer graphischen Oberfläche werden verschiedene Elemente verwendet, z. B. ein Textanzeigefeld (Label) und ein Knopf (Button). Beide können einen kurzen Text, vielleicht auch ein Bildsymbol darstellen, aber nur der Knopf bietet die Möglichkeit, bei der Betätigung eine Aktion durchzuführen.
Ebenso sind verschiedene Arten von Knöpfen denkbar (PushButton, ToggleButton), die viele Gemeinsamkeiten haben.
Die Klassen haben teilweise gleichen Programmcode. Dieser sollte nicht mehrfach existieren.

Um dieses Problem zu lösen, kann man Klassen ableiten.

Klasse B werde definiert als Ableitung der Klasse A. Dann ist B eine Unterklasse von A und A die Oberklasse von B.

B erbt alle Methoden und Attribute von A, kann sie jedoch überschreiben. Deren Sinn bleibt in jedem Fall erhalten.

Außerdem kann B zusätzliche Methoden und Attribute definieren.

Es hat keinen Sinn, Konstruktoren zu erben, deshalb definiert jede Klasse ihre eigenen.

Objekte von B können wie Objekte von A betrachtet und verwendet werden, andersherum jedoch nicht.

Überschreibt B eine Methode x() von A und wird ein Objekt von B als Objekt von A verwendet und darauf die Methode x() angewandt, so wird die von B überschriebene Methode x() verwendet.

Eine Klasse kann beliebig viele Unterklassen haben, eine Klasse von mehreren Oberklassen abzuleiten ist jedoch problematisch, weil Konflikte auftreten können.

Viele solcher voneinander abgeleitenen Klassen bilden eine Klassenhierarchie.

Die obigen Beispiele könnten durch eine solche Hierarchie wie folgt implementiert werden :

Die unterschiedlichen Dateiarten werden als Unterklassen einer gemeinsamen Dateiklasse definiert. Das erwähnte Programm (bzw. Methode) bekommt jeweils ein Objekt einer dieser Unterklassen übergeben. Dieses wird also als ein Objekt der (gemeinsamen) Oberklasse betrachtet. Deren Ausgabemethoden werden dann verwendet. Diese wiederum werden in den jeweiligen Unterklassen unterschiedlich definiert.

Für die graphische Oberfläche definiert man eine sehr allgemeine Element-Klasse, die alle graphischen Elementarten repräsentiert. Davon wird die Klasse für das Textanzeigefeld abgeleitet und die Darstellungsmethode(n) überschrieben. Davon wiederum wird eine allgemeine Knopf-Klasse abgeleitet, die die Möglichkeit der Betätigung bietet. (Diese alleine ist nicht besonders sinnvoll.) Davon wiederum werden die beiden Knopf-Klassen PushButton und ToggleButton abgeleitet und die Methoden zur Steuerung bzw. Betätigung überschrieben.

Java

Programmumgebung

Java-Programme laufen in einer virtuellen Maschine, der Java Virtual Machine. Diese bildet die Schnittstelle zum Betriebssystem.

Einige Fundamentale Klassen, die zum Sprachumfang gehören, haben Native Methoden, die innerhalb der JVM implementiert sind.

Durch Konfiguration der JVM kann der Zugriff auf Systemressourcen eingeschränkt werden. Dies dient zur Sicherheit bei der Verwendung von fremden Programmen. Das ist insbesondere sinnvoll zur Verwendung in Browsern in Form von Applets, aber auch in Webservern als Servlets, in Application Servern oder als eingenständige Programme.

Benötigte Klassen werden durch einen Class Loader zur Laufzeit gesucht und geladen. Sie werden häufig als Class Files (*.class) oder in Form von Archivdateien als Java Archives (*.jar) bereitgestellt, können jedoch auch z. B. über ein Netzwerk geladen werden. Dies ist insbesondere für Applets hilfreich.

Der Programmcode in den Klassen, der in der JVM ausgeführt wird, nennt sich Byte Code. Er ist architekturunabhängig und wird normalerweise mittels eines Compilers aus Java-Quelltext erzeugt, kann aber auch z. B. durch einen Byte Code Assembler erzeugt werden.

Sprache

Attribute werden in Java Feldvariablen genannt.

Es stehen folgende Primitive Datentypen zur Verfügung :

Bezeichnung Beschreibung
long Ganzzahl, 64 Bit, mit Vorzeichen
int Ganzzahl, 32 Bit, mit Vorzeichen
short Ganzzahl, 16 Bit, mit Vorzeichen
byte Ganzzahl, 8 Bit, mit Vorzeichen
char Schriftzeichen, Unicode, 16 Bit, ohne Vorzeichen
float Gleitkomma, 32 Bit, IEEE 754
double Gleitkomma, 64 Bit, IEEE 754
boolean Wahrheitswert (true oder false)

Primitive Datentypen sind keine Klassen.

Objekte werden ausschließlich über Referenzen verwendet. Daher gibt es für jede Klasse den entsprechenden Referenz-Datentyp. Das Symbol null ist als Wert für alle Referenzen geeignet.

Klassen werden in Pakete zusammengefaßt. Dadurch braucht ein Klassenname nur innerhalb eines Paketes eindeutig zu sein.

Klassen können importiert werden. Dadurch lassen sie sich im Folgenden ohne Paketnamen verwenden.

Eine Klasse wird nach folgendem Schema definiert :

    package mypkg.testpkg;
    import java.lang.String;
    import java.io.File;
    public class MyClass extends OtherClass
    {
      ...  // Definitionen von Feldvariablen und Methoden
    }

Die Klausel extends bewirkt eine Ableitung der Klasse. Fehlt sie, so wird die Oberklasse Object angenommen. Diese stellt die Spitze der Hierarchie aller Klassen dar.

Feldvariablen werden mit Datentyp deklariert :

    private int myVariable;  // einfache Integer-Variable
    private File myFile;     // Referenz auf Objekt der Klasse File

Methoden werden mit Return-Datentyp und Parameterliste definiert :

    public boolean myMethod (int parm1, File parm2) throws MyException
    {
      ...  // Programmcode
    }

Der Zusatz throws erklärt, daß die Methode eine Exception auslösen kann. Exceptions sind kontrollierte Abbrüche des Programmablaufs. Sie können ausgelöst und abgefangen werden. Sie werden durch besondere Klassen repräsentiert.

Feldvariablen und Methoden können als static deklariert werden. Dann beziehen sie sich nicht auf Objekte, sondern auf die Klasse selber und können daher nicht auf Objekte angewandt werden.

    public class Math
    {
      public static double sin (Double val)
      {
        ...
      }
    }

Methoden können auch als abstract deklariert werden. Dabei wird die Methode nicht definiert, sondern nur deklariert. Die betreffende Klasse muß dann auch abstract deklariert werden :

    public abstract class Maker
    {
      public abtract void make ();
    }

Abstrakte Klassen können nicht instanziiert werden, können jedoch als Oberklasse zur Ableitung dienen. Eine nicht abstrakte Unterklasse muß dann alle abstrakten Methoden überschreiben :

    public class MyClass extends Maker
    {
      public void make ()
      {
        makeSomething ();
      }
    }

Konstruktoren werden wie Methoden, aber mit dem Klassennamen und ohne Datentyp definiert :

    public MyClass (File f) throws MyException
    {
      ...  // Programmcode
    }

Lokale Variablen werden ähnlich wie in C deklariert :

    int x;   // einfache Integer-Variable
    File f;  // Referenz auf Objekt der Klasse File

Diverse Konstrukte stehen zur Verfügung :

    // Zuweisung
    x = 123;
    // if-else
    if (a == b)
      doThis ();
    else
      doThat ();
    // while
    while (n > 0)
      processElement (n);
    // do-while
    do
      processNext ()
    while (n > 0);
    // for
    for (i = 0; i < n; i++)
      handleIndex (i);
    // Block
    {
      i = 0;
      while (i < n)
        handleIndex (i++);
    }
    // Objekt erzeugen
    f = new File ("my.conf");
    // Methode auf Objekt anwenden
    f.close ();
    // Statische Methode auf Klasse anwenden
    y = Math.sin (x);
    // Exception auslösen
    throw new FileNotFoundException ("my.conf not found in config path");
    // Exception abfangen
    try
    {
      f.open ();  // Diese Methode kann eine Exception auslösen
    }
    catch (FileNotFoundException ex)
    {
      cleanupFiles ();
      throw ex;  // "Weiterreichen" der Exception
    }
    finally
    {
      alwaysDoThis ();
    }
    // return
    return i;

Innerhalb einer Methode oder eines Konstruktors referenziert this das Objekt, auf das die Methode angewandt wird, bzw. das Objekt, das erzeugt wird. Durch super können die Methoden bzw. Konstruktoren der Oberklasse verwendet werden. Dies sollte in Konstruktoren von Unterklassen geschehen.

Neben Klassen gibt es in Java Interfaces. Diese sind abstrakten Klassen mit ausschließlich abstrakten Methoden ähnlich. Eine Klasse kann beliebig viele Interfaces implementieren, indem sie deren Methoden definiert :

    public interface Callable
    {
      public void call ();
    }
    public class Caller implements Callable
    {
      public void call ()
      {
        doSomething ();
      }
    }

Referenzen können nicht nur mit Klassen, sondern auch mit Interfaces deklariert werden. Solche Referenzen können mit Objekten aller Klassen, die das entsprechende Interface implementieren, verwendet werden :

    Callable c = new Caller ();
    c.call ();

Weitere Dokumentation

Äußerst hilfreich ist die Dokumentation der Java Fundamental Classes, die meist in den Java-Entwicklungsumgebungen enthalten ist.

Siehe auch die Einführung in die Programmierung in Java.


Thomas Conze <thomas.conze@gmx.net>