7. Klassen und Objekte (2)


Verallgemeinerung auf zwei Dimensionen

neue Vektorklasse

Für die Verallgemeinerung unserer Massenpunkt-Klasse auf zwei und mehr Dimensionen führen wir eine neue Klasse, die Klasse Vektor ein. Hier also die Deklaration einer Klasse für Vektoren in zwei Dimensionen:

  class Vektor {

    double x, y;

    Vektor(double x, double y) {
      this.x = x;
      this.y = y;
    }

    Vektor(Vektor v) {
      x = v.x;
      y = v.y;
    }

    double norm() {
        return Math.sqrt(x * x + y * y);
    }

    void mal(double s) {
      x *= s;
      y *= s;
    }

    void plus(Vektor v) {
      x += v.x;
      y += v.y;
    }

    void plusvs(Vektor v, double s) {
      x += v.x * s;
      y += v.y * s;
    }


  }

Der Aufbua der Vektorklasse folgt wieder dem gleichen einfachen Schema, das wir schon im letzten Abschnitt kennengelernt haben:

Der Vorteil der so definierten Klasse Vektor ist nicht nur Eleganz und Einfachkeit (weil wir jetzt mit den kurzen und einfachen Befehlen - Methodenaufrufen - auf die ganzen Vektoren wirken), sondern auch vereinfachte Erweiterbarkeit; um auf dreidimensionale Vektoren umzusteigen, brauchen wir der Implementierung der Klasse Vektor nur noch eine weitere Komponente hinzuzuf&uum;gen. Außerhalb der Klasse sind meistens keine weiteren Programmänderungen notwendig, da die meisten Algorithmen für Vektoren von beliebiger Dimension gültig sind!

Neue Massenpunkt-Klasse

Mit dieser Vektor-Klasse können wir nun nun unsere Massenpunkt-Klasse aus dem letzten Abschnitt auf zwei Dimensionen erweitern. Die neue Klassendeklaration sieht fast genauso aus wie im eindimensionalen Fall. Wir brauchen wieder Attribute:

  class Massenpunkt {

    Vektor q, p;
    double m;

Der einzige Unterschied ist hier der Typ der Attribute q und p; Wir benutzen Vektor statt double! Wir brauchen wieder (mindestens) einen Konstruktor:

    Massenpunkt(double qx, double qy, double px, double py, double m) {
      q = new Vektor(qx, qy);
      p = new Vektor(px, py);
      this.m = m;
    }

In diesem Konstruktor wird der Konstruktor der Vektor-Klasse aufgerufen, um die entsprechenden Datenfelder bereitzustellen. Auf diese Weise können Klassen und ihre Konstruktoren beliebig tief verschachtelt werden! Zuletzt brauchen wir noch die Methode euler:

    void euler(Vektor kraft, double dt) {
      q.plusvs(p, dt / m);
      p.plusvs(kraft, dt);
    }

  }

Diese Methode ist jetzt übrigens unabhängig von der Dimension des Vektors! Wenn wir nun also zu dreidimensionalen Vektoren übergehen wollen, so brauchen wir hier nichts mehr zu verändern!

Anwendung der neuen Klassen

Als Beispiel, wie man die oben definierte Klasse und ihre Methoden anwendet, geben wir wieder einmal eine komplette paint-Routine an (beachten Sie auch die Ähnlichkeit mit dem eindimensionalen Fall!):

  public void paint(Graphics g) {

    Massenpunkt M1 = new Massenpunkt(-1.0, 1.5, 0.0, -0.2, 1.0);
    Massenpunkt M2 = new Massenpunkt(0.5, 1.5, 0.0, -0.2, 2.0);
    double tmax = 25.0, dt = 0.00001;
    Vektor kraft1, kraft2;

    setRange(-2.0, 2.0, -2.0, 2.0);

    for(double t = 0.0; t <= tmax; t += dt) {

      // Momentaufnahme der Kraefte
      kraft1 = Federkraft(M1, M2);
      kraft2 = Federkraft(M2, M1);

      // Euler Integration
      M1.euler(kraft1, dt);
      M2.euler(kraft2, dt);

      // M1 wird in blau und M2 in rot gezeichnet
      g.setColor(Color.blue);
      g.fillOval(sx(M1.q.x) - 2, sy(M1.q.y) - 2, 4, 4);
      g.setColor(Color.red);
      g.fillOval(sx(M2.q.x) - 2, sy(M2.q.y) - 2, 4, 4);
    }
  }

Die Anwendung der neuen Massenpunkt-Klasse unterscheidet sich kaum von dem Beispiel aus dem letzten Abschnitt. Der Konstruktor wird wieder eingesetzt, um neue Objekte zu erzeugen und die euler-Methode wird verwendet, um die Integration von Ort und Impuls auszuführen. Im Gegensatzt zum eindimensionalen Beispiel haben wir hier eine Federkraft zwischen den beiden Massenpunkten angenommen. Diese Federkraft implementieren wir als Methode in unserem Applet:

  Vektor Federkraft(Massenpunkt auf, Massenpunkt von) {

    final double D = 1.0; // Federkonstante

    return new Vektor( -D * (auf.q.x - von.q.x),
                       -D * (auf.q.y - von.q.y) );
  }

Ihre Aufgabe ist es nun, die Methode Federkraft so zu modifizieren, dass Sie nicht die lineare Federkraft, sondern die Gravitationskraft zurückgibt! Sehen Sie sich dazu auch die Aufgabe in Planeten in zwei Dimensionen an.

Falls Sie Gefallen gefunden haben an Klassen und Objekten, dann können Sie sich ja auch einmal überlegen, ob es Sinn macht, eine Klasse Gravitationskraft zu deklarieren, wie diese aussehen müsste und wie sie einzusetzen wäre...

Separate Kompilation

Um unser Hauptprogramm übersichtlich zu halten, bietet es sich an, fertige Klassen in seperaten Dateien abzulegen und bei Bedarf automatisch vom Compiler in unsere Programme einbinden zu lassen. Die Vorgehensweise ist denkbar einfach:

  1. Schreiben Sie eine Klassendeklaration oder kopieren Sie eine, z.B. aus dieser html-Seite... Vergessen Sie auch nicht, evtl. benötigte import-Befehle an den Anfang zu stellen!

  2. Speichern Sie die Klassendeklaration in einer Textdatei mit der Endung .java ab. Der Name der Datei muss dabei wieder mit dem Namen der Klasse übereinstimmen. Diese Datei stellen Sie in das Arbeitsverzeichnis, in dem Sie auch ihr Hauptprogramm haben.

  3. Zum Testen ihrer Klassendeklaration können Sie nun wie gewohnt
    javac NeueKlasse.java
    aufrufen. Wenn die Klassendeklaration dann fehlerfrei ist, brauchen Sie sich nicht weiter um sie zu kümmern! Der Compiler wird beim Übersetzen ihres Hauptprogrammes, sobald er die Klasse benötigt, automatisch das aktuelle Verzeichnis nach einer passenden Datei durchsuchen, sie falls nötig übersetzen und die Klasse dann einbinden!

Achtung! Der Compiler sucht beim Übersetzen immer zuerst nach ".class-Dateien". Findet er eine solche für die benötigte Klasse, so bindet er sie ein; auch dann, wenn eine passende ".java-Datei" neueren Datums vorliegt. Das bedeutet, dass Aktualisierungen an (seperaten) Klassendeklarationen immer erst wirksam werden, wenn man die alte ".class-Datei" löscht oder aber die geänderte Klasse mit einem Aufruf von

javac NeueKlasse.java
explizit neu übersetzt (dadurch wird dann die alte ".class-Datei" überschrieben).