Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

techniken:objektorientjava

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

Referenzvariablen

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).

In der Praxis macht das insbesondere bei Zuweisungen von Variablen einen Unterschied:

Bei primitiven Variablen...

  • Wird der Inhalt der Variable kopiert.
  • Beide Variablen haben danach getrennte Kopien des selben Wertes.
  • Veränderungen der einen Variable haben nach einer Zuweisung keinen Einfluss auf den Wert der anderen Variable

Bei Variablen mit Klassentyp...

  • Wird nur die Referenz / Speicheradresse kopiert.
  • Beide Variablen verweisen danach auf das selbe Objekt.
  • Veränderungen des Objektes über die eine Variable wirken sich dementsprechend auf das aus, was über die andere Referenzvariable ausgelesen werden kann.

Hier ein Beispiel, welches das unterschiedliche Verhalten von Variablen mit primitiven und Klassentypen:

//Primitive Variablen enthalten einen Wert, der bei Zuweisungen kopiert wird:
int a=1;
int b=2;
a=b;         // Der Inhalt von b (2) wird in a kopiert.
println(a);  // Ausgabe "2"
println(b);  // Ausgabe "2"
b=3;         // Der Inhalt von b wird mit "3" überschrieben, a bleibt davon unberührt.
println(a);  // Ausgabe "2" (unverändert)
println(b);  // Ausgabe "3"
 
//Variablen mit Klassentyp enthalten eine Referenz auf ein Objekt:
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
 
robotOne=robotTwo; // RobotOne zeigt auch jetzt auf das Objekt, auf das vorher nur RobotTwo gezeigt hat.
// Das Objekt, auf das robotOne vorher gezeigt hat, wird vergessen. (und vom Garbage Collector entsorgt)
 
 
robotTwo.direction=PI/2; // Ändere die Richtung des Objekts, auf das die Referenz in robotTwo zeigt.
println(robotOne.direction); // Da beide Variablen das gleiche Objekt referenzieren, kommt auch hier PI/2 heraus!

Ein fertiges Programm

Zum Abschluss nochmal ein kleines Programm, in dem alles, was bisher passiert ist, angewandt wird:

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)
 
  //Diese Methode zeichnet einen Roboter 
  //an seiner aktuellen Position und Ausrichtung.
  void drawRobot(){ //"void" bedeutet dass es keinen Rückgabewert gibt
    ellipse(posX,posY,20,20); // Zeichne einen Kreis an der Position des Roboters.
    line(posX,posY,posX+cos(direction)*50, posY+sin(direction)*50);// Zeichne eine Linie welche die Orientierung des Roboters angibt.
  }
 
  //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")
  }
}
 
Robot myRobot; // deklariere eine Referenz auf einen Roboter (sie verweist in diesem Moment ins Leere)
 
 
// Das Processing "setup"
void setup(){
  myRobot=new Robot();
}
 
 
// Das Processing "setup"
void draw(){
  background(235);       // Male den Hintergrund des Fensters neu an...
  myRobot.drawRobot();   // Zeichne in das nun leere Fenster den Roboter
  myRobot.moveRobot();   // Diese Funktion bewegt den Roboter ein wenig.
  myRobot.direction+=PI/200; // drehe den Roboter ein Stück gegen den Uhrzeigersinn
}

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.

Tabs in Processing

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.

Das ist noch nicht alles...

Mit dem, was bis hierhin besprochen wurde, könnt ihr schonmal einigermaßen Ordunung in euer Programm bringen und die von einer Library bereitgestellen Klassen benutzen.

Für ein vollständiges Verständnis von Objektorientierung in Java fehlen noch:

  • Zugriffssteuerung auf Memberfunktionen und Variablen durch private, public, protected
  • Vererbung

Eure Programme werden besser lesbar, wenn ihr die folgenden „Style-Konventionen“ einhaltet: http://www.oracle.com/technetwork/java/codeconventions-135099.html

techniken/objektorientjava.txt · Zuletzt geändert: 2016/07/14 14:43 von c.jaedicke