Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

techniken:objektorientjava2

Objektorientiert Programmieren in Java mit Processing - Teil II: Praktisches

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;
  }
 
}

Zugriffssteuerung auf Memberfunktionen und Variablen

Im Paradigma des Objektorientierten Programmierens werden Zugriffsmodifizierer verwendet um die Rechte anderer Objekte einzuschränken (Datenkapselung). Unter Java werden folgende Zugriffsmodifizierer verwendet:

Die Klasse selbst, innere Klassen Klassen im selben Package Unterklassen Sonstige Klassen
private Ja Nein Nein Nein
(default) Ja Ja Nein Nein
protected Ja Ja Ja Nein
public Ja Ja Ja Ja

private

Der Zugriffsmodifizierer private verhindert jeden Zugriff von außerhalb einer Klasse auf den entsprechenden Member. Üblicherweise werden Klassenvariablen auf diese Weise deklariert und können dann nur über sog. getter- und setter-Methoden verändert werden. Für dieses Vorgehen gibt es gute Gründe, siehe Kapselung.

(default)

Der Zugriffsmodifizierer (default) wird implizit verwendet, d.h. wenn kein anderer Zugriffsmodifizierer angegeben wird ist der Member (default) oder package private. Auf Member mit diesem Zugriffsmodifizierer kann nur innerhalb eines Package zugegriffen werden.
Wie ihr vielleicht bemerkt habt kommt Processing ganz ohne Zugriffsmodifizierer aus. Das liegt daran das der Processing-Sketch zusammen mit allen Tabs in einem Package liegt und daher (default) ausreicht um auf alle Member zugriff zu haben. Um es dem Programmieranfänger einfach zu machen müsst ihr nicht extra angeben das sich alles in einem Package befindet, das übernimmt der Processing-Compiler.

protected

Der Modifizierer protected ist etwas freizügiger als (default). Auf solche Member können auch Subklassen zugreifen, sogar wenn sie in einem anderen Package liegen als der betroffene Member.
Protected wird im Zusammenhang mit API-Programmierung und Templates verwendet.

public

Mit public gestattet ihr allen Klassen zugriff auf den jeweiligen Member. Wird in der Regel bei wichtigen Methoden, Konstruktoren und Datentypen eingesetzt.

Welche Zugriffsmodifizierer solltest du verwenden?

Das ist letztendlich Geschmacksache und kommt darauf an wie und wo du an Code arbeitest.
Felix sagt: „Hier im Kurs sind Zugriffsmodifizierer unnötig, KISS-Prinzip“.
Ein Java Programmierer würde sagen: „In einfachen Fällen reicht private für Variablen und public für Methoden, Konstruktoren und Datentypen. Es gilt die Faustregel - so streng wie möglich, so freizügig wie nötig“.

Beispiel für Zugriffsmodifizierer

public class PointIn2D {
    private int x, y; // private Klassenvariablen
    public PointIn2D(int x, int y) { // Konstruktor, für alle nutzbar
        setX(x);
        setY(y);
    }
    public int getX() { // getter-Methode
        return x; 
    }
    public int getY() { 
        return y; 
    }
    public void setX(int x) { // setter-Methode zum Ändern der Klassenvariable
        this.x = x; 
    }
    public void setY(int y) { 
        this.y = y; 
    }
}

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

Exception Handling

In diesem Abschnitt geht es um Fehlerbehandlung von Fehlern die zur Laufzeit eures Programmes auftreten. Das sind alle Fehler die nicht schon beim Kompilieren eures Codes auftreten. Dazu gehören Fehler wie versuchter Zugriff auf bisher nicht erzeugte Objekte oder Division durch Null.
Im Gegensatz zu anderen Programmiersprachen, wie etwa C, muss in Java ein Fehler nicht gleich zum Programmabsturz führen. Solche Fehler werden in Java als Exceptions behandelt und können abgefangen werden. Am Beispiel der Roboter-Klasse führen wir eine Null-Pointer-Exception herbei, indem wir das Robot-Objekt nicht initialisieren und trotzdem versuchen die drawRobot-Methode aufzurufen:

Robot myRobot;
 
void setup() {
  size(199,199);
}
 
void draw() {
    myRobot.drawRobot();
}

Dieser Code führt zum Abbruch des Programms und es wird der Hinweis NullPointerException ausgegeben. Der Vorteil des sogenannten try-catch-Blocks ist das der Fehler nicht zum Absturz führt und eine detaillierte Fehlerausgabe mit Angabe der fehlerhaften Programmzeile generiert wird.

Robot myRobot;
void setup() {
  size(199,199);
}
void draw() {
  try
  {
    myRobot.drawRobot();
  }
  catch(Exception e)
  {
    println("Exception: " + e.getMessage());
    e.printStackTrace();
  }
}

Die Ausgabe zeigt euch den Pfad in dem der Fehler aufgetreten ist, wobei die erste Zeile eure Message ist, die zweite der Exception-Typ und die dritte gibt euch am Ende der Zeile die entsprechende Zeile in eurem Code:

Exception: null
java.lang.NullPointerException
	at sketch_160720a.draw(sketch_160720a.java:24)
	at processing.core.PApplet.handleDraw(PApplet.java:2402)
	at processing.awt.PSurfaceAWT$12.callDraw(PSurfaceAWT.java:1527)
	at processing.core.PSurfaceNone$AnimationThread.run(PSurfaceNone.java:316)

Processing fügt eurem Code vor dem Kompilieren noch einige Zeilen Code hinzu, daher verschiebt sich die Zeilenangabe etwas. Die Verschiebung müsst ihr experimentell feststellen!

techniken/objektorientjava2.txt · Zuletzt geändert: 2016/07/21 15:02 von c.jaedicke