Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

techniken:objektorientjava

Dies ist eine alte Version des Dokuments!




Objektorientiertes Programmieren mit Java

In Java läuft nichts ohne objektorientiertes Programmieren. Es hilft euch nicht nur, euren eigenen Code in übersichtliche Einheiten zu strukturieren - auch viele Bibliotheken stellen ihre Funktionalität in Form von Klassen zur Verfügung - mit diesen müsst ihr arbeiten, um von der darin verpackten Intelligenz zu profitieren…

Wir werden das alles am Beispiel eines zweirädrigen Roboters durchexerzieren…

Klassen

Klassen sind Beschreibungen von funktionellen Einheiten

In einer Klassendefinition legen wir fest, welchen Namen diese funktionelle Einheit hat, über welche Informationen sie verfügt und was sie alles tun kann.

Wir werden hier erstmal eine leere „Roboter“ Klasse anlegen, die wir nach und nach mit Leben füllen:

// das Stichwort "class" leitet die Definition einer Klasse ein. Danach folgt ihr Name ("Robot")
class Robot {
  // hier (in den "Klassenrumpf") kommen gleich die Variablen und Methoden der Klasse rein.
}

Klassen sind Datentypen

Nachdem eine Klasse definiert ist, kann ihr Name genau wie einer der eingebauten Datentypen (int, float, etc.) verwendet werden.

Um im Programm eine Variable, die einen ganzen Roboter (und nicht nur eine einzelne Zahl) speichert anzulegen könnnen also schreiben:

Robot myRobot;  // Deklariere die Objektvariable "myRobot" vom Typ "Robot"

Klassen können Variablen enthalten

Eine Klasse kann sogenannte Membervariablen enthalten, die Informationen über die durch sie repräsentierte funktionelle Einheit speichern. Sie werden einfach im Stile einer Variablendeklaration in den Klassenrumpf (also zwischen die geschweiften Klammern) geschrieben.

Mit dem folgenden Code geben wir dem Roboter ein Gedächtnis für seine Lage im Raum (x- und y-Koordinaten sowie Winkel zur x-Achse):

class Robot { // Deklaration der Klasse Robot
  float posX = 100;     //Die x-Position des Roboters in Pixel
  float posY = 100;     //Die y-Position des Roboters in Pixel
  float direction= PI;  //Die Richtung in die der Roboter schaut, in Bogenmaß, gegen den Uhrzeigersinn, relativ zur X-Achse.
                        // (PI  => der Roboter schaut nach links)
}

Klassen können eigene Funktionen ("Methoden" genannt) enthalten

Schön wäre auch, wenn wir den Roboter mit einem einzigen Funktionsaufruf ein kleines Stück nach vorne gehen lassen könnten.

Das erreichen wir, indem wir der Klassendefinition eine entsprechende Methode (Funktion) hinzufügen:

class Robot { // Deklaration der Klasse Robot
  float posX = 100;   //Die x-Position des Roboters in Pixel
  float posY = 100;   //Die y-Position des Roboters in Pixel
  float direction= PI;  //Die Richtung in die der Roboter schaut 
 
  //Die moveForward-Funktion verändert die Position so, wie es einen Schritt nach vorne entspricht.
  void moveForward(float distance){
    posX = posX+cos(direction)*distance; // Innerhalb der Funktion kann direkt auf die Klassenvariablen 
    posY = posY+sin(direction)*distance; // zugegriffen werden. (Sie sind im Objekt quasi "lokal")
  }
}

Klassenmethoden funktionieren genauso wie Funktionen:

  • Sie können einen Rückgabewert haben (z.B. float, int, oder einen anderen Klassentypen)
  • Wenn sie nichts zurückgeben, schreibt man vor ihren Namen anstelle eines Datentyps „void“
  • Die Parameter können im inneren der Funktion verändert werden, ohne dass das Auswirkungen nach außen hat

Objekte: Konkrete Manifestationen eines in einer Klasse beschriebenen Konzepts

Mit unserer Klassendefinition haben wir beschrieben, welche Informationen ein Roboter grundsätzlich enthält und was er tun kann. Diese Beschreibung bietet praktisch eine Schablone, mit der wir in unserem Programm eine beliebige Anzahl von konkreten Manifestationen (Objekte genannt) erzeugen können.

In Java müssen Objekte explizit mit "new" erzeugt werden.

Im Gegensatz zur Deklaration von Variablen mit „primitivem“ Datentyp wie „int“ oder „float“ müssen Objekte explizit erzeugt werden:

// Deklariere die Objektvariable "myRobot" vom Typ "Robot" (die danach erst einmal leer ist)
Robot myRobot;
myRobot = new Robot(); // erzeuge ein Objekt com Typ "Robot" und speichere eine Referenz darauf in der "myRobot" Variable

Bei Arduino (C++) ist das anders: hier wird bereits bei der Deklaration eines Objekts eine Instanz erzeugt!

Auf Membervariablen und Methoden eines Objekts kann mit dem "." zugegriffen werden,

Unser frischgebackenes Roboterobjekt können wir jetzt verwenden, um einen Roboter in Bewegung zu simulieren.

Die entsprechende Funktion rufen wir auf, indem wir an den Namen des Objekts einen Punkt anhängen:

myRobot.direction=myRobot.direction+PI/2;  // drehe den Roboter um 90° gegen den Uhrzeigersinn 
myRobot.moveForward(1); // sag ihm, dass er eine Bewegung um einen Schritt nach vorne simulieren soll

Jedes Objekt hat seinen eigenen Satz von Membervariablen

Da jedes Objekt seine eigenen Membervariablen enthält, können wir ganz einfach mehrere Roboter erzeugen und in unterschiedliche Richtungen fahren lassen:

Robot robotOne=new Robot(); // ein erster Roboter
Robot robotTwo=new Robot(); // ein zweiter Roboter
 
robotOne.direction=PI;  // der erste Roboter schaut nach links
robotTwo.direction=0;  // der zweite Roboter schaut nach rechts

Methoden wirken "lokal" auf die Membervariablen des Objekts, mit dem sie aufgerufen werden

So kann z.B. die moveForward Methode genau die Positionsvariablen des Objekts verändern, mit dem sie aufgerufen wurde:

robotOne.moveForward(1); // verändert die Variablen robotOne.posX und robotOne.posY
 
robotTwo.moveForward(1); // verändert die Variablen robotTwo.posX und robotTwo.posY

In Java speichern Variablen nur Referenzen auf Objekte

Wenn ihr eine Variable mit einem Klassentyp deklariert (also z.B. unseren Roboter), dann wird in dieser Variable nicht etwa „das Objekt selbst“ (also ein Wert) gespeichert, sondern nur „welches Objekt mit dieser Variable gemeint ist“ (also eine Speicheradresse).

Konventionen

Bei der Benennung von Methoden sollte man sich an zwei grundsätzliche Konventionen halten:

  1. Der Name der Methode fängt mit einem kleinen Buchstaben an und Namen welche mehrere Worte beinhalten fangen beim zweiten Wort mit einem Großbuchstaben an.
  2. Der Name sollte beschreiben wie die Methode mit dem Objekt interagiert.

Unsere Klasse bekommt zwei weitere Methoden, eine um den Roboter an seiner aktuellen Position zu zeichnen und eine um den Roboter um einen gewissen Winkel zu drehen.

class Robot { // Deklaration der Klasse Robot
 
  int dRobot = 50;    //Der Durchmesser des Roboters in Pixel
  float direction = PI;  //Die Richtung in die der Roboter schaut 
                      //(PI (links), da der Winkel in Bogenmaß angegeben wird)
  float posX = 100;   //Die x-Position des Roboters in Pixel
  float posY = 100;   //Die y-Position des Roboters in Pixel
 
  //Diese Methode zeichnet einen Roboter 
  //an seiner aktuellen Position und Ausrichtung.
  void drawRobot(){ //"void" bedeutet dass es keinen Rückgabewert gibt
    // Zeichne einen Kreis an der Position des Roboters.
    ellipse(posX,posY,dRobot,dRobot);
    // Zeichne eine Linie welche die Orientierung des Roboters angibt.
    line(posX,posY,posX+cos(direction)*50, posY+sin(direction)*50);
  }
 
  //Die moveForward-Funktion verändert die Position so, wie es einen Schritt nach vorne entspricht.
  void moveForward(float distance){
    posX = posX+cos(direction)*distance; // Innerhalb der Funktion kann direkt auf die Klassenvariablen 
    posY = posY+sin(direction)*distance; // zugegriffen werden. (Sie sind im Objekt quasi "lokal")
  }
 
  //Diese Methode dreht den Roboter um einen gewissen Winkel 
  //welcher von "winkelaenderung" vorgegeben wird.
  // Die Methode bekommt einen Übergabeparameter vom Typ "float".
  void turnRobot(float angle){ 
    this. direction += angle;
  }
 
}

Jetzt können wir die fertige Klasse verwenden. Um dies in Processing auf eine möglichst übersichtliche weise zu tun sollte ein neuer Tab geöffnet werden:

In diesem neuen Tab speichert ihr eure Klasse.

Klasse verwenden in Processing

Klassen geben den Rahmen vor für unsere Objekte. Wie ihr bereits kennengelernt habt, kann man aus einer Klasse ein Objekt erzeugen durch den „new“ -Operator. Ein Programm welches in Processing geschrieben wurde kann die „Robot“-Klasse z.B. folgendermaßen einsetzen:

//Erstellt ein global verfügbares Objekt vom Typ "Robot", 
//wie "int" oder "double", und geben ihm den Namen "myRobot".
Robot myRobot;
 
void setup() {
  // Festlegen der Fenstergröße
  size(500,500);
  // Festlegen der Hintergrundfarbe
  background(235);
  //Initialisiert das "Robot" Objekt
  myRobot = new Robot();
}
 
void draw() {
  //Zu beginn jeder draw-Iteration wird das Fenster zurückgesetzt.
  background(235);
  //Das Robot-Objekt verfügt über eine Funktion die den Roboter malt.
  myRobot.drawRobot();
  //Diese Funktion bewegt den Roboter ein wenig.
  myRobot.moveRobot();
  //Dieser Funktionsaufruf sorgt dafür das der Roboter nach jeder translation 
  //um PI/200 gedreht wird.
  myRobot.turnRobot(PI/200);
}

Klassen für Fortgeschrittene

In diesem Abschnitt ist das Beispiel von oben nochmals aufgeführt in einer „best-practise“-Version. Hierbei ändern sich zwei wesentliche Dinge:

  1. Statt einfachen Kommentaren werden die Konventionen der JavaDoc angewandt. Die JavaDoc ist eine Documentation des Codes welche automatisch aus den Kommentaren generiert wird sofern diese der vorgegebenen Konvention entspricht. Wie diese Konvention im Einzelnen aussieht erfahrt ihr z.B. hier: https://www.wecowi.de/wiki/Javadoc.
  2. Diese Version enthält Konstruktoren. Mit Konstruktoren können die Klassenvariablen direkt beim erstellen eines Objektes modifiziert werden. Der „default“-Konstruktor ist auch dann vorhanden wenn er nicht explizit definiert wurde, deshalb konnte in der einfachen Version ein Objekt erstellt werden mit new Robot(). In Java können Konstruktoren und Methoden im allgemeinen überladen werden. Das bedeutet es darf mehrere Methoden mit demselben Namen geben solange sich die Übergabeparameter bzw. Rückgabetyp unterscheiden. Im folgenden Beispiel sind zwei Konstruktoren enthalten.
class Robot { // Deklaration der Klasse Robot
 
  /** Der Durchmesser des Roboters. */
  int dRobot;
  /** Die Richtung in die der Roboter schaut. */
  float winkel;
  /** Die x-Position des Roboters. */
  float posX;
  /** Die y-Position des Roboters. */
  float posY;
 
  /**
   * Der "default"-Konstruktor erstellt ein Roboter-Objekt mit Standardwerten für alle notwendigen Parameter.
   */
  public Robot() {
    this.dRobot = 50;
    this.winkel = PI;
    this.posX = 100;
    this.posY = 100;
  }
  /**
   * Dieser Konstruktor erstellt ein Roboter-Objekt mit allen notwendigen Parametern,
   * entsprechend der Vorgabe durch die Eingabeparameter.
   */
  public Robot(int dRobot, float winkel, float posX, float posY) {
    this.dRobot = dRobot;
    this.winkel = winkel;
    this.posX = posX;
    this.posY = posY;
  }
 
  /**
   * Diese Methode zeichnet einen Roboter an seiner aktuellen Position und Ausrichtung.
   */
  void drawRobot(){
    // Zeichne einen Kreis an der Position des Roboters.
    ellipse(posX,posY,dRobot,dRobot);
    // Zeichne eine Linie welche die Orientierung des Roboters angibt.
    line(posX,posY,posX+cos(winkel)*50, posY+sin(winkel)*50);
  }
 
  /**
   * Diese Methode bewegt einen Roboter ein kleines Stück in die Richtung in die er gerade schaut.
   */
  void moveRobot(){
    this.posX = this.posX+cos(this.winkel)*2;
    this.posY = this.posY+sin(this.winkel)*2;
  }
 
  /**
   * Diese Methode dreht den Roboter um einen gewissen Winkel welcher von "winkelaenderung" vorgegeben wird.
   */
  void turnRobot(float winkelaenderung){
    this.winkel = winkel + winkelaenderung;
  }
 
}

Referenzvariablen

Variablen welche stellvertretend für ein Objekt verwendet werden, z.B. myRobot aus dem vorherigen Bsp., stellen lediglich eine Referenz auf das Objekt dar. Man kann die Variable also auch als die Speicheradresse des Objektes verstehen. Dieser Sachverhalt ist insbesondere dann von Bedeutung wenn ein Objekt kopiert werden soll. Die „best-practise“ ist das Anlegen eines „copy“-Konstruktors in jeder Klasse, dieser überträgt alle Attribute des zu kopierenden Objektes in ein neues Objekt:

class DummyBean {
  String dummy;
  int yummyDummy;
 
  DummyBean(DummyBean dummyBeanToCopy) {
    this.dummy = dummyBeanToCopy.dummy;
    this.yummyDummy = dummyBeanToCopy.yummyDummy;
  }
}

Und können diesen Konstruktor entsprechend aufrufen:

//A Object of my DummyBean class
DummyBean myFirstDummy = new DummyBean();
//make a copy of the myFirstDummy object
DummyBean mySecondDummy = new DummyBean(myFirstDummy);

Kopiert man Objekte nicht auf diese Weise sondern durch:

//A Object of my DummyBean class
DummyBean myFirstDummy = new DummyBean();
//silly copy
DummyBean mySecondDummy = myFirstDummy;

, dann wirken sich Änderungen an „myFirstDummy“ auch auf „mySecondDummy“ aus da beide Variablen auf das selbe Objekt verweisen.

Vererbung

In Java kann unter Vererbung eine Spezialisierung einer bestehenden Klasse verstanden werden. Wir unterscheiden dazu in Super- und Subklassen. Die Superklasse (oder Elternklasse) beinhaltet eher allgemeine Attribute und Methoden. Die Klasse „Stift“ könnte z.B. Attribute für Stiftlänge, Stiftfarbe, usw. enthalten. Die Subklasse bekommt von der Superklasse alle Attribute und Methoden vererbt, erweitert dieses Set aber um eigene Attribute und Methoden. Z.B. könnte die Subklasse „Kugelschreiber“ das Attribut Stift eingefahren/Stift ausgefahren hinzufügen und eine Methode die diesen Zustand verändert.

Eine ausführliche Beschreibung wie Vererbung funktioniert findet ihr hier: http://www.java-tutorial.org/vererbung.html

techniken/objektorientjava.1467801681.txt.gz · Zuletzt geändert: 2016/07/06 12:41 von fbonowski