Kurzeinführung in Java - BFH-TI Staff

Werbung
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
5. März 2007
Roger Weber
1.0
Kurzeinführung in Java
Inhaltsverzeichnis
Inhaltsverzeichnis
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 .......................................................................................................................... 2
1.2
Ein erstes Beispiel ................................................................................................................................... 2
1.3
Entwicklung von Java-Programmen ....................................................................................................... 3
1.3.1
Texteditor ........................................................................................................................................ 3
1.3.2
Compiler.......................................................................................................................................... 3
1.3.3
Interpreter........................................................................................................................................ 3
1.3.4
Übersicht ......................................................................................................................................... 4
1.4
Applikationen.......................................................................................................................................... 4
1.5
Applets .................................................................................................................................................... 4
1.6
Vor- und Nachteile von Java ................................................................................................................... 7
2
Funktionale Programmierung mit Java ............................................................................................................8
2.1
Einführung .............................................................................................................................................. 8
2.2
Lexikalische Elemente ............................................................................................................................ 8
2.2.1
Zeichensatz...................................................................................................................................... 8
2.2.2
Bezeichner....................................................................................................................................... 8
2.2.3
Kommentare.................................................................................................................................... 8
2.2.4
Escape-Sequenzen........................................................................................................................... 9
2.3
Schlüsselwörter ..................................................................................................................................... 10
2.4
Variablen und elementare Datentypen .................................................................................................. 10
2.4.1
Deklaration und Initialisierung...................................................................................................... 10
2.4.2
Elementare Datentypen ................................................................................................................. 11
2.4.3
Datentyp-Umwandlungen ............................................................................................................. 13
2.4.4
Sichtbarkeit ................................................................................................................................... 13
2.4.5
Globale Variablen ......................................................................................................................... 14
2.5
Zeichenketten ........................................................................................................................................ 14
2.5.1
String............................................................................................................................................. 14
2.5.2
StringBuffer................................................................................................................................... 15
2.6
Arrays.................................................................................................................................................... 15
2.6.1
Eindimensionale Arrays ................................................................................................................ 15
2.6.2
Mehrdimensionale Arrays ............................................................................................................. 17
2.7
Konstanten ............................................................................................................................................ 18
2.8
Operatoren............................................................................................................................................. 18
2.8.1
Arithmetische Operatoren ............................................................................................................. 19
2.8.2
Vergleichsoperatoren .................................................................................................................... 19
2.8.3
Logische Operatoren ..................................................................................................................... 20
2.8.4
Bitweise Logische Operatoren ...................................................................................................... 20
2.8.5
Zuweisungsoperatoren .................................................................................................................. 21
2.8.6
Fragezeichen-Operator .................................................................................................................. 21
2.8.7
Cast-Operator ................................................................................................................................ 22
2.8.8
Zusätzliche Operatoren der OOP .................................................................................................. 22
2.9
Kontrollstrukturen ................................................................................................................................. 22
2.9.1
Einführung .................................................................................................................................... 22
2.9.2
if .................................................................................................................................................... 22
2.9.3
switch ............................................................................................................................................ 24
2.9.4
while.............................................................................................................................................. 25
2.9.5
do while......................................................................................................................................... 26
1
Version 1.0, 05.03.07
Seite II
Kurzeinführung in Java
Inhaltsverzeichnis
2.9.6
for .................................................................................................................................................. 26
2.9.7
Beeinflussung von Schleifen mit break und continue ................................................................... 27
2.10 Methoden .............................................................................................................................................. 29
2.10.1
Name, Parameterliste und Rückgabewert...................................................................................... 29
2.10.2
Signatur, Prototyp, Overloading.................................................................................................... 31
2.10.3
Rekursion ...................................................................................................................................... 31
2.11 main() .................................................................................................................................................... 32
3
OOP mit Java .......................................................................................... Fehler! Textmarke nicht definiert.
4
Applets ...........................................................................................................................................................67
Version 1.0, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.07
Seite 9
Kurzeinführung in Java
Funktionale Programmierung 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 true und false als Literale für den Wert von boolean sowie null als Literal für die Null-Referenz
reserviert.
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, 05.03.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, 05.03.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, 05.03.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
short
cast
int
cast
cast
long
cast
cast
cast
float
cast
cast
cast
cast
double
cast
cast
cast
cast
cast
cast
cast
cast
cast
cast
cast
boolean
char
cast
cast
Tabelle 7: Datentyp-Umwandlung
: kein cast erforderlich, Zuweisung ohne Datenverlust möglich
cast: Datentyp-Umwandlung erforderlich, Datenverlust möglich
: 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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.07
Seite 32
Kurzeinführung in Java
Funktionale Programmierung in Java
Parameter args[0] = Hallo
Parameter args[1] = 2
Version 1.0, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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 new aufgerufen werden, um ein
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, 05.03.07
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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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, 05.03.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 publicDeklaration erübrigt sich.
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, 05.03.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, 05.03.07
Seite 66
Kurzeinführung in Java
Applets
4 Applets
Version 1.0, 05.03.07
Seite 67
Herunterladen