Informatik 4. OOP mit Java

Berner Fachhochschule Technik und Informatik Fachbereich Elektro- und Kommunikationstechnik Labor für Technische Informatik Informatik 4 OOP mit Java...
Author: Brit Adenauer
5 downloads 2 Views 459KB Size
Berner Fachhochschule Technik und Informatik Fachbereich Elektro- und Kommunikationstechnik Labor für Technische Informatik

Informatik 4 OOP mit Java Teil I: Einführung Sprachkonstrukte Einstieg in die OOP © 2004 bis 2007 BFH-TI R. Weber, Burgdorf

Dateiname: Erstellt am: Autor: Version:

Skript_Informatik_4.doc 12. März 2007 Roger Weber 1.3

Informatik 4

Vorwort

Vorwort Das vorliegende Skript wird den Studierenden zum Unterrichtsmodul "Informatik 4" im Fachbereich Elektround Kommunikationstechnik der BFH-TI in Burgdorf abgegeben. Es umfasst inhaltlich den ersten Teil des Moduls, das Skript zum zweiten Teil stammt von Elham Firouzi. Im ersten Kapitel dieses Skripts erfolgt eine Einführung in Java: Es werden Eigenschaften, Einsatzgebiete, Vorund Nachteile dieser modernen Programmiersprache aufgezeigt sowie der Entwicklungsablauf für JavaApplikationen besprochen. Im zweiten Kapitel werden die Sprachkonstrukte von Java behandelt, dabei setzen wir auf den C-Kenntnissen aus den Modulen "Informatik 1" und "Informatik 2" auf. In Kapitel 3 erfolgt schliesslich der Einstieg in die Welt der objektorientierten Programmierung (OOP). Anhand von verschiedenen Beispielen werden Themen wie Abstraktion, Kapselung, Vererbung und Polymorphismus erklärt. Der Unterricht wird ergänzt durch Übungen sowie durch eine Projektarbeit am Ende des Moduls. Zusätzlich zum Skript empfehle ich das Buch von Guido Krüger "Handbuch der Java-Programmierung", welches in elektronischer Fassung auch auf unserem Server zugänglich ist. Alle Übungen und die Projektarbeit werden mit Hilfe der Entwicklungsumgebung "Eclipse" durchgeführt. Am Schluss dieses Moduls sollten sie in der Lage sein: 1) Das Design für eine objektorientierte Applikation in UML zu erstellen 2) Ein kleineres OOP-Projekt in Java zu programmieren

Burgdorf, März 2007 Roger Weber

Version 1.3, 12.03.07

Seite II

Informatik 4

Inhaltsverzeichnis

Inhaltsverzeichnis 1

Einstieg in Java ................................................................................................................................................1 1.1 Einleitung ................................................................................................................................................ 1 1.1.1 Eigenschaften von Java ................................................................................................................... 1 1.1.2 Einsatzgebiete von Java .................................................................................................................. 1 1.1.3 Empfohlene Bücher......................................................................................................................... 1 1.1.4 Infos zu Java.................................................................................................................................... 2 1.2 Ein erstes Beispiel ................................................................................................................................... 2 1.3 Entwicklung von Java-Programmen ....................................................................................................... 3 1.3.1 Texteditor ........................................................................................................................................ 3 1.3.2 Compiler.......................................................................................................................................... 3 1.3.3 Interpreter........................................................................................................................................ 3 1.3.4 Übersicht ......................................................................................................................................... 4 1.4 Applikationen.......................................................................................................................................... 4 1.5 Applets .................................................................................................................................................... 4 1.6 Vor- und Nachteile von Java................................................................................................................... 7 2 Funktionale Programmierung mit Java ............................................................................................................8 2.1 Einführung .............................................................................................................................................. 8 2.2 Lexikalische Elemente ............................................................................................................................ 8 2.2.1 Zeichensatz...................................................................................................................................... 8 2.2.2 Bezeichner....................................................................................................................................... 8 2.2.3 Kommentare.................................................................................................................................... 8 2.2.4 Escape-Sequenzen......................................................................................................................... 10 2.3 Schlüsselwörter ..................................................................................................................................... 11 2.4 Variablen und elementare Datentypen .................................................................................................. 11 2.4.1 Deklaration und Initialisierung...................................................................................................... 11 2.4.2 Elementare Datentypen ................................................................................................................. 12 2.4.3 Datentyp-Umwandlungen ............................................................................................................. 14 2.4.4 Sichtbarkeit ................................................................................................................................... 15 2.4.5 Globale Variablen ......................................................................................................................... 16 2.5 Zeichenketten ........................................................................................................................................ 16 2.5.1 String............................................................................................................................................. 16 2.5.2 StringBuffer................................................................................................................................... 17 2.6 Arrays.................................................................................................................................................... 18 2.6.1 Eindimensionale Arrays ................................................................................................................ 18 2.6.2 Mehrdimensionale Arrays ............................................................................................................. 20 2.7 Konstanten ............................................................................................................................................ 20 2.8 Operatoren............................................................................................................................................. 21 2.8.1 Arithmetische Operatoren ............................................................................................................. 21 2.8.2 Vergleichsoperatoren .................................................................................................................... 22 2.8.3 Logische Operatoren ..................................................................................................................... 22 2.8.4 Bitweise Logische Operatoren ...................................................................................................... 22 2.8.5 Zuweisungsoperatoren .................................................................................................................. 23 2.8.6 Fragezeichen-Operator .................................................................................................................. 23 2.8.7 Cast-Operator ................................................................................................................................ 24 2.8.8 Zusätzliche Operatoren der OOP .................................................................................................. 24 2.9 Kontrollstrukturen ................................................................................................................................. 25 2.9.1 Einführung .................................................................................................................................... 25 2.9.2 if .................................................................................................................................................... 25 2.9.3 switch ............................................................................................................................................ 26 2.9.4 while.............................................................................................................................................. 27 2.9.5 do while......................................................................................................................................... 28 Version 1.3, 12.03.07

Seite III

Informatik 4

Einstieg in Java

2.9.6 for .................................................................................................................................................. 29 2.9.7 Beeinflussung von Schleifen mit break und continue ................................................................... 30 2.10 Methoden .............................................................................................................................................. 31 2.10.1 Name, Parameterliste und Rückgabewert...................................................................................... 31 2.10.2 Signatur, Prototyp, Overloading.................................................................................................... 33 2.10.3 Rekursion ...................................................................................................................................... 34 2.11 main().................................................................................................................................................... 35 2.12 Unterschiede zu C ................................................................................................................................. 36 3 Einführung in die OOP mit Java ....................................................................................................................37 3.1 Einleitung .............................................................................................................................................. 37 3.1.1 Probleme funktionaler Programmiersprachen ............................................................................... 37 3.1.2 Abstrakte Datentypen.................................................................................................................... 37 3.1.3 Forderungen an eine OO-Programmiersprache............................................................................. 38 3.2 Eigenschaften der OOP ......................................................................................................................... 38 3.2.1 Abstraktion durch Objekte ............................................................................................................ 38 3.2.2 Kapselung...................................................................................................................................... 39 3.2.3 Vererbung und Polymorphismus................................................................................................... 39 3.2.4 Eigenschaften von Java ................................................................................................................. 40 3.3 Klassen und Objekte ............................................................................................................................. 40 3.3.1 Definitionen................................................................................................................................... 40 3.3.2 Syntax............................................................................................................................................ 40 3.3.3 Attribute ........................................................................................................................................ 41 3.3.4 Methoden ...................................................................................................................................... 41 3.3.5 Zugriffsmodifier............................................................................................................................ 42 3.3.6 Klassendiagramme ........................................................................................................................ 43 3.3.7 Klassenmethoden und Klassenattribute......................................................................................... 44 3.3.8 Instanzierung, Konstruktoren ........................................................................................................ 45 3.3.9 Destruktoren und Garbage Collection ........................................................................................... 47 3.3.10 Übergabe und Rückgabe von Objekten......................................................................................... 49 3.3.11 Innere Klassen............................................................................................................................... 50 3.4 Vererbung.............................................................................................................................................. 51 3.4.1 Grundlagen.................................................................................................................................... 51 3.4.2 Reihenfolge der Konstruktoren und Destruktoren......................................................................... 55 3.4.3 abstrakte Klassen und Methoden................................................................................................... 56 3.4.4 Polymorphismus............................................................................................................................ 59 3.4.5 Casting von Klassen ...................................................................................................................... 60 3.5 Beziehungen zwischen Klassen ............................................................................................................ 61 3.6 Interfaces............................................................................................................................................... 64 3.7 Packages................................................................................................................................................ 67 3.7.1 Verwendung und Import ............................................................................................................... 67 3.7.2 Bereits definierte Packages ........................................................................................................... 69 3.7.3 Erstellen eigener Packages ............................................................................................................ 69 3.7.4 Sichtbarkeit ................................................................................................................................... 70 3.8 OO Design............................................................................................................................................. 70 3.8.1 UML.............................................................................................................................................. 70

Version 1.3, 12.03.07

Seite IV

Informatik 4

Einstieg in Java

1 Einstieg in Java 1.1 Einleitung Java ist eine moderne Programmiersprache, welche von der Firma SUN Microsystems entwickelt wurde. Java wurde 1995 offiziell angekündigt und ist seit 1999 als OpenSource-Lizenz verfügbar.

1.1.1 Eigenschaften von Java Die Eigenschaften von Java können wie folgt zusammengefasst werden: •

objektorientiert: mit Ausnahme der einfachen Datentypen wie integer, character und boolean sind alle Komponenten Objekte.



plattformunabhängig: Java-Programme können auf fast allen Plattformen (PC, Mac, Workstation ...) und Betriebssystemen (Windows, MAC-OS, Linux ...) eingesetzt werden.



interpretiert: durch die sogenannte Virtual Machine (VM) wird Java-Code als sogenannter Bytecode einheitlich interpretiert. Java-Bytecode wird entweder als Applet (in einem Webbrowser) oder als Standalone-Applikation ausgeführt.



robust: durch verschiedene Massnahmen werden eine Reihe typischer Programmierfehler vermieden (keine Pointer, Garbage Collection ...).



sicher: durch die Bereitstellung von Sicherheitsmodellen.



dynamisch: Java lädt genau die Klassen (übers Netz), welche die Anwendung benötigt.



offen: Module, welche in anderen Sprachen programmiert wurden, können eingebunden werden.



netzwerkfähig: eine einfache Implementation verteilter Systeme über Netzwerke (Netzwerkapplikationen) wird ermöglicht.



Grafik: einfaches programmieren grafischer Oberflächen (GUI) ist plattformunabhängig möglich.



Multithreading: Java unterstützt parallele Abläufe und stellt Mechanismen für die Synchronisation zur Verfügung.



Klassenbibliotheken: Java stellt umfangreiche Bibliotheken zur Verfügung.

1.1.2 Einsatzgebiete von Java Java wurde ursprünglich im klassischen EDV-Sektor und für Internet-Applikationen eingesetzt. Heute findet man Java zunehmend auch in „Handheld“-Geräten wie Mobiltelefonen, Organizern, Set-Top-Boxen usw. Im Echtzeitbereich konnte sich Java bis heute noch nicht durchsetzen (siehe Kapitel 1.6).

1.1.3 Empfohlene Bücher Go To Java 2, Guido Krüger, Addison-Wesley, ISBN 3-8273-1370-8 Programmieren in Java, Fritz Jobst, HANSER-Verlag, ISBN 3-446-22061-5

Version 1.3, 12.03.07

Seite 1

Einstieg in Java

Informatik 4

1.1.4 Infos zu Java Originaldokumentation von SUN: http://java.sun.com/docs http://java.sun.com/docs/books/tutorial Verschiedene Links für Java: http://www.javabuch.de (HTML-Version des Buches „Go To Java 2“) http://www.galileocomputing.de/openbook/javainsel4/ ("Java ist auch eine Insel" von Christian Ullenboom) http://java.seite.net/ (die deutsche Java-Seite) http://www.java.de (Java User Group Deutschland) http://www.javamagazin.com (Web-Seite der Zeitschrift Java-Magazin) http://www.javaworld.com (Web-Seite der Zeitschrift Java World) weitere Literatur: Die UML 2.0 Kurzreferenz für die Praxis, B. Oestereich, Oldenbourg-Verlag Wie sie weitere Infos suchen: Wenn sie mit der Online-Dokumentation nicht weiter kommen, versuchen sie es am besten in den diversen Newsgroups. Beispiel: Google öffnen, Begriff "Groups" über dem Suchfeld wählen und anschliessend sinnvolle Suchkriterien eingeben.

1.2 Ein erstes Beispiel Ein erstes Beispiel soll veranschaulichen, wie ein sehr einfacher Java-Sourcecode aussieht. /** * Erstes Beispielprogramm Java * * @author (WBR1) * @version (1.0 17.11.03) */ public class Bsp01 { /** * Hauptprogramm main() * @param args[0] gibt die Anzahl Studierende in einer Klasse an */ public static void main(String args[]) { int nbrStudents; System.out.println("Hello");

// Ausgabe "Hello"

if(args.length > 0) // Argument vorhanden? { // ja --> Anzahl Studierende bestimmen nbrStudents = Integer.parseInt(args[0]); System.out.println("Die Klasse hat " + nbrStudents + " Studierende"); } System.exit(0);

// Programm beenden

} }

Auf den ersten Blick unterscheidet sich dieser Java-Sourcecode nicht wesentlich von einem C/C++ Programm. Die nachfolgenden Bemerkungen dienen als kurzer Einstieg. Sie werden in den nachfolgenden Kapiteln genauer beschrieben.

Version 1.3, 12.03.07

Seite 2

Einstieg in Java

Informatik 4

• • • • • •

Ein Java-Programm besteht aus einer Menge von Klassen. Jede Klasse sollte in einer separaten Datei definiert sein. Kommentare werden durch /* */ oder // gesetzt. Befehle werden mit einem Semikolon abgeschlossen. Jedes Programm (ausser Applets) muss eine Methode public static void main(args[]) enthalten. Mit System.out.println() kann Text auf die Standardausgabe geschrieben werden. Zeichenketten werden mit dem Befehl „+“ verkettet.



System.exit() beendet das Programm.

1.3 Entwicklung von Java-Programmen SUN stellt kostenlos das JDK (Java Development Kit) zur Verfügung (www.java.sun.com). Im JDK sind folgende Tools enthalten: javac: Java-Compiler, übersetzt den Java-Sourcecode java: führt ein Standalone Java-Programm aus appletviewer: führt ein Applet aus javedoc: erstellt eine Dokumentation des Sourcecodes All diese Tools müssen aus der Kommandozeile gestartet werden. Häufig verwendet man deshalb sogenannte IDE’s (Integrated Development Environment), welche auf dem JDK aufbauen und alle Tools zusammen mit einem Texteditor und einem Debugger integrieren. Beispiele sind Eclipse, JCreator, JBuilder usw.

1.3.1 Texteditor Zum editieren des Sourcecodes können alle Texteditoren verwendet werden, bei denen keine Formatierbefehle eingebunden werden. Häufig verwendet man Editoren, welche Sprachkonstrukte, Kommentare usw. farblich hervorheben.

1.3.2 Compiler Der Java-Compiler javac übersetzt den Java-Sourcecode in den platformunabhängigen, ausführbaren JavaBytecode. Während der Kompilierung wird der Java-Sourcecode auf Fehler überprüft. Der Bytecode kann nur generiert werden, wenn keine Syntaxfehler im Sourcecode vorkommen. Files mit Java-Sourcecode haben die Endung „java“. Die vom Compiler erzeugten Files mit Java-Bytecode haben die Endung „class“. Eingabe in der Kommandozeile zum Starten des Compilers: javac

.java

Der Compiler generiert daraus: .class

1.3.3 Interpreter Die Virtual Machine (VM) ist ein Bytecode-Interpreter und führt den Java-Bytecode aus. Oft wird die VM durch einen programmierten Simulator gebildet, welcher relativ einfach für verschiedene Plattformen realisiert werden kann. Durch den Befehl „java“ wird die VM gestartet. Eingabe in der Kommandozeile zum Starten der Applikation: java



Version 1.3, 12.03.07

Seite 3

Einstieg in Java

Informatik 4

1.3.4 Übersicht Entwicklung und Ausführung eines Java-Programmes können grafisch wie folgt dargestellt werden:

Editor

JavaSourcecode

.java

JavaCompiler

JavaBytecode

Browser mit integrierter VM

Betriebssystem mit VM

.class

VM in speziellem VLSI-Chip

Hardware (Network-Computer, Spiel-Konsole, mobiles Telefon, Haushaltgeräte ...) Abbildung 1: Entwicklung und Ausführung eines Java-Programmes

1.4 Applikationen Java-Applikationen sind eigenständige Anwendungen und entsprechen Programmen aus anderen Programmiersprachen. Java-Applikationen sind jedoch auf einen Interpreter angewiesen. Vereinzelt werden Java-Applikationen auch nachträglich mit einem Just-In-Time Compiler übersetzt. Dadurch entsteht plattformabhängiger Maschinencode, der schneller ausgeführt werden kann als im Interpreter. Ein Beispiel für eine Applikation finden sie im Kapitel 1.2.

1.5 Applets Applets werden in einer XHTML- oder HTML-Seite eingefügt und in einem javafähigen Webbrowser ausgeführt. Applets sind aktiv, laufen auf dem Rechner des Anwenders ab und nutzen dessen Ressourcen. Sie sind grafikfähig und bekommen innerhalb des Browsers eine Fläche zugewiesen. Anstelle eines Webbrowsers können Applets auch in einem Applet-Viewer gestartet werden. Nachfolgender Code zeigt ein Applet, welches den Text „I like Java“ in einem Kreis ausgibt:

Version 1.3, 12.03.07

Seite 4

Einstieg in Java

Informatik 4

import java.applet.Applet; import java.awt.Graphics; import java.awt.*; /** * Class BspApplet - A simple Applet * * @author WBR1 * @version 16.06.2005 */ public class BspApplet extends Applet { // instance variables private final String msg = "I like Java"; private Font font; /** * Called by the browser or applet viewer to inform this Applet that it * has been loaded into the system. It is always called before the first * time that the start method is called. */ public void init() { font = new Font("Helvetica", Font.BOLD, 24); } /** * Returns information about this applet. * @return a String representation of information about this Applet */ public String getAppletInfo() { // provide information about the applet return "Title: First Applet \nAuthor: WBR1 \nA simple hello-applet "; } /** * Draw the applet whenever necessyry * @param g Reference to graphics system */ public void paint(Graphics g) { // draw red circle g.setColor(Color.CYAN); g.fillOval(10,10,300,300); // set text g.setColor(Color.black); g.setFont(font); g.drawString(msg, 90,170); } }

Version 1.3, 12.03.07

Seite 5

Informatik 4

Einstieg in Java

Das Applet muss wie folgt in den XHTML-Code eingebunden werden: Hello Applet Beispiel-Applet

Wird die XHTML-Seite im Webbrowser gestartet, so erscheint folgendes Fenster:

Abbildung 2: Unser Applet

Version 1.3, 12.03.07

Seite 6

Informatik 4

Einstieg in Java

1.6 Vor- und Nachteile von Java Wie so viele Sachen im Leben bringt Java nicht nur Vorteile mit sich. In diesem Kapitel werden deshalb die Vor- und Nachteile dieser Programmiersprache aufgeführt. Vorteile • Java ist plattformunabhängig. Derselbe Code kann auf unterschiedlichen Hardwareplattformen und Betriebssystemen laufen, ohne ihn neu kompilieren zu müssen. • Bei Java wird sehr viel Funktionalität durch die Sprache definiert (GUI, Internetanbindung ...). Bei anderen Sprachen werden dazu Bibliotheken verwendet, welche nicht unbedingt standardisiert sind. • Für Java gibt es sehr viele, recht preisgünstige Entwicklungsumgebungen. • Java ist eine relativ einfach zu erlernende Programmiersprache (jedenfalls etwas einfacher als C++). • Vereinzelt wird behauptet, dass die Produktivität bei der Java-Entwicklung im Vergleich zu C/C++ höher ist (Pointer-Problematik, Speicherverwaltung, Laufzeitprüfungen..). Nachteile • Die Garbage-Collection hat die Aufgabe, nicht mehr verwendeten Speicher wieder frei zu geben. Dies hat aber zur Folge, dass der Code dadurch nicht mehr deterministisch ist (die Garbage-Collection wird irgendwann aufgerufen und hat eine hohe Priorität). Dies ist für Programme im EDV-Bereich kein Nachteil. Bei Applikationen im Echtzeitbereich ist dies jedoch verheerend. Mit anderen Worten heisst dies, dass Java für Echtzeitanwendungen nicht geeignet ist. • Java ist eine interpretierte Sprache. Ein Interpreter hat für die Ausführung des Codes aber immer länger als ein Programm in Maschinencode. Dies heisst, dass Java-Programme immer langsamer laufen als beispielsweise Programme welche in C/C++ geschrieben wurden. Mit einem Just-In-Time Compiler, welcher aus dem Bytecode Maschinencode erzeugt, kann dieses Problem entschärft werden. • Der direkte Zugriff von Java-Programmen auf die Hardware ist nicht vorgesehen. Schlussfolgerungen Java ist eine moderne und mächtige Programmiersprache, welche vor allem im Internetbereich ihre Stärken hat. Im Echtzeitbereich wird Java heute noch wenig verwendet. Oft läuft eine VM als Task auf einem Echtzeitbetriebssystem. Internetanbindung und GUI werden in Java programmiert. Alle echtzeitkritischen Aufgaben werden in C/C++ programmiert und laufen in anderen Tasks, welche höhere Prioritäten haben.

Version 1.3, 12.03.07

Seite 7

Informatik 4

Funktionale Programmierung in Java

2 Funktionale Programmierung mit Java 2.1 Einführung Java ist eine objektorientierte Programmiersprache. Bevor wir aber im nächsten Kapitel in die OOP einsteigen, befassen wir uns einmal mit den Sprachelementen, mit welchen man auch in Java rein funktionale Programmierung machen kann (Datentypen, Kontrollstrukturen, Methoden). Diese Sprachkonstrukte sind denjenigen der Programmiersprachen C und C++ sehr ähnlich. Das Kapitel hat zum Ziel, diese Sprachelemente aufzulisten und die Unterschiede zu den Programmiersprachen C/C++ aufzuzeigen. Weiter gehen wir in diesem Kapitel davon aus, dass sie mit der Programmiersprache C vertraut sind.

2.2 Lexikalische Elemente 2.2.1 Zeichensatz Java ist eine Programmiersprache, welche weltweit eingesetzt wird. Sie benützt deshalb den sogenannten Unicode-Zeichensatz. Im Unicode-Zeichensatz werden eine Vielzahl internationaler Zeichensätze zusammengefasst. Ein Unicode-Zeichen ist 2 Bytes lang. Die ersten 128 Zeichen sind mit dem ASCII-Zeichensatz und die ersten 256 Zeichen mit dem ISO-8859-1 Zeichensatz kompatibel. Ein Java-Programm besteht aus einer Folge von Unicode-Zeichen. Dadurch können bei symbolischen Namen auch nationale Sonderzeichen verwendet werden.

2.2.2 Bezeichner Bezeichner sind Namen für Variablen, Methoden, Klassen, Objekte usw. Für Bezeichner sind folgende Zeichen erlaubt: • Gross- und Kleinbuchstaben (d.h. Gross- und Kleinschrift wird unterschieden!) • Ziffern (0, 1, 2, 3, 4, 5, 6, 7, 8, 9), der Bezeichner darf aber nicht mit einer Ziffer beginnen • Underscore „_“ • Dollarzeichen „$“ • alle Zeichen des Unicode-Zeichensatzes oberhalb 00C0 Beispiele für erlaubte Bezeichner: JavaTools Java_Tools Java_3

Beispiele für nicht erlaubte Bezeichner: Java-Tools Java/Tools 3_Java

2.2.3 Kommentare Wie auch bei der Programmiersprache C zeichnet sich ein qualitativ guter Code unter anderem dadurch aus, dass er verständlich kommentiert ist. Dadurch wird die Fehlersuche oder eine spätere Wartung vereinfacht und neue Mitarbeiter können sich besser in den Code einarbeiten. Java stellt drei alternative Kommentare zur Verfügung: • Zeilenkommentare • Blockkommentare Version 1.3, 12.03.07

Seite 8

Funktionale Programmierung in Java

Informatik 4



Dokumentations-Kommentare

2.2.3.1 Zeilenkommentare Mit Zeilenkommentaren werden kurze Anmerkungen zum Code angebracht. Sie erlauben die Eingabe eines Kommentars bis ans Zeilenende. Zeilenkommentare werden durch // eingeleitet und durch das Zeilenende abgeschlossen. Dieser Kommentar entspricht dem Zeilenkommentar von C++. Beispiele: // Dies ist ein Zeilenkommentar int loop = 0; // Dies ist auch ein Zeilenkommentar

2.2.3.2 Blockkommentare Mit Hilfe von Blockkommentaren können mehrere Zeilen auskommentiert werden. Der Kommentar ist in den Zeichen /* und */ eingeschlossen. Nach dem /* kann jedes beliebige Zeichen ausser * folgen (siehe Dokumentations-Kommentar). Beispiel: /* Diese ist ein Block-Kommentar. */

2.2.3.3 Dokumentations-Kommentare / "javadoc" Der Dokumentations-Kommentar wird für die Generierung einer automatischen Dokumentation mit "javadoc" verwendet. Der Dokumentationskommentar wird durch die Zeichen /** und */ eingeschlossen. "javadoc" durchsucht den Quellcode nach dieser Zeichenfolge und generiert daraus die Dokumentation im HTML-Format! Beispiel: /** * Kommentar zur Methode * @param p1 Beschreibung des ersten Parameters * @return Rückgabewert */ public static int myFunc(int p1) { }

Innerhalb des Dokumentations-Kommentars können mit dem Sonderzeichen "@" weitere Zusatzinformationen angegeben werden. Die wichtigsten sind in der nachfolgenden Tabelle zusammengefasst: Zusatzinformation @author name @version version @see reference

@param name description @return description @exception classname descr.

Beschreibung Autor Version Querverweis auf eine andere Klasse, Methode oder einen beliebigen anderen Teil der Dokumentation. Name und Beschreibung für einen MethodenParameter. Beschreibung des Rückgabewerts einer Methode. Beschreibung einer Ausnahme, die von dieser Methode ausgelöst wird

Verwendung in Klasse, Interface Klasse, Interface Klasse, Interface

Methoden Methoden Methoden

Tabelle 1: Zusatzinformationen für "javadoc"

Version 1.3, 12.03.07

Seite 9

Funktionale Programmierung in Java

Informatik 4

Die nachfolgende Abbildung zeigt einen Ausschnitt aus der von "javadoc" automatisch erzeugten Dokumentation für das Applet-Beispiel aus Kapitel 1. Mit Hilfe eines Browsers ist es nun sehr komfortabel, innerhalb der Dokumentation mit Hilfe von Links zu navigieren.

Abbildung 3: Von "javadoc" automatisch generierte Dokumentation des Codes

2.2.4 Escape-Sequenzen Für Zeichen, welche nicht darstellbar sind, werden sogenannte Escape-Sequenzen, d.h. besondere Zeichenkombinationen, zur Verfügung gestellt. Diese beginnen immer mit einem „\“. Escape-Sequenz \b \n \r \t \f \\

Bedeutung Backspace Linefeed Carriage Return Horizontaler Tabulator Formfeed \

Escape-Sequenz \‘ \“ \u \x \oo

Bedeutung ‘ “ Unicode-Zeichen Hexadezimal-Zeichen Oktal-Zeichen

Tabelle 2: Escape-Sequenzen

Version 1.3, 12.03.07

Seite 10

Funktionale Programmierung in Java

Informatik 4

2.3 Schlüsselwörter Java reserviert eine Anzahl von Schlüsselwörtern, die nicht für Bezeichner verwendet werden dürfen. Dies sind: abstract assert boolean break byte case catch char class const continue default do

double else extends final finally float for goto if implements import instanceof int

interface long native new package private protected public return short static strictfp super

switch synchronized this throw throws transient try void volatile while

Tabelle 3: Schlüsselwörter von Java Weiter sind reserviert.

true

und

false

als Literale für den Wert von boolean sowie

null

als Literal für die Null-Referenz

2.4 Variablen und elementare Datentypen In einer Variablen werden Daten gespeichert. Variablen sind immer typisiert, d.h. der Typ der zu speichernden Daten muss definiert werden. Je nach Typ wird dafür mehr oder weniger Platz im Speicher (RAM) reserviert. Variablen werden über einen Bezeichner angesprochen. Java kennt drei Arten von Variablen: • lokale Variablen: Diese werden innerhalb einer Methode oder eines Blocks definiert und existieren nur bis ans Ende der Methode oder des Blocks. • Klassenvariablen: Diese werden in der Klassendefinition definiert und existieren unabhängig von einem Objekt, d.h. während der Laufzeit des ganzen Programms (siehe Kapitel OOP). • Objektvariablen: Diese werden ebenfalls in einer Klassendefinition definiert, werden aber bei der Erzeugung eines Objektes angelegt und existieren so lange wie das Objekt lebt (siehe Kapitel OOP).

2.4.1 Deklaration und Initialisierung Variablen werden wie folgt Deklariert:

;

Dabei entspricht dem Typ der Variablen. Das kann z.B. ein elementarer Datentyp (siehe Kapitel 2.4.2) aber auch eine Klasse sein. steht für den Bezeichner der Variablen. Eine Variable kann entweder bei der Deklaration oder auch erst später initialisiert werden: // Initialisierung bei der Deklaration int a = 1234; // Initialisierung nach der Deklaration int a; a = 1234;

Version 1.3, 12.03.07

Seite 11

Funktionale Programmierung in Java

Informatik 4

Variablen können (im Gegensatz zu ANSI C) irgendwann innerhalb einer Methode oder eines Blockes deklariert werden (d.h. nicht zwangsläufig zu Beginn der Methode).

2.4.2 Elementare Datentypen Java unterscheidet zwischen elementaren und zusammengesetzten Datentypen. Elementare Datentypen (oft auch einfache oder primitive Datentypen genannt) sind Zahlen, Zeichen oder Wahrheitswerte. Zusammengesetzte Datentypen sind Zeichenketten, Felder und Klassen. Die verschiedenen Datentypen benötigen unterschiedlich Speicherplatz. Sie sind denjenigen von C/C++ sehr ähnlich, es gibt aber wichtige Unterschiede: • • •

Der Speicherbedarf jedes elementaren Datentyps wird unabhängig von der Plattform durch Java definiert. Probleme wie bei C, wo etwa die Grösse des Typs abhängig von der Hardware definiert wurde, existieren bei Java nicht. Alle numerischen Variablen sind vorzeichenbehaftet. Es gibt keine numerischen „unsigned“ Datentypen. Typumwandlungen sind im Vergleich zu C nicht beliebig möglich.

2.4.2.1 Ganze Zahlen Ganze zahlen sind exakt und intern im binären Datenformat gespeichert. Folgende ganzzahlige vorzeichenbehaftete Datentypen sind definiert: Type byte short int long

Grösse in bit 8 16 32 64

Bereich -128 bis 127 -32768 bis 32767 -2'147'483'648 bis +2'147’483’647 -9'223’372'036'854'775'808 bis +9'223’372'036'854'775'807

Beispiel byte b = 63; short s = -4253; int i = 523413241; long l = -1234242L;

Tabelle 4: Ganze Zahlen Bemerkung zu long: Der Zusatz L oder l präzisiert, dass es sich um ein Long-Format handelt. Die Werte der ganzen Zahlen können in dezimal, in hexadezimal oder in oktal zugewiesen werden: dezimal: Werte werden normal zugewiesen, Bsp.: int i = 123; hexadezimal: Werte beginnen mit "0x", Bsp.: int i = 0x234; oktal: Werte beginnen mit einer "0", Bsp.: int i = 0123; Für alle elementaren Datentypen existieren sogenannte Wrapper-Klassen (Hüllklassen, siehe Kapitel OOP). Diese Klassen stellen die Konstanten MIN_VALUE und MAX_VALUE zur Verfügung, welche die oben genannten Bereiche spezifizieren. Die Klassen sind gleich benannt wie die Datentypen, beginnen aber mit einem Grossbuchstaben. Für die ganzen Zahlen sind das: Byte, Short, Integer, Long. Beispiel: byte minByte = Byte.MIN_VALUE; long maxLong = Long.MAX_VALUE;

Weiter stellen all diese Wrapper-Klassen Methoden für die Ausgabe von Zahlen zur Verfügung (Umwandlung des Wertes in einen String). Dies sind folgende Methoden: toString(n) toBinaryString(n) toHexString(n) toOctalString(n)

Version 1.3, 12.03.07

Umwandlung ins Dezimalzahlensystem Umwandlung ins Dualzahlensystem Umwandlung ins Hexadezimalzahlensystem Umwandlung ins Oktalzahlensystem

Seite 12

Funktionale Programmierung in Java

Informatik 4

Beispiel: int i = 127; System.out.println("Hexadezimal: " + Integer.toHexString(i));

führt zu folgender Ausgabe: Hexadezimal: 7F

2.4.2.2 Gleitkommazahlen Gleitkommazahlen (oder auch Gleitpunktzahlen, reelle Zahlen genannt) werden im IEEE 754 Standard-Format gespeichert. Gleitkommazahlen sind mit Darstellungsfehlern behaftet, es ergeben sich Rundungsfehler. Folgende Datentypen für Gleitkommazahlen sind definiert: Type float

Grösse in bit 32

double

64

Bereich +/- 1,4*E-45 bis 3,4*E38 Genauigkeit: 6 Dezimalstellen +/- 4,9*E-324 bis 1,7*E308 Genauigkeit: 15 Dezimalstellen

Beispiel float fl = 123.0f; float f2 = 1.23E2f; double d1 = 1.4E2; double d2 = 1.0d;

Tabelle 5: Gleitkommazahlen Bemerkungen: Gleitkommazahlen müssen immer mit einem Punkt vor den Nachkommastellen geschrieben werden (double d3 = 1 wäre falsch). Der Zusatz f oder F bei float ist obligatorisch. Der Zusatz d oder D bei double ist fakultativ, Zahlen ohne Zusatz werden als double interpretiert. Auch bei Gleitkommazahlen existieren die Wrapper-Klassen (Float und Double), welche wie bei den ganzen Zahlen beschrieben die Bereiche spezifizieren (MIN_VALUE und MAX_VALUE).

2.4.2.3 Formatierte Ausgabe Ganz- und Fliesskommazahlen können mit Hilfe der Klasse DecimalFormat für die Ausgabe formatiert werden. Dazu wird ein Formatstring verwendet, welcher folgende Formatzeichen enthalten kann: Formatzeichen 0 # . , E %

Beschreibung Eine einzelne Ziffer Eine einzelne Ziffer, welche ausgelassen wird, falls eine führende Null vorhanden ist. Dezimaltrennzeichen Tausendertrennzeichen Exponentialdarstellung Formatierung als Prozentwert

Tabelle 6: Formatzeichen für die formatierte Ausgabe Beispiel: public class FormatierteAusgabe { public static void printFormatted(double value, String format) { DecimalFormat df = new DecimalFormat(format); System.out.println(df.format(value)); } public static void main(String[] args) { double value = 1234.4567; printFormatted(value, "00000.000");

Version 1.3, 12.03.07

// Ausgabe 1

Seite 13

Funktionale Programmierung in Java

Informatik 4

printFormatted(value, printFormatted(value, printFormatted(value, printFormatted(value,

"#0.000"); // Ausgabe 2 "#0.0"); // Ausgabe 3 "#,###,###.000"); // Ausgabe 4 "0.000E00"); // Ausgabe 5

} }

führt zu folgender Ausgabe: 01234.457 1234.457 // 1234.5 1'234.457 1.234E03 //

// Ausgabe 1 Ausgabe 2 // Ausgabe 3 // Ausgabe 4 Ausgabe 5

Bemerkung: In Java gibt es bis heute keine printf-Methode, welche die Formatierung wie in der Programmiersprache C ermöglicht. Dies ist jedoch für die Version 1.5 geplant.

2.4.2.4 Zeichen Zur Darstellung eines Zeichens wird der Unicode (siehe Kapitel 2.2.1) verwendet. Type char

Grösse in bit 16

Bereich alle Zeichen

Beispiel char c = 'a'; Tabelle 7: Zeichen

Wichtig: In der Programmiersprache C werden für eine Variable vom Typ char 8 bit Speicher reserviert, bei Java sind dies 16 bit!

2.4.2.5 Boolsche Zahlen Der Bedarf des Typs boolean entsteht vor allem bei Vergleichen. Boolsche Zahlen haben entweder den Wert true oder false, sind also keine Zahlen im üblichen Sinne. Boolean können auch nicht in andere Datentypen umgewandelt werden. Type boolean

Grösse in bit 8

Bereich true oder false

Beispiel boolean isReady = true; Tabelle 8: boolean

Wichtig: In der Programmiersprache C wird false oft mit dem Wert "0" und true mit dem Wert "!=0" gleichgesetzt. Dies ist bei Java nicht so! Bedingungen wie "if" (siehe Kapitel 2.9.2) verlangen zwingend einen boolschen Wert. if(1) wäre in C korrekt, in Java ist es jedoch falsch.

2.4.3 Datentyp-Umwandlungen Mit Hilfe der Datentyp-Umwandlung (auch Casting genannt) kann ein Datentyp in einen anderen umgewandelt werden. Syntax:

= (Typ der Zielvariable)

Beispiel: int i1, i2; float f1 f1 = (float)i1 / (float)i2;

Version 1.3, 12.03.07

Seite 14

Funktionale Programmierung in Java

Informatik 4

Es werden zwei Arten der Datentyp-Umwandlung unterschieden: Explizites Casting: Die Typumwandlung wird vom Programmierer durchgeführt Implizites Casting: Die Typumwandlung wird vom Compiler automatisch vorgenommen. Bei der Datentyp-Umwandlung kann es zu unerwünschten Seiteneffekten kommen, z.B. Datenverlust bei folgendem Beispiel: byte b1; short s1; b1 = (byte) s1;

// Datenverlust des High-Byte, auf Risiko des Programmierers

Die nachfolgende Tabelle zeigt eine Übersicht der möglichen Fälle von Datentyp-Umwandlungen elementarer Datentypen. Die Tabelle ist wie folgt zu interpretieren: Typ der linken Seite = (cast erforderlich?) Typ der rechten Seite Typ linke Seite byte short int long float double boolean char

Typ rechte Seite byte a a a a a a r cast

short cast a a a a a r cast

int cast cast a a a a r cast

long cast cast cast a a a r cast

float cast cast cast cast a a r cast

double cast cast cast cast cast a r cast

boolean r r r r r r a r

char cast cast a a a a r a

Tabelle 9: Datentyp-Umwandlung a: kein cast erforderlich, Zuweisung ohne Datenverlust möglich cast: Datentyp-Umwandlung erforderlich, Datenverlust möglich r: cast in Java nicht möglich

2.4.4 Sichtbarkeit Variablen sind innerhalb eines Blockes (Bereich zwischen "{ }") sichtbar, genauer gesagt ab ihrer Definition bis ans Blockende. Innerhalb von Unterblöcken in diesem Block sind sie ebenfalls sichtbar. Beispiel: public static void main(String args[]) { int a = 1; // nur a ist sichtbar { int b = 2; // a und b sind sichtbar { int c = 3; // a, b und c sind sichtbar } // c ist nicht mehr sichtbar } // b ist nicht mehr sichtbar }

Version 1.3, 12.03.07

Seite 15

Informatik 4

Funktionale Programmierung in Java

2.4.5 Globale Variablen Variablen können nur innerhalb von Methoden oder auf der Ebene von Klassen (siehe OOP) angelegt werden. Globale Variablen im Sinne von C gibt es in Java nicht. Als Ersatz werden static-Klassenvariablen verwendet. Auf diese kann man unabhängig von Objekten zugreifen. Beispiel: public class Global { static int myGlobal;

// globale Klassenvariable

public static void main(String args[]) { myGlobal = 7; } }

2.5 Zeichenketten Ein String ist ein Array von Zeichen (Unicode!) und wird zum Speichern von Text verwendet. Strings sind nicht mehr elementare Datentypen, sondern Objekte erster Klasse. Da Strings auch für die Programmierung rein funktionaler Software interessant sind, greifen wir an dieser Stelle dem Kapitel OOP etwas vor. Java unterstütz zwei Klassen zum Speichern von Zeichenketten: String für unveränderliche Zeichenfolgen und StringBuffer für den Aufbau von Zeichenketten.

2.5.1 String Ein String-Objekt kann auf folgende Arten angelegt werden: String student1 = "Hans"; String student2 = new String("Thomas");

Die wichtigsten Methoden, die auf ein String-Objekt angewendet werden können, sind: int compareTo(String b): vergleicht zwei Strings. a.compareTo(b) liefert 0 falls a nach b kommt boolean equals(String b):

vergleicht zwei Strings. a.equals(b) liefert true, wenn a und b denselben Inhalt haben.

int length():

liefert die Länge des Strings. a.length();

Weitere Methoden finden sie unter java.lang.String. Ein String kann mit dem Operator "=" kopiert werden (nicht wie bei C!). Der Operator "+" hängt zwei Strings beispielsweise für einen print zusammen. Beispiel: public class StringTest { public static void main(String args[]) { // Anlegen von Strings String student_1 = "Asterix"; String student_2 = new String("Obelix"); String student_3 = "Idefix"; System.out.println(student_1 + " " + student_2 + " " + student_3);

Version 1.3, 12.03.07

Seite 16

Informatik 4

Funktionale Programmierung in Java

// Vergleiche und Länge von Strings int res1 = student_1.compareTo(student_2); boolean res2 = student_1.equals(student_2); int len = student_1.length(); System.out.println("compare: " + res1 + " equals: " + res2 + " len: " + len); // Kopieren und anfügen von Strings student_1 = student_2; student_2 = "Zäsar"; student_3 += student_2; System.out.println(student_1 + " " + student_2 + " " + student_3); } }

führt zu folgender Ausgabe: Asterix Obelix Idefix compare: -14 equals: false len: 7 Obelix Zäsar IdefixZäsar

2.5.2 StringBuffer Sollen kompliziertere Zeichenketten aufgebaut werden, so bietet sich die Klasse StringBuffer an. Diese beinhaltet Methoden zum Einfügen und Anfügen von Zeichenketten. Die wichtigsten Methoden, die auf ein StringBuffer-Objekt angewendet werden können, sind: StringBuffer append(Typ b): hängt eine Zeichenkette oder einen elementaren Datentyp an den bestehenden Buffer an. StringBuffer insert( int offset, Typ b):

fügt eine Zeichenkette oder einen elementaren Datentyp an der Position offset in den bestehenden Buffer ein.

int length():

liefert die Länge des Strings. a.length();

Ein StringBuffer-Objekt wird bei seiner Definition mit einer Grösse von 16 Zeichen angelegt. Diese Länge wird aber bei Bedarf dynamisch vergrössert. Beispiel: public class StringBufferTest { public static void main(String args[]) { int i = 7; StringBuffer buffer = new StringBuffer(); buffer.append("Hans "); buffer.append("Meier"); buffer.insert(3, i); System.out.println("Inhalt Buffer: " + buffer); System.out.println("Länge Buffer: " + buffer.length()); } }

Folgender Text wird durch obiges Programm ausgegeben: Inhalt Buffer: Han7s Meier Länge Buffer: 11

Version 1.3, 12.03.07

Seite 17

Informatik 4

Funktionale Programmierung in Java

2.6 Arrays 2.6.1 Eindimensionale Arrays Als Synonym für den Begriff Array werden auch die Begriffe Felder oder Vektoren verwendet. Arrays speichern mehrere Variablen desselben Datentyps. Ein einzelner Platz innerhalb des Arrays wird Element genannt, auf dieses wird über einen Index zugegriffen (Bsp. myArray[2]), wobei das erste Element den Index 0 hat. Arrays müssen immer deklariert, allokiert und initialisiert werden:

2.6.1.1 Deklaration eines Arrays Der erste Schritt ist das Anlegen einer Variablen, welche eine Referenz auf das Array ist. Syntax: [];

oder: [] ;

Beispiele: int intArray[]; boolean boolArray[];

Wichtig: Arrays werden immer über diese Referenz-Variablen verwaltet. Durch das Anlegen der ReferenzVariablen wird jedoch noch kein Speicher für die Array-Elemente angelegt!

2.6.1.2 Allokierung eines Arrays In diesem Schritt wird Speicherplatz für das Array angefordert. Die Anzahl der Elemente wird also erst zur Laufzeit definiert, nach der Erstellung des Arrays lässt sich aber diese Anzahl nicht mehr verändern. Die einzelnen Felder werden mit dem Defaultwert (0) belegt. Die Grösse eines Arrays lässt sich durch das Attribut "length" bestimmen, welches die Anzahl der Array-Elemente angibt. Syntax: = new [];

Beispiele: intArray = new int [5]; // reserviert Speicher für 5 Elemente vom Typ int, //intArray ist eine Referenz darauf boolArray = new boolean [10]; // reserviert Speicher für 10 Elemente vom Typ boolean

Wichtig: Arrays werden mit "new" dynamisch (zur Laufzeit) generiert und am Schluss durch die garbage Collection automatisch entsorgt. In C oder C++ sind sie als Programmierer für die Freigabe des dynamisch angeforderten Speichers selber verantwortlich, in Java übernimmt das die garbage Collection. Seien sie vorsichtig, wenn sie sich an Java gewöhnt haben und auf ein C/C++ Projekt umsteigen !!!! Deklaration und Allokation können auch zusammengefasst werden. Syntax: [] = new [];

2.6.1.3 Initialisierung des Arrays Im letzten Schritt müssen die Elemente des Arrays initialisiert werden, sofern sie einen vom Defaultwert (0) unterschiedlichen Inhalt haben sollen. Dies kann durch Zuweisung eines Wertes zu jedem einzelnen Element wie folgt geschehen: Version 1.3, 12.03.07

Seite 18

Funktionale Programmierung in Java

Informatik 4

intArray[i] = wert;

Ein Array kann aber auch schon bei der Deklaration initialisiert werden, wie wir das von C kennen. Deklaration, Allokierung und Initialisierung erfolgen dann in einem Schritt: Syntax: [] = {, , ..... , };

Beispiele: int intArray [] = {1,2,3,4,5}; // reserviert Speicher für 5 Elemente vom Typ int und initialisiert die Elemente Natürlich dürfen die hier zugewiesenen Werte auch berechnete Werte sein, sofern diese bei der Zuweisung schon eindeutig definiert sind. Beispiele: public class EindimArray { public static void main(String args[]) { int intArray[]; intArray = new int[2]; intArray[0] = 12; intArray[1] = 17; boolean boolArray[] = {true, false};

// Deklaration // Allokation // Initialisierung

// Daklaration, Allokation u. Init.

System.out.println("intArray[]: " + intArray[0] + " " + intArray[1]); System.out.println("boolArray[]: " + boolArray[0] + " " + boolArray[1]); } }

2.6.1.4 Indexfehler Einer der häufigsten Fehler in C/C++ ist der Indexfehler (d.h. Unterlauf oder Überlauf) beim Zugriff auf Arrays. Dieser Fehler führt dazu, dass Daten in der Umgebung des Arrays überschrieben werden. Er ist besonders schwierig zu finden, weil sich der Fehler nicht an der Stelle des Auftretens bemerkbar macht, sondern erst an einer anderen Stelle Probleme verursacht. In Java werden Indexfehler durch die Exception ArrayIndexOutOfBoundsException abgefangen. Beispiel: public class ArrayException { public static void main(String args[]) { int intArray[]; intArray = new int[2]; intArray[0] = 12; intArray[1] = 17;

// Deklaration // Allokation // Initialisierung

System.out.println("intArray[2]: " + intArray[2]); } }

In der letzten Codezeile wird auf das dritte, nicht existierende Element zugegriffen. Dies führt zur folgenden Laufzeit-Fehlermeldung: ArrayIndexOutOfBoundsException: 2

Version 1.3, 12.03.07

Seite 19

Funktionale Programmierung in Java

Informatik 4

2.6.2 Mehrdimensionale Arrays Auch in Java können mehrdimensionale Arrays angelegt werden. Wie schon bei den eindimensionalen Arrays müssen diese deklariert, allokiert und initialisiert werden. Syntax für zweidimensionale Arrays: [][]; = new [] [];

oder: [][] = new [] [];

Auf die einzelnen Elemente kann wie folgt zugegriffen werden: [row][column] = ;

Auch hier können wir Deklaration, Allokation und Initialisierung zusammenfassen: [] [] = { {, ,.... ,}, {, ,.... ,} };

Beispiel: public class ZweidArray { public static void main(String args[]) { int array1[][] = {{1,2,3},{10,11,12},{20,21,22}}; int array2[][] = {{100,101,102},{110,111,112},{120,121,122}}; int array3[][] = new int[3][3]; // Addtion der Matrizen array1 und array2, Resultat in array3 for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { array3[i][j] = array1[i][j] + array2[i][j]; System.out.print(array3[i][j] + " "); } System.out.println(""); } } }

2.7 Konstanten In Java werden Konstanten mit Hilfe des Schlüsselwortes final gebildet. Mit final wird eine Variable deklariert und initialisiert, welche im Verlauf des Programms nicht mehr verändert werden kann. Beispiel: final float PI = 3.1415f;

Es können folgende Arten von Konstanten deklariert werden: • Ganzzahlige Konstanten: Bsp. final int i = 1234; final long l = 1234L; • Gleitkomma-Konstanten: Bsp. final float f = 1.23f; final double d = 1.23d; • Zeichen-Konstanten: Bsp. final char c = 'a'; • Zeichenketten-Konstanten: Bsp. final String str = "Hallo"; Wichtig: Die Präprozessoranweisung #define, welche sie von der Programmiersprache C kennen, können sie in Java nicht anwenden.

Version 1.3, 12.03.07

Seite 20

Funktionale Programmierung in Java

Informatik 4

2.8 Operatoren In Java wie auch in C/C++ ist ein Ausdruck die kleinste ausführbare Einheit eines Programms. Ein Ausdruck besteht aus einem oder mehreren Operanden sowie mindestens einem Operator, welcher auf die Operanden ausgeführt wird. Operatoren, die nur einen Operanden benötigen, nennt man unär, solche mit zwei Operanden binär. Ein Ausdruck liefert immer einen Rückgabewert, dessen Typ vom Operator und den Operanden abhängig ist. Treten mehrere Operatoren in einem Ausdruck auf, so ist deren Vorrangstufe entscheidend. Die Reihenfolge der Operatoren innerhalb derselben Vorrangstufe ist in Java ebenfalls definiert. Weiter wird die Assoziativität (d.h. die Klammerung) definiert. Vorrang Operatoren Hinweis Assoziativität (precedence) links nach rechts 1 (höchster) [] . (params) expr++ ++,-- postfix expr-rechts nach links 2 ++expr --expr +expr -expr unär ~ ! Cast 3 new (type)expr links nach rechts * / % 4 binär links nach rechts + 5 links nach rechts > >>> 6 links nach rechts < > = instanceof 7 links nach rechts == != 8 & links nach rechts 9 ^ links nach rechts 10 | links nach rechts 11 && links nach rechts 12 || links nach rechts 13 ?: rechts nach links 14 rechts nach links = += -= *= /= %= &= 15 (niedrigster) ^= |= = >>>= Tabelle 10: Operatoren, geordnet nach Vorrang

2.8.1 Arithmetische Operatoren Arithmetische Operatoren erwarten numerische Operanden (in der Regel je einen Operanden auf jeder Seite des Operators) und liefern einen numerischen Wert. Haben die beiden Operanden unterschiedliche Typen, so entspricht der Typ des Resultates dem Typ des grösseren Operanden (Bsp.: Addition int und long ergibt als Ergebnis long). Operator - op1 op1 + op2 op1 – op2 op1 * op2 op1 / op2 op1 % op2 op1++ ++op1 op1---op1

Bezeichnung Negatives Vorzeichen Addition Subtraktion Multiplikation Division Modulo post-Inkrement pre-Inkrement post-Dekrement pre-Dekrement

Beschreibung Das Vorzeichen wird invertiert Summe von op1 und op2 Differenz von op1 und op2 Produkt von op1 und op2 Quotient von op1 und op2 Rest der ganzzahligen Division op1 nach Ausführung inkrementiert op1 vor Ausführung inkrementiert op1 nach Ausführung dekrementiert op1 vor Ausführung dekrementiert

Tabelle 11: Arithmetische Operatoren

Version 1.3, 12.03.07

Seite 21

Informatik 4

Funktionale Programmierung in Java

Die arithmetischen Operationen werden von links nach rechts gemäss den Prioritäten aus Tabelle 10 ausgeführt. Im Zweifelsfalle ist es ratsam, Klammern zu setzen. Klammern erhöhen zudem die Lesbarkeit des Codes. Die Schreibweise für arithmetische Operatoren kann in Kombination mit Zuweisungen vereinfacht werden, (siehe Kapitel 2.8.5).

2.8.2 Vergleichsoperatoren Vergleichsoperatoren (auch relationale Operatoren genannt) vergleichen zwei numerische Operanden oder Ausdrücke miteinander. Sie liefern als Ergebnis den Typ boolean.

Operator op1 = = op2 op1 != op2 op1 < op2 op1 op2 op1 >= op2

Bezeichnung Gleich Ungleich Kleiner Kleiner gleich Grösser Grösser gleich

Beschreibung ergibt true wenn op1 und op2 gleich sind. ergibt true wenn op1 ungleich op2 ist. ergibt true wenn op1 kleiner op2 ist. ergibt true wenn op1 kleiner oder gleich op2 ist ergibt true wenn op1 grösser op2 ist. ergibt true wenn op1 grösser oder gleich op2 ist Tabelle 12: Vergleichende Operatoren

2.8.3 Logische Operatoren Mit Hilfe der Logischen Operatoren werden boolsche Werte miteinander verknüpft. Die Operanden sind zwingend vom Typ boolean, das Resultat ebenfalls. Operator !op1 op1 && op2 op1 || op2

Bezeichnung NOT AND OR

Beschreibung ergibt false wenn op1 true ist und umgekehrt ergibt true wenn sowohl op1 als auch op2 true sind ergibt true wenn mindestens ein Operand true ist Tabelle 13: Logische Operatoren

Wichtig: Beim Operator && wird das Resultat false sobald der linke Operand false ist, der rechte Operand wird danach nicht mehr geprüft. Beim Operator || wird das Resultat true sobald der linke Operand true ist, der rechte Operand wird danach nicht mehr geprüft.

2.8.4 Bitweise Logische Operatoren Die bitweisen logischen Operatoren (auch binäre Operatoren genannt) führen Operationen auf Bitebene durch. Sie werden insbesondere zum Maskieren von Werten oder zum bitweisen Verschieben verwendet. Als Operatoren werden nur ganzzahlige Typen akzeptiert. Operator Bezeichnung Beschreibung ~op1 Komplement Invertiert jedes Bit des Operanden op1 & op2 AND bitweise UND-Verknüpfung von op1 und op2 op1 | op2 OR bitweise ODER-Verknüpfung von op1 und op2 op1 ^ op2 EXOR bitweise EXOR-Verknüpfung von op1 und op2 op1 > op2 shift right, mit Vorzeichen op1 wird um op2 Positionen nach rechts verschoben, von links wird das Vorzeichen eingefügt. op1 >>> op2 shift right, mit 0 op1 wird um op2 Positionen nach rechts verschoben, von

Version 1.3, 12.03.07

Seite 22

Funktionale Programmierung in Java

Informatik 4

links wird "0" eingefügt. Tabelle 14: Bitweise Logische Operatoren Die bitweise logischen Operatoren wurden von C/C++ übernommen, die Resultate der Operationen entsprechen denjenigen von C/C++. Die Schreibweise für bitweise logische Operatoren kann in Kombination mit Zuweisungen vereinfacht werden (siehe Kapitel 2.8.5).

2.8.5 Zuweisungsoperatoren Der Zuweisungsoperator weist der Variablen auf der linken Seite den Wert des Ausdruckes auf der rechten Seite zu. Dabei wird der Ausdruck auf der rechten Seite vor der Zuweisung ausgewertet. Der Zuweisungsoperator kann mit verschiedenen arithmetischen und logischen Operatoren kombiniert werden, um die Schreibweise zu vereinfachen.

Operator op1 = op1 += op2 op1 -= op2 op1 *= op2 op1 /= op2 op1 %= op2 op1 &= op2 op1 |= op2 op1 ^= op2 op1 = op2

op1 >>>= op2

Bezeichnung Zuweisung

Beschreibung Weist op1 das Ergebnis des Ausdrucks auf der rechten Seite zu. Zuweisung mit Addition Weist op1 die Summe aus op1 + op2 zu. Zuweisung mit Subtraktion Weist op1 die Differenz von op1 – op2 zu. Zuweisung mit Multiplikation Weist op1 das Produkt aus op1 * op2 zu. Zuweisung mit Division Weist op1 den Quotienten aus op1 / op2 zu. Zuweisung mit Modulo Weist op1 den Rest aus op1 % op2 zu. Zuweisung mit bitweisem AND Weist op1 das Resultat der bitweisen UND-Verknüpfung zu. Zuweisung mit bitweisem OR Weist op1 das Resultat der bitweisen OR-Verknüpfung zu. Zuweisung mit bitweisem EXOR Weist op1 das Resultat der bitweisen EXORVerknüpfung zu. Zuweisung mit Linksverschiebung Verschiebt op1 um op2 bits nach links und speichert das Ergebnis in op1. Zuweisung mit vorzeichenVerschiebt op1 um op2 bits nach rechts, wobei das berücksichtigter Vorzeichen berücksichtigt wird, und speichert das Rechtsverschiebung Ergebnis in op1. Zuweisung mit nullexpandierter Verschiebt op1 um op2 bits nach rechts, wobei rechts Rechtsverschiebung jeweils eine "0" eingeschoben wird, und speichert das Ergebnis in op1. Tabelle 15: Zuweisungsoperatoren

Bei allen Operationen aus Tabelle 15 liefert der ganze Ausdruck denselben Rückgabewert wie das Ergebnis, welches in op1 gespeichert wurde. Beispiel: int int int op3

op1 = 5; op2 = 8; op3; = (op1 += op2);

führt zu folgendem Ergebnis: op1 = 13, op3 = 13

2.8.6 Fragezeichen-Operator Der Fragezeichen-Operator ist der einzige Operator mit drei Operanden.

Version 1.3, 12.03.07

Seite 23

Informatik 4

Funktionale Programmierung in Java

Syntax: expr1 ? expr2 : expr3

expr1 ist ein logischer Ausdruck. Hat expr1 den Wert true, so entspricht das Resultat des gesamten Ausdrucks dem Wert von expr2, hat expr1 den Wert false, so ist das Resultat gleich dem Wert von expr3. expr2 und expr3 sind entweder vom Typ boolean, numerisch oder ein Referenztyp. Beispiel: boolean grossesSalaer = (salaer > 10000) ? true : false;

Der Fragezeichen-Operator ist eigentlich dasselbe wie eine if-else Kontrollstruktur (siehe Kapitel 2.9.2). Er wird insbesondere angewendet wenn eine kompakte Schreibweise für die Lesbarkeit des Codes erwünscht ist. Der generierte Code ist jedoch nicht effizienter als bei einer if-else Struktur.

2.8.7 Cast-Operator Mit dem Cast-Operator können Ausdrücke vom Typ A in einen Ausdruck vom Typ B umgewandelt werden. Das Casting wurde schon in Kapitel 2.4.3 behandelt.

2.8.8 Zusätzliche Operatoren der OOP Java kennt noch einige zusätzliche Operatoren, welche in der objektorientierten Programmierung verwendet werden. Diese werden im OOP-Teil dieser Unterlagen genauer beschrieben. Der Vollständigkeit halber werden sie bereits hier aufgeführt. +Operator für die Stringverkettung Ist mindestens einer der beiden Operanden im Ausdruck "a + b" ein String, so wird das Resultat des Ausdruckes ein zusammengesetzter String. Der Nicht-String Operand wird vor der Verkettung in einen String umgewandelt. = = und != für Referenzen Diese Operatoren prüfen, ob zwei Referenzen auf dasselbe Objekt zeigen oder nicht. Es wird jedoch nicht geprüft, ob die Objekte inhaltlich übereinstimmen. instanceof-Operator Mit Hilfe des instanceof-Operators wird geprüft ob ein Objekt zu einer bestimmten Klasse gehört. Diese Prüfung kann entweder zur Übersetzungszeit oder zur Laufzeit (Objekte haben zusätzliche Runtime-Informationen, RTI) erfolgen. new-Operator Arrays oder Objekte können mit dem new-Operator erzeugt werden. Der new-Operator gibt eine Referenz auf das gerade erzeugte Objekt zurück. Punkt-Operator Der Punkt-Operator erlaubt den Zugriff auf Klassen- und Instanz-Variablen sowie Methoden.

Version 1.3, 12.03.07

Seite 24

Funktionale Programmierung in Java

Informatik 4

2.9 Kontrollstrukturen 2.9.1 Einführung Die Kontrollstrukturen von Java entsprechen denjenigen der Programmiersprachen C und C++. Mit Hilfe dieser Kontrollstrukturen kann der Programmablauf abhängig von Bedingungen beeinflusst werden. Die Nachfolgenden Unterkapitel fassen diese Kontrollstrukturen kurz zusammen. Dabei wird jeweils die Syntax, das Struktogramm (Nassi-Shneidermann), die Backus-Naur Form, eine Kurzbeschreibung sowie ein Beispiel angegeben.

2.9.2 if Syntax: if() { ; } else { ; }

Struktogramm: Bedingung true

false

Anweisung_1 Anweisung_2

grafische Backus-Naur Form:

if

(

Expression

)

Statement

else

Statement

Beschreibung: if führt eine Programmverzweigung aufgrund einer Bedingung aus. Zuerst wird die Bedingung ausgewertet. Ist sie true, so werden die Anweisungen im if-Teil ausgeführt. Ist sie false, so werden die Anweisungen im else-Teil ausgeführt. Beispiel: if(val < 0) { System.out.println("Die Zahl ist negativ"); } else { System.out.println("Die Zahl ist positiv"); }

Bemerkungen: • Der else-Teil kann auch weggelassen werden.

Version 1.3, 12.03.07

Seite 25

Funktionale Programmierung in Java

Informatik 4

• •

Bestehen der if-Teil oder der else-Teil nur aus einer Anweisung, können die Klammern weggelassen werden. if-Anweisungen können verschachtelt werden: if() { if() { ; } } else { ; }



Es können auch eine Serie von Bedingungen geprüft werden: if() ; else if() ; else ;



Im Gegensatz zu C muss die geprüfte Bedingung zwingend vom Typ boolean sein! Beispiel: int value = Wert; if(value) {} // Fehler in Java, in C korrekt if(value != 0) {} // ist korrekt

2.9.3 switch Syntax: switch() { case : ; break; case : ; break; ... default: ; }

Struktogramm: Ausdruck Const_1

Const_2

Anweisung_1 Anweisung_2

Version 1.3, 12.03.07

default ...

Anweisungen

Seite 26

Funktionale Programmierung in Java

Informatik 4

grafische Backus-Naur Form:

switch

(

Expression

)

{

}

case

:

Expression

default

:

Statement

Beschreibung: switch führt eine Mehrfachverzweigung aufgrund eines Ausdrucks aus. Zuerst wird der Ausdruck ausgewertet. Danach werden die case-Werte mit diesem Ausdruck verglichen. Stimmt ein case-Wert mit dem Ausdruck überein, so werden alle folgenden Anweisungen bis zum Ende der switch-Instruktion ausgeführt. Um jeweils nur die Anweisungen in einem case auszuführen müssen diese mit einem break abgeschlossen werden. Stimmt kein case-Wert mit dem Ausdruck überein, so wird der default-Teil ausgeführt, sofern dieser vorhanden ist. Beispiel: int zahl; ... switch(zahl) { case (1): System.out.println("Zahl Eins"); break; case (2): System.out.println("Zahl Zwei"); break; ... default: System.out.println("weder noch"); }

Bemerkungen: • Der default-Teil ist optional. Wenn keine Übereinstimmung eines case mit dem Ausdruck gefunden wird, so wird keine Anweisung ausgeführt. • "Fall through": Mehrere gleich zu behandelnde Fälle können in einer Gruppe von verschiedenen case zusammengefasst werden. Die auszuführenden Anweisungen werden nach dem letzten case angegeben. Das "Fall through" sollten sie unbedingt kommentieren! Beispiel: case 'o': // Fall through case 'O': Sysetm.out.println("Buchstabe o oder O"); break;



Sowohl der Ausdruck beim switch als auch die konstanten Ausdrücke bei den case müssen ganzzahlig und vom Typ byte, char, short oder int sein. Vergleiche mit anderen Datentypen sind nicht zulässig.

2.9.4 while Syntax: while() { ; }

Version 1.3, 12.03.07

Seite 27

Funktionale Programmierung in Java

Informatik 4

Struktogramm: while (Bedingung) Anweisungen

grafische Backus-Naur Form:

(

while

Expression

)

Statement

Beschreibung: while ist eine Schleife mit Vorabprüfung. Zuerst wird die Bedingung ausgewertet. Ist sie true, wird der Anweisungsblock der while-Schleife ausgeführt. Anschliessend wird die Bedingung neu ausgewertet. Die whileSchleife wird so lange ausgeführt bis die Bedingung false wird. Beispiel: int i = 0; while( i < 10) { System.out.println("i = " + i); i++; }

Die Anweisung println() wird 10 mal ausgeführt (i = 0 bis und mit 9) Bemerkungen: • Ist die Bedingung schon zu Beginn false, wird keine Anweisung ausgeführt. • Die geprüfte Bedingung muss zwingend vom Typ boolean sein! • Die geprüfte Bedingung muss ihren Wert innerhalb der Schleife verändern, da sonst Endlosschleifen entstehen. • while-Schleifen können verschachtelt werden. • Besteht der Anweisungsblock aus nur einer Anweisung, können die geschweiften Klammern weggelassen werden.

2.9.5 do while Syntax: do { ; } while();

Struktogramm: do

Anweisungen while (Bedingung)

Version 1.3, 12.03.07

Seite 28

Funktionale Programmierung in Java

Informatik 4

grafische Backus-Naur Form:

Statement

do

while

(

Expression

)

;

Beschreibung: do-while ist eine Schleife mit Endprüfung. Der Anweisungsblock wird ein erstes Mal ausgeführt. Anschliessend wird die Bedingung ausgewertet. Ist sie true, wird der Anweisungsblock erneut ausgeführt. Die Schleife wird so lange ausgeführt bis die Bedingung false wird. Beispiel: int i = 0; do { System.out.println("i = " + i); i++; } while( i < 10);

Die Anweisung println() wird 10 mal ausgeführt (i = 0 bis und mit 9) Bemerkungen: • Im Vergleich zur while-Schleife wird bei der do-while Schleife der Anweisungsblock mindestens einmal ausgeführt. • Die geprüfte Bedingung muss zwingend vom Typ boolean sein! • Die geprüfte Bedingung muss ihren Wert innerhalb der Schleife verändern, da sonst Endlosschleifen entstehen. • do-while Schleifen können verschachtelt werden. • Besteht der Anweisungsblock aus nur einer Anweisung, können die geschweiften Klammern weggelassen werden.

2.9.6 for Syntax: for(; ; ) { ; }

Struktogramm: for (Initialisierung; Bedingung; Ausdruck) Anweisungen

grafische Backus-Naur Form:

for

(

Expression

;

Expression

;

Expression

Variable declaration )

Version 1.3, 12.03.07

Statement

Seite 29

Informatik 4

Funktionale Programmierung in Java

Beschreibung: Zuerst wird die Schleifenvariable initialisiert. In Java kann diese Variable auch erst an dieser Stelle deklariert und gleichzeitig initialisiert werden. Anschliessend wird die Bedingung geprüft. Ist sie true, so wird der Anweisungsblock ausgeführt. Danach wird der Ausdruck ausgewertet. Normalerweise wird hier die Schleifenvariable erhöht. Nun wird die Bedingung erneut geprüft. Die Schleife und die anschliessende "Änderungsanweisung" wird solange ausgeführt bis die Bedingung false ergibt. Beispiel: for(int i = 0; i < 10; i += 2) { System.out.println("i = " + i); }

Die Anweisung println() wird 5 mal ausgeführt (i = 0, 2, 4, 6 und 8) Bemerkungen: • Wird die Schleifenlvariable erst innerhalb der for-Schleife deklariert, so ist sie nur innerhalb dieser Schleife sichtbar. • Besteht der Anweisungsblock aus nur einer Anweisung, können die geschweiften Klammern weggelassen werden. • Die geprüfte Bedingung muss zwingend vom Typ boolean sein! • Die for-Schleife könnte auch durch eine while-Schleife wie folgt ersetzt werden: ; while() { ; ; }



Die einzelnen Komponenten der for-Schleife (Initialisierung, Bedingung, Ausdruck) können auch fehlen. Die Semikolon innerhalb der Klammern müssen aber auf jeden Fall stehen. Beispiel: for( ; i < 10; ) { }



// i wird weder initialisiert noch geändert!

for-Anweisung können verschachtelt werden.

2.9.7 Beeinflussung von Schleifen mit break und continue Den break Befehl haben wir bereits bei der switch-Anweisung kennen gelernt, um ein case abzuschliessen. Für diesen Fall ist case durchaus gebräuchlich. break und continue sind zwei Befehle mit denen man den Ablauf von Schleifen beeinflussen kann. Die Verwendung von break und continue zeugt normalerweise von schlechtem Programmierstiel (schlechte Lesbarkeit, schlechte Wartbarkeit). Es ist aber manchmal sinnvoll, break oder continue trotzdem zu verwenden um etwa die Schachtelungstiefe zu verringern. In diesem Falle sollten sie den Code sehr gut dokumentieren.

2.9.7.1 break Mit der break-Anweisung wird eine Schleife verlassen, unabhängig davon ob die Abbruchbedingung der Schleife erfüllt ist oder nicht. Die nach der break-Anweisung vorhandenen Programmzeilen innerhalb desselben Anweisungsblocks werden nicht mehr ausgeführt. Bei verschachtelten Schleifen wird die aktuelle Schleife verlassen, die Verschachtelungstiefe wird um 1 reduziert. Theoretisch kann auch ein break zu einem Label ausgeführt werden. Auf diese Weise können verschachtelte Schleifen verlassen werden. Dies sollte man aber unterlassen, es entspricht etwa dem goto von C. Die break-Anweisung sollte immer bedingt ausgeführt werden (mit einer if-Anweisung kombiniert). Beispiel: int array[] = new int[10]; ... // array einlesen

Version 1.3, 12.03.07

Seite 30

Informatik 4

Funktionale Programmierung in Java

// suchen ob bestimmtes Element (hier 55) in array vorhanden ist: for(int i = 0; i < array.length; i++) // { if(array[i] = = 55) { System.out.println("got it"); break; // hier wird die Suche abgebrochen falls das Element gefunden wurde! } System.out.println(array[i]); }

2.9.7.2 continue Mit der continue-Anweisung wird der Anweisungsblock einer Schleife verlassen, ohne die Programmzeilen nach continue noch auszuführen. Im Gegensatz zur break-Anweisung wird aber nicht die Schleife abgebrochen, sondern nach continue wird die Bedingung neu geprüft. Bei der for-Anweisung wird nach continue zuerst die Schleifenvariable geändert (Änderungsanweisung) und anschliessend die Bedingung geprüft. Die continue-Anweisung sollte immer bedingt ausgeführt werden (mit einer if-Anweisung kombiniert). Wie schon bei der break-Anweisung kann auch mit continue zu einem Label gesprungen werden. Auf diese Weise können verschachtelte Schleifen verlassen werden. Seien sie auch hier äusserst zurückhaltend. Beispiel: Division durch 0 mit continue vermeiden int array[] = new int[10]; ... // array einlesen // Ausgabe der Kehrwerte: for(int i = 0; i < array.length; i++) // { if(array[i] = = 0) { System.out.println("Element ist 0 "); continue; // der Kehrwert von 0 darf nicht berechnet werden! } System.out.println("Kehrwert von " + array[i] + "ist: " + (1 / array[i])); }

2.10 Methoden 2.10.1 Name, Parameterliste und Rückgabewert Grundsätzlich kennt Java Klassen- und Objektmethoden, dazu mehr im Kapitel OOP. In diesem Kapitel werden wir uns mit Klassenmethoden (statischen Methoden) befassen. Mit statischen Methoden ist es möglich, in Java rein funktional zu programmieren. Statische Methoden haben etwa dieselben Eigenschaften wie Funktionen, welche sie von der Programmiersprache C kennen, sie "gehören" aber immer zu einer Klasse. Es gibt keine Methoden ausserhalb von Klassen. Sie können Code, den sie mehrmals verwenden müssen, in einer Methode "zusammenfassen", sie können die Methoden aus anderen Methoden aufrufen, oder Methoden können sich selber aufrufen (rekursiv). Methoden verleihen ihrem Programm eine klare modulare Struktur. Der Code wird testbar und wiederverwendbar. Syntax von Methoden: modifier Rückgabedatentyp methoden_name(parameter_liste) { // Rumpf der Methode }

Version 1.3, 12.03.07

Seite 31

Informatik 4

Funktionale Programmierung in Java

Eigenschaften von Methoden • Der Methodenkopf definiert die Schnittstelle der Methode. Aufrufende Methoden haben sich an diese Schnittstelle zu halten. • Der modifier legt die Sichtbarkeit fest. Zudem kann hier mit dem Schlüsselwort "static" angegeben werden, dass es sich um eine statische Methoden handelt. • Der Rückgabedatentyp gibt an, welchen Datentyp der Rückgabewert hat. Dieser wird am Schluss der Methode mit dem Befehl return zurückgegeben. Der Rückgabewert wird normalerweise verwendet um Ergebnisse einer Berechnung oder allfällige Fehler weiterzuleiten. Im Kapitel Exception Handling werden wir noch eine weitere Möglichkeit kennen lernen, um Fehler anzuzeigen. Es kann maximal ein Wert zurückgegeben werden. Wird kein Parameter zurückgegeben, so ist der Rückgabedatentyp void. Rückgabewerte können elementare Datentypen, Arrays oder Objekte von Klassen sein. Elementare Datentypen werden "by value" zurückgegeben, Arrays und Objekte als Referenzen. • Der Methodenname wird zur Identifikation der Methode verwendet. Der symbolische Name hat sich an die Syntaxregeln aus Kapitel 2.2.2 zu halten. In Java werden Methodennamen in der Regel klein geschrieben. • Mit Hilfe der Parameterliste können der Methode Werte übergeben werden. In dieser Liste müssen sowohl die Datentypen als auch die Namen der Parameter angegeben werden (formale Parameter). Diese Parameter gelten im gesamten Methodenrumpf. Als Parameter können sie elementare Datentypen, Arrays oder Klassen übergeben. Wenn sie elementare Datentypen als Parameter haben, so werden diese als "Call by Value" übergeben, d.h. jeweils eine Kopie. Sie können so die Parameter innerhalb der Methode ändern, ohne dabei das Original zu verändern. Eine Übergabe elementarer Datentypen "by reference", wie sie das von C kennen, ist in Java nicht möglich. Arrays und Objekte von Klassen werden als Referenz übergeben. Werden keine Parameter übergeben, so sind die Klammern des Methodenkopfs leer. • Der Rumpf der Methode innerhalb der geschweiften Klammern enthält eine Folge von Variablendeklarationen und Anweisungen. Variablen, welche innerhalb einer Methode deklariert werden, sind auch nur innerhalb dieser Methode sichtbar. Oder noch genauer: Variablen sind vom Zeitpunkt ihrer Deklaration bis zum Methodenende bzw. bis zum Ende des Blocks, in welchem sie deklariert wurden, sichtbar. Sobald die Methode verlassen wird, werden alle lokalen Variablen ungültig. Dynamisch erzeugte Variablen werden von der garbage collection entsorgt. • Die Reihenfolge, wie sie die Methoden definieren, ist beliebig. Prototypen von Methoden sind nicht erforderlich. Das heisst insbesondere, dass Java keine Header- oder Include-Files wie C/C++ braucht. Beispiel: public class Methoden_1 { static final float PI = 3.1415f; /** * Beispiel call by value, return als Wert * berechneFlaecheKreis() berechnet die Fläche eines Kreises */ static float berechneFlaecheKreis(float r) { return(r*r*PI); } /** * Beispiel call by reference * loescheArray() löscht alle Elemente eines Arrays. */ static void loescheArray(int array[]) { for(int i = 0; i < array.length; i++) { array[i] = 0; } } /** * Beispiel return als Referenz * erzeugeBooleanArray generiert ein Array von Boolean und gibt dieses * als Referenz zurück. */

Version 1.3, 12.03.07

Seite 32

Funktionale Programmierung in Java

Informatik 4

static boolean [] erzeugeBooleanArray() { boolean bool[] = new boolean[3]; bool[0] = true; bool[1] = true; bool[2] = false; return(bool); } /** * Hauptprogramm */ public static void main(String[] args) { // Test: call by value, return als Wert float r = 2.12f; // Radius des Kreises float f; // Fläche des Kreises f = berechneFlaecheKreis(r); System.out.println("Radius = " + r + " Fläche Kreis = " + f); // Test: call by reference int feld[] = {1, 6, 83}; // Original-Array loescheArray(feld); System.out.print("Int Array: "); for(int i = 0; i < feld.length; i++) System.out.print(feld[i] + " "); System.out.println(""); // Test: return als Referenz boolean boolarray[]; boolarray = erzeugeBooleanArray(); System.out.print("Boolean Array: "); for(int i = 0; i < boolarray.length; i++) System.out.print(boolarray[i] + " "); System.out.println(""); } }

Dieser Code führt zu folgender Ausgabe auf dem Bildschirm: Radius = 2.12 Fläche Kreis = 14.119156 Int Array: 0 0 0 Boolean Array: true true false

2.10.2 Signatur, Prototyp, Overloading Unter dem Begriff Signatur versteht man den Namen der Methode sowie die Definition der Parameterliste. Bsp: funcName(int, int) . Die Namen der Parameter müssen in der Signatur nicht unbedingt spezifiziert werden. Unter dem Begriff Prototyp einer Methode versteht man deren Signatur mit zusätzlicher Angabe des Typs des Rückgabewertes und der Modifier. Bsp. public static void funcName(int, int). Methoden mit gleichem Namen aber unterschiedlicher Parameterliste (Anzahl oder Typ der Parameter) haben eine unterschiedliche Signatur und gelten als verschieden. Diese Unterscheidung nennt man Overloading. Das Overloading-Konzept hat Java von C++ übernommen. Die Anwendung des Overloading-Konzepts ist dann sinnvoll, wenn wir Methoden mit gleicher Funktionalität aber unterschiedlichen Parametern haben, wie beispielsweise die Berechnung des Maximums: public class Overloading { static int max(int i, int j) { System.out.println("Aufruf von int max(int, int)"); return(i > j) ? i : j;

Version 1.3, 12.03.07

Seite 33

Informatik 4

Funktionale Programmierung in Java

} static long max(long i, long j) { System.out.println("Aufruf von long max(long, long)"); return(i > j) ? i : j; } public static void main(String[] args) { int a1 = 1, a2 = 2, max_int; long b1 = 22L, b2 = 33L, max_long; max_int = max(a1, a2); // ruft die Methode int max(int, int) auf max_long = max(b1, b2); // ruft die Methode long max(long, long) auf System.out.println("max_int = " + max_int + " max_long = " + max_long); } }

Merke: Eine Methode static long max(int i, int j) würde in diesem Beispiel zu einer Fehlermeldung führen, weil sie dieselbe Signatur wie die Methode static int max(int, int) hat.

2.10.3 Rekursion Rekursion oder rekursiver Aufruf bedeutet, dass sich eine Methode selber wieder aufruft. Lokale Variablen werden durch den rekursiven Aufruf jeweils neu angelegt und initialisiert. Wichtig ist, dass eine Rekursion ein Abbruchkriterium definiert, welches auch erreicht wird! Ein rekursiver Ansatz kann dann gewählt werden, wenn sich ein Problem in Teilprobleme aufteilen lässt, welche ähnlich wie das Hauptproblem gelöst werden können (Beispiel Fakultät). Entscheidend ist dabei das Abbruchkriterium, sonst kann es sein, dass für die Lösung des Problems der Speicher nicht reicht oder das Programm in einer Endlosschleife dreht! Für viele Probleme lassen sich aber auch nichtrekursive Lösungen finden. Beachte: Bei rekursiven Ansätzen ist Vorsicht geboten! Lieber einen nichtrekursiven Ansatz wählen, auch wenn dieser auf den ersten Blick vielleicht nicht ganz so elegant erscheint. In sicherheitsrelevanten Applikationen sind rekursive Ansätze verboten! Beispiel: Berechnung des ggt mit Hilfe des euklidschen Algorithmus: public class Euklid { static int ggt(int a, int b) { if(b != 0) // Abbruchkriterium return(ggt(b, a%b)); // rekursiver Aufruf, ggt() ruft sich selber auf. return(a); } public static void main(String args[]) { int a, b, res; a = Integer.parseInt(args[0]); b = Integer.parseInt(args[1]); res = ggt(a, b); System.out.println("ggt von " + a + " und " + b + " ist: " + res); } }

Version 1.3, 12.03.07

Seite 34

Informatik 4

Funktionale Programmierung in Java

2.11 main() Beim Programmstart kann man einer Applikation Parameter mitgeben: java .... Die einzelnen Parameter werden durch Leerschläge getrennt. Jedes Java-Programm hat genau eine Methode namens main(), die Applikation wird über dieses main() gestartet. Der Name der Klasse, in welcher main() definiert ist, muss mit dem Programmnamen übereinstimmen. Die Methode main() hat folgenden Prototyp: public static void main(String args[]). Die vom Anwender eingegebenen Parameter werden der Methode main() im String-Array args übergeben. Die einzelnen Parameter können mit args[i] ausgelesen werden. Im Gegensatz zu C/C++ enthält args[0] in Java nicht den Programmnamen, sondern den ersten Parameter! Der zweite Parameter wird in args[1] abgelegt usw. Die Parameter werden immer als String übergeben. Manchmal muss man deshalb eine Umwandlung vornehmen, beispielsweise wenn ein Parameter als Integer interpretiert werden soll: int parameter_1 = Integer.parseInt(args[1]); Mit args.length kann überprüft werden, wie viele Parameter eingegeben wurden. Es ist oft sinnvoll, die Anzahl der eingegebenen Parameter zu überprüfen und im Fehlerfalle eine Meldung auszugeben. Beispiel: public class MainArgs { public static void main(String[] args) { for(int i = 0; i < args.length; i++) { System.out.println("Parameter args[" + i + "] =" + args[i]); } } }

beim Aufruf von java MainArgs Hallo 2

wird folgender Inhalt auf dem Bildschirm ausgegeben: Parameter args[0] = Hallo Parameter args[1] = 2

Version 1.3, 12.03.07

Seite 35

Informatik 4

Funktionale Programmierung in Java

2.12 Unterschiede zu C In diesem Kapitel werden die wichtigsten Unterschiede von Java zur Programmiersprache C aus der Sicht der funktionalen Programmierung zusammengefasst. Dies sind insbesondere: • • • • • • • • • • • • •

In Java spricht man von Methoden, Attributen und Variablen, in C von Funktionen und Variablen. Zeilenkommentare können mit "//" erzeugt werden. Dies wird von vielen C-Compilern ebenfalls unterstützt, entspricht aber nicht ANSI-C. Es gibt kein "#include" und keine Header-Dateien. Klassen werden mit "import" eingebunden. Variablen können auch innerhalb des auszuführenden Codes deklariert werden. Ausgaben auf die Konsole erfolgen mit System.out.print() anstelle von printf(). Variablen-Typen sind in Java eindeutig definiert (so ist beispielsweise ein "int" immer 32 bit). "char" sind in UNICODE definiert. Java unterstützt den Typ boolean. Der Java-Compiler führt eine strenge Typenprüfung durch. Ausdrücke wie "if(i=1)" werden als Fehler taxiert. Java unterstützt Strings, Strings sind eine Klasse und nicht ein primitiver Datentyp. Arrays sind in Java sicherer als in C. Insbesondere löst ein Zugriff auf einen Index ausserhalb des Arrays eine Exception aus. Java hat keine Pointer. Dafür gibt es Referenzen. Eine Parameterübergabe "call by reference" kann in Java nicht mit primitiven Datentypen erfolgen (wie bei C mit Pointern). Entweder müssen Arrays oder Objekte übergeben werden (Referenzen). In Java können Methoden überladen werden, d.h. sie haben denselben Namen aber unterschiedliche Argumente. In Java werden folgende C-Konstrukte nicht unterstützt: "typedef", "struct", "enum", "#define", "goto" (goto braucht ein guter Programmierer sowieso nicht!).

Version 1.3, 12.03.07

Seite 36

Informatik 4

Einführung in die OOP mit Java

3 Einführung in die OOP mit Java 3.1 Einleitung Heute unterscheidet man zwei Gruppen von Programmiersprachen: 1) Die prozedurale oder funktionale Programmierung (C, Pascal, Fortran usw.): Diese Programmiersprachen haben gemeinsam, dass dem funktionalen Ablauf (den Aktionen) besondere Bedeutung beigemessen wird. Programme werden dabei Top-Down ausgeführt. Daten sind ebenfalls wichtig, sie treten aber im Vergleich zum funktionalen Ablauf in den Hintergrund. 2) Die objektorientierte Programmierung (Java, C++, Smalltalk usw.): Hier wird versucht, die Realität in Objekten darzustellen. Die objektorientierten Programmiersprachen unterstützen diesen Ansatz wesentlich besser als funktionale Programmiersprachen. Beispiel: In einem Grafikprogramm sollen Kreise gezeichnet werden können. In einem funktionalen Ansatz würden sie Variablen für Radius, Mittelpunkt und vielleicht Farbe des Kreises definieren. Idealerweise würden sie alle diese Daten in einem struct zusammenfassen. Danach brauchen sie Funktionen welche beispielsweise in der Lage sind, einen Kreis zu zeichnen oder einen Kreis zu verschieben. Sie können nun diesen Funktionen Werte von Kreisen übergeben und die Funktionen werden diese Kreise darstellen. Es fehlt aber ein übergeordnetes Ganzes zwischen Funktionen und Daten. Ein nächster Schritt wären abstrakte Datentypen (siehe Kapitel 3.1.2). Hier könnte man versuchen, die Daten der Kreise gegen aussen zu verbergen und Funktionen für deren Modifikation bereitzustellen (z.B. setRadius() ). Dieser Ansatz ist eigentlich gut, sie erhalten aber von funktionalen Programmiersprachen fast keine Unterstützung dafür. In einem objektorientierten Ansatz würden sie eine Klasse Kreis implementieren (eine Klasse ist eine Art Bauplan oder Kochbuch für das, was ein Objekt hat und kann). Diese Klasse würde sowohl die Daten, welche zu einem Kreis gehören, verwalten und gegen aussen verbergen. Gleichzeitig würde sie alle Funktionen (in der OOP spricht man dann von Methoden) auf diese Daten bereitstellen. Nun können sie eine beliebige Anzahl Objekte dieser Klasse anlegen (d.h. nun werden aufgrund des Bauplanes oder des Kochbuchs aus der Sicht der Software "lebende" Objekte generiert). Der Anwender ihres Grafikprogramms will ja vielleicht mehrere Kreise zeichnen. Dies wären dann alles Objekte der Klasse Kreis, jedoch mit unterschiedlichen Radien, Farben usw. Was in diesem Einführungsbeispiel kurz skizziert wurde soll in diesem Kapitel ausführlich besprochen werden.

3.1.1 Probleme funktionaler Programmiersprachen Bei herkömmlichen, funktionalen Programmiersprachen treten insbesondere bei grösseren Projekten, die über eine lange Zeitdauer gewartet und ergänzt werden, folgende Probleme auf: 1) Datenstrukturen werden global definiert. Funktionen welche diese Strukturen verwenden sind über das ganze Programm verteilt. Wenn sie nun die Datenstrukturen anpassen, müssen sie alle diese Funktionen ebenfalls modifizieren. 2) Wenn sie neue Funktionalität in ihr Programm einbauen müssen, welche ähnlich ist wie eine bereits existierende Funktion, werden sie diese kopieren und anpassen. Dadurch entstehen riesige Berge von Programmcode mit vielen Redundanzen. Sie müssen alle diese neuen Funktionen wieder testen. Noch schlimmer wird es, wenn sie in einer "ursprünglichen" Funktion einen Fehler entdecken. Dann müssen sie diesen auch in sämtlichen Klonen korrigieren.

3.1.2 Abstrakte Datentypen Abstrakte Datentypen (ADT, Abstract Data Types) können auch mit funktionalen Sprachen programmiert werden. Ihr Hauptmerkmal ist, dass sie Schnittstelle und Implementierung trennen. Grundsätzlich sind sie wie folgt aufgebaut:

Version 1.3, 12.03.07

Seite 37

Informatik 4

• •

Einführung in die OOP mit Java

Daten: Die Struktur der Daten ist nur innerhalb des ADT bekannt, sie wird gegen aussen verborgen (information hiding). Methoden: Diese Bearbeiten die Daten. Ein Anwender kann nur über diese Methoden auf die internen Daten zugreifen.

Mit Hilfe von ADT's können Programme sauber modularisiert und die Daten gekapselt werden. CodeÄnderungen sind dadurch gut durchführbar: Sie können beispielsweise die Datenstruktur innerhalb eines ADT anpassen. Nun müssen sie natürlich auch die Methoden dieses ADT's anpassen, welche auf diese Daten zugreifen. Solange sie aber die Schnittstellen des ADT nicht verändern, müssen sie ihr Programm an anderen Stellen nicht ändern. Die Änderung beschränkt sich auf den ADT. Das Problem liegt nun wie eingangs schon erwähnt darin, dass funktionale Programmiersprachen ihnen fast keine Unterstützung für ADT's bieten.

3.1.3 Forderungen an eine OO-Programmiersprache Aus obigen Erkenntnissen können folgende Anforderungen an eine objektorientierte Programmiersprache abgeleitet werden: 1) Es müssen neue, problembezogene "Datentypen" definiert werden können. Daten und Methoden dieser Datentypen müssen eine Einheit bilden. 2) Die internen Datenstrukturen sollen von aussen nur zugänglich sein, wenn dies ausdrücklich gefordert ist, ansonsten werden sie versteckt. 3) Nach Aussen sind nur die Schnittstellen der Methoden sichtbar, deren Implementierung wird verborgen. 4) Gemeinsamer Programmcode soll ausgeklammert werden können. 5) Es muss eine Technik zur Code-Änderungen von bestehendem Code bereitgestellt werden.

3.2 Eigenschaften der OOP In der OOP wird versucht, die reale Welt durch Objekte im Programm abzubilden. Dies können Pflanzen, Tiere, Menschen, Maschinen, Fahrzeuge usw. sein. Viele dieser Objekte haben ähnliche Eigenschaften und Beziehungen zueinander, wodurch eine Hierarchie dieser Objekte entsteht (Bsp. ein Kreis und ein Rechteck sind beides geometrische Objekte, welche Koordinaten und eine Farbe haben). Die OOP baut auf folgenden Prinzipien auf: • Abstraktion • Kapselung • Vererbung und Polymorphismus

3.2.1 Abstraktion durch Objekte Der Programmierer wird versuchen, die Realität in geeigneten Objekten in seinem Programm abzubilden. Die Betonung liegt hier auf dem Wort "versuchen", denn dieser Schritt kann sehr schwierig sein und erfordert bei komplexeren Problemen viel Erfahrung. Ein Objekt bildet eine Software-Einheit. Es enthält sowohl Attribute als auch Methoden: Die Attribute sind die Datenelemente. Sie können während der Lebensdauer des Objektes geändert werden. Beispiele für Attribute sind etwa: Grösse, Länge, Gewicht, Farbe, Drehzahl, Geschwindigkeit usw. Dies können auch andere Objekte sein (Bsp.: ein Kreis hat ein Attribut Mittelpunkt, dieses ist ein Objekt Punkt). Die Methoden sind die Funktionen, die auf dieses Objekt anwendbar sind. Sie definieren das Verhalten eines Objekts. Beispiele sind: addieren, sortieren, setzen, löschen, starten, sich zeichnen, fahren usw. Beispiel Objekt Kreis: Attribute: x_coord, y_coord, radius, isVisible usw. Methoden: setCoord(), draw(), move() usw. Sie können mehrere "lebende" Kreise in ihrem Programm verwalten. Diese haben alle dieselben Attribute und Methoden. Die Werte der Attribute können aber unterschiedliche sein. Kreis 1 hat vielleicht einen anderen

Version 1.3, 12.03.07

Seite 38

Informatik 4

Einführung in die OOP mit Java

Radius als Kreis 2 und auch andere Koordinaten. Wenn der Anwender eines Grafikprogramms einen neuen Kreis zeichnen will, generiert das Programm einfach ein neues Objekt vom Typ Kreis, setzt Mittelpunkt, Radius usw. und ruft danach die Methode draw() auf, wodurch der Kreis auf dem Bildschirm erscheint. x_coord y_coord radius isVisible

Programm

setCoord() draw() move()

Kreis 3 r=5

Kreis 1 r=4

Bildschirm Kreis 2 r = 10

Abbildung 4: Objekte des Typs Kreis Mehr zum Thema Objekte finden sie in Kapitel 3.3.

3.2.2 Kapselung Normalerweise sind die Attribute eines Objektes nach aussen nicht sichtbar, sie sind versteckt. Der Anwender kann sie nur über Methoden desselben Objekts modifizieren. Von den Methoden ist nur die Schnittstelle bekannt, die Implementation wird ebenfalls verborgen. Dies hat den Vorteil, dass sie in ihrem Programm die internen Datenstrukturen des Objektes oder die Implementation verändern können, ohne dass sie andere Codesequenzen, die mit diesen Objekten arbeiten, ebenfalls ändern müssen. Bedingung dazu ist natürlich, dass sie die Schnittstellen der Methoden nicht modifizieren müssen. Beispiel: Sie wollen in einem Objekt die Methode calculate() zur Berechnung eines komplexen Algorithmus verbessern. Sie können nun sowohl die Methode als auch die Datentypen der verwendeten Attribute anpassen, ohne dass dies Konsequenzen für andere Programmteile hat. Merke: Das Konzept der Kapselung führt dazu, dass die Datensicherheit erhöht und der Code änderungsfreundlicher wird.

3.2.3 Vererbung und Polymorphismus Bei der Vererbung geht es darum, Gemeinsamkeiten verschiedener Objekte zu finden um schlussendlich eine Hierarchie der Objekte zu entwerfen. Gemeinsamer Code kann damit zusammengefasst werden. Neue Objekte können definiert werden und auf bereits existierendem Code aufbauen. Einleitend haben wir bereits das Beispiel der geometrischen Objekte Kreis und Rechteck erwähnt. Ausgangslage ist ein geometrisches Objekt, es hat Koordinaten (X und Y) sowie ein Attribut isVisible, welches angibt, ob das Objekt auf dem Bildschirm angezeigt werden soll oder nicht. Das geometrische Objekt dient als Basisobjekt für die von ihm abgeleiteten Objekte Kreis und Rechteck, welche die Eigenschaften des geometrischen Objektes erben (Attribute und Methoden). Der Kreis hat damit bereits Koordinaten. Neu muss für den Kreis noch der Radius definiert werden. Auch das Rechteck erbt vom geometrischen Objekt die Koordinaten. Für das Rechteck müssen noch Breite und Höhe definiert werden.

Version 1.3, 12.03.07

Seite 39

Einführung in die OOP mit Java

Informatik 4

geometrisches Objekt x_coord y_coord isVisisble

Kreis radius

Rechteck width height

Abbildung 5: Hierarchie grafischer Objekte Die Methoden wurden in dieser Abbildung weggelassen. Ein Kreis hat nun zusätzlich zum Attribut Radius auch die vom geometrischen Objekt geerbten Attribute x_coord, y_coord und isVisible. Ein Rechteck hat folgende Attribute: x_coord, y_coord, isVisibel, width und height. Mehr zum Thema Vererbung und Polymorphismus finden sie in den Kapiteln 3.4 und 3.4.4.

3.2.4 Eigenschaften von Java Java ist eine objektorientierte Programmiersprache mit folgenden Eigenschaften: • Java basiert auf Klassen (siehe Kapitel 3.3) • In Java sind alle Klassen von einer gemeinsamen Basisklasse abgeleitet: Object • Java unterstützt keine Mehrfachvererbung (Kapitel 3.4) • Java unterstützt das Schnittstellenkonzept (Interfaces, Kapitel 3.6) • Java unterstützt Polymorphismus (Kapitel 3.4.4) • In Java können Methoden überladen werden

3.3 Klassen und Objekte 3.3.1 Definitionen Bleiben wir vorerst beim Beispiel mit den geometrischen Objekten Kreis und Rechteck. Im Programmcode müssen die Attribute und Methoden irgendwo definiert werden. Dies geschieht durch sogenannte Klassen. Klassen sind somit Baupläne oder Kochbücher für die Objekte, sie bilden den statischen Teil des Programms. Nun kann man Objekte einer Klasse anlegen (instanzieren, siehe Kapitel 3.3.8), wodurch Exemplare oder Instanzen der Klasse entstehen ("lebende" Objekte im Sinne des Programms). Diese bilden den dynamischen Teil des Programms: Instanzen können erzeugt und wieder vernichtet werden. Im Vergleich zur Programmiersprache C wären etwa anwenderdefinierte Datenstrukturen die Klassen und Variablen davon die Instanzen. Der Aufruf einer Methode wird auch als Versenden einer Nachricht an eine Instanz bezeichnet.

3.3.2 Syntax Eine Klasse wird mit Hilfe des Schlüsselwortes class definiert: modifier class Klassenname { // Deklaration der Attribute // Deklaration der Methoden }

Version 1.3, 12.03.07

Seite 40

Informatik 4

Einführung in die OOP mit Java

Der Klassenname muss projektweit eindeutig sein und mit dem Filenamen übereinstimmen. Für unser KreisObjekt könnten wir eine Klasse Kreis definieren und diese im File Kreis.java speichern. Ein Java-Projekt besteht fast immer aus mehreren bis sehr vielen Klassen. Üblicherweise wird jede Klasse in einem eigenen File gespeichert. Eine Ausnahme bilden z.B. die inneren Klassen (siehe Kapitel 3.3.11). Sollten sie dennoch mehrere Klassen in einem File speichern, so darf nur eine dieser Klassen den Zugriffsmodifier „public“ haben (siehe Kapitel 3.3.5), die anderen werden ohne Modifier definiert.

3.3.3 Attribute Attribute sind ähnlich wie Methoden-Variablen, die wir schon kennen gelernt haben. Ihre Sichtbarkeit bezieht sich aber nicht nur auf eine Methode, sondern die Attribute gehören zur ganzen Klasse. Wenn eine Instanz einer Klasse angelegt wird, so wird Speicherplatz für alle Attribute dieses Objektes bereitgestellt. Die Lebensdauer dieser Attribute ist damit identisch zu derjenigen der Instanz. modifier class Klassenname { // Deklaration der Attribute modifier datatype name_1; modifier datatype name_2; ... // Deklaration der Methoden ... }

Attribute werden deklariert, indem man einen Modifier (siehe Kapitel 3.3.5), den Datentyp sowie einen Namen für das Attribut angibt. Auf die Attribute einer Instanz wird mit Hilfe des Punktoperators zugegriffen: Instanzname.Attributname

3.3.4 Methoden Statische Methoden haben wir bereits im Kapitel "Funktionale Programmierung mit Java" kennen gelernt. Allgemein führen Methoden irgendwelche Funktonen auf ein Objekt aus, sei dies, dass sie Attribute verändern oder eine Aktion auslösen (z.B. draw() für den Kreis). Methoden können nur innerhalb von Klassen definiert werden. Es gibt in Java keine Möglichkeit, globale Funktionen zu definieren. Methoden werden wie folgt definiert: modifier class Klassenname { ... // Deklaration der Methoden modifier rückgabedatentyp methoden_name(parameter_liste) { // Rumpf der Methode } ... }

Methoden werden deklariert, indem man einen Modifier (siehe Kapitel 3.3.5), den Rückgabe-Datentyp, den Namen der Methode sowie eine Parameterliste für die Übergabewerte angibt. Auf die Methoden einer Instanz wird mit Hilfe des Punktoperators zugegriffen: Instanzname.Methodenname()

Version 1.3, 12.03.07

Seite 41

Einführung in die OOP mit Java

Informatik 4

3.3.5 Zugriffsmodifier Die Sichtbarkeit von Klassen, Methoden und Attributen kann durch die Schlüsselwörter public, protected und private definiert werden. Der Vollständigkeit halber wird hier auch schon die Sichtbarkeiten innerhalb von Packages (siehe Kapitel 3.7) angegeben. Mit der Sichtbarkeit wird definiert, ob Attribute oder Methoden einer Klasse verwendet werden können oder nicht (. respektive .). Dabei wird jeweils unterschieden, ob es sich um Code aus derselben Klasse, einer abgeleiteten Klasse, Code aus demselben Package oder generellem Code handelt. Die Sichtbarkeit kann in 4 Stufen eingeteilt werden: Schlüsselwort public keines

protected

private

Zugriff möglich öffentlich , von überall

Beschreibung Zugriff von überall ohne Einschränkungen, ist für Attribute oft nicht sinnvoll. in der gleichen Klasse und aus Zugriff ausserhalb des Package nicht möglich allen Klassen des gleichen Package, nicht aber aus abgeleiteten Klassen in der gleichen und in allen Der Programmierer hat innerhalb der Klasse und abgeleiteten Klassen sowie im allen abgeleiteten Klassen Zugriff auf alle gleichen Package Attribute und Methoden, nicht aber der Anwender von Instanzen dieser Klasse. nur in der gleichen Klasse Private Attribute und Methoden sind nach aussen nicht sichtbar. Dies ist sehr restriktiv: Auch in abgeleiteten Klassen hat der Programmierer keinen Zugriff. Wird oft für Attribute verwendet. Tabelle 16: Zugriffsmodifier

Bemerkung zu Zugriffsmodifiern für Klassen (class) und Schnittstellen (interface): Hier gibt es nur den Zugriff von überall (public) oder den Zugriff aus demselben Package (kein Schlüsselwort, default). Für innere Klassen (siehe Kapitel 3.3.11) sind auch private und protected erlaubt. Beispiele: public class Kreis { private int x_coord; private int y_coord; private int radius; public boolean isVisible; public void setCoord(int x, int y) { x_coord = x; y_coord = y; } protected void draw() { // draws the circle } private void move(int x, int y) { x_coord += x; y_coord += y; draw(); } }

Version 1.3, 12.03.07

Seite 42

Informatik 4

Einführung in die OOP mit Java

public class TestAccess { public static void main(String[] args) { Kreis meinKreis = new Kreis(); // //meinKreis.x_coord = 5; // meinKreis.isVisible = true; // meinKreis.setCoord(5, 7); // meinKreis.draw(); // //meinKreis.move(1,2); // } }

Konstruktor, Erklärung erfolgt später im Skript Compiler:The field meinKreis.x_coord is not visible ist immer ok ist immer ok ist ok für gleiches Package Compiler: The method move() is not visible

Beschreibung dieses Beispiels: Die main()-Methode, durch welche das Programm gestartet wird, ist in der Klasse TestAccess definiert. Zuerst wird hier eine Instanz namens meinKreis der Klasse Kreis angelegt. Anschliessend folgen verschiedene Beispiele wo versucht wird, mit Hilfe des Punktoperators auf die Attribute und Methoden dieses Objektes zuzugreifen. Auf das Attribut x_coord kann nicht zugegriffen werden, weil x_coord in der Klasse Kreis als private deklariert wurde. Der Zugriff auf das Attribut isVisible ist hingegen möglich, weil isVisible public ist. Dasselbe gilt für die Methode setCoord, welche ebenfalls public ist. Der Aufruf der Methode draw() ist hier ebenfalls möglich, weil die Klassen TestAccess und Kreis im selben Package definiert sind. Wäre dies nicht so, könnte man auch nicht auf die Methode draw() zugreifen. Die Aufruf der Methode move() führt ebenfalls zu einer Fehlermeldung des Compilers. move() kann nur innerhalb der Klasse Kreis aufgerufen werden, da diese Methode private ist.

3.3.6 Klassendiagramme Analyse und Design der objektorientierten Programmierung werden meistens mit der UML (Unified Modeling Language) durchgeführt. Die sogenannten Klassendiagramme sind ein Bestandteil der UML, andere Diagramme werden im Kapitel 3.8.1 beschrieben. Bei den Klassendiagrammen geht es darum, die einzelnen Klassen grafisch darzustellen und ihre Beziehungen untereinander zu zeigen. Eine Klasse wird wie folgt modelliert:

Class name Attributes Methods

Abbildung 6: Design einer Klasse Beispiel: Von der Klasse Kreis aus Kapitel 3.3.5 würde das Klassendiagramm wie folgt aussehen:

Kreis x_coord y_coord radius isVisible setCoord() draw() move()

Abbildung 7: Klassendiagramm der Klasse Kreis Version 1.3, 12.03.07

Seite 43

Einführung in die OOP mit Java

Informatik 4

Klassendiagramme (und allgemein UML-Diagramme) werden mit SW-Tools erstellt. Professionelle Tools bieten durchgehend Hilfe für Analyse, Design und Code-Phase. Sie sind in der Lage, aus den gezeichneten Diagrammen Code zu erstellen, aus Code Diagramme zu erstellen (Reverse Engineering) und den Programmierer auf Inkonsistenzen hinzuweisen. Je nach verwendetem Tool werden im Klassendiagramm in Abbildung 7 die Modifier mit grafischen Symbolen dargestellt (beispielsweise ein Schlüssel für private). Ein professionell arbeitender Softwareentwickler wird immer zuerst ein Klassendiagramm entwickeln bevor er mit dem Codieren beginnt.

3.3.7 Klassenmethoden und Klassenattribute Normalerweise sind Attribute und Methoden einer Klasse immer an eine lebende Instanz "gebunden". Dies haben wir schon im Beispiel in Kapitel 3.3.5 gesehen. Wir mussten zuerst einen Kreis instanzieren, um anschliessend Attribute dieses Objekts zu setzten oder auch seine Methoden aufzurufen. Nun gibt es aber auch Attribute und Methoden, die nicht an eine Instanz gebunden sind, sondern vom Programmstart bis ans Programmende existieren. Man nennt diese Klassenattribute und Klassenmethoden. Sie werden mit Hilfe des Schlüsselwortes static deklariert. Wir sind bereits mehrmals Klassenmethoden begegnet, ohne dies explizit anzugeben. Wenn ein Programm gestartet wird, so wird nach einer main()-Methode gesucht. Diese muss aber aufgerufen werden können, ohne dass schon irgendwelche Objekte existieren: public static void main(String[] args)

main() ist also eine Klassenmethode. Klassenmethoden können nur Klassenattribute und andere Klassenmethoden aufrufen, welche ebenfalls durch das Schlüsselwort static deklariert sind. Im Kapitel 2 (Funktionale Programmierung mit Java) haben wir ausschliesslich mit Klassenattributen und Klassenmethoden gearbeitet. Beispiel: Im Beispiel aus Kapitel 3.3.5 soll die Klasse Kreis um ein Klassenattribut und eine Klassenmethode erweitert werden: public class Kreis { private int x_coord; private int y_coord; private int radius; public boolean isVisible; public static int myClassAttr = 8; public void setCoord(int x, int y) { x_coord = x; y_coord = y; myClassAttr++; }

public static void printInfo() { //isVisible = false;

// Klassenattribut

// Zugriff auf Klassenattribut ist ok

// Klassenmethode

// Fehler, kein Zugriff auf normale // Attribute aus Klassenmethoden ! System.out.println("myClassAttr = " + myClassAttr); // ist ok.

} } public class Klassenmethoden { public static void main(String[] args) { // Test Zugriff über die Klasse, es existiert noch keine Instanz !

Version 1.3, 12.03.07

Seite 44

Einführung in die OOP mit Java

Informatik 4

Kreis.printInfo(); //myClassAttr = 6; Kreis.myClassAttr = 5; //Kreis.isVisible = true; Kreis.printInfo(); //Kreis.setCoord(5,7);

// // // // // //

ist ok Fehler ist ok Fehler, kein Klassenattribut ist ok Fehler, keine Klassenmethode

// Test Zugriff über die Instanz Kreis meinKreis = new Kreis(); meinKreis.isVisible = true; meinKreis.myClassAttr = 6; meinKreis.printInfo(); meinKreis.setCoord(5,7);

// // // //

ist ist ist ist

ok ok, aber nicht gebräuchlich ok, aber nicht gebräuchlich ok

} }

Ausgabe auf dem Bildschirm: myClassAttr = 8 myClassAttr = 5 myClassAttr = 6

Merke: Klassenmethoden und Klassenattribute werden über den Klassennamen und den Punktoperator angesprochen. Es ist auch möglich sie über eine Instanz anzusprechen, dies ist aber weniger gebräuchlich.

3.3.8 Instanzierung, Konstruktoren Das Generieren eines konkreten Objektes einer Klasse wird allgemein als Instanzierung oder instanzieren bezeichnet. Dazu wird der Operator new verwendet. Im Beispiel in Kapitel 3.3.5 haben wir bereits eine Instanz der Klasse Kreis angelegt durch: Kreis meinKreis = new Kreis();

Allgemein formuliert wird folgende Syntax verwendet: ; = new ;

oder = new ;

Instanz_Name ist eine Referenz auf die erzeugte Instanz der Klasse Klassentyp. Über diese Referenz kann man Attribute und Methoden dieser Instanz aufrufen. new legt das neue Objekt an und speichert seine Adresse in der Referenz Instanz_Name. Wird diese Instanz nicht mehr gebraucht, so wird sie von der Garbage Collection automatisch entsorgt. Bei der Instanzierung wird festgestellt, wie viel Speicher für das neue Objekt bereitgestellt werden muss. Dies ist abhängig von den Attributen, welche in der Klasse definiert sind. Werden zwei Instanzen derselben Klasse angelegt, so wird für beide gleich viel Speicher reserviert. Konstruktoren sind spezielle Methoden, welche bei der Instanzierung durch Objekt zu initialisieren. Konstruktoren haben folgende Eigenschaften: • Sie haben denselben Namen wie die Klasse • Sie haben keinen Rückgabewert (auch nicht void) • Es können ihnen Parameter übergeben werden • Sie können überladen werden.

Version 1.3, 12.03.07

new

aufgerufen werden, um ein

Seite 45

Einführung in die OOP mit Java

Informatik 4

Beispiel: Klasse Kreis mit zwei Konstruktoren und main()-Methode: public class Kreis { private int x_coord; private int y_coord; private int radius; private boolean isVisible; // 1. Konstruktor, ohne Parameter Kreis() { System.out.println("1. Konstruktor ohne Parameter"); x_coord = 1; y_coord = 1; radius = 1; isVisible = false; } // 2. Konstruktor mit Parameter, überladen des 1. Konstruktors Kreis(int x, int y, int r, boolean isVisible) { System.out.println("2. Konstruktor mit Parameter"); x_coord = x; y_coord = y; radius = r; this.isVisible = isVisible; } // Ausgabe aller Attribute public void printKreis() { System.out.println("x_coord = " + x_coord); System.out.println("y_coord = " + y_coord); System.out.println("radius = " + radius); System.out.println("isVisible = " + isVisible); System.out.println(""); } // Start des Hauptprogramms public static void main(String[] args) { System.out.println("Start Programm"); Kreis kreis1 = new Kreis(); kreis1.printKreis(); Kreis kreis2 = new Kreis(3,7,10,true); kreis2.printKreis();

// Instanzierung 1. Kreis

// Instanzierung 2. Kreis

} }

Ausgabe auf dem Bildschirm: Start Programm 1. Konstruktor ohne Parameter x_coord = 1 y_coord = 1 radius = 1 isVisilbe = false 2. Konstruktor mit Parameter x_coord = 3 y_coord = 7 radius = 10 isVisilbe = true

Version 1.3, 12.03.07

Seite 46

Einführung in die OOP mit Java

Informatik 4

In main() wird zuerst eine erste Instanz Kreis angelegt. Da dem new-Operator keine Parameter übergeben werden, wird der 1. Konstruktor aufgerufen. Danach wird die Methode printKreis() der Instanz kreis1 aufgerufen, wodurch die Attribute mit den Default-Werten ausgegeben werden. Anschliessend wird eine zweite Instanz kreis2 generiert. Hier werden dem Operator new Parameter übergeben, wodurch der 2. Konstruktor aufgerufen wird. Die Attribute werden entsprechend initialisiert. Speziell beim 2. Konstruktor ist, dass der Name des vierten Parameters (isVisible) gleich lautet wie ein Attribut. Dies kann sinnvoll sein, damit nicht dauernd neue Namen für Parameter erfunden werden müssen. In diesem Falle muss jedoch bei der Zuweisung das Schlüsselwort this verwendet werden: this.isVisible = isVisible; this ist eine Referenz auf die eigene Instanz. Sie können bei allen Methoden mit Parameterübergabe auf diese Weise die Parameternamen und die Attributnamen gleich benennen.

Wichtig: • Wenn in einer Klasse kein Konstruktor definiert ist, wird vom System automatisch ein DefaultKonstruktor (ohne Parameter) erzeugt. • Wenn in einer Klasse mindestens ein Konstruktor definiert ist, wird vom System kein Default-Konstruktor erzeugt. Wenn sie einen solchen trotzdem benötigen, so müssen sie diesen selbst codieren (siehe obiges Beispiel). Merke: Ein OO-Programm ist schlussendlich eine Ansammlung von Instanzen, welche: • erzeugt und wieder vernichtet werden • miteinander kommunizieren (gegenseitig ihre Methoden aufrufen) Im obigen Beispiel werden von der Klasse Kreis zur Laufzeit zwei Instanzen kreis1 und kreis2 angelegt. Grafisch kann dies wie folgt dargestellt werden: class Kreis

Instanzierung

kreis1 x=1 y=1 r=1

Kreis2 x=3 y=7 r = 10

Abbildung 8: Instanzierung von zwei Objekten der Klasse Kreis

3.3.9 Destruktoren und Garbage Collection Eine Instanz belegt Speicherplatz für ihre Attribute. Kommt diese Instanz an ihr Lebensende wird dies von der Garbage Collection erkannt, die Instanz wird entsorgt und der Speicher wird wieder frei gegeben. Auf diese Weise wird vermieden, dass sich über eine längere Zeitdauer des Programms Speicherlöcher ansammeln und am Schluss kein Speicher mehr für neue Instanzen vorhanden ist. Eine Instanz wird von der Garbage Collection liquidiert sobald keine Referenz mehr auf sie zeigt. Der Programmierer muss sich nicht darum kümmern.

Version 1.3, 12.03.07

Seite 47

Informatik 4

Einführung in die OOP mit Java

Es kann nun aber sein dass bei der Entsorgung von Instanzen noch irgendwelche Aufräumarbeiten durchgeführt werden müssen. Dies ist insbesondere dann der Fall wenn Instanzen irgendwelche Ressourcen belegen (Geräte, Dateien, Sockets usw.). Ein erster Ansatz ist, dass sie beispielsweise eine Methode close() programmieren, welche diese Ressourcen wieder frei gibt. Der Aufruf dieser Methode liegt aber in der Verantwortung des Anwenders! Ein eleganterer Ansatz ist die Verwendung des Destruktors. In Analogie zum Konstruktor wird der Destruktor vor der Zerstörung der Instanz von der Garbage Collection aufgerufen. In Java ist der Destruktor eine Methode namens finalize(). Eigenschaften von Destruktoren: • Sie haben den Namen finalize() • Sie haben den Rückgabewert void • Sie haben keine Parameter • finalize() kann im Gegensatz zu Konstruktoren auch als normale Methode aufgerufen werden. finalize() sollte deshalb robust gegen mehrfache Ausführungen programmiert werden. Beispiel: public class Test { private int count; Test(int count) // Konstruktor { this.count = count; System.out.println("Konstruktor " + count + ". Objekt"); } protected void finalize() // Destruktor { System.out.println("Destruktor " + count + ". Objekt"); } } public class TestDestruktor { public static void print(int i) { Test localTest = new Test(i); } public static void main(String[] args) { System.out.println("Start Programm"); Test myTest; myTest = new Test(1); print(2); System.gc(); System.out.println("Mitten im Programm"); myTest = null; System.gc(); System.out.println("Ende Programm"); }

// 2. Objekt anlegen // 2. Objekt freigeben

// 1. Objekt anlegen // 2. Objekt anlegen // Aufruf Garbage Collection // 1. Objekt freigeben // Aufruf Garbage Collection

}

Ausgabe auf dem Bildschirm: Start Programm Konstruktor 1. Objekt Konstruktor 2. Objekt Destruktor 2. Objekt Mitten im Programm Destruktor 1. Objekt Ende Programm

Version 1.3, 12.03.07

Seite 48

Informatik 4

Einführung in die OOP mit Java

In main() wird zuerst ein lokales Objekt myTest (Test-Objekt Nr. 1) angelegt. Durch Aufruf der Methode print() wird eine zweite Instanz angelegt, welche aber gleich wieder frei gegeben wird, da sie nur lokal in print() vorkommt. Das erste Objekt wird durch löschen der Referenz (myTest = null) freigegeben. Die Garbage Collection wird normalerweise vom System automatisch irgendwann im Hintergrund gestartet. Damit wir in diesem Beispiel besser sehen, wann genau welche Instanzen zerstört werden, wurde hier die Garbage Collection von Hand aufgerufen (System.gc). Dies sollte normalerweise nicht notwendig sein. Vorsicht: Der Aufruf von System.gc() muss die Garbage Collection nicht zwingend starten. Dies hängt von der Implementation der VM (Virtual Machine) ab. Entsprechend wäre die Bildschirmausgabe der Destruktoren aus obigem Beispiel zeitlich erst später. Wichtig: Auch wenn es schon erwähnt wurde: Wenn sie von Java auf C++ umsteigen, müssen sie sich daran gewöhnen, allozierten Speicher von Hand (oft im Destruktor) wieder frei zu geben. In C++ sollten sie zu jeder Klasse einen Destruktor programmieren, in Java nur falls sie andere Ressourcen als Speicher wieder frei geben müssen.

3.3.10 Übergabe und Rückgabe von Objekten Auch Objekte können den Methoden als Parameter übergeben oder von diesen zurückgegeben werden. Da immer eine Referenz auf ein Objekt verweist wird jeweils nur die entsprechende Referenz übergeben. public class Bruch { // Attribute int zähler; int nenner; // Konstruktor Bruch(int zähler, int nenner) { if(nenner != 0) // prüfe ob Division durch Null { this.zähler = zähler; this.nenner = nenner; } else { System.out.println("Division durch Null"); this.zähler = 0; this.nenner = 1; } } // Multiplikation public Bruch mul(Bruch para) { Bruch res = new Bruch(0, 1); res.zähler = zähler * para.zähler; res.nenner = nenner * para.nenner; return(res); }

// neue Instanz für Resultat // Multiplikation Zähler // Multiplikation Nenner

public void print() { System.out.println(zähler + "/" + nenner); } public static void main(String[] args) { int z1, n1; int z2, n2;

Version 1.3, 12.03.07

Seite 49

Einführung in die OOP mit Java

Informatik 4

if(args.length == 4) { z1 = Integer.parseInt(args[0]); n1 = Integer.parseInt(args[1]); z2 = Integer.parseInt(args[2]); n2 = Integer.parseInt(args[3]);

// Einlsen der Parameter

Bruch b1 = new Bruch(z1, n1); b1.print(); Bruch b2 = new Bruch(z2, n2); b2.print();

// Bruch 1 und 2 anlegen

Bruch b3 = b1.mul(b2); b3.print();

// Multiplikation

} else { System.out.println("Geben sie bitte 4 Parameter ein:"); System.out.println("Zähler1, Nenner1, Zähler2, Nenner2"); } } }

Aufruf des Programms mit folgenden Parametern: "3"

"5"

"7"

"8"

Ausgabe auf dem Bildschirm: 3/5 7/8 21/40

Interessant ist insbesondere die Methode mul(). Sie erhält als Parameter eine Referenz auf ein Bruch-Objekt. Die Multiplikation wird durchgeführt mit den Werten der eigenen Instanz multipliziert mit diesem Parameter. Innerhalb der Methode wird zuerst eine Instanz der Klasse Bruch für das Resultat angelegt. Dieses Resultat wird am Schluss als Referenz zurückgegeben. Die Methode mul() wird aus dem Hauptprogramm in der Zeile Bruch b3 = b1.mul(b2);

// Multiplikation

aufgerufen. Es findet somit eine Multiplikation der Instanzen b1 mit b2 statt, das Ergebnis wird in b3 abgelegt. Beachten sie, dass b3 in der Methode mul() instanziert wird.

3.3.11 Innere Klassen In Java können Klassen innerhalb von anderen Klassen definiert werden, man nennt diese "innere Klassen". Innere Klassen haben folgende Eigenschaften: • Sie können auf Attribute und Methoden der äusseren Klasse zugreifen (sogar private Elemente). • Innerhalb der umfassenden Klasse kann eine Instanz der inneren Klasse angelegt und auf deren Elemente zugegriffen werden. • Sie sind ausserhalb des sie definierenden Blocks nur über einen qualifizierenden Namen sichtbar. Dieser lautet UmfassendeKlasse.InnereKlasse. Beispiel: public class UmfassendeKlasse { private int umfassendeVar = 10; // Innere Klasse public class InnereKlasse { public int innerVar = 10; public void test(int i) {

Version 1.3, 12.03.07

Seite 50

Einführung in die OOP mit Java

Informatik 4

innerVar = umfassendeVar * 2; System.out.println("test() InnereKlasse " + i); } } public void test(int i) { System.out.println("test() UmfassendeKlasse " + i); InnereKlasse innen = new InnereKlasse(); innen.test(i); } } public class TestInnereKlasse { public static void main(String[] args) { // Aufruf der umfassenden Klasse UmfassendeKlasse aussen = new UmfassendeKlasse(); aussen.test(1); // Aufruf der inneren Klasse UmfassendeKlasse.InnereKlasse innen = aussen.new InnereKlasse(); innen.test(2); } }

Ausgabe auf dem Bildschirm: test() UmfassendeKlasse 1 test() InnereKlasse 1 test() InnereKlasse 2

Im Hauptprogramm wird als Erstes eine Instanz der Klasse UmfassendeKlasse angelegt und von dieser die Methode test() aufgerufen. In der Testmethdode der umfassenden Klasse wird eine Instanz der inneren Klasse angelegt und von dieser ebenfalls die Testmethode aufgerufen. Anschliessend wird im Hauptprogramm direkt eine Instanz der inneren Klasse angelegt. Beachten sie insbesondere den qualifizierenden Namen sowie den newOperator. Üblicherweise wird nicht von Aussen auf innere Klassen zugegriffen.

3.4 Vererbung 3.4.1 Grundlagen Ohne Vererbung wäre die OOP wie eine italienische Pasta ohne ein Glas Rotwein. Bei der Vererbung geht es darum, gemeinsamen Code verschiedener Klassen in einer sogenannten Oberklasse (auch Elternklasse genannt) zusammenzufassen. Die Oberklasse vererbt dann ihre Eigenschaften (Attribute und Methoden) an die Unterklassen. In den Unterklassen werden anschliessend neue Eigenschaften hinzugefügt. Eine Unterklasse kann eine geerbte Methode überschreiben, d.h. neu definieren. Vererbung wird oft auch als Ableitung oder Spezialisierung bezeichnet. Das Vererbungskonzept hat folgende Vorteile: • Durch die Vererbung wird viel Redundanz von Code vermieden. Gemeinsamer Code wird in einer Oberklasse zusammengefasst. Der Programmieraufwand wird dadurch reduziert.

Version 1.3, 12.03.07

Seite 51

Informatik 4

Einführung in die OOP mit Java

Klasse_1

Klasse_2

[] gemeinsam

[] gemeinsam

[] speziell

[] speziell

Oberklasse [] gemeinsam

Klasse_1u [] speziell

Klasse_2u [] speziell

Abbildung 9: Zusammenfassung von gemeinsamem Code in einer Oberklasse • • • • •

Wird in einer Oberklasse neuer Code hinzugefügt (etwa eine neue Methode), so ist dieser Code für alle Unterklassen verfügbar. Werden in einer Oberklasse Änderungen am Code durchgeführt, so sind diese auch für alle Unterklassen gültig. Dies kann den Testaufwand erheblich reduzieren. Bei einer guten Klassenhierarchie können später einfach neue Unterklassen hinzugefügt werden. So kann die Software erweitert werden, ohne dass man bestehenden Code ändern muss. Die Hierarchie der Klassen entspricht den realen Anwendungen (Grafische Objekte, Personen, Fahrzeuge, Tierwelt, Pflanzenwelt usw.). Die Software wird dadurch überschaubar und leichter verständlich. Der Code ist besser wiederverwendbar. Sie können auf bereits bestehende Klassen zurückgreifen und diese durch vererben modifizieren.

Eigenschaften der Vererbung mit Java: • In Java kann nur von einer Oberklasse geerbt werden, Mehrfachvererbung ist nicht möglich. • Wird keine Oberklasse angegeben, so wird automatisch von der Java-Klasse Object geerbt. Dadurch erben alle Klassen die Eigenschaften der Klasse Object, weil die oberste Klasse der Hierarchie von Object erbt und diese Eigenschaften an alle Unterklassen weitergibt. • In der Unterklasse wird durch das Schlüsselwort extends die Oberklasse angegeben, von welcher geerbt wird. • Konstruktoren der Oberklasse werden mit dem Schlüsselwort super aufgerufen. super wird ebenfalls verwendet, wenn in einer Methode der Unterklasse eine Methode der Oberklasse mit gleichem Namen aufgerufen werden soll (überschriebene Methode). Wichtig: Das Design einer Klassenhierarchie, d.h. das Suchen von gemeinsamem Code verschiedener Klassen, ist eine höchst anspruchsvolle und kreative Arbeit. Ein gutes Klassendiagramm in einem komplexen Projekt erfordert viel Zeit und Erfahrung, zahlt sich aber durch ein stabiles Grundgerüst der Software und durch eine gute Wartbarkeit aus.

Version 1.3, 12.03.07

Seite 52

Informatik 4

Einführung in die OOP mit Java

Im Klassendiagramm wird die Vererbung wie folgt dargestellt: Oberklasse

Unterklasse

Abbildung 10: Klassendiagramm Vererbung Beispiel einer Oberklasse Parent und einer Unterklasse Child: Die Klasse Parent soll drei Attribute p1, p2 und p3 haben, diese haben die Modifier public, protected und private. Ferner gibt es einen Konstruktor, und die Methoden do() und print(). Die Child-Klasse erbt alle diese Eigenschaften. Sie hat zusätzlich ein Attribut c1, einen Konstruktor Child() und die Methoden doChild() und print(). Die Methode print() der Klasse Child überschreibt die Methode print() der Parent-Klasse. Das Klassendiagramm sieht wie folgte aus:

Parent int p1 int p2 int p3 Parent() doParent() print()

Child int c1 Child() doChild() print()

Abbildung 11: Klassendiagramm Beispiel Parent und Child public class Parent { // Attribute public int p1; protected int p2; private int p3; // Konstruktor Parent(int p1, int p2, int p3) { System.out.println("Konstruktor Klasse Parent"); this.p1 = p1; this.p2 = p2; this.p3 = p3; } protected void doParent() { System.out.println("doParent() Klasse Parent"); }

Version 1.3, 12.03.07

Seite 53

Informatik 4

Einführung in die OOP mit Java

public void print() { System.out.println("print() Klasse Parent"); System.out.println("p1: " + p1 + " p2: " + p2 + " }

p3: " + p3);

} public class Child extends Parent { // Attribute protected int c1; // Konstruktor Child(int c1, int p1, int p2, int p3) { super(p1, p2, p3); // Konstruktor der Oberklassse aufrufen System.out.println("Konstruktor Klasse Child"); this.c1 = c1; } protected void doChild() { System.out.println("doChild() Klasse Child"); doParent(); // Methode doParent() der Oberklasse } public void print() // überschreibe Parent-Methode { System.out.println("print() Klasse Child"); super.print(); // print() der Oberklasse aufrufen System.out.println("c1: " + c1); } public static void main(String[] args) { Child child = new Child(1, 2, 3, 4); child.doChild(); child.print(); child.p1 = 0; child.p2 = 0; //child.p3 = 0; }

//Child instanzieren

// ist ok weil p1 public // ist ok weil p2 protected // Fehler weil p3 private

}

Ausgabe auf dem Bildschirm: Konstruktor Klasse Parent Konstruktor Klasse Child doChild() Klasse Child doParent() Klasse Parent print() Klasse Child print() Klasse Parent p1: 2 p2: 3 p3: 4 c1: 1

Im Hauptprogramm main() wird zuerst eine Instanz der Klasse Child angelegt. Dadurch wird der Konstruktor von Child aufgerufen. Im Child-Konstruktor muss zuerst der Konstruktor der Oberklasse mit super() aufgerufen werden. Dies ist notwendig, damit die Oberklasse initialisiert wird, bevor die Unterklasse die Eigenschaften der Oberklasse verwenden kann. Allgemein sollten sie in einem Konstruktor immer zuerst den Konstruktor der Oberklasse aufrufen bevor sie den eigenen Code ausführen. Aus main() wird anschliessend die Methode doChild() aufgerufen. Innerhalb der Methode doChild() wird die Methode doParent() der Oberklasse aufgerufen. Dieser Aufruf ist korrekt, weil innerhalb der Child-Klasse keine Methode namens doParent() existiert. Sie können aus einer Unterklasse sämtliche Attribute und Methoden der Oberklasse aufrufen, solange die Aufrufe eindeutig sind. Die Methode print() der Klasse Child überschreibt die Methode print() der Klasse Parent. D.h. die Methode print() der Oberklasse wäre für die Unterklasse nicht korrekt und muss deshalb angepasst oder Version 1.3, 12.03.07

Seite 54

Informatik 4

Einführung in die OOP mit Java

überschrieben werden. Oft ist es jedoch so, dass der Code der Oberklasse dennoch verwendet werden kann, er muss nur ergänzt werden wie hier durch die Ausgabe des Attributs c1. Deshalb wird in der Methode print() der Child-Klasse die print()-Methode der Parent-Klasse aufgerufen. Da der Aufruf print() nicht eindeutig ist (es wäre damit die Methode print der Unterklasse gemeint), muss mit super.print() die Methode der Oberklasse aufgerufen werden. Diese gibt zuerst alle Attribute der Oberklasse aus, bevor in der Methode print() der Unterklasse auch noch das Attribut c1 ausgegeben wird. In main() ist der Zugriff auf child.p1 korrekt, weil p1 als public deklariert wurde. Der Zugriff auf child.p2 ist ebenfalls zulässig, weil p2 als protected deklariert wurde. Auf child.p3 kann jedoch nicht zugegriffen werden, weil p3 als private deklariert wurde. Auf p3 kann nur innerhalb der Klasse Parent zugegriffen werden.

3.4.2 Reihenfolge der Konstruktoren und Destruktoren Die Reihenfolge der Konstruktoren wird durch folgende Regeln definiert: 1) Als erste Instruktion in einem Konstruktor muss der Konstruktor der Oberklasse mit dem Schlüsselwort super aufgerufen werden. Ist dies nicht der Fall, so wird der Default-Konstruktor aufgerufen. Dieser muss dann zwingend existieren, sonst gibt es einen Compiler-Fehler. 2) Als Konsequenz daraus gilt: Es kann nur ein Konstruktor der Oberklasse aufgerufen werden. Das heisst, dass immer zuerst der Konstruktor der Oberklasse und erst danach der Konstruktor der Unterklasse ausgeführt wird. Bei den Destruktoren ist es umgekehrt: Hier wird zuerst der Destruktor der Unterklasse und erst danach der Destruktor der Oberklasse ausgeführt. Sie müssen jedoch die Methode finalize() der Oberklasse auch wieder selber aufrufen. Beispiel: public class Parent { // Attribute public int p1; protected int p2; private int p3; // Konstruktor Parent(int p1, int p2, int p3) { System.out.println("Konstruktor Klasse Parent"); this.p1 = p1; this.p2 = p2; this.p3 = p3; } // Destruktor protected void finalize() { System.out.println("Destruktor Klasse Parent"); } public void print() { System.out.println("print() Klasse Parent"); } } public class Child extends Parent { // Attribute protected int c1;

Version 1.3, 12.03.07

Seite 55

Informatik 4

Einführung in die OOP mit Java

// Konstruktor Child(int c1, int p1, int p2, int p3) { super(p1, p2, p3); // Konstruktor der Oberklassse aufrufen System.out.println("Konstruktor Klasse Child"); this.c1 = c1; } // Destruktor protected void finalize() { System.out.println("Destruktor Klasse Child"); super.finalize(); // Destruktor der Oberklasse aufrufen } public void print() // überschreibe Parent-Methode { System.out.println("print() Klasse Child"); super.print(); // print() der Oberklasse aufrufen } } public class TestReihenfolge { public static void main(String[] args) { Child child = new Child(1, 2, 3, 4); child.print(); child = null; System.gc(); } }

// Child instanzieren // Child-Objekt freigeben // Aufruf Garbage Collection

Ausgabe auf dem Bildschirm: Konstruktor Klasse Parent Konstruktor Klasse Child print() Klasse Child print() Klasse Parent Destruktor Klasse Child Destruktor Klasse Parent

Im Hauptprogramm wird eine Instanz der Klasse Child angelegt. Dadurch wird zuerst der Code des Konstruktors der Oberklasse und anschliessend derjenige der Unterklasse ausgeführt. mit child = null wird die Instanz zum Löschen freigegeben und danach durch die Garbage Collection entsorgt: Zuerst wird der Code im Destruktor der Unterklasse und anschliessend derjenige in der Oberklasse ausgeführt.

3.4.3 abstrakte Klassen und Methoden Wenn wir gemeinsame Eigenschaften verschiedener Klassen in einer Oberklasse zusammenfassen kann es sein, dass wir künstliche Oberklasse erzeugen. Diese Oberklassen nennt man abstrakt. So gibt es beispielsweise kein konkretes geometrisches Objekt, es gibt nur Kreise, Rechtecke usw. Eine abstrakte Methode ist eine Methode bei der nur die Signatur deklariert wird, die Implementation aber noch fehlt. Eigenschaften von abstrakten Klassen: • Jede abstrakte Klasse enthält mindestens eine abstrakte Methode. • Von einer abstrakten Klasse kann man keine Instanzen erzeugen. • Die abstrakten Methoden müssen in der abgeleiteten Klasse implementiert werden. • Eine Unterklasse kann nur dann instanziert werden, wenn sie alle abstrakten Methoden der Oberklasse implementiert. Wenn dies nicht der Fall ist bleibt auch sie eine abstrakte Klasse. • Abstrakte Klassen definieren eine Schnittstelle zu allen Unterklassen

Version 1.3, 12.03.07

Seite 56

Einführung in die OOP mit Java

Informatik 4

In Java wird zur Bildung von abstrakten Klassen und Methoden das Schlüsselwort abstract verwendet. Syntax: public abstract class Oberklasse { public abstract void method(); }

// abstrakte Methode

public class Unterklasse extends Oberklasse { public void method() // Implementation in der Unterklasse { // Implementation } }

Wir können nun unser Beispiel der geometrischen Objekte erweitern, indem wir eine abstrakte Klasse Geometrisches Objekt einführen. Dieses soll eine Methode area() zur Berechnung der Fläche haben. Diese Methode ist ebenfalls abstrakt, da es hier nicht möglich ist, eine Fläche zu berechnen, weil die geometrische Form gar nicht bekannt ist. Die Methode area() wird dann in den Unterklassen implementiert. Durch die Deklaration der Methode area() erhalten wir jedoch eine gemeinsame Schnittstelle für alle geometrischen Objekte. Darauf werden wir im Kapitel 3.4.4 Polymorphismus nochmals zurückkommen. Beispiel: abstract GeometrischesObjekt x_coord y_coord isVisible GeometrischesObjekt() setCoord() draw() move() abstract area()

Kreis radius Kreis() area() getRadius()

Rechteck width height Rechteck() area() getWidth() getHeight()

Abbildung 12: Klassendiagramm geometrische Objekte public abstract class GeometrischesObjekt { // Attribute protected double x_coord; protected double y_coord; protected boolean isVisible; // Konstruktor GeometrischesObjekt(double x, double y, boolean isVisible) { x_coord = x; y_coord = y; this.isVisible = isVisible; }

Version 1.3, 12.03.07

Seite 57

Informatik 4

Einführung in die OOP mit Java

public void setCoord(double x, double y) { x_coord = x; y_coord = y; } public void draw() { // do something } public void move(double x, double y) { x_coord += x; y_coord += y; } public abstract double area();

// Abstrakte Methode

} public class Kreis extends GeometrischesObjekt { // Attribute protected double radius; protected static final double PI = 3.1415; // Konstruktor Kreis(double x, double y, double r, boolean isVisible) { super(x, y, isVisible); radius = r; } public double area() {return(radius * radius * PI);} // Implementation von area() public double getRadius() {return(radius);} } public class Rechteck extends GeometrischesObjekt { // Attribute protected double width; protected double height; // Konstruktor Rechteck(double x, double y, double width, double height, boolean isVisible) { super(x, y, isVisible); this.width = width; this.height = height; } public double area() {return(width * height);} public double getWidth() {return(width);} public double getHeight() {return(height);}

// Implementation von area()

} public class TestAbstract { public static void main(String[] args) { // Objekte instanzieren Version 1.3, 12.03.07

Seite 58

Einführung in die OOP mit Java

Informatik 4

Rechteck rechteck = new Rechteck(1,3,10,5,true); Kreis kreis = new Kreis(3,7,5,true); // Fläche ausgeben System.out.println("Fläche Rechteck: " + rechteck.area()); System.out.println("Fläche Kreis: " + kreis.area()); } }

Ausgabe auf dem Bildschirm: Fläche Rechteck: 50.0 Fläche Kreis: 78.53750000000001

Im obigen Beispiel sind die Klasse GeometrischesObjekt und deren Methode area() abstract. Die Klassen Kreis und Rechteck sind von dieser Oberklasse abgeleitet und implementieren jeweils die Methode area() entsprechend ihrer Fläche. In der main()-Methode wird jeweils eine Instanz Kreis und Rechteck angelegt und von jeder die Methode area() aufgerufen.

3.4.4 Polymorphismus Im vorangehenden Kapitel haben wir gesehen, wie in einer Unterklasse eine abstrakte Methode der Oberklasse definiert werden kann. Sie können jedoch immer in einer Unterklasse Methoden der Oberklasse, die sie geerbt haben, überschreiben. Dies wird auch Overriding genannt. Dadurch kann jede Klasse eine Methode für ihren Zweck spezifisch anpassen, auch wenn diese in der Oberklasse schon definiert wurde. Mit Hilfe des Polymorphismus wird zur Laufzeit jeweils abhängig vom Objekttyp die korrekte Methode aufgerufen. Polymorphismus (auch dynamisches Binden oder "late binding" genannt) hat folgende Vorteile: • Die korrekte Auswahl der Methode in Abhängigkeit des aktuellen Objekttyps ist durch das Laufzeitsystem gegeben. Datentypunterscheidungen im Code zur Auswahl der richtigen Methode sind dadurch nicht notwendig. • Änderungen am bestehenden Code werden dadurch vereinfacht. Sie können von einer bestehenden Klasse erben und gezielt einzelne Methoden ändern, indem sie diese überschreiben. Polymorphismus hat aber auch Nachteile: • Es werden mehr Rechner-Resourcen gebraucht. Insbesondere wird der Code etwas langsamer, da zur Laufzeit zuerst die korrekte Methode bestimmt werden muss. • Das Debuggen der aufgerufenen Methoden wird schwieriger. Im Beispiel in Kapitel 3.4.3 haben wir im Hauptprogramm zwei Instanzen vom Typ Kreis und Rechteck angelegt und von diesen jeweils die Methoden area() aufgerufen. Dieser Code ist noch nicht sehr elegant. Dank dem Polymorphismus können wir die beiden Instanzen Kreis und Rechteck in einem Container für geometrische Objekte ablegen und die Methode area() für diese geometrischen Objekte aufrufen. Das Laufzeitsystem wird uns die korrekten area() Methoden aufrufen, d.h. für das Kreis-Objekt die Methode der Klasse Kreis und für das Rechteck-Objekt die Methode der Klasse Rechteck. Beispiel: public class TestPolymorphismus { public static void main(String[] args) { GeometrischesObjekt geo []; geo = new GeometrischesObjekt[2]; geo[0] = new Rechteck (1,3,10,5,true); geo[1] = new Kreis(3,7,5,true); for (int i = 0; i < geo.length; i++) System.out.println("Fläche : " + geo[i].area()); } }

Version 1.3, 12.03.07

Seite 59

Einführung in die OOP mit Java

Informatik 4

Ausgabe auf dem Bildschirm: Fläche : 50.0 Fläche : 78.53750000000001

Im obigen Beispiel wird zuerst ein Array für zwei geometrische Objekte angelegt. Anschliessend werden ein Rechteck und ein Kreis instanziert und im Array abgelegt. Am Schluss wird in einer Schleife für jedes geometrische Objekt die Methode area() aufgerufen. Die korrekten Methoden werden zur Laufzeit bestimmt und ausgeführt, d.h. zuerst die Methode für die Rechteck-Instanz, anschliessend die Methode der Kreis-Instanz. Dieser Code ist sehr wartungsfreundlich: Sie können später neue Klassen für geometrische Objekte definieren (beispielsweise Dreiecke) und deren Instanzen ebenfalls im Array ablegen.

3.4.5 Casting von Klassen Nehmen wir an, dass die Klassen Kreis und Rechteck viele gemeinsame Methoden haben, daneben aber auch Methoden, die nur in den jeweiligen Unterklassen vorkommen. Beispielsweise könnte die Klasse Rechteck eine Methode rotate() haben, welche für die Klasse Kreis nicht definiert ist. Wenn wir wie im Beispiel in Kapitel 3.4.4 alle geometrischen Objekte in einem Array speichern, so ist der folgende Code für ein Rechteck nicht korrekt und führt zu einem Compiler-Fehler: geo[0].rotate();

Dieser Code ist falsch, weil für geometrische Objekte die Methode rotate() nicht definiert ist. Wir müssen deshalb zuerst aus einem geometrischen Objekt wieder ein Rechteck machen, um die Methode aufzurufen. Dies erfolgt mit einem Cast: ((Rechteck)geo[0]).rotate();

Dieser Code ist insofern gefährlich, als dass er auch auf Kreise angewendet werden kann. Für Kreise existiert aber keine Methode rotate() und es wird zur Laufzeit eine ClassCastException geworfen. Es ist deshalb besser, wenn im Code zur Laufzeit geprüft wird, ob es sich um eine Instanz des Typs Rechteck handelt, bevor rotate() aufgerufen wird. Dies kann wie folgt gemacht werden: if(geo[0] instanceof Rechteck) ((Rechteck)geo[0]).rotate();

Beispiel: Der Code aus Kapitel 3.4.4 soll so erweitert werden, dass a) die Klasse Rechteck eine Methode rotate() erhält und b) bei der Berechung der Flächen im Hauptprogramm ausgegeben wird, um welchen Typ von geometrischem Objekt es sich handelt. public class Rechteck extends GeometrischesObjekt { // Attribute protected double width; protected double height; // Konstruktor Rechteck(double x, double y, double width, double height, boolean isVisible) { super(x, y, isVisible); this.width = width; this.height = height; } public public public public

double area() {return(width * height);} double getWidth() {return(width);} double getHeight() {return(height);} void rotate(int angle) {System.out.println("rotate");}

}

Version 1.3, 12.03.07

Seite 60

Einführung in die OOP mit Java

Informatik 4

public class TestClassCast { public static void main(String[] args) { GeometrischesObjekt geo []; geo = new GeometrischesObjekt[2]; geo[0] = new Rechteck (1,3,10,5,true); geo[1] = new Kreis(3,7,5,true); //geo[0].rotate(); ((Rechteck)geo[0]).rotate(30); //((Rechteck)geo[1]).rotate(30);

// method rotate() is // ist ok // ClassCastException

undefined

for (int i = 0; i < geo.length; i++) { if(geo[i] instanceof Rechteck) System.out.println("Fläche Rechteck: " + geo[i].area()); else if(geo[i] instanceof Kreis) System.out.println("Fläche Kreis: " + geo[i].area()); } } }

Ausgabe auf dem Bildschirm: rotate Fläche Rechteck: 50.0 Fläche Kreis: 78.53750000000001

Im Hauptprogramm wird zuerst versucht, ein geometrisches Objekt zu drehen. Dies führt zu einem Compilerfehler, weil es keine Methode rotate() für geometrische Objekte gibt. Anschliessend wird ein Cast des ersten geometrischen Objektes auf den Typ Rechteck gemacht. Dies ist korrekt, weil das erste geometrische Objekt im Array ein Rechteck ist. Danach kann die Methode rotate() aufgerufen werden. Anschliessend wird versucht aus dem zweiten geometrischen Objekt im Array (das wäre ein Kreis) ein Rechteck zu machen. Entsprechend kann hier die Methode rotate() aufgerufen werden. Das ist syntaktisch korrekt und führt zu keinem Compiler-Fehler. Zur Laufzeit wird jedoch eine ClassCastException geworfen. In der for-Schleife wird überprüft um welche Art von Objekt es sich handelt. Dies ist eine sichere Variante um klassenspezifische Methoden aufzurufen. Merke: Grundsätzlich sind Casts eine nicht besonders elegante Art von Code, manchmal kann man sie aber nicht vermeiden. Prüfen sie auf jeden Fall ihr Design und studieren sie Varianten, bevor sie sich für einen Cast entscheiden. Im obigen Code könnte der Cast vermieden werden, indem die Klasse GeometrischesObjekt eine Methode rotate() erhält, welche nichts macht (aber dennoch keine abstrakte Methode ist). In der Klasse Rechteck kann diese dann überschrieben werden. Der Text zur Ausgabe der Fläche könnte in die Unterklassen verlagert werden.

3.5 Beziehungen zwischen Klassen Klassen können unterschiedliche Beziehungen zueinander haben. Diese werden im Klassendiagramm dargestellt. Die wichtigsten Beziehungen sind:

Version 1.3, 12.03.07

Seite 61

Einführung in die OOP mit Java

Informatik 4

Beziehung Vererbung "Ist ein"

Beschreibung UML-Klassendiagramm Die Beziehung entsteht durch Vererbung. Man sagt, B A ist ein A wenn B von A abgeleitet wird. Beispiele: Ein Kreis ist ein geometrisches Objekt. B

Aggregation "hat ein"

Ein Objekt der Klasse B ist Teil eines Objektes der Klasse A, existiert aber unabhängig vom Objekt der Klasse A. Beispiel: Ein Student hat einen Taschenrechner.

Komposition "hat ein"

Ein Objekt der Klasse B ist Teil eines Objektes der Klasse A und existiert nur solange das Objekt der Klasse A existiert. Beispiel: Ein Baum hat eine Wurzel und einen Stamm.

Assoziation "benutz ein"

Eine Assoziation ist eine lockere, inhaltliche Beziehung zweier Klassen. Beispiel: Die Klasse GrafischesObjekt kann die Methode print() der Klasse Printer benutzen, um die Grafik auszudrucken.

A

B

A

B

A

B

Abbildung 13: Beziehungen zwischen Klassen Merke: Beziehungen zwischen Klassen können etwas irreführend sein! Ein Kreis ist kein Punkt mit zusätzlichen Eigenschaften, sonder ein Kreis hat einen Punkt als Zentrum. Beispiel: Eine Klasse A soll ein Attribut der Klasse C haben. Die Klasse C benutzt die Klasse D um sich auszudrucken. Eine Klasse B erbt die Eigenschaften der Klasse A.

A

C

D

Method_A()

Method_C()

Method_D()

C c

B

E

Method_B()

Method_E()

Abbildung 14: Klassendiagramm Beziehungen zwischen Klassen public class A { protected C c; A(C c) // Konstruktor { System.out.println("Konstruktor Klasse A"); this.c = c; // Aggregation }

(7)

protected void Method_A()

Version 1.3, 12.03.07

Seite 62

Einführung in die OOP mit Java

Informatik 4

{ System.out.println("Method_A aufgerufen"); c.Method_C(); // Methode der Oberklasse } } public class B extends A { B(C c) // Konstruktor { super(c); // Konstruktor der Elternklasse System.out.println("Konstruktor Klasse B"); }

(6)

public void Method_B() { System.out.println("Method_B aufgerufen"); Method_A(); } } public class C { protected E e; C (E e) // Konstruktor { System.out.println("Konstruktor Klasse C"); this.e = e; // Aggregation }

(5)

public void Method_C() { System.out.println("Method_C aufgerufen"); D d = new D(); d.Method_D("Hallo"); } } public class D { D() // Konstruktor { System.out.println("Konstruktor Klasse D"); } public void Method_D(String str) { System.out.println("Method_D aufgerufen"); System.out.println(str); } } public class E { protected B b; protected C c; E() // Konstruktor { System.out.println("Konstruktor Klasse E"); c = new C(this); // Komposition b = new B(c); // Komposition }

Version 1.3, 12.03.07

(1) (2)

(3) (4)

Seite 63

Einführung in die OOP mit Java

Informatik 4

public void Method_E() { System.out.println("Method_E aufgerufen"); c.Method_C(); b.Method_B(); } public static void main(String[] args) { E e = new E(); e.Method_E(); } }

Ausgabe auf dem Bildschirm: Konstruktor Klasse E Konstruktor Klasse C Konstruktor Klasse A Konstruktor Klasse B Method_E aufgerufen Method_C aufgerufen Konstruktor Klasse D Method_D aufgerufen Hallo Method_B aufgerufen Method_A aufgerufen Method_C aufgerufen Konstruktor Klasse D Method_D aufgerufen Hallo

Die Klasse E hat je ein Attribut der Klasse B und C [(1) und (2)], welche im Konstruktor angelegt werden (Komposition) [(3) und (4)]: Dem Konstruktor der Klasse C wird eine Referenz auf das eigene Objekt mitgegeben (this), (3), wodurch dieser das Attribut "e" initialisieren kann (Aggregation) (5). Dem Konstruktor der Klasse B wird eine Referenz auf das Objekt "c" mitgegeben (4). Der Konstruktor der Klasse B gibt diese Referenz dem Konstruktor der Klasse A weiter (Elternklasse) (6), wo diese dem Attribut "c" zugewiesen wird (Aggregation) (7).

3.6 Interfaces In Kapitel 3.4 haben wir gesehen wie Klassen Eigenschaften ihrer Oberklassen durch Vererbung übernehmen können. In Java ist jedoch nur das Erben von einer Oberklasse möglich, Mehrfachvererbung wird nicht unterstützt. Beispiel: Definiert sei eine Klasse GeometrischesObjekt sowie eine Klasse Printer, welche die Schnittstelle zum Ausdrucken definiert. Nun ist es nicht möglich, dass eine Klasse Rechteck die Eigenschaften von beiden Klassen erbt.

GeometrischesObjekt

Printer

Rechteck

Abbildung 15: Mehrfachvererbung ist in Java nicht möglich Mit Hilfe der Interfaces (Schnittstellenvererbung) kann das Problem gelöst werden. Ein Interface ist eine besondere Form einer Klasse, die nur die Definition von Methoden und Konstanten enthält, nicht aber ihre

Version 1.3, 12.03.07

Seite 64

Einführung in die OOP mit Java

Informatik 4

Implementierung (ähnlich wie eine abstrakte Klasse, bei der jedoch einzelne Methoden implementiert sein können). Bemerkung für C++ Umsteiger: In C++ sind Mehrfachvererbungen möglich. Dafür kennt C++ die Interfaces nicht. Syntax der Interface-Klasse: public interface Printer { void methode1(); }

Syntax der Klasse, welche das Interface implementiert public class Name implements Interface-Name1, Interface-Name2 { // Implementation des Interfaces: void methode1() { ... } ... }

Interfaces haben folgende Eigenschaften: • Interfaces bestehen aus abstrakten Methoden und Konstanten. Durch diese wird die Schnittstelle definiert. • In einer Klasse, welche ein Interface implementiert, müssen alle Methoden dieses Interfaces implementiert werden. • Eine Klasse die ein Interface implementiert ist dann auch vom Typ dieses Interfaces. • Auch abgeleitete Klassen können Interfaces implementieren. • Interfaces lassen sich vererben. • Eine Klasse kann mehrer Interfaces implementieren, indem nach dem Schlüsselwort implements mehrere Interface-Klassen eingefügt werden, die durch Kommas getrennt werden. UML: Ein Interface wird wie eine Klasse dargestellt. Der Pfeil zwischen Interface und Implementierung wird jedoch gestrichelt gezeichnet:

A

B

Abbildung 16: Klasse B implementiert das Interface A

Beispiel: Das Beispiel aus der Einleitung dieses Kapitels soll umgesetzt werden: Eine Klasse Rechteck erbt die Eigenschaften der Oberklasse GeometrischesObjekt (siehe auch Beispiel Kapitel 3.4.3) und implementiert gleichzeitig das Interface "Drucker".

Version 1.3, 12.03.07

Seite 65

Informatik 4

Einführung in die OOP mit Java

abstract GeometrischesObjekt x_coord y_coord isVisible GeometrischesObjekt() setCoord() draw() move() abstract area()

Rechteck width height Rechteck() area() getWidth() getHeight()

abstract Printer CONST_1 print() feedPaper()

Abbildung 17: Beispiel Interface Im nachfolgenden Code wird die Klasse GeometrischesObjekt nicht abgebildet. Der Code ist identisch mit demjenigen aus Kapitel 3.4.3. // Interface Definition public interface Printer { public static final int CONST_1 = 100; public void print(String str); public void feedPaper(); } public class Rechteck extends GeometrischesObjekt implements Printer { // Attribute protected double width; protected double height; // Konstruktor Rechteck(double x, double y, double width, double height, boolean isVisible) { super(x, y, isVisible); this.width = width; this.height = height; } // Methods public double area() {return(width * height);} public double getWidth() {return(width);} public double getHeight() {return(height);} // Interface implementation public void print(String str) { System.out.println(str); }

Version 1.3, 12.03.07

Seite 66

Einführung in die OOP mit Java

Informatik 4

public void feedPaper() { System.out.println("Pater speed is: " + CONST_1); } } public class TestInterface { public static void main(String[] args) { // Objekte instanzieren Rechteck rechteck = new Rechteck(1,3,10,5,true); // Fläche ausgeben rechteck.print("Fläche Rechteck: " + rechteck.area()); rechteck.feedPaper(); } }

Die Ausgabe erfolgt in diesem Testprogramm nicht auf einem Printer sonder auch wieder auf dem Bildschirm: Fläche Rechteck: 50.0 Pater speed is: 100

Bemerkung 1: Es wäre in diesem Beispiel auch möglich (und sicher auch besser), wenn die Oberklasse GeometrischesObjekt das Interface "Printer" implementieren würde, damit alle von ihr abgeleiteten Klassen eine Methode print() hätten. Bemerkung 2: Interfaces können auch Konstanten (d.h. Membervariablen mit den Attributen static und final) enthalten. Eine Klasse die das Interface implementiert erbt auch all diese Konstanten.

3.7 Packages Ein Package (Paket) ist eine Sammlung verschiedener Klassen mit ähnlichen Aufgaben. Packages werden insbesondere bei grösseren Projekten verwendet, um ein zusätzliches Strukturelement einzuführen. Bei Bedarf können zudem Subpackages gebildet werden. Packages sind aus folgenden Gründen sinnvoll: • Sie schaffen übersichtliche Strukturen durch das Zusammenfassen verschiedener Klassen • Sie ermöglichen die Identifikation von Klassen in sehr grossen Projekten • Sie steuern die Sichtbarkeit von Klassen, Methoden und Attributen in einem Projekt Klassen und Packages können wie folgt angesprochen werden: packagename.classname

oder packagename.subpackagename.classname

Wichtig: Packagename und Subpackagename entsprechen immer den Verzeichnisnamen, in welchen die einzelnen Klassendateien abgelegt sind. So befindet sich das Package java.awt.image im Verzeichnis java\awt\image. Die Suche nach den Klassendateien erfolgt relativ zum systemspezifischen Installationsverzeichnis (z.B. c:\java.1.4.2\bin), welches in der Umgebungsvariablen CLASSPATH abgelegt ist.

3.7.1 Verwendung und Import Bevor sie eine Klasse in ihrem Code verwenden können müssen sie angeben, aus welchem Package sie stammt. Dies können sie auf folgende Arten tun: Version 1.3, 12.03.07

Seite 67

Informatik 4

Einführung in die OOP mit Java

1) Angabe des vollständigen Namens inklusive Package und Subpackage. 2) Einbindung der Klasse am Anfang des Codes mit import. 3) Einbindung des ganzen Packages am Anfang des Codes, ebenfalls mit import.

3.7.1.1 Angabe des vollständigen Namens Wenn sie eine Klasse verwenden wollen, geben sie an, in welchem Package (und allenfalls in welchem Subpackage) sie sich befindet. Package.Class variable = new Package.Class();

oder Package.Subpackage.Class variable = new Package.Subpackage.Class();

Beispiel: public class Datum01 { public static void main(String[] args) { java.util.GregorianCalendar date = new java.util.GregorianCalendar(); System.out.println(date.getTime()); } }

Ausgabe auf dem Bildschirm: Mon Feb 23 09:08:46 CET 2004

3.7.1.2 Einbindung der Klasse am Anfang des Codes Wenn sie eine Klasse verwenden wollen importieren sie diese am Anfang der Datei mit import. Beispiel: // Import Klasse aus Package import java.util.GregorianCalendar; public class Datum02 { public static void main(String[] args) { GregorianCalendar date = new GregorianCalendar(); System.out.println(date.getTime()); } }

3.7.1.3 Einbindung des Package am Anfang des Codes Wenn sie eine Klasse verwenden wollen importieren sie am Anfang der Datei das Package, in welchem sich diese Klasse befindet. Diese Variante erspart ihnen etwas Schreibarbeit. Sie kostet auch keinen Speicherplatz, da die Klasse erst beim Programmstart im Package gesucht wird. Beispiel: // Import Package import java.util.*; public class Datum03 { public static void main(String[] args) { GregorianCalendar date = new GregorianCalendar(); System.out.println(date.getTime()); } }

Version 1.3, 12.03.07

Seite 68

Einführung in die OOP mit Java

Informatik 4

3.7.2 Bereits definierte Packages Zum Lieferumfang des J2SDK gehören sehr viele Klassenbibliotheken, welche auf mehr als 20 Packages aufgeteilt sind. Die wichtigsten sind: java.applet java.awt java.io java.lang java.net java.util

für die Implementation von Java-Applets für grafische Benutzeroberflächen für die Ein- und Ausgabe auf Bildschirm und Dateien allgemeine Sprachbestandteile, wird implizit von jeder Klasse importiert für die Netzwerkunterstützung für die Verwendung verschiedener Datenstrukturen (Kalender, Hashtables ...)

3.7.3 Erstellen eigener Packages Um eigene Packages zu erstellen ist wie folgt vorzugehen 1) wählen sie einen aussagekräftigen Namen für das Package (Name beginnt mit Kleinbuchstaben) 2) erstellen sie die entsprechende Verzeichnisstruktur 3) in jedem File muss am Anfang der Name des Packages angegeben werden (Schlüsselwort package), in welchem sich die im File definierte Klasse befindet. Beispiel: Es soll eine Klasse Test_A im Package paket1 definiert werden. Eine weitere Klasse Test_B soll im Subpackage paket1_unterpaket1 angelegt werden. Die Applikation liegt im Hauptverzeichnis im File PackageTest.java. Die Verzeichnisstruktur sieht wie folgt aus: PackageTest.java

paket1

Test_A.java

paket1_unterpaket1

Test_B.java

Datei PackageTest.java: // Import Package und Subpackage import paket1.*; import paket1.unterpaket1.*; public class PackageTest { public static void main(String[] args) { // Aufruf Objekt a Klasse Test_A Test_A a = new Test_A(); a.sayHello(); // Aufruf Objekt b Klasse Test_B, diesmal etwas anders new Test_B().sayHello(); } }

Version 1.3, 12.03.07

Seite 69

Informatik 4

Einführung in die OOP mit Java

Datei Test_A.java: package paket1; public class Test_A { public void sayHello() { System.out.println("My name is Bond, "); } }

Datei Test_B.java: package paket1.unterpaket1; public class Test_B { public void sayHello() { System.out.println("James Bond"); } }

Das Default-Package Das Default-Package wird verwendet wenn für eine Klasse keine Package-Zuordnung definiert wird. Alle Klassen des Default-Packages können ohne explizite Angabe der import-Anweisung verwendet werden.

3.7.4 Sichtbarkeit In Tabelle 16: Zugriffsmodifier wurde die Sichtbarkeit abhängig von den Schlüsselwörtern public, protected und private auch für Packages bereits angegeben. Kurz zusammengefasst kann festgehalten werden, dass es zwei Möglichkeiten gibt damit eine Klasse A eine andere Klasse B einbinden kann: 1) entweder gehören A und B in dasselbe Package 2) oder B wurde als public deklariert Wenn nur Default-Packages verwendet werden liegen beide Klassen im selben Package und die Deklaration erübrigt sich.

public-

3.8 OO Design 3.8.1 UML Bis in die zweite Hälfte der 1990er Jahre gab es verschiedene Ansätze um objektorientierte Softwaresysteme formal zu beschreiben. Am bekanntesten waren die Methoden von Grady Booch, Ivar Jacobson und Jim Rumbaugh, welche voneinander unabhängig verschiedene Ansätze entwickelten. Der Firma Rational Rose gelang es darauf, die drei "amigos" – wie sie oft genannt werden – anzustellen, worauf diese das Beste aus den bestehenden Methoden herausnahmen und gemeinsam die Beschreibungssprache UML (Unified Modelling Language) entwickelten. UML ist eine Methode um beliebige Systeme (nicht nur Softwaresysteme) formal zu beschreiben. Mit Hilfe von UML können verschiedene Sichten (Blickwinkel) auf ein System erzeugt werden. Die für die Softwareentwicklung wichtigsten sind: • Sicht des Anwenders Æ use-case Modell • Sicht auf die statische Programmstruktur Æ Klassendiagramme • Sicht auf das dynamische System Æ Zustandsdiagramme, Sequenzdiagramme Klassendiagramme sind uns bereits in den vorangegangenen Kapiteln begegnet. Eine Zusammenstellung finden sie in Kapitel 3.5. In den nachfolgenden Unterkapiteln wollen wir die Darstellung von Packages und die Sequenzdiagramme etwas genauer betrachten. Version 1.3, 12.03.07

Seite 70

Einführung in die OOP mit Java

Informatik 4

3.8.1.1 Packages Das Package Diagram wird verwendet um die Hierarchie der Packages zu zeigen. Insbesondere können Packages geschachtelt werden (Subpackages). Ein Package Diagram könnte wie folgt aussehen:

Package 2

Package 1 Package 3

KompillationsAbhängigkeit

Package 4 Class 1

Class 2

Vererbung

Package

Class 3

Abbildung 18: Package Diagram

3.8.1.2 Sequenzdiagramme Das Sequenzdiagramm (sequence diagram) erlaubt eine dynamische Sicht auf das System. Dabei wird der zeitliche Verlauf von Interaktionen (Aufruf von Methoden) zwischen ausgewählten Objekten dargestellt. In einem Sequenzdiagramm werden folgende Elemente verwendet: Konstruktion des Objekts

Objekt 1

Objekt 2 Beschreibung kann hier am Rand erfolgen

new()

nachricht() Objekt ist aktiv antwort()

Lebenslinie

delete()

Destruktion des Objekts

Abbildung 19: Elemente des Sequenzdiagramms

Version 1.3, 12.03.07

Seite 71

Einführung in die OOP mit Java

Informatik 4

Folgende Abbildung zeigt ein einfaches Beispiel eines Sequenzdiagramms:

Module

Students

Prof

Room

getModuleInfo() getAddress()

getName()

getInfo()

Abbildung 20: Beispiel Sequenzdiagramm

3.8.1.3 Kollaborationsdiagramme Ein Kollaborationsdiagramm (collaboration diagram) zeigt ebenfalls eine Menge von Interaktionen zwischen ausgewählten Objekten. Im Gegensatz zum Sequenzdiagramm steht hier aber nicht der zeitliche Ablauf sondern die Zusammenarbeit der Objekte im Vordergrund. Der zeitliche Ablauf wird durch die Numerierung der Nachrichten verdeutlicht.

1.1: getAddress()

1: getModuleInfo()

Module

Students

Prof

1.2: getName()

1.3: getInfo()

Room

Abbildung 21: Beispiel Kollaborationsdiagramm

Version 1.3, 12.03.07

Seite 72