Lukas Fässler, Barbara Scheuner, David Sichau Programmieren mit Java Begleitunterlagen Zum Onlinekurs Programmieren mit Java Begleitunterlagen Zum Onlinekurs Lukas Fässler, Barbara Scheuner, David Sichau iii Trotz sorgfältiger Arbeit schleichen sich manchmal Fehler ein. Die Autoren sind Ihnen für Anregungen und Hinweise per Email an [email protected] dankbar! Dieses Material steht unter der Creative-Commons-Lizenz Namensnennung - Nicht kommerziell - Keine Bearbeitungen 4.0 International. Um eine Kopie dieser Lizenz zu sehen, besuchen Sie http://creativecommons.org/licenses/by-nc-nd/4.0/deed.de Herstellung und Verlag: BoD – Books on Demand, Norderstedt ISBN 978-3-7412-4989-1 Bibliografische Information der Deutschen Nationalbibliothek Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.dnb.de abrufbar. iv Version: 2, Datum: 07 August 2017, Hash: a9ee87a Inhaltsverzeichnis Wie soll dieses Buch verwendet werden? 1 0 Programme erstellen in Java 3 Theorieteil 0.1 Modulübersicht . . . . . . . . . . . . . . . . . . . . . . 0.2 Schreiben von Computerprogrammen . . . . . . . . . . 0.2.1 Computerprogramme bestehen aus Daten und 0.2.2 Programme müssen übersetzt werden . . . . . 0.3 Anweisung . . . . . . . . . . . . . . . . . . . . . . . . . 0.4 Kommentare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Instruktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Variablen und Datentypen Theorieteil 1.1 Modulübersicht . . . . . . . . . . . . . . . . . . . . . 1.2 Darstellen von Zahlen und Zeichen im Computer . . 1.2.1 Binäres System . . . . . . . . . . . . . . . . 1.2.2 Darstellung von Zahlen im binären System . 1.2.3 Darstellung von Zeichen im binären System 1.3 Datentypen . . . . . . . . . . . . . . . . . . . . . . . 1.4 Variablen und Konstanten . . . . . . . . . . . . . . . 1.4.1 Deklaration . . . . . . . . . . . . . . . . . . 1.4.2 Initialisierung und Wertzuweisung . . . . . . 1.4.3 Konstanten . . . . . . . . . . . . . . . . . . 1.5 Operatoren und Ausdrücke . . . . . . . . . . . . . . . 1.5.1 Operatoren (Teil I) . . . . . . . . . . . . . . 1.5.2 Ausdrücke . . . . . . . . . . . . . . . . . . . 1.5.3 Weitere Arithmetische Operatoren . . . . . 1.6 Der Datentyp String . . . . . . . . . . . . . . . . . . 4 4 4 5 6 6 7 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 10 10 10 10 11 11 14 14 15 16 17 17 17 18 19 v 1.7 Ein- und Ausgabe von Daten . . . . . . . . . . . . . . . . . . . . . . . . . 1.7.1 Ausgabe in die Konsole . . . . . . . . . . . . . . . . . . . . . . . . 1.7.2 Eingabe über die Tastatur . . . . . . . . . . . . . . . . . . . . . . Selbstständiger Teil 1.8 Bremsweg-Berechnung . . . . . . . . . . . . . . . . . . . 1.8.1 Einführung . . . . . . . . . . . . . . . . . . . . 1.8.2 Aufgabenstellung und Programmanforderungen 1.9 Zinseszins-Berechnung . . . . . . . . . . . . . . . . . . . 1.9.1 Einführung . . . . . . . . . . . . . . . . . . . . 1.9.2 Aufgabenstellung und Programmanforderungen 1.9.3 Erweiterung . . . . . . . . . . . . . . . . . . . . 1.10 Geldautomat . . . . . . . . . . . . . . . . . . . . . . . . 1.10.1 Einführung . . . . . . . . . . . . . . . . . . . . 1.10.2 Aufgabenstellung . . . . . . . . . . . . . . . . . 1.10.3 Zwischenschritte . . . . . . . . . . . . . . . . . 1.10.4 Erweiterungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Kontrollstrukturen und Logik Theorieteil 2.1 Modulübersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1.1 Anweisungen und Blöcke . . . . . . . . . . . . . . . . . . 2.2 Operatoren (Teil II) . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.1 Relationale Operatoren . . . . . . . . . . . . . . . . . . . 2.2.2 Logische Operatoren . . . . . . . . . . . . . . . . . . . . 2.3 Verzweigungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3.1 Einseitige Verzweigung: bedingte Programmausführung . 2.3.2 Zweiseitige Verzweigung . . . . . . . . . . . . . . . . . . 2.3.3 Mehrstufige Verzweigungen . . . . . . . . . . . . . . . . 2.3.4 Fallauswahl (Switch) . . . . . . . . . . . . . . . . . . . . 2.4 Schleifen (Loops) . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.1 for-Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4.2 while-Schleife . . . . . . . . . . . . . . . . . . . . . . . . 2.4.3 do-while Schleife . . . . . . . . . . . . . . . . . . . . . . 2.4.4 Geschachtelte Schleifen . . . . . . . . . . . . . . . . . . . 19 20 20 22 22 22 22 22 22 23 23 23 23 23 24 24 27 . . . . . . . . . . . . . . . 28 28 28 29 29 30 30 31 31 32 33 34 34 35 36 37 Selbstständiger Teil 2.5 Notendurchschnitt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.5.1 Aufgabenstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.5.2 Programmanforderungen . . . . . . . . . . . . . . . . . . . . . . . 39 39 39 39 vi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.6 2.7 2.8 2.5.3 Zwischenschritte . . . . . . . . . . . . . . . . . . Zinseszins mit Schleifen . . . . . . . . . . . . . . . . . . . . 2.6.1 Einführung . . . . . . . . . . . . . . . . . . . . . 2.6.2 Aufgabenstellung und Programmanforderungen . 2.6.3 Zwischenschritte . . . . . . . . . . . . . . . . . . Zahlen raten . . . . . . . . . . . . . . . . . . . . . . . . . . 2.7.1 Aufgabenstellung . . . . . . . . . . . . . . . . . . 2.7.2 Programmanforderungen . . . . . . . . . . . . . . 2.7.3 Zwischenschritte . . . . . . . . . . . . . . . . . . 2.7.4 Erweiterungen . . . . . . . . . . . . . . . . . . . . Pokern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.8.1 Einführung . . . . . . . . . . . . . . . . . . . . . 2.8.2 Ausgangssituation und Programmanforderungen 2.8.3 Zwischenschritte . . . . . . . . . . . . . . . . . . 2.8.4 Erweiterungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Arrays 39 40 40 40 40 41 41 41 41 42 42 42 42 44 45 47 Theorieteil 3.1 Modulübersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Eindimensionale Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.1 Arrays deklarieren . . . . . . . . . . . . . . . . . . . . . . . 3.2.2 Arrays erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.3 Arrays initialisieren . . . . . . . . . . . . . . . . . . . . . . . 3.2.4 Auf Array-Elemente zugreifen . . . . . . . . . . . . . . . . . 3.2.5 Array-Durchlauf mit Schleifen . . . . . . . . . . . . . . . . . 3.2.6 Länge eines Arrays bestimmen . . . . . . . . . . . . . . . . 3.3 Zwei- und mehrdimensionale Arrays . . . . . . . . . . . . . . . . . . . 3.3.1 Initialisieren und Erzeugen eines zweidimensionalen Arrays 3.3.2 Werte ins zweidimensionale Array ein- und auslesen . . . . . 3.3.3 Mehrdimensionale Arrays . . . . . . . . . . . . . . . . . . . 3.4 Zeichenketten (Strings) als Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 48 48 49 49 50 51 51 52 52 52 53 53 53 Selbstständiger Teil 3.5 Bowling . . . . . . . . . . 3.5.1 Einführung . . . 3.5.2 Aufgabenstellung 3.5.3 Zwischenschritte 3.5.4 Erweiterungen . . 3.6 Tic Tac Toe . . . . . . . . 3.6.1 Einführung . . . . . . . . . . . . . . . . . . . . . . . . 55 55 55 55 56 56 56 56 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii 3.6.2 Aufgabenstellung . . 3.6.3 Zwischenschritte . . 3.6.4 Erweiterungen . . . . 3.7 Such- und Sortieralgorithmen 3.7.1 Einführung . . . . . 3.7.2 Suchalgorithmen . . 3.7.3 Erweiterungen . . . . 3.7.4 Sortieralgorithmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Methoden und Funktionen 56 57 57 58 58 58 59 59 63 Theorieteil 4.1 Modulübersicht . . . . . . . . . . . . . . . . . . . . . 4.2 Methoden . . . . . . . . . . . . . . . . . . . . . . . . 4.2.1 Methoden ohne Rückgabewert (Prozeduren) 4.2.2 Methoden mit Rückgabewert (Funktionen) . 4.2.3 Methoden mit Parametern . . . . . . . . . . 4.3 Methoden aus der Klasse Math . . . . . . . . . . . . 4.4 Überladen von Methoden . . . . . . . . . . . . . . . . 4.5 Gültigkeitsbereiche von Variablen . . . . . . . . . . . 4.6 Rekursion . . . . . . . . . . . . . . . . . . . . . . . . 4.6.1 Beispiel 1: Fakultät . . . . . . . . . . . . . . 4.6.2 Beispiel 2: Fibonacci . . . . . . . . . . . . . 4.7 Fehlerbehandlung mit Exceptions . . . . . . . . . . . 4.7.1 Werfen einer Exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 64 64 64 65 67 67 68 69 71 71 72 73 73 Selbstständiger Teil 4.8 Erweiterungen Pandemie-Simulation . 4.8.1 Einführung . . . . . . . . . . 4.8.2 Setzen der Startbedingungen 4.8.3 Erweiterungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 75 75 75 75 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Objekte Theorieteil 5.1 Modulübersicht . . . . . . . . . . . . . . . . . . . . . . . 5.2 Klassen und Objekte . . . . . . . . . . . . . . . . . . . . 5.2.1 Klassen . . . . . . . . . . . . . . . . . . . . . . 5.2.2 Objektvariablen und Methoden . . . . . . . . . 5.2.3 Erstellen von Objekten unter Verwendung einer viii 77 . . . . . . . . . . . . . . . . Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 78 78 79 79 80 Selbstständiger Teil 5.3 Hotel-Verwaltung . . . . . 5.3.1 Aufgabenstellung 5.3.2 Zwischenschritte 5.3.3 Erweiterungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 85 85 86 86 ix Wie soll dieses Buch verwendet werden? Das vorliegende Buch enthält alle Begleitunterlagen zum Onlinekurs Programmiergrundlagen mit Java. Für den kostenlosen Kurs können Sie sich über folgende URL registrieren und einschreiben: https://et.ethz.ch Der Kurs besteht aus folgenden 6 Modulen: 1. 2. 3. 4. 5. 6. Programme erstellen in Java Variablen und Datentypen Kontrollstrukturen Arrays Methoden Objekte Jedes Modul dauert abhängig von Ihrem Vorwissen 4 bis 8 Arbeitsstunden. Die Materialien in diesem Buch und auf der Webseite begleiten Sie von der Einführung der Begriffe und Konzepte, über deren Verwendung in einfachen Programmier-Beispielen bis hin zur selbstständigen Anwendung der Programmier-Konzepte in kleinen ProgrammierProjekten. Jedes Modul ist wie folgt organisiert: 1. SEE: Kurze Einführung in die wichtigsten Begriffe und Programmier-Konzepte des Moduls. 2. TRY: Computerbasierte Einführung an einfachen Programmier-Beispielen direkt in einer Programmierumgebung. Angeleitet werden Sie dabei von einem elektronischen Tutorial (E.Tutorial® ). 3. DO: Selbstständige Umsetzung kleiner Programmier-Projekte. Verknüpfung der neuen Programmier-Konzepte mit den bereits bekannten. 4. EXPLAIN: Diskussion der individuellen Resultate aus Phase 3 mit Fokus auf die neuen Konzepte aus Phase 1. Dieses Buch enthält alle Begleitmaterialien für die Phasen 1 und 3. Danksagung Wir danken Dennis Komm und Jens Maue für das Korrekturlesen. 1 Programmieren mit Java Modul 0 Programme erstellen in Java Theorieteil Autoren: Lukas Fässler, Barbara Scheuner Begriffe Programmiersprache Bytecode Programm Klasse Programmierumgebung Kommentar Editor Algorithmus Syntax Quelltext Semantik Compiler Anweisung 3 Theorieteil 0.1 Modulübersicht Die Entwicklung des Computers ermöglicht uns, Rechenarbeit durch Maschinen erledigen zu lassen. Der Computer kann jedoch allein keine Probleme lösen, sondern ihm muss ein Lösungsweg (eine Bearbeitungsvorschrift) gegeben werden. Dieser Lösungsweg wird ihm in Form eines Programms mitgeteilt. Dies geschieht wiederum in einer speziellen Sprache, der Programmiersprache. Eine Bearbeitungsvorschrift zur Lösung einer Aufgabe wird Algorithmus genannt. Hierbei fordern wir, dass ein Algorithmus seine Arbeit immer beendet, also nicht unendlich lange braucht, wenn er ausgeführt wird und für jede Eingabe eine sinnvolle Ausgabe generiert. Algorithmus ist somit ein recht abstrakter Begriff. Wir können z.B. ein Kuchenrezept oder eine Wegbeschreibung als einen Algorithmus verstehen. Wir betrachten hier hingegen nur Alogrithmen, die konkret in einer Programmiersprache ausformuliert worden sind. 0.2 Schreiben von Computerprogrammen Wenn zwei Menschen miteinander kommunizieren, wird dies von vielen Dingen, wie beispielsweise Mimik und Gestik, begleitet. Auf die Frage „Wie geht es dir?“ kann eine Antwort „Gut.“ ganz unterschiedlich interpretiert werden, abhängig davon, wie der Antwortende dies zum Beispiel betont. Menschen besitzen einen Intellekt, der es ihnen ermöglicht, einen Dialog zu interpretieren und in einen Kontext zu setzen. Computer haben diese Fähigkeit nicht. Um mit einem Rechner zu kommunizieren, müssen wir uns exakt ausdrücken. Der Computer weiss nicht, was wir eigentlich gemeint haben, sollten wir uns falsch ausgedrückt haben. Für die ersten Computer war dies eine sehr mühselige Aufgabe, denn die Sprache, die ein Computer versteht, ist für Menschen nicht sehr intuitiv. Deshalb wurden sogenannte Hochsprachen entwickelt, die unserer natürlichen Sprache näher sind. In diesem Kurs werden Sie eine solche Sprache, nämlich Java, verwenden, um Algorithmen als Computerprogramme umzusetzen. 4 0.2.1 Computerprogramme bestehen aus Daten und Instruktionen Ein Computerprogramm ist im Wesentlichen eine Auswahl von Daten und eine Folge von Instruktionen, die – wenn sie ausgeführt werden – jeweils eine bestimmte Funktion erfüllen. Eine Instruktion kann beispielsweise eine Berechnung ausführen. Zum besseren Verständnis können Sie sich, wie oben erwähnt, ein Kochrezept vorstellen. Es enthält als erstes die Mengenangaben der Zutaten (Daten) und danach die Reihenfolge der Schritte (Instruktionen), die man ausführen muss, um ein bestimmtes Gericht zu kochen. Das Grundschema eines Rezepts ist meistens dasselbe: zuerst die Zutaten, danach die einzelnen Arbeitsschritte. Mit einem Computerprogramm verhält es sich ähnlich. Jedes Programm folgt ebenfalls einem Grundschema. Bei der Programmierung spricht man allerdings nicht von Schema, sondern von der Syntax einer Programmiersprache, d.h. von den Regeln, die für den Aufbau eines Programms befolgt werden müssen. Wie bereits erwähnt, gibt es allerdings einen wesentlichen Unterschied zu den Schritten in einem Kochrezept. Bei den Instruktionen müssen wir präzise sein. Vorschriften analog zu „nach eigenem Ermessen würzen“ werden wir hier nicht finden, da der Computer sie nicht eindeutig auswerten kann. Folgende Zeilen zeigen ein sehr einfaches Beispiel für ein Programm in der Programmiersprache Java: public class HalloWelt { public static void main(String[] args) { System.out.println("Willkommen zur Javaprogrammierung."); } } Unser Programm enthält in diesem Fall • ein Grundgerüst, bestehend aus einer Klasse mit dem Namen „HalloWelt“. Der Name der Klasse muss zwingend mit dem Namen der Datei übereinstimmen, in der das Programm gespeichert ist. Unser Code wird deshalb in der Datei HalloWelt.java gespeichert. Die Klasse enthält eine Methode, die „Hauptmethode“ (main) genannt wird. • eine Instruktion in der Hauptmethode (System.out.println() als Anweisung). • die Daten (hier den Text Willkommen zur Javaprogrammierung.). Wird dieses Programm nun ausgeführt, wird folgende Zeile in die Konsole ausgegeben: Willkommen zur Javaprogrammierung. 5 Das, was ein Programm ausführt, also seine Bedeutung, nennt man die Semantik des Programms. 0.2.2 Programme müssen übersetzt werden Programme in einer Programmiersprache wie Java sind für uns Menschen lesbar und verständlich. Wie bereits erwähnt, versteht ein Computer sie aber nicht direkt, sondern nur nach einer Umwandlung in Instruktionen für seinen Prozessor. Diese sind für uns nicht nur schwer verständlich, sondern auch wesentlich simpler als die Anweisungen eines Programms in einer Hochsprache wie Java. Das heisst, eine einzelne Instruktion eines Programms führt zu einer Folge mehrerer Prozessor-Instruktionen. Damit nun ein Computer unser Programm ausführen kann, müssen die Anweisungen des Programms in Instruktionen des Computers übersetzt werden. Für das Übersetzen von Programmen aus einer Programmiersprache in eine Folge von Prozessor-Instruktionen gibt es spezielle Computerprogramme, so genannte Kompilierer (Compiler, Übersetzer). Der Vorgang des Übersetzens wird deshalb auch kompilieren genannt. Schreiben und Ausführen eines Java-Programms Programme werden in Dateien gespeichert. Um diese Dateien editieren und abspeichern zu können, brauchen wir einen Editor. Für Java gibt es eine Vielzahl von Editoren und Entwicklungsumgebungen. Nachdem Sie ein Programm geschrieben haben, wird es als Quellcode gespeichert. Dateien, die Java-Quellcode enthalten, haben die Erweiterung .java. Im nächsten Schritt übersetzt der Compiler den Quellcode in ein Format namens Bytecode, das für die Anwenderin oder den Anwender nicht lesbar ist. Dieser bekommt die Endung .class. 0.3 Anweisung Eine Anweisung (statement) ist die kleinste ausführbare Einheit eines Programms. Wie in vielen anderen Programmiersprachen auch, wird eine Anweisung mit einem Strichpunkt oder Semikolon (;) abgeschlossen. Schreibweise: Anweisung; 6 Beispiel: System.out.println("Hallo Welt"); 0.4 Kommentare Kommentare sind Lesehilfen für uns Menschen. Sie dienen der Dokumentation des Programmcodes. Es können beliebig viele Kommentare eingefügt werden. Der Compiler liest über die Kommentare hinweg und ignoriert diese vollständig. Es muss festgelegt werden, wo ein Kommentar beginnt und wo er endet. In Java können Kommentare auf zwei Arten geschrieben werden. Einerseits gibt es Zeilenkommentare, welche nur eine Zeile lang sein können (also ohne Zeilenumbruch). Andererseits gibt es Blockkommentare, welche über mehrere Zeilen gehen können und ein einleitendes sowie ein abschliessendes Zeichen besitzen. Schreibweise Zeilenkommentar: Im folgenden Beispiel werden die Zeilen 1 und 3 vom Compiler ignoriert, die 2. Zeile wird hingegen übersetzt. // Dies ist ein Kommentar und wird vom Compiler ignoriert. System.out.println("Zeile wird vom Compiler übersetzt."); // Dies ist ein Kommentar und wird vom Compiler ignoriert. Schreibweise Blockkommentar: Compiler ignoriert. Im folgenden Beispiel werden alle drei Zeilen vom /* Dies ist ein Kommentar und wird vom Compiler ignoriert. Diese Zeile wird vom Compiler ebenfalls ignoriert. Diese Zeile wird vom Compiler ebenfalls ignoriert. */ 7 Programmieren mit Java Modul 1 Variablen und Datentypen Theorieteil Autoren: Lukas Fässler, Barbara Scheuner, David Sichau Begriffe Binärsystem Deklaration Bit/Byte Wertzuweisung Datentyp Initialisierung ASCII-Code Konstante Ganzzahl (Integer) Gleitkommazahl (Double) Arithmetische Operatoren Zeichenkette (String) Bildschirm Ein- und Ausgabe Variable Typenkonvertierung 9 Theorieteil 1.1 Modulübersicht Die beiden Konzepte Variablen und Datentypen sind für jede Programmierung grundlegend. Bei Variablen handelt es sich um Speicherbereiche, in denen Werte gespeichert werden können, und der Datentyp gibt an, welche Werte erlaubt sind (z.B. nur Ganzzahlen). In einem Programm werden Daten verarbeitet, die sich in ihrer Art unterscheiden, z.B. Zeichen, Zahlen oder logische Daten. Digitale Daten werden immer durch Ziffern dargestellt. Daher auch der Name, digit bedeutet Ziffer. 1.2 Darstellen von Zahlen und Zeichen im Computer Um die Darstellung von Zeichen, Zahlen und Texten im Computer zu verstehen, muss man das binäre System verstehen. 1.2.1 Binäres System Alle Rechner stellen Informationen im binären System dar. Dieses kennt nur zwei Ziffern, nämlich 0 und 1 (im Gegensatz zum Dezimalsystem mit den Ziffern 0 bis 9). Eine solche Ziffer wird als Bit bezeichnet (Abkürzung für Binary Digit, übersetzt „Binäre Ziffer“). Ein Bit entspricht dem kleinsten speicherbaren Wert in einem Computer. Jeweils 8 Bit werden zu einem Byte zusammengefasst. Ein Byte kann somit 28 = 256 verschiedene Sequenzen von je 8 Bit speichern. 1.2.2 Darstellung von Zahlen im binären System Betrachten wir die Zahl 91, die binär mit 8 Bit als 01011011 dargestellt wird (siehe Tabelle 1.1). Wir reden deswegen in diesem Zusammenhang von der Binärdarstellung von 91 (und nicht von der Dezimaldarstellung, die für uns lesefreundlicher ist). Eine 8-Bit-Zahl, wie in unserem Beispiel, kann Werte zwischen 00000000 (0 im Dezimalsystem) und 11111111 (255 im Dezimalsystem) speichern. Für die Umrechnung vom 10 Bit 8 Binärwert 0 7 6 1 7 5 0 6 1 5 4 4 3 2 1 1 0 1 1 3 2 1 Wertigkeit 2 = 128 2 = 64 2 = 32 2 = 16 2 = 8 2 = 4 2 = 2 20 = 1 Dezimalwert 0 64 0 16 8 0 2 1 = 91 Tabelle 1.1: Binäre Darstellung der Dezimalzahl 91. Details siehe Text. Binär- in den Dezimalwert multiplizieren wir für jedes Bit den Binärwert mit der Wertigkeit des Bits (0 oder 1) und summieren diese auf. Ist die Zahl, die wir darstellen wollen, grösser, muss ein grösserer Speicherbereich als 8 Bits bereitgestellt werden. 1.2.3 Darstellung von Zeichen im binären System Für die Darstellung von Zeichen im Computer wurde der so genannte ASCII-Code entwickelt. ASCII steht für American Standard Code for Information Interchange, was übersetzt so viel heisst wie Amerikanische Standardcodierung für den Datenaustausch. Mit Hilfe des 7-Bit-ASCII-Codes können 128 verschiedene Zeichen (27 ) dargestellt werden oder umgekehrt wird jedem Zeichen ein Bitmuster aus 7 Bit zugeordnet (siehe Tabelle 1.2). Die Zeichen entsprechen weitgehend einer Computertastatur. Der ASCII-Code wurde später auf 8 Bit erweitert, was die Darstellung von 256 Zeichen (28 ) erlaubt. Die ASCII-Tabelle enthält auch nicht darstellbare Zeichen (wie etwa ein Zeichen, das einen Zeilenumbruch repräsentiert). Die wichtigsten sind in Tabelle 1.3 dargestellt. 1.3 Datentypen Der Datentyp gibt an, welche Daten in einem Programm gespeichert werden können. Programmiersprachen besitzen vordefinierte Datentypen, die sich in der Art der Interpretation der gespeicherten Daten und in der Grösse unterscheiden. Die meisten Programmiersprachen unterscheiden folgende Datentypen: • Typ für Zahlenwerte • Typ für Zeichenwerte • Typ für Wahrheitswerte (siehe Modul 2) Tabelle 1.4 gibt einen Überblick über die wichtigsten Datentypen, die in vielen Programmiersprachen vorkommen. 11 0-31 31-63 64-95 Dez Zeichen Dez Zeichen Dez Zeichen Dez Zeichen 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 NUL SOH STX ETX EOT ENQ ACK BEL BS HT LF VT FF CR SO SI DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 SP ! “ # $ % & ’ ( ) * + , . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ˆ _ 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 ‘ a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ DEL Tabelle 1.2: ASCII-Tabelle. 12 96-127 Dez Zeichen Bedeutung 8 BS Backspace. Linkes Zeichen löschen 10 NL New Line. Neue Zeile beginnen 32 SP Space. Leerzeichen 127 DEL Delete. Rechtes Zeichen löschen Tabelle 1.3: Nicht darstellbare Zeichen der ASCII-Tabelle. Typ Beschreibung Grösse Wertebereich in Bit boolean Wahrheitswert 1 char Zeichen byte Ganzzahl 8 short Ganzzahl 16 −320 768 . . . 320 767 (−215 . . . + 215 − 1) int Ganzzahl 32 −20 1470 4830 648 . . . 20 1470 4830 647 (−231 . . . + 231 − 1) long Ganzzahl 64 −90 2230 3720 0360 8540 7750 808 . . . 16 true oder false Unicode-Zeichen −128 . . . 127 (−27 . . . + 27 − 1) 90 2230 3720 0360 8540 7750 807 (−263 . . . + 263 − 1) float Gleitkommazahl 32 +/ − 3.40282347 × 1038 double Gleitkommazahl 64 +/ − 1.79769313486231569 × 10308 Tabelle 1.4: Die wichtigsten Datentypen in Java. 13 1.4 Variablen und Konstanten Variablen können wir uns als Behälter zur Aufbewahrung von Werten vorstellen. Sie haben einen Namen, über den sie aufgerufen werden können, und speichern einen konkreten Wert. Der Wert der Variablen kann sich während der Ausführung des Programms ändern (er kann variieren, daher der Name). Um eine Variable in einem Programm verwenden zu können, sind folgende Operationen notwendig: 1. Deklaration 2. Initialisierung 1.4.1 Deklaration Bevor eine Variable in einem Programm verwendet werden kann, muss sie deklariert werden. Das heisst, dass Sie als Programmiererin oder Programmierer einen Speicherbereich für einen bestimmten Datentyp belegen und diesem Speicherplatz einen Namen geben. Über diesen Namen kann der Speicherbereich während des Programmablaufs aufgerufen werden. Namen von Variablen beginnen in Java gemäss Konvention jeweils mit einem Kleinbuchstaben, sie dürfen keine Leerzeichen enthalten und sollten möglichst aussagekräftig sein. Schreibweise: Datentyp name; Beispiel: // Variable a vom Typ Integer. int a; // Variable b vom Typ Double. double b; // Variable c vom Typ Character. char c; Mehrere Variablen vom gleichen Typ können auch wie in folgendem Beispiel in einer gemeinsamen Deklaration geschrieben werden: // 3 Variablem vom Typ Integer. int meineZahl1, meineZahl2, meineZahl3; 14 1.4.2 Initialisierung und Wertzuweisung Das Speichern von Werten geschieht mit dem Zuweisungsoperator. In Java wird hierfür ein Gleichheitszeichen (=) verwendet. Dabei wird der Wert des Ausdrucks rechts des Zuweisungsoperators der Variablen auf der linken Seite zugewiesen. Wenn einer Variable das erste Mal ein Wert zugewiesen wird, spricht man von ihrer Initialisierung. Schreibweise: variable = wert; Beispiel: meineZahl = 4; // meineZahl hat den Wert 4. Wie erwähnt kann sich der Wert einer Variablen während der Ausführung eines Programms ändern. In folgendem Beispiel wird in der Variablen meineZahl zuerst der Wert 4 gespeichert, der dann in einer weiteren Zeile mit dem Wert 6 überschrieben wird: meineZahl = 4; // Wert von meineZahl ist 4. meineZahl = 6; // Wert von meineZahl ist 6. Bei einer Zuweisung handelt es sich also immer um einen schreibenden Zugriff auf eine Variable mit dem Resultat, dass sich deren Wert ändern kann. Der alte Wert wird überschrieben. Damit einer Variablen ein Wert zugewiesen werden kann, darf die Variable nicht als Konstante definiert sein (siehe nächster Abschnitt) und der Typ der Variablen muss mit dem Typ des Werts kompatibel sein. Auf jeden Fall kompatibel sind Variablen und Werte desselben Datentypes. Wenn die Datentypen nicht übereinstimmen, nimmt Java eine implizite Typkonvertierung vor. Dies ist jedoch eine häufige Fehlerquelle und sollte daher vermieden werden. Bei der impliziten Typkonvertierung in Java werden nur dann eine Typkonvertierung durchgeführt, wenn sie ohne Informationsverlust erfolgen kann, also wenn der Zieldatentyp einen gleichen oder grösseren Wertebereich hat als der Ausgangsdatentyp. 15 Beispiel: // Variable ganzeZahl vom Typ Integer. int ganzeZahl; // Variable kommaZahl vom Typ Double. double kommaZahl; ganzeZahl = 4; kommaZahl = ganzeZahl; // Variable kommaZahl wird zum Typ Integer konvertiert. Ein Wert vom Typ int kann einer Variablen vom Typ double zugewiesen werden: Möchte man eine Zuweisung machen, bei der der Zieldatentyp einen kleineren Wertebereich hat, muss eine explizite Typenkonvertierung durchgeführt werden, das sogenannte Typecasting. Die Programmiererin/der Programmierer ist dabei selber dafür verantwortlich, dass die Zuweisung möglich ist. Beispiel: // Variable d vom Typ Double. double d = 1.3; float f = (float)d; // Variable d muss explizit zum Typ Float konvertiert werden. Eine Variable kann in einem Programm nur in einem bestimmten Bereich des Programms gelten. Weiteres dazu erfahren Sie in einem späteren Modul. 1.4.3 Konstanten Konstanten werden wie Variablen mit einem Namen bezeichnet. Sie enthalten während der gesamten Programmausführung einen konstanten Wert. Es kann also nach der Initialisierung keine weitere Wertzuweisung vorgenommen werden. Konstanten können jedoch Teil einer Wertzuweisung an Variablen sein. Eine Konstante wird zusätzlich zu Namen und Datentyp mit dem Schlüsselwort final deklariert. Schreibweise: final Datentyp name; 16 Beispiel: // Deklaration und Initialisierung der Konstante k. final int k = 4; 1.5 Operatoren und Ausdrücke 1.5.1 Operatoren (Teil I) Um in einem Programm Berechnungen durchzuführen zu können, stehen diverse arithmetische Operatoren zur Verfügung, die in Tabelle 1.5 gezeigt sind. Operator Ausdruck Beschreibung Liefert + a+b Addition Summe - a-b Subtraktion Differenz ∗ a∗b Multiplikation Produkt / a/b Division Quotient % a%b Modulo Ganzzahliger Rest einer Division Tabelle 1.5: Arithmetische Operatoren in Java. Weitere Operatoren (logische und Vergleichsoperatoren) lernen Sie in Modul 2 kennen. 1.5.2 Ausdrücke Ausdrücke (expressions) sind in einer Programmiersprache Teil der kleinsten ausführbaren Einheiten eines Programms. Dabei handelt es sich um Verarbeitungsvorschriften, die sich aus Variablen, Konstanten und Operatoren zusammensetzen können und ein Resultat ergeben. Variablen und Konstanten, die mit einem Operator verknüpft werden, nennt man Operanden. Ein Ausdruck kann auch aus einer einzelnen Variablen bestehen. Folgendes Beispiel zeigt einen Ausdruck, der aus einer Variablen i, einem Operator + und einer Konstanten 5 besteht. Somit sind i und 5 Operanden. i + 5 17 Das Resultat des Ausdrucks kann wieder in einer Variablen gespeichert werden. In folgendem Beispiel nutzen wir hierzu die Variable i. Der vorherige Wert von i wird dadurch überschrieben. i = i + 5; Die Reihenfolge, in der Ausdrücke bearbeitet werden, kann durch die Wahl des Operators und durch Klammern beeinflusst werden. Hierfür gelten die mathematischen Regeln, wie wir sie in der Schule gelernt haben, also „Klammern zuerst, dann Punkt vor Strich“. Beispiel: 5 * (2 + 10) Die Klammern erzwingen, dass die Addition vor der Multiplikation ausgeführt wird. 1.5.3 Weitere Arithmetische Operatoren Es gibt in Java noch weitere arithmetische Operatoren. Zuweisungsoperator: i i i i i += -= *= /= %= 1; 1; 1; 1; 1; // // // // // entspricht entspricht entspricht entspricht entspricht i i i i i = = = = = i i i i i + * / % 1; 1; 1; 1; 1; Die Zuweisungsoperatoren dienen dazu die Anweisungen kompakter darzustellen, da man weniger Zeichen benötigt. Increment und Decrement Operatoren: i++; // entspricht i = i + 1; i--; // entspricht i = i - 1; Diese Operatoren sind meistens in sogenannten for-Schleifen anzutreffen, wo sie einen Zähler hochzählen (siehe Modul 2) . 18 1.6 Der Datentyp String Der Datentyp String unterscheidet sich von den bisher thematisierten Datentypen insofern, dass er eine Zusammenfassung von mehreren gleichartigen Variablen darstellt. Dieser Datentyp ist auch kein primitiver Datentyp mehr, da er mehrere Elemente zusammenfasst. Er speichert nämlich alle Buchstaben einzeln in je einer char-Variablen. Wie diese Zusammenfassung der einzelnen Buchstaben funktioniert, lernen Sie, wenn es um die Objektorientierung geht. Die Deklaration und Initialisierung der Variablen funktioniert jedoch wie in 1.4.1 und 1.4.2 beschreiben. Bei der Initialisierung von String-Variablen muss der Wert zwischen Anführungszeichen (") angegeben werden. Beispiel: // Deklaration des Strings vorname. String vorname; // Initialisierung mit dem Wert "Paul". vorname = "Paul"; Da ein String mehrere char-Variablen enthält, kann dem String auch ein einzelner char zugewiesen werden. Beispiel: String name; name = "a"; Mehrere Strings können mit einem Plus-Operator (+) verbunden werden. So entsteht aus mehreren Einzelteilen ein neuer Text. Beispiel: String text; text = "Hallo, " + "das " + "sind " + "mehrere " + "Wörter."; 1.7 Ein- und Ausgabe von Daten Oft möchte man, dass die Benutzerin oder der Benutzer des Programms mit diesem interagieren kann. Das bedeutet, dass die Benutzerin oder der Benutzer etwas eingeben kann 19 (zum Beispiel über die Tastatur) oder das Programm eine Ausgabe macht (zum Beispiel das Resultat einer Berechnung oder einen Text). Um dies zu realisieren verwenden wir Funktionalitäten, welche von Java zur Verfügung gestellt werden. 1.7.1 Ausgabe in die Konsole Damit die Benutzerin oder der Benutzer sieht, was im Programm berechnet wurde, kann im Programmcode angegeben werden, dass ein bestimmter Text oder der Wert einer Variablen ausgegeben wird. Beispiel: Ausgabe eines vorgegebenen Texts System.out.println("Das Programm hat geendet."); Im obigen Beispiel wird der Text „Das Programm hat geendet.” in der Konsole ausgegeben. Der Text, der ausgegeben wird, steht zwischen einem Paar von Anführungs- und Schlusszeichen, die nicht mit ausgegeben werden. Man möchte aber nicht immer nur vorgegebenen Text ausgeben, sondern z.B. das Resultat einer Berechnung, das in einer Variablen (z.B. ganzeZahl) gespeichert ist. Beispiel: Ausgabe des Wertes einer Variablen System.out.println(ganzeZahl); Diese Anweisung gibt den Wert der Variablen ganzeZahl in der Konsole aus. Variablenwerte und Text können in Java mit einem Plus-Zeichen (+) verbunden werden. Beispiel: Ausgabe von Text und Variablenwert System.out.println("Es wurde " + ganzeZahl + " berechnet."); 1.7.2 Eingabe über die Tastatur Oft möchte man den Wert einer Variablen durch die Benutzerin oder den Benutzer eines Programms bestimmen lassen. Eine häufige Form der Eingabe ist über die Tastatur der Benutzerin oder des Benutzers. Der Programmablauf wird solange gestoppt, wie die Benutzerin oder der Benutzer über die Tastatur eine Eingabe macht, welche mit der Return-Taste beendet wird. Eine Benutzereingabe ist in Java etwas aufwändiger als bei anderen Programmiersprachen. Sie beinhaltet folgende zwei Schritte: 20 • Schritt 1: Paket einbinden • Schritt 2: Werte einlesen Schritt 1: Paket einbinden Mit folgender Importanweisung zu Beginn unseres JavaProgramms muss zunächst die Klasse Scanner des Pakets util eingebunden werden: import java.util.Scanner; Schritt 2: Werte einlesen Mit folgenden zwei Zeilen können wir Werte in Form von Zeichenketten (String) vom Konsolenfenster einlesen und einer Variablen (z.B. wert) zuweisen: Scanner eingabe = new Scanner(System.in); String wert = eingabe.next(); Nun weisen wir den eingelesenen Wert unserer Variablen wert zu. Hierfür muss der eingelesene Text noch in einen Integer umgewandelt werden: Integer.parseInt(wert); Beispiel: Mit den folgenden Anweisungen übergeben wir eine Eingabezahl von der Konsole an die Variable x vom Typ Integer: int x; Scanner eingabe = new Scanner(System.in); String wert = eingabe.next(); x = Integer.parseInt(wert); Einlesen von Datentypen Für das Einlesen der Standard-Datentypen (siehe Tabelle 1.4) bietet Scanner auch Möglichkeiten, diese direkt einzulesen. Beispiel: int ganzeZahl; double kommaZahl; ganzeZahl= eingabe.nextInt(); kommaZahl= eingabe.nextDouble(); 21 Selbstständiger Teil 1.8 Bremsweg-Berechnung 1.8.1 Einführung Der Anhalteweg ist die Strecke, die ein Fahrzeug vom Zeitpunkt des Auftretens eines Hindernisses bis zum Stillstand zurücklegt. Der Anhalteweg setzt sich aus dem Reaktionsweg und dem Bremsweg zusammen: Anhalteweg = Reaktionsweg + Bremsweg Reaktionsweg und Bremsweg lassen sich vereinfacht mit folgenden Formeln berechnen (Reaktionsweg und Bremsweg in Metern; Geschwindigkeit in km/h): Reaktionsweg = 3 · Bremsweg = Geschwindigkeit 10 Geschwindigkeit Geschwindigkeit · 10 10 1.8.2 Aufgabenstellung und Programmanforderungen Schreiben Sie ein Java-Programm, welches den Reaktionsweg, den Bremsweg und den Anhalteweg (in Metern) für eine eingegebene Geschwindigkeit berechnet und auf dem Bildschirm ausgibt. 1.9 Zinseszins-Berechnung 1.9.1 Einführung Wir möchten berechnen, wie viel Geld wir auf dem Konto haben, wenn wir 2000 Franken bei 2% für 10 Jahre anlegen. 22 1.9.2 Aufgabenstellung und Programmanforderungen Schreiben Sie ein Java-Programm, welches für jedes Jahr angibt, wie viel Zins hinzugekommen ist, und wie hoch der Betrag nach der Zinsgutschrift auf dem Konto ist. Die Ausgabe soll für jedes Jahr so aussehen: Im x. Jahr gibt es xx Fr. Zins. Neuer Kontostand: xxx Fr. 1.9.3 Erweiterung Die Benutzerin oder der Benutzer soll als Parameter eingeben können, wie viel Geld sie oder er zu wie viel Prozent angelegen möchte. 1.10 Geldautomat 1.10.1 Einführung Bei dieser Aufgabe geht es um das Speichern und Überschreiben von Werten in Variablen. Zudem kommen zwei verschiedene Divisions-Operatoren zum Einsatz. 1.10.2 Aufgabenstellung In dieser Aufgabe sollen Sie einen Geldautomaten simulieren. Der Kunde soll eingeben können, wie viel Geld er oder sie abheben möchte. Der Geldautomat soll dann berechnen, wie viele und welche Banknoten (100er, 50er, 20er und 10er) er ausgeben soll. Die Anzahl der verwendeten Variablen soll möglichst klein gehalten werden, indem sie wiederverwendet werden. So könnte beispielsweise die Ausgabe für den Betrag 571 aussehen: Wie viel möchten Sie abheben? 571 Eingegebener Geldbetrag: 571 Fr. 100er 5 50er 1 20er 1 10er 0 Rest: 1 23 1.10.3 Zwischenschritte • Erstellen Sie eine Benutzereingabe für einen beliebigen Geldbetrag und speichern Sie den Wert in einer Variablen. • Definieren Sie für jede Art von Banknoten (100er, 50er, 20er, 10er) je eine Variable. • Berechnen Sie zuerst, wie viele 100er-Noten herausgegeben werden sollen und geben Sie den Wert auf dem Bildschirm aus. Ganzzahliger Wert einer Division Mit a/100 erhalten Sie den ganzzahligen Wert der Division von a durch 100. Beispiel: 571/100 = 5. • Berechnen Sie den Restwert. Ganzzzahliger Rest einer Division Mit a%100 erhalten Sie den Rest einer Division von a und 100. Beispiel: 571%100 = 71 • Berechnen Sie analog zu den 100er-Noten Schritt für Schritt die Anzahlen aller anderen Banknoten. Tipp: Kopieren Sie den Anweisungsblock für die 100er-Noten und ändern Sie ihn für die anderen Noten ab. 1.10.4 Erweiterungen Für diese Erweiterungen benötigen Sie Bedingungsprüfungen, die erst im nächsten Modul ausführlich behandelt werden. Bedingte Programmausführung Syntax: Die Anweisungen werden nur ausgeführt, wenn die Bedingung zutrifft: if (Bedingung) { Anweisungen } 24 • Überprüfen Sie nach der Eingabe des Geldbetrags, ob abgerundet werden muss und informieren Sie den Kunden über den tatsächlich ausbezahlten Betrag. • Lassen Sie nur die Banknotenarten anzeigen, die tatsächlich ausgegeben werden. • Nehmen Sie an, dass nur ein bestimmter Maximalbetrag abgehoben werden kann. Prüfen Sie deshalb, ob die gewünschte Summe des Kunden dieses Limit nicht überschreitet, und informieren Sie ihn darüber, falls dies der Fall sein sollte. • Es kann sein, dass der Kunde gerne etwas kleinere Noten haben möchte. Fragen Sie ihn deshalb danach (Anwort z.B. mit 0=nein, 1=ja), ob er gemischte Noten wünscht. Überlegen Sie sich zuerst, wie Sie die Noten zusammenstellen wollen. Ändern Sie danach das Programm entsprechend. 25 Programmieren mit Java Modul 2 Kontrollstrukturen und Logik Theorieteil Autoren: Lukas Fässler, Barbara Scheuner, David Sichau Begriffe Anweisungsblock Verzweigung Anweisungskopf for-Schleife Anweisungskörper while-Schleife logische Operatoren Wahrheitswert do-while-Schleife relationale Operatoren geschachtelte Schleife 27 Theorieteil 2.1 Modulübersicht Ein Algorithmus, der als Programm formuliert ist, besteht in der Regel aus mehreren Anweisungen. Diese Anweisungen werden in einer von der Programmiererin oder dem Programmierer festgelegten Reihenfolge abgearbeitet. Diese Abfolge verläuft selten linear. Oft kommt es vor, dass sich eine Programmsequenz (Folge von Anweisungen) in zwei oder mehrere Programmsequenzen verzweigt, wobei jede nur unter bestimmten Bedingungen ausgeführt wird (Verzweigung). Um einen Algorithmus zu vereinfachen, werden oft bestimmte Programmsequenzen wiederholt ausgeführt (Schleifen). Mit Hilfe von Kontrollstrukturen, die in den meisten Programmiersprachen vorkommen, kann der Programmablauf beeinflusst werden. Die Entscheidung, wie der Ablauf gesteuert wird, muss in Bedingungen formuliert werden. 2.1.1 Anweisungen und Blöcke Wie bereits in Modul 0 erwähnt, werden einzelne Anweisungen durch ein Semikolon abgeschlossen. Mehrere Anweisungen können in einem Anweisungsblock zusammengefasst werden. In Java werden zur Markierung von Anweisungsblöcken geschweifte Klammern {} verwendet. { \\öffnet den Block anweisung1; anweisung2; ... } \\schliesst den Block Die Ausführung von Blöcken kann durch Kontrollstrukturen (z.B. Verzweigungen oder Schleifen) gesteuert werden. Diese Kontrollstrukturen bestehen aus einem Kopf (head) und Körper (body). 28 \\Kopf (head) { \\Körper (body) } Bei folgendem Programm wird der Anweisungsblock 1 durch einen Anweisungsblock 2 unterbrochen: \\Beginn Anweisungsblock 1 Kopf 1 { \\Körper 1 \\Beginn Anweisungsblock 2 Kopf 2 { \\Körper 2 } \\Ende Anweisungsblock 2 \\Fortsetzung Körper 1 } \\Ende Anweisungsblock 1 2.2 Operatoren (Teil II) Die arithmetischen Operatoren sind bereits in Modul 1 beschrieben worden. Im Zusammenhang mit Kontrollstrukturen kommen logische und relationale Operatoren zum Einsatz. 2.2.1 Relationale Operatoren Relationale Operatoren werden gebraucht, um Werte (Operanden) miteinander zu vergleichen. Sie liefern ein logisches Ergebnis wahr (true) oder falsch (false). Werte, die mit relationalen Operatoren verknüpft sind, nennt man in der Aussagenlogik auch Elementaraussagen. Die relationalen Operatoren in Java sind in Tabelle 2.1 zusammengefasst. 29 Operator Ausdruck Beschreibung Liefert wahr (true), wenn... > a>b grösser als a grösser ist als b. < a<b kleiner als a kleiner ist als b. == a == b gleich a und b denselben Wert haben. != a != b ungleich a und b ungleiche Werte haben. >= a >= b grösser oder gleich a grösser oder gleich b ist. <= a <= b kleiner oder gleich a kleiner oder gleich b ist. Tabelle 2.1: Relationale Operatoren in Java. 2.2.2 Logische Operatoren Logische Operatoren verknüpfen Elementaraussagen miteinander. Dabei werden Wahrheitswerte miteinander verglichen. Das Ergebnis ist ebenfalls ein Wahrheitswert, also wahr (true) oder falsch (false). Da dies die Operanden und Operatoren der Boolschen Aussagenlogik sind, heisst der Datentyp Boolean. Die in Java verwendeten logischen Operatoren sind in Tabelle 2.2 dargestellt. Operator Ausdruck Liefert wahr (true), wenn... ! !a a falsch ist (NOT). && a && b sowohl a als auch b wahr sind (AND). Ist a falsch, wird b nicht ausgewertet. k akb mindestens a oder b wahr sind (OR). Ist a wahr, wird b nicht mehr ausgewertet. ˆ aˆb a und b unterschiedliche Wahrheitswerte haben Tabelle 2.2: Logische Operatoren in Java. 2.3 Verzweigungen Verzweigungen überprüfen einen Zustand des Programms. Je nachdem, ob eine bestimmte Bedingung erfüllt ist oder nicht, fährt das Programm mit unterschiedlichen Blöcken von Anweisungen fort. Verzweigungen werden in Java, so wie in vielen anderen 30 Programmiersprachen auch, mit dem Schlüsselwort if eingeleitet. Die Bedeutung des if ist analog zur englischen Sprache. If it is raining, then I will take the bus, otherwise I will walk. Dies könnte in Java wie folgt geschrieben werden: if (rain) {bus} else {walk}; Falls die Bedingung rain wahr (true) ist, wird der Block mit der Anweisung bus ausgeführt, andernfalls wird der Block mit der Anweisung walk ausgeführt. Allgemein kann mit einer if-Anweisung zur Laufzeit entschieden werden, ob eine Anweisung oder ein Anweisungsblock ausgeführt werden soll oder nicht. Um Bedingungen zu formulieren, können sowohl Boolsche Variablen, Relationen wie Gleichheit, grösser oder kleiner als auch logische Operatoren verwendet werden. Je nachdem wie viele Fälle zu unterscheiden sind, ist eine einseitige (2.3.1), zweiseitige (2.3.2) oder mehrstufige Verzeigung (2.3.3) zu wählen. 2.3.1 Einseitige Verzweigung: bedingte Programmausführung Eine einseitige Verzweigung besteht aus einer Bedingungsabfrage und einem Anweisungsblock, welcher ausgeführt wird oder nicht. Schreibweise: if (Bedingung) { Anweisungsblock; } Beispiel: if (rain == true) { System.out.println("Es regnet."); } Der Satz "Es regnet." wird nur ausgegeben, wenn die Variable rain den Wert true hat. 2.3.2 Zweiseitige Verzweigung Bei einer zweiseitigen Verzeigung kann zusätzlich angegeben werden, was im anderen Fall (else), wenn also die Bedingung nicht zutrifft, ausgeführt werden soll. 31 Schreibweise: if (Bedingung) { Anweisungsblock1; } else { Anweisungsblock2; } Beispiel: if (rain == true) { System.out.println("Es regnet."); } else { System.out.println("Es regnet nicht."); } Hat die Variable rain den Wert true, wird der Satz "Es regnet." ausgegeben, im anderen Fall (false) wird der Satz "Es regnet nicht." ausgegeben. 2.3.3 Mehrstufige Verzweigungen Mit einer mehrstufigen Verzweigung können mehrere Vergleiche gemacht werden. Das kann nötig sein, wenn Sie unterschiedliche Möglichkeiten in einer bestimmten Reihenfolge prüfen möchten. Schreibweise: if (Bedingung1) { Anweisungsblock1; } else if (Bedingung2) { Anweisungsblock2; } else if (Bedingung3) { Anweisungsblock3; } ... 32 Beispiel: if (rain == true) { System.out.println("Es regnet."); } else if (snow == true) { System.out.println("Es schneit."); } else if (sun == true) { System.out.println("Es scheint die Sonne."); } else { System.out.println("Die Wetterlage ist unklar."); } Hat die Variable rain den Wert true, wird wieder der Satz "Es regnet." ausgegeben. Hat sie hingegen den Wert false, wird als nächstes die Variable snow geprüft. Hat snow den Wert true, wird der Satz "Es schneit." ausgegeben. Hat snow den Wert false, wird als nächstes die Variable sun geprüft. Hat sun den Wert true, wird der Satz "Es scheint die Sonne." ausgegeben. Hat sun auch den Wert false, wird der Satz "Die Wetterlage ist unklar." ausgegeben. 2.3.4 Fallauswahl (Switch) Eine andere Möglichkeit, während des Programmablaufs zwischen unterschiedlichen Möglichkeiten auszuwählen, ist die switch-Anweisung. Dabei wird der Wert einer Variablen mit unterschiedlichen Werten verglichen. Schreibweise: switch (ausdruck){ case constant: Anweisungsblock; default: Anweisungsblock; } Im Gegensatz zur if-Verzweigung kann mit dem switch-Statement nur auf Gleichheit geprüft werden. Vergleiche auf grösser oder kleiner sind nicht möglich. Als Ausdruck im switch-Statement sind alle ganzzahligen Datentypen und, seit Java 7, auch String-Typen zugelassen. Zusätzlich werden bei einem switch-Statement alle Anweisungen ab dem 33 Einstiegspunkt abgearbeitet. Ist dies nicht erwünscht, sollte der Anweisungsblock mit einem break abgeschlossen werden. Beispiel: switch (test) { case 1: System.out.println("Ich wurde ausgewählt."); break; case 2: System.out.println("Du wurdest ausgewählt."); break; case 3: System.out.println("Wir wurden ausgewählt."); break; default: System.out.println("Keiner wurde ausgewählt."); } 2.4 Schleifen (Loops) Mit Hilfe von Schleifen (loops) können dieselben Anweisungen wiederholt ausgeführt werden. Wie in anderen Programmiersprachen gibt es auch in Java verschiedene Schleifenarten. Eine Schleife besteht aus einem Schleifenkopf und einem Schleifenkörper. Der Schleifenkörper enthält den zu wiederholenden Anweisungsblock. Der Schleifenkopf steuert die Schleife. Er gibt an, wie oft oder unter welchen Bedingungen die Anweisungen des Schleifenkörpers wiederholt werden sollen. 2.4.1 for-Schleife Bei der zählergesteuerten for-Schleife wird die Anzahl der Schleifendurchläufe durch eine Laufvariable von einem Startwert- bis zu einem Endwert durchgezählt. Bei jedem Schleifendurchgang wird der Zähler verändert. 34 Schreibweise: for (init; test; update){ Anweisungsblock } • Initialisierung (init): Deklarieren der Laufvariable und setzen des Startwerts. • Logischer Ausdruck (test): Es wird bei jedem Durchlaufen geprüft, ob die Schleife weiterlaufen muss oder der Endwert schon erreicht worden ist. • Aktualisierung (update): Die Laufvariable wird nach jedem Durchlaufen der Schleife verändert. Beispiel: Folgende Anweisung gibt die Werte 0 bis 4 am Bildschirm aus: for (int i=0; i<5; i++){ System.out.println(i); } Zunächst wird die Laufvariable i deklariert (Datentyp Integer) und auf den Anfangswert 0 gesetzt. Danach wird geprüft, ob i kleiner ist als 5. Ist dies der Fall, werden die Anweisungen des Schleifenkörpers durchlaufen und dann der Wert von i um 1 erhöht. 2.4.2 while-Schleife Es ist nicht immer vorhersehbar, wie oft Anweisungen wiederholt werden müssen, da die Anzahl der Wiederholungen von dem abhängen kann, was im Schleifenkörper passiert. Hier geraten wir bei zählergesteuerten Schleifen an eine Grenze. Bei bedingungsabhängigen Schleifen wird die Anzahl der Wiederholungen nicht von einem Zähler, sondern von einer Bedingung abhängig gemacht. Diese Bedingung wird bei jedem Schleifendurchgang überprüft. While- und do-while-Schleifen unterscheiden sich dadurch, ob diese Bedingung vor oder nach dem Anweisungsblock überprüft wird. Schreibweise: Initialisierung der Variablen while (Bedingung){ Anweisungsblock Aktualisierung } • Initialisierung: Deklarieren einer oder mehrerer Variablen und initialisieren der Startwerte. 35 • Bedingung: Die Bedingung wird geprüft, sobald die while-Schleife erreicht wird. Ist die Bedingung wahr (true), wird der Schleifenkörper ausgeführt. Ist die Bedingung falsch (false), wird die Schleife abgebrochen und die Anweisungen des Schleifenkörpers werden nicht mehr ausgeführt. Nach jedem Durchlaufen der Schleife wird die Bedingung erneut geprüft. • Aktualisierung: Innerhalb des Schleifenkörpers müssen sich Werte so verändern, dass die Bedingung irgendwann erreicht wird, sonst droht eine Endlosschleife, was der Definition eines Algorithmus widerspricht (ein Algorithmus muss seine Arbeit immer beenden). Beispiel: Folgende Anweisung gibt die Werte 0 bis 4 am Bildschirm aus: int i=0; while (i<5){ System.out.println(i); i++; } Zunächst wird eine Variable i initialisiert und auf 0 gesetzt. Zu Beginn der Schleife wird geprüft, ob i kleiner ist als 5. Ist dies der Fall (true), wird der Schleifenkörper ausgeführt. Ist dies nicht der Fall (false), wird die Schleife abgebrochen. Die Variable i wird innerhalb des Schleifenkörpers jedes Mal um 1 erhöht. 2.4.3 do-while Schleife Der Schleifenkörper einer do-while-Schleife wird im Gegensatz zur while-Schleife mindestens einmal ausgeführt, da die Bedingungsprüfung zur Wiederholung jeweils am Ende des Schleifenkopfs erfolgt. Schreibweise: Initialisierung der Variablen do { Anweisungsblock Aktualisierung } while (Bedingung); • Initialisierung: Deklarieren einer Variable und setzen des Startwerts. • Aktualisierung: Innerhalb des Schleifenkörpers müssen sich Werte so verändern, dass die Bedingung irgendwann erreicht wird, sonst droht eine Endlosschleife. 36 • Bedingung: Die Bedingungsprüfung findet erst statt, nachdem der Schleifenkörper durchlaufen ist. Sie enthält die Bedingung zum Wiederholen der Schleife. Trifft diese Bedingung zu, wird die Schleife erneut durchlaufen, sonst wird sie abgebrochen. Beispiel: Folgende Anweisung gibt die Werte 0 bis 4 am Bildschirm aus: int i=0; do { System.out.println(i); i++; } while (i<5); Es wird eine Variable i initialisiert und auf 0 gesetzt. Im Schleifenkörper wird die Variable i um 1 erhöht. Erst jetzt wird geprüft, ob i kleiner ist als 5. Sobald i den Wert 5 erreicht, wird die Schleife abgebrochen. 2.4.4 Geschachtelte Schleifen Beim Programmieren kommt es oft vor, dass zwei Schleifen ineinander geschachtelt werden (nested loops). Das hat zur Folge, dass eine äussere Schleife eine innere steuert. Dies kann wie folgt dargestellt werden: Äussere Schleife { Innere Schleife { Anweisungsblock } } Eine Analogie zu den geschachtelten Schleifen findet man bei unserer Erde, die sich um die Sonne dreht. Eine Umkreisung in einem Jahr wäre mit der äusseren Schleife vergleichbar, und eine Drehung der Erde um die eigene Achse innerhalb eines Tages wäre mit der inneren Schleife vergleichbar. In Java könnte ein Programm zur Anzeige von Tagen und Stunden eines Jahres (das kein Schaltjahr ist) mit folgender geschachtelten Schleife geschrieben werden: 37 for (int tage=0; tage<365; tage++){ for (int stunden=0; stunden<24; stunden++) { System.out.println("Tag " + tage); System.out.println("Stunde " + stunden); } } Die ersten drei Ausgaben lauten: Tag 0: Stunde 0 Tag 0: Stunde 1 Tag 0: Stunde 2 Die letzten drei Ausgaben lauten: Tag 364: Stunde 21 Tag 364: Stunde 22 Tag 364: Stunde 23 38 Selbstständiger Teil 2.5 Notendurchschnitt 2.5.1 Aufgabenstellung Ein Programm soll beliebig viele Noten einlesen können und daraus den Notendurchschnitt berechnen. 2.5.2 Programmanforderungen Schreiben Sie ein Programm, bei dem der User beliebig viele Noten eingeben kann. Er oder sie soll Noten eingeben können, bis ein bestimmter Wert (z.B. 0) eingegeben wird. In diesem Fall endet die Noteneingabe und es wird der Durchschnitt der eingegebenen Noten berechnet. So könnte die Ausgabe Ihres Programms aussehen: Bitte geben Sie ihre Noten ein (0 für Eingabe beenden): 1. Note: 3 2. Note: 4.5 3. Note: 5 4. Note: 6 5. Note: 0 Sie haben 4 Noten eingegeben. Schnitt = 4.625 2.5.3 Zwischenschritte • Schreiben Sie die Noteneingabe für eine Note und speichern Sie den eingegeben Wert in einer Variablen (Datentyp beachten!). • Konstruieren Sie eine Schleife zur Eingabe beliebig vieler Noten. Folgende Fragen müssen geklärt werden: – Schleifenkopf: Wie wird die Schleife abgebrochen? – Schleifenkörper: Welche Anweisungen werden wiederholt? 39 Tipp: Eine Möglichkeit besteht darin, so lange nach Noten zu fragen, wie ein definierter Wert (z.B. 0 oder 9) nicht eingegeben wird. • Führen Sie weitere Variablen für die Berechnung des Durchschnitts (nächster Schritt) ein: – Zähler: Zählt die Anzahl eingegebener Noten. – Summe: Enthält die Summe aller eingegebenen Noten. – Durchschnitt: Speichert den Notendurchschnitt (Summe/Zähler). Tipp: Es empfiehlt sich zu Testzwecken, die Variablenwerte bei jedem Schleifendurchgang anzuzeigen. So werden Sie allfällige Berechnungsfehler schneller erkennen und beheben können. • Berechnen Sie den Durchschnitt und geben Sie das Resultat am Bildschirm aus. 2.6 Zinseszins mit Schleifen 2.6.1 Einführung Im vorangegangenen Modul haben Sie eine Aufgabe zur Zinseszins-Berechung gelöst. Das Schleifen-Konzept erlaubt uns nun eine elegantere Lösung dieses Problems. 2.6.2 Aufgabenstellung und Programmanforderungen Setzen Sie eine Schleife ein, um die gleiche Aufgabenstellung mit einem kürzeren Programm zu lösen. Gestalten Sie das Programm ausserdem flexibler, indem der Nutzer zusätzlich die Anzahl der Anlagejahre als Parameter eingeben kann. 2.6.3 Zwischenschritte • Implementieren Sie das Programm neu unter Anwendung einer for-Schleife, die über die vorgegebenen 10 Jahre iteriert. • Lassen Sie den Benutzer zu Beginn die Anzahl Jahre über die Konsole eingeben. Das Programm soll nun für die angegebene Anzahl Jahre die Zinseszins-Berechnung durchführen. 40 2.7 Zahlen raten 2.7.1 Aufgabenstellung Bei dieser Aufgabe ist ein Spiel umzusetzen, bei dem sich der eine Spieler/die eine Spielerin eine Zahl ausdenkt und der/die andere diese Zahl erraten muss. 2.7.2 Programmanforderungen Eine Spielerin oder ein Spieler soll wiederholt raten, bis er oder sie eine festgelegte Zahl erraten hat. Bei jedem Rate-Versuch soll angegeben werden, ob die gesuchte Zahl grösser oder kleiner ist als die eingegebene Zahl. Zählen sie dabei auch die Anzahl der Versuche mit und geben Sie diese am Ende des Spiels bekannt. So könnte Ihre Ausgabe aussehen (zu erratende Zahl: 52): Gesucht ist eine Zahl zwischen 1 und 100. raten Sie! 4 zu klein raten Sie 94 zu gross raten Sie 52 Erraten! 3 mal geraten. 2.7.3 Zwischenschritte • Legen Sie eine Zahl fest, die erraten werden soll, oder lassen Sie die Zahl von einer Person über die Konsole eingeben. • Setzen Sie eine Boolean-Variable auf den Wert false. • Schreiben Sie den Schleifenkopf, welcher als Bedingung die Boolean-Variable enthält. • Schreiben Sie dann den Code für die Eingabe einer Zahl (zwischen 0 und 100). • Prüfen Sie die eingegebene Zahl und teilen Sie dem Spielenden mit, wenn sie zu klein oder zu gross ist. • Schreiben Sie die Anweisungen, die ausgeführt werden sollen, falls die Zahl erraten wurde. 41 2.7.4 Erweiterungen • Wenn die eingegebene Zahl grösser 100 oder kleiner 0 ist, dann soll sie nicht verglichen werden. Stattdessen soll eine Fehlermeldung ausgegeben werden, dass diese Zahlen nicht im Suchbereich liegt. • Wie könnte das Programm zum Erraten von Buchstaben abgeändert werden? • Wie können Sie auch das Raten automatisieren? • Überlegen Sie sich, welche die schnellste Ratestrategie ist. Begründen Sie Ihre Antwort. 2.8 Pokern 2.8.1 Einführung Beim Poker-Spiel erhält jede Spielerin oder jeder Spieler fünf Karten, die als Hand bezeichnet werden (siehe Beispiel in Abbildung 2.1). Abbildung 2.1: Beispiel einer Hand beim Pokern. Die vier Farben sind Herz, Karo, Pik und Kreuz. Die 13 Werte sind 2 bis 10, Junge (J), Dame (Q), König (K) und Ass (A). Eine Hand wird nach der Höhe der KartenKombination bewertet. In Tabelle 2.1 sind die Wertigkeiten verschiedener Hände der Reihe nach geordnet. Eine Hand mit einer höheren Wertigkeit schlägt jedes Blatt mit einer niedrigeren Wertigkeit. 2.8.2 Ausgangssituation und Programmanforderungen Bei dieser Aufgabe müssen Sie nicht den ganzen Code von Grund auf neu schreiben. Sie erhalten einen Ausgangs-Code pokern.java, den Sie im Folgenden erweitern werden. Was das Programm schon kann Beim vorgegebenen Programm können Sie bereits fünf Karten einer Hand eingeben (absteigend sortiert). 42 Name Bedeutung Royal Flush Strasse vom Ass abwärts in einer Farbe Straight Flush Strasse in einer Farbe Four of a Kind Vierling (4 Gleiche) Full House ein Drilling (3 Gleiche) und ein Paar (2 Gleiche) Flush fünf Karten von einer Farbe Straight Strasse: 5 Karten in einer Reihe (nicht gleiche Farbe) Three of a Kind Drilling (3 Gleiche) Two Pairs zwei Paare: 2 mal 2 Karten mit dem gleichen Wert One Pairs ein Paar: 2 Karten mit dem gleichen Wert Beispiel Tabelle 2.1: Wertigkeiten verschiedener Hände beim Pokern. 43 Beispiel: Sie haben eingegeben: Karte 1 (Wert|Farbe): Karte 2 (Wert|Farbe): Karte 3 (Wert|Farbe): Karte 4 (Wert|Farbe): Karte 5 (Wert|Farbe): 12 1 9 3 8 2 7 3 4 4 Diese Eingabe würde der Hand in Abbildung 2.1 entsprechen. Was das Programm noch nicht kann Ihre Aufgabe besteht nun darin, das Programm so zu erweitern, dass es aufgrund der eingegebenen fünf Karten der Hand automatisch ausgibt, welche Karten-Kombination der Spieler hat. Beispiel: Sie haben eingegeben: Karte 1 (Wert|Farbe): Karte 2 (Wert|Farbe): Karte 1 (Wert|Farbe): Karte 1 (Wert|Farbe): Karte 1 (Wert|Farbe): 12 2 11 2 10 2 9 2 7 2 Sie haben FLUSH Programmieren Sie mindestens die Erkennung von fünf Poker-Blättern. 2.8.3 Zwischenschritte • Laden Sie die Datei poker.java auf Ihren Rechner und öffnen Sie den AusgangsCode in Ihrer Programmierumgebung. • Studieren Sie den Ausgangs-Code. Geben Sie ein paar Kartenkombinationen ein. • Programmieren Sie die Erkennung der Kartenkombinationen. Tipp: Überlegen Sie sich, welche Poker-Hände ähnliche Eigenschaften (z.B. die gleiche Farbe) aufweisen, um den Programmieraufwand für die Bedingungsprüfungen möglichst klein zu halten. 44 2.8.4 Erweiterungen • Überprüfen Sie, ob die Spielerin/der Spieler die Karten tatsächlich der Grösse nach absteigend eingegeben hat. • Wie könnten die Karten absteigend der Reihe nach sortiert werden? 45 Programmieren mit Java Modul 3 Arrays Theorieteil Autoren: Lukas Fässler, Barbara Scheuner, David Sichau Begriffe Datenstruktur Array-Dimension Array Array-Länge Array-Index Array-Durchlauf Array-Element Zweidimensionales Array 47 Theorieteil 3.1 Modulübersicht Mit den Standarddatentypen, die Sie bis hierhin kennen gelernt haben, kann fast jede beliebige Zahl oder jedes beliebige Zeichen dargestellt werden. Oft werden beim Programmieren aber zusammengehörige Daten verwendet (z.B. Lottozahlen, Temperaturen, Abfahrtszeiten). Eine Möglichkeit, eine zusammengehörige Gruppe von Elementen des gleichen Typs abzuspeichern, bieten Arrays (Reihe, Felder). Auf diese Weise muss nicht für jedes Element eine eigene Variable deklariert werden, sondern sie können alle unter einem Bezeichner gespeichert werden. Die Datenstruktur Array kommt in fast jeder modernen Programmiersprache vor. 3.2 Eindimensionale Arrays Eindimensionale Arrays sind die einfachste Form von Arrays. Sie bestehen aus einer geordneten Menge von n Elementen desselben Datentyps. Die Elemente können über einen sogenannten Index angesprochen werden. Dieser gibt die Position eines Elements im Array an. In vielen Programmiersprachen (so auch in Java) hat das erste Element den Index 0, das zweite den Index 1 und das letzte den Index n − 1 (siehe Beispiel in Tabelle 3.1). Index 0 1 2 3 4 5 Wert 12 13 15 17 23 32 Tabelle 3.1: Beispiel für ein eindimensionales Array mit sechs Elementen. Folgende Operationen werden mit Arrays typischerweise ausgeführt: Array deklarieren, erzeugen, Werte in ein Array ein- und auslesen. 48 3.2.1 Arrays deklarieren Arrays müssen wie Variablen zunächst deklariert werden. Das heisst, es werden Name und Datentyp festgelegt. Um anzuzeigen, dass nicht nur ein Element in der Variablen gespeichert werden kann, werden in Java eckige Klammern [] verwendet. Zur Zeit der Deklaration ist die Anzahl Elemente noch nicht festgelegt. Schreibweise: Datentyp[] name; Beispiel: Folgende Anweisung deklariert ein Array mit dem Namen zahlen vom Datentyp Integer: int[] zahlen; 3.2.2 Arrays erzeugen Um ein Array zu erzeugen, wird der Operator new verwendet. Die Array-Länge (d.h. die Anzahl Elemente) wird in eckigen Klammern [] hinter den Datentyp geschrieben. Ist die Länge einmal festgelegt, kann sie nachher nicht mehr geändert werden. Die Länge des Arrays muss vom Datentyp Integer sein. Die Länge kann mit einer Zahl, einer Konstanten oder einem Ausdruck angegeben werden. Schreibweise: name = new Datentyp[anzahl]; Beispiel: Folgende Anweisung erzeugt sechs Speicherplätze im Array mit dem Namen zahlen, in welchem sechs Elemente von Typ int gespeichert werden können: zahlen = new int[6]; // oder zahlen = new int[4+2]; Bei der Erzeugung des Arrays werden die Elemente mit Default-Werten belegt. Beim Datentyp Integer ist dies der Wert 0. Deklaration und Erzeugen von Arrays kann alternativ auch in einer einzigen Anweisung durchgeführt werden. 49 Schreibweise: Datentyp[] name = new Datentyp[anzahl]; Beispiel: Folgende Anweisung erzeugt ein Array mit dem Namen zahlen vom Datentyp Integer mit sechs Elementen: int[] zahlen = new int[6]; 3.2.3 Arrays initialisieren Einem Array-Element kann unter Angabe des Indexes ein Wert zugewiesen werden. In Java hat das erste Element den Index 0. Ein Array mit sechs Elementen hat somit die Indizes 0, 1, 2, 3, 4 und 5. Schreibweise: name[Index] = Wert; Beispiel: So weisen wir dem ersten Element des Arrays den Wert 12 und dem zweiten den Wert 13 zu: zahlen[0] = 12; zahlen[1] = 13; In Java können bei der Erzeugung des Arrays die Elemente auch direkt initialisiert werden, indem geschweifte Klammern {} gesetzt und die einzelnen Werte getrennt mit Kommata (,) eingegeben werden. Die Grösse des Arrays wird durch die Anzahl der Werte festgelegt. Der Operator new entfällt in diesem Fall. Schreibweise: Typ[] name = {wert1, wert2,..., wertN}; Beispiel: Deklaration, Erzeugung und Initialisierung des Arrays zahlen mit den sechs Elementen 12, 13, 15, 17, 22 und 32: int[] zahlen = {12, 13, 15, 17, 22, 32}; 50 3.2.4 Auf Array-Elemente zugreifen Auf einzelne Elemente eines Arrays wird über einen Index (z.B. i) zugegriffen. x[i] liefert somit das Element aus dem Array x an der Position i. Es gilt zu beachten, dass die Indizes bei 0 beginnen und bei einem weniger als der Anzahl der Elemente des Arrays enden. Es können einzelne Elemente oder Bereiche von Arrays aufgerufen werden und es kann mit Elementen von Arrays gerechnet werden. Beispiel: //Array mit 3 Elementen. int[] c = new int[3]; c[0]=1; c[1]=2; c[2]=3; //Aufruf eines Elements (Resultat: 1). System.out.println(c[0]); //Addition zweier Elemente (Resultat: 5). System.out.println(c[1]+c[2]); 3.2.5 Array-Durchlauf mit Schleifen Es ist üblich, zur Bearbeitung von Arrays for-Schleifen zu verwenden. Der Wert der Laufvariablen entspricht dabei dem Index-Wert des Arrays. Der Aufwand reduziert sich dadurch auf wenige Anweisungen, egal wie viele Elemente ein Array besitzt. Dieser Vorgang wird auch Array-Durchlauf genannt. Beispiel: Mit folgender Anweisung können die sechs Elemente des Arrays zahlen am Bildschirm untereinander ausgegeben werden: for (int i=0; i<6; i++) { System.out.println(zahlen[i]); } Die for-Schleife zählt von 0 bis 5. Bei jedem Schleifendurchlauf wird die Variable i als Index verwendet, um das Array Element an der entsprechenden Stelle auszugeben. 51 3.2.6 Länge eines Arrays bestimmen Jedes Array hat in Java eine Eigenschaft length, mit welcher die Länge des Arrays abgefragt werden kann. Beispiel: for (int i=0; i<zahlen.length; i++) { System.out.println(zahlen[i]); } Das ist vor allem beim Durchlaufen des Arrays hilfreich, um die obere Grenze der Schleife auszurechnen. Dies hat den Vorteil, dass bei einer Änderung der Array-Länge die Schleife nicht angepasst werden muss. 3.3 Zwei- und mehrdimensionale Arrays Besteht ein Element eines Arrays selbst wieder aus einem Array, entsteht ein zweidimensionales Array. Man kann es sich als Tabelle mit m mal n Elementen vorstellen, die jeweils über zwei Indizes angesprochen werden (siehe Beispiel in Tabelle 3.2). Index 0 1 2 3 4 5 0 12 13 15 17 23 39 1 14 53 45 87 27 62 2 22 33 17 19 83 32 Tabelle 3.2: Beispiel für ein zweidimensionales Array mit drei mal sechs Elementen. 3.3.1 Initialisieren und Erzeugen eines zweidimensionalen Arrays Schreibweise: Typ[][] name = new Typ[anzahlZeilen][anzahlSpalten]; 52 Beispiel: Folgende Anweisung erzeugt ein zweidimensionales Array mit dem Namen zahlen vom Datentyp Integer mit 3 mal 5 Elementen: int[][]zahlen = new int[3][5]; 3.3.2 Werte ins zweidimensionale Array ein- und auslesen Um auf ein einzelnes Element eines zweidimensionalen Arrays zuzugreifen, werden die zwei Indizes für die Zeilen- und Spaltennummer angegeben: name[zeilennummer][spaltennummer] = wert; Beispiel: zahlen[0][0] = 22; Um zweidimensionale Arrays iterativ zu bearbeiten, sind geschachtelte Schleifen mit zwei Indexvariablen notwendig. Beispiel: Folgende Zeilen geben alle Elemente des zweidimensionalen Arrays zahlen am Bildschirm aus: for (int i=0; i<3; i++) { for (int j=0; j<6; i++) { System.out.println(zahlen[i][j]); } } 3.3.3 Mehrdimensionale Arrays Ein Array kann auch mehr als zwei Dimensionen haben. Für jede weitere Dimension wird ein weiterer Index für den Zugriff auf die Elemente benötigt. 3.4 Zeichenketten (Strings) als Arrays Wie bereits in Modul 1 erwähnt, ist eine Variable des Types String eine Zusammenfassung mehrerer Variablen des Typs Character (char). In Java wird eine Zeichenkette als Array des Datentyps Character angelegt. Ein String kann somit auch aus einem 53 Character-Array erzeugt werden. Auf die einzelnen Buchstaben im String kann somit auch wie beim Array über den Index zugegriffen werden. Beispiel: char[] meinText = {’d’,’e’,’r’,’ ’,’T’,’e’,’x’,’t’}; String meinString = new String(meinText); char erstesZeichen= meinString.charAt(0); Bei diesem Beispiel wird als erstes ein char-Array der Länge 8 erzeugt und direkt mit acht Zeichen initialisiert. Auf Basis dieses Arrays wird dann ein String erzeugt, der den Text „der Text“ enthält. Aus diesem String wird anschliessend das erste Zeichen ausgelesen. Will man alle Zeichen eines Textes auf diese Weise einzeln auslesen, kann man eine Schleife einsetzen: for (int i=0; i<meinString.length(); i++){ System.out.print(meinString.charAt(i)); } 54 Selbstständiger Teil 3.5 Bowling 3.5.1 Einführung Beim Bowling werden die Resultate typischerweise in einer Tabelle aufgeschrieben und ausgewertet. Aufgeschrieben wird die Anzahl umgeworfener Pins jeder Runde. Es sind somit Zahlen zwischen 0 (keiner getroffen) und 10 (alle getroffen, ein sogenannter Strike) möglich. Spieler 1 2 3 4 6 2 2. Runde 2 8 0 3. Runde 10 2 5 4. Runde 3 4 5 5. Runde 6 8 10 Summe 25 28 22 1. Runde Tabelle 3.1: Resultate eines Bowlingspieles. 3.5.2 Aufgabenstellung Ihr Programm soll die Resultate von 3 Spielenden über 5 Runden hinweg aufnehmen und auswerten (siehe Tabelle 3.1). Zum Speichern der Resultate wird ein zweidimensionales Array und zur Berechnung der Summen ein eindimensionales Array benötigt. 55 3.5.3 Zwischenschritte Gehen Sie wie folgt vor: • Deklarieren der Variablen: Deklarieren Sie die Variable resultate als zweidimensionales Integer-Array (3 Spieler, 5 Runden) und summen als eindimensionales Integer-Array. • Einlesen der Resultate: Lesen Sie die Resultate in das Array resultate ein. Es sollen für jede der 5 Runden die Punkte für jeden der 3 Spieler eingegeben werden können. • Berechnen der Resultate: Hier soll im Array summen die Summe der Punkte jedes einzelnen Spielers gespeichert werden. • Ausgeben der Resultate: Geben Sie die Punktetabelle (resultate) und die Summen (summen) in tabellarischer Form auf dem Bildschirm aus. 3.5.4 Erweiterungen • Überprüfen Sie, ob die eingegebene Zahl erlaubt ist. • Geben Sie am Ende aus, wer wie viele Strikes geschafft hat, und wie oft jede Person keinen Pin getroffen hat. • Passen Sie ihr Programm so an, dass die Anzahl der Runden und die Anzahl der Spieler am Anfang eingegeben werden können. • Geben Sie aus, wer die meisten Punkte hat. • Berechnen Sie, in welcher Runde die jeweiligen Spieler ihren ersten Strike geschafft haben, und geben Sie das Resultat am Bildschirm aus. 3.6 Tic Tac Toe 3.6.1 Einführung Beim Tic Tac Toe (auch 3 gewinnt) spielen zwei Spieler gegeneinander. Abwechselnd setzen die Spieler ihr Zeichen (z.B. x oder o) in eines der leeren Felder. Gewonnen hat derjenige Spieler, der eine Spalte, Zeile oder Diagonale mit seinem Zeichen vollständig besetzen kann. 3.6.2 Aufgabenstellung • Zwei Spieler sind abwechslungsweise an der Reihe. • Bei jedem Spielzug muss deutlich gemacht werden, welcher der beiden Spieler an der Reihe ist. 56 • Nach jedem Spielzug soll das aktuelle Spielbrett ausgegeben werden. • Es soll angezeigt werden, wenn eine Person gewonnen hat. 3.6.3 Zwischenschritte • Spielen Sie das Spiel zunächst auf Papier. Welche Schritte werden nacheinander ausgeführt? • Programmieren Sie dann folgende Schritte: – Definieren Sie ein 3 × 3-Spielbrett vom Typ Character. – Fügen Sie eine Variable spieler ein, welche zwei Werte (z.B. 1 und 2) annehmen kann. – Setzten Sie alle Werte im Feld auf Leerzeichen (' '). – Programmieren Sie eine Ausgabe des Spielfeldes. 1 2 3 -------1: | | | 2: | | | 3: | | | Spieler 1: • Über eine Benutzereingabe soll der Spieler das Feld eingeben können, in welches sein Zeichen gesetzt werden soll. • Setzen Sie die jeweiligen Zeichen (z.B. x oder o) in die vom Spieler gewünschten Felder. • Wiederholen Sie die Spielzüge, bis das Spielbrett voll ist oder jemand gewonnen hat. 3.6.4 Erweiterungen Überprüfen Sie, bevor ein Spielzug ausgeführt wird, ob an der gewünschten Stelle bereits ein Zeichen gesetzt worden ist. Steht an dieser Stelle bereits ein Zeichen, soll der Spieler eine neue Eingabe machen müssen. Das gleiche soll passieren, wenn ein Spieler eine Eingabe macht, die ausserhalb des Spielfeldes liegt. 57 3.7 Such- und Sortieralgorithmen 3.7.1 Einführung Das Suchen in gesammelten Daten und das Sortieren von Daten sind zwei der häufigsten Aufgaben, mit denen sich ein Programmierer konfrontiert sieht. Zu diesen Themen gibt es mittlerweile unzählige Bücher, denn da Such- und Sortieralgorithmen so oft verwendet werden, ist es besonders wichtig, dass sie so effizient wie möglich programmiert werden. Ferner gibt es eine grosse Anzahl von Strategien, die verfolgt werden können, um einen Sortieralgorithmus umzusetzen. In den Entwurf und die Analyse dieser Algorithmen wurde seit Mitte des zwanzigsten Jahrhunderts viel Energie gesteckt. Wir werden hier nur eine kleine Auswahl kennenlernen. Vorbereitendes Laden Sie das Ausgangsprogramm lottozahlen.java auf Ihren Rechner. Studieren Sie das Programm. 3.7.2 Suchalgorithmen Es gibt, wie oben erwähnt, verschiedene Suchalgorithmen, die sich in ihrem Aufbau und ihrer Effizienz unterscheiden. Die bekanntesten sind die lineare und die binäre Suche. Wir wollen hier die lineare Suche betrachten. So funktioniert die lineare Suche Eine Menge von Elementen (z.B. ein Array) wird nach einem bestimmten Element durchsucht. Die Suche beginnt beim ersten Element, und die Elemente werden in der Reihenfolge durchlaufen in der sie abgespeichert sind. Entspricht das betrachtete Element dem gesuchten Element, wird die Suche beendet, ansonsten wird weiter gesucht. 58 Aufgaben Durchsuchen Sie das Array nach dem höchsten Wert und geben Sie den Wert und die Position der Daten in der Konsole aus. So wird nach dem maximalen Wert gesucht • Die Position des (momentanen) Maximums wird in der Variable max gespeichert. • Zuerst wird das erste Element des Arrays als das Maximum angenommen. • Es werden nun alle Elemente des Arrays (ausser des ersten) durchlaufen. • Ist der Wert des Feldes an der momentanen Position grösser als das bisher angenommene Maximum, dann wird diese Position in max gespeichert. 3.7.3 Erweiterungen • Suchen Sie im Array auf die gleiche Weise auch das Minimum. Verwenden Sie für beide Suchen eine gemeinsame Schleife. • Überlegen Sie sich, was passiert, wenn der gesuchte Wert mehr als einmal vorkommt. Wie müsste ihr Programm darauf reagieren? • Wie beurteilen Sie den Suchaufwand? Haben Sie Ideen für eine Optimierung? 3.7.4 Sortieralgorithmen Das Ziel von Sortieralgorithmen ist es, die Elemente einer Menge nach einem bestimmten Kriterium zu sortieren. Nach dem Sortieren liegen die Elemente in aufsteigender oder absteigender Reihenfolge vor. Es gibt verschiedene Sortieralgorithmen, die sich in ihrem Aufbau und ihrer Effizienz unterschieden. Bekannte Vertreter sind Bubble-Sort, Insertion-Sort, Merge-Sort und Quick-Sort. 59 So funktioniert Bubble-Sort (für eine aufsteigende Sortierung, siehe Abbildung 3.1) 1. Es werden jeweils zwei benachbarte Elemente eines Arrays verglichen. Begonnen wird mit dem Element mit dem Index 0 und 1, dann 1 und 2, dann 2 und 3, etc. 2. Wenn der Wert des linken Elements grösser ist als der Wert des rechten, werden die beiden Werte vertauscht. Hinweis: Vorsicht swap! 3. Mit einem Arraydurchlauf „wandert“ so das grösste Element ans Ende des Arrays. 4. Nun werden die Schritte 1 bis 3 wiederholt, um das zweitgrösste Element an die zweitletzte Position zu bringen. Hinweis: Der Vergleich des zweitletzten mit dem letzten entfällt, da das letzte das grösste ist. 5. Die Schritte 1 bis 4 werden so lange wiederholt, bis die zwei kleinsten Elemente miteinander verglichen werden. Aufgabe Versuchen Sie den Bubble-Sort-Algorithmus zu implementieren! Erweiterungen • Wie könnte die Effizienz von Bubble-Sort erhöht werden? 60 Abbildung 3.1: Bubble-Sort. Details siehe Text. 61 Programmieren mit Java Modul 4 Methoden und Funktionen Theorieteil Autoren: Lukas Fässler, Barbara Scheuner, David Sichau Begriffe Methode Sichtbarkeit von Variablen Subroutine Überladen Prozedur Rekursion Funktion Parameter Exception Rückgabewert Zufallszahl Rückgabetyp Modularität 63 Theorieteil 4.1 Modulübersicht Durch das Modularitätsprinzip wird ein Gesamtproblem in getrennte Teilprobleme zerlegt. In diesem Modul lernen Sie Möglichkeiten kennen, wie Sie Anweisungen in Unterprogrammen (oder Subroutinen) zusammenfassen können. Unterprogramme sind funktionale Einheiten, die von mehreren Stellen in einem Programm aufgerufen werden können. Auf diese Weise muss ein Programmteil nur einmal entwickelt und getestet werden, wodurch sich der Programmieraufwand verringert und der Programmcode verkürzt. Werden beim Aufrufen des Unterprogramms Daten übergeben, werden diese als Parameter bezeichnet. In vielen Programmiersprachen werden zwei Varianten von Unterprogrammen unterschieden: jene mit einem Rückgabewert (Funktionen) und jene ohne Rückgabewert (Prozeduren). In Java bezeichnet man beide Varianten generell als Methoden, die Parameter und Rückgabewert haben können. 4.2 Methoden In Java besteht eine Methode aus einem Kopf (oder Signatur) mit den Modifikatoren public static1 , dem Rückgabetyp, dem Namen, den Parametern sowie dem Rumpf in geschweiften Klammern {}. Die Ausführung einer Java-Applikation startet meist in der Haupt oder main-Methode, die sich oft darauf beschränkt, andere Methoden aufzurufen. 4.2.1 Methoden ohne Rückgabewert (Prozeduren) Methoden ohne Rückgabewert haben Sie bereits verwendet: Zum Beispiel die Methode System.out.println(), welche einen Zeilenumbruch auslöst. Sie können aber auch eigene Methoden schreiben, welche von Ihnen vorgegebene Aufgaben erfüllen. Methoden ohne Rückgabewert sehen wie folgt aus: 1 Die Bedeutung der Modifikationen ergeben sich aus der Objektorientierung von Java und werden in diesem Buch nicht behandelt. 64 Schreibweise: void methodenName() { //Signatur der Methode // Anweisungen } Das Wort void gibt dabei an, dass kein Rückgabewert erwartet wird. Die leere Klammer () bedeutet, dass keine Parameter übergeben werden. Beispiel: Die folgende Klasse beinhaltet die Haupt-Methode mit dem Namen main und die Methode ausgabe, welche in der main-Methode aufgerufen wird: public class ProzedurTest{ public static void main(String[] args){ ausgabe(); } public static void ausgabe(){ //Signatur der Methode System.out.println("die Methode wurde ausgeführt"); } } 4.2.2 Methoden mit Rückgabewert (Funktionen) Methoden mit Rückgabewert werden verwendet, um ein Resultat aus den Anweisungen in der Methode zu gewinnen. Der Datentyp des Rückgabewerts steht dabei direkt vor dem Methodennamen. In der Methode selbst muss sichergestellt werden, dass in jedem Fall ein Wert vom entsprechenden Typ zurückgegeben wird. Um einen Wert zurückzugeben, wird das Wort return verwendet. Schreibweise: rückgabetyp methodenName() { //Signatur der Methode // Anweisungen } 65 Beispiel: Die folgende Klasse beinhaltet eine main-Methode und eine weitere Methode getAnswer, welche in der main-Methode aufgerufen wird: public class ProzedurTest{ public static void main(String[] args){ int wert = getAnswer(); } public static int getAnswer(){ //Signatur der Methode return 42; } } Der Rückgabewert in obrigen Beispiel ist vom Typ Integer. Bei Methoden mit Rückgabewert muss darauf geachtet werden, dass in jedem Fall, der eintreffen könnte, zwingend ein Wert zurückgegeben wird. Beispiel: boolean hallo(){ int i = 1; if (i == 1){ return true; } else { System.out.println("tritt nie ein"); } } Auch wenn der else-Fall in dieser Methode nie eintrifft, muss darauf geachtet werden, dass auch in diesem Fall etwas zurückgegeben würde. Korrekt wäre somit: boolean hallo(){ int i=1; if (i==1){ return true; } else { System.out.println("tritt nie ein"); return false; } } 66 4.2.3 Methoden mit Parametern Beide Arten von Methoden (also Prozeduren und Funktionen) können in der Signatur Parameter verlangen. Parameter sind Variablen, deren Initialisierung beim Aufruf der Methode geschieht und die zur Erfüllung der Aufgabe der Methode nötig sind. Beispiel: void addiereUndGibAus(int x, int y){ int z = x + y; System.out.println("Summe: "+z); } Diese Methode kann nun z.B. mit den Werten 3 und 2 aufgerufen werden mit der Anweisung: addiereUndGibAus(3,2); Methoden mit Rückgabewert können nun auch noch einen Wert zurückgeben. Beispiel: int addiere (int x, int y){ int z = x + y; return z; } Diese Methode kann nun z.B. aufgerufen werden mit: int resultat = addiere(3,2); Der Rückgabewert wird hier in der Variable resultat gespeichert, deren Datentyp mit dem Rückgabetyp übereinstimmt. 4.3 Methoden aus der Klasse Math Die Klasse Math bietet einige Methoden an, welche mathematische Funktionen realisieren. All diese Methoden sind Funktionen, weil sie einen Rückgabewert (das Resultat der Berechnung) besitzen. Als Übergabeparameter erwarten sie meist eine oder zwei Zahlen. 67 Beispiel: public class TestMath{ public static void main(String[] args){ double x= 19.7; double y= 20.3; double groessereZahl = Math.max(x,y); double kleinereZahl = Math.min(x,y); double abrunden = Math.ceil(x); } } Eine Methode ohne Parameter in der Klasse Math ist die Funktion Math.random(). Diese Funktion gibt eine Zufallszahl zwischen 0 und 1 zurück, wobei die Zahl gleich 0 sein kann, aber immer kleiner als 1 ist. Mit Hilfe dieser Methode kann eine zufällige ganze Zahl zwischen 1 und x erzeugt werden. Dies geschieht in drei Schritten: 1. Erzeugen einer Zufallszahl zwischen 0 und 1. 2. Multiplikation dieser Zahl mit x. Dies ergibt eine Gleitkommazahl Zahl zwischen 0 und x. 3. Runden dieser Zahl. Beispiel: // Generierung ganzer Zahl zwischen 0 und 99 durch abrunden int zufallsZahl1 = (int)(Math.random()*100); // Generierung ganzer Zahl zwischen 0 und 100 durch runden long zufallsZahl2 = Math.round(Math.random()*100); 4.4 Überladen von Methoden Verschiedene Methoden können denselben Namen haben, wenn sie unterschiedliche Parameter erhalten. Unterschiedliche Rückgabewerte alleine sind nicht ausreichend. Haben zwei Methoden denselben Namen, aber erhalten unterschiedliche Typen oder Anzahl von Parameter, nennt man die Methoden überladen. Ein populäres Beispiel, das Sie schon oft verwendet haben, ist die Methode print() bzw. println(). Dieser Methode können Werte von Typ String, int, double, etc. übergeben werden. Für Sie als Benutzer sieht es aus, als würden Sie immer dieselbe Methode aufrufen. Tatsächlich ist es aber so, dass unterschiedliche Methoden entsprechend dem Datentyp aufgerufen werden. 68 Wir können also die beiden folgenden Methoden mit identischem Namen linie in der gleichen Klasse schreiben: // Signatur mit einem Parameter public static void linie(int x){ for (int i=0; i<x; i++){ System.out.print(’-’); } } // Signatur mit zwei Parametern public static void linie(int x, char c){ for (int i=0; i<x; i++){ System.out.print(c); } } Wenn Sie nun die Methode mit nur einem Parameter-Wert aufrufen, wird die erste Methode verwendet, mit zwei entsprechenden Werten die zweite: linie(8); linie(19, ’-’); 4.5 Gültigkeitsbereiche von Variablen Variablen haben eine beschränkte Lebensdauer. Einige Variablen werden erst mit dem Beenden des Programms gelöscht, andere schon früher. Eine Variable ist immer für den Anweisungsblock (siehe Modul 2) oder eine Klasse gültig, in dem sie deklariert wurde. Eine Variable, welche am Anfang einer Methode deklariert wird, wird auch wieder gelöscht, wenn die Methode beendet wird. Wenn man den Wert dieser Variablen später noch benötigt, muss er an die aufrufende Methode zurückgegeben werden. 69 Beispiel: public class VariablenSichtbarkeit{ public static int global = 20; public static void main(String[] args){ System.out.println("Werte zwischen 1 und "+ global); ausgabe(); } public static void ausgabe(){ int zufall = 0; for (int i=0; i<30; i++){ zufall = (int) (Math.random() * global) + 1; } } } Die Variable global ist dabei für die ganze Klasse gültig. Die Variable zufall ist hingegen nur innerhalb der Methode ausgabe gültig und die Variable i nur innerhalb der Schleife. 70 Beispiel: Folgender Code würde, wenn die kommentierte Zeile verwendet würde, zu einer Fehlermeldung führen, da die Variable zufall in der main-Methode nicht gültig ist: public class VariablenSichtbarkeit2{ public static void ausgabe(){ int zufall = (int) (Math.random() * global) + 1; } public static void main(String[] args){ ausgabe(); // System.out.println("Werte von 1 bis "+ zufall); } } 4.6 Rekursion Bei der Rekursion ruft eine Methode sich selber wieder auf. Damit sich die Methode nicht endlos immer wieder selbst aufruft, was die gleichen Konsequenzen wie eine Endlosschleife hätte, benötigt sie eine Abbruchbedingung, welche diese Folge von Selbst-Aufrufen stoppt. Um eine Rekursion zu programmieren, müssen Sie zwei Elemente bestimmen: • Basisfall: in diesem Fall ist das Resultat der Berechnung schon bekannt. Dieser Fall ist die Abbruchbedingung der Rekursion. • Rekursiver Aufruf: es muss bestimmt werden, wie der rekursive Aufruf geschehen soll. 4.6.1 Beispiel 1: Fakultät Die Berechnung der Fakultät f (x) = x! von x kann mit einer rekursiven Methode realisiert werden. • Basisfall: für die Werte 0 und 1 ist die Fakultät 1. • Rekursion: x! = x · (x − 1)! für x > 1. 71 int fakultaet(int x){ if ((x == 0) || (x == 1){ // Basisfall return 1; } else { return x * fakultaet(x-1); // Rekursiver Aufruf } } 4.6.2 Beispiel 2: Fibonacci Ein weiteres beliebtes Beispiel für Rekursionen ist die Fibonacci-Folge. Diese unendliche Folge von natürlichen Zahlen beginnt mit 0 und 1. Die danach folgende Zahl ergibt sich jeweils aus der Summe der zwei vorangegangenen Zahlen: Die Folge lautet also 0, 1, 1, 2, 3, 5, 8, . . . Bei der Fibonacci-Folge sind bei jedem Schritt zwei rekursive Aufrufe nötig: f (n) = f (n − 1) + f (n − 2) für n ≥ 2 mit den Anfangswerten f (1) = 1 und f (0) = 0. • Basisfall: Für den Fall, dass n gleich 0 oder 1 ist, wissen wir, dass 0 bzw. 1 zurückgegeben werden muss. • Rekursion: Für alle anderen Fälle rufen wir die Funktion wieder auf, wobei wir den übergebenen Wert um jeweils 1 und 2 verringern. int fibonacci(int n) { if (n == 0){ // Basisfall 1 return 0; } else if (n == 1){ // Basisfall 1 return 1; } else { // zwei Mal rekursiver Aufruf return (fibonacci(n-1) + fibonacci(n-2)); } } Beachten Sie, dass Funktion fibonacci in Beispiel 2 sich selbst gleich zweimal aufruft. Man spricht auch von kaskadenförmiger Rekursion. In Beispiel 1 erfolgt nur ein einzelner Selbstaufruf, was man als lineare Rekursion bezeichnet. 72 4.7 Fehlerbehandlung mit Exceptions Exceptions (Ausnahmen) werden durch Ausnahmesituationen bei der Programmausführung ausgelöst. Dadurch sollen Programmabbrüche verhindert werden. In Java ermöglicht die try-catch-Anweisung das Auffangen und Behandeln von Exceptions innerhalb einer Methode. Beispiel: Vielleicht wollten Sie schon einmal aus Versehen einen Text „jk“ in eine Zahl umwandeln oder in einem Array auf einen Index zugreifen, der ausserhalb des Arrays liegt. Exceptions müssen nicht zwingend bis zum Benutzer durchdringen (d.h. auf der Konsole erscheinen). Man kann sie auch abfangen (catch) und eine Lösung für das Problem suchen. Abgefangen können die Exceptions mit: try { // Irgendetwas, dass einen Fehler auslösen könnte } catch (Exception c) { // Lösung des Problems } Beispiel: Hier könnte der Benutzer anstelle einer ganzen Zahl zum Beispiel ein Zeichen eingeben. Diese Fehlereingabe kann z.B. wie folgt behandelt werden: public static void main(String[] args) { Scanner scan = new Scanner(System.in); int zahl = 0; System.out.println("Bitte eine ganze Zahl eingeben"); String wert = scan.nextLine(); try { zahl = Integer.parseInt(wert); } catch (Exception e){ System.out.println("Das ist keine ganze Zahl"); Zahl = 0; } } 4.7.1 Werfen einer Exception Sie können das Konstrukt der Exception auch selber verwenden. Wenn Sie in einer eigenen Methode einen Fehler kommunizieren wollen, dann können Sie dort eine Exception werfen 73 (throw). Mit der Anweisung throw new Exception() lösen Sie einen Fehler aus. Die Signatur einer Methode, welche eine Exception auslösen kann, muss noch durch den Zusatz throws Exception (heisst so viel wie wirft Fehlermeldungen) ergänzt werden. Beispiel: public class ExceptionTester{ public static void main(String[] args) { try { tester(-3); } catch (Exception e) { System.out.println(e.getMessage()); } } public static void tester(int x) throws Exception{ if (x < 0){ throw new Exception(); } } } 74 Selbstständiger Teil 4.8 Erweiterungen Pandemie-Simulation 4.8.1 Einführung Erweitern Sie Ihre Pandemie-Simulation aus dem E.Tutorial® wie folgt: • Setzen der Startbedingungen: Dauer der Simulation, Ansteckungswahrscheinlichkeit, etz. • Die Person, welche am Anfang krank ist, könnte zufällig ausgewählt werden. • Die Ansteckungswahrscheinlichkeit könnte auch zufällig sein. 4.8.2 Setzen der Startbedingungen Lassen Sie vor dem Start der Simulation den User die Startbedingungen setzen: • Wie lange soll die Simulation dauern? • Wie ansteckend ist die Krankheit? Die Ansteckungswahrscheinlichkeit x = 1/n soll mit einer ganzen Zahl n angegeben werden können. Beachten Sie bei jedem Punkt, ob die Variable global oder lokal definiert werden soll. Die Benutzereingabe kann wie gewohnt mit dem Scanner über die Konsole vorgenommen werden. 4.8.3 Erweiterungen Implementieren Sie eine der folgenden Ideen: • • • • Vergrössern Sie die Simulation (z.B. 100 × 100). Lassen Sie den Nutzer die Grösse der Simulation auswählen. Die Zahlen könnten noch farbig gestaltet werden (siehe www.processing.org). Viele Krankheiten sind zu Beginn ansteckender als am Ende. Machen Sie die Ansteckungswahrscheinlichkeit abhängig von der Dauer der Krankheit der bereits Erkrankten. 75 • Geben Sie eine Tabelle aus, in welcher die wichtigsten Kennzahlen ersichtlich sind, z.B.: Simulationsschritt: 20 Noch nicht erkrankte Personen: 40 Kranke Personen: 64 Geheilte Personen: 36 • Wenn viele Nachbarn krank sind, soll die Wahrscheinlichkeit grösser sein, dass eine gesunde Person angesteckt wird. • Geheilte Personen sind nur eine gewisse Zeit immun gegen die Krankheit. Passen Sie Ihre Simulation so an, dass geheilte Personen nach einer gewissen Zeit (z.B. 4 Tagen) wieder angesteckt werden können. 76 Programmieren mit Java Modul 5 Objekte Theorieteil Autoren: Lukas Fässler, Barbara Scheuner, David Sichau Begriffe Klassen Attribut Objekte Objekt-Methode Objekt-Eigenschaft Referenztypen 77 Theorieteil 5.1 Modulübersicht Ziel der objektorientierten Programmierung ist es, analog zur „realen Welt“, Daten mit ihrer zugehörigen Funktionalitäten in Form von Objekten abzubilden. Während bei der prozeduralen Programmierung Daten und die auf ihnen durchgeführten Operationen voneinander getrennt werden, sind bei der objektorientierten Programmierung Daten und Operationen in einer einzigen Struktur, der Klasse, vereinigt. Eine Klasse stellt eine Beschreibung („Schablone“ oder „Bauplan“) für Objekte dar, die durch gleiche oder ähnliche Eigenschaften und Verhaltensweisen charakterisiert sind. Von einer Klasse können mehrere Objekte (Exemplare oder Instanzen) erzeugt werden. 5.2 Klassen und Objekte Objekte dienen dazu, Daten (Variablen) und ihre dazugehörigen Funktionalität (Methoden) zu verwalten. Aus welchen Variablen und Methoden ein Objekt aufgebaut ist, wird in der zugehörigen Klasse festgelegt. Man kann sich die Variablen einer Klasse auch als Elemente einer Liste vorstellen, wie zum Beispiel: Name Vorname Jahrgang Beruf p1 Mueller Hans 1942 Maurer p2 Meier Maria 1954 Elektrikerin p3 Koller Andreas 1991 Student Die Klasse (analog zur Tabellenüberschrift) gibt an, dass in den Objekten (hier die einzelnen Zeilen) die Variablen name, vorname, jahrgang und beruf abgespeichert werden sollen. Die zugehörigen Objekte p1, p2 und p3 sehen wie folgt aus: 78 p1: Person p2: Person p3: Person name: Mueller name: Meier name: Koller vorname: Hans vorname: Maria vorname: Andreas jahrgang: 1942 jahrgang: 1954 jahrgang: 1991 beruf: Maurer beruf: Elektrikerin beruf: Student Objektmethoden arbeiten mit diesen Informationen. Über eine Methode kann man zum Beispiel die Informationen über eine Person abfragen oder die Informationen updaten. Dabei gilt, dass die Methoden für jedes Objekt existieren und deshalb mit den Variablenwerten (Zustände) des zugehörigen Objekts arbeiten. 5.2.1 Klassen Eine Klasse enthält die Vorschrift, was alles zu einem Objekt gehört. In unserem Fall sind dies Name, Vorname, Jahrgang und Beruf. Eine Klasse wird mit dem Befehl class definiert. Schreibweise: public class Person { // Eigenschaften des Objekts (Objektvariablen) // Funktionalität des Objekts (Objektmethoden) } 5.2.2 Objektvariablen und Methoden Objektvariablen und Methoden definieren alle Elemente, welche in jedem Objekt vorkommen. Diese werden alle nicht static deklariert und dadurch an ein Objekt und nicht an die Klasse gebunden. Die Objektmethoden, meist einfach als Methoden bezeichnet, arbeiten mit diesen Variablen. Meistens sind die Methoden dazu da, die Variablen zu verändern, ihren Wert bekannt zu geben, oder aufgrund von mehreren Variablen einen Zustand zu berechnen. Objektvariablen (Attribute) Die Attribute sind die Eigenschaften, welche in einem Objekt gespeichert werden. In diesen Variablen kann der Zustand eines Objekts gespeichert werden. Unsere Klasse Person, welche die Vorschrift für ein Personenobjekt enthält, sieht dann wie folgt aus: 79 public class Person { String name; String vorname; int jahrgang; String beruf; } Objektmethoden Klassen können nicht nur Daten in Form von Variablen speichern, sondern auch Funktionalitäten anbieten. Diese Funktionalität ermöglicht die Verarbeitung der Daten. Für die Klasse Person kann z.B. eine Methode angeboten werden, welche die Informationen zu einer Person ausgibt (print). Eine weitere Methode gibt das Alter der Person zurück, wenn das aktuelle Jahr übergeben wird (altersAusgabe): public class Person { String name; String vorname; int jahrgang; String beruf; void print(){ System.out.println(name +"/"+ vorname); System.out.println(jahrgang +"/"+ beruf); } int altersAusgabe(int jahr){ int alter= jahr-jahrgang; System.out.println(alter); } } Einer Objektmethode stehen ausser den Parametern, welche der Methode übergeben werden, auch noch die Objektvariablen (Attribute) zur Verfügung. 5.2.3 Erstellen von Objekten unter Verwendung einer Klasse Damit man eine Person verwenden kann, muss als erstes ein Objekt dieses Typs erzeugt werden. So deklariert und erzeugt man beispielsweise das Objekt p1 vom Typ Person: 80 1. Deklaration: Referenz auf die Klasse Person setzen und mit p1 benennen: Person p1; Im Unterschied zu primitiven Typen ist der Standardwert null anstelle von 0. 2. Erzeugung: Mit new eine Instanz der Klasse Person erzeugen und in der Referenz p1 speichern: p1 = new Person(); Meistens werden die beiden Schritte in einer Anweisung geschrieben: Person p1 = new Person(); Das Erzeugen von Objekten geschieht meist in einer weiteren Klasse, welche ihre Daten in der angebotenen Klasse Person speichern möchte. Die Klasse Personalverwaltung möchte zum Beispiel neue Personen-Objekte erzeugen können. Angenommen, diese Personalverwaltung sei der Startpunkt unseres Programms, dann könnte diese z.B. so aussehen: public class Personalverwaltung { public static void main(String[] args){ //Deklaration Person p1; //Erzeugung eines neuen Objektstion p1 = new Person(); //Zuweisung von Werten für die Attribute p1.name = "Müller"; p1.vorname = "Andreas"; p1.jahrgang = 1960; p1.beruf = "Maurer"; } } Unterschiede zwischen primitiven Datentypen und Referenztypen Wenn eine Variable ein Objekt verwalten soll, dann nennt man diese Variable eine Referenzvariable. Bei der Deklaration gibt es zwischen Referenztypen und primitiven Datentypen (int, char, double, etc.) keine Unterschiede. Bei der Initialisierung hingegen gibt es Unterschiede. Referenztypen halten Referenzen auf Objekte und bieten ein Mittel, um auf die Objekte zuzugreifen, die irgendwo im Speicher abgelegt sind, ähnlich einem Link oder einer Verknüpfung. Die Speicherorte selbst sind für das Programmieren irrelevant. 81 Beispiel: Angenommen, wir möchten bei unserem Beispiel eine neue Person p2 erzeugen (dies würde dem Einfügen einer neuen Zeile in der Tabelle entsprechen), reicht es nicht, die Referenz zu kopieren. Folgendes Beispiel demonstriert, weshalb wir zunächst per new-Operator ein neues Objekt erzeugen müssen, bevor wir die Werte der Variablen neu setzen können. Gegeben seien: int wert = 8; Person p1 = new Person(); Der aktuelle Speicher präsentiert sich wie in Abbildung 5.1: wert 8 p1 Referenz name null vorname null jahrgang 0 beruf null Abbildung 5.1: Speicher nach Erzeugung eines neuen Objektes. Im Speicher des primitiven Datentyps befindet sich eine 8, die zum Variablennamen wert gehört. Beim Referenztyp befindet sich eine Referenz auf ein Personenobjekt, welche unter dem Variablennamen p1 erreichbar ist. Im Speicherbereich der Person ist zu sehen, dass nur beim Jahrgang ein Wert gespeichert ist. Dies ist so, weil String ebenfalls eine Klasse bezeichnet (beachten Sie die Grossschreibung). Ist noch nicht bestimmt, auf welchen Speicherbereich sich eine Referenzvariable bezieht, wird null initialisiert. Mit folgenden Zeilen werden die Attribut-Werte zugewiesen: p1.name = "Meier"; p1.vorname = "Hans"; p1.jahrgang = 1960; p1.beruf = "Maler"; Der Speicher ändert sich wie in Abbildung 5.2 dargestellt. Nun führen wir folgende Zeilen aus: int wert2 = wert; Person p2 = p1; Der Speicher präsentiert sich nun wie in Abbildung 5.3 dargestellt. 82 wert 8 Referenz p1 Referenz name Referenz vorname jahrgang Meier Hans 1960 Referenz beruf Maler Abbildung 5.2: Speicher nach Zuweisung der Attributswerte. wert 8 Referenz p1 nz wert2 8 Referenz name re fe Re Referenz vorname jahrgang Meier Hans 1960 Referenz beruf Maler p2 Abbildung 5.3: Speicher nachdem p2 mit p1 initalisiert wurde. Beim primitiven Datentyp wird neuer Speicher bereitgestellt, der Wert wird kopiert. Bei der Referenzvariablen wird die Referenz kopiert, nicht aber das ganze Objekt. Werden nun die folgenden Zeilen ausgeführt, sieht der Speicher anschliessend wie in Abbildung 5.4 aus. wert2 = 10; p2.jahrgang = 1967; wert 8 Referenz p1 wert2 10 Referenz name f Re z en er Referenz vorname jahrgang beruf Meier Hans 1967 Referenz Maler p2 Abbildung 5.4: Speicher nachdem das Attribut p2 verändert wurde. Folgende Tabelle fasst die Unterschiede zwischen Referenztypen und primitive Typen 83 zusammen. Primitive Datentypen Referenztypen Anzahl Umfasst die Typen boolean, char, byte, short, long, float und double Die Anzahl von Referenztypen ist unbegrenzt, da diese vom User definiert werden. Default-Wert 0 null Speicherort Der Speicherort speichert die tatsächlichen Daten, die der primitive Typ enthält. Der Speicherort speichert eine Referenz auf die Daten. Zuweisung an eine an- Wird der Wert eines primitiven dere Variable Typs einer anderen Variablen zugewiesen, wird eine Kopie erstellt. Wird einem Referenztyp ein anderer Referenztyp zugewiesen, zeigen beide auf das gleiche Objekt. Übergabe an Methode 84 Wird einer Methode ein pri- Wird eine Methode ein Objekt mitiver Typ übergeben, wird übergeben, kann die aufgerufenur eine Kopie des primitiven ne Methode den Inhalt des ObWerts übergeben. Die aufgeru- jekts ändern, nicht aber seine fene Methode hat keinerlei Zu- Adresse griff auf den ursprünglichen primitiven Wert und kann ihn deswegen nicht ändern. Die aufgerufene Methode kann den kopierten Wert ändern. Selbstständiger Teil 5.3 Hotel-Verwaltung 5.3.1 Aufgabenstellung In dieser Aufgabe sollen Sie Hotel-Objekte in einem Ort inklusiv Buchungsmöglichkeit erstellen. Die Klasse Hotel soll aus folgenden Attributen und Methoden bestehen: Hotel int stockwerke int zimmerProStockwerk int belegung int getZimmer() void einchecken() void auschecken() void printInfo() Eigenschaften (Attribute): • stockwerke: Hier wird angegeben, wie viele Stockwerke das Hotel hat. • zimmerProStockwerk: Hier wird angegeben, wie viele Zimmer sich auf einem Stockwerk befinden. • belegung: In diesem Attribut wird gespeichert, wie viele Zimmer aktuell belegt sind. 85 Methoden: • int getZimmer(): In dieser Methode wird zurückgegeben, wie viele Zimmer im Hotel zur Verfügung stehen. • void einchecken(): In dieser Methode wird der Wert der Belegung um eins erhöht. • void auschecken(): In dieser Methode wird der Wert der Belegung um eins dekrementiert. • void printInfo(): Mit dieser Methode wird auf der Konsole ausgegeben, wie viele Zimmer das Hotel hat, und wie viele davon aktuell belegt sind. Verwenden Sie dazu die Methode getZimmer(). 5.3.2 Zwischenschritte • Erstellen Sie die Klasse Hotel mit den angegebenen Eigenschaften. • Erstellen Sie eine Klasse Ferienort mit main-Methode sowie mindestens 3 Hotels. Setzen sie die Eigenschaften der Hotels. • Checken Sie einige Personen ein und wieder aus, und geben Sie die Informationen zu den Hotels aus. Die Ausgabe könnte z.B. wie folgt aussehen: Hotel Edelweiss 5 von 40 belegt Hotel Astoria 4 von 200 belegt Hotel Alpenblick 10 von 30 belegt 5.3.3 Erweiterungen • Ändern Sie die Methode einchecken so, dass zurückgegeben wird, ob im Hotel noch Platz ist. Geben Sie zurück, ob die Person einchecken konnte oder nicht (ja = true, nein = false). • Ändern Sie die Methode auschecken analog, dass keine Personen mehr auschecken können, sobald keine Personen mehr im Hotel sind. 86