Einführung in Java (Teil 2)



Crashkurs Objektorientierte Programmierung

In der objektorientierten Programmierung wird das zu lösende Problem durch Objekte modelliert, die miteinander in Beziehung stehen.
Die drei fundamentalen Elemente einer objektorientierten Sprache sind Klassen, Vererbung und Polymorphie. Sie werden im folgenden kurz vorgestellt.

Klassen: Eine Klasse ist eine abstrakte Beschreibung einer Gruppe von Objekten. Objekte enthalten sowohl Variablen, die den Zustand bzw. Eigenschaften des Objekts beschreiben, als auch Methoden, die das Verhalten des Objekts bestimmen.
Beispiel: Objekt Hund
Zustand: Name, Rasse, Fellfarbe, ...
Methoden: bellen, Schwanz wedeln, apportieren, ...

Vererbung: Unter Vererbung versteht man die Möglichkeit, ein neues Objekt von einem bereits vorhandenen Objekt abzuleiten, wobei das neue Objekt alle Merkmale und Fähigkeiten des alten besitzt. Dem neu erzeugten Objekt können noch weitere charakteristische Merkmale hinzugefügt werden. Die Klasse, von der abgeleitet wird, nennt man Oberklasse; die abgeleitete Klasse bezeichnet man als Unterklasse.
Beispiel: Oberklasse Fahrrad
Unterklassen: Mountainbike, Tandem ...

Polymorphie(Vielgestaltigkeit): Polymorphie bedeutet, daß verschiedene Unterklassen dieselbe Botschaft verstehen, aber völlig unterschiedlich umsetzen können.
Beispiel:
Oberklasse: Säugetiere
Unterklassen: Kühe, Löwen
Botschaft: Essen
Verschiedene Reaktionen auf die Botschaft Essen:
Kühe: wiederkäuen von Gras
Löwen: verschlingen der erlegten Beute
 

Datenkapselung:

Das objektorientierte Programmieren basiert auf dem Konzept der Datenkapselung. Dadurch ist der Zugriff auf bestimmte Objekte eingeschränkt und nur über wohldefinierte Schnittstellen erlaubt.
Auf private Datenfelder kann nur über Methoden zugegriffen werden.

Beispiel 1:

//Programm Ausgabe von Vektoren.

public class vektor
{
 private double x, y;

  //Konstruktor
 public vektor (double x, double y)
 {
  this.x = x;
  this.y = y;
 }

  //Ausgabe Vektor
  public String toString()
 {
  return new String("(" + x + ", " + y + ")");
 }

 public static void main(String[ ] args)
 {
   vektor u = new vektor(2,4);
   System.out.println("Vektor u = " + u);
  //  System.out.println("x-Komponente: x = " + u.x);  nicht moeglich da x private
 }
}

Output:
Vektor u = (2.0, 4.0)

Auf die privaten Datenfelder x und y kann nur über die Methode toString() zugegriffen werde. Direkter Zugriff, wie beispielsweise mit System.out.println("x-Komponente: x = " + u.x) führt zu Fehlermeldungen.

Im folgenden werden zwei spezielle Methoden beschrieben, wie man den Wert eines privaten Datenfeldes lesen  (Accessor Methoden) bzw. verändern kann (Mutator Methoden).

Accessor Methoden:

Accessor Methoden sind read-only Methoden. Sie liefern nur die Werte  privater Datenfelder,
verändern aber nicht den Zustand des Objekts.

Syntax:
private  Datentyp x;

public void  getx(double x)
 {
   return x;
 }

Es ist  üblich als Methodenname getVariablenname zu verwenden.

Beispiel 2:

//Programm Ausgabe von Vektoren.

public class vektor
{
 private double x, y;

 //Konstruktor
 public vektor(double x, double y)
 {
  this.x = x;
  this.y = y;
 }

 //Accessor Methoden
 public double getx()
 {
  return x;
 }

 public double gety()
 {
  return y;
 }

  //Ausgabe Vektor
  public String toString()
 {
  return new String("(" + x + ", " + y + ")");
 }

 public static void main(String[ ] args)
 {
   vektor u = new vektor(2,4);
   System.out.println("Vektor u = " + u);
   System.out.println("x-Komponente: x = " + u.getx());
   System.out.println("y-Komponente: y = " + u.gety());
 }
}

Output:
Vektor u = (2.0, 4.0)
x-Komponente: x = 2.0
y-Komponente: y = 4.0
 

Mutator Methoden:

Mutator Methoden sind read-write Methoden. Sie können den Zustand des Objekts verändern.

private  Datentyp x;

public void  setx(double x)
 {
  this.x = x;
 }

Es ist allgemein üblich als Methodenname setVariablenname zu verwenden.

Beispiel 3:

//Programm Ausgabe von Vektoren.

public class vektor
{
 private double x, y;

 //Konstruktor
 public vektor(double x, double y)
 {
  this.x = x;
  this.y = y;
 }

 //Mutator Methoden
 public void setx(double x)
 {
  this.x = x;
 }

 public void sety(double y)
 {
  this.y = y;
 }

  //Ausgabe Vektor
  public String toString()
 {
  return new String("(" + x + ", " + y + ")");
 }

 public static void main(String[ ] args)
 {
   vektor u = new vektor(2,4);
   System.out.println("Vektor u = " + u);
   u.setx(5);
   u.sety(1);
   System.out.println("Vektor u = " + u);
 }
}

Output:
Vektor u = (2.0, 4.0)
Vektor u = (5.0, 1.0)

Komposition und Vererbung

Komposition und Vererbung sind zwei verschiedene Möglichkeiten Programmcode wiederzuverwenden. Dabei werden bei der Komposition bereits programmierte Klassen in das aktuelle Programm eingebaut. Komposition ist daher eine Zusammenfassung von verschiedenen Klassen. Bei der Vererbung können Datenelemente und Methoden einer Klasse von einer anderen Klasse geerbt werden, die noch weiterere spezifische Datenelemente und Methoden hinzufügen darf. Vererbung bedeutet daher immer eine Spezialisierung.

1.)Komposition

Komposition ist die Erzeugung einer Klasse, wobei schon bereits programmierte Klassen genutzt werden können. Beispiel 4 und Beispiel 5 zeigen die Erzeugung eines Kreisobjekts mit und ohne Verwendung einer anderen Klasse. In Beispiel 4 wird eine Kreisklasse definiert mit den Datenfeldern Radius und x, y (für die Koordinaten des Kreismittelpunktes). Als Methoden werden die Berechnung des Kreisumfangs und der Kreisfläche implementiert.

Beispiel 4:  Kreisobjekt

//Programm: Kreis.java

class Kreis
{
 private double x,y;
 private double Radius;

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

public double x()
 {
  return x;
 }

 public double y()
 {
  return y;
 }

 public double Durchmesser()
 {
  return 2*Radius;
 }

 public double Flaeche()
 {
  return Math.PI * Radius * Radius;
 }
 

 public String toString()
 {
  return new String("Mittelpunkt = (" + x + ", " + y + ")" + ", Radius = " + Radius);
  }

public static void main(String[ ] args)
 {
  Kreis kreis = new Kreis(3,1,2.0);
  System.out.println("Kreis: " + kreis);
  System.out.println("Kreismittelpunkt = (" + kreis.x() + ", " + kreis.y() + ")");
  System.out.println("Durchmesser = " + kreis.Durchmesser());
  System.out.println("Flaeche = " + kreis.Flaeche());
 }
}

Output:

Kreis: Mittelpunkt = (3.0, 1.0), Radius = 2.0
Kreismittelpunkt = (3.0, 1.0)
Durchmesser = 4.0
Flaeche = 12.566370614359172

In Beispiel 5 wird eine Kreisklasse definiert, die als Datenelemente den Radius und den Mittelpunkt (Koordinaten x und y sind jetzt als Punkt zusammengefasst) besitzt. Die Implementierung der Methoden ist völlig analog zu Beispiel 4. Die Punktklasse war schon vorher vorhanden, so daß sie in das Kreisprogramm eingebaut werden konnte (Die Punktklasse ist dieselbe Klasse wie die vektorklasse, nur das der Name der Klasse auf Punkt geändert wurde).

Beispiel 5:

Strukturierter:

//Datei 1: Punkt.java

public class Punkt
{
 private double x,y;

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

 public double x()
 {
  return x;
 }

 public double y()
 {
  return y;
 }

 public String toString()
 {
  return new String("(" + x + ", " + y + ")");
 }
}



//Datei 2: Kreis.java

class Kreis
{
 private Punkt Mittelpunkt;
 private double Radius;

 public Kreis(Punkt Mittelpunkt, double Radius)
 {
  this.Mittelpunkt = Mittelpunkt;
  this.Radius = Radius;
 }

 public Punkt Mittelpunkt()
 {
  return Mittelpunkt;
 }

 public double Durchmesser()
 {
  return 2*Radius;
 }

 public double Flaeche()
 {
  return Math.PI * Radius * Radius;
 }

  public String toString()
 {
  return new String("Mittelpunkt = " + Mittelpunkt + ", Radius = " + Radius);
 }
}



//Datei 3: TestKreis.java

class TestKreis
{
 public static void main(String[] args)
 {
  Kreis kreis = new Kreis(new Punkt(3,1),2.0);
  System.out.println("Kreis: " + kreis);
  System.out.println("Kreismittelpunkt = " + kreis.Mittelpunkt());
  System.out.println("Durchmesser = " + kreis.Durchmesser());
  System.out.println("Flaeche = " + kreis.Flaeche());
 }
}

Die drei Dateien werden im gleichen Verzeichnis abgelegt.
> ls Pfadname/Verzeichnisname
Kreis.java      Punkt.java       TestKreis.java
> cd Pfadname/Verzeichnisname
> javac TestKreis.java
> ls
Kreis.class      Punkt.class       TestKreis.class
Kreis.java       Punkt.java         TestKreis.java
> java  TestKreis
Kreis: Mittelpunkt = (3.0, 1.0), Radius = 2.0
Kreismittelpunkt = (3.0, 1.0)
Durchmesser = 4.0
Flaeche = 12.566370614359172
 

2.)Vererbung

Eine der wichtigsten Eigenschaften von objektorientierten Systemen stellt die sogenannte "Vererbung" von Oberklassen  auf Unterklassen dar. Die Unterklasse erbt alle (nicht privaten) Datenelemente und Methoden der Oberklasse und kann noch neue Methoden und Datenelemente hinzufügen. Jede Klasse besitzt eine Oberklasse. Ist diese jedoch nicht näher spezifiziert, ist die Klasse automatisch eine Unterklasse der Klasse Objekt. Da jede Klasse eine Oberklasse besitzt, bilden die Klassen in Java eine Klassenhierarchie, die durch eine Baumstruktur dargestellt werden kann.

Beispiel 6:

//Datei 1: Oberklasse.java

class Oberklasse
{
  protected int o;                                                                        // 1

  public int fo()
  {
   return -o;
  }

  public void printo()
  {
   System.out.println("(" + o + ")");
  }
}



//Datei 2: Unterklasse.java

class Unterklasse extends Oberklasse                                    // 2
{
  private int u = 4;

  public int fu()
  {
   return -u;
  }

  public void printu()
  {
   System.out.println("(" + u + ", " + o + ")");                              // 3
  }
}



//Datei 3: TestVererbung.java

class TestVererbung
{
 public static void main(String[ ] args)
 {
  System.out.println("In Oberklasse: ");
  Oberklasse O = new Oberklasse();
  System.out.print("o = ");O.printo();
  System.out.println("O.fo()= " + O.fo());

  System.out.println("In Unterklasse: ");
  Unterklasse U = new Unterklasse();
  System.out.print("u = ");U.printu();
  System.out.println("U.fu()= " + U.fu());
  System.out.println("U.fo()= " + U.fo());                                  // 3
 }
}

Output:
In Oberklasse:
o = (0)
O.fo()= 0
In Unterklasse:
u = (4, 0)
U.fu()= -4
U.fo()= 0

Aus diesem Beispiel kann man einiges über Unterklassen lernen:

  1. Unterklassen erben keine privaten Felder. Als Modifier wird meist protected verwendet. Der Modifier protected bedeutet, daß Elemente nur in Klassen innerhalb desselben Package oder in Unterklassen angesprochen werden können, nicht aber von fremden Klassen (geschützt).
  2. Wenn von einer Klasse eine Unterklasse abgeleitet werden soll, muß das Schlüsselwort extends im Kopf der Unterklassendeklaration verwendet werden.
  3. Die Unterklasse kann auf die Datenelemente und Methoden der Oberklasse zugreifen.
Im folgenden Programmausschnitt wird eine Unterklasse GraphischerKreis von Kreis definiert, die einen Kreis in ein Fenster zeichenen soll. Die geerbten Datenelemente (Kreismittelpunkt (x,y) und Radius r) werden um die Elemente Füllung und Kontur erweitert. Das folgende Beispiel setzt voraus, daß bereits 2 Klassen definiert sind: public class GraphischerKreis extends Kreis
{
  //Konstruktor
  GraphischerKreis(double x, double y, double r, Color Kontur, Color Fuellung)
  {
    this.x = x;
    this.y = y;
    this.r = r;
    this.Fuellung = Fuellung;
    this.Kontur = Kontur;
  }

  //Zusaetzliche Datenelemente
   Color Fuellung, Kontur;

  //Methode zum Zeichnen eines Kreises
  public void draw(DrawWindow dw)
  {
     dw.drawCircle(x, y, r, Kontur, Fuellung)
  }

Konstruktoren werden nicht automatisch von der Oberklasse übernommen sondern müssen neu definiert werden, wenn man mehr als den Default-Konstruktor mit leerer Parameterliste braucht.
Allerdings hätte man auch den Konstruktor der Oberklasse aufrufen können, so daß man nur diejenigen Datenelemente initialisieren muß, die gegenüber der Oberklasse neu sind. Der Konstruktor der Oberklasse kann als erstes Statement mit super() aufgerufen werden.
Damit läßt sich der GraphischerKreis - Konstruktor auch folgendermaßen schreiben:

GraphischerKreis(double x, double y, double r, Color Kontur, Color Fuellung)
  {
    super(x, y, r);
    this.Fuellung = Fuellung;
    this.Kontur = Kontur;
  }

Java unterstützt keine mehrfache Vererbung.  Unter mehrfacher Vererbung versteht man die Möglichkeit, daß eine Klasse Unterklasse mehrerer Oberklassen ist. Nach extends darf also nur eine Oberklasse angegeben werden. Eine mehrfache Vererbung läßt sich mit Interfaces realisieren (s. Interfaces).

Verdeckung von Variablen

Beispiel 7:

//Datei 1: Oberklasse.java
class Oberklasse
{
  protected int o;

  public int fo()
  {
   return -o;
  }

  public void printo()
  {
   System.out.println("(" + o + ")");
  }
}



//Datei 2: Unterklasse.java

class Unterklasse extends Oberklasse
{
  private int o = 4, u = super.o;

  public int fu()
  {
   return -o;
  }

  public void printu()
  {
   System.out.println("(" + o + ", " + u +")");
  }
}



//Datei 3: TestVerdeckung.java

class TestShadowing
{
 public static void main(String[ ] args)
 {
  System.out.println("In Oberklasse: ");
  Oberklasse O = new Oberklasse();
  System.out.print("o = ");O.printo();
  System.out.println("O.fo()= " + O.fo());

  System.out.println("In Unterklasse: ");
  Unterklasse U = new Unterklasse();
  System.out.print("o = ");U.printu();
  System.out.println("U.fu()= " + U.fu());
  System.out.print("(o, u) = ");U.printu();
 }
}

Output:
In Oberklasse:
o = (0)
O.fo()= 0
In Unterklasse:
o = (4, 0)
U.fu()= -4
(o, u) = (4, 0)

Die Variable o in der Unterklasse verdeckt die entsprechende Variable der Oberklasse. Auf diese kann aber mit dem Schlüsselwort super noch zugegriffen werden.

Überschreiben von Methoden(Overriding)

Geerbte Methoden  können überschrieben werden. Das nennt man Overriding. Dardurch hat eine Unterklasse die Möglichkeit eine Methode anders zu implementieren als ihre Oberklasse.

Beispiel 8:

//Datei 1: Oberklasse.java

class Oberklasse
{
  protected int o;

  public int fo()
  {
   return -o;
  }

  public String toString()
  {
   return new String("(" + o + ")");
  }
}



//Datei 2: Unterklasse.java

class Unterklasse extends Oberklasse
{
  private int u = 4;

  public int fu()
  {
   return -u;
  }

  public String toString()
  {
   return new String("(" + u + ", " + o + ")");
  }
}



//Datei 3: TestOverriding.java

class TestOverriding
{
 public static void main(String[ ] args)
 {
  System.out.println("Overriding:");                              //1

  Oberklasse O1 = new Oberklasse();
  System.out.println("O1.fo()= " + O1.fo());

  //return new String("(" + o +")");
  System.out.println("O1 = " + O1);

  Unterklasse U = new Unterklasse();
  System.out.println("U.fu()= " + U.fu());

  //return new String("(" + u + ", " + o + ")");
  System.out.println("U = " + U);

  System.out.println("Polymorphie:");                             //2
  Oberklasse O2 = U;
  System.out.println("O2.fo()= " + O2.fo());
  System.out.println("O2 = " + O2);
  }
}

Output:
Overriding:
O1.fo()= 0
O1 = (0)
U.fu()= -4
U = (4, 0)
Polymorphie:
O2.fo()= 0
O2 = (4, 0)
 

  1. In der Unterklasse wird die Methode toString überschrieben, da sie in beiden Klassen den gleichen Namen, Returnwert und Parameterliste (ist leer) hat.
  2. Jedes Unterklassenobjekt ist auch ein Oberklassenobjekt. Das Unterklassenobjekt ist dann eine Instanz der Oberklasse, die sich aber wegen der überschriebenen Methoden anders verhält. In unserem Beipiel liefert  O2 = (4, 0) und nicht O2 = (0). Die Eigenschaft, daß Instanzen von verschiedenen abgeleiteten Klassen als Instanzen der gemeinsamen Oberklasse angesprochen werden können, aber in verschiedenen Punkten unterschiedliches Verhalten zeigen, nennt man Polymorphie (Vielgestaltigkeit).
Wenn eine Methode in der Oberklasse als final deklariert ist, kann sie von Unterklassen nicht
überschrieben werden.

Abstrakte Klassen:

Abstrakte Klassen enthalten  Methoden, die nicht mit einem Funktionskörper ausgestattet sind.
Für abstrakte Klassen wird das Schlüsselwort abstract verwendet.

Syntax für die Definition einer abstrakten Klasse:
abstract class  KlassenName
{
  public abstract Rückgabedatentyp Methodenname1 (Parameterliste) ;
  public abstract void Methodenname2 (Parameterliste) ;
  ...
}

Jede abgeleitete Klasse  muß alle geerbten Methoden implementieren,  sonst meldet der Compiler einen Fehler.

In Beispiel 9 sollen Flächen und Umfänge verschiedener geometrischer Figuren (hier nur Kreis und Rechteck) aufaddiert werden. Zur Realisierung verwendet man einen Array, dessen Elemente die berechneten Flächen enthalten und einen weiteren, der die einzelnen Umfänge speichert. Diese werden unter Verwendung von Schleifen aufaddiert.  Eine elegante Methode dies zu tun, ist die Definition einer abstrakten Klasse, in der zwei allgemeine Methoden Fläche und Umfang aufgeführt sind. Von der Klasse Shape abgeleitet sind zwei Klassen Kreis und Rechteck, in denen die abstrakten Methoden zur Berechnung der Fläche und des Umfangs implementiert werden.

Beispiel 9:

//Datei1

abstract class Shape
{
 public abstract double Flaeche();
 public abstract double Umfang();
}



//Datei2

class Kreis extends Shape
{
  //Radius r
  private double r;

  //Konstruktoren
  Kreis()
  {
   r = 1.0;
  }

  Kreis(double r)
  {
   this.r = r;
  }

 //Methoden zur Bestimmung von Flaeche und Umfang
  public double Flaeche()
  {
   return Math.PI * r * r;
  }

  public double Umfang()
  {
   return 2 * Math.PI * r;
  }
}



//Datei3

class Rechteck extends Shape
{
  //Hoehe und Breite
  private double h, b;

  //Konstruktoren
  Rechteck()
  {
   b = 1.0; h = 1.0;
  }

  Rechteck(double b,double h)
  {
   this.b = b;
   this.h = h;
  }

  //Methoden zur Bestimmung von Flaeche und Umfang
  public double Flaeche()
  {
   return b * h;
  }

  public double Umfang()
  {
   return 2 * (b + h);
  }
}



//Datei4

class TestShape
{
 public static void main(String[ ] arg)
 {
  Shape[ ] Shapes = new Shape[3];
  Shapes[0] = new Kreis(2.0);
  Shapes[1] = new Rechteck(1.0,2.0);
  Shapes[2] = new Kreis(5.0);

  double Gesamtflaeche = 0;
  for(int i = 0; i < Shapes.length; i++)
    Gesamtflaeche += Shapes[i].Flaeche();

  System.out.println("Gesamtflaeche = " + Gesamtflaeche);

 // Bestimmung des Gesamtumfangs geht völlig analog
  }
}

Output:
Gesamtflaeche = 93.106186954104

Interfaces:

In Java gibt es keine Mehrfachvererbung. Eine Klasse kann aber von einer Oberklasse und mehreren Interfaces erben. Wenn eine Klasse ein Interface erbt, sagt man, es implementiert das Interface.
Interfaces sind etwas ähnliches wie Klassen, die aber nur das Konzept für die Unterklassen skizzieren, das dann von diesen "implementiert" werden muß. Interfaces sind also reine Schnittstellen und enthalten nur abstrakte Methoden und Konstanten.

Syntax für die Definition eines Interfaces:
public interface InterfaceName
{
  public abstract Rückgabedatentyp Methodenname1 (Parameterliste) ;
  public abstract void Methodenname2 (Parameterliste) ;
  ...
}

Für die Implementierung eines Interfaces wird das Schlüsselwort implements verwendet. Nach dem Wort implements werden ein oder auch mehrere Interfaces angegeben (durch Komma getrennt). Allgemein sieht dies folgendermaßen aus:

public class Unterklasse extends OberKlasse implements Interface1, Interface2, ... { }

Die Unterklasse muß alle in diesen Interfaces definierten Methoden implementieren.


Graphische Benutzeroberflächen (GUI: Graphical User Interfaces)

Graphische Benutzeroberflächen bestehen aus Menüs, Fenstern, Eingabefeldern, Buttons usw. Das Paket, das die dafür notwendigen Klassen enthält, ist das Abstract Windowing Toolkit (AWT).
Das AWT stellt eine Reihe von GUI-Elementen zur Verfügung, wie beispielsweise:

Alle Komponenten besitzen die folgenden Methoden: Für die einzelnen Komponenten gibt es meist noch weitere, spezielle Methoden, wie z.B. Im folgenden Beispiel wird ein leeres Frame erzeugt. Ein Frame ist ein Fenster mit Rahmen.

Beispiel 10:

//Programm Fenster: Dieses Programm erzeugt ein leeres Fenster.

import java.awt.Frame;

class TestFrame
{
 public static void main(String[ ] args)
 {
  System.out.println("Fenster der Groesse 250x100 Pixel.");
  Frame Fenster = new Frame("Mein erstes Fenster");

  Fenster.setLocation(10,10);
  Fenster.setSize(250,100);
  Fenster.setVisible(true);
 

  System.out.println("Exit mittels Crtl+C.");
 }
}

>javac TestFrame.java
>java TestFrame
Fenster der Groesse 250x100 Pixel.
Exit mittels Crtl+C.

Das Programm erzeugt eine Instanz von Frame namens Fenster der Groesse 250x100 Pixel (Methode setSize(250,100)) bei den Koordinaten (10,10) (Methode setLocation(10,10)).Die
 Koordinaten (x,y) werden dabei so gezählt, daß (0,0) links oben ist.
Das Fenster erscheint erst, wenn die Methode setVisible(true) aufgerufen wird.
Der Benutzer kann das Fenster vergrößern, verkleinern und verschieben,  allerdings nicht schliessen. Daher erfolgt das "Schliessen" mit der Tastenkombination Crtl + C (Abbruch des laufenden Programms).

Output:

Wir haben in diesem Beispiel ein spezielle Fenster der Größe 250x100 Pixel erzeugt. Spezialisierung bedeutet aber immer, daß man das Problem auch mit Vererbung eleganter schreiben kann. Das nächste Beispiel definiert eine eigene Klasse (Klasse MyFrame) von Fenstern der Größe 250x100, die von der Klasse Frame abgeleitet werden können.

Beispiel 11:

//Datei 1

import java.awt.Frame;

class MyFrame extends Frame
{
 MyFrame(String s)
 {
   super(s);
   setLocation(10,10);
   setSize(250,100);
   setVisible(true);
  }
}



//Datei 2

class TestFrame
{
 public static void main(String[ ] args)
 {
  System.out.println("Fenster der Groesse 250x100 Pixel");
  new MyFrame("Mein erstes Fenster");
  System.out.println("Exit mittels Crtl+C");
 }
}

Möchte man nun etwas Farbe ins Spiel bringen, so braucht man Datei1 nur leicht abzuändern: mit der Methode setBackground() läßt sich die Hintergrundfarbe des Fensters ändern. Man muß allerdings die Klasse java.awt.Color importieren, in der die verschiedene Farben definiert sind.

Beispiel 12:

//Datei 1

import java.awt.Frame;
import java.awt.Color;

class MyFrame extends Frame
{
 MyFrame(String s)
 {
   super(s);
   setBackground(Color.red);
   setLocation(10,10);
   setSize(250,100);
   setVisible(true);
  }
}

Output:


In Beispiel 12 werden alle Veränderungen am Fenster direkt in der Klasse MyFenster vorgenommen.
Will man z.B. anstatt eines roten Fensters ein grünes Fenster erzeugen, so muß man in der Klasse MyFrame den Befehl setBackground(Color.red) in setBackground(Color.green) abändern.
Man kann Veränderungen allerdings auch in die Klasse TestFrame auslagern, die sie dann der Klasse MyFrame über Parameterübergabe mitteilt.

Beispiel 13:

//Datei 1

import java.awt.Frame;
import java.awt.Color;

class MyFrame extends Frame
{
 MyFrame(String s, Color color, int x, int y)
 {
   super(s);
   setBackground(color);
   setLocation(x,y);
   setSize(250,100);
   setVisible(true);
 }
}



//Datei 2

import java.awt.Color;

class TestFrame
{
 public static void main(String[ ] args)
 {
  System.out.println("Fenster der Groesse 250x100 Pixel");
  new MyFrame("Mein erstes gruenes Fenster", Color.green, 10, 10);
  System.out.println("Exit mittels Crtl+C");
 }
}

Output:


Frames können weitere Komponenten (z.B. Buttons) enthalten. Damit gehören sie zu den Containern.  Container enthalten zusätzlich zu den Komponentenmethoden noch die folgenden Methoden:

Im folgenden Beispiel wird ein Button in das Fenster eingefügt. Dabei wird die Methode add(Komponente) verwendet.

Beispiel 14:

//Datei 1

import java.awt.Frame;
import java.awt.Color;
import java.awt.Button;

class MyFrame extends Frame
{
 MyFrame(String s, Color color, int x, int y)
 {
   super(s);
   setBackground(color);
   setLocation(x,y);
   setSize(250,100);
   setVisible(true);
   add(new Button("Button"));
 }
}



//Datei 2

import java.awt.Color;

class TestFrame
{
 public static void main(String[ ] args)
 {
  System.out.println("Fenster der Groesse 250x100 Pixel");
  new MyFrame("Mein erster Button", Color.cyan, 10, 10);
  System.out.println("Exit mittels Crtl+C");
 }
}

Output:


Die Methode add(new Button("Button")) fügt einen Button mit der Aufschrift "Button" in das Fenster ein.

LayoutManager:

Enthält ein Fenster mehrere Komponenten, so können diese in einer bestimmten Anordnung innerhalb des Fensters dargestellt werden. Die Anordnung bestimmt der  LayoutManager. Es gibt fünf verschiedene Layouts, von denen wir hier aber nur zwei betrachten wollen:

FlowLayout:

Beispiel 15:

//Datei 1

import java.awt.Frame;
import java.awt.Color;
import java.awt.Button;
import java.awt.FlowLayout;

class MyFrame extends Frame
{
 MyFrame(String s, Color color, int x, int y)
 {
   super(s);
   setBackground(color);
   setLocation(x,y);
   setSize(250,100);
   setVisible(true);
   setLayout(new FlowLayout());
   for (int i = 1; i <= 6; i++)
    add(new Button("Button" + i));
 }
}



//Datei 2

import java.awt.Color;

class TestFrame
{
 public static void main(String[] args)
 {
  System.out.println("Erstelle Fenster der Groesse 250x100 Pixel");
  new MyFrame("6 Buttons", Color.yellow, 10, 10);
  System.out.println("Exit mittels Crtl+C");
 }
}
 

Output:

Problem:


GridLayout:

Beispiel 16:

//Datei 1

import java.awt.*;     //dadurch werden alle Klassen aus java.awt eingebunden

class MyFrame extends Frame
{
 MyFrame(String s, Color color, int x, int y)
 {
   super(s);
   setBackground(color);
   setLocation(x,y);
   setSize(250,100);
   setVisible(true);
   setLayout(new GridLayout(2,3));
   for (int i = 1; i <= 6; i++)
    add(new Button("Button" + i));
 }
}



//Datei 2

import java.awt.Color;

class testFrame
{
 public static void main(String[] args)
 {
  System.out.println("Erstelle Fenster der Groesse 250x100 Pixel");
  new MyFrame("6 Buttons", Color.orange, 10, 10);
  System.out.println("Exit mittels Crtl+C");
 }
}

Output:


Ein Button ist definiert als ein Knopf mit Text, der vom Benutzer mit der Maus angeklickt werden kann und dabei etwas bewirkt wird. Unsere Buttons bewirken bislang garnichts. Das soll im folgenden geändert werden.

Ereignisse

Ein Ereignis ist eine von Benutzer angestoßene Aktion, wie ein Tastendruck oder ein Mausklick. Jede Komponente kann Events aussenden. Die Ereignisbehandlung beruht auf Objekten, die die Ereignisse entgegennehmen können. Diese Objekte heißen Event-Listener. Sie implementieren  Listener-Interfaces und damit Methoden zur Behandlung von Ereignissen.
Wir betrachten im folgenden zwei Arten von Ereigissen: Action Events und Window Events.

1.) Action Event
 
Event-Klasse Interface Methoden Quellen
ActionEvent ActionListener actionPerformed(ActionEvent) Button
MenuItem
TextField

Als Beispiel behandeln wir das Ereignis, daß ein Button gedrückt wird. Jedes Mal, wenn man den Button drückt, soll  die Methode actionPerformed(ActionEvent) aufgerufen werden, die im Terminal ein  zusätzliches DANKE! erscheinen läßt.

Beispiel 17:

//Datei 1

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 

class MyFrame extends Frame implements ActionListener
{
 Button button;

 MyFrame(String s, Color color, int x, int y)
 {
   super(s);
   setBackground(color);
   setLocation(x,y);
   setSize(250,100);
   setVisible(true);
   setLayout(new FlowLayout());
   button = new Button("Drueck mich!");
   add(button);
   button.addActionListener(this);
 }

 //Methode von ActionListener
 public void actionPerformed(ActionEvent event)
 {
  if (event.getActionCommand().equals("Drueck mich!"))
    System.out.println("DANKE!");
 }
}



//Datei 2

import java.awt.Color;

class testFrame
{
 public static void main(String[] args)
 {
  System.out.println("Erstelle Fenster der Groesse 250x100 Pixel");
  new MyFrame("Klick-Ereignis", Color.magenta, 10, 10);
  System.out.println("Exit mittels Crtl+C");
 }
}

>javac testFrame.java
>java testFrame
Erstelle Fenster der Groesse 250x100 Pixel
Exit mittels Crtl+C
DANKE!
DANKE!

Output:


Veranschaulichung:

button = new Button("Drueck mich!");
add(button);


button.addActionListener(this);


Beim Drücken des Buttons wird ein ActionEvent-Objekt erzeut, wobei der String "Drueck mich!" des Buttons an das actionCommandfeld des Event-Objekts zugewiesen wird. Das ActionEvent Objekt wird an die Methode actionPerformed() übergeben.

Die Methode getActionCommand() liefert den  String "Drueck mich" . Dieser wird mit dem String "Drueck mich" über den Befehl event.getActionCommand().equals("Drueck mich!") verglichen. Falls Übereinstimmung vorliegt wird der String "DANKE!" im Teminal ausgegeben.

2.) Window Event

Das WindowListener-Interface dient dazu, auf Ereignisse zu reagieren, die den Status eines Fensters betreffen.
 
Event-Klasse Interface Methoden Quellen
WindowEvent WindowListener windowActivated()
windowClosed()
windowClosing()
windowDeactivated()
windowDeiconified()
windowIconified()
windowOpened()
Window

Im folgenden Beispiel möchten wir unser Fenster schliessen können. Dafür gibt es die Methode windowClosing(). Da im WindowListener aber eine Reihe von Methoden implementiert sind, müssen wir auch alle diese Methoden im Programm aufführen.

Beispiel 18:

//Datei 1
import java.awt.*;
import java.awt.event.*;

class MyFrame extends Frame implements ActionListener, WindowListener
{
 Button button;

 MyFrame(String s, Color color, int x, int y)
 {
   super(s);
   setBackground(color);
   setLocation(x,y);
   setSize(250,100);
   setVisible(true);
   setLayout(new FlowLayout());
   button = new Button("Drueck mich!");
   add(button);
   button.addActionListener(this);
   addWindowListener(this);
 }

// Methoden von WindowListener:
 public void windowClosed(WindowEvent event) {}
 public void windowDeiconified(WindowEvent event) {}
 public void windowIconified(WindowEvent event) {}
 public void windowActivated(WindowEvent event) {}
 public void windowDeactivated(WindowEvent event) {}
 public void windowOpened(WindowEvent event) {}
 public void windowClosing(WindowEvent event)
 {
  System.exit(0);
 }

 //Methode von ActionListener
 public void actionPerformed(ActionEvent event)
 {
  if (event.getActionCommand().equals("Drueck mich!"))
    System.out.println("DANKE!");
  }
}



//Datei 2

import java.awt.Color;

class TestFrame
{
 public static void main(String[] args)
 {
  System.out.println("Erstelle Fenster der Groesse 250x100 Pixel");
  new MyFrame("Klick-Ereignis", Color.lightGray, 10, 10);
 }
}

>javac testFrame.java
>java testFrame
Erstelle Fenster der Groesse 250x100 Pixel
DANKE!
DANKE!

Output:


Veranschaulichung:

new MyFrame("Klick-Ereignis", Color.lightGray, 10, 10);

addWindowListener(this);

Sobald der Benutzer den Schließknopf im Fensterrahmen betätigt, wird ein WindowEvent durch das Fenster erzeugt, daß die Methode windowClosing() aufgeruft und das Fenster schließt.

Umrechnung von Fahrenheit nach Celsius

In diesem letzten Beispiel darf der Benutzer eine Zahl (Temperatur in Fahrenheit) in ein Textfeld eingeben. Dieser Zahlenwert wird nach dem Betätigen der Returntaste in Celsius umgerechnet und das Ergebnis im Fenster bekannt gegeben.

Beispiel 19:

//Datei 1

import java.awt.*;
import java.awt.event.*;

class MyFrame extends Frame implements ActionListener, WindowListener
{
 Label Eingabeaufforderung;
 TextField Eingabe;
 Label Ergebnis;

 MyFrame(String s, Color color, int x, int y)
 {
   super(s);
   setBackground(color);
   setLocation(x,y);
   setSize(250,100);
   setLocation(100,100);
   setLayout(new FlowLayout());
   addWindowListener(this);
   Eingabeaufforderung = new Label("Temperatur in Fahrenheit: ");
   add(Eingabeaufforderung);                //Einfuegung der Eingabeaufforderung in das Fenster
   Eingabe = new TextField(2);
   add(Eingabe);                                     //Einfuegung des Textfeldes in das Fenster
   Eingabe.addActionListener(this);
   Ergebnis=new Label("                  ");
   Ergebnis.setFont(new Font("TimesRoman 12 point bold.",20,20));
   add(Ergebnis);                                    //Einfuegung des Ergebnisses in das Fenster
   setVisible(true);
   }

 // Methoden von WindowListener:
 public void windowClosed(WindowEvent event) {}
 public void windowDeiconified(WindowEvent event) {}
 public void windowIconified(WindowEvent event) {}
 public void windowActivated(WindowEvent event) {}
 public void windowDeactivated(WindowEvent event) {}
 public void windowOpened(WindowEvent event) {}

 public void windowClosing(WindowEvent event)
 {
  System.exit(0);
 }

 //Methode von ActionListener
 public void actionPerformed(ActionEvent event)
 {
  int  Fahrenheit = Integer.parseInt(Eingabe.getText());     //Umwandlung von String in int
  long Celsius = Math.round(5.0*(Fahrenheit-32)/9.0);
  Eingabe.setText("");                                                            //Reset des Eingabefeldes
  //Ausgabe des Ergebnisses
  Ergebnis.setText(Fahrenheit + "\u00B0F = " + Celsius + "\u00B0C");
  }
}



//Datei 2

import java.awt.Color;

class testFrame
{
 public static void main(String[ ] args)
 {
  System.out.println("Erstelle Fenster der Groesse 250x100 Pixel");
  new MyFrame("Fahrenheit-Celsius", Color.orange, 10, 10);
  }
}
 

Output:


 


 


Veranschaulichung:

Eingabeaufforderung = new Label("Temperatur in Fahrenheit: ");
add(Eingabeaufforderung);

Ein Label ist ein einfaches Feld für die Darstellung eines einzeiligen Textes.

Eingabe = new TextField(2);
add(Eingabe);

Ein TextField ist ein einzeiliges Feld für Tastatur-Eingaben.

Eingabe.addActionListener(this);


Ergebnis=new Label("                  ");
add(Ergebnis);


Beim Drücken der Returntaste nach Eingabe eines Zahlenwertes in das Textfeld wird ein ActionEvent-Objekt erzeugt, das an die Methode actionPerformed() übergeben wird. Die in das Textfeld eingegebene Zahl kann mit der Methode getText() ausgelesen werden. Da mathematische Operatoren nicht auf Objekte angewandt werden dürfen, muß der String zunächst mit der Methode Integer.parseInt(Eingabe.getText()) in eine Variable vom Typ int umgewandelt werden. Danach erfolgt die Umrechnung in Celsius. Das Eingabefeld wird mit der Methode setText(); neu gesetzt, in unserem Fall gelöscht und das Ergebnis mit dem Befehl Ergebnis.setText(Fahrenheit + "\u00B0F = " + Celsius + "\u00B0C"); im Fenster ausgegeben.
 

     



Last update: 4.Februar 2001
E-Mail: rcbruens@astro.uni-bonn.de