Berner Fachhochschule Hochschule für Technik und Informatik, HTI Fachbereich Elektro- und Kommunikationstechnik Labor für Technische Informatik Kurzeinführung in Java Einführung Sprachkonstrukte Einstieg in die OOP Applets © 2004, HTI Burgdorf R. Weber Dateiname: Erstellt am: Autor: Version: Skript_Einf_Java.doc 21. Februar 2007 Roger Weber 1.0 Kurzeinführung in Java 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 auf dem Web .......................................................................................................................... 1 1.2 Ein erstes Beispiel ................................................................................................................................... 1 1.3 Entwicklung von Java-Programmen ....................................................................................................... 1 1.3.1 Texteditor ........................................................................................................................................ 1 1.3.2 Compiler.......................................................................................................................................... 1 1.3.3 Interpreter........................................................................................................................................ 1 1.3.4 Übersicht ......................................................................................................................................... 1 1.4 Applikationen.......................................................................................................................................... 1 1.5 Applets .................................................................................................................................................... 1 1.6 Vor- und Nachteile von Java................................................................................................................... 1 2 Funktionale Programmierung mit Java ............................................................................................................1 2.1 Einführung .............................................................................................................................................. 1 2.2 Lexikalische Elemente ............................................................................................................................ 1 2.2.1 Zeichensatz...................................................................................................................................... 1 2.2.2 Bezeichner....................................................................................................................................... 1 2.2.3 Kommentare.................................................................................................................................... 1 2.2.4 Escape-Sequenzen........................................................................................................................... 1 2.3 Schlüsselwörter ....................................................................................................................................... 1 2.4 Variablen und elementare Datentypen .................................................................................................... 1 2.4.1 Deklaration und Initialisierung........................................................................................................ 1 2.4.2 Elementare Datentypen ................................................................................................................... 1 2.4.3 Datentyp-Umwandlungen ............................................................................................................... 1 2.4.4 Sichtbarkeit ..................................................................................................................................... 1 2.4.5 Globale Variablen ........................................................................................................................... 1 2.5 Zeichenketten .......................................................................................................................................... 1 2.5.1 String............................................................................................................................................... 1 2.5.2 StringBuffer..................................................................................................................................... 1 2.6 Arrays...................................................................................................................................................... 1 2.6.1 Eindimensionale Arrays .................................................................................................................. 1 2.6.2 Mehrdimensionale Arrays ............................................................................................................... 1 2.7 Konstanten .............................................................................................................................................. 1 2.8 Operatoren............................................................................................................................................... 1 2.8.1 Arithmetische Operatoren ............................................................................................................... 1 2.8.2 Vergleichsoperatoren ...................................................................................................................... 1 2.8.3 Logische Operatoren ....................................................................................................................... 1 2.8.4 Bitweise Logische Operatoren ........................................................................................................ 1 2.8.5 Zuweisungsoperatoren .................................................................................................................... 1 2.8.6 Fragezeichen-Operator .................................................................................................................... 1 2.8.7 Cast-Operator .................................................................................................................................. 1 2.8.8 Zusätzliche Operatoren der OOP .................................................................................................... 1 2.9 Kontrollstrukturen ................................................................................................................................... 1 2.9.1 Einführung ...................................................................................................................................... 1 2.9.2 if ...................................................................................................................................................... 1 2.9.3 switch .............................................................................................................................................. 1 2.9.4 while................................................................................................................................................ 1 2.9.5 do while........................................................................................................................................... 1 Version 1.0, 21.02.07 Seite II Kurzeinführung in Java Inhaltsverzeichnis 2.9.6 for .................................................................................................................................................... 1 2.9.7 Beeinflussung von Schleifen mit break und continue ..................................................................... 1 2.10 Methoden ................................................................................................................................................ 1 2.10.1 Name, Parameterliste und Rückgabewert........................................................................................ 1 2.10.2 Signatur, Prototyp, Overloading...................................................................................................... 1 2.10.3 Rekursion ........................................................................................................................................ 1 2.11 main()...................................................................................................................................................... 1 3 OOP mit Java ................................................................................................. Error! Bookmark not defined. 4 Applets .............................................................................................................................................................1 Version 1.0, 21.02.07 Seite III Kurzeinführung in Java 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 Programmieren in Java, Fritz Jobst, HANSER-Verlag, ISBN 3-446-22061-5 Go To Java 2, Guido Krüger, Addison-Wesley, ISBN 3-8273-1370-8 Version 1.0, 21.02.07 Seite 1 Kurzeinführung in Java Einstieg in Java 1.1.4 Infos auf dem Web 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://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) http://javareport.com (Web-Seite der Zeitschrift Java Report) 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. • 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. Version 1.0, 21.02.07 Seite 2 Kurzeinführung in Java Einstieg in Java 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 JCreator, JBuilder, BlueJ 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 <filename>.java Der Compiler generiert daraus: <filename>.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 <filename> Version 1.0, 21.02.07 Seite 3 Einstieg in Java Kurzeinführung in Java 1.3.4 Übersicht Entwicklung und Ausführung eines Java-Programmes können grafisch wie folgt dargestellt werden: Editor JavaSourcecode <dateiname>.java JavaCompiler JavaBytecode Browser mit integrierter VM Betriebssystem mit VM <dateiname>.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 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 roten Kreis ausgibt: Version 1.0, 21.02.07 Seite 4 Kurzeinführung in Java Einstieg in Java import java.applet.Applet; import java.awt.Graphics; import java.awt.*; /** * Class BspApplet - A simple applet * * @author WBR1 * @version 17.11.2003 */ 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 start * method is called for the first time. */ public void init() { font = new Font("Helvetica", Font.BOLD, 32); } /** * 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 necessary * @param g Reference to graphics system */ public void paint(Graphics g) { // draw red circle g.setColor(Color.red); g.fillOval(10,10,300,300); // set text g.setColor(Color.black); g.setFont(font); g.drawString(msg,90,170); } } Version 1.0, 21.02.07 Seite 5 Kurzeinführung in Java Einstieg in Java Das Applet muss wie folgt in den HTML-Code eingebunden werden: <html> <head> <title>BspApplet Applet</title> </head> <body> <h1>BspApplet Applet</h1> <hr> <applet code="BspApplet.class" width=320 height=320 </applet> <hr> </body> </html> Wird die HTML-Seite im Webbrowser gestartet, so erscheint folgendes Fenster: Abbildung 2: Unser Applet Version 1.0, 21.02.07 Seite 6 Kurzeinführung in Java 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.0, 21.02.07 Seite 7 Kurzeinführung in Java 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 qualitative 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.0, 21.02.07 Seite 8 Funktionale Programmierung in Java Kurzeinführung in Java • 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 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 der Zeichenfolge /** und generiert daraus die Dokumentation im HTMLFormat. Beispiel: /** * Kommentar zur Methode * @param p1 Beschreibung des ersten Parameters */ public static void myFunc(int p1) { } 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 1: Escape-Sequenzen Version 1.0, 21.02.07 Seite 9 Funktionale Programmierung in Java Kurzeinführung in Java 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 2: 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: <type> <ident>; Dabei entspricht <type> dem Typ der Variablen. Das kann z.B. ein elementarer Datentyp (siehe Kapitel 2.4.2) aber auch eine Klasse sein. <ident> 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; 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). Version 1.0, 21.02.07 Seite 10 Funktionale Programmierung in Java Kurzeinführung in Java 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 3: 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) Umwandlung ins Dezimalzahlensystem Umwandlung ins Dualzahlensystem Umwandlung ins Hexadezimalzahlensystem Umwandlung ins Oktalzahlensystem Beispiel: int i = 127; Version 1.0, 21.02.07 Seite 11 Funktionale Programmierung in Java Kurzeinführung in Java 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 4: 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 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 5: 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.4 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 6: 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. Version 1.0, 21.02.07 Seite 12 Funktionale Programmierung in Java Kurzeinführung in Java 2.4.3 Datentyp-Umwandlungen Mit Hilfe der Datentyp-Umwandlung (auch Casting genannt) kann ein Datentyp in einen anderen umgewandelt werden. Syntax: <Ziel-Variable> = (Typ der Zielvariable) <Ausgangs-Variable> Beispiel: int i1, i2; float f1 f1 = (float)i1 / (float)i2; 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 7: 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 { Version 1.0, 21.02.07 Seite 13 Kurzeinführung in Java Funktionale Programmierung in Java 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 } 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 vor b kommt = 0 falls der Inhalt gleich ist >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. Version 1.0, 21.02.07 Seite 14 Kurzeinführung in Java Funktionale Programmierung in Java Beispiel: public class StringTest { public static void main(String args[]) { String student_1 = "Hans"; String student_2 = new String("Thomas"); String student_3 = student_1; System.out.println(student_1 + " " + student_2 + " " + student_3); } } 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 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. Version 1.0, 21.02.07 Seite 15 Kurzeinführung in Java Funktionale Programmierung in Java 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: <Array type> <Array name> []; oder: <Array type> [] <Array name>; 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 die Methode length() bestimmen, welche die Anzahl Elemente zurückgibt. Syntax: <Array name> = new <Array type>[<number of elements>]; 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: <Array type> <Array name> [] = new <Array type>[<number of elements>]; 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: 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: <Array type> <Array name> [] = {<value1>, <value2>, ..... , <valueN>}; Beispiele: int intArray [] = {1,2,3,4,5}; // reserviert Speicher für 5 Elemente vom Typ int und initialisiert die Elemente Version 1.0, 21.02.07 Seite 16 Kurzeinführung in Java Funktionale Programmierung in Java Natürlich dürfen die hier zugewiesenen Werte auch berechnete Werte sein, sofern diese bei der Zuweisung schon eindeutig definiert sind. Beispiel: 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 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: <Array type> <Array name> [][]; <Array name> = new <Array type>[<number of rows>] [<number of columns>]; oder: <Array type> <Array name> [][] = new <Array type>[<number of rows>] [<number of columns>]; Auf die einzelnen Elemente kann wie folgt zugegriffen werden: <Array name> [row][column] = <value>; Version 1.0, 21.02.07 Seite 17 Kurzeinführung in Java Funktionale Programmierung in Java Auch hier können wir Deklaration, Allokation und Initialisierung zusammenfassen: <Array type> <Array name> [] [] = { {<value11>, <value12>,.... ,<value1N>}, {<value21>, <value22>,.... ,<value2N>} }; 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. 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. Version 1.0, 21.02.07 Seite 18 Funktionale Programmierung in Java Kurzeinführung in Java Vorrang (precedence) 1 (höchster) 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (niedrigster) Operatoren Hinweis Assoziativität [] . (params) expr++ expr-++expr --expr +expr -expr ~ ! new (type)expr * / % + << >> >>> < > <= >= instanceof == != ++,-- postfix links nach rechts unär rechts nach links Cast links nach rechts binär links nach rechts links nach rechts links nach rechts links nach rechts & links nach rechts ^ links nach rechts | links nach rechts && links nach rechts || links nach rechts ?: rechts nach links = += -= *= /= %= &= ^= |= <<= >>= >>>= rechts nach links Tabelle 8: 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 9: Arithmetische Operatoren Die arithmetischen Operationen werden von links nach rechts gemäss den Prioritäten aus Tabelle 8 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. Version 1.0, 21.02.07 Seite 19 Kurzeinführung in Java Operator op1 = = op2 op1 != op2 op1 < op2 op1 <= op2 op1 > op2 op1 >= op2 Funktionale Programmierung in Java 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 10: 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 11: 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 ~op1 op1 & op2 op1 | op2 op1 ^ op2 op1 << op2 Bezeichnung Komplement AND OR EXOR shift left op1 >> op2 shift right, mit Vorzeichen op1 >>> op2 shift right, mit 0 Beschreibung Invertiert jedes Bit des Operanden bitweise UND-Verknüpfung von op1 und op2 bitweise ODER-Verknüpfung von op1 und op2 bitweise EXOR-Verknüpfung von op1 und op2 op1 wird um op2 Positionen nach links verschoben, von rechts werden "0" eingefügt. op1 wird um op2 Positionen nach rechts verschoben, von links wird das Vorzeichen eingefügt. op1 wird um op2 Positionen nach rechts verschoben, von links wird "0" eingefügt. Tabelle 12: 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). Version 1.0, 21.02.07 Seite 20 Funktionale Programmierung in Java Kurzeinführung in Java 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 = <expr> op1 += op2 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 13: Zuweisungsoperatoren Bei allen Operationen aus Tabelle 13 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. 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; Version 1.0, 21.02.07 Seite 21 Kurzeinführung in Java Funktionale Programmierung in Java 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. 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(<Bedingung>) { <Anweisungen>; } else { Version 1.0, 21.02.07 Seite 22 Funktionale Programmierung in Java Kurzeinführung in Java <Anweisungen>; } 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. • 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(<Bedingung>) { if(<Bedingung_2>) { <Anweisungen>; } } else { <Anweisungen>; } • Es können auch eine Serie von Bedingungen geprüft werden: if(<Bedingung_1>) <Anweisung_1>; else if(<Bedingung_2>) <Anweisung_2>; else <Default-Anweisung>; • 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 Version 1.0, 21.02.07 Seite 23 Funktionale Programmierung in Java Kurzeinführung in Java 2.9.3 switch Syntax: switch(<Ausdruck>) { case <konstanter_Ausdruck_1>: <Anweisungen>; break; case <konstanter_Ausdruck_2>: <Anweisungen>; break; ... default: <Anweisungen>; } Struktogramm: Ausdruck Const_1 Const_2 default Anweisung_1 Anweisung_2 ... Anweisungen 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: Version 1.0, 21.02.07 Seite 24 Funktionale Programmierung in Java Kurzeinführung in Java • • 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(<Bedingung>) { <Anweisungen>; } 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. Version 1.0, 21.02.07 Seite 25 Funktionale Programmierung in Java Kurzeinführung in Java 2.9.5 do while Syntax: do { <Anweisungen>; } while(<Bedingung>); Struktogramm: do Anweisungen while (Bedingung) grafische Backus-Naur Form: do Statement 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(<Initialisierung>; <Bedingung>; <Ausdruck>) { <Anweisungen>; } Struktogramm: Version 1.0, 21.02.07 Seite 26 Funktionale Programmierung in Java Kurzeinführung in Java for (Initialisierung; Bedingung; Ausdruck) Anweisungen grafische Backus-Naur Form: for ( Expression ; Expression ; Expression Variable declaration ) Statement 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: <Initialisierung>; while(<Bedingung>) { <Anweisungen>; <Ausdruck>; } • 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 Version 1.0, 21.02.07 Seite 27 Kurzeinführung in Java Funktionale Programmierung in Java 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 // 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])); } Version 1.0, 21.02.07 Seite 28 Kurzeinführung in Java Funktionale Programmierung in Java 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 } 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; /** Version 1.0, 21.02.07 Seite 29 Kurzeinführung in Java Funktionale Programmierung in Java * 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. */ 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 Version 1.0, 21.02.07 Seite 30 Kurzeinführung in Java Funktionale Programmierung in Java 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; } 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 Version 1.0, 21.02.07 Seite 31 Kurzeinführung in Java Funktionale Programmierung in Java { 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); } } 2.11 main() Beim Programmstart kann man einer Applikation Parameter mitgeben: java <Programmname> <Parameter_1> <Parameter_2> .... <Parameter_n> 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: Version 1.0, 21.02.07 Seite 32 Kurzeinführung in Java Funktionale Programmierung in Java Parameter args[0] = Hallo Parameter args[1] = 2 Version 1.0, 21.02.07 Seite 33 Kurzeinführung in Java 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: • Daten: Die Struktur der Daten ist nur innerhalb des ADT bekannt, sie wird gegen aussen verborgen (information hiding). Version 1.0, 21.02.07 Seite 34 Kurzeinführung in Java • Einführung in die OOP mit Java 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 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. Version 1.0, 21.02.07 Seite 35 Kurzeinführung in Java Einführung in die OOP mit Java x_coord y_coord radius isVisible Programm setCoord() draw() move() Kreis 3 r=5 Kreis 1 r=4 Bildschirm Kreis 2 r = 10 Abbildung 3: 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.0, 21.02.07 Seite 36 Einführung in die OOP mit Java Kurzeinführung in Java geometrisches Objekt x_coord y_coord isVisisble Kreis radius Rechteck width height Abbildung 4: 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.0, 21.02.07 Seite 37 Kurzeinführung in Java 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. 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() 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 (<Objekt>.<Attribut> respektive <Objekt>.<Methode>). Dabei wird jeweils unterschieden, ob es sich um Version 1.0, 21.02.07 Seite 38 Einführung in die OOP mit Java Kurzeinführung in Java 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 14: 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(); } } public class TestAccess { public static void main(String[] args) { Kreis meinKreis = new Kreis(); Version 1.0, 21.02.07 Seite 39 Kurzeinführung in Java Einführung in die OOP mit Java //meinKreis.x_coord = 5; meinKreis.isVisible = true; meinKreis.setCoord(5, 7); meinKreis.draw(); //meinKreis.move(1,2); // // // // // 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 5: 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 6: Klassendiagramm der Klasse Kreis Version 1.0, 21.02.07 Seite 40 Kurzeinführung in Java Einführung in die OOP mit Java 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 6 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.0, 21.02.07 Seite 41 Kurzeinführung in Java Einführung in die OOP mit Java 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 ok 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: <Klassentyp> <Instanz_Name>; <Instanz_Name> = new <Klassentyp()>; oder <Klassentyp> <Instanz_Name> = new <Klassentyp()>; 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) und keine Modifier • Es können ihnen Parameter übergeben werden • Sie können überladen werden. Version 1.0, 21.02.07 new aufgerufen werden, um ein Seite 42 Kurzeinführung in Java Einführung in die OOP mit Java 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 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("isVisilbe = " + 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.0, 21.02.07 Seite 43 Kurzeinführung in Java Einführung in die OOP mit Java 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) 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. 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) { this.count = count; Version 1.0, 21.02.07 // Konstruktor Seite 44 Kurzeinführung in Java Einführung in die OOP mit Java 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 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. 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; Version 1.0, 21.02.07 Seite 45 Einführung in die OOP mit Java Kurzeinführung in Java // 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; 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 Version 1.0, 21.02.07 Seite 46 Kurzeinführung in Java Einführung in die OOP mit Java 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) { 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 Version 1.0, 21.02.07 Seite 47 Kurzeinführung in Java Einführung in die OOP mit Java 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. • 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. Im Klassendiagramm wird die Vererbung wie folgt dargestellt: Version 1.0, 21.02.07 Seite 48 Kurzeinführung in Java Einführung in die OOP mit Java Oberklasse Unterklasse Abbildung 7: 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 8: 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.0, 21.02.07 Seite 49 Kurzeinführung in Java 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.0, 21.02.07 Seite 50 Kurzeinführung in Java 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.0, 21.02.07 Seite 51 Kurzeinführung in Java 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. In Java wird zur Bildung von abstrakten Klassen und Methoden das Schlüsselwort abstract verwendet. Syntax: Version 1.0, 21.02.07 Seite 52 Einführung in die OOP mit Java Kurzeinführung in Java 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() area() Kreis radius Kreis() area() getRadius() Rechteck width height Rechteck() area() getWidth() getHeight() Abbildung 9: 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.0, 21.02.07 Seite 53 Kurzeinführung in Java 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; // Konsruktor Kreis(int x, int y, int 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 Rechteck rechteck = new Rechteck(1,3,10,5,true); Kreis kreis = new Kreis(3,7,5,true); Version 1.0, 21.02.07 Seite 54 Kurzeinführung in Java Einführung in die OOP mit Java // 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.0, 21.02.07 Seite 55 Einführung in die OOP mit Java Kurzeinführung in Java 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.0, 21.02.07 Seite 56 Kurzeinführung in Java Einführung in die OOP mit Java 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.0, 21.02.07 Seite 57 Einführung in die OOP mit Java Kurzeinführung in Java 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 10: 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 Method_B() Abbildung 11: Klassendiagramm Beziehungen zwischen Klassen public class D { public static void Method_D(String str) { System.out.println(str); } } Version 1.0, 21.02.07 Seite 58 Einführung in die OOP mit Java Kurzeinführung in Java public class C { public void Method_C() { D.Method_D("Hallo"); } } public class A { C c; // Komposition A() { c = new C();} // Konstruktor protected void Method_A() { c.Method_C(); } } public class B extends A { public static void main(String[] args) { B b = new B(); b.Method_A(); } } // Methode der Oberklasse In der main()-Methode der Klasse B wird ein Objekt der Klasse B instanziert und die Methode Method_A() der Oberklasse aufgerufen. Dies ist möglich weil B ein A ist und damit die Methoden der Oberklasse erbt. Die Klasse A hat ein Attribut der Klasse C (Komposition), welches durch den Konstruktor angelegt wird. Die Instanz C innerhalb der Klasse A lebt nur so lange wie die Instanz b des Hauptprogramms. Aus Method_A() wird Method_C() der Instanz c aufgerufen. Diese wiederum ruft Method_D() auf, welche als Klassenmethode der Klasse D definiert wurde. Die Klasse D ist unabhängig von der Klasse C. 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 12: 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 Implementierung (ähnlich wie eine abstrakte Klasse, bei der jedoch einzelne Methoden implementiert sein können). Version 1.0, 21.02.07 Seite 59 Einführung in die OOP mit Java Kurzeinführung in Java 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 13: 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.0, 21.02.07 Seite 60 Kurzeinführung in Java Einführung in die OOP mit Java abstract GeometrischesObjekt x_coord y_coord isVisible GeometrischesObjekt() setCoord() draw() move() area() Rechteck width height Rechteck() area() getWidth() getHeight() abstract Printer style print() feedPaper() Abbildung 14: 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 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); } public void feedPaper(){}; } Version 1.0, 21.02.07 Seite 61 Kurzeinführung in Java Einführung in die OOP mit Java // Test Application 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()); } } Die Ausgabe erfolgt in diesem Testprogramm nicht auf einem Printer sonder auch wieder auf dem Bildschirm: Fläche Rechteck: 50.0 Bemerkung: 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. 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: 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. Version 1.0, 21.02.07 Seite 62 Kurzeinführung in Java Einführung in die OOP mit Java 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.0, 21.02.07 Seite 63 Einführung in die OOP mit Java Kurzeinführung in Java 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.0, 21.02.07 Seite 64 Kurzeinführung in Java 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 14: 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.0, 21.02.07 Seite 65 Einführung in die OOP mit Java Kurzeinführung in Java 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 15: Package Diagram 3.8.1.2 Sequenzdiagramme Das Sequenzdiagramm (Sequence Diagram) erlaubt eine dynamische Sicht auf das System. Aus dem Sequenzdiagramm ist ersichtlich, welche Objekte welche anderen Objekte in welcher Reihenfolge aufrufen. Object1:Class name Object2 Object3 Object4 1. event 2. operation 3. operation (parameter list) 4. operation (parameter list) 5. operation Abbildung 16: Sequenzdiagramm Sequenzdiagramme haben zwei Dimensionen. Die vertikale Achse stellt normalerweise die Zeit dar. Die grauen Balken geben an, wie lange die Objekte leben. Auf der horizontalen Achse werden verschiedene Objekte dargestellt. Über Pfeile wird angegeben, welche Objekte Methoden anderer Objekte aufrufen. Version 1.0, 21.02.07 Seite 66 Kurzeinführung in Java Applets 4 Applets Version 1.0, 21.02.07 Seite 67