Ein erster Blick auf ein Java-Programm 14 Sparkonto kontonummer: Zeichenkette kontoinhaber: Zeichenkette kontostand: Ganzzahl speichern kontonummer speichern kontoinhaber speichern kontostand ausgabe kontonummer ausgabe kontoinhaber ausgabe kontostand bearbeiten kontobewegung Klassenname Attribute mit Datentyp Methoden Bei der objektorientierten Programmentwicklung ist es üblich, die Klasse in einem UML-Klassen-Diagramm zu beschreiben. UML steht für Unified Modeling Language. Im oberen Teil des Diagramms steht der Name der Klasse. Darunter werden die Attribute notiert. Im unteren Teil werden die Methoden aufgeführt. In der Bankpraxis weist ein Sparkonto noch weitere Attribute und Methoden auf. Zur Erklärung, was eine Klasse ist und wie sie in Java umgesetzt wird, mag die obige Festlegung reichen. 1.2.2 Umsetzung in eine Java-Klasse Das obige UML-Klassendiagramm lässt sich relativ leicht in eine Java-Klasse umsetzen. Klasse Sparkonto [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18] [19] [20] 0804_01.indb 14 public class Sparkonto { private String kontonummer; private String kontoinhaber; private int kontostand; public Sparkonto() { } public void setKontonummer (String ktonr) { kontonummer = ktonr; } public void setKontoinhaber (String sparer) { kontoinhaber = sparer; } public void setKontoStand (int betrag) { kontostand = betrag; } public String getKontonummer() { return kontonummer; } // Konstruktor 27.03.2012 11:19:59 28 Programmierumgebung kann der Compiler das Maschinenprogramm erzeugen. Dieses Programm muss dann der Programmierer auf logische Richtigkeit testen. Aus der Abbildung wird zum einen deutlich, dass ein Programm nicht „so mal eben“ entwickelt werden kann. Zum anderen deuten die Rückbezüge der Korrekturpfeile an, dass der Quellcode wie auch der Klassenentwurf oftmals korrigiert werden müssen, bis das Programm ordnungsgemäß arbeitet. Für die „komfortable“ Eingabe und Compilierung des Quellcodes ist eine integrierte Entwicklungsumgebung unabdingbar. Sie fordert die erforderlichen Systeminformationen vom Programmierer an und erleichtert ihm so die Arbeit. Zur Eingabe eines neuen Programms legen Sie zunächst ein Projekt an. Darunter versteht BlueJ ein Verzeichnis, in das später alle Dateien, die zu dem Projekt gehören, gespeichert werden. Im vorliegenden Fall wurde im Hauptverzeichnis C: das Unterverzeichnis javalernen angelegt. Als Projektnamen wurden die Kapitelnummern, zum Beispiel Kapitel2 oder AufgabenKapitel2 gewählt. Um ein Projekt anzulegen klicken Sie auf Projekt/Neues Projekt. In dem Dialogfenster Neues Projekt legen Sie den Ordner sowie den Projektnamen fest. Anschließend klicken Sie die Schaltfläche Neue Klasse und geben in dem Dialogfenster den Namen der Klasse ein, hier zum Beispiel Sparkonto. 0804_01.indb 28 27.03.2012 11:20:02 Programmierumgebung 38 Sie sehen, die Eingabe wurde korrekt übernommen. Bei den beiden String-Variablen wird null angezeigt, da noch nichts eingegeben wurde. Der Zustand ist noch undefiniert. Die Angabe null darf nicht verwechselt werden mit der Zahl 0. Schließen Sie das Inspektionsfenster und rufen als nächstes die Methode kontobewegung auf. Dort geben Sie in das Dialogfenster zum Beispiel den Betrag 444 ein. Das Ergebnis können Sie sich mittels der Methode getKontoStand() anzeigen lassen. Sie sehen, dass die Methode korrekt arbeitet. 2.6.2 Gesamttest Auch wenn ein Programm ohne Fehlermeldung läuft und „richtige“ Ergebnisse anzeigt, kann es aufgrund einer falschen Befehlsreihenfolge oder einer nicht korrekten Anweisung fehlerhaft sein. Um bei mathematischen Problemstellungen die logische Korrektheit zu überprüfen, empfiehlt es sich, vor dem Programmtest Zahlenbeispiele „manuell“ durchzurechnen. Bei anschließenden Programmläufen werden die Testwerte in den Rechner eingegeben. Stimmen jeweils die Ergebnisse überein, kann man im Allgemeinen davon ausgehen, dass das Programm in Ordnung ist. Die Suche nach logischen Fehlern kann sehr mühselig und zeitraubend sein. Hier hilft BlueJ mit einem Debugger. Unter einem Debugger versteht man ein Programm, das zur Diagnose eines Programms oder auch zur Fehlersuche eingesetzt wird. In dem Wort steckt der Begriff bug, der eigentlich im Englischen Wanze bedeutet und als Synonym für einen Fehler in einem technischen System gebraucht wird. Mit Hilfe eines Debuggers kann der Programmierer das Programm schrittweise durchlaufen und den Inhalt von Variablen prüfen. Zum Testen eines Programms aktivieren Sie den Debugger im Menü Ansicht oder mit Hilfe der Tastenkombination <Strg><D>. Anschließend setzen Sie im Quellcode der main-Methode einen Haltepunkt, hier am besten vor die Anweisung, in der das Objekt der Klasse Sparkonto erzeugt wird. Dazu klicken Sie in die Vorspalte. 0804_01.indb 38 27.03.2012 11:20:04 Elemente eines Java-Programms 61 Da in Kontofuehrung2UI die Vereinbarung in der Methode run() erfolgt, ist die Objektvariable einsKonto nur zur Laufzeit dieser Methode gültig. Außerhalb existiert sie nicht. Die Objektvariable einsKonto wird aber in verschiedenen Methoden benötigt. Deshalb muss sie als Instanzvariable vereinbart werden. Sie ist somit zu Beginn der Klassendefinition zu deklarieren: public class Kontofuehrung2UI { Sparkonto2 einsKunde, zweiKunde; 3.6 // Objektvariablen Methoden überladen Methoden haben in Java eine sogenannte Signatur. Sie besteht aus dem Namen der Methode sowie aus der Anzahl und Reihenfolge der Parameterdatentypen. Diese Angaben kennzeichnen eine Methode. Folglich werden namensgleiche Methoden in einer Klasse, die eine unterschiedliche Signatur aufweisen, als unterschiedliche Methoden erkannt. Beispiel a) public double berechne ( int a, double b, int c) b) public double berechne (double a, int b, double c) c) public void berechne (double a, int b) Alle drei Methoden tragen den Namen berechne. Es sind unterschiedliche Methoden, da sie sich hinsichtlich des Rückgabewertes, Anzahl der Parameter wie auch der Datentypen der Parameter unterscheiden. Bei einem Methodenaufruf der Form System.out.print (objektvariable.berechne(17.5, 6, 58.44) ) würde die Methode b) aufgerufen, weil da die Parameter in Anzahl und Datentyp übereinstimmen. Das Java-System würde die Methode a) nicht aufrufen, weil dort die Datentypen der Parameter nicht „passen“. Die Methode c) wird in einem Ausgabebefehl ignoriert, weil sie keinen Rückgabewert aufweist. Ein weiteres Beispiel: Es sollen die Zinsen eines Guthabens berechnet werden. Dazu wurde die Klasse Sparkonto3 gebildet. In der Methode [18] public double getKontostand (int tage) { wird ein Zinssatz angewendet, der in einer Instanzvariablen festgelegt wurde. Als aktueller Parameter wird lediglich eine Ganzzahl für die Anzahl der Zinstage erwartet. In der gleichnamigen Methode [23] 0804_01.indb 61 public double getKontostand (int tage, double zinssatz) { 27.03.2012 11:20:08 128 Auswahlanweisung Darstellung der zweiseitigen Auswahl in Form eines Programmablaufplans: Die zweiseitige Auswahl ist in dem Struktogramms farblich hervorgehoben. 0804_01.indb 128 27.03.2012 11:20:20 Auswahlanweisung 139 } public static void main (String args[]) { Kdrabatt1 kr = new Kdrabatt1(); kr.Rabattberechnen(); } } Der Programmlauf führt zu folgenden Ergebnissen Eingabe Kundennummer 4000 Rabattsatz 10 Eingabe Kundennummer 6000 Rabattsatz 0 Eingabe Kundennummer 2000 Rabattsatz 12 Ergebnis falsch! Richtig wäre 0 Ergebnis falsch! richtig wäre 10 Ergebnis richtig! 12 Die Ergebnisse sind mit Ausnahme des letzten falsch. Somit ist das Progamm fehlerhaft, auch wenn es optisch richtig aussieht. Der Grund dafür: das else in (3) wird der Regel entsprechend auf das unmittelbar voraufgehende if in (2) bezogen. Um das else in (3) dem if (kdnr <= 5000) in Anweisung (1) zuordnen zu können, muss man sich eines „Tricks“ bedienen. Man schiebt hinter die Anweisung rabsatz = 12; in Anweisung (4) noch ein else mit einer Leeranweisung ein. import java.util.Scanner; public class Kdrabatt2 { int kdnr, rabsatz = 0; public Kdrabatt2() { } public void Rabattberechnen() { Scanner sc = new Scanner (System.in); System.out.print ("Eingabe Kundennummer "); kdnr = sc.nextInt(); if (kdnr <= 5000) if (kdnr < 3000) rabsatz = 12; else; // eingeschobene Leeranweisung else rabsatz = 10; System.out.println("Rabattsatz "+ rabsatz); } //(1) //(2) public static void main (String args[]) { 0804_01.indb 139 27.03.2012 11:20:21 Wiederholungsanweisungen 168 In Java gibt es folgende Wiederholungsanweisungen: • while • do while • for 6.1 Kopfgesteuerte Schleife: while 6.1.1 Funktionsweise Die while-Schleife sei am Beispiel der Durchschnittsrechnung dargestellt. Dabei werden die eingegebenen Werte summiert. Nach Abschluss der Eingabe wird die Summe durch die Anzahl der eingegebenen Werte dividiert. Der Grob-Algorithmus, dargestellt im Pseudo-Code ist leicht einsehbar: Algorithmus Durchschnitt Summe = 0 Eingabe Zahl Solange Zahl > 0 Summenbildung Anzahl erhöhen Eingabe Zahl Durchschnittsberechnung Ausgabe Durchschnitt Ende In Form eines Struktogramms stellt sich der Algorithmus wie folgt dar. 0804_01.indb 168 27.03.2012 11:20:26 Objektsammlungen 213 [54] // :::::::::::::main:::::::::::::::::::::::::::: [55] public static void main(String args[]) { [56] Umsatz3 ums3 = new Umsatz3(); [57] ums3.run(); [58] } [59] } Die Beschriftung für die beiden Dimensionen wurde in den Feld-Konstanten DEPA und QUARTAL notiert. Sie verfügen damit über die Methode length. Diese Methode liefert die Anzahl der Elemente eines Objektes. Somit können Sie DEPA.length und QUARTALE.length zur Instanziierung von umsatzTabelle (Zeile 6) wie auch zur Schleifensteuerung verwenden. for (int s = 0; s < DEPA.length; s++) for (int z = 0; z < QUARTALE.length; z++) Auf diese Weise stellt man sicher, dass die Feld-Grenzen nicht überschritten werden. 7.2 Felder für Objekte In Feldern können nicht nur einfache Datentypen, sondern auch Objekte angelegt und verwaltet werden. Problem Für ein Hotel ist eine (vereinfachte) Zimmerverwaltung zu erstellen. Es verfügt über fünf Zimmer. Jedes Zimmer sei gekennzeichnet durch die Eigenschaften Zimmernummer, Preis und Belegt-Status. Hotelzimmer zimmernummer: int preis: double belegt: boolean zimmerEinrichten() zimmerDrucken() /** * Eigenschaften des Hotelzimmers. */ public class Hotelzimmer { int zimmerNr; double preis; boolean belegt = false; public Hotelzimmer() { } 0804_01.indb 213 27.03.2012 11:20:33 Objektsammlungen 216 Das Programm läuft in der Weise ab, dass in der main-Methode das Objekt Hotel1 angelegt und dort die Methode run() aufgerufen wird. In der run()-Methode wird die Methode einrichten() gestartet, in der die Hotelzimmer instanziiert und mit Werten versorgt werden. Anschließend erfolgt der Ausdruck der Zimmerdaten. Die Ausgabe der Überschrift erfolgt in der Methode zimmerListeDrucken(). Die Datenausgabe wird in der Methode zimmerDrucken() im Objekt Hotelzimmer vorgenommen. Die Steuerung der Datenausgabe erfolgt durch die for-Anweisung in der Methode zimmerListeDrucken. 7.3 ArrayList 7.3.1 ArrayList bei vordefiniertem Objekttyp Ein Feld kann bei solchen Problemstellungen eingesetzt werden, bei denen die Anzahl der Elemente zu Beginn der Programmnutzung bekannt sind. Das ist im Beispiel der Hotelzimmer wohl gegeben. Wenn es aber darum geht, die Daten von Objekten zu erfassen, deren Anzahl nicht im Vorhinein bekannt ist, ist das Feld nicht gut geeignet. Hier ist dann eine Klasse gefragt, die die Daten dynamisch verwalten kann. Problem In „unserem“ Hotel werden Veranstaltungen durchgeführt, zu denen man sich vorher anmelden muss. Die Anmeldungen erfolgen telefonisch. Zu erfassen ist der Name des Gastes. Der Veranstalter braucht neben der Anzahl der angemeldeten Personen eine Liste mit der Reihenfolge der Anmeldungen sowie eine alphabetisch sortierte Liste. Da sich Personen auch wieder abmelden können, muss es die Möglichkeit geben, Namen aus der Liste zu entfernen. Diese Programmanforderungen sind mit einem Feld-Objekt nicht zu erfüllen. Hier bietet sich die Klasse ArrayList an. Sie ist in dem Paket java.util enthalten. Das nachstehende Programmlisting zeigt am Beispiel der Klasse Gaesteliste, wie die Klasse ArrayList verwendet wird. 0804_01.indb 216 27.03.2012 11:20:34 Vererbung, Polymorphie und Interface 250 8 Vererbung, Polymorphie und Interface 8.1 Konzept Die Vererbung ist eines der wichtigsten Konzepte einer objektorientierten Programmiersprache. Sie sei am Beispiel einer vereinfachten Lohnabrechnung dargelegt. Beispiel Bei der Lohnberechnung muss zwischen Angestellten und Arbeitern unterschieden werden. Die Angestellten erhalten ein feststehendes Monatsgehalt. Bei den Arbeitern bildet sich der Lohn aus der Multiplikation von Stundenlohn und Anzahl der Arbeitsstunden. Der Stundenlohn sei hier mit 25,00 Euro vorgegeben. Die Kinderzulage, die der Betrieb freiwillig zahlt, richtet sich nach der Anzahl der Kinder. Pro Kind beträgt die Zulage 50,00 Euro. Man könnte nun zwei Klassen bilden: Angestellter Arbeiter Name Kinderzahl Monatsgehalt Name Kinderzahl Stundenlohn berechneKinderzulage berechneMonatslohn berechneKinderzulage berechneMonatslohn Zwei fast gleiche Datensätze einzurichten und später programmtechnisch zu betreuen ist aufwändig. Um den Aufwand zu verringern, bildet man eine eigene Klasse, die die gemeinsamen Elemente aufnimmt. Diese Klasse nennt man Oberklasse bzw. Superklasse. Die übrigen Elemente verbleiben in der bisherigen Klasse, die dann als Unterklasse oder Subklasse bezeichnet wird. In unserem Beispiel könnte sich folgendes UML-Diagramm ergeben. Mitarbeiter - Name - berechneKinderzulage Angestellter Arbeiter Monatsgehalt Stundenlohn berechne Monatslohn berechneMonatslohn 0804_01.indb 250 27.03.2012 11:20:39 Exeptions – Behandlung von Ausnahmen 281 In diesem Zusammenhang sei auf das sogenannte Auto-Boxing hingewiesen. Danach lässt Java eine Wertzuweisung eines einfachen Datentyps auf ein Objekt der korrespondierenden Wrapper-Klasse zu. Eine umgekehrte Wertzuweisung ist in diesem Fall auch zulässig. 9.1.4 Ablauflogik der try-catch-Konstruktion In dem Einführungsbeispiel ist ein Eingabestring in eine double-Zahl umzuwandeln. Das ist ein kritischer Vorgang. Wurde ein Buchstabe mit eingegeben, tritt ein Laufzeitfehler auf. In der Folge bricht das Programm ab. Ebenfalls stürzt das Programm ab, wenn bei einer Dezimalzahl ein Komma anstatt eines Punktes als Dezimaltrennzeichen verwendet wurde. Wurde das System auf den deutschen Zeichensatz eingestellt, tritt der Fehler im umgekehrten Fall auf. Durch try wird die Ausführung der Zuweisung unter Beobachtung gestellt. [3 [4] [5] try { zahl = Double.parseDouble(ein); } Falls bei dieser Operation ein Fehler auftritt, werden die Anweisungen des catch-Blocks bearbeitet. Anschließend werden die Methoden ausgeführt, die dem catch-Block folgen. Trat im try-Block kein Fehler auf, wird der catch-Block übersprungen. Die nachstehende Abbildung zeigt die Ablauflogik der try-catch-Konstruktion. Die try-catch-Konstruktion ist im Prinzip einfach. Ein großes Problem besteht jedoch darin, dass Java nicht zu der Programmstelle zurückkehrt, an der der Fehler aufgetreten ist. Dafür müssen Sie durch eine entsprechende Programmierung selber sorgen. Das sei 0804_01.indb 281 27.03.2012 11:20:44 Benutzeroberflächen 315 Komponenten hinzufügen NetBeans erzeugt nun die Klasse Rechnen sowie eine Grundmaske für RechnenGUI. Diese Maske stellt eine Art Container dar, in dem die Komponenten wie Textfelder, Beschriftungselemente und Schaltflächen angeordnet werden. Neben dem Container wird ein Palettenfenster eingeblendet, in dem eine Vielzahl von Fenster-Elementen bereitgehalten wird. Die Elemente sind in mehrere Gruppen eingeteilt, wie zum Beispiel Swing Containers und Swing Controls. Im unteren Bereich ist ein Properties-Fenster angesiedelt. In der deutschen NetBeans-Version wird es mit Eigenschaften bezeichnet. Die Grundmaske lässt sich vergrößern und verkleinern. Probieren Sie es ruhig. Fahren Sie mit dem Cursor über den rechten bzw. unteren Randbereich. Sobald sich der Cursor in einen Doppelpfeil verwandelt, können Sie die Grundmaske nach rechts bzw. unten vergrößern. Die Benutzeroberfläche soll einen Namen haben. Klicken Sie deshalb mit der rechten Maustaste in die Grundfläche. Damit rufen Sie ein Kontext-Menü auf, in dem Sie auf den Auswahlpunkt Properties klicken. Es öffnet sich ein Dialogfenster JFrame. Im Register Properties tragen Sie in der title-Zeile Rechner ein und beenden den Dialog mit einem Klick auf <Close>. In die Grundmaske werden nun die Komponenten eingebaut. Zuerst wird ein Rahmen gezogen. Dazu klicken Sie auf die Komponente Panel im Swing-Container und ziehen den Rahmen auf die Grundmaske in die linke obere Ecke. Der Feldrahmen ist aktiviert. Vergrößern Sie ihn auf etwa die Hälfte der Grundmaske und klicken im PropertiesFenster in der Border-Zeile auf den <…>-Button. Damit rufen Sie das Jpanel-BorderFenster auf, in dem Sie auf die Zeile Titled Border klicken. Es wird dann ein Textfeld Title Border eingeblendet, in das Sie den Begriff Rechnen eingeben. 0804_01.indb 315 27.03.2012 11:20:49 Benutzeroberflächen 319 Die obige Abbildung gibt einen Überblick über die Vielzahl von Ereignissen, die programmtechnisch abgefragt werden können. 11.3 Entwicklung einer komplexen Anwendung 11.3.1 Problemstellung Die Entwicklung eines Java-Programms für eine komplexe Aufgabenstellung erfolgt am Beispiel des Paketdienstes, das im letzten Kapitel behandelt wurde. Dort wurde die Erfassung und Speicherung der Daten mit Hilfe zweier Klassen durchgeführt: Klasse Paket sowie einer Klasse zur Bearbeitung der Daten. Bei Einsatz einer graphischen Bedienoberfläche arbeitet man mit drei Klassen. Eine Klasse befasst sich mit der Bildschirmmaske und ihren Komponenten. Sie reicht die eingegebenen Daten zur Verarbeitung an die Steuerungsklasse. Diese Klasse verarbeitet die Daten. Die dritte Klasse beschreibt das Datenmodell. Bezogen auf die Eingabe und Verarbeitung von Daten kann das Zusammenspiel dieser drei Klassen wie folgt skizziert werden. Die Bildschirmmaske könnte wie folgt gestaltet werden: 0804_01.indb 319 27.03.2012 11:20:50 Datenbank-Anwendung 342 ter, in das Sie den Namen der Datenbank eintragen. Hier wurde zum Beispiel ueb3db vermerkt. Nach dem Klick auf <OK> wird die Datenbank angelegt. 12.4 Mit einer Datenbank arbeiten Der Zugriff auf die Datenbank kann auf zweierlei Wegen erfolgen. Zum einen bietet NetBeans ein Bediensystem an, mit dem Sie Tabellen anlegen und ändern, Daten eingeben und anzeigen oder auch SQL-Statements ausführen können. Zum anderen ist ein Zugriff mittels eines Java-Programms möglich. 12.4.1 Direkter Zugriff auf die Datenbank Um eine Tabelle anzulegen, klicken Sie im Service-Fenster auf den Knoten vor jdbc:mysql://localhost:3306/ueb3db. Es werden die mit diesem Treiber angelegten Datenbanken angezeigt. Anschließend klicken Sie auf den Knoten vor ueb3db. Drei Unterverzeichnisse werden angezeigt. Klicken Sie mit der rechten Maustaste auf Tables. Mit einem Klick auf Create Table öffnen Sie das Create Table-Fenster. Dort können Sie die Struktur einer Tabelle eintragen. Als Tabellenname geben Sie Ort ein. Anschließend betätigen Sie <Add column>. In das Add Column-Fenster geben Sie als Spaltenname ortNr und als Typ Smallint an, Abschluss mit <OK>. 0804_01.indb 342 27.03.2012 11:20:53 Datenbank-Anwendung 349 } Ergebnis: 12.5 Windows-Anwendung und Datenbankzugriff In den voraufgegangenen Klassen wurden die Eingabedaten oftmals unmittelbar in den Quellcode hineingeschrieben. Das geschah aus Gründen der Vereinfachung. Diese Vorgehensweise entspricht jedoch nicht den Anforderungen der Praxis. Üblich ist die Eingabe der Daten über eine Bildschirmmaske. Im folgenden Beispiel sollen die Daten der Mitarbeiter mit Hilfe des nachstehenden Bildschirmformulars eingegeben werden. Nach der Eingabe des Ortskennzeichens sollen die eingegebenen Daten im Kontrollausdruck angezeigt werden. Nach der Betätigung von <OK> werden die Daten in der Datenbank gespeichert. Darüber wird eine Meldung im Kontrollausdruck gemacht. 0804_01.indb 349 27.03.2012 11:20:55