1 Informatik I: Programmierung Herbert Kuchen V4 + Ü2, WWU Münster, WS 2002/2003 Mo+Do 14.15-15.45 Uhr Gliederung: 1. Einführung 2. Objektorientierte Programmierung (in Java) 3. Deklarative Programmierung (in Haskell) 4. Semantik von Programmiersprachen 2 Literatur • Java: ? J. Bishop: Java lernen, 752 S. + CD, Addison Wesley, 2001. ? D.D. Riley: The Object of Java – Introduction to Programming Using Software Engineering Principles, 670 S. + CD, Addison Wesley, 2002. ? D. Arnow, G. Weiss: Introduction to Programming Using Java, 805 S., Addison Wesley, 2000. • Haskell: ? S. Thompson: Haskell – The Craft of Functional Programming, AddisonWesley, 1999. ? R. Bird: Introduction to Functional Programming using Haskell, Prentice Hall, 1998. • Semantik: ? G. Winskel: The Formal Semantics of Programming Languages, MIT Press, 1993. 3 1. Einführung Programmiersprache: “zur Festlegung der Arbeitsabläufe eines Rechners” Klassifikation nach Programmierniveau 1) Maschinensprachen (1. Generation) 2) Assemblersprachen (2. Generation) 3) Höhere Programmiersprachen (3. Generation) 4) 4GL-Sprachen (anwendungsbezogen) 4 von-Neumann-Rechnermodell Inhalt Adresse Programmzähler 0 27 42 1 03 01 2 18 42 ... ... 02 ... ... ... 00 Programm Speicher 42 ... n-1 n laden CPU speichern Akku/Register verarbeiten Daten 5 Maschinensprachen Programm: Folge von elementaren Befehlen bestehend aus Operationscode und Argument(en) Befehle für: • Transport zwischen Speicher und CPU (Laden, Speichern, . . . ) • arithmetische und logische Befehle (Addition, Konjunktion, . . . ) auf CPU-Registern • Sprungbefehle (bedingt/unbedingt, absolut/relativ) • ... 6 Beispiel: 27 |{z} inAkku laden 42 |{z} Inhalt von Adresse 42 03 |{z} addiere zu Akku − Inhalt 01 |{z} Zahl 1 18 |{z} speichere Akku − Inhalt 42 ... |{z} Adresse 42 beachte: verschiedene “Adressierungsarten” (abh. von Op.-Code) Eigenschaften: • sehr niedriges Programmierniveau • aufwendig, fehleranfällig, schwer modifizierbar • rechnerabhängig, nicht portierbar • in Praxis zur Programmierung nicht verwendet 7 Assemblersprachen • wie Maschinensprache, jedoch symbolische Namen für Op-Codes, Speicherplätze, Sprungmarken, . . . Beispiel: DEFINE x 42 LOAD x ADD 1 STORE x Eigenschaften: • sehr niedriges Programmierniveau • aufwendig, fehleranfällig, rechnerabhängig • leicht in Maschinensprache übersetzbar (nur die “versteht” der Rechner; → Assembler) • nur in Ausnahmefällen zur Programmierung verwendet 8 Höhere Programmiersprachen • heute in Praxis üblich Klassifikation nach Paradigmen a) imperative Programmiersprachen z.B. Pascal, C, Fortran, Modula, Ada, Basic, Algol, Cobol, PL/I, . . . ⊃ objektorientierte PS: z.B. Java, C++, C#, Eiffel, Smalltalk, Ada95, . . . ? Grundidee: Programm bewirkt Speichertransformation (→ von-Neumann-Modell) ? oft effizienter Bsp.: x := x + 1 9 b) deklarative Programmiersprachen % funktionale PS: z.B. Haskell, Miranda, ML, Lisp, Scheme, Erlang, . . . & logische PS: z.B. Prolog ? Grundidee: ausführbare Spezifikation, “was statt wie” ? höheres Programmierniveau, z.T. weniger effizient 10 Ziele der Programmierung • Korrektheit, Robustheit • leichte Wartbarkeit, Verständlichkeit, Testbarkeit durch geeignete Programmierkonzepte, Modularisierung, Strukturierung, geeignetes Layout, sprechende Namen (z.B. Variable “Gehalt” statt “x”), Kommentare,. . . • effiziente Programme durch geignete Algorithmen und Datenstrukturen (→ Informatik II) 11 2. Objektorientierte Programmierung in Java 2.1 Erste Java-Programme public class HelloWorld{ public static void main(String[] args){ System.out.println("Hello World!");} } Schritte zur Ausführung: • J2SE <http://java.sun.com/j2se/1.4.1/download.html> (oder andere Entwicklungsumgebung) besorgen und installieren (u.a. Pfade!) • Klasse mit Editor erstellen und in Datei HelloWorld.java speichern • Compilieren: javac HelloWorld.java erzeugt HelloWorld.class (Byte-Code, Zwischencode) • Ausführen: java HelloWorld führt die Methode main der Hauptklasse aus 12 Grundbegriffe der Objektorientierung • ein Java-Programm besteht aus mehreren Klassen • zu jeder Klasse gibt es Objekte (Instanzen) • alle Instanzen der gleichen Klasse haben gleiche Struktur (Attribute, Variablen) und gleiches Verhalten (Methoden) • jedem Attribut ist ein Wertebereich (wie z.B. ganze Zahl (int), “reelle Zahl” (float) oder Verweis auf ein Nachbarobjekt) zugeordnet • jedes Attribut kann nur einen Wert des zugehörigen Wertebereichs annehmen • ein Objekt hat eine Identität (OID) (Mehrfachverweise möglich; Objekte mit gleichem Zustand ggfs. ungleich) 13 Klassendiagramme • Klassen und deren Beziehungen werden durch Klassendiagramme visualisiert • Klasse: dargestellt durch Rechteck (unterteilt in Name, Attribute und Methoden) • eine (Nachbarschaft-)Beziehung (Assoziation) zwischen zwei Klassen wird dargestellt durch Linie zwischen den zugehörigen Rechtecken Beispiel: SimpelBank Multiplizitäten 1 ueberweise main Klasse Konto 2 nr saldo getSaldo Assoziation setSaldo Name Attribute Methoden 14 2.1.1 Beispiel: SimpelBank in Java public class SimpelBank{ // Attribute (inkl. Assoziationen) private static Konto kunde1 = new Konto(1,50); private static Konto kunde2 = new Konto(2,50); // Methoden public static void ueberweise(Konto k1, Konto k2,int betrag){ k1.setSaldo(k1.getSaldo()-betrag); //Methodenaufrufe k2.setSaldo(k2.getSaldo()+betrag);} public static void main(String[] args){ System.out.println("alter Saldo von Kunde System.out.println("alter Saldo von Kunde ueberweise(kunde1,kunde2,10); System.out.println("neuer Saldo von Kunde System.out.println("neuer Saldo von Kunde } 1: "+kunde1.getSaldo()); 2: "+kunde2.getSaldo()); 1: "+kunde1.getSaldo()); 2: "+kunde2.getSaldo());} 15 Beispiel: SimpelBank in Java (Fortsetzung) public class Konto{ // Attribute private int nr; private int saldo; //Assoziation zu SimpelBank-Obj. hier weggelassen // Methoden, hier: Kontruktor, getter und setter public Konto(int nr, int betrag){ this.nr = nr; // Zuweisung saldo = betrag;} public int getSaldo(){ return saldo;} // Rueckgabeanweisung public void setSaldo(int betrag){ saldo = betrag;} } 16 Syntax und Semantik • jedes Sprachkonstrukt hat eine Syntax und eine Semantik • Syntax: Wie sieht das Konstrukt aus? • Semantik: Was bedeutet es? • die Syntax wird oft beschrieben durch (endlich viele) Regeln in erweiterter Backus-Naur-Form (EBNF) (s.u.) • Beschreibung der Semantik: ? präzise mathematisch (vgl. Kapitel 4) oder ? weniger präzise in natürlicher Sprache (hier: Deutsch) 17 Erweiterte Backus-Naur-Form • EBNF-Regel hat Gestalt (Syntax): <Nichtterminal> ::= <EBNF-Ausdruck> wobei ein <EBNF-Ausdruck> wie folgt aufgebaut sein kann: (metasprachliche Erläuterungen selbst in EBNF) <EBNF-Ausdruck> <EBNF-Ausdruck> <EBNF-Ausdruck> <EBNF-Ausdruck> <EBNF-Ausdruck> <EBNF-Ausdruck> <EBNF-Ausdruck> ::= ::= ::= ::= ::= ::= ::= <Nichtterminal> <Terminal> <EBNF-Ausdruck>∗ <EBNF-Ausdruck>? <EBNF-Ausdruck>+ <EBNF-Ausdruck> | <EBNF-Ausdruck> (<EBNF-Ausdruck>) // beliebig häufige Wiederholung (auch 0) // optionaler Ausdruck (0 oder 1 Mal) // mindestens einmalige Wiederholung // Alternative // Klammern klären Zusammengehörigkeit • zu jedem Nichtterminal: weitere EBNF-Regeln, die den syntaktischen Aufbau des hierdurch beschriebenen Konstrukts festlegen • Terminale werden nicht durch weitere Regeln beschrieben, sondern so verwendet, “wie sie in dem EBNF-Ausdruck vorkommen” 18 Klassendeklaration • im Beispiel: SimpelBank und Konto • allgemeine Syntax (im folgenden generell vereinfacht): <Klasse> ::= <Sichtbarkeit> class <Klassenname>{ <Attribut-Deklaration>∗ <Methode>∗} • Semantik: legt Struktur (Attribute) und Verhalten (Methoden) jedes Objekts der Klasse fest 19 Attribut-Deklaration • Beispiel: private int saldo; • Syntax: <Attribut-Deklaration> ::= <Sichtbarkeit> <Typ> <Attributname> (= <Ausdruck>)? ; • Semantik: ? reserviert benannten Speicherplatz in jedem Objekt der zugehörigen Klasse ? dieser Speicherplatz kann nur Werte des angegebenen Typs aufnehmen ? Initialisierung optional 20 Sichtbarkeit und Namen <Sichtbarkeit> ::= private | public | . . . • Sichtbarkeitsangaben regeln die Zugriffsrechte auf eine Variable bzw. Methode • private: nur Methoden der betrachteten Klasse dürfen zugreifen • public: von überall her darf zugegriffen werden (bei Attributen vermeiden!) <Klassenname>, <Attributname>, <Methodenname>, sonstige Namen: Buchstabe gefolgt von Buchstaben und Ziffern 21 Typen <Typ> ::= int | float | <Klassenname> | . . . • Speicherplätze enthalten Bitfolgen • die gleiche Bitfolge kann (semantisch) unterschiedlich interpretiert werden • z.B.: 2.0 (float) und 1073741824 (int) durch gleiche Bitfolge repräsentiert • der Typ legt fest, wie die Bitfolge zu interpretieren ist 22 Methodendeklarationen • Beispiel: public int getSaldo(){return saldo;} • Syntax: <Methode> ::= <Sichtbarkeit>(<Typ>| void) Methodenname (<Parameterliste>) { <Anweisung>∗ } • Semantik: ? stellt für die Objekte der Klasse eine benannte, parametrisierte Anweisungsfolge zur Verfügung ? durch Methodenaufruf (s.u.): Anweisungsfolge wird ausgeführt ? das Ergebnis ist vom angegebenen Typ (oder fehlt: void) wobei <Parameterliste> ::= (<Parameter> (, <Parameter>)∗)? <Parameter> ::= <Typ> <Parametername> 23 Anweisungen • Syntax: <Anweisung> ::= <Zuweisung> | <Rückgabeanweisung> | <Ausdruck>? ; | { <Anweisung>∗ } | . . . • Semantik: ? s. Zuweisung, Rückgabeanweisung, . . . ? wird ein Ausdruck als Anweisung verwendet, so interessieren nur die bei seiner Auswertung auftretenden Seiteneffekte (z.B. Ein-/Ausgaben); der berechnete Wert wird ignoriert ? Anweisungsfolgen werden von links nach rechts (oben nach unten) ausgeführt 24 Zuweisungen • Beispiele: ? saldo = betrag; ? saldo = saldo + 1; • Syntax: <Zuweisung> ::= <Attributname> = <Ausdruck>; • Semantik: ? der Ausdruck wird ausgerechnet, ? der erhaltene Wert wird in dem mit <Attributname> benannten Speicherplatz des betrachteten Objekts abgelegt ? Achtung: Zuweisung nicht verwechseln mit math. Gleichung! 25 Rückgabeanweisungen • Beispiel: return saldo; • Syntax: <Rückgabeanweisung> ::= return <Ausdruck>?; • Semantik: ? ? ? ? Rückgabeanweisungen kommen nur in der Anweisungsfolge einer Methode vor der Ausdruck wird ausgerechnet und als Ergebnis des Aufrufs der zugehörigen Methode geliefert der Typ dieses Werts muss dem aus der zugehörigen Methodendeklaration entsprechen 26 Ausdrücke • Beispiele: ? Konstanten, z.B. 2 oder 2.0 ? i+j ? kunde1.getSaldo() • Syntax: <Ausdruck> ::= <Konstante> | <Variable> | <Methodenaufruf> | <Ausdruck> <Operationssysmbol> <Ausdruck> • Semantik: ? der Wert einer numerischen Konstanten ist “die Konstante selber” ? der Wert einer Variablen (inkl. Attribut und Parameter) findet sich im zugehörigen Speicherplatz ? bei zusammengesetzten Ausdrücken: ∗ die Teilausdrücke werden von links nach rechts ausgewertet ∗ die so erhaltenen Werte werden gemäß des Operationssymbols verknüpft ∗ Beispiele für Operationssymbole: +, -, *, /, <<, !=, && 27 Methodenaufrufe • Beispiele: kunde1.getSaldo() kunde1.setSaldo(10) • Syntax: <Methodenaufruf> ::= (<Ausdruck>.)? <Methodenname>( (<Ausdruck> (, <Ausdruck>)∗)?) • Semantik: (call by value) ? der Ausdruck vor . wird ausgewertet und muss eine Objektreferenz liefern ? die übrigen Ausdrücke werden ausgewertet ? ihre Werte werden den Parameter-Variablen zugeordnet, die in der zugehörigen Methodendeklaration vorkommen ? die zur Methode gehörige Anweisungsfolge wird ausgeführt ? hierbei wird auf die Attribute des ermittelten Objekts zugegriffen ? Abarbeitung endet i.d.R. bei Erreichen einer Rückgabeanweisung ? Wert eines Methodenaufrufs: in Rückgabeanweisung angegebener Wert 28 Beispiel: Methodenaufruf mit call-by-value-Semantik public class Testcbv{ private static int k = 3; public static void foo(int i, int j){ System.out.print("i: "+ i); i = i+j; System.out.print(", i: "+ i);} public static void main(String argv[]){ foo(k,k+2); System.out.println(", k: "+ k);} } liefert: i: 3, i: 8, k: 3 29 Konstruktoren • ein Konstruktor ist eine spezielle Methode ohne Ergebnistyp (auch nicht void) • jeder Konstruktor einer Klasse heißt genau wie die Klasse • der Ausdruck: new <Konstruktor>((<Ausdruck> (, <Ausdruck>)∗)?) liefert ein neues Objekt der zugehörigen Klasse • Beispiel: new Konto(1,50) • beim Anlegen dieses Objekts werden die Anweisungen des Konstruktors ausgeführt • zu jeder Klasse kann es mehrere Konstruktoren mit unterschiedlichen Parametertypen geben • hat eine Klasse keinen Konstruktor, so wird ein trivialer Konstruktor ohne Parameter automatisch ergänzt 30 2.1.2 Beispiel: Verwandschaft public class Person{ // Attribute (inkl. Assoziationen) private String name; private Person vater; private Person mutter; // Verknüfung mit Kindern ignoriert // Methoden public Person(String n, Person v, Person m){ name = n; vater = v; mutter = m;} public String getName(){return name;} public Person getVater(){return vater;} public Person getMutter(){return mutter;} ... 2 Person name getName getVater getMutter grosseltern main * 31 public void grosseltern(){ if (vater != null){ if (vater.getMutter() != null) System.out.println(vater.getMutter().getName()); if (vater.getVater() != null) System.out.println(vater.getVater().getName());} if (mutter != null){ if (mutter.getMutter() != null) System.out.println(mutter.getMutter().getName()); if (mutter.getVater() != null) System.out.println(mutter.getVater().getName());}} public static void main(String[] args){ Person adam = new Person("Adam",null,null); Person eva = new Person("Eva",null,null); Person seth = new Person("Seth",adam,eva); Person lamech = new Person("Lamech",seth,null); Person noah = new Person("Noah",lamech,null); seth.grosseltern(); lamech.grosseltern(); noah.grosseltern();} } 32 neue Konstrukte im Verwandschaft-Beispiel: • bedingte Anweisung (if) • Typen boolean und String • lokale Variablen 33 Weitere Typen <Typ> ::= . . . | boolean | String • der Typ boolean umfasst die Wahrheitswerte true und false • String ist kein Basistyp (wie z.B. int und boolean) sondern eine Klasse • eine String-Objekt repräsentiert eine Zeichenkette • Stringkonstanten (Literale) werden in Anführungszeichen eingeschlossen • Strings können durch + verkettet werden • Beispiel: "Dies ist ein " + "verketteter String" 34 Bedingte Anweisung (Verzweigung) • Beispiele: ? if (vater != null){...} ? if (x > y) max = x; else max := y; • Syntax: <Anweisung> ::= <if-Anweisung> | . . . <if-Anweisung> ::= if ( <Ausdruck> ) <Anweisung> (else <Anweisung>)? • Semantik: ? der Ausdruck muss vom Typ boolean sein ? falls die Auswertung des Ausdrucks true liefert, wird die erste Anweisung ausgeführt ? andernfalls die zweite (sofern vorhanden) • im Zweifel gehört ein else-Zweig zum innersten if • Beispiel: if (x>=0) if (x>0) x = 0; else x = 1; 35 Deklaration lokaler Variablen • lokale Variablen werden in einer Anweisungsfolge (einer Methode) deklariert • die Deklaration enspricht syntaktisch der von Attributen, jedoch ohne Sichtbarkeitsangabe • Beispiel: Person seth = new Person("Seth",adam,eva); • Syntax:<Anweisung> ::= <Variablen-Deklaration> | . . . <Variablen-Deklaration> ::= <Typ> <Variablenname> (= <Ausdruck>)? ; • Semantik: ? reserviert benannten Speicherplatz, der nur während der Abarbeitung der zugehörigen Anweisungsfolge bereitsteht ? außerhalb der Anweisungsfolge ist die Variable unbekannt 36 2.2 Basistypen und Basisoperationen • Java unterstützt die folgenden Basistypen Ganze Zahlen byte short int long Gleitkommazahlen float double sonstige char boolean Bits 8 16 32 64 -263 Bereich -128 .. 127 -32768 .. 32767 -231 .. (231 − 1) .. (263 − 1), z.B. 42L 32 64 +/- 3.4E+38 .. +/- 1.4E-45 +/- 1.7E+308 .. +/-4.9E-324 16 ? Unicode-Zeichen true, false 37 Arithmetische Operationen (auf Zahlen) • binär (infix): +,-,*,/,% • unär: --,++ (präfix oder postfix), +,• Beispiele: x * (-1), x++, ++x, 1 + 1.0 (liefert: 2.0) 38 Relationale Operationen • binär, infix • >,<,>=,<=,==,!=,instanceof • Ergebnistyp: boolean • Vor.: Operanden haben “kompatiblen Typ” • Beispiel: x >= 0, kunde1 instanceof Konto 39 Boolesche Operationen • Operanden und Ergebnis boolean • binär (infix): &&: nicht-striktes “und” 2. Argument wird nur ausgewertet, wenn 1. Argument true ||: nicht-striktes “oder” 2. Argument wird nur ausgewertet, wenn 1. Argument false &: striktes “und”; beide Argumente werden ausgewertet |: striktes “oder”; beide Argumente werden ausgewertet • unär: !: “nicht”; in Präfixnotation, z.B. ! fertig 40 Beispiel: Strikte vs. nicht-strikte Operationen 1) if (x > 1 && y % x-- > 0) x = y; 2) if (x > 1 & y % x-- > 0) x = y; • wenn x den Wert 0 hat: 2) Absturz, 1) nicht 41 Verzweigung auf Ausdruck-Ebene statt: if (x == 0) x = 1; else x = 1/x; auch: x = (x == 0) ? 1 : 1/x; allgemein: <Ausdruck> ::= ( <Ausdruck>0 ) ? <Ausdruck>1 : <Ausdruck>2 • falls <Ausdruck>0 zu true ausgewertet wird, ist der Wert von <Ausdruck>1 das Ergebnis • sonst der von <Ausdruck>2 • nur einer von beiden Ausdrücken wird ausgewertet 42 Bit-Operationen • Vor.: Argumente und Ergebnis byte, short, int oder long • op1 >> op2 Binärdarstellung von op1 wird um op2 Positionen nach rechts geschoben • op1 << op2 Linksshift, analog • weiterhin: & (bitweises “und”), | (bitweises “oder”), ^ (bitweises “xor”), ˜ (bitweises Komplement, unär) • geeignet zur effizienten Implementierung von Mengenoperationen • Beispiele: 7>>1 = ˆ 3, 6&3 = ˆ 2 43 Zuweisungsoperationen • Grundform: x = e • außerdem: x += e = ˆ x = x + e • analog: -=, *=, /=, %=, &=, |=, ^=, <<=, >>= • Beispiele: ? x += 1 = ˆ x = x + 1 = ˆ x++ ? x *= 2 = ˆ x = x * 2 44 Operatorpräzedenz Operation [] . (params) expr++ expr-++expr --expr +expr -expr ˜ ! new (type)expr * / % +<< >> >>> • absteigende Präzedenz, z.B. < > <= >= instanceof -x+y*2 == z<<1 && !b = ˆ == != & (((-x)+(y*2)) == (z<<1)) && (!b) ^ | && || ?: = += usw. 45 Konstanten • werden Attribute als final gekennzeichnet, so ist ihr Wert unveränderlich • sie entsprechen damit benannten Konstanten • Beispiel: private final int Mehrwertsteuer = 16; • zur besseren Verständlichkeit und Wartungsfreundlichkeit sollten in Anweisungen keine unbenannten Konstanten (außer 0, 1, 2) verwendet werden Beispiel: Brutto = Netto * (Mehrwertsteuer + 100)/100; 46 Fallen der Rechnerarithmetik • Rechnerarithmetik ist i.allg. nicht assoziativ und nicht kommutativ Beispiel: 4.0 * 8E+307 /2.0 → Infinity 4.0 * (8E+307 /2.0) → 1.6E+308 • bei Rechnerarithmetik entstehen Rundungsfehler • ⇒ Gleitkommazahlen nicht auf Gleichheit oder Ungleichheit testen! Beispiel: statt: besser: if (x != 1.0) ... final float epsilon = 0.0001; ... if (Math.abs(x - 1.0) > epsilon) ... 47 2.3 Felder (Arrays) Motivation public class FirmaNaiv{ private int umsatzM1; private int umsatzM2; ... private int umsatzM12; • häufig: viele gleichartige Daten public int jahresUmsatz(){ int summe = 0; summe += umsatzM1; summe += umsatzM2; ... summe += umsatzM12; return summe;} • naiv und umständlich: jeweils eigene Variable • Beispiel: // weitere Methoden } 48 Jahresumsatz-Beispiel mit Arrays public class Firma{ private int[] umsatz = new int[12]; public int jahresUmsatz(){ int summe = 0; for (int i = 0; i < umsatz.length; i++) summe += umsatz[i]; return summe;} // weitere Methoden } 49 Eigenschaften von Arrays • verwendet bei mehreren gleichartigen Daten (Vor.: feste oder begrenzte Anzahl) • Zugriff auf Element über Index, z.B. umsatz[i] • zugehörige Kontrollstruktur meist for-Schleife (s.u.) • für ein Array a liefert a.length die Anzahl der Elemente • der Indexbereich läuft von 0 bis a.length-1 • Achtung: Indexbereich nicht verlassen! • ein Arrayelement kann wie eine Variable verwendet werden • new <Typ>[n] erzeugt ein Array mit n Elementen vom angegeben Typ 50 Erweiterungen der Syntax • <Typ> ::= <Typ>[<Ausdruck>?] | . . . • <Ausdruck> ::= <Arrayname>[<Ausdruck>] | new <Typ> . . . • <Zuweisung>::= <Arrayname>[<Ausdruck>] <Zuweisungsoperator> <Ausdruck>; | . . . 51 Zuweisung von Arrays • bei einer Zuweisung von Arrays werden nicht die Elemente sondern nur der Verweis auf das Array kopiert • beide Arrays arbeiten im folgenden auf den gleichen Speicherplätzen, Beispiel: int[] a = new int[12]; // erzeugt a[5] = 42; int[] b; // erzeugt einen Verweis b = a; // jetzt verweisen a und System.out.println("b[5]:"+b[5]); // liefert b[5] = 56; System.out.println("a[5]:"+a[5]); // liefert ein Array; a verweist darauf auf ein Array, aber kein Array b auf das gleiche Array 42 56 52 Zählschleifen • Idee: eine Anweisung wird wiederholt ausgeführt • Beispiel: for (int i=1; i <= n; i++) fakultaet *= i; • geeignet, wenn Anzahl der Schleifendurchläufe vorab bekannt • Syntax: <Anweisung> ::= <for-Schleife> | . . . <for-Schleife> ::= for ( <Ausdruck>0 ; <Ausdruck>1 ; <Ausdruck2> ) <Anweisung> 53 Semantik: • Initialisierung: ? <Ausdruck>0 wird am Anfang der Schleife ausgewertet ? typischerweise wird hierbei eine Zählvariable (bereitgestellt und) initialisiert • Abbruchbedingung: ? ? ? ? ? <Ausdruck>1 muss boolean sein er wird vor jeder Abarbeitung der Anweisung ausgewertet liefert <Ausdruck>1 den Wert false, wird die Schleife beendet anderfalls wird die Anweisung ein (weiteres) Mal ausgeführt ist <Ausdruck>1 direkt false, wird die Anweisung nie ausgeführt • Inkrementierung: ? <Ausdruck>2 wird nach jeder Abarbeitung der Anweisung ausgewertet ? typischerweise wird hier die Zählvariable hochgezählt 54 2.4 Kontrollstrukturen • bestimmen, in welcher Reihenfolge die Anweisungen ausgeführt werden • elementare Anweisung: Zuweisung, Methodenaufruf, Variablendeklaration • Sequenz • Verzweigung: if, Mehrfachverzweigung (switch) • Schleife: for-, while- und do-while-Schleife • Rekursion • Exceptions (später) • Threads (später) 55 Mehrfachverzweigung Beispiel: int wochentag; ... switch (wochentag){ case 1: System.out.println(" vormittags" ); case 2: case 3: case 4: case 5: System.out.println(" geöffnet" ); break; default: System.out.println(" geschlossen" );} • Syntax: <Anweisung> ::= <switch-Anweisung> | break ; . . . <switch-Anweisung> ::= switch ( <Ausdruck> ) { <Fall>+ (default : <Anweisung>∗)? } <Fall> ::= case <Konstante> : <Anweisung>∗ 56 Semantik: • der Ausdruck muss vom Typ int sein • die Konstanten müssen (unbenannte) ganze Zahlen sein • zunächst wird der Ausdruck ausgewertet • dann wird der Fall ermittelt, dessen Konstante dem erhaltenen Wert entspricht • die Anweisungen dieses Falls und der folgenden Fälle (!) werden sukzessiv abgearbeitet, bis eine break-Anweisung erreicht wird • durch eine break-Anweisung wird die switch-Anweisung beendet (und die hierauf folgende Anweisung bearbeitet) • wird kein passender Fall gefunden, so werden die Anweisungen des default-Falls ausgeführt (sofern vorhanden) 57 while-Schleife Beispiel: while (x > y) x = x - y; berechnet x mod y (für x>0, y>0) • geeignet bei komplizierter Abbruchbedingung und wenn #Durchläufe unbekannt • Syntax: <Anweisung> ::= <while-Anweisung> | <do-while-Anweisung> . . . <while-Anweisung> ::= while ( <Ausdruck> ) <Anweisung> • Semantik: ? solange der (boolesche) Ausdruck true liefert, wird die Anweisung ausgeführt (ggfs. gar nicht) ? die Überprüfung findet vor Ausführung der Anweisung statt 58 do-while-Schleife Beispiel: do x = x - y; while (x > y); • geeignet, wenn #Durchläufe vorab unbekannt aber > 0 • Syntax: <do-while-Anweisung> ::= do <Anweisung> while <Ausdruck> ; • Semantik: ? führt die Anweisung aus, solange der (boolesche) Ausdruck true liefert (mindestens ein Mal) ? die Überprüfung findet nach Ausführung der Anweisung statt 59 Rekursion • oft lässt sich die Lösung eines Problems auf die Lösung eines gleichartigen (“kleineren”) Problems zurückführen • hierzu eignen sich rekursive (d.h. sich selbst aufrufende) Methoden Beispiel: Türme von Hanoi public static void turm(int n, int quelle, int ziel, int hilf){ if (n >= 1) { turm(n-1,quelle,hilf,ziel); System.out.println("von " +quelle+ " nach " +ziel); turm(n-1,hilf,ziel,quelle);}} Aufruf z.B. turm(2,1,2,3) 60 Weiterführung des Verwandschaft-Beispiels public class Person{ private String name; private Person vater; private Person mutter; private Person[] kind = new Person[10]; private int kinder = 0; public Person(String n, Person v, Person m){ name = n; vater = v; mutter = m; if (vater != null) vater.addKind(this); if (mutter != null) mutter.addKind(this);} public void addKind(Person neugeboren){kind[kinder++] = neugeboren;} //getName, getVater, getMutter wie bisher; vorfahren, nachkommen s.u. public static void main(String[] args){ // Person adam, eva, seth, lamech, noah wie bisher noah.vorfahren(); eva.nachkommen();} } 61 public void vorfahren(){ if (vater != null){ System.out.println(vater.getName()); vater.vorfahren();} if (mutter != null){ System.out.println(mutter.getName()); mutter.vorfahren();}} public void nachkommen(){ for(int i=0; i<kinder; i++){ System.out.println(kind[i].getName()); kind[i].nachkommen();}} 62 2.5 Objektorientierung Motivation • ein Großteil der IT-Kosten für Wartung • OO senkt Wartungskosten ? da zusammengehörige Codestücke nicht verstreut, sondern in Klassen zusammengefasst ? Änderungen bleiben dadurch oft lokal eingegrenzt • Entwicklungskosten lassen sich durch Wiederverwendung deutlich senken • OO unterstützt Wiederverwendung durch ? inhaltliche Strukturierung in Klassen ? Vererbung (s.u.) (→ Frameworks) 63 Motivation (Fortsetzung) • Softwareentwicklung erfolgt in Teams • Teamarbeit erfordert klare Schnittstellen • Klassen bieten klare Schnittstellen (Datenabstraktion, Kapselung) ? nur Entwickler kennen Implementierung (Attribute und Methodenrümpfe) ? Anwender kennen nur die Schnittstelle, d.h. Name, Parametertypen, Ergebnistyp und Spezifikation jeder öffentlichen (public) Methode ? Änderungen der Implementierung haben für die Anwender keine Auswirkungen, solange die Schnittstelle bleibt 64 Grundbegriffe der Objektorientierung • Klasse, Objekt, Attribut, Methode: vgl. 2.1 • Nachrichtenaustausch, Methodenaufruf: vgl. 2.1 • Vererbung, Vererbungspolymorphismus, spätes Binden: s.u. 65 Beispiel: Naive Programmierung ohne Kapselung public class Punkt{ public double x; public double y; public Punkt(double x, double y){ this.x = x; this.y = y;} } public class Strecke{ public Punkt anfang; public Punkt ende; public double laenge; public Strecke(Punkt p1, Punkt p2){ anfang = p1; ende = p2; laenge = Math.sqrt( Math.pow(p1.x - p2.x,2.0) + Math.pow(p1.y - p2.y,2.0));} } möglicher Anwendercode: ... Punkt p1 = new Punkt(1.0,1.0); Punkt p2 = new Punkt(4.0,5.0); Strecke s = new Strecke(p1,p2); System.out.println("Anfangspunkt: (" +p1.x+","+p1.y+")"); System.out.println("Endpunkt: (" +p2.x+","+p2.y+")"); System.out.println("Laenge: "+ s.laenge); ... Problem: • eine Änderung der Implementierung von Strecke oder Punkt erfordert umfangreiche Anpassungen des Anwendercodes 66 public class Punkt{ private double x; private double y; public Punkt(double x, double y){ this.x = x; this.y = y;} public double getX(){return x;} public double getY(){return y;} } public class Strecke{ private Punkt anfang; private Punkt ende; public Strecke(Punkt p1, Punkt p2){ anfang = p1; ende = p2;} public double getLaenge(){ return Math.sqrt( Math.pow(anfang.getX() - ende.getX(),2.0) + Math.pow(anfang.getY() - ende.getY(),2.0));} } Programmierung mit Kapselung möglicher Anwendercode: Punkt p1 = new Punkt(1.0,1.0); Punkt p2 = new Punkt(4.0,5.0); Strecke s = new Strecke(p1,p2); System.out.println("Anfangspunkt: (" +p1.getX()+","+p1.getY()+")"); System.out.println("Endpunkt: (" +p2.getX()+","+p2.getY()+")"); System.out.println("Laenge: " +s.getLaenge()); • für den Anwender ist egal, ? dass die Länge hier (aus Platzgründen?) nicht gespeichert, sondern jedesmal berechnet wird ? ob ein Punkt kartesisch oder mit Polarkoordinaten dargestellt wird 67 Vererbung • häufig ist eine Klasse A eine Spezialisierung (durch zusätzliche Attribute und Methoden) einer anderen Klasse B • um die vorhandenen Methoden wiederverwenden zu können, kann A als Unterklasse von B deklariert werden (mit extends) • Unterklassen erben Struktur und Verhalten ihrer Oberklassen • ggf. nicht (völlig) passende Methoden von A können in B überschrieben werden • die gleiche Methode kann auf Objekten der Ober- und Unterklasse ausgeführt werden (Vererbungs-Polymorphismus) • erst zur Laufzeit wird ermittelt, ob die Oberklassenoperation oder eine gleichnamige Unterklassenoperationen anzuwenden ist (→ spätes Binden) 68 Vererbung (Fortsetzung) • enthält die Klasse des Empfängerobjekts einer Nachricht keine entsprechende Methode, so wird die gleichnamige Methode der nächstgelegenen Oberklasse verwendet • in Java hat jede Klasse höchstens eine Oberklasse • (Ober-)Klassen ohne Instanzen heißen abstrakte Klassen • abstrakte Klassen erlauben die Zusammenfassung gemeinsamer Teilstrukturen und gemeisamen Verhaltens ähnlich aufgebauter (Unter-)Klassen 69 Beispiel: Vererbung public class Punkt{ protected double x,y; public Punkt(double x1, double y1){ x = x1; y = y1;} public class FarbPunkt extends Punkt{ // erbt x,y,getX,getY, // verschiebe,inRechteck protected int farbe; public void getX(){return x;} public FarbPunkt(double x1, double y1, int f){ super(x1,y1); farbe = f;} public void getY(){return y;} public void verschiebe(double dx, double dy){ x = x+dx; y = y+dy;} public boolean inRechteck( double minx, double maxx, double miny, double maxy){ return ((minx <= x) && (x <= maxx) && (miny <= y) && (y <= maxy));} } public getFarbe(){return farbe;} } 70 Anwendung: FarbPunkt p = new FarbPunkt(1.0,2.0,42); System.out.println("x: "+p.getX()); p.verschiebe(2.0,2.0); System.out.println(p.inRechteck(2.0,4.0,3.0,4.0)); System.out.println("Farbe: "+p.getFarbe()); • beachte: auf FarbPunkte auch Methoden der Oberklasse Punkt anwendbar • durch super(x1,y1) wird der Konstruktor der Oberklasse aufgerufen • Syntax: <Klasse> ::= <Sichtbarkeit> class <Klassenname> extends <Klassenname> { <Attribut-Deklaration>∗ <Methode>∗} <Sichtbarkeit> ::= protected | . . . • Attribute sollten i.d.R. die Sichtbarkeit protected (statt private) haben • dann werden sie von den Unterklassen geerbt (bei private nicht!) 71 Beispiel: Überschreiben public class Kreis extends Punkt{ protected double r; public Kreis(double x1,double y1,double r1){ super(x1,y1); r = r1;} public double flaeche(){return Math.PI*r*r;} public boolean inRechteck(){ double minx, double maxx, double miny, double maxy){ return ((minx <= x-r) && (x+r <= maxx) && (miny <= y-r) && (y+r <= maxy));} } 72 Vererbung im Klassendiagramm Punkt x y getX getY verschiebe inRechteck FarbPunkt farbe getFarbe Kreis r flaeche inRechteck • Vererbung dargestellt durch Pfeil mit offener Spitze von der Unterklasse zur Oberklasse 73 Unterklassen und Vererbung in Unterklasse möglich: • zusätzliche Attribute und Methoden • eingeschränkte Wertebereiche der Attribute • Änderung der Implementierung von Methoden der Oberklasse nicht möglich: • Weglassen von Attributen oder Methoden der Oberklasse • Änderung der Parameter von Methoden 74 Diskussion von Vererbung • Vorteil: Vererbung erlaubt die Wiederverwendung von Code • Nachteil: Vererbung untergräbt die Datenkapselung ? ? ? ? um Unterklassen zu verstehen, müssen Oberklassen bekannt sein Lokalität von Änderungen geht verloren Änderung von Oberklasse kann Änderung von Unterklassen erfordern daher: Vererbung behutsam einsetzen (möglichst) nur bei Spezialisierung ? insbesondere: Oberklassenoperationen möglichst nicht überschreiben • Vererbung vereinfacht das Testen nicht 75 Spätes Binden boolean alleImRechteck(Punkt[] punkte, double minx, double maxx, double miny, double maxy){ for(int i = 0; i < punkte.length; i++) if (!punkte[i].inRechteck(minx,maxx,miny,maxy)) return false; return true;} • punkte kann neben Objekten der Klasse Punkt auch Objekte einer Unterklasse (z.B. Kreis) enthalten • welche Methode inRechteck jeweils aufgerufen wird, hängt ab von der Klasse von punkte[i] • Auswahl der Methode zur Laufzeit (spätes Binden) • Vorteil: alleImRechteck muß beim Hinzufügen weiterer Unterklassen von Punkt nicht geändert werden 76 Schlechter Stil: Programmierung ohne spätes Binden boolean alleImRechteck(Object[] punkte, double minx, double maxx, double miny, double maxy){ for(int i = 0; i < punkte.length; i++) if (punkte[i] instanceof Kreis){ if (! ((Kreis) punkt[i]).inRechteck(minx,maxx,miny,maxy)) return false;} else if (punkte[i] instanceof Punkt) if (! ((Punkt) punkt[i]).inRechteck(minx,maxx,miny,maxy)) return false;} return true;} Problem: • alleImRechteck bezieht sich explizit auf alle hier möglichen Klassen • bei Änderung der Klassenhierarchie von Punkt: aufwändige Anpassung • Bemerkung: in Nicht-OO-Sprachen lässt sich dieser Stil nicht vermeiden 77 Klassenvariablen und Klassenmethoden • bisher: Methoden angewendet auf Objekte • nun auch: an Klasse (statt Instanzen) gebundene Variablen und Methoden • Klassenvariablen und -methoden werden durch static gekennzeichnet public class Punkt{ static int num punkte = 0;// Klassenvariable protected double x,y; // Attribute public void Punkt(double x1, double y1){ x = x1; y = y1; num punkte++;} public static Punkt lexgeq(Punkt p, Punkt q){ // Klassenmethode if ((p.x >= q.x) || ((p.x == q.x) && (p.y >= q.y))) return p; else return q;} } 78 Verwendung von Klassenmethoden und Klassenvariablen Beispiele: Punkt p = new Punkt(1.0,2.5); Punkt q = new Punkt(1.0,3.2); Punkt v = Punkt.lexgeq(p,q); System.out.println("pi:"+Math.PI); • System.out und Math.PI sind (public) Klassenvariablen der Bibliotheksklassen System bzw. Math •Syntax: <Ausdruck> ::= <Klassenname>.<Klassenvariablenname> | . . . | <Klassenname>.<Klassenmethodenname>((<Ausdruck> (, <Ausdruck>)∗)?) 79 Interfaces • einige OO-Sprachen (z.B. C++) erlauben, dass eine Klasse von mehreren Oberklassen erbt → ggfs. Namenskonflikte • in Java: ? nur eine Oberklasse (implizit Object) ? Simulation von Mehrfachvererbung durch Implementation von Interfaces • Interface: = ˆ abstrakter Klasse mit ausschließlich abstrakten Methoden • zur Implementierung eines Interfaces müssen alle zugehörigen Methoden implementiert werden 80 Beispiel: Interfaces public class Wasserfahrzeug{ protected double tiefgang; public Wasserfahrzeug(double tg){tiefgang = tg;} public double getTiefgang(){return tiefgang;}} public interface Flugobjekt{ public abstract void setFlughoehe(double h); public abstract double getFlughoehe();} public class Wasserflugzeug extends Wasserfahrzeug implements Flugobjekt{ protected double flughoehe = 0.0; public Wasserflugzeug(double tg){super(tg);} public void setFlughoehe(double h){flughoehe = h;} public double getFlughoehe(){return flughoehe;}} 81 Verwendung: Wasserflugzeug w = new Wasserflugzeug(0.52); Flugobjekt f = w; f.setFlughoehe(f.getFlughoehe() + 100.0); System.out.println(w.getFlughoehe()+","+ w.getTiefgang()+","+ f.getFlughoehe()); Wasserfahrzeug tiefgang getTiefgang setTiefgang <<interface>> Flugobjekt getFlughoehe setFlughoehe Wasserflugzeug flughoehe 82 Syntax-Erweiterungen <Interface-Deklaration> ::= <Sichtbarkeit> interface <Interface-Name> { <Methode>∗} <Klasse> ::= <Sichtbarkeit> abstract? class <Klassenname> extends <Klassenname> implements <Interface-Name> (, <Interface-Name>)∗ { <Attribut-Deklaration>∗ <Methode>∗} <Methode> ::= <Sichtbarkeit> abstract (<Typ>| void) Methodenname (<Parameterliste>) ; | . . . 83 2.6 Graphische Benutzerschnittstellen • Benutzerschnittstellen früher: aktiver Frage-/Antwort-Dialog • heute: graphische Benutzerschnittstelle (GUI) bestehend aus Fenstern, Menüs, Knöpfen, Texteingabefeldern, . . . • ereignisorientiert: jede GUI-Komponente wartet auf das Eintreten relevanter Ereignisse (z.B. Mausklick, Tastendruck, Knopfdruck), interpretiert sie und bearbeitet sie • die Art und Reihenfolge der Ereignisse ist nicht vorhersehbar • daher keine aktive Dialogsteuerung sondern passive Ereignisbehandler, die durch ein Ereignis aktiviert werden 84 Framework • objektorientierte Klassenbibliothek • stellt aufgabenbezogene Grundfunktionalität (System von Klassen) bereit • anpassbar durch Ergänzung eigener Unterklassen • Beispiele für GUI-Frameworks: Java AWT, Swing (mächtiger; basiert auf AWT) 85 Erstellung einer graphischen Benutzerschnittstelle • Anpassung eines GUI-Frameworks durch Bereitstellung eigener Unterklassen • Aufbau eines Objektnetzes, das alle GUI-Komponenten und die Fachkonzeptklassen verknüpft • insbesondere: Ereignisbehandler implementieren • statt textuell kann die GUI auch mit einer visuellen Entwicklungsumgebung (z.B. Forte) graphisch am Bildschirm entworfen und der zugehörige Java-Code automatisch generiert werden 86 Beispiel: Zähler Zählerstand: Weiterzählen import java.awt.*; import java.awt.event.*; Zurücksetzen Beenden public class Zaehler extends Frame implements ActionListener{ protected protected protected protected TextField zstand Button weiter Button zurueck Button ende = = = = new new new new TextField("0",4); Button("Weiterzaehlen"); Button("Zuruecksetzen"); Button("Beenden"); public Zaehler(){... s.u. ...} public void actionPerformed(ActionEvent event){... s.u. ...} static public void main(String[] args){ new Zaehler();} } 87 public Zaehler(){ setBounds(400,400,250,300); setLayout(new FlowLayout()); setBackground(Color.white); add(new Label("Zaehlerstand: ")); add(zstand); add(weiter); add(zurueck); add(ende); weiter.addActionListener(this); zurueck.addActionListener(this); ende.addActionListener(this); pack(); show();} public void actionPerformed( ActionEvent event){ if (event.getSource() == weiter){ int wert = Integer.parseInt( zstand.getText()); zstand.setText(""+(wert+1));} else if (event.getSource() == zurueck) zstand.setText("0"); else if (event.getSource() == ende) dispose();} 88 Beispiel: SimpelBank mit graphischer Benutzerschnittstelle Wilkommen bei der SimpelBank Kontonr. Geheimzahl Was wollen Sie tun? Beenden Überweisen Ein-/Auszahlen Überweisung Konto des Empfängers Beenden Ein-/Auszahlung Betrag Betrag ok ok • Dialog beschrieben durch endlichen Automaten (UML Statechart) 89 Klasse MeinFenster import java.awt.*; import java.awt.event.*; public abstract class MeinFenster extends Frame{ public static void put(Container ctr, Component comp, int x, int y, int w){ GridBagLayout g = (GridBagLayout) ctr.getLayout(); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.BOTH; c.gridx = x; c.gridy = y; c.gridwidth = w; c.gridheight = 1; c.weightx = 0.0; c.weighty = 0.0; ctr.add(comp); g.setConstraints(comp,c);} public MeinFenster(String titel) setTitle(titel); setBounds(400,400,250,300); setLayout(new GridBagLayout()); setBackground(Color.white); } 90 Klasse Willkommen import java.awt.*; import java.awt.event.*; public class Willkommen extends MeinFenster implements ItemListener{ private class UeberweisenFenster extends MeinFenster implements ActionListener{...s.u. ...} private class EinAuszahlFenster extends MeinFenster implements ActionListener{...analog zu UeberweisenFenster...} protected TextField konto = new TextField(8); protected TextField pin = new TextField(8); protected Choice funktion = new Choice(); public Willkommen(){... s.u. ...} public void itemStateChanged(ItemEvent event){... s.u. ...} static public void main(String[] args){ new Konto(1001,50); new Konto(990,50); new Willkommen();} } 91 public Willkommen(){ super("Willkommen bei der SimpelBank"); funktion.addItem("Beenden"); funktion.addItem("Ueberweisen"); funktion.addItem("Einzahlen"); funktion.addItem("Auszahlen"); put(this, put(this, put(this, put(this, put(this, new Label("Kontonr.: "), konto, new Label("Geheimzahl: "), pin, funktion, funktion.addItemListener(this); pack(); show();} 0,0,1); 1,0,2); 0,1,1); 1,1,2); 1,2,1); 92 public void itemStateChanged(ItemEvent event){ String Operation = funktion.getSelectedItem(); if (Operation.equals("Beenden")) {dispose(); return;} Konto kunde = Konto.suche(Integer.parseInt(konto.getText())); if (kunde == null) {konto.setText("falsch"); return;} if (! kunde.checkPin(Integer.parseInt(pin.getText()))){ pin.setText("falsch"); return;} if (Operation.equals("Ueberweisen")) new UeberweisenFenster(kunde); else if (Operation.equals("Einzahlen")) new EinAuszahlFenster(kunde,true); else if (Operation.equals("Auszahlen")) new EinAuszahlFenster(kunde,false);} 93 Klasse UeberweisenFenster public class UeberweisenFenster extends MeinFenster implements ActionListener{ protected protected protected protected TextField empfaenger = new TextField(8); TextField betrag = new TextField(8); Button ok = new Button("ok"); Konto kunde; public UeberweisenFenster(Konto kunde){ super("Ueberweisung"); this.kunde = kunde; put(this, new Label("Kontonr. des Empfaengers: "), 0,0,1); put(this, empfaenger, 1,0,2); put(this, new Label("Betrag: "), 0,1,1); put(this, betrag, 1,1,2); put(this, ok, 1,2,1); ok.addActionListener(this); pack(); show();} 94 public void actionPerformed(ActionEvent event){ if (event.getSource() == ok){ int wert = Integer.parseInt(betrag.getText()); Konto ziel = Konto.suche(Integer.parseInt(empfaenger.getText())); if (ziel == null){empfaenger.setText("?"); return;} Ueberweisung ueberweisung = new Ueberweisung(kunde,ziel,wert); dispose();}} } 95 Kontroll-Klasse Ueberweisung public class Ueberweisung{ public Ueberweisung(Konto kunde, Konto ziel, int betrag){ kunde.setSaldo(kunde.getSaldo()-betrag); ziel.setSaldo(ziel.getSaldo()+betrag);} } • sinnvoll: Ablauf jedes Geschaftsvorfalls in eigener Klasse kapseln • so bleiben auch Änderungen der Funktionalität lokal begrenzt 96 Datenhaltungsklasse Konto public class Konto{ protected static final int maxAnzahl = 10000; protected static int anzahl = 0; protected static Konto[] konten = new Konto[maxAnzahl]; //in Praxis: Datenbank protected int nr; protected int pin; protected int saldo; public Konto(int nr, int betrag){ konten[anzahl++] = this; pin = nr % 9735; saldo = betrag;} public public public public int getNr(){return nr;} int getSaldo(){return saldo;} void setSaldo(int betrag){saldo = betrag;} boolean checkPin(int eingabe){return pin == eingabe);} public static Konto suche(int knr){ for (int i=0; i<anzahl; i++) if (konten[i].getNr() == knr) return konten[i]; return null;} } this.nr = nr; 97 Bemerkungen: • eine Methode mit Ergebnistyp void kann durch return; verlassen werden • bei einem Array von Objekten (z.B. konten) werden zunächst nur null-Referenzen angelegt • jedes (Konten-)Objekt muss separat angelegt werden • Hilfsklasse (wie UeberweisenFenster) z.B. als innere Klasse realisierbar • ist sie private oder protected, dann ist sie von außen nicht sichtbar • ist sie nicht static, kann sie auf die Attribute des Objekts der umgebenden Klasse zugreifen 98 Innere Klassen • eingeschachtelte static-Klassen über Pfad verfügbar (sofern: public) Bsp.: List.ListElem • in eingeschachtelter nicht-static-Klasse (“member class”): Attribute des zugehörigen Objektes der umgebenden Klasse zugreifbar • lokale Klasse: ? in Methodenrumpf eingeschachtelte Klasse ? Zugriff auf private-Attribute des zugehörigen Objektes der umgebenden Klasse wie bei Member-Klasse ? ggfs. anonym • typische Verwendung: Ereignisbehandler-Klassen 99 Beispiel: Listen public class List { private static class ListElem{ ListElem succ; // Rekursion! Object content; ListElem(Object cont, ListElem next){ succ = next; content = cont;}} protected ListElem head = null; protected ListElem current = null; public public public public public public public public } List(){super();} boolean isEmpty() { return head == null; } Object getValue(){return current.content;} void setValue(Object val){current.content = val;} void first(){current = head;} void next(){current = current.succ;} boolean atEnd(){return (current == null);} void insertFirst(Object item){head = new ListElem(item, head);} 100 2.7 Ausnahmebehandlung • umständliche naive Fehlerbehandlung: ? jede Methode liefert Information über erfolgreiche Ausführung ? nach Aufruf: Fehlercheck und ggfs. Reaktion • besser: bei Fehler Exception auslösen und an geeigneter Stelle abfangen • kein umständliches Durchreichen von Erfolgsinformationen • neben benutzerdef. Exception-Klassen gibt es systeminterne, z.B. ArithmeticException, ClassCastException, NullPointerException 101 Syntaxerweiterungen <Anweisung> ::= try <Anweisungsfolge> (catch (<Klassenname> <Variablenname> <Anweisungsfolge>)+ (finally <Anweisungsfolge>)? | throw <Ausdruck>? ; <Anweisungsfolge> ::= { <Anweisung>∗ } <Methode> ::= <Sichtbarkeit> abstract? (<Typ>| void) Methodenname (<Parameterliste>) (throws <Klassenname> (, <Klassenname>)∗)? . . . Rumpf wie bisher . . . 102 Semantik • throw löst eine Exception aus • hinter throw: Instanz (einer Unterklasse) von Exception • eine Methode muss alle explizit ausgelösten Exceptions, die in ihrem Rumpf (ggf. indirekt) auftreten können, abfangen oder hinter der Parameterliste angeben • durch catch kann eine “typmäßig passende” Exception abgefangen werden, die in der hinter try folgenden Anweisungsfolge ausgelöst wurde • die auf catch folgende Anweisungsfolge wird dann ausgeführt • mehrere catch-Böcke (für verschiedene Exceptions) sind möglich • an “innerster passender” Abfangstelle wird die Programmausführung fortgesetzt • falls finally-Block: immer ausgeführt (mit oder ohne Exception) 103 Beispiel: naive Fehlerbehandlung ohne Exception import java.lang.*; class Foo{ Foo(){ List liste = new List(); for (int i=0; i<10; i++) liste.insertFirst(new Integer(i%4)); liste.first(); if (!listDiv(liste)) System.out.println("Fehler");} static boolean listDiv(List l){ if (l.atEnd()) return true; int val = ((Integer) l.getValue()).intValue(); if (val == 0) return false; else {l.setValue(new Integer(100/val)); l.next(); return listDiv(l);} } 104 sonstige Bemerkungen zum Beispiel: • um universell verwendbar zu sein, verwendet List Elemente vom Typ Object • dadurch: Simulation von parametrischem Polymorphismus • Basiswerte sind keine Objekte • sie müssen mit “Wrapper-Klassen” verpackt werden (z.B. int → Integer) • da Listenelemente vom Typ Object sind, müssen sie zur Weiterverarbeitung i.a. in ihren ursprünglichen Typ zurückverwandelt werden (Casting) • Beispiel: (Integer) l.getValue() • fehlen Sichtbarkeitsangaben, so wird die Sichtbarkeit package eingestellt • hierbei sind die Bezeichner in allen Klassen des betreffenden Pakets bekannt 105 Beispiel: Auslösen von Exceptions class DivisionException extends Exception{ public DivisionException(String s){super(s);}} class IntException extends Exception{ public IntException(String s){super(s);}} class Foo{ Foo(){... s.u....} static void listDiv(List l) throws DivisionException,IntException{ if (!l.atEnd()) return; if (! (l.getValue() instanceof Integer)) throw new IntException("keine Integerzahl"); int val = ((Integer) l.getValue()).intValue(); if (val == 0) throw new DivisionException("Dividend 0"); l.setValue(new Integer(100/val)); l.next(); listDiv(l);} } 106 Beispiel: Abfangen von Exceptions Foo(){ List liste = new List(); for (int i=0; i<10; i++) liste.insertFirst(new Integer(i%4)); liste.first(); try {listDiv(liste);} catch (DivisionException e1){System.out.println(e1.getMessage());} catch (IntException e2){System.out.println(e2.getMessage());} finally{System.out.println("Ende");}}