(Verantwortlich für die Seiten: W. Seyboldt / Stand: 11. Mai 2006)
(Praxis, siehe hier)
Die Idee der objektorientierten Programmierung ist in den 70er Jahren am MIT (Massachusetts Institute of Technology) und am PARC (Paolo Alto Research Center) von Mitarbeditern der Firma Xerox formuliert worden. Das Klassenkonzept wurde von der Programmiersprache Simula-67 übernommen und weiterentwickelt. Die Apple- und später die Windows-Oberfläche wurden wesentlich von den dort entwickelten Ideen beeinflusst.
Andere Programmierarten sind etwa: prozedurale Programmierung (etwa Pascal), logische Programmierung (etwa Prolog), funktionale Programmierung (etwa Lisp, Mathematica), strukturierte Programmierung (etwa Ada), visuelle Programmierung, Tabellenkalkulation, ... Nicht alle Arten sind grundsätzlich verschieden, so ist z.B. die strukturierte Programmierung eine Weiterentwicklung der prozeduralen und das objektorientierte Java enthält alle Elemente einer prozeduralen Programmiersprache. Im Endeffekt werden übrigens alle Arten in Maschinensprache umgewandelt, in eine prozedurale Vorgehensweise.
Bei der klassischen Programmierung, der prozeduralen und der strukturierten Programmierung, wird klar zwischen Variablen und Prozeduren (Methoden) unterschieden. Variable sind Adressen von Speicherplätzen, die Zahlen oder Text enthalten. Der Name einer Variablen wird vom Compiler in eine Adresse übersetzt. Der Ausdruck "a=a+b;" bedeutet z.B.: Hole die Zahlen, die in den mit den Namen a bzw. b beschriebenen Speicherbereichen stehen (im exe-File stehen an dieser Stelle keine Namen, sondern Zahlen, Adressen, die bei einem 32-Bit Rechner aus 4 Bytes bestehen), addiere die beiden Zahlen und kopiere das Ergebnis in den mit a bezeichneten Speicherplatz. Prinzipiell kann jeder Codeteil auf alle Variablen des Programms zugreifen und sie (beabsichtigt oder nicht) ändern. Damit ein Code leichter überschaubar wird, wird er in möglichst vernünftige Teile unterteilt. Man redet dann von Prozeduren, Subroutinen, Unterprogrammen, Funktionen oder Methoden. Eine solche Methode bestimmt beispielsweise das Maximum zweier Zahlen. Damit diese Methode mit Daten von unterschiedlichen Variablen aufgerufen werden kann, wird zwischen formalen und aktuellen Parametern unterschieden: Der Programmierer der aufgerufenen Methode benennt z.B. die Variablen, von denen das Maximum bestimmt werden soll, mit a1 und a2 - dies sind die formalen Parameter. Das aufrufende Programm möchte aber das Maximum der mit den Namen x1 und c5 benannten Variablen bestimmen - dies sind die so genannten aktuellen Parameter. Beim Aufruf wird nun automatisch der Wert in den Speicherplätzen x1 und c5 auf die mit a1 und a2 benannten Speicherplätze kopiert (diese Speicherplätze sind meist Bestandteil eines speziellen Bereichs des Hauptspeichers, des Stacks (1)). Änderungen der Variablenwerte in der Methode wirken sich also nicht auf die ursprünglichen Variablen aus (2). Hat das Unterprogramm das Maximum bestimmt, wird dieser Wert bei Aufruf von return maxVariable automatisch in einen speziell dafür reservierten Bereich kopiert (der meist wieder Teil des Stacks ist), auf den das Hauptprogramm dann zugreift. Das Hauptprogramm kopiert den Wert dann in einen anderen Speicherplatz und löscht den Stack danach. Anschließend wird der Code der aufrufende Methode an der Stelle nach dem Aufruf fortgesetzt. Damit dieses Vorgehen auch bei komplexeren Programmen überschaubar bleibt, wurde bei der strukturierten Programmierung gefordert, dass ein Unterprogramm möglichst nicht auf Daten zugreifen soll, die nicht als Parameter übergeben werden - oder die Programmierer müssen sich unzweideutig absprechen. Leider hat es sich ergeben, dass der zweite Fall viel häufiger als erwünscht auftrat, und dass Absprachen oft nur schwer einzuhalten waren.
Wie sich in der Praxis zeigte, benötigen komplexere Programmteile sehr viele Variablen und sehr viele Methoden. Dadurch hat sich die klassische Programmierung bei der Wartung von großen Programmsystemen als problematisch herausgestellt. Änderungen an Teilen von Programmen sind nur schwer vorzunehmen, da die Datenbestände, d.h. die Summe aller Variablen, kaum gegliedert sind und eine Änderung einer Datenstruktur immer eine Änderung von vielen Prozeduren nach sich zieht (man denke z.B. an das Jahr-2000-Problem). Vor allem ist oft nicht genau bekannt, welche Prozeduren auf einzelne Variable zugreifen und deshalb von einer Änderung der Datenstruktur betroffen sind.
Die objektorientierte Programmierung versucht diese Probleme dadurch zu umgehen, dass die primäre Gliederung der Programme in Variablen und Code aufgegeben wird. Ein Programm wird stattdessen in Objekte gegliedert, wobei jedes Objekt kleine überschaubare Portionen an Variablen und Code enthält. Ein Objekt ist in der Modellvorstellung der Entwickler der objektorientierten Programmierung eine Art von "Lebewesen", das auf "Nachrichten" reagiert, indem es antwortet (ebenfalls Nachrichten sendet), seinen inneren Zustand (den Inhalt der Variablen) ändert oder sich am Bildschirm darstellt. Weitere Vorgehensweisen der Programmierung werden durch Information Hiding, abstrakter Datentyp, Nachrichten, Vererbung, Datenkapselung, Klassen usw. angesprochen.
Die Objektorientierung beginnt bereits beim Planen des Programms. Das durch ein Computerprogramm zu lösende Problem wird in logisch zusammengehörende Teile zerlegt, die in Form von Objekten im Rechner abgebildet werden können (3). Der innere Zustand der Objekte wird durch Daten beschrieben, auf die im Idealfall andere Objekte nicht direkt zugreifen können, sondern nur durch die zum Objekt gehörenden Methoden. Der Austausch von Informationen geschieht logisch betrachtet dadurch, dass Objekte sich gegenseitig Nachrichten zusenden, indem sie öffentlich (engl.: public, siehe Qualifizierer) zugängliche Methoden anderer Objekte aufrufen, die die gewünschten Informationen an das aufrufende Programm übergeben. Programmiert werden aber nicht die einzelnen Objekte, sondern sogenannte Klassen, die Schablonen der zum Objekt gehörenden Variablen und Methoden enthalten. Diese Klassen sind eine Art von Bauplan für ein Objekt. Ein Objekt ist eine Realisierung, man sagt auch Instantiierung einer Klasse. Jedes Objekt wird im Lauf des Programms erzeugt. Dabei werden im Heap genannten Teil des Memories entsprechende Speicherbereiche reserviert und von den Konstruktoren eingerichtet (siehe z.B. [B880], S.124ff). Eine Klasse kann im Lauf eines Programms mehrfach instantiiert werden.
Die objektorientierte Programmierung vereinfacht bei komplexen Programmen die Codestruktur und ermöglicht, dass früher geschriebene ähnliche Programmteile leichter übernommen und den geänderten Bedingungen angepasst werden können. Da bei einem objektorientierten Programm Code und Daten nicht mehr getrennt sind, ist das Programm in der Praxis meist leichter zu warten. Unterprogramme, d.h. Klassen, die bei früheren Projekten erstellt und sauber dokumentiert wurden (von denen zumindest die öffentlichen Methoden klar beschrieben sind), können bei ähnlichen neuen Projekten großenteils übernommen werden. Es müssen oft nur einzelne Methoden abgeändert (man sagt überschrieben) werden. Hierfür bietet die objektorientierte Programmierung ebenfalls Hilfestellung, die mit dem Stichwort Vererbung angesprochen wird.
Einige Bemerkungen zu wichtigen Begriffen, die zum Umfeld "Objektorientierte Programmierung" gehören.
Es gibt im Internet viele mehr oder weniger gute Einführungen in die Theorie und die Praxis der objektorientierten Programmierung mit Java. Einige Beispiele finden sich im Literaturverzeichnis. Eine ausführlichere Beschreibung der wesentlichen Elemente der ObjektOrientierten Programmierung (OOP) findet man etwa im ersten Kapitel des elektronischen Buches Bruce Eckel: Thinking in Java (siehe [JK03] / vgl. auch Versionen am GZG).
(Theorie siehe hier)
In Java sind alle ausführbaren Programmteile letztendlich Objekte, sogenannte Instanzen von Klassen. Objekte sind Realisierungen von Klassen. Klassen sind Schablonen, Baupläne von Objekten, so wie Häuser Realisierungen eines Bauplans sind. Klassen, bzw. Objekte, bestehen aus Code und den für die Ausführung des Codes nötigen Datenstrukturen (meist Variablen genannt). Der Code eines Objekts ist in Form von Methoden strukturiert, die die entsprechenden Daten ändern oder abhängig von diesen Daten andere Methoden aufrufen. Methoden sind Prozeduren, Unterprogramme, die mehr oder weniger komplexe Algorithmen ausführen. In anderen Programmiersprachen heißen die Methoden Procedure, Function, Subroutine, ... .
Namen von Klassen beginnen in Java mit einem Großbuchstaben, Namen von Objekten mit einem Kleinbuchstaben.
Mit Java werden keine Objekte programmiert, sondern immer nur Klassen. Klassen sind durch einen Namen und ein vorausgehendes Schlüsselwort class gekennzeichnet. Klassen werden immer von anderen Klassen abgeleitet, d.h. sie erben alle Datenstrukturen und alle Methoden der bereits programmierten sogenannten Superklasse und all ihrer Ahnen. Die Superklasse (super class) wird durch das vorausgehende Schlüsselwort extends gekennzeichnet. Der Programmierer der abgeleiteten neuen Klasse ergänzt diese Daten und Methoden durch neue, und / oder er überschreibt die Methoden der Superklasse.
Der Programmierer einer neuen Klasse kann die Methoden einer Superklasse also einfach kommentarlos übernehmen - dann muss er nichts tun - oder er kann sie überschreiben, d.h. er kann neue Methoden mit einem bereits vergebenen Namen erstellen. Z.B. wird im Applet Hallo1 die Methode paint() der Klasse Applet überschrieben. Wenn dann einem Objekt dieser Klasse eine Botschaft geschickt wird, die entsprechend benannte Methode auszuführen, so wird die neu erstellt ausgeführt und nicht die Methode der Superklasse.
class Hallo1 extends Applet {
// zusätzliche Daten und zusätzlicher oder geänderter Code
}
Im obigen Applet erbt die neu deklarierte Klasse Hallo1 von der Klasse Applet alle Methoden und Variablen.
Bemerkung: Ist die Superklasse die Klasse Objects, so muss das Kennwort und der Name dieser Superklasse nicht angegeben werden.
Jedes Objekt muss während des Programmlaufs erzeugt werden, d.h. es muss der entsprechende Speicherplatz für die Daten und für die Methoden reserviert werden (genauer der Speicherplatz für einen Zeiger auf die Methoden.) und die Daten müssen initialisiert werden, d.h. es muss im Speicherplatz der Variablen ein Startwert abgespeichert werden. Nach der Erzeugung eines Objekts wird deshalb ein sogenannter Konstruktor aufgerufen, eine Methode, die die nötigen Initialisierungen vornimmt. Der Name dieser Konstruktor-Methode lautet in Java gleich wie die Klasse, beginnt also mit einem Großbuchstaben. Der Konstruktor enthält keine Qualifizierer und gibt keine Werte oder Objekte zurück. Der Konstruktor der Klasse Hallo1 wäre also Hallo1(). Wenn der Programmierer keinen Konstruktor erstellt, wird bei der Realisierung eines Objektes automatisch der Konstruktor der Superklasse aufgerufen. Bei Hallo wird also der Konstruktor der Superklasse Applet, nämlich Applet() aufgerufen. Dem Konstruktor können Parameter übergeben werden. (Es gibt aber immer einen Konstruktor ohne Parameter, selbst dann, wenn dieser nicht explizit programmiert wurde. Oft gibt es mehrere Konstruktoren, die sich durch die Anzahl der Parameter unterscheiden.)
Die Klassen enthalten die Variablen und die Methoden der später erzeugten Objekte. Bevor eine Methode jedoch ausgeführt werden kann, bevor in einer Variable ein Wert abgespeichert werden kann, muss ein entsprechendes Objekt erzeugt werden. Bei Applets muss dies nicht selbst vorgenommen werden, da der Browser stets ein entsprechendes Objekt erzeugt.
Daten sind Namen von Speicherplätzen, von Teilen des Memories (Hauptspeichers), denen bei der Erzeugung eines Objekts eine feste Adresse zugewiesen wird. Ein solcher Speicherplatz besteht aus ein oder mehreren Bytes, deren Bits je nach Art der Variable interpretiert werden (siehe).
Methoden sind logisch zusammengehörende Aktionen, die aus Zuweisungen, Schleifen und bedingten Anweisungen bestehen (siehe).
Eine Methode wird innerhalb einer Klasse deklariert, d.h. mit einem Bezeichner, Namen versehen, und programmiert. Sie wird durch Nennen des Namens aufgerufen (d.h. der Code dieser Methode wird ausgeführt), wobei dem Namen stets ein rundes Klammerpaar folgt, etwa methode1(). Werden der Methode keine Daten, keine Parameter, übergeben, steht nichts zwischen den Klammern, ansonsten stehen zwischen den Klammern die übergebenen Variablen oder Parameter. Sollen Methoden aufgerufen werden, die nicht Bestandteil des aufrufenden Objekts sind, so wird die Methode durch zusätzliches Nennen des entsprechenden Objekts aufgerufen, also mit objekt.methode(). Z.B. ruft g.setColor() die Methode setColor des Objekt g auf . Man sagt auch, man sendet einem Objekt eine Nachricht, wenn man eine Methode eines anderen Objekts aufruft. Der Name des Objekts geht dem Namen der Methode voraus und wird mit einem Punkt von diesem getrennt. (Die aufgerufene Methode muss dabei public sein.). Eine Methode, die zur augenblicklichen Klasse gehört, kann auch mit this.methode() aufgerufen werden.
Aufgerufenen Methoden können von den aufrufenden Objekten Daten und andere Objekte übergeben werden. Die übergebenen Daten (oder Objekte) werden in den runden Klammern nach dem aufrufenden Methodennamen übergeben. Im Applet Hallo1 lautet der Aufruf der Methode g.drawString("Hallo, ...", 50, 30). Dies bedeutet: Sende dem Objekt g (der Klasse Graphics) die Nachricht, es soll die Methode drawString() starten. Die Parameter, die die Methode benötigt, sind der String "Hallo ...", d.h. der Text, der am Bildschirm angezeigt werden soll, und die beiden Integer-Zahlen 50 und 30, die die Anfangsposition des Strings auf dem Bildschirm beschreiben.
Jede Methode besitzt am Ende eine öffnende und eine schließende Klammer - auch dann, wenn der Methode keine Parameter übergeben werden. Namen, die Klammern am Ende besitzen, sind Methoden, Namen, die keine Klammern besitzen, sind Objekt oder Daten.
Das Schlüsselwort this bezeichnet stets das Objekt, das gegenwärtig aktiv ist (nicht die Klasse!). Das aufrufende Programm kann sich damit selbst an die aufgerufene Methode mit dem Parameter this übergeben. d.h. der aufgerufenen Methode wird mitgeteilt, welches Objekt die Methode aufgerufen hat, z.B. wird der Methode addActionListener() des Objekts button1 mit button1.addActionListener(this) mitgeteilt, welches Objekt die Methode aufgerufen hat, d.h. eine Methode des Objekts button1 kann später eine bestimmte Methode dieses Objekts aufrufen, wenn das Objekt button1 sich den Namen des Objekts merkt, d.h. in einer Objektvariablen speichert. In dem Beispiel ruft eine Methode des Objekts button1 später die Methode actionPerformed(..) auf.
Methoden können Daten und Objekte zurückgeben. Gibt eine Methode keine Objekte zurück, so steht bei der Deklaration der Methode vor dem Namen des Objekts void: z.B.: void paint(..). Gibt eine Methode eine Variable oder ein Objekt zurück, so steht bei der Deklaration der Methode vor dem Namen der Methode der Bezeichner des zurückgegebenen Objekts, etwa: int meth3(double d) ist eine Methode, der eine Variable des Typs double übergeben wird und die dem aufrundenden Objekt eine Variable vom Typ int zurückgibt. Im Codeteil steht dann z.B. int i = meth3(7.31);
Methoden und Daten können entweder private oder public sein (siehe Qualifizierer). Im ersten Fall können die Methoden oder Variablen nur von den eigenen Methoden aufgerufen werden, im letzten Fall von den Methoden aller Objekte. Dies wird bei der Deklaration der Methode festgelegt. Durch die Deklaration von public void paint(Graphics g) wird also festgelegt, dass von jeder Methode eines beliebigen Objekts einem Objekt der Klasse Hallo1 die Nachricht übersandt werden kann, die Methode paint() zu starten. Diese Nachricht muss als Parameter von paint ein Objekt der Klasse Graphics enthalten. Die Methode paint gibt der aufrundenden Methode keine Daten zurück, sie zeigt nur Daten am Bildschirm an. Der Browser erzeugt, bevor das Applet am Bildschirm angezeigt wird, ein Objekt hallo1 gemäß der Klasse Hallo1. Eine Methode von Applet, die Methode paint wird in Hallo1 neu geschrieben. Die Methode paint wird von der Methode repaint aufgerufen. Die Methode repaint wird immer dann aufgerufen, wenn der Bildschirm (oder der dargestellte Frame beim Applet) neu gezeichnet werden muss.
Das Objekt g, eine Realisierung der Klasse Graphics (die beim Start vom Browser ebenfalls instantiiert wird), umfasst die Methoden, mit denen die Bildschirmpunkte, die Pixel, zum Leuchten gebracht werden. Die Klasse Graphics besitzt eine Methode drawString, mit dem auf dem Bildschirm ein Text zur Darstellung gebracht werden kann. Durch g.drawString(..) wird dem Computer mitgeteilt, dass die Methode drawString aufgerufen werden soll, wobei diese Methode Bestandteil des Objekts g ist. g.drawString wird außer dem Text (ein Objekt der Klasse String), der am Bildschirm angezeigt werden soll, noch die Position übergeben, an dem der Text beginnt (genau die linke untere Position der Textlinie). Diese wird durch die x- und die y-Koordinate des entsprechenden Pixels beschrieben; dabei zeigt die x-Achse von links nach rechts, die y-Achse von oben nach unten.
Machen wir uns das objektorientierte Vorgehen von Java an drei konkreten Beispielen klar. Das Beispiel 1 gehört zur Lerneinheit 4.1 (Applet Zahlenpaar01), die Beispiele 2 und 3 zu Lerneinheit 4.2 (Applet Polynom01).
Beschreibung der Objekteigenschaften und -aufgaben:
Variablen und Methoden (der Code der Klasse wird im Anschluss an den kurzen beschreibenden Text angezeigt):
Z01 class Zahlenpaar {
Z02 // interne Datenstruktur
Z03 double x, y;
Z04 // Konstruktor
Z05 Zahlenpaar(double xe, double ye) {
Z06 x=xe; y=ye;
Z07 }
Z08
Z09 // öffentliche Methoden
Z10 public double getMaximum() {
Z11 if (x>y) return x;
Z12 else return y;
Z13 }
Z14 public double getMinimum() {
Z15 if (x<y) return x;
Z16 else return y;
Z17 }
Z18 public double getX() {return x;}
Z19 public double getY() {return y;}
Z20
Z21 public String toString() {
Z22 return " ( "+x+" / "+y+ " ) ";
Z23 }
Z24 } // Ende der Klasse Zahlenpaar
Z01 class Funktion {
Z02 // Klassenvariable
Z03 double a,b,c,d;
Z04 double xmin, xmax, ymin, ymax;
Z05
Z06 Funktion(double a,double b,double c, double d) {
Z07 this.a=a; this.b=b; this.c=c; this.d=d;
Z08 }
Z09
Z10 private double f(double x) {
Z11 return a*x*x*x+b*x*x+c*x+d;
Z12 }
Z13
Z14 public void zeichneDich(Koordinatensystem ks) {
Z15 xmin=ks.getXmin(); xmax=ks.getXmax();
Z16 ymin=ks.getYmin(); ymax=ks.getYmax();
Z17 double dx=(xmax-xmin)/100;
Z18 for (double x = xmin; x<xmax; x+=dx) {
Z19 ks.drawLine(x,f(x),x+dx,f(x+dx));
Z20 }
Z21 }
Z22
Z23 public String toString() {
Z24 return "Polynom mit a="+a+" / b="+b+" / c="+c+" / d="+d;
Z25 }
Z26 } //Ende Klasse Funktion
Z01 class Koordinatensystem extends Canvas {
Z02 static int MAXFUNK = 3;
Z03 private double xmin, xmax, ymin, ymax;
Z04 // Zeichenbereich in xyKoordinaten
Z05 private Funktion funk[];// Array mit zu zeichnenden Polynomen
...
Z10 Koordinatensystem(int pdx, int pdy, // Konstruktor
Z11 double xmin, double xmax, double ymin, double ymax) {
Z12 this.setSize(pdx,pdy); //Größe der Leinwand einstellen
Z13 this.setVisible(true); // sichtbar machen
Z14 setBackground(Color.yellow); // Hintergrundfarbe
Z15 setzeXYGröße(xmin, xmax, ymin, ymax); //xy-Bereich setzen
Z16 funk = new Funktion[MAXPOLYS];
Z17 funktionsanzahl=0; // noch keine Funktion eingegeben
Z18 }
Z19
Z20 public void setzeXYGröße(double xmin, double xmax,
Z21 double ymin, double ymax) {
Z22 this.xmin=xmin; // Parameter abspeichern
...
Z31 // Einheiten auf der x- bzw. y-Achse bestimmen
Z32 einheitx = 100;
Z33 if (dx<300) einheitx =10;
Z34 if (dx<30) einheitx = 1;
Z35 if (dx<3) einheitx = 0.1;
...
Z41 public void setFunction(Funktion f) { // neue Funktion eingeben
Z42 for (int i=funktionsanzahl-1; i>=0;i--) { // alte umspeichern
Z43 if (i<MAXFUNK-1) funk[i+1]=funk[i];
Z44 }
Z45 funk[0] = f; // neues in erstes Element des Arrays
Z46 if (funktionsanzahl<(MAXFUNK)) funktionsanzahl++;
Z47 }
Z48
Z49 private int convertXToPixel(double x) {
Z40 // konvertiert x-Koordinaten in Pixel
Z50 return (int)((x-xmin)/dx*dpx);
Z51 }
Z52 public void drawLine(double x1,double y1, double x2,double y2) {
Z53 // Wandelt die xy-Koordinaten in Pixelkoordinaten um und zeichnet
Z54 // eine Gerade zwischen den beiden Punkten
Z55 g.drawLine( (int)((x1-xmin)/dx*dpx), (int)((ymax-y1)/dy*dpy),
Z56 (int)((x2-xmin)/dx*dpx), (int)((ymax-y2)/dy*dpy));
Z57 }
Z58
Z59 private void zeichneKoordinatenachsen() {
Z60 // x-Achse mit Pfeil
Z61 drawLine(xmin*0.975,0,xmax*0.975,0);
Z62 g.drawLine(convertXToPixel(xmax*0.975)-15,convertYToPixel(0)-10,
Z63 convertXToPixel(xmax*0.975),convertYToPixel(0) );
Z64 g.drawLine(convertXToPixel(xmax*0.975)-15,convertYToPixel(0)+10,
Z65 convertXToPixel(xmax*0.975),convertYToPixel(0));
Z66 //y-Achse mit Pfeil
...
Z71 //Einheiten auf den Achsen
Z72 g.drawLine(convertXToPixel(einheitx),convertYToPixel(0)+5,
Z73 convertXToPixel(einheitx),convertYToPixel(0)-5);
...
Z81 public void paint(Graphics g) {
Z82 this.g = g; // wird von drawLine benötigt
Z83 ...
Z84 zeichneKoordinatenachsen();
Z85 for (int i=0; i<funktionsanzahl;i++) {
Z86 funk[i].zeichneDich(this);
Z87 }
Z88 }
Z89
Z90 public String toString() {
Z91 return " Zeichenbereich: x=" +xmin+" .. "+xmax+
Z92 " / y="+ymin+" .. "+ymax;
Z93 }
Z94 } // Ende Klasse KoordinatensystemDer zu zeichnende Bereich des xy-Koordinatensystems wird durch xmin und xmax bzw. ymin und ymax beschrieben. Dieser Bereich ist auf die Pixel der Realisierung des Zeichenbereichs zu projizieren, die durch die Ausdehnung in x-Richtung (Variable dpx) und y-Richtung (Variable dpy) gegeben ist. Die Methoden der Klasse
- speichern die nötigen xy-Koordinaten-Beschreibungen einschließlich Hilfsgrößen zur Umrechnung ab: Methode setzeXYGröße() (Zeilen 20ff)
- wandeln die xy-Koordinaten in die Pixelkoordinaten des Canvas um: Methoden convertXToPixel() (Zeilen 49ff) und drawLine() (Zeile 52ff). Letztere zeichnet eine Linie zwischen zwei in xy-Koordinaten beschriebenen Punkten.
- zeichnen die Koordinatenachsen: Methode zeichneKoordinatenachsen() (Zeilen 59ff)
- koordinieren die nötigen Aufgaben, wenn neu gezeichnet werden muss: Methode paint() (Zeilen 81ff)
- und zeigen den Größenbereich des Koordinatensystems an: Methode toString() (Zeilen 90ff)
- Außerdem verwaltet die Klasse bis zu drei Funktionen (siehe Methode SetFunction(), Zeilen 41ff), die im xy-Bereich gezeichnet werden sollen. Die Objekte, die diesen Funktionen entsprechen, sind von der Klasse Funktion (siehe oben) und stellen eine Methode namens zeichneDich() (oben Zeile 14) zur Verfügung, die mittels der Methode drawLine() (siehe Zeilen 52ff) den Graphen mit genügend vielen Punkten zeichnen.
Bei der Umrechnung zwischen den x-Koordinaten und den Pixeln in x-Richtung geachtet man, dass xmin auf 0 abgebildet wird und der x-Bereich dx=(xmax-xmin) auf dpx, die Pixel-Ausdehnung in x-Richtung. Ein Punkt mit den x-Koordinaten x wird also auf (x-xmin)/dx*dpx abgebildet. Dabei ist diese Zahl auf eine ganze Zahl zu runden, da die Pixel int-Zahlen sind. Bei der Umrechnung der y-Koordinaten in y-Pixel geht man prinzipiell gleich vor, beachtet aber, dass die y-Pixel nach unten, die y-Achse nach oben orientiert ist. Damit ergibt sich die Umrechnung (ymax-y)/dy*dpy, die wieder in eine int-Zahl zu verwandeln ist.
Jedes Objekt muss nach der Deklaration, beispielsweise:
- Button löschButton;
durch Aufruf des Schlüsselwortes new erzeugt und initialisiert werden, beispielsweise:
löschButton = new Button("Löschen");
Deklaration und Initialisierung können gemeinsam erfolgen.
- Button löschButton = new Button("Löschen");
- Die Variable löschButton ist ein Objekt der Klasse Button. Durch den Aufruf new Button() wird das Objekt erzeugt (es wird Speicherplatz für die Variablen und die Methoden des Objekts reserviert) und gleichzeitig der Konstruktor Button der Klasse Button aufgerufen. Der Konstruktor erwartet in diesem Fall einen Parameter.
Bei der Erzeugung des Objekts wird ein Konstruktor mit dem Namen der Klasse aufgerufen. Der Konstruktur ist eine Methode, deren Name mit einem Großbuchstaben beginnt, und die von Java automatisch bei der Erzeugung des Objekts aufgerufen wird. Jede Klasse muss einen solchen Konstruktor besitzen.
Beim Start eines Applets, beispielsweise beim Start des Applets Hallo1 führt der Browser folgenden Codeteil automatisch aus:
Hallo1 hallo1 = new Hallo1();
Der Bowser erzeugt also ein Objekt hallo1 vom Typ der Klasse Hallo1 und ruft dabei den entsprechenden Konstruktor auf. Da es keinen speziellen Konstruktor Hallo1 gibt, wird der Konstruktor der Superklasse Applet aufgerufen. Dieser Konstruktor ruft die Methode init() auf, so dass der Programmierer nötige Initialisierungen durchführen kann.
Java erlaubt, dass Klassen sogenannte innere Klassen enthalten, d.h. Klassen können innerhalb anderer Klassen definiert werden. Die so definierten Klassen können auf die Komponenten der sie umfassenden Klasse zugreifen. Siehe z.B. Button01c.
Vgl. die Einführung in Applets
Die Klasse Color enthält die zum Einstellen der Farbinformationen nötigen Methoden und einige vordefinierte Farben.
Die Methoden setColor (der Klasse Graphic) und setBackground (der Klasse Applet, wobei diese die Methode von der Klasse Component erbt) erwarten als aktuellen Parameter ein Objekt der Klasse Color. Beispielsweise können dies die Objekte Color.white, Color.red, ... sein. (Übrigens, die Bezeichnung lässt ahnen, dass diese Objekte in gewissen Sinn eine Ausnahme sind, es sind Objekte, die Bestandteil einer Klasse sind!)
Außer diesen Standardfarben, kann auch zuvor ein eigenes Objekt farbe der Klasse Color (d.h. eine selbstdefinierte Farbe) erzeugt werden. Dies geschieht etwa mit Color farbe = new Color(0.5, 0.7, 0.23). Das Objekt farbe setzt sich aus den drei RGB-Farbanteilen (rot, grün, blau) zusammen. Der rote Farbanteil beträgt 50 % des Maximalwertes, der grüne 70% und der blau 23%.
Java arbeitet mit dem 24-Bit-Farbenmodell, d.h. jede Farbe wird durch einen Wert von insgesamt 3 Bytes festgelegt, durch den sogenannten RGB-Wert (Rot, Grün, Blau).
Genauere Informationen zum RGB-Farbmodell finden sich z.B. in der HTML-Einführung selfhtml 7.0 von Stefan Münz (siehe hier) oder auf der offiziellen Seite des W3-Konsortium (siehe http://www.w3.org/pub/WWW/Graphics/Color/sRGB.html). Applets zur Veranschaulichung des Farbkreises: Farbkreis01 bzw. Farbkreis04.In Java kann der RGB-Wert auch durch eine drei Byte große Zahl verschlüsselt werden: Das höchstwertige Byte bestimmt den R-, das zweitniederste Byte den G- und das niederwertigste Byte schließlich den B-Wert. Der Byte-Wert 0 bedeutet dabei stets keinen Anteil der entsprechenden Farbe, der Wert 0xFF = 255 (0x bedeutet, die folgende Zahl wird im Hexadezimalsystem angegeben) bedeutet volle Farbe. Die Farbe schwarz ist also 0x000000 (=dezimal 0,0,0), die Farbe weiß ist 0xFFFFFF (=dezimal 255, 255, 255) und grau 0x404040 (=dezimal 128,128,128)
Merke: Java kennt (mehr als) drei Konstruktoren für Farbe:
- Color rot = new Color(255,128,30): Die Parameter sind drei int-Werte zwischen 0 und 255 für die Anteile von R,G und B. (hier also: voller R-Anteil, wegen 255, halber Grün-Anteil, wegen 128 und 30/255-Blau Anteil.
- Color gruen = new Color(0x8800FF): Der Parameter ist der oben beschrieben drei-Byte-Wert (0x bedeutet, die Zahl wird in Hexform eingegeben).
- Color hellOrange = new Color(1.0f, 0.79f, 0.12f): Die drei Parameter sind float-Werte zwischen 0 (kein Farbanteil) und 1 (voller Farbanteil) für die drei RGB-Anteile (hier also: 100% Rot-Anteil, 79% Grün- und 12% Blau-Anteil.
Daneben gibt es bereits einige fest definierte Farben. Diese sind:
Color.red, Color.green, Color.yellow, ...Beispiele, eine Stiftfarbe festzulegen sind also:
- g.setColor(Color.red);
- g.setColor(new Color(0.3, 0.4, 0.1);
- g.setColor(new Color(128, 128, 128);
- Color f = new Color(255, 0, 128); g.setColor(f);
siehe die sogenannten aktiven AWT-Klassen des Java Event Models 1.1:
siehe String
siehe Array
(1) Ein Stack ist ein Bereich im Hauptspeicher, auf den die CPU mit speziellen Befehlen zugreift. Mit dem Befehl push wird ein Datenelement oben auf den Stapel gelegt und mit dem Befehl pop wird das oberste Datenelement wieder entfernt. Die CPU besitzt ein sogenanntes Stack-Register, dessen Inhalt die augenblickliche obere Grenze des Stacks angibt. Das Stack-Register zeigt normalerweise auf den ersten leeren Speicherplatz. (siehe [B467], S.162)
(2) Da Änderungen der übergebenen Variablen oft erwünscht sind, kann man in bestimmten Programmiersprachen nicht nur die Werte selbst, sondern auch die Adressen der Werte, d.h. die Adressen der Variablen, auf den Stack schreiben. In Pascal nennt man dies Arbeiten mit VAR-Parametern. Dann kann man in diesen Sprachen die aktuellen Parameter ändern. In Java löst man diese Aufgaben mit Objekten und ihren Variablen; wenn ein Objekt übergeben wird, wird immer die Adresse des Objekts übergeben. Wenn man dann Methoden dieses auf dem Stack übergebenen Objekts aufruft, die innere Variablen des Objekts verändern, so sind diese Änderungen dauerhaft - natürlich auch, wenn man direkt auf public-Variable zugreift.
(3) Dabei werden heutzutage oft Hilfsprogramme (tools) verwendet, die eine formale Sprache zum Aufbau der Objektstruktur unterstützen. Objektorientiere Konzepte werden oft in der UML Notation beschreiben (UML = unified modeling language), siehe etwa [B880], S. 123.