Java - Eine Einführung
Transcription
Java - Eine Einführung
Java - Eine Einführung Ole Schulz-Trieglaff 7. April 2004 2 Inhaltsverzeichnis 1 Der erste Tag 1.1 Einleitung . . . . . . . . . . . . . . . . 1.2 Jetzt geht es los . . . . . . . . . . . . . 1.2.1 Ausführen des Programms . . . 1.3 Java-Applets . . . . . . . . . . . . . . 1.4 Variablen und Operatoren . . . . . . . 1.4.1 Datentypen . . . . . . . . . . . 1.4.2 Operatoren und Berechnungen 1.5 Typecasts . . . . . . . . . . . . . . . . 2 Der 2.1 2.2 2.3 2.4 2.5 2.6 2.7 zweite Tag Schleifen und Anweisungen . . . . if-Anweisung . . . . . . . . . . . . Die while-Schleife . . . . . . . . . . do-while-Schleife . . . . . . . . . . Die for-Schleife . . . . . . . . . . . Die switch-Anweisung . . . . . . . Break und continue . . . . . . . . . 2.7.1 Labeled Breaks . . . . . . . 2.7.2 continue . . . . . . . . . . . 2.8 Arrays . . . . . . . . . . . . . . . . 2.8.1 Zugriff auf Array-Elemente 2.8.2 Mehrdimensionale Arrays . 2.9 Lesen von der Kommandozeile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 5 6 7 7 7 8 8 10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 11 11 12 12 13 13 14 14 14 15 15 15 16 3 Der dritte Tag 3.1 Ojektorientierte Programmierung . . 3.1.1 Klassen und Objekte . . . . . 3.1.2 Methoden . . . . . . . . . . . 3.1.3 Parameter . . . . . . . . . . . 3.1.4 Rückgabewerte . . . . . . . . 3.1.5 Überladen von Methoden . . 3.1.6 Rekursion . . . . . . . . . . . 3.1.7 Konstruktoren . . . . . . . . 3.1.8 Verkettung von Konstuktoren 3.2 Ein- und Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 17 17 17 18 19 20 20 20 21 21 3 4 INHALTSVERZEICHNIS 4 Der 4.1 4.2 4.3 4.4 4.5 vierte Tag Vererbung . . . . . . . . . . . . Modifier . . . . . . . . . . . . . static . . . . . . . . . . . . . . . Abstrakte Klassen . . . . . . . Interfaces . . . . . . . . . . . . 4.5.1 Mehrfachvererbung . . . 4.6 Graphische Ausgabe mit Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 23 24 25 25 26 27 27 Kapitel 1 Der erste Tag 1.1 Einleitung Java wurde Anfang der 90er Jahre von der kalifornischen Firma SUN Microsystems entwickelt. SUN wollte damals ein System entwickeln mit dem sich die unterschiedlichsten technischen Geräte ansteuern lassen. Alle bisherigen Programmiersprachen waren zu unflexibel, d.h. es hätte einen sehr großen Aufwand erfordert, ein Programm, dass z.B. für einen PC entwickelt worden war, an einen Toaster anzupassen. (Der Fachbegriff dafür ist portieren) Java wurde im Rahmen dieses Projektes mit dem Ziel entwickelt möglichst einfach, zuverlässig und portabel zu sein. Das Projekt selbst war nicht besonders erfolgreich, nur Java blieb übrig und wurde weiter entwickelt. Eine kleine Anekdote: Java hieß ursprünglich Oak (engl. für Eiche), wie die Bäume, die vor dem Büro eines der Chef-Entwickler von Java, James Gosling, stehen. Es stellte sich jedoch heraus, das es bereits eine Programmiersprache mit diesem Namen gab. Um juristische Schwierigkeiten zu vermeiden, wurde schnell nach einem neuen Namen gesucht. Eine der Kaffesorten des Ladens, in denen das Team seinen Kaffee kaufte, hieß Java... Jetzt schnell noch ein Überblick über die wichtigsten Merkmale von Java: • Java ist objektorientiert. Das ist so ziemlich die wichtigste Eigenschaft. Sie sagt im Wesentlichen aus, dass man in Java versucht Teile eines Programms in sog. Objekte oder Klassen zu strukturieren. Was dies genau heisst, werden wir erst später sehen. • Java unterstützt das Internet. Java ist auf Netzwerkanwendungen optimiert d.h. viele Programme die Daten in Netzwerken übertragen müssen, lassen sich mit Java sehr leicht realisieren. • Java ist fexibel und robust. Wie oben schon erwähnt lassen sich in Java geschriebene Programme ohne große Mühe an verschiedene Rechner anpassen. Außerdem wurde bei der Entwicklung großer Wert auf Datensicherheit gelegt. Alle Java Programme laufen in einer Art Schutzkäfig (engl. Sandbox), der sie vor anderen Programmen abschirmt. Dadurch wird verhindert, dass ein Java-Programm Schaden verursachen kann. • Java verfügt über viele Standardfunktionen Auch ein gutes Argument für Java: für viele häufig auftretenden Probleme besitzt Java schon 5 6 KAPITEL 1. DER ERSTE TAG vorgefertigte Lösungen, die kostenlos (wie auch die Programmiersprache selbst) mitgeliefert werden. Noch eine kleine Anmerkung: Die erste Version von Java wurde 1995 veröffentlicht und hatte die Versionsnummer 1.0. Ab der Version 1.2 spricht man kurioserweise vom Java 2 SDK ( = Software Development Kit). Die neuste Version heisst allerdings wieder Java 1.5.... 1.2 Jetzt geht es los Wir wollen uns jetzt ein Java-Programm mal etwas genauer anschauen. Grundsätzlich unterscheidet man (und frau auch) zwischen sog. Applets, die fest in Webseiten eingebaut sind und Java-Programmen, die sich auch alleine ausführen lassen. /* Hallo.java Ein erstes Java-Programm Ole Schulz-Trieglaff, März 2004 */ // Hier geht es los public class Hallo { public static void main(String[] args) { System.out.println("Hallo!"); System.out.println("Ich ein Java-Programm!"); } } Was bedeutet das alles ? • Die ersten sieben Zeilen bilden Kommentare. • public class Hallo {..} Definiert alles, was zu (Haupt-)Klasse Hallo gehört. Die Inhalt der Klasse wird von geschweiften Klammern umrahmt: { und }. • public static void main(String[] args) {..} Beim Ausführen der Klasse wird dieser Abschnitt zuerst ausgeführt. Alles was zu diesem main-Abschnitt gehört, wird wieder von Klammern eingerahmt. • System.out.println("Hallo!"); Dieser Befehl gibt Hallo! auf dem Bildschirm aus. Alle Befehle müssen in Java mit einem Semikolon abgeschlossen werden. 1.3. JAVA-APPLETS 1.2.1 7 Ausführen des Programms Das Ganze läuft immer gleich: 1. Programm schreiben / abtippen mit Editor der Wahl und speichern. Der Dateiname muss dabei dem Namen der Hauptklasse entsprechen. (Im Beispiel oben Hallo.java ). 2. Für den Computer übersetzen (kompilieren) mit: javac Hallo.java Dieser Befehl erzeugt eine Datei Hallo.class, die Java-Bytecode enthält. 3. Ausgeführt wird dieser Bytecode dann mit dem Befehl java Hello. 1.3 Java-Applets Der Vollständigkeit halber möchte ich hier nochmal kurz ein Java-Applet vorstellen. Applets sind kleine Programme, die in eine Internetseite eingebaut sind und von einem Webbrowser (z.B. dem Internet Explorer) ausgeführt werden. Ein kurzes Applet kann z.B. so aussehen: import java.awt.*; import java.applet.Applet; public class FirstApplet extends Applet { public void paint (Graphics g) { g.drawString("Hallo! Ich bin ein Java-Applet.",50,50); } } 1.4 Variablen und Operatoren Variablen sind Platzhalter oder Container. Sie speichern Werte, mit denen ein Programm arbeiten soll. Um eine Variable verwenden zu können, muss man sie zuerst deklarieren und sie dann initialisieren. Beispiel: \\ Wir erstellen eine Variable vom Typ int mit Namen var int var; // Deklaration: Variable var hat Typ int var = 99; // Initialisierung: var hat jetzt den Wert 99 Deklaration und Initialisierung sind Anweisungen, deshalb werden sie auch mit einem Semikolon beendet. Java unterscheidet zwischen Groß- und Kleinschreibung. Deshalb sind var und Var unterschiedliche Variablen. Es gibt ebenfalls strenge Regeln, welche Zeichen Variablennamen (identifier) enthalten dürfen: 1. Die Zeichen des Alphabets (java letter), also a-z and A-Z 2. Der Unterstrich 3. Die Ziffern von 0-9 8 KAPITEL 1. DER ERSTE TAG 4. Ein identifier muss mit einem java letter oder $ beginnen. Es gilt die Konvention, dass eine Variable immer mit einem kleinen Buchstaben beginnen muss. Falls der Name aus mehreren Teilen besteht, wird jeder außer dem ersten Teil mit einem Großbuchstaben begonnen, z.B. : hoeheDerBox 1.4.1 Datentypen Man unterscheidet in Java (und in den meisten anderen Programmiersprachen auch) zwischen: • primitiven Datentypen. Sie sind nicht weiter unterteilbar. • und zusammengesetzten Datentypen. Sie bestehen aus mehren Teilwerten, die jeweils einzeln manipuliert werden können. Ein Beispiel dafür sind Arrays, die Ähnlichkeit zu Listen in Haskell besitzen. Hier eine Übersicht über die wichtigsten primitiven Datentypen: Name Grösse Wertebereich byte 8 -126 bis +128 short 16 -32768 bis 32767 int 32 −231 bis 231 − 1 long 64 −263 bis 263 − 1 float 32 −3.4 × 1038 bis +3.4 × 1038 double 64 −1.797 × 10308 bis +1.797 × 10308 boolean 1 true oder false char 16 Druckbare Zeichen: ‘a‘ bis ‘Z‘ auch Steuerzeichen Der wichtigste zusammengesetzte Datentyp ist String. Er sieht in Java genauso aus wie in Haskell. Er besteht aus einem oder mehreren Zeichen, eingefasst von doppelten Anführungsstrichen, wie z.B. “Java ist toll !! “. Man kann zwei Strings aneinanderhängen (konkatenieren) mit dem + Operator (Nicht ++ wie bei Haskell !!): string str = "Java" + " ist " + "toll!"; int i = 5; System.out.println("i hat den Wert: " + i); 1.4.2 Operatoren und Berechnungen Java kennt folgende Operatoren: arithmetische Operatoren: • binäre: +, −, ∗, / und % (Modulooperation, Rest bei Division) int x = 3 / 2; float z = 3.0 / 2; // ergibt 1 // ergibt 1.5 1.4. VARIABLEN UND OPERATOREN 9 • und unäre: ++ und −−. Beide gibt es in einer Präfix- und Postfix-Version. Sie können nur auf Variablen angewendet werden. int i = 5; int k = i++; int l = ++i; // k = 5, i = 6 // l = 7, i = 7 Vergleichsoperatoren: • == und ! = (ungleich) sind auf beliebige Typen anwendbar • <, <=, >, >= für Zahlen und Buchstaben Boolesche Operatoren: Auch Java kennt Lazy-Evaluation ! Es wird hier aber Short-Circuit-Evaluation genannt. • ! Nicht (not in Haskell) • && Und, || Oder mit Short-Circuit-Evaluation • & logisches und, | Oder, ∧ exklusives Oder (XOR) Bitoperatoren Mit diesen Operatoren kann direkt auf die Binärdarstellung von numerischen Datentypem zugegriffen werden. • bitweises Komplement: ∼ inervertiert jedes einzelne Bit • & bitweises Und, | bitweises Oder, ∧ bitweises XOR , << Linksshift, >> arithmetischer Rechtsshift, >>> logischer Rechtsshift // Mit dem Linksshift schnell potenzieren: byte potenz = 2 << 2; // ergibt 2^3 = 8, denn 00000010 wird zu 00001000 Zuweisungsoperatoren Der Zuweisungsoperator ist in Java einfach das Gleichheitszeichen =. (Wer hätte das gedacht!) Es gibt aber zusätzlich noch die Möglichkeit, Operationen mit dem Gleichheitszeichen zu kombinieren. Ein Beispiel: Wenn wir den Wert einer Variablen x um 5 erhöhen wollen, dann könnten wir schreiben: x = x + 5; // x hat jetzt den Wert 5 Viel kürzer ist jedoch: x += 5; // x hat jetzt auch den Wert 5 Das Gleiche geht auch mit: + =, − =, ∗ =, / =, % =, & =, | =, ∧ =, <<=, >>=, >>>= Was man auch noch wissen sollte: der Zuweisungsoperator liefiert das Ergebnis zurück. Es sind also auch Verkettungen möglich wie: float i,j,k; i = j = 7; k = 5 + 6*(i += j/2); Zum Schluss gibt es noch einen Operator, von dem man etwas gehört haben sollte, und zwar ?. Er wird auch als ternärer Auswahloperator bezeichnet: max = (i>j) ? i : j; 10 1.5 KAPITEL 1. DER ERSTE TAG Typecasts Oft gerät man in Situationen, in denen man verschiedene Datentypen ineinander umwandeln will oder muss. So einen Vorgang nennt man Typecast. Man unterscheidet zwischen expliziten und impliziten Typecasts: expliziter Typecast: float f = 2.5f; int i = (int) f; // Float wird zu Int, i hat den Wert 2 impliziter Typecast: 5/2.0 // double 2.5 int i = 5; System.out.println("i hat den Wert: " + i); // Typecast von int nach String Allgemein gilt, dass erweiternde Typecasts, bei denen keine Genauigkeit verloren geht, vom Compiler automatisch (implizit) durchgeführt werden. Also z.b. von short nach float o. ä. In die umgekehrte Richtung ist eine explizite Anweisung des Programmiers erforderlich. Kapitel 2 Der zweite Tag 2.1 Schleifen und Anweisungen Ein Block ist eine Zusammenfassung von Anweisungen, diese werden nacheinander abgearbeitet. Eine Variable ist ab ihrer Deklaration sichtbar und zwar bis zu dem Ende des Blocks, in dem sie eingeführt wurde. int i = 0; { int j = i; // OK { int k = i; // OK int k = 0; // Fehler: k schon belegt } j = k; // Fehler: k nicht bekannt int k = 0; // OK: k neu belegen } ++j; // Fehler: j nicht bekannt ++i; // OK 2.2 if-Anweisung Syntax if (Bedingung) Anweisung; // Wenn Bedingung true ist, wird Anweisung ausgeführt oder if (Bedingung) Anweisung1; // Bedingung ist erfüllt else Anweisung2; // Bedingung ist nicht erfüllt Beispiel: if (Temperatur < -10 ) System.out.println("Brrr!!"); 11 12 KAPITEL 2. DER ZWEITE TAG Es geht auch: if (Bedingung1) Anweisung1; // Bedingung1 ist erfüllt else if (Bedingung2) Anweisung2; // Bedingung2 ist erfüllt statt if (Bedingung1) Anweisung1; // Bedingung1 ist erfüllt else if (Bedingung2) Anweisung2; // Bedingung2 ist erfüllt Statt einer einzelnen Anweisung kann auch eine Folge von Anweisungen stehen. Diese müssen dann allerdings als Block zusammengefasst werden. Einer der typischen Fallstricke, wie sie in vielen Programmiersprachen auftreten ist das Problem des dangling else: if (a) if (b) s1; else s2; Diese Codefragment wird nicht so ausgeführt, wie es die Einrückung vermuten lässt. Der else- Zweig gehört zur innersten Verzweigung mit if(b).... Ein “freies“ else wird immer an das am weitesten innen liegende if angehängt. 2.3 Die while-Schleife // berechne ganzzahligen Logarithmus von n // zur Basis 2 fuer n > 0 int log = 0; while (n > 1) { ++log; n /= 2; } System.out.println("log = "+log); 2.4 do-while-Schleife // berechne ganzzahligen log von n // zur Basis 2 fuer n > 0 int log =-1; do { ++log; n /= 2; } while (n > 0); System.out.println("log = "+log); 2.5. DIE FOR-SCHLEIFE 13 Diese Schleife wird mindestens einmal ausgeführt, selbst wenn die while-Bedingung nicht erfüllt ist. 2.5 Die for-Schleife Syntax: for (init; test; update) Anweisung; init und test sind Ausdrücke. Beide können auch aus mehreren Ausdrücke bestehen. Diese müssen dann durch Kommata getrennt werden. An der Stelle von init dürfen auch Initialisierungen stehen. Beispiel: // berechnet 2^n // also die n-te 2er Potenz für n>0 int potenz = 2; for (int i=1; i<n; i++) { potenz *= 2; } System.out.println(n + "-te 2er Potenz ist " + potenz); init, test und update können auch leer sein: \\ Die forever-Schleife for (;;) { System.out.println("Das hört ja nie auf."); } 2.6 Die switch-Anweisung switch (Ausdruck) { case Constant1: Anweisung1; case Constant2: Anweisung2; .... default: DefaultAnweisung; } Ausdruck wird zuerst ausgewertet. Er muss vom Typ byte, short, char oder int sein. In Abhängigkeit vom Ergebnis wird dann die case-Marke angesprungen, deren Konstante mit dem Ergebnis übereinstimmt und die entsprechende Anweisung ausgeführt. Beispiel: switch (Wochentag) { case 1: System.out.println("Es ist Montag."); 14 KAPITEL 2. DER ZWEITE TAG break; case 2: System.out.println("Es ist Dienstag."); break; // usw. ... case 7: System.out.println("Es ist Sonntag."); break; default: System.out.println("Falsche Eingabe!"); } 2.7 Break und continue Die break -Anweisung kann dazu eingesetzt werden um Schleifen vorzeitig zu verlassen. for (int i=0; i<irgendwas; i++) { // Mach was if (Schluss) break; // For-Schleife wird verlassen // kein Schluss } // Hier geht es nach break weiter... Wichtig: Bei verschachtelten Schleifen wird nur die innerste Schleife verlassen! 2.7.1 Labeled Breaks Mit Hilfe von einem Label können wir auch mehrere Schleifen auf einmal verlassen: for (int i=0; i<n; ++i) { forLabel: // Label für die Break-Anweisung unten for (int j=0; j<n; ++j) { for (int k=0; k<n; ++k) { // ... if (Spring) break forLabel; // ... } } // bis hierher wird gesprungen } 2.7.2 continue Die continue-Anweisung arbeitet ähnlich wie break. Ohne ein Label springt continue an das Ende der aktuellen Schleife und beginnt mit dem nächsten Durchlauf. Mit Label springt continue an das Ende der markierten Schleife. Man sollte mit break und continue sparsam umgehen! Ein Programm kann durch sie sehr unübersichtlich werden. 2.8. ARRAYS 2.8 15 Arrays Arrays sind die nächste Entsprechung zu den Listen in Haskell und haben eine ähnlich große Bedeutung. Sie sind eine Zusammenfassung (genauer: ein Tupel) von Elementen gleichen Typs. Genau wie primitive Datentypen werden sie zuerst deklariert int[] arr1; double arr2[]; // alternative Schreibweise, Tribut an die Sprache C++ bool[] barr; und dann initialisiert arr1 = new int[5]; // arr1 kann 5 Elemente vom Typ Int aufnehmen arr2 = new double[10] barr = new boolean[15]; In diesem Beispiel oben werden alle Elemente mit 0 bzw. false initialisiert. Alternativ können die Elemente auch schon bei der Initialisierung angeben werden: int[] x = {1,2,3,4,5}; boolean[] b = {false,false}; 2.8.1 Zugriff auf Array-Elemente Wie in den Haskell-Listen beginnt die Indizierung bei 0. Der Zugriff auf einzelne Elemente erfolgt durch einen Index, der in eckigen Klammern hinter dem ArrayNamen angegeben wird. // Alle Elemente initialisieren for (int i=0; i<arr.length; i++) { arr[i] = i*10; } // Alle Elemente eines Arrays ausgeben for (int i=0; i<arr.length; i++) { System.out.println(arr[i]); } 2.8.2 Mehrdimensionale Arrays Mehrdimensionale Arrays werden als Arrays von Arrays angelegt. Sie müssen nicht rechteckig sein, d.h. ein Array kann Arrays unterschiedlicher Größe enthalten. Der Zugriff auf einzelne Elemente erfolgt durch Angabe aller Indizes, jeweils in eckigen Klammern. float[][] matrix = new float[10][10]; int[][][] matrix3d = new int[100][100][100]; // Ausgabe for (int i=0; i<matrix.length; i++) { for (int j=0; j<matrix[i].length; j++) System.out.println("Der Eintrag an Stelle " + i +" " + j + " ist: " + matrix[i][j]); } 16 KAPITEL 2. DER ZWEITE TAG 2.9 Lesen von der Kommandozeile Wenn man ein Programm mit dem Befehl java ausführt kann man hinter dem Programmnamen noch weitere Parameter angeben. Diese Parameter landen im dem Array args aus der Main-Methode. public class Echo { public static void main(String[] args) { for (int i=0; i<args.length; ++i) System.out.print(args[i]+" "); System.out.println(); } } Mit Integer.parseInt(string) lässt sich dann ein String-Parameter in einen Int umwandeln und mit Double.parseDouble(string) in einen Double. Beispiel: // Dieses Programm summiert alle Zahlen auf, // die als Eingabeparameter übergeben wurden // Aufruf: java Add 1 2 3 public class Add { public static void main(String[] args) { int sum = 0; for(int i=0; i<args.length;i++) { sum += Integer.parseInt(args[i]); } System.out.println("Summe: " + sum); } } Kapitel 3 Der dritte Tag 3.1 3.1.1 Ojektorientierte Programmierung Klassen und Objekte Im Gegensatz zu Haskell sind nicht Funktionen das zentrale Element in Java, sondern Klassen. Allgemein fasst eine Klasse Daten und Funktionen, die auf diesen Daten operieren zusammen. Allerdings ist eine Klasse nichts Konkretes. Am Besten stellt man sie sich als eine Art Bauplan oder Rezept vor, nachdem der Computer vorgeht. Das Ergebnis einer solchen Konstruktion ist dann eine Instanz (auch Objekt genannt). Um bei unserem Beispiel zu bleiben: Wenn man sich eine Klasse als einen Bauplan z.B. für ein Auto vorstellt, dann definiert dieser Bauplan, was ein Auto für Eigenschaften hat und was man damit machen kann. Ein Auto hat eine Farbe und eine bestimme Leistung (in PS). Ein Auto kann beschleunigen und abbremsen. Ein konkretes Auto, z.B. ein Porsche oder Volkswagen wäre dann eine Instanz der Klasse Auto. public class Auto { int ps; \\ Membervariablen oder Felder String farbe = "Blau"; } Wird für ein Feld kein Wert angegeben, wird es automatisch mit 0 bzw. f alse initialisiert. Eine Instanz dieser Klasse lässt sich mit new erzeugen: Auto meinAuto = new Auto(); meinAuto.ps = 300; System.out.println("Mein Auto hat " + meinAuto.ps + " PS"); 3.1.2 Methoden Methoden definieren das Verhalten einer Klasse. Eine Klasse Auto sollte bremsen und beschleunigen können. Eine Methode kann auch verwendet werden, um die Felder einer Klasse zu ändern oder auszugeben. 17 18 KAPITEL 3. DER DRITTE TAG class Auto { int ps = 200; String farbe = "Blau"; void tune(int p) { // ändert die Membervariable PS ps = p; // kein Zugriff mit ’.’ } void zeigeFarbe() { // gibt die Farbe aus System.out.println("Dieses Auto hat die Farbe: " + Farbe); } } public class AutoTest { public static void main(String[] argv) { Auto Golf = new Auto(); Golf.zeigeFarbe(); // "Dieses Auto hat die Farbe: blau " Golf.tune(300); // Dieser Golf hat jetzt 300 PS } Wie man in diesem Beispiel sieht, können Methoden einer Klasse auf die Felder ohne den Punkt-Operator ’.’ zugreifen. Wenn man deutlich machen will, dass man auf die Felder der aktuellen Klasse zugreift, dann kann man this verwenden, eine Referenz auf die aktuelle Klasse: void tune(int p) { this.ps = p; } // ändert die Membervariable PS Die Definition einer Methode hat den folgenden Aufbau (alles in eckigen Klammern ist optional): [Modifier] Rückgabewert Name( [Parameter] ) { [Anweisungen;] } Über die sogenannten Modifier, von denen wir bisher nur public kennen, werden erst morgen mehr erfahren. 3.1.3 Parameter Eine Methode kann mehrere Parameter als Übergabewerte erhalten. Anders als bei Haskell-Funktionen muss nicht nur der Typ angegeben werden, sondern auch ein Name für die Variable. void beschleunigen(int wieOft) { while (wieOft-- > 0) System.out.println("Brumm"); } Wichtig ist, dass in Java Parameter by value übergeben werden, d.h. es werden nur Kopien übergeben und die Aufrufer der Methode merkt nichts von Änderungen der Variable in der Methode. Dies gilt nicht für Objekte, wie z.B. Arrays oder eigene Klassen! Wenn man ein Objekt einer Methode übergibt, dann sind 3.1. OJEKTORIENTIERTE PROGRAMMIERUNG 19 Änderungen in der Methode auch für den Aufrufer sichtbar. Das liegt daran, dass Objekte Referenzen sind, also auf einen Speicherbereich verweisen. Auto Porsche = new Auto(); int i = 10; Porsche.beschleunigen(i); System.out.println(i); // ergibt 10 3.1.4 Rückgabewerte Statt void (nichts) kann eine Funktion auch Werte wie int oder char zurückgeben. Die Anweisung dafür lautet return. Wenn diese Anweisung ausgeführt wird, führt dies zum Beenden der Methode. Der Java-Compiler stellt sicher, dass jeder Weg aus der Methode heraus mit einnem return abgeschlossen wird. Beispiele: int wrong() { if (flag) // flag ist boolesche variable return 1; // Compilerfehler: kein return fuer !flag } int alsoWrong() { if (flag) return 1; if (!flag) // Fehler: return 0; // Compiler nicht schlau genug } int ok() { if (flag) return 1; else return 0; } Noch ein Beispiel mit Parametern und Rückgabewert: class Rechner { // Immer nur eine public Klasse pro Datei !! int log (int n) { int log = 0; while (n > 1) { ++log; n /= 2; } return log; } } public class LogTest { public static void main(String[] argv) { 20 KAPITEL 3. DER DRITTE TAG if (argv.length != 1) { System.out.println("usage: " +"java Factorial <n>"); return; } int n = Integer.parseInt(argv[0]); Rechner re = new Rechner(); int l = re.log(n); System.out.println("2er Logarithmus von " + n + " ist " + l); } } 3.1.5 Überladen von Methoden Es ist erlaubt innerhalb einer Klasse zwei Methoden mit dem gleichen Namen zu definieren. Der Compiler unterscheidet die Methoden dann anhand der Anzahl und des Typs ihrer Parameter. Der Rückgabewert trägt nicht zur Unterscheidung bei. Es ist nicht erlaubt, zwei Methoden mit gleichem Namen und gleichen Parametern zu definieren. 3.1.6 Rekursion Rekursion ist in Java längst nicht so wichtig wie in Haskell, aber auch möglich: int fakultät(int n) { if (n < 2) return 1; return n * fakultät(n-1); } 3.1.7 Konstruktoren Unter einem Konstruktor versteht man eine spezielle Methode, die bei der Initialisierung des Objekts aufgerufen wird. Der Konstruktor hat den gleichen Namen wie die Klasse zu der er gehört und hat keinen Rückgabewert. Er kann beliebig viele Parameter haben und darf überladen werden. Er kann dazu benutzt werden, die Felder seiner Klasse zu initialisieren: class Auto { int ps; String farbe; Auto(int p) { // Konstruktor Nr. 1 ps = p; } Auto(int p, String f) { // Konstruktor Nr. 2 ps = p; farbe = f; } 3.2. EIN- UND AUSGABE 21 } public class AutoTest { public static void main(String[] args) { Auto BMW = new Auto(200); // erster Konstruktor Auto Mercedes = new Auto(210,"Schwarz"); // zweiter Konstruktor } } Wenn man keinen Konstruktor expliziert definiert, dann wird vom Compiler ein Default- Konstruktor ohne Parameter erstellt. Wenn man wie in dem Beispiel oben nur parametrisierte Konstruktoren angibt, dann wird kein DefaultKonstruktor erstellt. 3.1.8 Verkettung von Konstuktoren Konstruktoren können sich auch gegenseitig aufrufen. Der aufzurufende Konstruktor wird dabei als normale Methode angesehen. Er wird über den Namen this aufgerufen. Die beiden Konstruktoren aus dem Beispiel von oben könnten auch so geschrieben werden: Auto(int p) { ps = p; } Auto(int p, String f) { this(p); farbe = f; } // ruft ersten Konstruktor auf Dieses Beispiel ist vielleicht etwas verwirrend, aber bei größeren Klassen mit vielen Feldern spart man sich Tipparbeit. 3.2 Ein- und Ausgabe Die Ein- und Ausgabe von Daten wird in Java über Streams abgewickelt. Je nach Empfänger und Sender der Daten gibt es verschiedene Streams. Will man z.B. Eingaben von der Tastatur aus einlesen muss man einen anderen Stream verwenden, als wenn man in eine Datei schreiben wollte. Streams sind selbst Klassen. Wir arbeiten mit ihnen, indem wir Instanzen von ihnen anlegen und ihre Methoden aufrufen. Streams lassen sich auch ineinander verschachteln z.B. um die Eingabe zu filtern. Einen wichtigen Stream haben wir schon kennengelernt: System.out.println("Ausgabe: "); // Ausgabe mit Zeilenumbruch int i = 10; System.out.print(i); // Ausgabe ohne Zeilenumbruch 22 KAPITEL 3. DER DRITTE TAG Es ist leider etwas komplizierter, Daten von der Tastatur einzulesen. Wir müssen zuerst eine Instanz der Klasse InputStreamReader anlegen und diese dann mit einem BufferedReader verknüpfen. import java.io.*; public class Eingabe { public static void main(String[] args) throws IOException { int a; InputStreamReader InStream = new InputStreamReader(System.in); BufferedReader BReader = new BufferedReader(InStream); System.out.print("Bitte geben sie eine Zahl ein: "); String str = BReader.readLine(); // eine Zeile lesen a = Integer.parseInt(str); // String in int umwandeln System.out.println("Sie haben eingegeben: " + a); } } Die Klasse Integer ist eine sogenannte Wrapper-Klasse. Sie dient unter anderem dazu, andere Datentypen in Integer zu konvertieren. Der Vollständigkeit halber noch ein Beispiel zur Ausgabe in eine Datei: import java.io.*; public class Ausgabe { public static void main(String[] args) throws IOException { int zahl = 40; File datei = new File("Test.txt"); FileWriter OutStream = new FileWriter(datei); PrintWriter OutWriter = new PrintWriter(OutStream); OutWriter.println("Das wird jetzt in die Datei geschrieben"); OutWriter.println(zahl); // Schreibt 40 in die Datei OutWriter.close(); // schliest dann Stream zur Datei } } Hier wird erst eine Instanz der Klasse File erzeugt. Der Konstruktor erhält den Namen der Datei als String. Dann wird eine FileWriter- und eine PrintWriter-Instanz erzeugt. Die PrintWriter-Instanz kann jetzt wie System.out verwendet werden. Kapitel 4 Der vierte Tag 4.1 Vererbung Neben der Verwendung von Klassen ist Vererbung ein wichtiges Merkmal objektorientierter Sprachen. Unter Vererbung versteht man die Möglichkeit, Eigenschaften vorhandener Klassen auf andere zu übertragen. class Mitarbeiter { String name; int gehalt; Mitarbeiter(String n, int g) { name = n; gehalt = g; } void gehaltErhoehen(int erhoehung) { gehalt += erhoehung; } } class Chef extends Mitarbeiter { // Diese Klasse erbt von Mitarbeiter String abteilung; // Klasse Chef hat zusätzliches Feld abteilung // Konstruktor der Oberklasse wird aufgerufen Chef(String n, int g, String abt) { super(n,g); // muss als erstes stehen abteilung = abt; } // Diese Methode überschreibt die Methode aus der Oberklasse void gehaltErhoehen(int erhoehung) { gehalt += 2*erhoehung; } } 23 24 KAPITEL 4. DER VIERTE TAG • Vererbung ermöglicht die Abbildung von Hierarchien und die Wiedervendung von Code. • Die Unterklasse erbt Methoden und Felder der Oberklasse. Sie kann aber auch eigene Member definieren. • Methoden der Unterklasse, welche die gleiche Signatur wie Methoden der Oberklasse besitzen, überschreiben diese. • Es gibt in Java keine Mehrfachvererbung d.h. eine Klasse hat immer nur eine Oberklasse und nicht mehrere. • Jede Java-Klasse erbt von der Klasse Objekt auch wenn es nicht explizit angegeben wird. public class TestMitarbeiter { public static void main(String[] args) { Mitarbeiter m = new Mitarbeiter("Heinz",1000); Chef c = new Chef("Ole",2000,"Abeilung2"); m.gehaltErhoehen(100); c.gehaltErhoehen(100); System.out.println(m.gehalt); System.out.println(c.gehalt); // ergibt 1100 // ergibt 2200 } } Mit dem Ausdruck super lässt sich nicht nur der Konstruktor der Oberklasse aufrufen, sondern auch auch überschriebene Methoden. In dem Beispiel von oben würde super.gehaltErhoehen(100); in der Klasse Chef die Methode der Klasse Mitarbeiter aufrufen. 4.2 Modifier Modifier sind eine Möglichkeit die Eigenschaften von Klassen, Methoden und Variablen zu verändern. Vorallem beeinflussen Modifier die Sichtbarkeit von Programmelementen. Sie erlauben eine Kontrolle darüber, inwiefern die abgeleiteten Klassen Zugriff auf die Elemente der Oberklasse haben. • public : Elemente diesen Typs sind überall sichtbar. Sie können in der eigenen Klasse und von Methoden beliebiger anderer Klassen benutzt werden. Auch Klassen können public sein. Allerdings darf in jeder Quelldatei nur eine öffentliche Klasse stehen. • protected : Methoden oder Variablen diesen Typs sind nur in der aktuellen Klasse und in von ihr abgeleiteten Klassen sichtbar. Außerdem sind sie für Klassen aus dem gleichen Paket sichtbar. 4.3. STATIC 25 • package scoped : Wenn kein Modifier angegeben wird, gilt die Regel, dass Klassen, Methoden und Variablen nur innerhalb ihres Pakets sichtbar sind. • static : Elemente mit diesem Attribut sind nicht an die Existenz einer Instanz gebunden, d.h. sie existieren vom Laden einer Klasse bis zum Beenden des Programms. • final Membervariablen vom Typ final können nicht verändert werden. Methoden mit diesem Typ dürfen nicht überschrieben werden und Klassen nicht zur Ableitung neuer Klassen benutzt werden. Solche Variablen kann man als Konstanten betrachten. Es gibt die Konvention konstante Variablen in Großbuchstaben zu benennen. • Außerdem gibt es noch die Modifier transient und volatile. Sie sind für die Serialisierung von Objekten und für die Programmierung von Threads wichtig. 4.3 static Wir haben schon verschiedene statische Membervariablen und Methoden kennengelernt z.B. ist out eine statische Membervariable von System. Die Klasse Math besitzt verschiedene statische Methoden wie abs, sin oder cos. Eine weitere Anwendung von statischen Klassenvariablen ist ein Instanzenzähler: public class TestAuto { public static int objcnt = 0; public Testauto { ++objcnt; } public void finalize() { --objcnt; } public static void main(String[] argv) { Testauto auto1; Testauto auto2 = new Testauto(); System.out.println("Anzahl Testauto-Instanzen: " + Testauto.objcnt); } } Auch die main-Methode ist statisch definiert. 4.4 Abstrakte Klassen Außerdem gibt es noch die Möglichkeit, abstrakte Methoden zu definieren. Eine solche Methode bestitzt anstelle der geschweiften Klammern mit Anweisungen lediglich ein Semikolon und das Attribut abstract. Eine Klasse mit abstrakten Methoden wird insgesamt als abstrakt gekennzeichnet. Sie kann nicht instanziert 26 KAPITEL 4. DER VIERTE TAG werden. Klassen, die sich von abstrakten Klassen ableiten müssen alle abstrakten Methoden aus der Oberklasse implementieren oder sind selbst abstrakt. abstract class Mitarbeiter { String name; public abstract double monatsBrutto(); // abstrakte Methode } class Arbeiter extends Mitarbeiter { double stundenlohn; double anzahlstunden; public double monatsBrutto() { return studenlohn * anzahlstunden; } // überschreibt monatsBrutto(); } 4.5 Interfaces Ein Interface ist eine besondere Klasse, die ausschließlich abstrakte Methoden und Konstanten enthält. Anstelle des Schlüsselwortes class verwendet man interface. Man kann ein Interface als eine Art Schnittstelle auffassen, die bestimmte Eigenschaften für ihre Unterklassen definiert. Es gelten ähnliche Regeln wie für abstrakte Klassen: • Von einem Interface können keine Instanzen gebildet werden. • Eine von einem Interface abgeleitete Klasse muss alle Methoden des Interface implementieren. • Wenn die abgeleitete Klasse nicht alle Methoden implementiert, muss sie als abstract definiert werden. • Ein Interface kann dazu benutzt werden um Mehrfachvererbung zu simulieren. public interface Groesse { public int laenge(); public int hoehe(); public int breite(); } public class Auto implements Groesse { public int laenge; public int hoehe; public int breite; public int leistung; public int lange() { return this.laenge; } public int hoehe() { return this.hoehe; } public int breite() { return this.breite; } } 4.6. GRAPHISCHE AUSGABE MIT SWING 27 Interfaces sind die nächste Entsprechung zu Klassen in Haskell. Beispielsweise gibt es in Java das Interface Comparable mit der abstrakten Methode compareTo. Dieses Interface kann von Klassen implementiert werden, die paarweise vergleichbar sind. 4.5.1 Mehrfachvererbung Eigentlich ist in Java keine Mehrfachvererbung möglich, d.h. eine Klasse kann immer nur von einer Oberklasse erben. Es ist allerdings möglich, dass eine Klasse von mehreren Interfaces erbt. Diese muss dann allerdings alle abstrakten Methoden der Interfaces implementieren, oder wird als abstract deklariert. Die Auto-Klasse von oben könnte z.B. die Interfaces Groesse und Comparable implementieren. public class public int public int public int punlic int Auto implements Groesse, Comparable { laenge; hoehe; breite; leistung; public int lange() { return this.laenge; } public int hoehe() { return this.hoehe; } public int breite() { return this.breite; } public int CompareTo(Object o) { int ret = 0; if (leistung < ((Auto)o).leistung) { ret = -1; } else if (leistung > ((Auto)o).leistung) { ret = 1; } return ret; } } 4.6 Graphische Ausgabe mit Swing Ein großer Vorteil von Java ist, dass Methoden zur Erzeugung von graphischen Oberflächen frei Haus mitgeliefert werden. Es gibt zwei Bibliotheken für die Erzeugung von Fenstern, Buttons etc. Die ältere heisst AWT (Abstract Window Toolkit). Sie ist etwas langsam und hat deshalb einen schlechten Ruf unter Programmiern. Dies war der Grund für die Entwicklung von Swing. Swing ist eine Sammlung von Klassen, die alles bieten was man zur Gestaltung von graphischen Oberflächen braucht. Leider ist diese Bibliothek sehr umfangreich. Eine Einführung würde einen eigenen Kurs füllen. Deshalb nur ein kurzes Beispiel wie man Dialoge und Eingabefelder mit Swing erzeugen kann: 28 KAPITEL 4. DER VIERTE TAG import javax.swing.JOptionPane; public class SwingDemo { public static void main(String[] args) { String input = JOptionPane.showInputDialog("Bitte geben sie etwas ein: "); JOptionPane.showMessageDialog(null,"Danke! Sie haben eingegeben: " + input); } }