Skript: Grundlagen der Programmierung

Kursname: WWIIBI_ 202 Programmierung und Programmiertechniken (Einführung in die Programmierung)

Kompetenzziele

Die Studierenden kennen die Grundprinzipien der Programmierung und Objektorientierung und können diese in einer adäquaten Programmiersprache anwenden. Sie sind in der Lage, einfache Problemstellungen algorithmisch zu formulieren, Algorithmen mit den Sprachelementen der Programmiersprache adäquat umzusetzen und Programme zu implementieren, zu testen und anzuwenden. Die Grundprinzipien der Objektorientierung an modellhaften Szenarien analysiert und implementiert werden.

Lehrinhalte

Prinzipien der Programmerstellung

  • Darstellung von Algorithmen
  • Erstellen von Quellcode
  • Programmierstil
  • Übersetzen
  • Programmausführung

Aufbau der Programmiersprache

  • Grundstruktur eines Programms
  • Variablen
  • einfache Datentypen
  • Operatoren und Ausdrücke
  • Anweisungen
  • Ablaufsteuerung
  • Kontrollstrukturen
  • strukturierte Datentypen
  • Referenzdatentypen (Felder, Klassen)

Prozedurales und modulares Programmieren:

  • Unterprogramme
  • Funktionen
  • Methoden
  • Rekursion

Grundprinzipien der objektorientierten Programmierung mit 

  • Kapselung
  • Klassen und Objekten
  • Klassenvariablen
  • Instanzvariablen
  • Klassenmenthoden
  • Zugriffsrechte
  • Vererbung, Unterklassen
  • Polymorphie

Fragen und Antworten zur Selbstkontrolle

1. Einführung

 Dieser Abschnitt gibt...

1.1 Buchreferenzen

 

Duke reading a book

  • 
Grundkurs Programmieren in Java
    • D. Ratz, J. Scheffler, D. Seese & J. Wiesenberger 

    • 
ISBN 3446440739, 34.90€
    • 7. Auflage, Hanser 2011, URL Amazon
  • Java-Grundkurs für Wirtschaftsinformatiker
    • Klaus-Georg Deck,
    • Herbert Neuendorf
    • ISBN:978-3-8348-1222-3
    • Broschiert, 456 Seiten
    • Zweite Auflage 26. März 2010
    • URL Amazon
  • Java als erste Programmiersprache: Vom Einsteiger zum Profi
    • Cornelia Heinisch, Frank Müller-Hofmann, Joachim Goll
    • ISBN: 978-3834818577
    • 1140 Seiten
    • 5.te Auflage 2007 URL Amazon
    • 6.te Auflage 2010 URL Amazon
    • 7.te Auflage 2013 URL Amazon
  • UML 2 (Das umfassende Handbuch)
    • Christoph Kecher
    • ISBN: 978-3-8362-1419-3
    • 424 Seiten
    • Dritte Auflage 2009, URL Amazon
  • Java Klassen API

Skript

Kostenlose E-Books

 

1.2 Algorithmen

Vorgehen vom Problem zur AUsführung

Im Diagramm links wird das typische Vorgehensmodell gezeigt welches von einer Problemstellung zur einer ausführbaren Anwendung führt.

  1. Lösungsidee: Der erste Schritt zur Lösung der in der Vorlesung vorgestellten Probleme basiert auf einer Idee zur Lösung des Problems
  2. Algorithmus: Im nächsten Schritt wird die Lösungsidee zu einem Algorithmus verfeinert und ausgearbeitet
  3. Programm: Ein wesentlicher Teil der Vorlesung wird aus der Umsetzung eines Algorithmus in ein ausführbares Java-Programm bestehen

Üblicherweise treten in jeder Phase Fehler auf, die ein Zurückgehen und Verbessern der Ergebnisse einer früheren Phase erfordern.

Algorithmus

  • Vorgehensbeschreibung, Handlungsanweisung
  • Alltagsbeispiele:
Kochrezept, Bastelanleitung, Partitur
  • Mathematische Beispiele:
    • 
Euklids ggT-Bestimmung, geometrische Konstruktionen
  • Name abgeleitet von Muhammed al-Chwarizmis Buch „Über das Rechnen mit indischen Ziffern“ (um 825)
  • Erster Computeralgorithmus:
1843 von Ada Lovelace für Charles Babbages Analytical Engine (Maschine wurde nicht vollendet)

Anforderungen an einen gültigen Algorithmus

  • Er verlangt gültige Eingaben und liefert definierte Ausgaben, durch die seine Korrektheit überprüft werden kann
  • Er umfasst endlich viele Einzelschritte und besteht aus hinreichend elementaren Anweisungen
  • Er ist in realistischer Zeit ausführbar
  • Er terminiert garantiert: d.h. ist nach endlich vielen Schritten fertig abgearbeitet, liefert ein Ergebnis und „hält an“
Definition
Algorithmus
  • Ein Algorithmus ist ein generelles, schrittweises, präzises, allgemeingültiges, endliches Lösungsverfahren für eine bestimmte Aufgabenart.
  • Er beruht auf elementaren Verarbeitungsschritten.
  • Alle Aufgaben einer bestimmten Art sind prinzipiell lösbar.

 

Beispiel

Das Problem des Findens einer Kubikwurzel ist ein Beispiel für das Entwickeln einer Lösungsidee und der Entwicklung eines Algorithmus.

Runterladen der jar Datei mit Demoprogramm.

Starten:

  • Ihr JRE ist korrekt konfiguriert: Doppelklick auf der Datei Kubikwurzel.jar in Ihrem Download Ordner
  • oder: starten im Kommandozeilenfenster im Download Ordner mit dem Befehl java -jar Kubikwurzel.jar

Quellcode des Applets und Anleitung zum Starten als Javaprogramm von der Konsole.

Problem

Bestimmen von Kubikwurzeln deren Ergebnis eine ganze Zahl zwischen Null und Hundert ist.

Das Demoprogramm links erlaubt Ihnen Ihre Fähigkeiten zu testen.

 

 

Lösungsidee

Lösungsidee 1: "Educated Guess"

Man kennt ja so einige Kubikwurzeln...

  • Kubikwurzel von 1 : 1
  • Kubikwurzel von 8 : 2
  • Kubikwurzel von 27 : 3
  • Kubikwurzel von 64 : 4
  • Kubikwurzel von 125 : 5
  • Kubikwurzel von 1000: 10

Lösungsidee 2: Taschenrechner mit Möglichkeit zur dritten Wurzel ziehen benutzen

Zum Kopfrechnen wenig geeignet...

Lösungsidee 3: Taschenrechner mit Potenzfunktion nutzen

Kubikwurzel x = x1/3

Zum Kopfrechnen auch weniger geeignet

Lösungsidee 4: Primzahlzerlegung!

8 = 2 * 2 * 2. Die Kubikwurzel von 8 ist 2!

Was tun bei größeren Zahlen?

Beispiel: 216 = 2 * 2 * 2 * 3 * 3 * 3

Lösung: 6 = 2 * 3

Vorgehen

Merke Dir jeden Primfaktor der dreimal vorkommt. Die Lösung ist das Produkt der Primfaktoren die 3 mal vorkommen.

Dieses Verfahren ist prinzipiell im Kopf zu meistern. Divisionen durch kleine Zahlen sind nicht so schwer.

Algorithmus

Vorbedingung

Die vorgebene Zahl k soll eine Kubikwurzel aus den natürlichen Zahlen kleiner, gleich 100 besitzen

Der Algorithmus

Verbale Beschreibung UML Diagramm
  1. Merke Dir Ergebnis e mit dem Wert 1
  2. Bestimme einen Primfaktor f der Zahl k
  3. Multipliziere e mit f und merke das Ergebnis als e
  4. Teile die Zahl k dreimal durch den Primfaktor f.
  5. Merke dieses Ergebnis als k
  6. Wiederhole Algorithmus ab Schritt 2 wenn k größer als 1 ist.
  7. Die Lösung des Problems ist e
UML Flussdiagramm

 

Wir gehen davon aus, dass Merken, Dividieren, Multiplizieren elementare Anweisungen sind die nicht weiter verfeinert werden müssen.

Der Algorithmus wird immer terminieren, da die Division durch den letzten Primfaktor eins ergeben wird.

Das Teilproblem Primfaktor von k bestimmen ist aber kein elementares Problem. Zur Lösung dieses Teilproblems wird eine neue Lösungsidee und ein neuer Algorithmus benötigt.

Implementierung in Java

 Implementierung in Java

/**
* Berechnet die Kubikwurzel einer positiven Zahl
*
* @param k Zahl von der die Kubikwurzel berechnet werden soll.
* @return ergebnis die Kubikwurzel
*/

public static int kubikwurzelVon(int k) {
   int ergebnis=1;
   do {
      int f = primfaktor(k);
      ergebnis = ergebnis*f;
      k = k / (f*f*f);
   } while (k>1);
   return ergebnis;
}

/**
* Diese Methode berechnet einen Primfaktor des Werts k
* Es wird 1 zurückgegeben wenn es keine anderen Primfaktoren
* gibt
* @param k Die Zahl von der ein Primfaktor berechnet wird
* @return f ein Primfaktor der Zahl oder 1 falls keiner existiert
*/
public static int primfaktor(int k) {
   int f = 1;
   do { f++;
   } while ((k/f*f!=k)&&(f<k));
   if (f==k) f=1;
   return f;
}

Der Algorithmus zum Bestimmen eines Primfaktors ist sehr naiv...

1.3 Grundlegende Konzepte

 Im Javaumfeld muss man zwei grundlegende Konzepte unterscheiden:

  • Die Programmiersprache Java: Mit ihr werden die zu programmierenden Algorithmen beschrieben
  • Das Laufzeitsystem Java zu dem man auch die Entwicklungswerkzeuge (JDK) zählt.
    • Mit Hilfe der Entwicklungswerkzeuge werden die (menschlich) lesbaren Java-Quellcodeprogramme in ein maschinenlesbares Format übersetzt und auf diverse Fehler geprüft
    • Das Laufzeitsystem kann dann diese übersetzten Programme ausführen.

1.3.1 Grundkonzepte von Programmiersprachen

 Programmiersprachen sind eine Ebene über den Maschinensprachen angesiedelt. Sie sind ein Mittel der Abstraktion und Strukturierung.

Die wichtigsten Komponenten einer Programmiersprache (Neuendorf S.40) sind:

  • Datentypen: (primitive und strukturierte)
    • Modellierung verschiedener Wertebereiche (Größen); Sicherheit durch Typprüfung
  • Variablen und Konstanten
    • Verwendung von Namen anstelle von Speicheradressen für Behälter die Daten verwalten
  • Ausdrücke
    • Verknüpfung von Variablen und Konstanten zu Termen die Werte berechnen
  • Zuweisungen
    • Speicherung von Ausdruckswerten in Variablen
  • Ablaufsteuerungsanweisungen: Verzweigungen und Schleifen
    • Manipulation des Programmverlaufs. Sie erlauben die Steuerung des Kontrollflusses
  • Unterprogrogramme: Prozeduren, Funktionen, Methoden
    • Kapselung von Programmteilen zur Wiederverwendung und übersichtlichen Strukturierung

Die oben aufgeführten Komponenten sind in allen gängigen Programmiersprachen zu finden.

In Java werden diese Komponenten zu Klassen zusammengeführt:

  • Sie sind die Zusammenfassung von Daten und Methoden in eigenständigen gekapselten Einheiten

Der Zusammenhang zwischen diesen Komponenten wird im folgenden UML Diagramm vereinfacht gezeigt:

Komponenten von Programmiersprachen

1.3.2 Klassen und Objekte

Bevor man ein System in einer Programmiersprache implementiert, verwendet man objektorientierte Techniken um mit dem Kunden den Problembereich (Anwendungsbereich) im Rahmen einer Systemanalyse zu modellieren.

Anschließend werden die modellierten Objekte und ihre Datentypen in eine Programmiersprache abgebildet.

Im Rahmen des Kurses werden Diagramme in UML (Unified Modeling Language) zur Beschreibung von Javaklassen verwendet. In Java sowohl als UML werden die folgenden Begriffe verwendet:

  • Klasse: Ein Typ eines Gegenstandes in der realen Welt. Mit Klassen werden nicht nur Typen der realen Welt beschrieben sondern auch abstrakte Konzepte wie zum Beispiel ein Vertrag
    • Attribute: Datenfelder einer Klasse. Sie haben einen Namen zur Unterscheidung und einen Typ
    • Methoden: Programme die auf alle Instanzen einer Klasse angewendet werden können.
      • Methoden können keinen, einen oder mehrere Eingabewerte haben
      • Methoden können keinen oder einen Rückgabewert haben
      • Methoden können die Datenfelder der Objekte verändern
  • Objekte:
    • Ausprägungen von Klassen
    • Entitäten mit gemeinsamen Eigenschaften
    • Instanzen von Klassen

Objekte haben einen Lebenszyklus. Sie können erzeugt, modifiziert und dereferenziert werden. Dereferenzierte Objekte in Java sind nicht mehr erreichbar und daher nutzlos. Sie sind defacto gelöscht.

Modellierung in UML

Im Folgenden ist eine Klasse Flugzeug in UML und zwei Objekte der Klasse a380 und jumbo modelliert.

UML Klassen, Objekte

Implementierung in Java

Die Modellierung der Klasse Flugzeug in Java ist nachfolgend dargestellt.

Die Erzeugung der beiden Objekte a380 und jumbo erfolgt in der Methode main(). Hierzu muss ein Javaprogramm ausgeführt werden.

Die Methode main() wird als Javaprogramm durch das Kommando java Flugzeug aufgerufen.

public class Flugzeug {
    String kennzeichen; // Ein Attribut vom Typ einer Zeichenkette
    int leerGewicht; // Ein Attribut vom Type einer Ganzzahl

    /**
     * Eine Methode zum Drucken der Attributbelegung des Objekts
     * Die Methode erfordert keine Eingaben. Sie erzeugt keine
     * Aufgaben
     */
    public void drucken() {
        System.out.println("Kennzeichen " + kennzeichen + " "
                + leerGewicht + "kg");
    }

    /**
     * Die Methode main() wird zum Starten des Programms benutzt
     * @param args Übergabe von Konsolenparameter. Hier nicht genutzt
     */
    public static void main(String[] args) {
        // Erzeugen zweier Objekte
        Flugzeug jumbo = new Flugzeug();
        Flugzeug a380 = new Flugzeug();
        // Belegen der Attribute des ersten Objekts mit Werten

        jumbo.kennzeichen = "D-ABYT";
        jumbo.leerGewicht = 191000;

        // Belegen der Attribute des zweiten Objekts mit Werten
        a380.kennzeichen = "D-AIMD";
        a380.leerGewicht = 286000;

        // Drucken der beiden Objekte auf der Konsole
        jumbo.drucken();
        a380.drucken();
    }
}

Das obige, ausführbare Programm hat die folgende Grundstruktur die in allen Javaklassen zufinden ist.

Einfache Klasse mit Erklärung der wichtigsten Syntax 

Javaklassen können noch mehr Bestandteile haben. Für die Einführung sind die folgenden Bestandteile die wichtigsten:

  • Eine Javaklasse sollte in einer Datei mit dem gleichen Namen und der Extension .java gespeichert werden
  • Das Schlüsselwort class steht unmittelbar vor dem gewählten Namen der Klasse
    • Vor dem Schlüsselwort class können noch weitere Schlüsselworte stehen, die z.Bsp. die Sichbarkeit der Klasse bestimmen
    • Nach dem Klassennamen können noch Oberklassen oder Implementierungen angegeben werden
  • Die Implementierung der Klasse steht zwischen dem folgenden geschweiften Klammerpaar
    • Attribute einer Klasse haben einen Typ und einen Namen. Sie werden immer mit einem Semikolon beendet.
    • Methoden einer Klasse besitzen
      • optionale Schlüsselworte um die Methodeneigenschaften zu spezifizieren
      • Rückgabetypen: Der Typ des Ergebnis der Berechnung
      • einen Namen
      • eine Parameterliste die mit runden Klammern beschrieben wird. Sie kann leer sein.
      • Einen Rumpf mit der Implementierung der Methode. Der Rumpf wird mit geschweiften Klammern implementiert. Die Implementierung des Rumpfs steht zwischen den geschweiften Klammern

1.4 Java Abgrenzung, Begriffsbestimmung

Java und andere Initiativen

Java ist:

Java in der Softwareentwicklung

  • Markenzeichen (Trademark) der Firma Oracle (ehemals Sun Microsystems)
    • nur von Oracle zertifizierte und/oder lizensierte Produkte dürfen das Warenzeichen führen
  • Programmiersprache
    • C/C++ ähnliche Syntax
    • 1995 veröffentlicht
    • konservative Weiterentwicklung der Sprache um Fragmentierung zu vermeiden
  • standardisierter Bytecode
    • wird aus Javaquellcode generiert
    • plattform- und betriebssystemunabhängig
  • Virtuelle Maschine (VM: Laufzeitumgebung)
    • führt (neutralen) Bytecode aus
    • verfügbar von unterschiedlichen Anbietern für viele Betriebsysteme
  • Laufzeitbibliotheken (Java API)
    • standardisiert
      • optionale, sowie verpflichtende Komponenten
    • reichhaltiger Funktionsvorrat der alle wesentlichen Funktionen eines Betriebssystem portabel für die Anwendung zur Verfügung stellt
      • Graphik
      • Netzwerkommunikation
      • Mathematik
      • Verschlüsselung
      • Dateizugriff
      • Datenbankzugriff
      • etc.
  • Java "Editionen" (Editions)
    • Funktionalitätsbündel für unterschiedliche Anwendungszwecke
    • gleiche Sprache, unterschiedliche VM, unterschiedlicher Bibliotheksumfang
    • z.Zt. existierende Editionen
      • SE: Standard Edition (Gegenstand des Kurses!)
        • Desktopanwendungen, fundament für Serveranwendungen, zunehmend auch Smartphones
      • EE: "Enterprise Edition"
        • Middleware für Java-Applikationsserver
        • Fokus: Mehrbenutzeranwendungen, Webfrontends, komplexe betriebswirtschaftliche Anwendungen
      • ME: "Micro Edition"
        • Mobile Endgeräte: Mobiltelefone, PDAs, Smartphone
        • Fokus: Animation auf Mobiltelefonen, Nutzung der Mobiltelefoninfrastruktur
        • unterschideliche "Profile und Konfigurationen" für unterschiedliche Geräteklassen
      • JavaCard
        • Javalaufzeitumgebung für intelligente EC und Kreditkarten mit Chip
      • weitere Varianten
        • JavaFX : Laufzeitumgebung für multimediale Benutzeroberflächen
          • Ist Seit Java 7.0 update 6 Teil der Javalaufzeitumgebung
        • Randbemerkung: JavaScript: Netscape führte im Frühjahr 1996 die Browserscriptsprache JavaScript ein. Netscape war zum damaligen Zeitpunkt ein sehr früher Kooperationspartner von Sun Microsystems, der als einer der ersten Partner eine Portierung von Java im Browser auslieferte. Sun Microsystems gewährte Netscape zum damaligen Zeitpunkt das Recht das Markenzeichen "Javascript" zu verwenden. Die Marke gehört noch heute Oracle (als Nachfolger von Sun Microsystems)
          • Interessant: JavaScript hat technologisch gesehen nichts mit der Programmiersprache Java gemein.

1.4.1 Java Standard Edition (SE)

Standalone Java-Programme

Java-Programme können lokal wie ein gewöhnliches Programm ausgeführt werden.

Java-Applikationsserver die selbst keine direkte Benutzeroberfläche haben und im Hintergrund laufen sind technisch gesehen Programme der Java Standardedition.

Java-Programme die selbst eine graphische Benutzerschnittstelle mit Hilfe der Java Swing/AWT Bibliotheken exponieren nennt man auch "Fat" oder "Rich Clients".

Das Starten eines Javaprogramms geschieht mit dem Kommando java welches die Javalaufzeitumgebung startet:

$ java HelloWorld
Hello World!

Applet-Plugins

Java-Programme können über ein Netzwerk transportiert und direkt in einem Browser ausgeführt werden wenn sie als Java-Applet implementiert wurden:

HelloWorldApplet.java

import java.applet.Applet;
import java.awt.Graphics;

public class HelloWorldApplet extends Applet{
    // Diese Methode wird aufgerufen wenn das Applet started
    public void init() {
         // Die Methode ist notwendig. Sie muss aber nicht mit Inhalt gefüllt
         // werden.
         System.out.println("HelloWorldApplet.init() aufgerufen");
     }


    // Diese Methode wird aufgerufen wenn das Applet beendet wird.
    // Der Benutzer hat die html Seite verlassen
     public void stop()
     {
     System.out.println("HelloWorldApplet.stop() aufgerufen");
     }

    // Die Standardmethode um etwas auf den Bildschirm zu malen

     public void paint(Graphics g)
     {
     //Diese Methode malt Text auf den Bildschirm
     // Text, x Koordinate, y Koordinate
      g.drawString("Dies ist ein Text",20,20);
      g.drawString("Hello World!",20,40);
     }
}

Das Applet weiter unten ist ein Javaprogramm welches solange läuft wie die html Seite angezeigt wird. Es läuft auf dem Computer des Betrachters. Es wurde jedoch vom Internet heruntergeladen.

Der Begriff "Applet" stammt aus dem englischen und beschreibt kleine Programme. Er wurde aus dem Begriff "Application" abgeleitet.

Der Begriff "Plugin" bezieht sich auf den Webbrowser. Die Javafunktionalität ist in einem modernen Browser eine Zusatzfunktion die man als "Plugin" nachinstallieren kann.

Ein in die Webseite eingebettetes Applet

Screen Shot Aktives Applet

Java Web Start Anwendungen

Java Web Start ist eine Weiterentwicklung der Applettechnologie. Sie erlaubt es vollständige Javaprogramme über das Netzwerk zu installieren und zu starten.

Ein Beispiel hierfür ist das Demoprogramm Notepad von Oracle (Wird wahrscheinlich nicht funktionieren wegen Betriebssystem-Sicherheitseinschränkungen).

Applets haben nur sehr eingeschränkte Nutzungsmöglichkeiten da sie innerhalb einer Bowserseite laufen. Ihre Lebensdauer hängt von der Lebensdauer der Browserseite ab.

Java Web Start verbindet die Vorteile des automatischen Ladens eines Applets mit den Vorteilen eines Java Standalone Programms. Java Web Start erlaubt normale Programme (mit Start über main() Methode) über das Netzwerk zu laden und auszuführen. Die Programme werden im Java Web Start Client gecacht und können beim nächsten Aufruf sofort wieder gestartet werden. Ist die Versionsnummer in der zugehörigen jnlp Datei größer als die gecachte Programmversion so wird die neue Version des Programms nach geladen.

 

Der typische Ablauf des Ladens und Startens eines Java Web Start Programms ist der Folgende.

  1. Ein Benutzer klickt auf einer Browserseite auf einen Hyperlink, der auf eine relativ kleine Datei vom Typ jnlp zeigt. Die Steuerdatei (hier Appl.jnlp) wird vom Browser geladen
  2. Der Browser delegiert die Verarbeitung dieser Datei an javaws
  3. javaws interpretiert die jnlp Datei und liest die Version des Programmes aus. Liegt das Programm nicht in dieser Version schon im Cache von javaws wird die Anwendung Appl.jar über das Netzwerk geladen
  4. Die Anwendung wurde neu geladen oder schon im cache gefunden. javaws startet die Anwendung wie ein normales Javaprogramm.

Java Web Start ermöglicht hiermit dynamisch neue Javaanwendungen an Benutzer zu verteilen.

Softwarepakte der Java Standard Edition

Hier wird der Stand der Entwicklung basierend auf Java 7 update 25 dokumentiert.

JRE: Java Runtime Environment

Dieses Softwarebündel enthält alle Komponenten die man zum Ausführen von Javaprogrammen benötigt. Seit Java 7u21 kann man sie in zwei Varianten installieren

JRE (für Desktops): Download

  • Javalaufzeitumgebung
  • Plugins und Bibliotheken für Browsereinbettung
  • Sicherheitskonsole
  • Kommentar: Dieses Javapaket sollten Sie installiert haben um die Applets des Kurses auf dieser Webseite zusehen

JRE for Server: Download

  • Javalaufzeitumgebung
  • Zusatzprogramme zum Monitoren von Serveranwendungen
  • das Paket enthält keine Browserplugins
  • Kommentar: Dieses Paket wird nicht für den Kurs benötigt

JDK: Java Development Kit (Download)

Dieses Softwarepaket wird für den Kurs verwendet. Es enhält

  • Javalaufzeitumgebung
  • Übersetzer
  • Analyse- und Testprogrogramme
  • Programme zum Erstellen von Javadokumentation

Hinweis: Sie müssen für den Kurs JRE und JDK installieren. Die Softwarepakete werden an unterschiedlichen Stellen installiert.

 

1.4.2 Java Enterprise Edition

 Die Java Enterprise Edition (JEE) ist eine Applikationservertechnologie die auf der Java Standard Edition aufbaut.

JEE Applikaktionsserver betreiben in der Regel Mehrbenutzeranwendungen die Webseiten für die Benutzer generieren. Die Enterprise Edition wird typischerweise in zwei Varianten eingesetzt.

  • Webcontainer: Um die Klasse Servlet entwickelte Technologien zum Erzeugen von html Seiten
  • EJB Container (Enterprise Java Beans): Ein auf EJB Klassen basierendes Framework um Anwendungslogik zu implementieren die unabhängig vom Benutzer ist (z. Bsp. Datenbank, Persistenz, Messaging, Sperren etc.)

Hinweis: Java EE ist nicht Gegenstand dieses Kurses.

1.5 Von der Javaweiterentwicklung zum Produkt

 JCP: Java Community Prozess

  • Konsortium aus Industrievertretern, OpenSource-Gruppen, Spezialisten
  • Ziel: Weiterentwicklung und Standardisierung von allen Javakomponenten

Prinzipielles Vorgehen ist das Arbeiten in gemeinsamen Untergruppen: JSR (Java Specification Request) nach folgendem Prinzip

  • Themenvorschlag für JSR
  • Annahme wenn relevant (allgemeines Interesse, kein Überlap etc.)
  • Gründung einer "Expert Group" unter Leitung des "Spezification Lead"
  • Gemeinsame Entwicklung eine Spezifikation bestehend aus
    • Spezifikation
    • Referenzimplementierung
    • TCK: Test Compatiblity Kit zum Testen von späteren Implementierungen von dritten PArteien
  • Standardisierung nach einer Reihe von Kontroll- und Freigabeprozessen in der Community

Ziel ist die breite allgemeine Verfügbarkeit der Technologie.

Wichtig:

  • Referenzimplementierung und TCK sind geistiges Eigentum des "Specification lead" um ihn für die Entwicklungskosten zu kompensieren
  • Die Modalitäten der Verbreitung kann der "Specification Leader" selbst bestimmen
    • Lizensierungsbedingungen von Referenzimplementierung und TCK
    • Open Source
    • kostenlose zur Verfügungstellung von Produkten
    • etc.

OpenJDK

Ein großer Teil der Java Standardedition von Oracle/Sun als Open Source bei OpenJDK.org freigegeben.

Hier findet zur Zeit (Stand 10.2010) die Entwicklung zu JDK 8 statt.

Produktverfügbarkeiten

Folge der unterschiedlichen Kollaborationsmodelle sind unterschiedlichste Produkte die zu unterschiedlichen Bedingungen genutzt werden können:

  • Kostenlos nutzbare Produkte (mit und ohne kommerzieller Wartung)
    • Beispiel Sun/Oracle JDK für Java SE
  • Lizenzpflichtige Produkte
    • z.Teil bei der Enterprise Edition
  • Gerätegebundene Produkte
    • JavaME: Mobile Edition
  • Open Source Lizensmodelle

Wichtig: Für Standard-, Micro-, Enterpriseedition: Nur Produkte die Zertifizierungstest mit den entsprechenden TCKs bestanden haben dürfen sich "Java" nennen.

 

 

1.6 Programme des Java JDK

 Java Standard Edition (SE) in seiner Distribution von Oracle kann in zwei Varianten benutzt werden:

Das JDK ist eine echte Obermenge des JRE und enhält die folgenden Komponenten die man zur Entwicklung und Ausführung von Java Programmen im Rahmen des Kurses benötigt:

Program Bedeutung Kommentar
java Startet Java Programme Laufzeitsystem. Interpretiert Javabytecode. Übersetzt häufig verwendeten Javabytecode in Maschinencode
javac Übersetzer Übersetzt Javaquellcode in Bytecode 
javadoc Generieren von Dokumentation Erzeugt Dokumentation durch Übersetzen von speziellen Kommentaren im Javaquellcode
jar   Java Archiver Bündelt Javabytecode und Dateien zu jar Dateien 
javah C Schnittstellengenerator  Erzeugt Schnittstellendeklarationen für C Programme die von Java aus benutzt werden sollen
javap Java Class File Disassembler  Extrahiert Schnittstelleninformation aus class Dateien und erzeugt öffentliche Schnittstellen
jdb Java Debugger Kommandozeilendebugger
jps Anzeige der Prozess Ids von Java Programmen Hilfswerkzeug für jconsole
jconsole Monitoren von Java Prozessen graphisches Werkzeug zum monitoren von Java Anwendungen
jinfo Auslesen der Konfiguration eines laufenden Javaprozess Konsolenausgabe der laufenden Prozessdaten
appletviewer Testen von Applets Ein Testprogramm für Entwickler von Applets. Erlaubt testen ohne Browser

Dies ergibt die folgenden Zusammenhänge zwischen den verschiedenen Dateien die bei der Entwicklung beteiligt sind:

 

 

Referenzen

JDK 8.0 Dokumentation 

1.6.1 Das erste Javaprogramm (Benutzung der Kommandos java und javac)

Überblick

Das Übersetzen von Javaquellprogrammen in interpretierbare Bytecodedateien erfolgt mit dem Java-Übersetzer javac. Das Ausführen der übersetzten Dateien erfolgt mit dem Kommando java, dem Javalaufzeitsystem, welches Javaprogramme startet.

Javaübersetzer: javac

Der Javaübersetzer übersetzt lesbare Quelldateien mit der Dateiextension *.java in eine oder mehrere Bytecodedateien mit der Dateiextension *.class.

Beispiel

$ javac HelloWorld.java

Dieser Befehl erzeugt eine Datei mit dem Namen HelloWorld.class im gleichen Verzeichnis

Tipp: DerJavaübersetzer erwartet den vollen Dateinamen inklusive der Dateiextension .java

Javalaufzeitsystem: java

Das Kommando java erlaubt das Starten von Java-Programmen.

Beispiel:

$ java HelloWorld

Javaprogramme müssen als Bytecodedateien (Dateiextension *.class) oder als zu Java-Archiven (Dateiextension *.jar) gebündelte Bytecodedateien vorliegen.

Tipp: Das Kommando java akzeptiert nur den Namen der auszuführenden Klasse. Es akzeptiert nicht den Namen der gleichnamigen Datei mit dem Bytecode!

Das Kammando java  sucht dann standardmässig im aktuellen Verzeichnis nach einer Bytecodedatei (Extension *.class) die den Interpretercode für die gewünschte Klasse enthalten.

Hiermit ergibt sich die Abfolge der Kommandos für ein einfaches Testprogrann HelloWorld.java:

1. Testen der Java Laufzeitumgebung

Überprüfen sie ob eine Javalaufzeitumgebung vorhanden ist. Dies geschieht mit der Option -version des Programms java:

$ java -version
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02-279-10M3065)
Java HotSpot(TM) Client VM (build 16.3-b01-279, mixed mode)

2. Erstellen des Java-Quellcodes

Erstellen sie eine Textdatei mit dem Namen HelloWorld.java mit dem folgenden Inhalt:

class HelloWorld {
	public static void main(String[] args) {
	   System.out.println("Hello World!");
	}
}

Zum Beispiel mit gedit:

 

3. Übersetzen des Quellcodes

Das Programm javac (javac.exe in Windows) übersetzt den Quellcode.

$ javac HelloWorld.java

Das Ergebnis ist eine Datei mit dem Namen HelloWorld.class. Diese Datei enthält den interpretierbaren Bytecode des Programms.

4. Ausführen des Programmes

Der generierte Bytecode der Datei HelloWorld.class wird dann mit Hilfe des Programms java ausgeführt. Die extension .class wird nicht mit angegeben:

$ java HelloWorld
Hello World!

javac

Die (vereinfachte) Syntax von javac ist:

$ javac [Optionen] [Quelldateien] 

Die wichtigsten Optionen des Javaübersetzers javac sind:

  • -classpath -cp classpath : Verzeichnisse in denen nach .class Dateien gesucht werden soll
  • -d directory : Verzeichnis in dem die erzeugten .class Dateien abgelegt werden. Das Verzeichnis muss bereits existieren. Fehlt diese Option, so werden die erzeugten .class Dateien im aktuellen Verzeichnis abgelegt
  • - help: druckt alle Standardoptionen auf der Konsole
  • -source release: Erlaubt das Parsen der Javaquelldateien nach alten Sprachstandards [1.5,5,1.4,1.3].
  • -sourcepath sourcepath: Suchen von Quelldateien in den angegebenen Verzeichnissen und jar Archiven
  • -X : Anzeige der nicht Standardoptionen

1.6.2 Generieren von Schnittstelleninformation

Schnittstellengenerierung mit javap

Der Schnittstellengenerator javap wird im gleichen Verzeichnis aufgerufen in dem sich die Datei HelloWorld.class befindet. Rufen Sie ihn mit dem Befehl "javap HelloWorld" auf. Die Dateiendung .class wird nicht benötigt. javap benutzt nicht die Quelldatei. javap benutzt die Binärdatei und sucht sie im vorgegebenen Suchpfad für Binärdateien

sschneid@scalingbits:~/l1$ javap HelloWorld
Compiled from "HelloWorld.java"
class HelloWorld extends java.lang.Object{
    HelloWorld();
    public static void main(java.lang.String[]);
}

Es werden die Informationen über alle öffentlichen Eigenschaften generiert. Es werden nicht Informationen über die Implementierung und private Attribute und Methoden generiert.

1.6.3 Generieren von Javadokumentation

1. Editieren Sie die Datei HelloWorld.java

/**
 *
 * @author Ihr Name
 */
public class HelloWorld {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        System.out.println("HelloWorld");
    }
}

Datei sichern...

2. Rufen Sie javadoc auf

sschneid@scalingbits:~/l1$ javadoc HelloWorld.java 
Loading source file HelloWorld.java...
Constructing Javadoc information...
Standard Doclet version 1.6.0_18
Building tree for all the packages and classes...
Generating HelloWorld.html...
Generating package-frame.html...
Generating package-summary.html...
Generating package-tree.html...
Generating constant-values.html...
Building index for all the packages and classes...
Generating overview-tree.html...
Generating index-all.html...
Generating deprecated-list.html

3. Kontrolle Ergebnis: Öffnen Sie mit Ihrem Browser die Datei index.html

Beispiel (Screenshot)

 

1.6.4 Generieren von C Schnittstellen

Das Programm javah generiert Schnittstellendateien für C Anwendungen. Hiermit lassen sich plattformabhängige C-Programme aus der Javalaufzeitumgebung aufrufen. Dieses Vorgehen wird von JNI (Java Native Interface) unterstützt.

C-Routinen werden nicht in Java implementiert. Sie werden nur deklariert und die Bibliotheken mit den Objektdateien (keine Javaobjekte...) zur Laufzeit dazu gebunden. Die Deklaration findet mit Hilfe des Schlüsselworts native statt. Im folgenden Beispiel wurde eine C-Routine test() als extern deklariert.

HelloWorld.java Quellcode

/**
 *
 * @author Stefan Schneider
 */
public class HelloWorld {
   native int test();

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        System.out.println("Hello Gedit");
    }
}

Die Datei HelloWorld.java muss zuerst mit javac übersetzt werden um dann auf der Datei HelloWorld.class das Kommanda javah aufzurufen.

javac HelloWorld.java
javah HelloWorld

Generierte Datei helloWorld.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    test
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_HelloWorld_test
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

 

1.6.5 Javaprozess-Monitoring mit jconsole, jinfo, jps

Monitoring mit jconsole

 jconsole ist eine grafische Javaanwendung die es erlaubt die Konfiguration eines Javapozess' zur Laufzeit zu beobachten. Einige der Eigenschaften die beobachtet werden können sind:

  • Speicherverbrauch
  • Parameter mit denen die VM konfiguriert ist
  • genauer Typ der VM
  • CPU-Verbrauch
  • Anzahl der Threads
  • Anzahl geladene Klassen
  • ...

Die Verwendung von jconsole geschieht wie folgt:

1. Starten eines Javaprogramm

Starten eines (länger) laufenden Javaprogramm

Starten des Programm DemoFrame

java DemoFrame

2. Bestimmen der Pozess-Id des laufenden Javaprogramms

Jeder Prozess des Betriebssystems hat eine eindeutige Nummer den "Process Identifier". Das Kommando jps listet unabhängig vom Betriebssystem alle Javaprozesse.

jps
254 
16964 Jps
16959 DemoFrame

3. Starten von jconsole

jconsole 16959

Wichtig: Das Javaprogramm darf zum Zeitpunkt an dem jps und jconsole aufgerufen werden noch nicht beendet sein!

Laufende jconsole Anwendung:

 

Monitoring mit jinfo

jinfo liest ebenfalls die wichtigsten Kenndaten eines laufenden Prozesses aus und gibt sie auf der der Konsole aus.

Das erfassen der ProzessId geschieht auch mit dem Hilfsprogramm jps:

1. Starten des Javaprogramms

Starten (länger) laufenden Javaprogramm

Starten des Programm DemoFrame

java DemoFrame

2. Bestimmen der Pozess-Id des laufenden Javaprogramms

Pegasus:bin sschneid$ jconsole 16959
Pegasus:bin sschneid$ jps
254 
17168 Jps
17166 DemoFrame

3. Starten von jinfo

Pegasus:bin sschneid$ jinfo 17166
Attaching to process ID 17166, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 16.3-b01-279
Java System Properties:

java.runtime.name = Java(TM) SE Runtime Environment
sun.boot.library.path = /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Libraries
java.vm.version = 16.3-b01-279
awt.nativeDoubleBuffering = true
gopherProxySet = false
mrj.build = 10M3065
java.vm.vendor = Apple Inc.
java.vendor.url = http://www.apple.com/
path.separator = :
java.vm.name = Java HotSpot(TM) Client VM
file.encoding.pkg = sun.io
sun.java.launcher = SUN_STANDARD
user.country = DE
sun.os.patch.level = unknown
java.vm.specification.name = Java Virtual Machine Specification
user.dir = /Users/sschneid/Documents/JavaKurs/beispiele/l1/HelloWorld
java.runtime.version = 1.6.0_20-b02-279-10M3065
java.awt.graphicsenv = apple.awt.CGraphicsEnvironment
java.endorsed.dirs = /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/endorsed
os.arch = i386
apple.awt.graphics.UseOpenGL = false
java.io.tmpdir = /var/folders/UO/UOnPVFsvGEO5k3UJnjadeE+++TI/-Tmp-/
line.separator = 

java.vm.specification.vendor = Sun Microsystems Inc.
os.name = Mac OS X
sun.jnu.encoding = MacRoman
java.library.path = .:/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
java.specification.name = Java Platform API Specification
java.class.version = 50.0
sun.management.compiler = HotSpot Client Compiler
os.version = 10.6.4
http.nonProxyHosts = local|*.local|169.254/16|*.169.254/16
user.home = /Users/sschneid
user.timezone = 
java.awt.printerjob = apple.awt.CPrinterJob
file.encoding = MacRoman
java.specification.version = 1.6
java.class.path = .
user.name = sschneid
apple.awt.graphics.UseQuartz = false
java.vm.specification.version = 1.0
java.home = /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home
sun.arch.data.model = 32
user.language = de
java.specification.vendor = Sun Microsystems Inc.
awt.toolkit = apple.awt.CToolkit
java.vm.info = mixed mode
java.version = 1.6.0_20
java.ext.dirs = /Library/Java/Extensions:/System/Library/Java/Extensions:/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/ext
sun.boot.class.path = /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Classes/jsfd.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Classes/classes.jar:/System/Library/Frameworks/JavaVM.framework/Frameworks/JavaRuntimeSupport.framework/Resources/Java/JavaRuntimeSupport.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Classes/ui.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Classes/laf.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Classes/sunrsasign.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Classes/jsse.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Classes/jce.jar:/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Classes/charsets.jar
java.vendor = Apple Inc.
file.separator = /
java.vendor.url.bug = http://bugreport.apple.com/
sun.io.unicode.encoding = UnicodeLittle
sun.cpu.endian = little
mrj.version = 1060.1.6.0_20-279
socksNonProxyHosts = local|*.local|169.254/16|*.169.254/16
ftp.nonProxyHosts = local|*.local|169.254/16|*.169.254/16
sun.awt.exception.handler = apple.awt.CToolkit$EventQueueExceptionHandler
sun.cpu.isalist = 

VM Flags:


1.6.6 appletviewer

Der appletviewer erlaubt das Testen von Applets ohne einen Webserver.

Der appletviewer kann Applets in html Seiten starten solange sie gewissen Bedingungen genügen. 

Das folgende Beispiel zeigt die einfachste Anwendung des Appletviewers:

1. Übersetzen eines Applets in Bytecode

Erzeugen der Quellcodedatei HelloWorldApplet.java:

import java.applet.Applet;
import java.awt.Graphics;
public class HelloWorldApplet extends Applet{
    /**
* Diese Methode wird aufgerufen wenn das Applet started
*
*/ public void init() { // Die Methode ist notwendig. Sie muss aber nicht mit Inhalt gefüllt // werden. System.out.println("HelloWorldApplet.init() aufgerufen"); } /**
* Diese Methode wird aufgerufen wenn das Applet beendet wird.
* Der Benutzer hat die html Seite verlassen
*/ public void stop() { System.out.println("HelloWorldApplet.stop() aufgerufen"); } /**
* Die Standardmethode um etwas auf den Bildschirm zu malen
*/ public void paint(Graphics g) { //Diese Methode malt Text auf den Bildschirm // Text, x Koordinate, y Koordinate g.drawString("Dies ist ein Text",20,20); g.drawString("Hello World!",20,40); } }

Übersetzen der Quelldatei HelloWorldApplet.java und Erzeugen der Bytecodedatei HelloWorldApplet.class

java HelloWordApplet.java

2. Erzeugen der html Datei HelloWorld.html

<html>
   <applet code="HelloWorldApplet.class" width="200" height="50" >
   </applet>
</html>

3. Aufruf des appletviewer

Der appletviewer muss im Verzeichnis aufgerufen werden in dem die Dateien HelloWorld.html HelloWorldApplet.class gespeichert sind.

appletviewer HelloWorld.html

Ergebnis:

1.7 Variablen und Zuweisungen

Zur Verwaltung der Objekte(Daten) in einem Programm dienen Datenbehälter die Variable oder Konstante genannt werden. 

Datenbehälter Bedeutung Beispiel
Konstante

Ein Datenbehälter dem über die gesamte Lebensdauer eines Programms genau ein Wert zugewiesen wird.

Hinweis: In Java ist eine Konstante eine Variable die beim Deklarieren mit dem Schlüsselwort final versehen wurde. Eine Konstante ist in Java eine Variable die nur einmal mit einem Wert belegt werden kann.

pi = 3.14;
Variable Datenbehälter der mit einem bestimmten Wert initialisiert wird: Ihm können im Laufe der Programmausführung unterschiedliche Werte zugewiesen werden
x = 0;
x = 1;
x = x +1;

Variablen müssen in Java immer deklariert sein bevor man sie benutzen kann. Dies erfolgt mit einer Deklaration in der folgenden Syntax:

 Deklaration von Variablen

Typ der Variablen: bestimmt den Wertebereich und Operationen auf einer Variablen. Er ist fest für die Lebensdauer einer Variablen.

Variablennamen: Ein beliebiger Bezeichner zum Benennen der Variable. Der Variablenname muß innerhalb eines gegebenen Kontexts eindeutig sein. Man kann einen Namen nicht zweimal in einem bestimmten Kontext verwenden.

Beispiele:

int x;
float y,z;

Variablen erhalten mit Zuweisungen eine neue Belegung. Hierfür können Konstanten, Variablen oder Audrücke benutzt werden deren Ergebnis auf eine Variable zugewiesen wird.

Die Syntax einer Zuweisung ist in Java immer:

Syntax Zuweisung
  • Das Gleichzeichen "=" ist in Java ein Operator. Es hat hier eine andere Bedeutung als in der Mathematik. Es handelt sich nicht um eine mathematische Gleichung!
  • Eine Zuweisung wird immer mit einem Semikolon beendet
  • Es ist optional möglich eine Variable in einem Schritt zu deklarieren und ihr gleichzeitig einen Wert zuzuweisen.

Beispiele:

int x = 1;
float y = 3.2* 4.1;

Die Bedeutung in Java ist:

Zuweisungsoperator Java ( Variable = Ausdruck;)
Nimm das Ergebnis der Berechnung der rechten Seite des Operators und weise der Variablen auf der linken Seite das Ergebnis zu.

Java führt immer zuerst die Berechnung des Ausdrucks auf der rechten Seite aus. Erst dann weist Java das Ergebnis der Variablen zu.

Der Ausdruck selbst muss nicht unbedingt eine komplexe Berechnung sein. Er kann auch eine Variable oder Konstante sein.

Tipps zum Verstehen einer Zuweisung

  • Identifizieren sie die 4 Bestandteile einer Zuweisung
    1. Zuweisungsoperator =
    2. Semikolon
    3. Variable (links vom Zuweisungsoperator)
    4. Term (rechts vom Zuweisungsoperator, links vom Semikolon
  • Lesen Sie eine eine Zuweisung immer von rechts nach links. Zuerst wird der Term auf der rechten Seite berechnet. Dann wird das Ergebnis der linken Seite zugewiesen

Ein Beispiel sei ein einfaches Programm welches die Eingaben in den Variablen x und y nutzt um durch fortgesetzte Addition zu Multiplizieren.

Prozessorschritte x y z

Initialisierung x=2; y=3; z=0;

x = x -1;

z = z + y;

x = x - 1; z = z + y;

2

1

1

0

3

3

3

3

0

0

3

6

Ergebnis in z 0 3 6

Beispiele

Zuweisung einer Konstanten auf eine Variable. Um genau zu sein: Es wird ein Literal zugewiesen.

pi = 3.14;

Zuweisung einer Variablen auf eine andere Variable:

d = pi;

Zuweisung eines mathematischen Ausdrucks(Term) auf eine Variable. Der Term wird vor der Zuweisung berechnet.

d = 2*pi*r;

Zuweisung eines Terms der die Variable selbst enthält. Der Term wird vor der Zuweisung mit dem alten Wert der Variablen berechnet. Erst nach der Berechnung wird der neue Wert zugewiesen.

d = 2* d;

1.8 Übungen

Duke als Boxer

1.7.1 Das erste Java Programm

class HelloWorld {
	public static void main(String[] args) {
	   System.out.println("Hello World!");
	}
}
  • Übersetzen Sie die Datei HelloWorld.java mit Hilfe des Kommandos javac in eine Bytecode-Datei mit dem Namen HelloWorld.class.
  • Führen Sie das Programm mit dem Kommando java aus.

1.7.2 Eine einfache grafische Oberfläche

Duke auf Schaukel

  1. Kopieren Sie den Quellcode des folgenden grafischen Programms DemoFrame in eine Datei mit dem Namen DemoFrame.java.
  2. Übersetzen Sie das Programm in Bytecode
  3. führen Sie das Programm aus
  4.  Ändern Sie durch experimentieren das Programm wie folgt ab:
  • Ändern Sie die den konstanten Text "Input Text:" zu einem anderen Text
  • Ändern Sie die Standardbelegung des Eingabetexts "Input" zu einer anderen Vorbelegung
  • Legen Sie das Ausgabefeld im Fenster über die Eingabezeile mt dem "Button" 

1.7.3 Swing Rechner

Diese Übung ist eine Gruppenübung. Sie ist wahrscheinlich zu schwer für das Selbststudium.

1. Quellcode erzeugen

Erzeugen Sie eine Datei mit dem Namen SwingRechner.java und dem folgendem Inhalt:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;

public class SwingRechner  extends javax.swing.JFrame {
    private javax.swing.ButtonGroup buttonGroup1;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JTextField jTextField1;
    private javax.swing.JTextField jTextField2;
    private javax.swing.JTextField jTextFieldOut;
    private javax.swing.JButton jButton1;
    private javax.swing.JButton jButton2;
    private javax.swing.JButton jButton3;

    private void initComponents() {
        jTextField1 = new javax.swing.JTextField();
        jTextField2 = new javax.swing.JTextField();
        jTextFieldOut = new javax.swing.JTextField();

        jButton1 = new javax.swing.JButton();
        jButton2 = new javax.swing.JButton();
        jButton3 = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Swing Rechner");
        jTextField1.setText("0");
        jTextField1.setColumns(6);
        jTextField2.setText("0");
        jTextField2.setColumns(6);
        jTextFieldOut.setText("0");
        jTextFieldOut.setEditable(false);

        jButton1.setText("XXXX");
        jButton2.setText("YYYY");
        jButton3.setText("ZZZZ");

        JPanel radioPanel = new JPanel(new GridLayout(1, 0));
        radioPanel.add(jButton1);
        radioPanel.add(jButton2);
        radioPanel.add(jButton3);

        jButton1.addActionListener(new ActionListener()
                {
                public void actionPerformed(ActionEvent e) {
                    if(e.getSource() == jButton1)
                        jTextFieldOut.setText(
                                executeOperation1(jTextField1.getText(),
                                jTextField2.getText()));
                    }
                }
          );
          jButton2.addActionListener(new ActionListener()
                {
                public void actionPerformed(ActionEvent e) {
                    if(e.getSource() == jButton2)
                        jTextFieldOut.setText(
                                executeOperation2(jTextField1.getText(),
                                jTextField2.getText()));
                    }
                }
          );
          jButton3.addActionListener(new ActionListener()
                {
                public void actionPerformed(ActionEvent e) {
                    if(e.getSource() == jButton3)
                        jTextFieldOut.setText(
                                executeOperation3(jTextField1.getText(),
                                jTextField2.getText()));
                    }
                }
          );

        this.setBounds(300, 300, 200, 30);
        setMinimumSize(new Dimension(200,30));
        getContentPane().add(jTextField1, BorderLayout.WEST);
        getContentPane().add(jTextField2, BorderLayout.EAST);
        getContentPane().add(radioPanel, BorderLayout.NORTH);
        getContentPane().add(jTextFieldOut, BorderLayout.SOUTH);
        pack();
   }
    public SwingRechner() {
        initComponents();
    }

    public static void main(String[] args) {

        SwingRechner f1 = new SwingRechner();
        f1.setVisible(true);

    }

    public String executeOperation1(String s1,String s2) {
        int op1= Integer.parseInt(s1);
        int op2= Integer.parseInt(s2);
        // Add Application logic here:
        int resultInt = 0;
        return (Integer.toString(resultInt)) ;
    }
    public String executeOperation2(String s1,String s2) {
        int op1= Integer.parseInt(s1);
        int op2= Integer.parseInt(s2);
        // Add Application logic here:
        int resultInt = 1;
        return (Integer.toString(resultInt)) ;
    }
    public String executeOperation3(String s1,String s2) {
        int op1= Integer.parseInt(s1);
        int op2= Integer.parseInt(s2);
        int resultInt = 2;
        return (Integer.toString(resultInt)) ;
    }
}

2. Erzeugen Sie die Javabytecodedatei SwingRechner.class

javac SwingRechner.java

3. Führen sie das Programm aus

 java SwingRechner

Das erscheinende Programmfenster sollte so aussehen

Programmvorlage Gelöste Aufgabe

4. Programmanpassung

Passen Sie das Programm an um einen Rechner für drei Grundrechenarten zu erhalten. Ersetzen sie in SwingRechner.java die folgenden Texte

  • XXXX durch ein Symbol einer Rechenart
  • YYYY durch eine Symbol einer Rechenart
  • ZZZZ durch ein Symbol einer Rechenart

Implementieren Sie die drei entsprechenden Grundrechenarten in den Methoden executeOperation1(), executeOperation2(), executeOperation3().

Ändern Sie hierfür die drei Zuweisungen sinngemäss und ersetzen sie die Zuweisungen von 0, 1, 2 durch den passenden mathematischen Term mit op1 und op2.

...
        int resultInt = 0;
...
        int resultInt = 1;
...
        int resultInt = 2;
...

Das Programm soll anschliesend die Operationen passend zu den Buttonbeschriftungen ausführen

5. Speichern, Übersetzen und Ausführen des Programms

1.7.4 Übersetzen und Ausführen eines Programms mit Konsoleneingaben

Erzeugen Sie eine Datei mit dem entsprechenden Namen. Setzen Sie den unten aufgeführten Quellcode ein. Übersetzen Sie ihn. Führen Sie ihn aus.

import java.util.Scanner;
public class GGT {
public static void main(String[] args) {
Scanner eingabe = new Scanner(System.in);
System.out.print("Geben Sie die erste Zahl ein: ");
int zahl1 = eingabe.nextInt();
System.out.print("Geben Sie die zweite Zahl ein: ");
int zahl2 = eingabe.nextInt();
int ergebnis = ggT(zahl1, zahl2);
System.out.println("Ergebnis: " + ergebnis);
}
/**
* Euklidscher Algorithmus als Java-Methode
*/
public static int ggT(int x, int y) {
while (x != y) {
System.out.println("Zwischenbelegung, x= " + x + ", y=" + y);
if (x > y) {
x = x - y;
} else {
y = y - x;
}
}
return x;
}
}

 1.7.8 Airline in Block 1

Warnung: Alle verwendeten Namen in Java sind sensitiv auf Klein- und Großschreibung! Dies bedeutet, dass für Java ein Name der wie in der deutschen Sprache mit einem Großbuchstaben beginnt, etwas anderes ist als ein Name mit der mit einem Kleinbuchstaben beginnt.

Legen Sie ein Projekt "javakurs1" für die erste Vorlesung an.

Benutzen Sie in Netbeans den Menüpunkt File->New Project

Neues Projekt

Sie erhalten dann einen modalen Dialog. Wählen Sie "Java Application" dann den Button "next".

modaler Dialog zur Projekt Erzeugung

Der Name des Projekts ist prinzipiell frei wählbar und hat keinen weiteren Einfluß für die Vorlesung (Sie müssen dieses Projekt nur wieder finden und sie sollten hier keine Klassen benutzen die noch in der Vorlesung kommen werden).

  • Tragen Sie nur im ersten Feld "Project Name" den Wert "javakurs1" ein.
  • Entfernen Sie in der Checkbox "Create Main Class" den Haken.
  • Clicken Sie auf "Finish"

Auswahl des Projektnamens

 

 Empfehlung: Ändern Sie in diesem keines der anderen Felder und einen wirklich guten Grund

2. Anlegen eines Pakets "Airline"

Pakete dienen zum Trennen und Gruppieren von Klassen in Java.

Bewegen Sie die Maus über das Projektsymbol (Kaffeetasse) mit dem Namen "javakurs1" und lassen Sie sich mit einem rechte Mausklick mit Halten das folgende Auswahlmenü anzeigen.

Wählen Sie hier "New"->"Java Package" aus:

Anlegen eines neuen Pakets

Es erscheint der folgende modale Dialog:

Package erzeugen

 Tragen Sie bei "Package Name" den Wert "Airline" ein un beenden Sie den Dialog mit dem "Finish" Button.

3. Erzeugen eines Unterpakets "Block1" im Paket "Airline"

In jeder Vorlesungsstunde(Block) wird in der Regel ein neues Paket verwenden.

Diese Aufgabe müssen Sie in jeder Vorlesungsstunde sinngemäß wiederholen.

In der linken Spalte kann man das Projekt "javakurs1" sehen. Diese Projekt hat eine Unterkategorie "Source packages". Hier sollte es jetzt einen Eintrag für ein Paket mit dem Namen "Airline" geben.

Man kann Unterpakte erzeugen, in dem man die Maus auf das Symbol von "Airline" bewegt. Führen Sie auf diesem Symbol einen Rechtsklick aus und halten Sie ihn.

Wählen Sie hier, wie zuvor "New"->"Java Package" aus

Auswahl eines Javapakets

Es erscheint ein modaler Dialog. Tragen Sie hier im Feld "Package Name" den Wert "Airline.Block1" ein.

Bennen des Pakets

Beenden Sie den Dialog mit "Finish"

Wichtig: Hier wird einer Unterpaket zum Paket Airline angelegt. Der Punkt zwischen "Airline" und "Block1" trennt die beiden Namen. Er ist notwendig. Hier darf kein anderes Zeichen (z.Bsp. Leerzeichen stehen).

 4. Anlegen der Klasse Flugzeug im Paket Airline.Block1

Gehen Sie link im Editor mit der Maus zum Paket "Airline.Block". Führen Sie einen Rechstklick aus und halten Sie ihn.

Anlegen einer Klasse

Es erscheint ein modaler Dialog.

Tragen Sie im Feld "Class Name" Flugzeug ein.

Ändern Sie die anderen Felder nicht.

Beenden Sie den Dialog mit dem Button "Finish"

Eintragen des Klassennamen

Sie haben jetzt eine leer Klasse Flugzeug im Paket Airline.Block1 angelegt.

Diese Klassen können Sie nun editieren. Der Editor sieht wie folgt aus:

leere Klasse Flugzeug

5. Erweitern der Klasse Flugzeug

Geben Sie Flugzeugen die Eigenschaft ein Kennzeichen zu haben. Kennzeichen sollen beliebige Zeichenketten sein. Zeichenketten sind in Java Klassen mit dem Namen String.

public class Flugzeug {
   String kennzeichen;

}

Sichern Sie diese Datei. Sie finden schon raus wie das geht :-)

Wenn die Syntax der Klasse korrekt ist, werden am rechten Rand keine roten Symbole erscheinen.

Hinweis: Sie können Javacode der Vorlesung mit Copy-und-Paste direkt in Eclipse oder Netbeans einfügen. Unter Windows kommt es vor, dass die Zeilenumbrüche von html verloren gehen. Nutzen Sie in diesem Fall die Windows Anwendungen Notepad oder Wordpad als "Zwischenlandeplatz".

6. Anlegen der Klasse Flughafen

Nutzen Sie Ihre neuen Kenntnisse und Erzeugen Sie eine Klasse Flughafen im Paket Airline.Block1.

7. Erweitern der Klasse Flughafen

Flughäfen sollen die folgenden Eigenschaften haben

  • einen Namen name der mit Zeichenketten verwaltet wird
  • Sechs Flugsteige gate1 bis gate6 an denen Flugzeuge andocken können
  • ein Treibstofflage treibstofflager in dem mit einer großen Fließkommazahl (double) der gelagerte Treibstoff verwaltet

Dies geschieht wenn Sie die Klasse Flugzeug mit dem fett gedruckten Text erweitern:

public class Flughafen {
    String name;     Flugzeug gate1;     Flugzeug gate2;     Flugzeug gate3;     Flugzeug gate4;     Flugzeug gate5;     Flugzeug gate6;     double treibstoffLager;
}

Es sollten nach Ihren Anpasssungen, links kein Fehler aufleuchten!

8. Schreiben eines Hauptprogramms in der Klasse Flughafen

Fügen Sie eine leer Methode mit dem Namen main() ein. Die Syntax muss exakt so aussehen wie im fett gedruckten Text

public class Flughafen {
   String name;
   Flugzeug gate1;
   Flugzeug gate2;
   Flugzeug gate3;
   Flugzeug gate4;
   Flugzeug gate5;
   Flugzeug gate6;
   double treibstoffLager;

   public static void main(String[] args) { 
   }
}

Nach dem Sichern der Datei Flughafen hat sich die Ikone in der linken Spalte verändert. Sie enthält jetzt eine grünes Dreieck.

Klasse mit main Methode in Netbeans Das grüne Dreick hat die Bedeutung der bekannten Starttaste. Diese Klasse kann man als Hauptprogramm ausführen!
Starten einer Klasse Diese Klasse können Sie ausführen indem Sie die Maus über das Symbol der Klasse bewegen
Konsolenausgabe Unten auf dem Bildschirm taucht eine Konsole auf. Es werden zunächst alle geänderten Klassen übersetzt und dann die gewünschte Klasse gestartet. Da die main() Methode der Klasse Flugzeug aber nichts macht, gibt es auch eine Ausgaben.

 Fügen Sie den folgenden Code innerhalb der geschwiften Klammern ein, die die Methode main() begrenzen.

public static void main(String[] args) {
   Flughafen pad = new Flughafen();
pad.name="Paderborn";
pad.treibstoffLager = 1000000;

System.out.println("*** Unser Flughafen ***");
System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate1);
System.out.println("Am Gate 2: " + pad.gate2);
System.out.println("Am Gate 3: " + pad.gate3);
System.out.println("Treibstoff: " + pad.treibstoffLager);
System.out.println("***********************");

}

Sichern Sie die Datei und starten Sie die Klasse Flughafen erneut an.

Sie sollten die folgende Konsolenausgabe sehen:

Konsolenausgabe

Was ist hier passiert?

Flughafen pad = new Flughafen();

 Eine Zeigervariable pad vom Typ Flughafen wird angelegt. Es wird ein neues Flughafenobjekt erzeugt und auf die Variable zugewiesen.

pad.name="Paderborn";

 Das Objekt pad bekommt seinen Namen "Paderborn" zugewiesen.

pad.treibstoffLager = 1000000;

 Das Treibstofflager des Objekt pad bekommt den Wert 1000000 zugewiesen.

System.out.println("*** Unser Flughafen ***");

 Die Methode System.out.println() druckt Zeichenketten auf der Konsole.
Drucke: *** Unser Flughafen ***

 

System.out.println("Flughafen " + pad.name);

Drucke den Namen des Objekt pad 

System.out.println("Am Gate 1: " + pad.gate1);

Drucke die Belegung von gate1

System.out.println("Am Gate 2: " + pad.gate2);

Drucke die Belegung von gate2 

 System.out.println("Am Gate 3: " + pad.gate3);

Drucke die Belegung von gate3

 System.out.println("Treibstoff: " + 
             pad.treibstoffLager);

Drucke die Treibstoffmenge

 

 System.out.println("***********************");

Drucke Sternchen...

9. Erzeugen eines Flugzeugs und Anlegen am Gate1 

Implementieren Sie die folgenden Befehle am Ende der main() Methode.

  • Legen Sie eine Zeigervariable vom Typ Flugzeug mit dem Namen lh1 and und Erzeugen Sie ein Flugzeugobjekt
  • Geben Sie dem Flugzeug das Kennzeichen D-ABTL
  • "Docken" Sie das Flugzeug am Gate 1 des Flughafens pad an.
  • Geben Sie den Namen des Flugzeugs an Gate aus
  • Wiederholen Sie all Druckbefehlee des vorhergenden Schritts.

Hierzu muss die folgende Codesequenz in der main() Methode vor der letzten geschwiften Klammer eingefügt werden:

        // Boeing 747, https://de.wikipedia.org/wiki/Boeing_747#747-400
Flugzeug lh1 = new Flugzeug();
lh1.kennzeichen ="D-ABTL";

pad.gate1 = lh1;

System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate1.kennzeichen); System.out.println("*** Unser Flughafen ***"); System.out.println("Flughafen " + pad.name); System.out.println("Am Gate 1: " + pad.gate1); System.out.println("Am Gate 2: " + pad.gate2); System.out.println("Am Gate 3: " + pad.gate3); System.out.println("Treibstoff: " + pad.treibstoffLager); System.out.println("***********************");

Sichern Sie die Datei. Übersetzen Sie Datei und führen Sie sie wieder aus.

Die Ausgabe auf der Konsole ist:

*** Unser Flughafen ***
Flughafen Paderborn
Am Gate 1: null
Am Gate 2: null
Am Gate 3: null
Treibstoff: 1000000.0
***********************
Flughafen Paderborn
Am Gate 1: D-ABTL
*** Unser Flughafen ***
Flughafen Paderborn
Am Gate 1: AirlineSolution.Block1.Flugzeug@677327b6
Am Gate 2: null
Am Gate 3: null
Treibstoff: 1000000.0
***********************

10. Anlegen eines zweiten Flugzeugs

Fügen Sie in der main() Methode nach dem Anlegen des ersten Flugzeugs die folgenden Befehle ein:

  • Legen Sie eine Variable lh2 für ein zweites Flugzeug und erzeugen Sie ein Flugzeugobjekt wie beim ersten Flugzeug
  • Das Kennzeichen des Flugzeugs soll D-AIMA sein
  • Legen Sie das Flugzeug an Gate 2 des Flughafens pad 

Fügen Sie hinter den Befehl der das erste Flugzeug an Gate 1 ausgibt einen gleichartigen Befehl für Gate 2

Die main() Methode sieht jetzt wie folgt aus. Die neuen Befehle sind fett gedruckt:

    public static void main(String[] args) {
Flughafen pad = new Flughafen();
pad.name="Paderborn";
pad.treibstoffLager = 1000000;

System.out.println("*** Unser Flughafen ***");
System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate1);
System.out.println("Am Gate 2: " + pad.gate2);
System.out.println("Am Gate 3: " + pad.gate3);
System.out.println("Treibstoff: " + pad.treibstoffLager);
System.out.println("***********************");

// Boeing 747, https://de.wikipedia.org/wiki/Boeing_747#747-400
Flugzeug lh1 = new Flugzeug();
lh1.kennzeichen ="D-ABTL";

pad.gate1 = lh1;

// Airbus A380 https://de.wikipedia.org/wiki/Airbus_A380#A380-800
Flugzeug lh2 = new Flugzeug();
lh2.kennzeichen = "D-AIMA";

pad.gate2 = lh2;


System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate1.kennzeichen);
System.out.println("Am Gate 2: " + pad.gate2.kennzeichen); System.out.println("*** Unser Flughafen ***");
System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate1);
System.out.println("Am Gate 2: " + pad.gate2);
System.out.println("Am Gate 3: " + pad.gate3);
System.out.println("Treibstoff: " + pad.treibstoffLager);
System.out.println("***********************");

}

Sichern Sie die Datei, übersetzen Sie sie, führen Sie die main() Methode der Klasse Flughafen aus.

*** Unser Flughafen ***
Flughafen Paderborn
Am Gate 1: null
Am Gate 2: null
Am Gate 3: null
Treibstoff: 1000000.0
***********************
Flughafen Paderborn
Am Gate 1: D-ABTL
Am Gate 2: D-AIMA
*** Unser Flughafen ***
Flughafen Paderborn
Am Gate 1: AirlineSolution.Block1.Flugzeug@677327b6
Am Gate 2: AirlineSolution.Block1.Flugzeug@14ae5a5
Am Gate 3: null
Treibstoff: 1000000.0
***********************

 

 

1.9 Lösungen

Running Duke

1.8.3 Swing Rechner

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;

public class SwingRechner  extends javax.swing.JFrame {
    private javax.swing.ButtonGroup buttonGroup1;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JTextField jTextField1;
    private javax.swing.JTextField jTextField2;
    private javax.swing.JTextField jTextFieldOut;
    private javax.swing.JButton jButton1;
    private javax.swing.JButton jButton2;
    private javax.swing.JButton jButton3;


    private void initComponents() {
        jTextField1 = new javax.swing.JTextField();
        jTextField2 = new javax.swing.JTextField();
        jTextFieldOut = new javax.swing.JTextField();

        jButton1 = new javax.swing.JButton();
        jButton2 = new javax.swing.JButton();
        jButton3 = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Swing Rechner");
        jTextField1.setText("0");
        jTextField1.setColumns(6);
        jTextField2.setText("0");
        jTextField2.setColumns(6);
        jTextFieldOut.setText("0");
        jTextFieldOut.setEditable(false);

        jButton1.setText("+");
        jButton2.setText("-");
        jButton3.setText("*");

        JPanel radioPanel = new JPanel(new GridLayout(1, 0));
        radioPanel.add(jButton1);
        radioPanel.add(jButton2);
        radioPanel.add(jButton3);

        jButton1.addActionListener(new ActionListener()
                {
                public void actionPerformed(ActionEvent e) {
                    if(e.getSource() == jButton1)
                        jTextFieldOut.setText(
                                executeOperation1(jTextField1.getText(),
                                jTextField2.getText()));
                    }
                }
          );
          jButton2.addActionListener(new ActionListener()
                {
                public void actionPerformed(ActionEvent e) {
                    if(e.getSource() == jButton2)
                        jTextFieldOut.setText(
                                executeOperation2(jTextField1.getText(),
                                jTextField2.getText()));
                    }
                }
          );
          jButton3.addActionListener(new ActionListener()
                {
                public void actionPerformed(ActionEvent e) {
                    if(e.getSource() == jButton3)
                        jTextFieldOut.setText(
                                executeOperation3(jTextField1.getText(),
                                jTextField2.getText()));
                    }
                }
          );

        this.setBounds(300, 300, 200, 30);
        setMinimumSize(new Dimension(200,30));
        getContentPane().add(jTextField1, BorderLayout.WEST);
        getContentPane().add(jTextField2, BorderLayout.EAST);
        getContentPane().add(radioPanel, BorderLayout.NORTH);
        getContentPane().add(jTextFieldOut, BorderLayout.SOUTH);
        pack();
   }
    public SwingRechner() {
        initComponents();
    }

    public static void main(String[] args) {

        SwingRechner f1 = new SwingRechner();
        f1.setVisible(true);

    }

    public String executeOperation1(String s1,String s2) {
        int op1= Integer.parseInt(s1);
        int op2= Integer.parseInt(s2);
        // Add Application logic here:
        int resultInt = op1+op2;
        return (Integer.toString(resultInt)) ;
    }
    public String executeOperation2(String s1,String s2) {
        int op1= Integer.parseInt(s1);
        int op2= Integer.parseInt(s2);
        // Add Application logic here:
        int resultInt = op1-op2;
        return (Integer.toString(resultInt)) ;
    }
    public String executeOperation3(String s1,String s2) {
        int op1= Integer.parseInt(s1);
        int op2= Integer.parseInt(s2);
        int resultInt = op1*op2;
        return (Integer.toString(resultInt)) ;
    }
}

Beispiel

 

1.7.8 Airline Block1

Klasse Flugzeug

package AirlineSolution.Block1;

/**
*
* @author stsch
*/
public class Flugzeug {
String kennzeichen;

}

Klasse Flughafen

/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package AirlineSolution.Block1;

/**
*
* @author stsch
*/
public class Flughafen {
String name;
Flugzeug gate1;
Flugzeug gate2;
Flugzeug gate3;
Flugzeug gate4;
Flugzeug gate5;
Flugzeug gate6;
double treibstoffLager;

public static void main(String[] args) {
Flughafen pad = new Flughafen();
pad.name="Paderborn";
pad.treibstoffLager = 1000000;

System.out.println("*** Unser Flughafen ***");
System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate1);
System.out.println("Am Gate 2: " + pad.gate2);
System.out.println("Am Gate 3: " + pad.gate3);
System.out.println("Treibstoff: " + pad.treibstoffLager);
System.out.println("***********************");

// Boeing 747, https://de.wikipedia.org/wiki/Boeing_747#747-400
Flugzeug lh1 = new Flugzeug();
lh1.kennzeichen ="D-ABTL";

pad.gate1 = lh1;

// Airbus A380 https://de.wikipedia.org/wiki/Airbus_A380#A380-800
Flugzeug lh2 = new Flugzeug();
lh2.kennzeichen = "D-AIMA";

pad.gate2 = lh2;

System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate1.kennzeichen);
System.out.println("Am Gate 2: " + pad.gate2.kennzeichen);

System.out.println("*** Unser Flughafen ***");
System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate1);
System.out.println("Am Gate 2: " + pad.gate2);
System.out.println("Am Gate 3: " + pad.gate3);
System.out.println("Treibstoff: " + pad.treibstoffLager);
System.out.println("***********************");

}
}

 

1.10 Fragen

Duke grübelt

1.10.1 Javakommandos des JDK

  • Mit welchem Befehl übersetzt man ein Javaquellprogramm in eine Javabinärdatei?
    • Welche Datei ensteht beim Übersetzen einer Datei mit dem Namen Fahrzeug.java die eine Klasse Fahrzeug enhält?
  • Mit welchem Befehl kann man Javabinärdateien ausführen?
  • Eine Datei Test.java enthält eine Klasse Test. Mit welcher Befehlsequenz übersetzen Sie die Javaquelldatei Test.java und führen die Klasse Test dann aus?
  • Nennen Sie mindestens drei Javabefehle die nur das JDK enthält nicht aber das JRE
  • Welcher Typ von Dateien wird vom Befehl javadoc erzeugt?

1.10.2 Zuweisungen

  • Welche vier Syntaxkomponenten enhält eine Anweisung?
  • In welcher Reihenfolge stehen die vier Syntaxkomponenten
  • Welche der folgenden Anweisungen sind erlaubt?
a  = 4;
a  = a;
a  = b
18 = c
c = 18 + a;
d = 18 - c;
a,b = 18;

1.10.3 Zuweisungsbeispiel

Welche Belegung haben die Variablen x,y und z wenn alle Zuweisungen ausgeführt?

Notieren Sie zur Hilfe alle Zwischenzustände

Prozessorschritte x y z

x = 2; y = 3; z = 4;
x = 5;
x = 2 * x;
y= z * x;
z= 18;

2

 

3

 

4

Endergebnis ? ? ?

1.10.4 Welche C-Funktion muss implementiert werden?

Ein chaotischer Entwickler sendet Ihnen die Binärdatei Raetsel.class und bittet Sie die notwendige native C Methode zu implementieren?

Ansonsten gibt er Ihnen leider keine zusaätzlichen Informationen...

  • Welcher Methoden und Attribute hat die Klasse Raetsel? Welches Werkzeug hilft Ihnen hier?
  • Wie heißt die C-Funktion die Sie implementieren sollen? Welches Werkzeug hilft Ihnen hier?

1.11 Antworten

1.11.1 Javakommandos des JDK

  • Mit welchem Befehl übersetzt man ein Javaquellprogramm in eine Javabinärdatei? javac
    • Welche Datei ensteht beim Übersetzen einer Datei mit dem Namen Fahrzeug.java die eine Klasse Fahrzeug enhält? Fahrzeug.class
  • Mit welchem Befehl kann man Javabinärdateien ausführen? java
  • Eine Datei Test.java enthält eine Klasse Test. Mit welcher Befehlsequenz übersetzen Sie die Javaquelldatei Test.java und führen die Klasse Test dann aus?
    • javac Test.java
    • java Test
  • Nennen Sie mindestens drei Javabefehle die nur das JDK enthält nicht aber das JRE
    • javac
    • javadoc
    • javap
    • javah
  • Welcher Typ von Dateien wird vom Befehl javadoc erzeugt?
    • javadoc erzeugt Dokumentationsdateien im html Format. Die Dateiendung ist daher .html

1.11.2 Zuweisungen

  • Welche vier Syntaxkomponenten enhält eine Anweisung?
    1. Variable
    2. Zuweisungsoperator =
    3. Term
    4. Semikolon
  • In welcher Reihenfolge stehen die vier Syntaxkomponenten?
    • siehe vorherige Antwort
  • Welche der folgenden Anweisungen sind erlaubt?
a  = 4;     // korrekt
a  = a;     // korrekt
a  = b      // falsch. Das Semikolon fehlt
18 = c;     // falsch. 18 ist kein gültiger Bezeichner. c = 18; wäre erlaubt (und sinnvoll)
c = 18 + a; // korrekt
d = 18 - c; // korrekt
a,b = 18;   // falsch. Man kann nur einer Variablen gleichzeitig einen Wert zuweisen

1.11.3 Zuweisungsbeispiel

Welche Belegung haben die Variablen x,y und z wenn alle Zuweisungen ausgeführt?

Hinweis: Es wurden nur geänderte Werte eingetragen. Ist ein Feld leer, so gilt der früherer Wert weiter oben

Prozessorschritte x y z

x = 2; y = 3; z = 4;

2

3

4

x = 5;

5

   

x = 2 * x;

10    

y= z * x;

 

40

 

z= 18

 

 

18

Endergebnis 10 40 18

 

1.11.4 Welche C-Funktion muss implementiert werden?

Laden Sie die Binärdatei auf Ihren Rechner.

Der folgende Befehl disassembliert die Klasse und erzeugt eine Klassenbeschreibung mit allen öffentlichen Komponenten der Klasse.

Rufen Sie javap in dem Verzeichniss aus in dem die Datei Raetsel.class steht:

pegasus:classes sschneid$ javap Raetsel
Compiled from "Raetsel.java"
public class Raetsel extends java.lang.Object{
public int variable1;
public float variable2;
public java.lang.String variable3;
public Raetsel();
public void testMethode();
public native void methodeInC();
}

Der notwendige C-Prototyp Raetsel.h wird mit dem Kommando javah erzeugt. Man muss das Kommando im Verzeichnis aufrufen in dem die Datei Raetsel.class steht:

javah Raetsel

Hierdurch wird eine Datei Raetsel.h erzeugt:

* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Raetsel */

#ifndef _Included_Raetsel
#define _Included_Raetsel
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Raetsel
* Method: methodeInC
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Raetsel_methodeInC
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

Der Name der zu implementierenden Funktion ist fettgedruckt. Alle anderen Zeilen bestehen aus Direktiven für die Programmiersprachen C, C++ und aus Kommentaren.

 

1.12 Lernziele

Am Ende dieses Blocks können Sie:

  • ... die wichigsten Eigenschaften eines Algorithmus erklären
  • ... die wichtigsten Programme des JRE und JDKs benutzen
    • Sie können erklären welche Typen von Dateien von den Programmen des JDKs gelesen und erzeugt werden.
  • ... ein einfaches Javaprogramm editieren, übersetzen und ausführen lassen.
  • ... die Unterschiede zwischen den Javaeditionen JME, JSE, JEE nennen.
  • ... das Konzept einer Variablen erklären und anwenden.
  • ... die Syntax zur Zuweisung eines Wertes auf eine Variable erklären.
  • ... bei nacheinander erfolgenden Zuweisungen zu verstehen welche Variable welchen Wert zu einem bestimmten Zeitpunkt hat.
  • ... zwischen Klassen und Objekten unterscheiden und deren Beziehung erklären.

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten:

2. Typen, Operationen und Zuweisungen

Dieser Unterrichtsblock beschäftigt sich mit den Grundlagen von Variablen und ihren Belegungen. Am Ende dieses Blocks haben Sie ein Grundverständnis von

2.1 Schlüsselwörter, Literale, Variablennamen, Kommentare

Schlüsselwörter

Java Schlüsselwörter sind:

abstract continue for new switch
assert default if package synchronized
boolean do goto private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const float native super while

 

Die Schlüsselworte in Kategorien gruppiert ergeben:

Diagramm Schlüsselworte

 

The Schlüsselworte "const" und "goto" sind reserviert auch wenn sie im Moment nicht benutzt werden.

"true" und "false" sind technisch gesehen boolsche Literale. (Java 7 Spez. §3.10.3). Ähnliches gilt für das "null" Literal (Java 7 Spez. §3.10.7).

Namen (Identifier)

Siehe Java Spezifikation: Identifier

Regeln für Namen in Java:

  1. Gültige Namen dürfen nur aus Buchstaben, Ziffern, und den Zeichen "_" und "$" bestehen. Sie dürfen beliebig lang sein.
  2. Das erste Zeichen muss ein Buchstabe, "_" oder  "$" Zeichen sein.
  3. Leerzeichen sind nicht erlaubt
  4. Schlüsselworte der Sprache Java dürfen nicht verwendet werden
  5. Groß- und Kleinschreibung von Buchstaben wird unterschieden

Literale

Literale dienen zum Beschreiben konkreter Werte in der Sprache Java.

Die Syntax wurde mit Hilfe von regulären Ausdrücken beschrieben.

Literale
Syntax Typ Beispiel
number int 17, -17
number[l|L] long 24l, -24L
0[x|X]hex int in Hexadezimaldarstellung 0x01234567890ABCDEF
0octal int in Oktaldarstellung 071
0b{0|1} int in Binärdarstellung (erst seit Java 7, JSR 334) 0b101010101010
[+|-][number].number double (Fließkommazahl) -3.1415
[+|-]number[f|F] float (Fließkommazahl) -3.1415F
[+|-]number[d|D] double (Fließkommazahl) -3.1415E13D
[+|-]number int mit Vorzeichen (signed number) -3, +3
[+|-]number.number[e|E][+|-]number Exponentendarstellung -3.1415E10
'character' einzelnes Zeichen 'a'
"characters" Zeichenkette "aB10"
"" Leere Zeichenkette ""
\b Zeichenposition 1 nach links (back space)  
\t Tabulator  
\n Zeilenvorschub  
\f Seitenvorschub  
\r Wagenrücklauf  
\" Doppeltes Anführungszeichen  
\' Einfaches Anführungszeichen  
\\ Schrägstrich rückwärts  
\uNNNN Unicodezeichen (NNNN in hexadezimal)  
true Boolscher Wert true
false Boolscher Wert false

 

Zahlen

  • Ganzzahlen
    • Dezimalzahlen mit den Ziffern 0 bis 9 und Vorzeichen
      • Beispiele: 1, -19, 45678
    • Hexadezimalen mit den Ziffern 0 bis 9 und als Ziffer interpretierte Zeichen a bis f und einer Präfix 0x
      • Beispiele: 0x1, 0xffff, 0xcafebabe
    • Oktalzahlen mit den Ziffern 0 bis 7 und einer vorgestellten 0
      • Beispiel 012 (=1*81+2*80=1010 ,  077 (=7*81+7*80=6310)
  • Gleitkommazahlen,
    • dargestellt im vertrauten Dezimalsystem
      • Nachkommateil durch Punkt abgetrennt
        • Beispiel: 5.4, 6. (stellt Gleitkommazahl 6.0 dar)
    • Zehnerpotenzen zur Basis Zehn werden mit dem Buchstaben e oder E bezeichnet
      • Beispiel: 4E-2 stellt 4*10-2= 0.04 da.

Ab JDK 7: Gruppierung in Zahlenliteralen

Seit JDK 7 sind durch die Integration des Projekts "Coin" (JSR 334) die Gruppierung von Zifferngruppen in Zahlenliteralen durch den Tiefstrich '_' möglich.

Der JSR Spezifikation entnommene Beispiele der neuen Syntax:

1234_5678
1_2_3_4__5_6_7_8L
0b0001_0010_0100_1000
3.141_592_653_589_793d
0x1.ffff_ffff_ffff_fP1_023 // Double.MAX_VALUE

nicht erlaubt sind:

_1234
0x_1234
1234_
0x1.0_p_-1022

Warnung: Java 7 wird noch nicht überall eingesetzt:

  • Bei Verwendung von javac Javaübersetzern von JDK 5 oder 6 wird diese Syntax nicht akzeptiert
  • Bei Verwendung des Javaübersetzers mit Optionen zur Verwendung alter Syntaxstandards wie javac -source 1.6 in JDK 7 wird die neue Syntax auch nicht akzeptiert werden!

Zeichen und Zeichenketten

  • Zeichen: Ein einzelnes Zeichen (char) wird in einfachen Hochkommas geschrieben
    • Beispiele: 'a', '2', '!', ' '
  • Zeichenketten: in Doppelhochkamma eingeschlossene Folgen von Zeichen
    • Beispiele: "Das ist eine Zeichenkette", 
    • Tipp: Zeichenketten dürfen nicht über den Zeilenrand hinwegreichen!

Zeichenketten ohne Namen nennt man Literale.

Kommentare

Kommentare erlauben das Dokumentieren eines Programmes. Sie werden vom Übersetzer ignoriert.

Java benutzt das Kommentarkonzept auch zur Generierung von Dokumentation. Dokumentationskommentare sind eine Sonderform der Kommentare und werden im Abschnitt zur Dokumentation vorgestellt.

Zeilenkommentare, Zeilenendkommentare

Zeilenkommentare beginnen nach dem doppelten Schrägstrich //. Der Javaübersetzer wird alle Zeichen hinter diesem Kommentarzeichen bis zum Ende der Zeile ignorieren.

Beispiel:

int a = 17; // Dies ist ein Zeilenendkommentar
// Dieser Kommentar umfasst eine ganze Zeile
int b = 18;

Mehrzeilige Kommentare

Java erlaubt es eine ganze Reihe von Zeilen als Kommentar zu kennzeichnen. Mehrzeilige Kommentare werden mit den Zeichen "/*" (Schrägstrich Stern) eingeleitet und mit der Zeichenkombination "*/" (Stern Schrägstrich) beendet. Hiermit kann man ganze Bereiche als Kommentar markieren.

Beispiel:

/* Hier beginnt ein Kommentar
diese Zeile gehört zum Kommentar
int i=1; diese Zeile wird nicht als Befehl sondern als Kommentar verarbeitet
der Kommentar endet in der nächsten Zeile
*/

 

2.2 Datentypen und Wertebereiche

Die Typen die ein Entwickler in Java vewendet sind entweder primitive Datentypen oder komplexe Datentypen. Die beiden Kategorien unterscheiden sich in den folgenden Eigenschaften:

  Primitiver Datentyp Komplexer Datentyp (Javaklassen)
Operatoren viele hochoptimierte Operationen Nur Operatoren für Objekte (Vergleich, Referenzierung). Ansonsten Methoden der Klasse
Lebensdauer hängt vom umgebenden Block, bzw. Objekt ab liegt im Freispeicher. Lebensdauer hängt von der letzten Referenz auf Objekt ab
Speicherverbrauch konstant meistens variabel
Speicherort im Prozedurblock oder in einer Klasse immer im Freispeicher (Heap)
Syntax immer klein geschrieben Systemklassen beginnen immer mit Großbuchstaben (Es ist guter Stil auch Benutzerklassen mit einem Großbuchstaben beginnen zu lassen).

Im nachfolgenden Diagramm wird die Klassifikation der wichtigsten Typen gezeigt:

Klassifikation der Javatypen

Die nächsten beiden Abschnitte behandeln die hier gezeigten Typen. Der dritte Abschnitt beschäftigt sich mit den Risiken von Zuweisungen zwischen Datentypen mit unterschiedlichen Wertebereichen.

2.2.1 Primitive Javatypen

Ganzzahlige Datentypen

Ganzzahlige Datentypen
Datentyp Bits Wertebereich Wertebereiche Konstante min. Konstante max.
byte 8=1 byte -27 bis 27-1 -128 bis +127

Byte.MIN_VALUE

Byte.MAX_VALUE

short 16=2 byte -215 bis 215-1 -32768 bis +32767

Short.MIN_VALUE

Short.MAX_VALUE

char 16=2 byte 0 bis 216-1 0 bis +65535 (Sonderfall!)

Character.MIN_VALUE

Character.MAX_VALUE

int 32=4 byte -231bis 231-1 -2147483648 bis +2147483647

Integer.MIN_VALUE

Integer.MAX_VALUE

long 64=8 byte -263 bis 263-1 −9,223,372,036,854,775,808 to +9,223,372,036,854,775,807

Long.MIN_VALUE

Long.MAX_VALUE

Die maximalen und minimalen Werte der Ganzzahltypen ergeben sich aus der Anzahl der Bits und der internen binären Präsentation. Dies ist am Beispiel des 8-Bit Typen Byte aufgeführt:

Java verwendet für die Darstellung negativer Ganzzahlen das Zweierkomplement. Der Vorteil des Zweierkomplement besteht in der einfacheren Implementierung von Arithmetikeinheiten in Prozessoren. Durch das Zweierkomplement kann das Vorzeichenbit bei Additionen und Subtraktion wie ein regulärers Bit des Wertebereichs behandelt werden.

Dies kann man gut am Beispiel der Zahl -1 erkennen. Addiert man zur Zahl -1 (Binärdarstellung 11111111) eine 1 (Binärdarstellung 00000001) so ergibt durch den Übertrag eine 0 (Binärdarstellung 00000000).

Die Anwendung zur Rechten ist in der Lage alle Ganzzahltypen in die Binärdarstellung umzuwandeln.

Das Vorzeichenbit wird in Rot dargestellt. Bei allen Typen die mehr als 16 Bit erfordern wird die Ausgabe nach 16 Bit umgebrochen. Das Bit mit der höchsten Ordnung wird zuerst ausgegeben. Das Bit mit der niedrigsten Ordnung wird am Ende ausgegeben.

Hinweis

Der Typ char ist ein Zahlentyp. Man muss jedoch genau ein beliebiges Zeichen (Buchstabe) im Eingabefenster eingeben. Der Typ char unterscheidet sich vom Typ short in der Benutzereingabe und im Wertebereich.

 Zum Runterladen: BinaerIntApplet_0.jar

Starten Sie die Anwendung mit einem Doppelclick im Download Ordner oder öffen ein Terminal und führen den folgenden Befehl im Download Ordner aus:

java -jar BinaerIntApplet_0.jar

Es sollte ein Fenster erscheinen, dass ähnlich dem folgenden Fenster aussieht:

Screen shot BinaerIntApplet 

Quellcode des Applets und Anleitung zum Starten als Javaprogramm von der Konsole.

Fließkomma-Standarddatentypen

Die Zahlendarstellung der Fließkommazahlen geschieht nach der Norm IEEE 754 getrennt nach Bits für Vorzeichen (V), Mantisse und Exponent mit unterschiedlicherAnzahl von Bits abhängig vom Typ nach der Regel:

z= (-1)V*Mantisse*2Exponent

Fließkomma-Standarddatentypen
Datentyp Bits V
(bits)
Mantisse
(bits)
Exponent
(bits)
Zahlenbereich Dezimalstellen in Mantisse
float 32=4 Byte 1 23 8 ≈-3.4*1038 bis +3,4*1038 7
double 64=8 Byte 1 52 11 ≈-1.7*10308 bis +1.7*10308 15

Die Minimal- und Maximalwerte als Konstanten können über die Attribute MIN_VALUE und MAX_VALUE der Klassen Float und Double abgerufen werden.

Die Berechnung der dezimalen Werte ist für den menschlichen Betrachter nicht so einfach wie die Umwandlung von Ganzzahlen.

  • Der Exponent ist um 127 verschoben um auch negative Zahlen darstellen zu können.
  • Bei der Mantisse wurde im Diagramm eine symbolische (rote) Eins eingefügt, die nicht Teil der 32 Bit Muster ist. Jede Mantisse muss mit einer führenden 1 beginnen damit sie normalisiert ist. Da dieser Wert immer konstant ist muss man ihn nicht extra im 32 bit Wort speichern.

Der IEEE 754 Standard ist recht anschaulich in Wikipedia beschrieben.

Eine 32 Bit Fließkommazahl berechnet sich nach IEEE 754 wie folgt:

= (-1)Vorzeichen*2(Exponent-127)*Mantisse

Für den 32 Bit Typ float ergibt sich so nach dem Standard IEEE 754 das folgende Bitmuster für verschiedene Werte:

 

Das Applet zur Rechten ist in der Lage 32 Bit Fließkommazahlen in die Binärdarstellung umzuwandeln.

Die Knöpfe auf der rechten Seite erlauben die Eingabe von Extremwerten wie

  • NaN: Not a Number (z,Bsp das Ergebnis der Wurzel von -1
  • positiv, negativ unendlich
  • Größter Wert
  • Kleinster Wert der noch größer als Null ist

 

 

 Zum Runterladen: BinaerFloatApplet_0.jar

Starten Sie die Anwendung mit einem Doppelclick im Download Ordner oder öffen ein Terminal und führen den folgenden Befehl im Download Ordner aus:

java -jar BinaerFloatApplet_0.jar

 Es sollte ein Fenster erscheinen, dass ähnlich dem folgenden Fenster aussieht:

Screenshot BinaerFloatApplet

Quellcode des Applets und Anleitung zum Starten als Javaprogramm von der Konsole

Wahrheitswerte

Wahrheitswerte
Datentyp Bits Wertebereich Werte Konstante
boolean 8 wahr oder falsch true,false Boolean.FALSE, Boolean.TRUE

Zeichen

Java behandelt einzelne Zeichen intern als ganze Zahlen. Man kann auf den Typ char alle Operationen anwenden die auch für Zahlen erlaubt sind. Der wesentliche Unterschied zum Typ short besteht in der Eingabe und Ausgabe, sowie im Wertebereich. Hier werden lexikalische Zeichen ein- oder ausgegeben. 

Wichtiger Sonderfall: Der Typ char benutzt 16 Bit zum kodieren wie auch der Typ short. Die Wertebereiche unterscheiden sich jedoch. Der Typ char kodiert nur positive Werte. Im Englischen wird ein solcher Typ "unsigned" gennannt. Es ist ein Typ ohne Vorzeichenbit.

Zeichen
Datentyp Bits Wertebereich Werte  Kommentar 
char 16 UC='\u0000' bis '\uffff' 16 Bit Unicode 4.0 Basic Multilingual Plane (BMP)   "supplementary character" Unterstützung seit JDK 1.5

 

2.2.2 Komplexe Javatypen

Es gibt eine Reihe von oft benutzten Datentypen die jedoch nicht primitiv sind.

Diese Datentypen werden mit Hilfe von Javaklassen implementiert. Die so erzeugten Datenstrukturen sind Javaobjekte und haben einen anderen Lebenszyklus als primitive Typen die in einer Javaklasse oder einem Programmierblock benutzt werden.

Komplexe Datentypen (Javaklassen) haben einen variablegroßen Speicherplatzbedarf. Sie werden auf dem Javafreispeicher (Heap) angelegt.

Zeichenketten

Zeichenketten sind in Java kein vordefinierter primitiver Typ. Zeichenketten werden im Paket java.lang mit Hilfe der Klasse String implementiert. Die Klasse String kann jedoch ohne eine spezielle Deklaration wie ein primitiver Typ verwendet werden.

Zeichenketten (Strings) sind in Java nicht modifizierbar. Bei jeder Zuweisung wird eine neue Datenstruktur angelegt. Bei primitiven Typen werden die Werte an der gleichen Stelle überschrieben.

Aufzählungstypen

Aufzählungstypen sind seit JDK 5.0 Bestandteil der Sprache.

Definition
Aufzählungstypen
Aufzählungstypen in Java haben einen Wertebereich der aus einer geordneten Menge von Konstanten besteht. Der aktuelle Wert einer Variablen besteht aus einem der Aufzählungskonstanten

Aufzählungstypen können nur als Bestandteil einer Klasse deklariert werden. Sie können nicht wie die anderen Basistypen innerhalb eines Blocks deklariert werden. Die Syntax einer Deklaration in einer Klasse ist die Folgende:

Syntax eines Aufzählungstyps

Beispiel einer Deklaration in einer Klasse:

class  AufzaehlungsDemo {
   enum Kartenfarbe {KARO, HERZ, PIK, KREUZ}
   enum Wochentag {MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG, FREITAG, SAMSTAG, SONNTAG}
}

Bei der Benutzung von Aufzählungstypen kann nur eine zuvor deklarierte Aufzählungskonstante benutzt werden.

Beispiel der Benutzung in einer Klassenmethode der zugehörigen Klasse:

Wochentag heute = Wochentag.MITTWOCH;
Kartenfarbe dame = Kartenfarbe.HERZ;

Aufzählungstypen wurden wie Referenzvariablen (die erst später vorgestellt werden) implementiert.

Referenzen

Referenztypen erlauben es auf andere Objekte zu referenzieren. Sie werden im Abschnitt 7 behandelt.

Übergroße Zahlen (Big Decimal)

Die Java BigDecimal Klassen erlauben das Rechnen mit einer beliebigen Präzision. BigDecimal Klassen werden im Rahmen dieser Einführung nicht behandelt.

2.2.3 Typkonversion bei Zuweisung verschiedener Typen

Die Sprache Java und ihre (Lautzeitumgebung) sind typsicher. Typsicher bedeutet, dass bei Zuweisungen für Variablen der Typ der Variable und der zugewiesene Typ eines Wertes geprüft werden und nur sichere Zuweisungen erlaubt werden. Der Übersetzer (javac) erkennt dies schon beim Parsen (lesen) des Quellcodes und meldet dies als Fehler.

Beispiel: Der Wertebereich einer Variable vom Type short endet bei 65535. Der Typ int hat jedoch einen Wertebereich der bis 4294967295 reicht. Im folgenden Fall kann es also zu einem Überlauf kommen:

short a;
int b;
....
a = b;

Der Übersetzer wird einen Fehler melden, da es hier zu einem Überlauf/Unterlauf aufgrund der unterschiedlichen Wertebereiche kommen kann.

Die Zuweisungskompatibilität kann man als Teilmengen mit dem folgen Symbol darstellen:  ⊃ . Es ergibt bei Java die folgenden Beziehung:

doublefloatlongintshort/char byte

  • Alle Zuweisungen von bytedouble sind erlaubt.
  • Alle Zuweisung von double ➔ byte werden vom Übersetzer nicht akzeptiert.

Bei Berechnungen vor der Zuweisung kommen eventuell verschieden Typen in Operationen (z.Bsp.) Addition vor.

Hier verfährt Java nach der folgenden Regel:

  • Der jeweils kleinere Operandentyp wird vor der Operation in den größeren konvertiert.
  • Die Operanden werden jedoch zumindest in den Typ int konvertiert
  • Das Ergebnis hat den gleichen Typ wie die beiden Operanden (nach Konvertierung)

Explizite Typkonvertierungen(Casts)

Um Zuweisungen zwischen inkompatiblen Typen zu erzwingen kann man den Zieltyp in runden Klammern dem Ausdruck voran stellen. Diesen Operator nennt man Castoperator (cast im englischen: gießen, betonieren).

Beispiel

short a;
int b;
....
a = (short)b; 

Hiermit erzwingt man Operationen die unsicher sein können! Es kann zu Überläufen in Wertebereichen oder Präzisionsverlusten kommen kann. Der Entwickler übernimmt hier die Verantwortung und überstimmt den Übersetzer. Der Castoperator sollte daher nur wenn nötig eingesetzt werden.

Das Applet zur Rechten ist in der Lage einen beliebigen Ganzzahltypen auf einen beliebigen anderen Ganzzahltypen zu zuweisen.

Die Variable y enthält den Eingabewert. Den Typ von y kann man in der rechten Spalte wählen.

Der Wert von y wird mit Hilfe einer Typkonversion (Cast) auff x zugewiesen. Den Typ von x kann man links wählen.

Nach der Wahl der Typen und des Eingabewerts kann die Zuweisung mit dem "Enter" Button gestartet werden.

Fragen:

  • Was geschieht bei der Typkonversion mit negativen Werten (siehe rotes Vorzeichenbit)?
  • Was geschieht bei der Zuweisung von großen Typen auf kleine Typen? Was geschieht mit den Vorzeichen?
  • Was geschieht wenn Zeichen (Typ char) auf andere Typen zugewiesen werden?

Zum Testen bitte runterladen: BinaerCastApplet.jar

Starten Sie die Anwendung mit einem Doppelclick im Download Ordner oder öffen ein Terminal und führen den folgenden Befehl im Download Ordner aus:

java -jar BinaerCastApplet.jar

Es sollte ein Fenster erscheinen welches wie folgt aussieht: 

Screenshot BinearCastApplet

 

Quellcode des Applets und Anleitung zum Starten als Javaprogramm von der Konsole

2.3 Operatoren und Ausdrücke

Ausdrücke

Ausdrücke in Java sind alles was einen Rückgabewert liefert:

  • Konstanten
  • Variablen
  • Methoden
  • Operatoren

Operatoren

Java verfügt über

  • unäre (einstellige, monadische) Operatoren
  • binäre (zweistellige, dyadische) Operatoren
  • einen dreistelligen (ternären, tryadischen) Operator (den Bedingungsoperator "_ ? _ : _")

 

Unäre Operatoren haben einen einzigen Operanden. Beispiele sind:

  • Vorzeichenoperator: -a
  • Postinkrement: a++
  • Negation: !a
Unärer Operator

 

Binäre Operatoren haben zwei Operanden. Beispiele sind:

  • Addition:  a + b
  • logischer Vergleich: a ==  b
Binaerer Operator

Arithmetische Operatoren

Die arithmetischen Operatoren können auf die folgenden Typen angewendet werden

  • byte
  • short
  • int
  • long
  • float
  • double
Zweistellige arithmetische Operatoren
Operator Beispiel Semantik
+ a + b Addition: Summe von a und b
- a - b Subtraktion: Differenz an a und b
* a * b Multiplikation: Produkt von a und b
/ a /  b Division: Quotient von a und b
% a % b Modulo: Rest einer ganzzahligen Division von a durch b

Die Division von Ganzzahlen ergibt immer ganzzahlige Ergebnisse!

Java arbeitet ohne eine Erkennung des Überlaufs der Wertebereiche. Der Entwickler muss selbst die entsprechenden Vorsichtsmaßnahmen ergreifen.

Weiterhin gibt es einstellige (unäre) arithmetische Operatoren

Unäre arithmetische Operatoren
Operator Beispiel Semantik
+ +a Der Wert von a bleibt erhalten (Idempotente Operation)
- -a Der Wert von a wird negiert
++

a++

++a

Postinkrement: Der Ausdruck behält ursprünglichen Wert . Der Wert von a wurde um 1 erhöht

Präinkrement: Der Wert von a wird um 1 erhöht und der Ausdruck erhält den erhöhten Wert von a

--

x--

--x

Postdekrement: Der Ausdruck behält ursprünglichen Wert . Der Wert von a wurde um 1 erniedrigt

Präidekrement: Der Wert von a wird um 1 erniedrigt und der Ausdruck erhält den verminderten Wert von a

Die folgen drei Anweisungen bewirken das gleiche:

a = a + 1;
a++;
++a;

Bei Inkrementen mit gleichzeitiger Zuweisung ergeben jedoch unterschiedliche Werte für den zugewiesenen Wert

Variante 1 Wert a Wert b Variante 2 Wert a Wert b

a = 10; b = 4;

b = a++;

10

11

4

10 (!)

a = 10; b = 4;

b = ++a;

10

11

4

11 (!)

Beispiel

Quellcode Konsolenausgabe
package block2;
public class Main {
    public static void PrePostFixTest(String[] args) {
        int x = 10;
        int y = 100;
        System.out.println("x = " + x + "; y = " + y);
        x++;
        System.out.println("x++ results in " + x);
        ++x;
        System.out.println("++x results in " + x);
        System.out.println("Set x to 0 ");
        x=0;
        System.out.println("x = " + x + "; y = " + y);
        y=x++;
        System.out.println("y=x++ (Postfix)");
        System.out.println("x = " + x + "; y = " + y);
        y=++x;
        System.out.println("y=++x (Prefix)");
        System.out.println("x = " + x + "; y = " + y);
}
.
.
.
.
.
x = 10; y = 100
.
x++ results in 11
.
++x results in 12
Set x to 0
. 
x = 0; y = 100
.
y=x++ (Postfix)
x = 1; y = 0
.
y=++x (Prefix)
x = 2; y = 2
.

Arithmetik der Ganzzahlen

  • Alle Operationen auf Ganzzahlen ergeben wieder Ganzzahlen. Dies wird nicht unbedingt von der Division erwartet!
  • Versuche durch 0 (Null) zu dividieren lösen eine ArithmeticException Ausnahme aus.

Arithmetik der Fließkommazahlen

Bei der Arithmetik mit Fließkommazahlen werden im Gegensatz zu den Ganzzahlen Überläufe erkannt. Die Fließkommazahlen besitzen eine Reihe Konstanten:

Konstante Semantik
POSITIVE_INFINITY Positiv unendlich
NEGATIVE_INFINITY Negativ unendlich
MAX_VALUE Größter darstellbarer Wert
MIN_VALUE Kleinster darstellbarer Wert
NaN "Not a number" Dieser Wert ist ungleich zu allen anderen Werten im Wertebereich

 

Vergleichsoperatoren

Gleichheit bzw. Ungleichheit bezieht sich auf den Wert der Variablen x und y

Vergleichsoperatoren
Operator Beispiel Semantik (Bedeutung)
== x == y ist x gleich y ?
!= x != y ist x ungleich y ?
< x < y ist x kleiner als y ?
<= x <= y ist x kleiner oder gleich y ?
> x > y ist x größer als y ?
>= x >= y ist x größer oder gleich y ?

 

Logische Operatoren

Die logischen Operatoren wirken auf den Typ Boolean der nur den Wert wahr oder falsch kennt.

Logische Operatoren
Operator Beispiel Semantik (Bedeutung)
! !a Negation
& a & b Und
| a | b Oder (inklusiv)
^ a ^ b Entweder-Oder
&& a && b bedingt auswertendes Und
|| a || b bedingt auswertendes Oder

 

Bedingungsoperator

Der dreistellige (ternäre) Bedingungsoperator (Konditionaloperator) erlaubt eine Zuweisung von dem Ergebnis einer Bedingung abhängig zu machen. Er hat die Form:

<ausdruck1> ? <ausdruck2> : <ausdruck3>

ausdruck1 muss einen boolschen Wert ergeben. Wird ausdruck1 wahr, so wird ausdruck2 der entsprechenden Variable zugewiesen. Wird ausdruck1 unwahr, so wird der ausdruck3 zugewiesen

Hiermit kann man Zuweisungen wie die Folgende formulieren

int maximum;
int x = 1;
int y =2 ;
maximum = (x > y) ? x : y ;

Das Ergebnis ist 2, da y (=2) größer als x (=1) ist.

Bedingt auswertende logische && und || Operatoren

Die bedingt auswertenden Operatoren werten Terme nur soweit aus bis das Endergebnis fest steht. Dies macht sie seht effizient.

Im Beispiel:

boolean a = ((1<3) || (4>5));

wird der Term (4>5) nicht mehr ausgewertet. Da (1<3) wahr ist, steht das Endergebnis schon fest.

Die bedingt auswertenden logischen Operatoren wendet man neben Ihrem Geschwindigkeitsvorteil auch gerne an um potentielle Fehler und Ausnahmen zu vermeiden.

Ein Beispiel hierfür ist:

if ((a>0) && (Math.sqrt(a)>2))

Die Wurzel wird nur ausgewertet wenn a größer als Null ist.

Vorsicht: Durch die bedingte Auswertung können unterschiedliche Ergebnisse enstehen wenn in einem Ausdruck gleichzeitig ein Wert verändert wird!

Beispiel:

  bedingter "Oder" Operator einfacher "Oder" Operator
Quellcode
public static void t1() {
   int a = 3;
   int b = 5;
   if ((a>1) || (a<b++)){
      System.out.println ("Hallo");
   }
   System.out.println("b= " + b);
}
public static void t2() {
   int a = 3;
   int b = 5;
   if ((a>1) | (a<b++)){
      System.out.println ("Hallo");
   }
   System.out.println("b= " + b);
}
Ausgabe
Hallo
b= 5
Hallo

b= 6

Bitoperatoren

Mit Bitoperatoren werden alle Bits einer Variablen einzeln manipuliert.

Bitoperatoren
Operator Beispiel Bedeutung
~ ~a Komplement
& a & b Und
| a | b Oder
^ a ^b exklusives Oder

Beispiel

package block2.skript;
public class BitOperator {
    public static void main(String[] args) {
        int a = 7;
        int b = 6;
        int result;

        result = a & b;
        System.out.println("a = " + a + "; b = " + b + " result = " + result);

        result = a | b;
        System.out.println("a = " + a + "; b = " + b + " result = " + result);

        result = a ^ b;
        System.out.println("a = " + a + "; b = " + b + " result = " + result);
    }
}

Ergebnis

a = 7; b = 6 result = 6
a = 7; b = 6 result = 7
a = 7; b = 6 result = 1

Erklärung

result = a & b
Variable Dezimal Binär
a 7 0 0000000 00000000 00000000 00000111
b 6 0 0000000 00000000 00000000 00000110
result = a & b 6 0 0000000 00000000 00000000 00000110
result = a | b
Variable Dezimal Binär
a 7 0 0000000 00000000 00000000 00000111
b 6 0 0000000 00000000 00000000 00000110
result = a | b 7 0 0000000 00000000 00000000 00000111

Bitschiebeoperatoren 

Bitschiebeoperatoren

Operator

Beispiel Bedeutung
<< a << b Wert des Ausdrucks sind die Bits von a die um b Positionen nach links verschoben wurden. Es wird mit 0 Bits aufgefüllt.
>> a >> b Wert des Ausdrucks sind die Bits von a  die um b Positionen nach rechts verschoben wurden. Es wird mit dem höchsten Bit aufgefüllt.
>>> a >>> b Wert des Ausdrucks sind die Bits von a die um b Positionen nach rechts verschoben wurden. Es wird mit dem "0" Bits aufgefüllt.

Das kleine Programm rechts erlaubt die drei Bitschiebeoperationen zu testen.

Es kann die Bits um jeweils eine Stelle verschieben.

Hier die jar Datei herunterladen.

Das Programm dann mit dem Kommandozeilenbefehl:

java -jar IntShiftApplet.jar

Es erscheint ein Fenster wie folgt:

 Bild des IntShiftApplet

Quellcode des Applets und Anleitung zum Starten als Javaprogramm von der Konsole.

Beispiel

public class ShiftingBits {
    public static void main(String[] args) {
        int x = 4;
        int result;
        int shift = 1;

        result = x << shift;
        System.out.println("x = " + x + "; shift = " + shift + " result = " + result);

        result = x >> shift;
        System.out.println("x = " + x + "; shift = " + shift + " result = " + result);

        result = result >> shift;
        System.out.println("x = " + x + "; shift = " + shift + " result = " + result);

        result = result >> shift;
        System.out.println("x = " + x + "; shift = " + shift + " result = " + result);

        result = result >> shift;
        System.out.println("x = " + x + "; shift = " + shift + " result = " + result);
    }
}

Ergebnis

x = 4; shift = 1 result = 8
x = 4; shift = 1 result = 2
x = 4; shift = 1 result = 1
x = 4; shift = 1 result = 0
x = 4; shift = 1 result = 0

Die interne Darstellung der verwendeten Werte:

Binäre Darstellung
Dezimalwert Binärwert
8 0 0000000 00000000 00000000 00001000
4 0 0000000 00000000 00000000 00000100
2 0 0000000 00000000 00000000 00000010
1 0 0000000 00000000 00000000 00000001
0 0 0000000 00000000 00000000 00000000

 

Zuweisungs- und Verbundoperatoren

Das Gleichzeichen = dient in Java als Zuweisungsoperator. Die Anweisung

x = y + z;

ist nicht als mathematische Gleichung zuverstehen, sondern als Zuweisung des Ausdrucks auf der echten Seite (y+z) auf die Variable x auf der linken Seite.

Zuweisungen wie:

x = y = 8;

sind auch möglich. Sie haben die gleiche Bedeutung wie

y = 8;
x = y;

Für die meisten binären Operatoren gibt es Verbundoperatoren mit denen man einer Variable etwas zuweisen kann und gleichzeitig den alten Wert verwenden kann:

Verbundoperator
Verbundoperator entspricht
a += b a = a + b
a -= b a = a - b
a *= b a = a * b
a /= b a = a / b
a %= b a = a % b
a &= b a = a & b
a |= b a = a | b
a ^= b a = a ^ b
a <<= b a = a << b
a >>= b a = a > b
a >>>= b a = a >>> b

 

Auswertungsreihenfolge

Für Ausdrücke mit mehreren Operatoren gelten die folgenden Regeln in Bezug auf die Reihenfolge der Auswertung:

  1. Teilausdrücke in runden Klammern werden wie in der Mathematik als erstes ausgewertet
  2. Ausdrücke mit unären Operatoren werden anschließend ausgewertet
  3. Zuletzt werden Teilausdrücke mit mehrstelligen Operatoren ausgewertet

Unäre Operatoren haben alle die gleiche Priorität

Ausführungsreihenfolge von Operatoren

Die Ausführungsreihenfolge von Operatoren bestimmt wie ein Term aufgelöst wir.

Tipp: Es ist guter Programmstil Terme übersichtlich zu gestalten. Verwenden Sie im Zweifelsfall Klammern!

Ausführungsreihenfolge von Operatoren
Rang Operator Beschreibung
1 =, +=, -=, *= ... Zuweisungsoperator
2 ?: Bedingungsoperator
3 || Logische Oder
4 && Logisches Und
5 | logisches oder bitweises Oder
6 ^ logisches oder bitweises Entweder-Oder
7 & logisches oder bitweises Und
8 ==, != Vergleichsoperatoren: Gleich, Ungleich
9 <, <=, >, >= Vergleichsoperatoren
10 <<, >>, >>> Schiebeoperatoren
11 +, - Addition, Subtraktion, Verketten von Zeichenketten
12 *, /, % Multiplikation, Division, Rest
13 ++, --, +, -, ~, ! unäre (einstellige) Operatoren

 Auswertung von Operatoren mit gleicher Priorität

Es kann vorkommen, dass ein Ausdruck mehrere Operatoren der gleichen Priorität besitzt. In diesen Fällen wird die Auswertereihenreihenfolge durch die Assoziativität der Operatoren bestimmt.

Definition
Operatorenasoziativität
Die Assoziativität von Operatoren ist die Reihenfolge in der Operanden durch Operatoren gleicher Priorität verknüpft werden

Ist ein Operator linksassoziativ, wird zuerst der linke Operand ausgewertet. Das Beispiel zeigt den Plus- und Minusoperator. Beide haben die gleiche Priorität. Hier wird zuerst der Operand a+b ausgewertet.

linksassoziative Operanden

Einige Operatoren in Java sind rechtsassoziativ. Ein Beispiel hierfür ist der Zuweisungsoperator

Bewertungsreihenfolge der Operanden eines Operators

Definition
Bewertungsreihenfolge der Operanden
In Java werden die Operanden eines Operators strikt von links nach rechts ausgewertet.

Diese Regel ist insbesondere wichtig, da Methoden und diverse Operatoren Nebeneffekte haben können. Das bedeutet, dass diese Operatoren den Wert von Variablen während der Auswertung des Gesamtausdrucks verändern. Beispiele sind die Inkrement- und Dekrementoperatoren.

j = i-- -i;

ist ein zulässiger Ausdruck in Java.  Der Wert der j zugewiesen wird ist immer 1;

Die Auswertung dieser Zuweisung geschieht in den folgenden Schritten:

  1. Auswertung des Subtrahenden (und Zwischenspeicherung)
  2. Dekrement von i
  3. Auswertung des Minuend und Berechnung der Differenz
  4. Zuweisung der Differenz auf j

Ein Beispielprogramm zum Testen:

public class PrePostInkrement {
public static void main(String[] args) {
int i = 4;
int j;

j=i-- -i;
System.out.println("i: " +i+", j= "+j);
}
}

Ausgabe:

i: 3, j= 1

Die Auswertung des Ausdrucks und der Zuweisung j= i-- -i; findet wie folgt statt:

i j j= i-- -i; Kommentar
4 0 j = 4 - i; Bestimmung des Minuend der Subtraktion
3 0 j = 4 - i; Postdekrement von i
3 0 j = 4 -3; Bestimmung des Subtrahend der Subtraktion
3 0 j = 1; Bestimmung der Differenz
3 1   Zuweisung

Regeln für den Ergebnistyp von arithmetischen Ausdrücken (Widening Conversions)

Java kann alle arithmetischen Operationen auch ausführen wenn die Zahlentypen im Ausdruck unterschiedlich sind. Das Ergebnis der Berechnung hängt von den Typen des Ausdrucks ab. Es gilt in der folgenden Reihenfolge:

  1. Ist einer der Typen ein double, so wird das Ergebnis zum Typ double konvertiert.
  2. Falls nicht, wird das Ergebnis zu einem float Typen konvertiert wenn ein Typ ein float Typ ist.
  3. Falls nicht, wird das Ergebnis zu einem long Typen konvertiert wenn ein Typ ein long Typ ist.
  4. Falls nicht, werden beide Operanden zuerst zu einem int Typen konvertiert.

 

2.4 Übungen

Duke als Boxer

2.4.1 Übung: Zuweisungen und Typen 

Erstellen Sie folgende Java-Applikation (Name "Calc"):
  • Deklarieren Sie in der main() Routine Variablen mit unterschiedlichen Datentypen (z.B. int, longshort, float).
  • Initialisieren Sie die Variablen mit geeigneten Werten.
  • Führen Sie mit den Variablen folgende arithmetischen Operationen aus:
    • Addition einer Konstanten zu einer int-Variablen,
    • Multiplikation einer int-Variablen mit einer Konstanten,
    • Subtraktion zweier Variablen unterschiedlichen Datentyps (int, short),
    • Division einer int-Variablen durch eine Konstante,
    • Multiplikation zweier Variablen (davon eine vom Typ "float").
Geben Sie die Variablenwerte jeweils aus.
Zu beantworten:
  • Wie groß sind die Wertebereiche der einzelnen Typen?
  • Was geschieht wenn man zwei Ganzzahlen (int) dividiert und das Ergebnis ist keine ganze Zahl?
  • Was geschieht wenn Sie einen Überlauf in einem Wertebereich einer Variablen provozieren?

Beispiellösung

2.4.2 Übung: Erlaubte Variablennamen

Welche der folgenden Symbole sind gültige Variablennamen in Java?
Begründen Sie Ihre Antwort.
maxvalue
maxValue
max_value
max value
end
End
10%ofSum
sum10
_10PercentOfSum

2.4.3 Übung: Literale

Warum übersetzt das folgende Programm nicht?

package block2;
public class Literale {
    public static void main(String[] args) {
        long i1 = 4000000000;
        long i2 = 4000000000L;
        System.out.println(i1);
        System.out.println(i2);
    }
}

2.4.4 Übung: Ausdrücke

Gegeben seien folgende Variablendeklarationen in Java:

long a = 3;
int b = 4;
short c = 5;
byte d = 6;

Welchen Wert liefern die folgenden Ausdrücke und von welchem Typ sind sie?

d / b * a
c + b * (d + 1)
d / (c - 1) * b / 2
d % b
-c % b

2.4.5 Übung: Zuweisungen

Gegeben seien folgende Variablendeklarationen in Java:
long a = 3;
int b = 4;
short c = 5;
byte d = 6;
Welche der folgenden Zuweisungen sind in Java erlaubt?
a = b + 3 * (d + 1);
b = c * c;
c = b / 3;
d = (byte)a + b;
d = (byte) ( a + b);

2.4.6 Übung: Ausdrücke

Schreiben Sie einen Java-Ausdruck, der eine ganze Zahl x auf das nächstliegende Vielfache von 100 rundet.

Beispiel:

  • der Wert 149 soll auf 100 abgerundet werden
  • der Wert 150 auf 200 aufgerundet werden.

2.4.7 Übung: Zeitrechnung

Schreiben Sie ein Java-Programm, das eine Anzahl von Sekunden einliest und in
die Anzahl von Stunden, Minuten und Sekunden umrechnet.
Testen Sie Ihr Programm mit vernünftigen Eingabewerten, z. B. mit 0, 59, 60,
100, 3600 und 4000.
 
Beispiel: Die Eingabe 1234 soll zur Ausgabe von 0 : 20 : 34 führen.

2.4.8 Übung: Polynomberechnung

Schreiben Sie ein Javaprogramm, das die Koeffizienten a, b, c, d sowie den Wert x des Polynoms
y = a * x3  + b * x2  + c * x + d
einliest und das Ergebnis y ausgibt.

2.4.9 Übung: Abstand zwischen Punkten

Schreiben Sie ein Java-Programm, das die x- und y-Koordinaten zweier Punkte einliest und den Abstand zwischen ihnen berechnet und ausgibt.

Tipp: Math.sqrt() berechnet eine Wurzel

2.4.10 Übung: Berechnung von ∏ (Pi) 

Die Kreiszahl ∏ kann durch folgende Näherungsformel berechnet werden:

             i  
 ∏    n  (-1)          1     1     1     1 
--- = ∑ ------- = 1 - --- + --- - --- + --- - ... 
 4   i=0 2*i+1         3     5     7     9
Schreiben Sie ein Java-Progamm, das ∏ mit dieser Formel annähert.
Bemerkung: Die Reihe konvergiert nur seht langsam gegen ∏ .

2.4.11 Übung: Analoge Uhr

Implementieren sie notwendigen Berechnungen zum Anzeigen von Sekunden-, Minuten, Stundenzeigern einer analogen Uhr.

Vorbereitungen

  1. Erzeugen Sie in einem Verzeichnis die beiden Quelldateien Uhr.java und Zeiger.java. Der Quellcode der beiden Dateien ist weiter unten aufgeführt.
  2. Übersetzen Sie die beiden Dateien in diesem Verzeichnis mit dem Befehl javac Uhr.java Zeiger.java
  3. Führen Sie das Programm aus: java Uhr
  4. Beobachten Sie den blauen Sekundenzeiger. Er wächst in jeder Sekunde um ein Pixel nach rechts unten

Aufgabe

Implementieren Sie die korrekte Position der Spitze der drei Zeiger abhängig von der aktuellen Zeit in der Klasse Zeiger.java

Die korrekte Spitze wird durch eine x und y Koordinate beschrieben. Der Ursprung des Koordinatensystems liegt in der Mitte der Uhr. Postive X-Koordinaten reichen nach rechts. Positive Y-Koordinaten reichen nach unten. Die Richtung der Y-Koordinaten ist typisch für für Java GUIs in Swing oder AWT.

Hinweis: Implementieren Sie zuerst den Sekundenzeiger. Er erlaubt ihnen binnen 60 Sekunden die visuelle Kontrolle Ihrer Programmierung. Die Programmierung der Minutenzeiger und Stundenzeiger sind dann sehr einfach, da sie auf der gleichen mathematischen Formel basieren.

Empfohlendes Vorgehen:

  1. Erstellen Sie die mathematische Formel für die aktuelle Spitze des Sekundezeigers abhängig von der Sekunde zwischen 0 und 59. Sie benötigen hierzu die Sinus- und Cosinusfunktion sowie die Konstante PI
  2. Finden sie in der Klasse Zeiger.java die Methoden sekundeX und sekundeY
  3. Ersetzen Sie in der Trivialimplementierung die Zuweisung xs=s; bzw ys=s; durch die korrekte Implementierung der beiden mathematischen Ausdrücke
  4. Sichern Sie die Datei Zeiger.java
  5. Übersetzen Sie die Datei Zeiger.java
  6. Testen Sie das Ergebnis mit dem Kommando: java Uhr
  7. Wiederholen Sie Schritt 3-6 bis Sie das gewünschte Ergebnis haben.
  8. Implementieren Sie den Minuten und Stundenzeiger sinngemäß.

Beispiele der Ausgangssituation und der korrekten Implementierung: 

Ausgangssituation Ziel der Übung
analoge Uhr, initial Zustand Analoge Uhr als Lösung nit korrekten Zeigern

Tipps

  • maxRadius: Diese Konstante der Klasse Zeiger gibt die maximale (vernünftige) Zeigergröße vor.
    • Nutzen Sie diesen Wert für einen großen regulären Sekundenzeiger
    • Skalieren Sie die Minuten- und Stundenzeiger relativ zu dieser Größe (z. Bsp. maxRadius*4/5 )
  • die Mathematikbibliothek wurde statisch importiert. Sie können direkt die benötigen trigonometrischen Operation und Konstanten benutzen:
  • Beachten Sie die Eingabe- und Ausgabeparameter der trigonometrischen Funktionen! Die Berechnungen erfolgen mit Fließkommazahlen. Das Ergebnis muss aber eine ganzzahlige Pixelposition sein.
  • Ganzzahloperation runden immer ab. Erwägen Sie die Benutzung einer Fließkommahilfsvariable
  • Benutzen nur double als Fließkommatyp. Die Operationen der Mathematikbibliothek beruhen auf dem Typ double. Sie vermeiden so unnötige Konvertierungen zu float Typen.

Klasse Uhr

/*
* Zeichnen einer analogen Uhr in einem JFrame
*/
import java.awt.*;
import java.awt.event.*;

 

import java.util.Calendar;
import javax.swing.*;

/**
*
* @author sschneid
*/
public class Uhr extends JPanel {

static int sekunde = 0;
static int minute = 0;
static int stunde = 0;
static String tzString; // aktuelle Zeitzone
static int initialHeight;
static float zoom = 1;

/**
* Hauptprogramm der Anwendung. Es werden keine Eingabeparameter benötigt
* @param args dieser Parameter wird nicht ausgewertet
*/
public static void main(String[] args) {
JFrame hf; // Das Fenster der Anwendung
hf = new JFrame("Uhr");
// Beenden der Anwendung bei Schliesen des Fenster
hf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Uhr dieUhr = new Uhr();
// Aufbau des Contentpanes
Container myPane = hf.getContentPane();
myPane.add(dieUhr, BorderLayout.CENTER);

// Erzeuge einen Menüeintrag zum Beenden des Programms
JMenuBar jmb = new JMenuBar();
JMenu jm = new JMenu("Datei");
JMenuItem exitItem = new JMenuItem("Beenden");
exitItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
jm.add(exitItem);
jmb.add(jm);
hf.setJMenuBar(jmb);

hf.pack();
// Das JFrame sichtbar machen
// Gewünschte Größe setzen
// 1. Parameter: horizontale Größe in Pixel
// 2. Parameter: vertikale Größe in Pixel
hf.setSize((int)(2 * Zeiger.maxRadius*zoom + 80),
(int)(2 * Zeiger.maxRadius*zoom + 80));
hf.setVisible(true);
hf.setAlwaysOnTop(true);
// Initialgröße merken. Entspricht Zoomfaktor 1
dieUhr.initialHeight = dieUhr.getHeight();
// Update von Panel in zyklischen Intervallen
try {
while (true) {
Thread.sleep(500); // Schlafe x Millisekunden
// Hole Systemzeit und belege statische Variablen
Calendar call = Calendar.getInstance();
tzString = call.getTimeZone().getDisplayName();
sekunde = call.get(Calendar.SECOND);
minute = call.get(Calendar.MINUTE);
stunde = call.get(Calendar.HOUR);
dieUhr.repaint();
}
} catch (InterruptedException e) {
System.out.println(
"Die Anwendung wird wegen einer Ausnahme beendet");
}
}
/**
* Überladene Paintmethode. Sie führt alle Zeichenoperationen im Panel aus
* @param g vom Laufzeitsystem übergebenes Graphikobjekt.
*/
@Override
public void paint(Graphics g) {
super.paint(g);
zoom = (float)getHeight()/(float)initialHeight;
int maxRadius = Zeiger.maxRadius;
int xCenter = (int)(maxRadius*zoom) + 40;
int yCenter = (int)(maxRadius*zoom) + 20;
float fontSize = g.getFont().getSize2D();

int charCenterOffSet = (int)(fontSize/2);
String timeString = stunde + ":" + minute + ":" + sekunde
+ " " + tzString;
// Zeichne Uhrenhintergrung und Koordinatensystem
g.setFont(g.getFont().deriveFont(fontSize));
g.setColor(Color.WHITE); // Farbe
g.fillArc((int)(xCenter - maxRadius*zoom),
(int)(yCenter - maxRadius*zoom),
(int)(maxRadius*zoom*2),
(int)(maxRadius*zoom*2), 0, 360);
g.setColor(Color.BLACK); // Farbe
g.fillArc((int)(xCenter - 3*zoom), (int)(yCenter - 3*zoom),
(int)(10*zoom), (int)(10*zoom), 0, 360);

g.drawLine(xCenter, yCenter, (int)(xCenter + 40*zoom), yCenter);
g.drawLine(xCenter, yCenter, xCenter, (int)(yCenter + 40*zoom));
g.drawString("X", (int)(xCenter + 45*zoom),
yCenter + +charCenterOffSet);
g.drawString("Y", xCenter - charCenterOffSet,
(int)(yCenter + 55*zoom));
g.drawString("12",xCenter - charCenterOffSet,
(int)(yCenter - maxRadius*zoom));
g.drawString("3", (int)(xCenter + maxRadius*zoom),
yCenter + charCenterOffSet);
g.drawString("6", xCenter - charCenterOffSet,
(int)(yCenter + 2*charCenterOffSet+maxRadius*zoom));
g.drawString("9", (int)(xCenter - maxRadius*zoom - charCenterOffSet),
yCenter + charCenterOffSet);
// Zeichne aktuelle Zeit zum Debuggen
g.drawString(timeString, 0,
(int)(yCenter + maxRadius*zoom));

// Zeichne Stundenzeiger
g.setColor(Color.BLACK);
g.drawLine(xCenter, yCenter,
(int)(xCenter + Zeiger.stundeX(stunde)*zoom),
(int)(yCenter + Zeiger.stundeY(stunde)*zoom));
g.drawString("h["
+ Zeiger.stundeX(stunde)
+ "," + Zeiger.stundeY(stunde) + "]",
0, (int)(yCenter + maxRadius*zoom - (3*fontSize)));
// Zeichne Minutenzeiger
g.setColor(Color.RED);
g.drawLine(xCenter, yCenter,
(int)(xCenter + Zeiger.minuteX(minute)*zoom),
(int)(yCenter + Zeiger.minuteY(minute)*zoom));
g.drawString("m["
+ Zeiger.minuteX(minute) + ","
+ Zeiger.minuteY(minute) + "]", 0,
(int)(yCenter + maxRadius*zoom - (2*fontSize)));
// Zeichne Sekundenzeiger
g.setColor(Color.BLUE);
g.drawLine(xCenter, yCenter,
(int)(xCenter + Zeiger.sekundeX(sekunde)*zoom),
(int)(yCenter + Zeiger.sekundeY(sekunde)*zoom-fontSize));
g.drawString("s["
+ Zeiger.sekundeX(sekunde) + ","
+ Zeiger.sekundeY(sekunde) + "]", 0,
(int)(yCenter + maxRadius*zoom - fontSize));
}
}

Klasse Zeiger

Implementieren sie

import static java.lang.Math.*;

/**
*
* @author sschneid
*/
public class Zeiger {
public static final int maxRadius = 100; // Beeinflusst GUI-Größe !

/**
* @param s Sekunden der aktuellen Zeit. Ein Wert zwischen 0 und 59
* @return aktuelle X Koordinate der Zeigerspitze des Sekundenzeigers
* auf dem Bildschirm
*/
public static int sekundeX(int s) {
int xs;
/* Implementierung Beginn */
xs = s; // Proformazuweisung. Sie ist zu ersetzen
/* Implementierung Ende */
return xs;
}

/**
* @param s Sekunden der aktuellen Zeit. Ein Wert zwischen 0 und 59
* @return aktuelle Y Koordinate der Zeigerspitze des Sekundenzeigers
* auf dem Bildschirm
*/
public static int sekundeY(int s) {
int ys;
/* Implementierung Beginn */
ys = s; // Proformazuweisung. Sie ist zu ersetzen
/* Implementierung Ende */
return ys;
}

/**
* @param m Minuten der aktuellen Zeit. Ein Wert zwischen 0 und 59
* @return aktuelle X Koordinate der Zeigerspitze des Minutenzeigers
* auf dem Bildschirm
*/
public static int minuteX(int m) {
int xm;
/* Implementierung Beginn */
xm = m; // Proformazuweisung. Sie ist zu ersetzen
/* Implementierung Ende */
return xm;
}

/**
* @param m Minuten der aktuellen Zeit. Ein Wert zwischen 0 und 59
* @return aktuelle Y Koordinate der Zeigerspitze des Minutenzeigers
* auf dem Bildschirm
*/
public static int minuteY(int m) {
int ym;
/* Implementierung Beginn */
ym = m; // Proformazuweisung. Sie ist zu ersetzen
/* Implementierung Ende */
return ym;
}

/**
* @param h Stunden der aktuellen Zeit. Ein Wert zwischen 0 und 12
* @return aktuelle X Koordinate der Zeigerspitze des Stundenzeigers
* auf dem Bildschirm
*/
public static int stundeX(int h) {
int xh;
/* Implementierung Beginn */
xh = h; // Proformazuweisung. Sie ist zu ersetzen
/* Implementierung Ende */
return xh;
}

/**
* @param h Stunden der aktuellen Zeit. Ein Wert zwischen 0 und 12
* @return aktuelle Y Koordinate der Zeigerspitze des Stundenzeigers
* auf dem Bildschirm
*/
public static int stundeY(int h) {
int yh;
/* Implementierung Beginn */
yh = h; // Proformazuweisung. Sie ist zu ersetzen
/* Implementierung Ende */
return yh;
}
}

Hilfestellung

Hier nicht weiterlesen, wenn Sie einfache trigonometrische Aufgaben lösen können!

max-Zeit ist bei Sekunden 60, bei Minuten 60, bei Stunden 12

Die X-Koordinate eines Zeigers berechnet sich wie folgt

x-Koordinate: Sinus(aktuelle-Zeit/max-Zeit*2*PI)*zeiger-länge-in-pixel

Die Y-Koordinate eines Zeigers berechnet sich wie folgt

Y-Koordinate: -Cosinus(aktuelle-Zeit/max-Zeit*2*PI)*zeiger-länge-in-pixel

Beachten Sie, dass in Java das Kommutativgesetz nicht vollständig gilt. Der Typ und die Auswertereihenfolge spielen auch eine Rolle!

2.4.12 Gruppenübung

Je eine Gruppe bearbeitet die folgenden Typen:

  • short
  • char
  • int
  • long
  • float
  • double

Jede Gruppe trägt die folgenden Aspekte eines Typs in 5 Minuten vor

  • Schlüsselwort des Typ
  • Anzahl der verwendeten Bits
  • Wertebreich 
    • Grenzen mit Konstanten dazu
    • Echte Obermengen oder Untermengen der anderen Typen nennen
    • Beispiel für die Benutzung dieses Types. Das Beispiel sollte nicht optimal für die anderen Typen sein!
  • Syntax der Literale um Werte zuzuweisen
  • Programmierbeispiele
    • Syntax der Literale benutzen und vorstellen
    • Typische Operation und Zuweisung
    • mehrere interessante Operationen und Zuweisungen die zu Sonderfällen bzw. Extremfällen führen

Zeitvorgaben

  • Selbst lesen: 5 Minuten
  • Teamdiskussion: 10 Minuten
  • Beispiel Programmieren: 20 Minuten
  • Zusammenfassung, Abstimmung Vortrag: 5 Minuten

 2.4.13 Airline

Vorbereitung

  • Legen Sie ein neues Paker block2 an
  • Nutzen Sie die neue Klasse Flughafen
  • Kopieren Sie die Klasse Flugzeug aus dem block1 in den block2

Aufgabe:

  • Legen Sie in der Klasse Flugzeug die folgenden Attribute als öffentliche (public) Attribute an. Wählen Sie einen passenden Typen
    • passagiere
    • besatzung
    • treibstoff
    • leergewicht
    • maxGewicht
  • Legen Sie zwei statische Variablen an in der Form
    • static final int durchschnittsGewicht= 75;
    • static final double kerosinGweicht= 0.796; // kg/dm3
  • Kopieren der Methoden (siehe unten) in die Klasse Flugzeug
    • gewicht()
    • toString()
    • drucken()
  • Implementieren der Methode gewicht()
    • Deklarieren Sie eine Variable
    • berechnen Sie das aktuelle Gesamtgewicht
    • geben Sie diese Variable zurück. Ersetzen Sie die 0 in return durch Ihre Variable
  • Erweitern Sie die Klasse Flughafen
    • Nutzen Sie das Objekt auf das lh1 zeigt.
    • Belegen Sie alle Attribute mit vernünftigen Werten
    • Rufen Sie die Methode drucken() für lh1 auf.
    • Kontrollieren Sie die Werte
  • Testen Sie das Programm in der Klasse Flugzeug
  • In der Klasse Flugzeug
    • Entnehmen Sie dem Treibstofflager des Flughafen 2000 Liter Treibstoff und füllen Sie Ihn in das Flugzeug lh1
    •  

Klasse Flugzeug

Im block2 anlegen!

package Airline.block2;

/**
*
* @author stsch
*/
public class Flugzeug {
String kennzeichen;

/**
*
* @return aktuelles Gewicht;
*/
public double gewicht() {
// Berechnen Sie das aktuelle Gewicht:
// 1. Anlegen einer Variablen
// 2. Berechnen des Gewichts
// 3. Variable in return zurückgeben
return 0;
}

public String toString() {return kennzeichen;}

public void drucken() {
System.out.println("Objekt: " + this);
System.out.println("kennzeichen: " + kennzeichen);
System.out.println("passagiere: " + passagiere);
System.out.println("Besatzung: " + besatzung);
System.out.println("Treibstoff: " + treibstoff);
System.out.println("Leergewicht: " + leergewicht);
System.out.println("maxGewicht: " + maxGewicht);
System.out.println("aktuelles Gewicht: " + gewicht());
}

}

Klasse Flughafen

package Airline.block2;

package Airline.block2;

/**
*
* @author stsch
*/
public class Flughafen {
String name;
Flugzeug gate1;
Flugzeug gate2;
Flugzeug gate3;
Flugzeug gate4;
Flugzeug gate5;
Flugzeug gate6;
double treibstoffLager;

public static void main(String[] args) {
Flughafen pad = new Flughafen();

pad.name = "Paderborn";
pad.treibstoffLager = 1000000;

// Boeing 747, https://de.wikipedia.org/wiki/Boeing_747#747-400
Flugzeug lh1 = new Flugzeug();
lh1.kennzeichen ="D-ABTL";

pad.gate1 = lh1;

System.out.println("*** Unser Flughafen ***");
System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate1);
System.out.println("Am Gate 2: " + pad.gate2);
System.out.println("Am Gate 3: " + pad.gate3);
System.out.println("Treibstoff: " + pad.treibstoffLager);
System.out.println("***********************");

pad.gate1=null;
pad.gate2=lh1;

System.out.println("*** Unser Flughafen ***");
System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate1);
System.out.println("Am Gate 2: " + pad.gate2);
System.out.println("Am Gate 3: " + pad.gate3);
System.out.println("Treibstoff: " + pad.treibstoffLager);
System.out.println("***********************");


}
}

 

2.5 Lösungen

2.5.1 Zuweisungen und Typen 

class ArithmeticTest {
  public static void main (String args[]) {
    short x = 6;
    int y = 4;
    float a = 12.5f;
    float b = 7f;
    System.out.println("x is " + x + ", y is " + y);
    System.out.println("x + y = " + (x + y));
    System.out.println("x - y = " + (x - y));
    /* ??? */
    System.out.println("x % y = " + (x % y));
    System.out.println("a is " + a + ", b is " + b);
    /* ??? */
  }
}

2.5.2 Erlaubte Variablennamen

siehe Spezifikation Java 7 (Identifier)

Lösung durch Testen mit javac

public class VariableNames {   
   int maxvalue;
   int maxValue;
   int max_value;
   //int max value;
   int end;
   int End;
   //int 10%ofSum;
   int sum10;
   int _10PercentOfSum;
  public static void main(String[] args) {
  }
}

Erklärung

  • maxvalue: korrekter Name
  • maxValue: korrekter Name
  • max_value: korrekter Name
  • max value: Leerstelle im Namen ist nicht erlaubt
  • end: korrekter Name
  • End: korrekter Name
  • 10%ofSum: Ziffer als erstes Zeichen ist nicht erlaubt
  • sum10: korrekter Name
  • _10PercentOfSum: korrekter Name

2.5.3 Literale

Das Programm übersetzt nicht weil

  • der Wert 4000000000 ein "Integer"-Literal ist
  • der Wert 4000000000 den Wertebereich einer Ganzzahl vom Typ "Integer" übersteigt
  • Die Zuweisung auf eine Variable vom Typ "Long" spielt zu diesem Zeitpunkt noch keine Rolle

2.5.4 Ausdrücke

public class Expressions {
    
    public static void main(String[] args) {
      long a = 3;
      int b = 4;
      short c = 5;
      byte d = 6;
      
      int  result1;
      long result2;

      result2 = d / b * a;
      System.out.println("d / b * a = " + result2 );

      result1 = c + b * (d + 1);
      System.out.println("c + b * (d + 1) = " + result1 );

      result1 = d / (c - 1) * b / 2;
      System.out.println("d / (c - 1) * b / 2 = " + result1 );

      result1 = d % b;
      System.out.println("d % b = " + result1 );

      result1 = -c % b;
      System.out.println("-c % b = " + result1 );
    }

}

Ergebnis

d / b * a = 3
c + b * (d + 1) = 33
d / (c - 1) * b / 2 = 2
d % b = 2
-c % b = -1

2.5.5 Zuweisungen

public class AllowedAssigments {

    public static void main(String[] args) {
        long a = 3;
        int b = 4;
        short c = 5;
        byte d = 6;

        a = b + 3 * (d + 1);
        b = c * c;
        c = (byte)(b / 3);
        d = (byte)(((byte)a + b));
        d = (byte) ( a + b);
    }

}

2.5.6 Ausdrücke

public class Runden {

    public static void main(String[] args) {
        int a, result;
        
        a =149;
        result = (a +50)/100*100;
        System.out.println("Eingabe = " + a + " Ergebnis = " + result);

        a =150;
        result = (a +50)/100*100;
        System.out.println("Eingabe = " + a + " Ergebnis = " + result);
    }
}

Ergebnis

Eingabe = 149 Ergebnis = 100
Eingabe = 150 Ergebnis = 200

2.5.7 Zeitrechnung

public class timeCalculation {

    public static void main(String[] args) {
        int a,h,m,s;

        a = 0;
        h = a/3600;
        m = (a-h*3600)/60;
        s = a - h*3600 - m*60;
        System.out.println("Sekunden = " + a + " = " + h + ":" + m + ":" +s);

        a = 59;
        h = a/3600;
        m = (a-h*3600)/60;
        s = a - h*3600 - m*60;
        System.out.println("Sekunden = " + a + " = " + h + ":" + m + ":" +s);

        a = 60;
        h = a/3600;
        m = (a-h*3600)/60;
        s = a - h*3600 - m*60;
        System.out.println("Sekunden = " + a + " = " + h + ":" + m + ":" +s);

        a = 100;
        h = a/3600;
        m = (a-h*3600)/60;
        s = a - h*3600 - m*60;
        System.out.println("Sekunden = " + a + " = " + h + ":" + m + ":" +s);

        a = 3600;
        h = a/3600;
        m = (a-h*3600)/60;
        s = a - h*3600 - m*60;
        System.out.println("Sekunden = " + a + " = " + h + ":" + m + ":" +s);

        a = 4000;
        h = a/3600;
        m = (a-h*3600)/60;
        s = a - h*3600 - m*60;
        System.out.println("Sekunden = " + a + " = " + h + ":" + m + ":" +s);
    }

}

Ergebnis

Sekunden = 0 = 0:0:0
Sekunden = 59 = 0:0:59
Sekunden = 60 = 0:1:0
Sekunden = 100 = 0:1:40
Sekunden = 3600 = 1:0:0
Sekunden = 4000 = 1:6:40

2.5.8 Polynomberechnung

public class Polynom {
    public static void main(String[] args) {
        double a, b, c, d, x, y;

        a =  1.2;
        b = -2.3;
        c =  4.5;
        d = -6.7;

        x = 8.9;

        y = a*x*x*x+b*x*x+c*x+d;
        System.out.print("Ergebnis = "+ y);
        System.out.println("\n oder...");

        y = a*Math.pow(x,3)+b*Math.pow(x,2)+c*x+d;
        System.out.print("Ergebnis = "+ y);
    }
}

Ergebnis

Ergebnis = 697.1298
 oder...
Ergebnis = 697.1297999999999

2.5.9 Abstand zwischen Punkten

public class Main {
    public static void main(String[] args) {
       double x1,x2, y1, y2, x, y, d;

       x1 = 1; y1 = 1;
       x2 = 4; y2 = 5;

       x = x1-x2;
       y = y1-y2;
       d = Math.sqrt(x*x+y*y);
       System.out.println("distance = " + d);
    }
}

Ergebnis

distance = 5.0

2.5.10 Berechnung von ∏ (Pi)

Hilfe; Java Spezifikation: Division

public class PiCalculation {

    public static void main(String[] args) {
        double pi;

        pi = (1.0 - 1.0/3.0 + 1.0/5.0 - 1.0/7.0 + 1.0/9.0 - 1.0/11.0 + 1.0/13.0)*4;
        System.out.println("Pi = "+ pi);
        System.out.println("\n oder ...");

        pi=1;
        for (int i=1;i<=10000;i++) {
            pi = pi + Math.pow(-1,i)/(2*i+1);
        }
        pi=pi*4;
        System.out.println("Pi = "+ pi);

    }

Ergebnis

Pi = 3.2837384837384844

 oder ...
Pi = 3.1416926435905346

2.5.11 Analoge Uhr

import static java.lang.Math.*;
/**
*
* @author sschneid
*/
public class Zeiger {
   public static final int maxRadius = 100; // Beeinflusst GUI-Größe !
   /**
   * @param s Sekunden der aktuellen Zeit. Ein Wert zwischen 0 und 59
   * @return aktuelle X Koordinate der Zeigerspitze des Sekundenzeigers
   * auf dem Bildschirm
   */
   public static int sekundeX(int s) {
      int xs;
      /* Implementierung Beginn */
      double sx = sin(s*2*PI/60)*maxRadius;
      xs = (int)sx;
      /* Implementierung Ende */
      return xs;
   }
   /**
   * @param s Sekunden der aktuellen Zeit. Ein Wert zwischen 0 und 59
   * @return aktuelle Y Koordinate der Zeigerspitze des Sekundenzeigers
   * auf dem Bildschirm
   */
   public static int sekundeY(int s) {
      int ys;
      /* Implementierung Beginn */
      double sy = -cos(s*2*PI/60)*maxRadius;
      ys = (int)sy;
      /* Implementierung Ende */
      return ys;
   }
   /**
   * @param m Minuten der aktuellen Zeit. Ein Wert zwischen 0 und 59
   * @return aktuelle X Koordinate der Zeigerspitze des Minutenzeigers
   * auf dem Bildschirm
   */
   public static int minuteX(int m) {
      int xm;
      /* Implementierung Beginn */
      double mx = sin(m*2*PI/60)*maxRadius*0.75;
      xm = (int)mx;
     /* Implementierung Ende */
     return xm;
   }
   /**
   * @param m Minuten der aktuellen Zeit. Ein Wert zwischen 0 und 59
   * @return aktuelle Y Koordinate der Zeigerspitze des Minutenzeigers
   * auf dem Bildschirm
   */
   public static int minuteY(int m) {
      int ym;
      /* Implementierung Beginn */
      double my = -cos(m*2*PI/60)*maxRadius*0.75;
     ym = (int) my;
     /* Implementierung Ende */
     return ym;
   }
   /**
   * @param h Stunden der aktuellen Zeit. Ein Wert zwischen 0 und 12
   * @return aktuelle X Koordinate der Zeigerspitze des Stundenzeigers
   * auf dem Bildschirm
   */
   public static int stundeX(int h) {
      int xh;
      /* Implementierung Beginn */
      double hx = sin(h*2*PI/12)*maxRadius*0.5;
      xh = (int)hx;
     /* Implementierung Ende */
     return xh;
   }
   /**
   * @param h Stunden der aktuellen Zeit. Ein Wert zwischen 0 und 12
   * @return aktuelle Y Koordinate der Zeigerspitze des Stundenzeigers
   * auf dem Bildschirm
   */
   public static int stundeY(int h) {
      int yh;
      /* Implementierung Beginn */
      double hy = -cos(h*2*PI/12)*maxRadius*0.5;
      yh = (int)hy;
      /* Implementierung Ende */
      return yh;
   }
}

 2.5.13 Airlines

Klasse Flughafen

package Airline.block2;

/**
*
* @author stsch
*/
public class Flughafen {
String name;
Flugzeug gate1;
Flugzeug gate2;
Flugzeug gate3;
Flugzeug gate4;
Flugzeug gate5;
Flugzeug gate6;
double treibstoffLager;

public static void main(String[] args) {
Flughafen pad = new Flughafen();

pad.name = "Paderborn";
pad.treibstoffLager = 1000000;

// Boeing 747, https://de.wikipedia.org/wiki/Boeing_747#747-400
Flugzeug lh1 = new Flugzeug();
lh1.kennzeichen ="D-ABTL";

pad.gate1 = lh1;

System.out.println("*** Unser Flughafen ***");
System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate1);
System.out.println("Am Gate 2: " + pad.gate2);
System.out.println("Am Gate 3: " + pad.gate3);
System.out.println("Treibstoff: " + pad.treibstoffLager);
System.out.println("***********************");

pad.gate1=null;
pad.gate2=lh1;

System.out.println("*** Unser Flughafen ***");
System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate1);
System.out.println("Am Gate 2: " + pad.gate2);
System.out.println("Am Gate 3: " + pad.gate3);
System.out.println("Treibstoff: " + pad.treibstoffLager);
System.out.println("***********************");

lh1.leergewicht = 184567;
lh1.maxGewicht = 412770;
lh1.passagiere = 200;
lh1.besatzung = 10;

lh1.drucken();

}
}

Klasse Flugzeug

package Airline.block2;

import Airline.Block1.*;

/*
*
* @author stsch
*/
public class Flugzeug {
String kennzeichen;
static final int durchschnittsGewicht= 75;
static final double kerosinGweicht= 0.796; // kg/dm3
public int passagiere;
public int besatzung;
public double treibstoff;
public double leergewicht;
public double maxGewicht;

/**
*
* @return aktuelles Gewicht;
*/
public double gewicht() {
// Berechnen Sie das aktuelle Gewicht:
// 1. Anlegen einer Variablen
// 2. Berechnen des Gewichts
// 3. Variable in return zurückgeben
return 0;
}

public String toString() {return kennzeichen;}

public void drucken() {
System.out.println("Objekt: " + this);
System.out.println("kennzeichen: " + kennzeichen);
System.out.println("passagiere: " + passagiere);
System.out.println("Besatzung: " + besatzung);
System.out.println("Treibstoff: " + treibstoff);
System.out.println("Leergewicht: " + leergewicht);
System.out.println("maxGewicht: " + maxGewicht);
System.out.println("aktuelles Gewicht: " + gewicht());
}
}

 

2.6 Webcast: Java Variablen, Zuweisungen und Operatoren

Ein graphisch animierter Webcast der die genauen Vorgänge des folgenden Beispielprogramms erläutert:

public class Mitarbeiter {
...
   public static void main (String[] args) {
      int a;
      long b;
      String eingabe;
      eingabe = args[0];
      a = 15;
      a++;
      b = 2*a;
      System.out.println("Ausgabe :" + eingabe + b);
   }
}

Das folgende Video dauert 8 Minuten.

Wichtige Aspekte:

  • genaue Lebensdauer von Variablen
  • genaues Vorgehen bei einem Cast
  • Verwalten von temporären Datenstrukturen

2.7 Fragen

Duke grübelt

2.7.1 Ganzzahlarithmetik

Ganzzahloperationen haben eine Reihe von Eigenheiten. Es wird auf eine Variable a eine Variable b zugewiesen die um 25% größer sein soll. Hierfür gibt es drei Möglichkeiten. Im ersten Fall sei a klein (=6). Welches ist die beste Implementierung? Welche Operationen sind heikel?

  Implementierung Ergebnis

.
.
a.)
b.)
c.)

int a = 0;
int b = 6;
a = b*5/4;
a = b/4*5;
a = b*(5/4);
 

 Was ist die beste Implementierung wenn man weiß das b groß ist? Z.Bsp. b= 230? Welche Operation ist heikel?

2.7.2 Sichere Zuweisungen

Welche der folgenden Zuweisungen sind sicher b.z.w unsicher?

Eine Zuweisung sei unsicher wenn bei der Zuweisung abhängig von der gewählten Variablenbelegung Datenverluster auftreten können.

Analysieren Sie die sechs Zuweisungen auf potentielle Datenverluste. Die Werte von a_short, b_int, c_long wurden absichtlich am oberen Ende des jeweiligen Wertebereichs gewählt um den Präzisionsverlust zu provozieren.

Hinweis: Vergleichen Sie die Wertebereiche der Ausgangs und Zielvariablen.

Ignorieren Sie die Auswertelogik. Das Programm ist ausführbar. Es wird die Belegungen der einzelnen Variablen auf der Konsole drucken. Es wird bei jedem Fall von Präzisionsverlust einen Text "(Fehler)" hinter die Ausgabe anfügen.

public class UebungTypkonversion {

public static void main(String[] args) {
// Alle Werte sind der drittgrößte Wert des jeweiligen Wertebereichs
short a_short = Short.MAX_VALUE-2; // short = 16 bit
int b_int = Integer.MAX_VALUE-2; // int = 32 bit
long c_long = Long.MAX_VALUE-2; // long = 64 bit

// cast auf float: Welche Zuweisungen können zu Präzisionsverlusten führen?
float c_float = a_short;
float d_float = b_int;
float e_float = c_long;

// cast auf double:Welche Zuweisungen können zu Präzisionsverlusten führen?
double f_double = a_short;
double g_double = b_int;
double h_double = c_long;

// Auswertelogik und Kontrolle
System.out.println("a_short=" + a_short);
System.out.println("a_int=" + b_int);
System.out.println("a_long=" + c_long);
System.out.print("short auf float= " + c_float + " ");

// Sind die Werte nach der Rückkonvertierung noch gleich?
if (a_short == (short)c_float) {System.out.println("(OK)");}
else {System.out.println("(Fehler)");}
System.out.print("int auf float= " + d_float);
if (b_int == (int)d_float) {System.out.println("(OK)");}
else {System.out.println("(Fehler)");}
System.out.print("long auf float= " + e_float);
if (c_long == (long)e_float) {System.out.println("(OK)");}
else { System.out.println("(Fehler)");}

System.out.print("short auf double= " + f_double + " ");
if (a_short == (short)f_double) {System.out.println("(OK)");}
else {System.out.println("(Fehler)");}
System.out.print("int auf double= " + g_double);
if (b_int == (int)g_double) {System.out.println("(OK)");}
else {System.out.println("(Fehler)");}
System.out.print("long auf double= " + h_double);
if (c_long == (long)e_float) {System.out.println("(OK)");}
else { System.out.println("(Fehler)");}

}
}

 

2.8 Antworten

2.8.1 Ganzzahlarithmetik

Bei Ganzzahldivisionen werden alle Ergebnisse abgerundet.

Option a.) ist hier die optimale Lösung da der Rundungsfehler relativ gesehen am kleinsten ist wenn man zuerst multipliziert um dann eine möglichst große Zahl zu teilen.

Option c.) ist schlecht, da da 5/4 schon innerhalb der Klammer auf 1 abgerundet wird.

Option b.) ist ebenfalls schlecht da 6/4 ebenfalls auf 1 abgerundet wird

  Implementierung Ergebnis

.
.
a.)
b.)
c.)

int a = 0;
int b = 6;
a = b*5/4;
a = b/4*5;
a = b*(5/4);
.
.
a=7
a=5
a=6 

Die Variable b ist sehr groß b= 230:

  • Option b.) ist vorteilhaft.
  • Hier wird die Multiplikation zum Risiko. Der maximale 32 Bit Ganzzahlwert (231-1) kann bei Option a.) und b.) überschritten werden. Der Überlauf wird vom Laufzeitsystem nicht gemeldet und das Ergebnis ist falsch da negativ. Ein Überlauf des Wertebereichs durch die Mutiplkation ist wahrscheinlich schlimmer als ein Rundungsfehler durch eine frühe Division.

2.8.2 Sichere Zuweisungen

Konsolenausgaben:

a_short=32765
a_int=2147483645
a_long=9223372036854775805
short auf float= 32765.0 (OK)
int auf float= 2.14748365E9(Fehler)
long auf float= 9.223372E18(Fehler)
short auf double= 32765.0 (OK)
int auf double= 2.147483645E9(OK)
long auf double= 9.223372036854776E18(Fehler)

Die Mantisse des Typ float ist nicht groß genug um extrem große (oder kleine) Werte des Typs int und long präzise zu verwalten.

Die Mantisse des Typ double ist nicht groß genug, um extrem große (oder kleine) Werte des Typs long präzise zu verwalten.

 

2.9 Lernziele

Am Ende dieses Blocks können Sie:

  • ....die Begriffe Schlüsselwort, Variable, Zuweisung, Typ, Literal, Operator im Javakontext erkennen, unterscheiden, erklären und anwenden.
  • ...Javaschlüsselworte erkennen und etwa 30 nennen (Dieses Lernziel soll erst am Ende der ersten Vorlesung erreicht sein)
  • ...erlaubte Namen in Java und Schlüsselworte unterscheiden.
  • ...den Unterschied zwichen Literalen und Namen erklären.
  • ...drei Arten von Kommentaren benutzen.
  • ...die unterschiedlichen primitiven Datentypen kennen und Ihren Werte aus dem Format herleiten.
  • ...wichtige Unterschiede in der Ganzzahlarithmetik und der Fließkommaarithmetik erklären
  • ...sichere von unsicheren Zuweisungen unterscheiden
  • ...erklären warum String kein primitiver Datentyp ist
  • ...ein Beispiel für einen Aufzählungstyp nennen
  • ...die Notwendigkeit von expliziten Typkonversionen nennen und das damit verbundene Risiko abschätzen
  • ...je einen unären, einen binären und einen ternären Operator nennen
  • ...die Auswertereihenfolge eines Terms beschreiben
  • ...mit Post- und Präinkrementoperatoren umgehen

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten:

3. Ablaufsteuerung, Kontrollstrukturen

3.1 Verzweigungen und Vergleichsoperationen

Durch Verzweigungen können im Ablauffluß des Programmes verschiedene Fälle geprüft werden und man kann abhängig davon unterschiedliche Anweisungen ausführen.

Befehlsblöcke

Java erlaubt das Zusammenfassen von Javabefehlen durch Blöcke. Blöcke werden geschweiften Klammern beschrieben.

Für Blöcke gilt

  • Im Block deklarierte Variablen sind nur innerhalb des Blocks, nach der Deklaration benutzbar
  • Befehle innerhalb eines Blocks müssen mit Semikolon getrennt werden
  • Auch der letzte Befehl in einem Block muss mit Semikolon abgeschlossen werden
  • Blöcke können geschachtelt werden

Beispiel

{
   int i = 1;
   i++;
   int j = i*2;
}

Tipp: Systematisches Einrücken und Klammern die genau übereinander stehen helfen sehr bei der Lesbarkeit

Einfachverzweigung (if, if else)

Eine Einfachverzweigung wird in Java mit dem Schlüsselwort if eingeleitet. Anschließend folgt eine Bedingung in runden Klammern. Ist die Bedingung erfüllt, wird die folgende Anweisung bzw. der Anweisungsblock in geschweiften Klammern ausgeführt. Hierdurch ergibt sich die folgende Syntax:

if (Bedingung) Anweisung;

oder

if (Bedingung) {Anweisung(en);}

Ist die Bedingung nicht erfüllt wird die Anweisung ausgelassen und der normale Ausführungsfluß wird wieder aufgenommen. Ein Beispiel hierfür ist:

int x = 8;
if (x > 5) System.out.println("Wert  x = " + x + " ist größer als 5");
System.out.println ("Wert  x = " + x);

Hier ergeben sich die Ausgaben:

Wert x = 8 ist größer als 5
Wert x = 8

da die bedingte Anweisung sowie die letzte Anweisung durchlaufen wird.

UML Darstellung in Aktivitätsdiagrammen

 

Entweder-oder Kontrollfluss mit dem Schlüsselwort else

Es gibt auch die Möglichkeit eines entweder-oder Kontrollflusses mit Hilfe des Schlüsselworts else:

if (Bedingung) {Anweisung(en);}
     else {Anweisung(en);};

Im Fall der if Bedingung ohne else Klausel kann man die geschweiften Klammer mit einem Anweisungsblock weglassen und durch eine einzelne Anweisung ersetzen.

int x = 8;
int y = 0;
if (x > 5) {
      System.out.println("Wert x = " + x + " ist größer als 5"); 
      y = x; 
   } 
   else { 
      System.out.println("Wert x = " + x + " ist kleiner oder gleich 5"); 
      y = -x; 
   } 
System.out.println ("Wert x = " + x ");

UML Darstellung in Aktivitätsdiagrammen

if Anweisungen lassen sich schachteln um komplexe Bedingungen prüfen zu können. Anbei ein Beispiel:

int a = 5;
int b = 10;
int maximum =0;
if (a > b)  // Äussere if Bedingung
   if (a > 0) 
      { maximum = a;} // Innere if Bedingung
   else 
      { maximum = 0; } //innere else Bedingung
System.out.println ("Wert maximum = " + maximum ");
else-Regel

 

Im oben gezeigten Beispiel gibt es eine else Bedingung von der es nicht offensichtlich ist zu welchem if Block sie gehört.

else-Regel
Ein else-Block gehört immer zum direkt vorhergehenden if-Block

Tipp: Der Quellcode wird bei geschachtelten if Bedingungen schnell unübersichtlich und fehleranfällig.

Verwenden Sie besser die im Folgenden vorgestellte switch Bedingung um komplexe Abfragen zu implementieren.

Mehrfachverzweigung (switch-case)

Eine Alternative zu if-else Anweisungen ist die switch-case Anweisung. Sie erlaubt eine sehr viel übersichtlichere Programmierung wenn es bei einem ganzzahligen Ausdruck mehr als zwei Alternativen gibt.

Im einfachen Fall sieht eine switch-case Anwendung wie folgt aus:

int Wochentag;
...
switch (Wochentag) {
   case 7:
      System.out.println("Sonntag");
      break;
   case 6:
      System.out.println("Samstag");
      break;
   case 5: case 4: case 3: case 2: case 1:
      System.out.println("Wochentag");
      break;
   default:
      System.out.println("Fehler");
}
Switch Anweisung in UML

 

int Wochentag;
...
switch (Wochentag) {
   case 7:
      System.out.println("Sonntag");
      break;
   case 6:
      System.out.println("Samstag");
      break;
   case 5: case 4: case 3: case 2: case 1:
      System.out.println("Wochentag");
      break;
   default:
      System.out.println("Kein gültiger Wochentag!");
}

Besonderheiten der switch-case Anweisung

  • Der zu prüfende Ausdruck muss bestimmten Typen genügen: 
    • byte, short, int, char, enum, String. (Die Unterstützung von String ist neu seit JDK 7, siehe JSR 334, Projekt Coin)
  • Die einzelnen Fälle (cases) werden nicht mit geschweiften Klammern geklammert
  • Das Schlüsselwort break dient zum Rücksprung aus einem Fall. Der nächste Fall wird mit abgearbeitet wenn es weggelassen wird!
  • Es können mehrere Fälle mit Doppelpunkt aufgeführt werden. Der darauf folgende Block wird dann für alle Fälle abgearbeitet.
  • Das Schlüsselwort default erlaubt einen Ausführungsblock anzugeben, der ausgeführt wird wenn kein anderer Fall zum Zuge kam
  • Der Wert in einem Fall (case) muss eine Konstante sein.

Allgemeine Form:

int Wochentag;
...
switch (Ausdruck) {
   case konstante1:
      Anweisung(en);
      break;
...
   case konstante2: case konstante3: ... case konstanteN:
      Anweisung(en);
      break;
   default:
      Anweisung(en);
      break;
}

Beispiel mit unterschiedlichen Break-Anweisungen

int Wochentag;
...
switch (Wochentag) {
   case 7:
      System.out.println("Sonntag");
      break;
   case 6:
      System.out.println("Samstag");
   case 5: case 4: case 3: case 2: case 1:
      System.out.println("Wochentag");
      break;
   default:
      System.out.println("Fehler");
}
Switch Anweisung mit komplexen break statements

 

 

3.2 Schleifen und Felder

Schleifen erlauben die Wiederholungen von Einzelanweisung. Schleifen bestehen typischerweise aus den folgenden Komponenten

  • einen Schleifenrumpf mit den auszuführenden Anweisungen
  • einem Kopf oder Fuß mit einer Ausführungsbedingung
  • oft einer Laufvariablen mit der die Durchläufe kontrolliert werden

Die while Schleife 

Die while Schleife überprüft vor dem Schleifeneintritt ob die Ausführungsbedingung (noch) erfüllt ist. Sie enthält die Schleifenbedingung im Kopf der Schleife.

Die Syntax der while Schleife ist die folgende:
while ( Bedingung) { Anweisung(en)} 

oder

while ( Bedingung) Anweisung; 

Beispiel: Arithmetische Summe 1+2+3+4+5

int i = 1;
int summe= 0;
while (i<=5) {
   summe = summe + i;
   i++;
}

UML Diagramm while Schleife

Die do-while-Schleife

Die do-while-Schleife überprüft nach dem ersten Schleifendurchlauf ob die Ausführungsbedingung erfüllt ist. Sie enthält die Schleifenbedingung im Fuß der Schleife. Die Syntax der do-while Schleife ist die folgende:

do { Anweisung(en)} while ( Bedingung );

oder

do Anweisung; while ( Bedingung );

Beispiel: Beende Schleife wenn Wert durch 3 ohne Rest teilt

int i = 9;
int j = 0; // Zählt Schleifendurchläufe
do {
   j++;
   i--;
} while (i%3 != 0);

Die Schleife wird dreimal durchlaufen.

UM Diagramm do while Schleife

Regel:

  • Die while Schleife ist eine abweisende Schleife: Sie wird nicht notwendigerweise durchlaufen.
  • Die do-while ist eine nicht abweisende Schleife: Sie wird mindestens einmal durchlaufen.

Diese Unterscheidung ist in verschiedenen Bereichen wichtig

  • Variablen werden bei einer abweisenden Schleife eventuell nicht belegt
  • Es wird manchmal in der Qualitätssicherung gefordert, dass alle Zeilen eines Programmes duchlaufen werden. Bei abweisenden Schleifen muss man unter Umständen bei der Implementierung der Testabdeckung mehr investieren.

Die for-Schleife

Die for-Schleife überprüft die Schleifenbedingung vor dem Eintritt in die Schleife.

Ihre Syntax ist die anspruchsvollste der 3 Schleifenarten:

for (Initialiserung; Bedingung; Veränderung) {Anweisung(en)}

oder

for (Initialiserung; Bedingung; Veränderung) Anweisung;

Der Kopf der for-Schleife besteht aus den folgenden drei Teilen:

  • Initialisierungsteil: Laufvariable und Startwert werden festgelegt.
  • Bedingungsteil: Wird der Wert der Bedingung unwahr (false) wird die Schleife verlassen oder nicht betreten. Ansonsten wird sie weiter fortgesetzt.
  • Veränderungsteil: Nach jedem Durchlauf der Schleife wird die Laufvariable entsprechen verändert (Typischerweise inkrementiert oder dekrementiert).

Beispiel einer einfachen for-Schleife

Im folgenden Beispiel wird die for-Schleife "b mal" durchlaufen. Durch das Aufaddieren der Variablen a wird eine Multiplikation von positiven Zahlen ausgeführt.

Ist b gleich Null oder negativ wird die Schleife nicht durchlaufen.

einfache for-Schleife

Geschachtelte Schleifen

Oft ist es notwendig Schleifen zu schachteln. Dies kommt oft bei mehrdimensionalen Datenstrukturen vor bei denen jedes Feld bearbeitet werden muss. Bei jedem Durchlauf der äusseren Schleife wird die innere aufgerufen, die ihre eigenen Durchläufe komplett bei jedem Durchlauf der äusseren Schleife durchführt. Anbei ein Beispiel einer naiven Multiplikation die auf Inkrementieren beruht:

class MultiplizierenNaiv {
public static void main(String[] args) {
int a = 5;
int b = 10;
int result = 0;
for (int i = 1; i <= a; i++) {
System.out.println("Äussere Schleife i = " + i);
for (int j = 1; j <= b; j++) {
System.out.println("Innere Schleife j = " + j);
result++;
}
}
System.out.println(a +"*"+b+" = "+result);
}
}

Geschachtelte Schleifen sind sehr mächtig und sie haben ein viel größeres Potential Rechnerleistung zu binden. Die Aufwände bei zwei geschachtelten Schleifen können quadratisch steigen im Vergleich zum linearen Aufwand einer einfachen Schleife. Generell gilt, dass eine teure Anweisung in einer Schleife n-mal statt einmal durchlaufen werden kann. Es ist daher wichtig auf die folgenden Dinge zu achten:

  • Lassen Sie alle Anweisungen die man vor oder nach der Schleife ausführen kann aus dem Schleifenblock draussen
  • Definieren und Initialisieren Sie Variablen wenn möglich ausserhalb der Schleife und verwenden Sie die Variablen wieder. Die Variable muss sonst jedes mal neu angelegt und wieder gelöscht werden.
  • Verzichten Sie auf unnötige Schleifendurchläufe
  • Seien Sie vorsichtig bei mehrfach geschachtelten Schleifen. Die Anzahl der Durchläufe kann theoretisch sehr schnell, sehr groß werden. Eine dreifach geschachtelte Schleife mit jeweils 1000 Durchläufen wird eine Milliarde mal ausgeführt!

Sprunganweisungen und Schleifenabbrüche mit continue und break Schlüsselwörtern

Java verfügt über zwei Möglichkeiten Schleifen in der Mitte von Ausführungsblöcken zu verlassen. Diese Programmiertechnik sollte man normalerweise vermeiden.

In manchen Fällen ist sie jedoch nützlich. Schleifenabbrüche werden durch die beiden folgenden Schlüsselwörter gesteuert:

  • break: die weitere Abarbeitung im Schleifenblock/rumpf wird abgebrochen. Das Programm verlässt die Schleife und nimmt die Abarbeitung hinter der Schleife wieder auf. die Abbruchkriterien der Schleife werden hier nicht mehr beachtet.
  • continue: die Abarbeitung des aktuellen Schleifendurchlaufs wird beendet. Die Schleife wird jedoch nicht verlassen. Die nächste Iteration wird geplant ausgeführt

Die break Anweisung ist schon vom switch Befehl her bekannt. Mit ihr kann man auch eine if Bedingung beenden.

Beispiel einer continue Anweisung:

... 
for (int i=0; i<= 100; i++) {
   if (i%2 == 0) continue;
   System.out.println("Die Zahl " + i + " ist ungerade");
}

Bei geraden Zahlen wird die Druckanweisung nach der if Anweisung nicht mehr ausgeführt. Die Schleifen werden jedoch alle durchlaufen.

for mit continue Befehl
Beispiel einer break-Anweisung:
...
for (int i=1; i<= 100; i++) {
   if (i== 32) break;
   System.out.println("Die Zahl " + i + " wurde bearbeitet"); }

Die Zahlen von 1 bis 31 werden ausgedruckt. Anschließend wird die Schleife abgebrochen. Die break und continue Anweisungen beziehen sich immer auf die nächst äussere Schleife. Mit Hilfe von Labels (Marken) kann man auch über mehrere geschachtelte Schleifen nach aussen springen.

for mit break Befehl

Verlassen von Blöcken mit Hilfe von Sprungzielen (Label)

Java erlaubt das Benennen von Blöcken mit Hilfe von "Labeln" (Marken). Mit ihnen kann man mit einer break-Anweisung beliebig viele Blöcke auf einmal verlassen.

Das folgende Beispiel zeigt wie man zum Label "Label1" springt um beide Schleifen auf einmal zu verlassen:

class MultiplizierenNaivLabel {
public static void main(String[] args) {
int a = 5;
int b = 10;
int result = 0;
Label1: for (int i = 1; i <= a; i++) {
System.out.println("Äussere Schleife i = " + i);
for (int j = 1; j <= b; j++) {
System.out.println("Innere Schleife j = " + j);
if ((i == 3) && (j == 3)) break Label1;
result++;
}
}
System.out.println(a +"*"+b+" = "+result);
System.out.println("Dieses Ergebnis ist falsch...");
}
}

Endlosenschleifen

Endlosenschleifen sind Schleifen die nicht terminieren. Ein Programm mit einer Endlosschleife sieht aus wie ein "hängendes" Programm. Es bindet jedoch sehr wahrscheinlich (mindestens) einen physikalischen Prozessor vollständig!

Anbei einige Beispiele mehr oder weniger leicht zu erkennende Endloschleifen:

while ( true) { /* Anweisung(en) */ }
...
for ( ; ; ) { /* Anweisung(en) */ }
...
int i=10;
while (i>5) i++;
...
int i=0;
while ( i!=99) {i++; i++;}

Der Übersetzer erkennt diverse einfache Endlosschleifen und meldet die darauf folgende Codezeile als "unreachable code".

Trivia: Welcher Obsthändler hat die Adresse 1 Infinite Loop, Cupertino, CA 95014?

Apple Hauptquartier

Felder (Arrays), Einführung

Die folgende Kurzüberblick von Feldern ist notwendig um die Übungen zu den Kontrollstrukturen zu lösen. Felder werden später noch einmal aufgegriffen und genauer behandelt.

Felder sind Datenbehälter mit einer oder mehreren Dimensionen um eine Anzahl primitive Datentypen gleichen Typs aufzunehmen.

Man kann auf die einzelnen Feldelemente wahlfrei, also direkt mit Hilfe eines Index zugreifen. Java benutzt rechteckige Klammern [] um auf einzelne Elemente eines Feldes zuzugreifen.

Initialisieren und Deklarieren von Feldern

Felder sind im Gegensatz zu Variablen mit primitiven Datentyp Objekte. Dies bedeutet, dass die die Variable nur aus einer Referenz besteht. Das Feld selbst muss dynamisch angelegt werden. Hierfür gibt es eine Reihe von Gründen.

  • Der Übersetzer kann nicht wissen wie groß ein Feld werden kann
  • Man kann mit einer Feldvariablen dynamisch neue Felder verwalten
  • Felder können sehr groß sein. Es ist oft klüger das Feld erst anzulegen wenn man es benötigt und die Größe kennt.

Die Deklaration von Feldern geschieht wie folgt:

int[] x;  // Anlegen einer Referenz, es ist noch keine Größe vorgeben, 
                    // es können noch keine Feldelemente benutzt werden<
double[] y; // Anlegen einer Referenz, es ist noch keine Größe vorgeben, 
                    //es können noch keine Feldelemente benutz werden

Um das eigentliche Feld anzulegen muss der new Operator verwendet werden wie für reguläre Objekte auch um den Speicher zu allozieren. Dies geschieht wie folgt:

int [] x;
x = new int[4];
double[] y;
y = new double[200];

Jetzt zeigt x auf 4 Datenbehälter für Ganzzahlen. Der Zugriff auf die vier Elemente erfolgt über einen Index im Bereich von 0 (erster Wert) bis 3.

Zugriff auf Felder

Das Feld kann nun wie folgt belegt und bearbeitet werden:

int [] x = new int[4];
x[0] = 99; x[1] = 88; x[2] = x[0]+x[1]; x[3] = x[0]*x[1];

Bestimmen der Länge eines Feldes

...geschieht mit der Methode .length(). Hier ein Beispiel:

int[] x;
int groesse;
...
groesse=x.length;

Erweiterte for Schleife (enhanced for-loop)

Seit Java 5 ist es möglich mit der for Schleife Felder komfortabler abzuarbeiten. Man kann mit einer Laufvariable alle Elemente des gegeben Feldes abarbeiten. Wie zum Beispiel:

int [] xxx = { 11, 22, 33, 44, 55};
for ( int position : xxx)  // for each i in xxx[]
  System.out.println("Wert: " + position);

Wird das folgende Ergebnis liefern:

Wert: 11 
Wert: 22
Wert: 33
Wert: 44
Wert: 55

 Die Oracle Java Dokumentation erklärt die Einsatzmöglichkeiten und Grenzen der "enhanced for-loop".

3.2.1 Schleifentransformationen

Die drei Java Schleifentypen sind für unterschiedliche Zwecke entwickelt worden. Man Sie jedoch ineinander Überführen.

Hier sind einige Regeln

While Schleife in Do-While Schleife überführen

  von While Schleife zu do-While Schleife
Java
 while (Bedingung) {Block A }
do
   if (Bedingung)
      { Block A }
while ( Bedingung )
UML UML DIagramm do-While Schleife While Schleife 

Do-While Schleife in While Schleife überführen

Eine Do-While Schleife führt einen Block mindestens einmal aus. Man kann Sie in eine While Schleife tranformieren in dem man den Block kopiert und voran stellt.

Vorsicht: Durch das duplizieren des Codes wird der Code schlechter wartbar!

  von Do-While Schleife zu While Schleife
Java
do
Block A
while ( Bedingung )
Block A
while
(Bedingung) {Block A }
UML UML DIagramm While Schleife do While Schleife

For-Schleife in eine While Schleife überführen 

  von For-Schleife zu While Schleife
Java
for (Initialiserung; Bedingung; Veränderung) {Block A}
Initialisierung
while (Bedingung) { Block A; Veränderung }

While Schleife in For-Schleife überführen

While Schleifen kan man recht einfach in For-Schleifen überführen.

  von While-Schleife zu For-Schleife
Java
while (Bedingung) { Block A;}
for (; Bedingung;) {Block A}

 Eine solche Überführung wird aber recht selten angewendet, da der Initialisierungsanteil und die Veränderung der for Schleife irgendwo im Block A verborgen sind.

Do-While Schleife in For-Schleife überführen

Do-While Schleifen kan man auch recht einfach in For-Schleifen überführen.

  von While-Schleife zu For-Schleife
Java
do { Block A;} while (Bedingung)
Block A; for (; Bedingung;) {Block A;}

 Eine solche Überführung wird aber recht selten angewendet, da der Initialisierungsanteil und die Veränderung der for Schleife irgendwo im Block A verborgen sind.

3.3 Übungen

Duke als Boxer

3.3.1 Übung: Schleifenterminierung

Welche der folgenden Schleifen terminieren?
    int i = 1, j = 1;
    do {
      i = i + j;
      j++;
    } while (i < 200);
b)  
    int i = 1, j = 20;
    while (i + j < i) {
      i = i + 2;
      j--;
    }
c)  
    int i = 1, j = 20;
    while (i + j > i) {
      i = i + 2;
      j--;
    }
d)  
    int i = 100, j = 27;
    while (i != j) {
      i = i / 2;
      j = j / 3;
    }

3.3.2 Übung: Ziffern einer Zahl

Schreiben Sie ein Java-Programm, das eine positive ganze Zahl n einliest und
feststellt, aus wie vielen Ziffern sie besteht.

Hilfestellung:

Einlesen von Zahlenwerten beim Starten von der Kommandozeile als Option wie zum Beispiel:

$ java Main 17 19

erfolgt durch Einlesen von Zeichenketten und Umwandlung in Zahlen. Anbei ein Rahmenprogramm welches das Problem löst:

public class Main {
    public static void main(String[] args) {
    int firstArg=0;
    int stellen=0;
    if (args.length > 0) {
        try {
            firstArg = Integer.parseInt(args[0]);
        } catch (NumberFormatException e) {
            System.err.println("Argument muss Ganzzahl sein");
            System.exit(1);
            }
        System.out.println("Die Zahl firstArg = " + firstArg + " wurde eingelesen");
        // Implementierung gehört hierher
        System.out.println(" Die Zahl " + firstArg + " hat " + stellen + " Stellen!");
    }
}

Eine andere Möglichkeit besteht darin, dass man das Programm startet und bei Bedarf eine Eingabe von der Konsole anfordert.

Achten Sie hier auf die fettgedruckte Importanweisung der Klasse Scanner. Sie ist notwendig um die Klasse zu benutzen:

import java.util.Scanner;
public class Eingabe {
public static void main(String[] args) {
Scanner eingabe = new Scanner(System.in);
System.out.print("Geben Sie die erste Zahl ein: ");
int zahl1 = eingabe.nextInt();
System.out.print("Geben Sie die zweite Zahl ein: ");
int zahl2 = eingabe.nextInt(); System.out.println("Ergebnis: " + zahl1); System.out.println("Ergebnis: " + zahl2);
} }

Das Programm Eingabe kann man auf der Konsole wie folgt bedienen:

$ java Eingabe
Geben Sie die erste Zahl ein: 34
Geben Sie die zweite Zahl ein: 56
Ergebnis: 34
Ergebnis: 56

3.3.3 Übung: Quersumme

Schreiben Sie ein Java-Programm, das die Quersumme einer positiven ganzen Zahl berechnet und ausgibt.

Beispiel: Zahl 4711 --> Quersumme 13.

3.3.4 Übung: Schleifentransformation

Wandeln Sie die folgende for-Schleife in eine while-Schleife, sowie in eine
do-while-Schleife um:
  int s = 0;
  for(;;) {
    int x = System.in.read(); //liest ein Zeichen von der Konsole
    if (x < 0) break;
    s = s + x;
}
Hinweis: Da Zeichen intern durch ganze Zahlen codiert werden, erlaubt Java, mit
Zeichen zu rechnen. char-Werte sind eine Teilmenge der int-Werte.

Der Quellcode wird erst ausführbar wenn man einen potentiellen IO Fehler behandelt. Siehe

...
import java.io.IOException;
...
            try {
                x = System.in.read(); //liest ein Zeichen von der Konsole
            } catch (IOException ex) {
                System.out.println(" Java IO Exception");
            }

3.3.5 Übung: Zahlenstatistik

Schreiben Sie ein Java-Programm, das eine Zahlenfolge liest und ihren
größten und kleinsten Wert sowie ihren Mittelwert berechnet und ausgibt.

Tipp: Die Wertebereiche für die Zahlen sind Ihnen freigestellt. Der Durchschnittswert sollte korrekt sein

Hilfestellung:

Einlesen eines Feldes (Arrays) von der Kommandozeile

package block3;
public class Zahlenstatistik {
    public static void main(String[] args) {
        int feld[];
        if (args.length > 0) {
            feld = new int[args.length];
            try {
                for (int i=0;i<args.length;i++) {
                    feld[i] = Integer.parseInt(args[i]);
                    // Einlesen der Kommandozeilenargumente und umwandeln in Ganzzahlen
                    }
            } catch (NumberFormatException e) {
                System.err.println("Argument muss Ganzzahl sein");
                System.exit(1);
                }
        // Ab hier steht das Feld mit allen Werten zur Verfügung
    }
}

3.3.6 Übung: Primfaktorzerlegung

Schreiben Sie ein Java-Programm, das eine positive ganze Zahl n einliest und in ihre Primfaktoren zerlegt.
Beispiel:
  • Die Zahl 100 besteht aus den Primfaktoren 2, 2, 5, 5;
  • die Zahl 252 aus den Primfaktoren 3, 3, 4, 7.

Option: Optimieren sie Ihr Programm auf die kürzeste Laufzeit. Verwenden Sie hierzu den Nanotimer von JDK 6.0 wie folgt:

package block3;
public class Primzahlzerlegung {
    public static void main(String[] args) {
        int firstArg = 0;
        int p=0;
        long time;
        time= System.nanoTime();
        if (args.length > 0) {
            try {
                p = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                System.err.println("Argument muss Ganzzahl sein");
                System.exit(1);
                }
            System.out.println("Eingelesene Zahl: " + p);
        }
    // Die Eingabe der Kommandozeile liegt als Variable p vor.
    // Implementierung ...
    time= System.nanoTime() - time;
    System.out.println("Zeit in m: "+ time);
    }
}

Überlegungen:

  • Welche Zeit wird hier gemessen?
  • Was hat ausser dem Algorithmus noch Einfluss auf die Ausführungsgeschwindigkeit?
  • Sind Ihre Messungen wiederholbar?
  • Welche Risiken hat eine Optimierung des Algorithmus?
  • Wie wichtig ist die Schnelligkeit eines Programms?

3.3.7 Übung: Codevereinfachung

Vereinfachen Sie folgende Codestücke:
 
a)  
// Annahme: j >= 0
    i = 0;
    while (i != j) i++;
b)  
  while (a < b) {
      c = a;
      a = b;
      b = c;
    }

3.3.8 Übung: Wochentagberechnung

Lesen Sie ein Datum in Form dreier Zahlen für den Tag, den Monat und das Jahr sowie eine weitere Zahl zwischen 0 und 6 ein, die den Wochentag (Sonntag bis Samstag) des 1. Januars dieses Jahres darstellt. Berechnen Sie den Wochentag des eingelesenen Datums und geben Sie diesen aus. Sie können davon ausgehen, dass die Eingaben korrekt sind. Berücksichtigen Sie auch Schaltjahre.

3.3.9 Übung: Überlaufprüfung

Lesen Sie zwei 32 Bit Ganzahlen (int) a und b ein und prüfen Sie, ob bei ihrer Addition ein Überlauf stattfindet, also eine Summe entstehen würde, die größer als 231 - 1 oder kleiner als  -231   ist.

3.3.10 Übung: Schnitt zweier Linien

Lesen Sie die Endpunkte zweier horizontaler oder vertikaler Linien in Form ihrer x- und y-Koordinaten ein und prüfen Sie, ob sich die beiden Linien schneiden. Die beiden Linien befinden sich in einem zweidimensionalen kartesischen System:

Hinweis: Wenn es einen Schnittpunkt gibt ist er (c,b)

3.3.11 Übung: Codevereinfachung

Vereinfachen Sie folgende Codestücke:

a)  

    if (b == 0)
      a = 2 * c;
    else
      if (c != 0)
        a = a * b + 2 * c;
      else
        a = a * b;

b)  

    if (x < 0 && y < 0)
      a = x * y;
    else
      if (x < 0)
        a = x * (-y);
      else
        if (y > 0)
          a = (-x) * (-y);
        else
          a = x * (-y);

3.3.12 Übung: Dreiecksbestimmung

Schreiben Sie ein Java-Programm, das die Seitenlängen eines Dreiecks einliest und prüft, ob es ein

  • gleichseitiges
  • gleichschenkeliges
  • rechtwinkeliges
  • sonstiges gültiges
  • ungültiges Dreieck ist.

Ein Dreieck ist ungültig, wenn die Summe zweier Seitenlängen kleiner oder gleich der dritten Seitenlänge ist. Beachten Sie, dass ein Dreieck sowohl rechtwinkelig als auch gleichschenkelig sein kann!

3.3.13 Übung: Sortieren

Schreiben Sie ein Java-Programm, das 3 Zahlen a, b und c einliest und sie in sortierter Reihenfolge wieder ausgibt. Im Rahmenprogramm muss die letzte Zeile durch einen Sortieralgorithmus und Ausgaben ersetzt werden.

public class Sortieren {
   public static void main(String[] args) {
       int a = 0;
       int b = 0;
       int c = 0;

if (args.length > 2 ) {
try {
a = Integer.parseInt(args[0]);
b = Integer.parseInt(args[1]);
c = Integer.parseInt(args[2]);
} catch (NumberFormatException e) {
System.err.println("Argument muss Ganzzahl sein");
System.exit(1);
}
}
System.out.println("Eingelesene Werte: " + a + ", " + b + ", " + c);

System.out.println("Anstatt dieser Zeile muss ein Sortieralgorithmus implementiert werden...";
}
}

3.3.14 Übung: Plausibilitätsprüfung

Lesen Sie ein Datum in Form dreier Zahlen für den Tag, den Monat und das Jahr ein. Prüfen Sie, ob es sich um ein gültiges Datum handelt. Berücksichtigen Sie auch Schaltjahre.

Hinweis: Gregorianischer Kalender: Ein Jahr ist ein Schaltjahr, wenn es durch 4 teilbar ist. Jahre, die durch 100, aber nicht durch 400 teilbar sind, sind keine Schaltjahre.

3.3.15 Übung: Textverschlüsselung

Schon im alten Rom verschlüsselte man Nachrichten. Ein einfaches Verschlüsselungsverfahren ist von Julius Cäsar überliefert. Es verschiebt jeden Buchstaben der Nachricht um einen fixen Wert n. Ist n gleich 2, so wird 'A' auf 'C', 'B' auf 'D' und 'Z' auf 'B' verschoben. Ziffern und Sonderzeichen werden nicht verschoben. Schreiben Sie ein Programm, das eine Zahl n und einen beliebigen Text liest und ihn nach obigem Verfahren verschlüsselt wieder ausgibt.

3.3.16 Übung: Verzweigungen

Schreiben Sie ein Java-Programm, das drei Werte x, y und z einliest und prüft, ob

  • x, y und z nicht lauter gleiche Werte enthalten,
  • x, y und z lauter verschiedene Werte enthalten,
  • mindestens zwei Werte gleich sind.

3.3.17 Übung: Häufigkeit von Zeichen

Schreiben Sie ein Programm, das einen Text liest und die Häufigkeit der darinvorkommenden Zeichen berechnet. Geben Sie die Zeichenhäufigkeit als Histogramm aus. Beispiel:

a ****
b **
c ********
...

 Hinweis: Dieses Programm setzt Kenntnisse von Feldern vorraus die erst später behandelt werden

3.3.18 Übung: Weckzeiten implementieren

Implementieren Sie zwei logische Tests für die Kontrolle der Weckzeit einer Uhr.

Diese Aufgabe baut auf der Übung zur Implementierung einer analogen Uhr auf.

Sie benötigen 3 Klassen für diese Aufgabe die alle im gleichen Verzeichnis stehen müssen:

  • Klasse Zeiger (Lösung von 2.4.11) oder Ihre eigene Implementierung. Diese Klasse muss nicht modifiziert werden.
  • Klasse WeckerUhr (weiter unten). Die Klasse muss nicht modifiziert werden. Die Klasse enthält die main() Methode. Es muss immer diese Klasse (java WeckerUhr) zum Starten der Anwendung verwendet werden!
  • Klasse Weckzeit: Diese Klasse muss in der Übung modifiziert werden. Die Vorlage der Klasse ist bereits übersetzbasr und ausführbar.

Es reicht die Klasse WeckerUhr zu übersetzen. Die beiden anderen Klassen werden automatisch rekursiv mitübersetzt.

Nach dem Starten der Anwendung mit java WeckerUhr erscheint das folgende Fenster:

Weckeruhr im normalen Zustand

Uhr im normalen Zustand

Weckeruhr beim Klingeln

Uhr beim Klingeln (roter pulsierender Kreis)

Erste Aufgabe: Kontrolle der Wertebereiche

Die Klasse Weckzeit enthält eine Methode korrekteWeckzeit(int h, int m, int s). Diese Methode prüft die Eingaben des GUI auf korrekte Wertebereiche. Das Ergebnis wird in einer boolschen Variablen result gespeichert. Die Vorlage liefert bei allen Eingaben false. Belegen Sie die Variable so, dass vernünftige Werte akzeptiert werden.

Fragen:

  • Was sind vernünftige Werte für Stunde (h), Minute(m), Sekunde(s)?
  • Welche logische Verknüpfung muss man nutzen wenn alle Werte im korrekten Bereich sein sollen?

Der Erfolg Ihrer Implementierung können Sie daran erkennen, dass die Eingaben des GUI nach dem klicken des OK Knopfs in das Anzeigenfeld übernommen worden sind.

Zweite Aufgabe: Bestimmen der richtigen Zeitpunkte zum Wecken

Das Lösen der ersten Teilaufgabe ist eine Voraussetzung für den zweiten Teil (Ihr Wecker funktioniert sonst nur Mittags und um Mitternacht...)

Das Hauptprogramm der Klasse WeckerUhr ruft in der aktuellen Implementierung viermal pro Sekunde die Methode klingeln() auf um zu kontrollieren ob der Wecker klingeln soll. Ändern Sie die Referenzimplementierung derart, dass nur nach der eingebenen Weckzeit für eine bestimmte Periode geklingelt wird.

Die zu modifizierende Referenzimplementierung:

result = (aktS%10 == 0);

lässt den Wecker bei allen Sekundenwerten die durch 10 teilbar sind für eine Sekunde klingeln. 

Die Variablen aktH, aktM, aktS enthalten die aktuelle Zeit. Die Variablen wzh, wzm, wzs enthalten die Weckzeit.

Fragen:

  • Wie lange soll der Wecker klingeln?
  • (Zur Verfeinerung) Wie kann man gewährleisten, dass der Wecker auch um 11:59:50 für 15 Sekunden klingelt?

Klasse Weckzeit

public class Weckzeit {
int wzh = 0; // Weckzeit in Stunden
int wzm = 0; // Weckzeit in Minuten
int wzs = 0; // Weckzeit in Sekunden
public void setzeWeckzeit(int hh, int mm, int ss) {
if (korrekteWeckzeit(hh,mm,ss)) {
wzh = hh;
wzm = mm;
wzs = ss;
}
}
public boolean korrekteWeckzeit(int h, int m, int s) {
boolean result;
// benutzen die Variablen h,m,s um eine gültige Zeit zu bestimmen
result = false;
return result;
}
public boolean klingeln(int aktH, int aktM, int aktS) {
boolean result;
// benutzen die Variablen der aktuellen Zeit aktH (Stunde),
// aktM (Minute), aktS (Sekunde) und die Weckzeit wzmh, wzm, wzs
// um zu bestimmern ob der Wecker klingeln soll

// Verbessern Sie diese Zuweisung
// In der aktuellen Implementieren klingelt der Wecker
// alle 10 Sekunden für 1 Sekunde
result = (aktS%10 == 0);
return result;
}
}

Klasse WeckerUhr


/*
* Zeichnen einer analogen Uhr in einem JFrame
*/
import java.awt.*;
import java.awt.event.*;
import java.util.Calendar;
import javax.swing.*;
/**
*
* @author sschneid
*/
public class WeckerUhr extends JPanel {

int sekunde = 0;
int minute = 0;
int stunde = 0;
boolean klingeln = false;
String tzString; // aktuelle Zeitzone
int initialHeight;
float zoom = 1;
boolean an = false;
JFrame hf; // Das Fenster der Anwendung
Container myPane;
JTextField h,m,s;
JButton eingabe;
Weckzeit wz;
int klingelRadius = 0;
/**
* Konstruktor der Klasse. Er initialisiert die Grafik
*/
public WeckerUhr() {
wz = new Weckzeit();
hf = new JFrame("Uhr");
h = new JTextField(2);
m = new JTextField(2);
s = new JTextField(2);
eingabe = new JButton("OK");
eingabe.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int stunde, minute, sekunde;
try {
stunde = Integer.parseInt(h.getText());
minute = Integer.parseInt(m.getText());
sekunde = Integer.parseInt(s.getText());
}
catch (NumberFormatException ex) {
// Es wurde keine korrekte Zahl eingegeben
stunde = -100;
minute = -100;
sekunde= -100;
}
wz.setzeWeckzeit(stunde,minute,sekunde);
}
});
// Beenden der Anwendung bei Schließen des Fenster
hf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Aufbau des Contentpanes
myPane = hf.getContentPane();
myPane.add(this, BorderLayout.CENTER);
JPanel wzPanel = new JPanel(new GridLayout(1,0));
wzPanel.add(new JLabel("hh:mm:ss"));
wzPanel.add(h);
wzPanel.add(m);
wzPanel.add(s);
wzPanel.add(eingabe);
myPane.add(wzPanel,BorderLayout.SOUTH);
// Erzeuge einen Menüeintrag zum Beenden des Programms
JMenuBar jmb = new JMenuBar();
JMenu jm = new JMenu("Datei");
JMenuItem exitItem = new JMenuItem("Beenden");
exitItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
jm.add(exitItem);
jmb.add(jm);
hf.setJMenuBar(jmb);
hf.pack();
// Das JFrame sichtbar machen
// Gewünschte Größe setzen
// 1. Parameter: horizontale Größe in Pixel
// 2. Parameter: vertikale Größe in Pixel
hf.setSize(2 * Zeiger.maxRadius + 80,
2 * Zeiger.maxRadius + 130);
hf.setVisible(true);
hf.setAlwaysOnTop(true);
initialHeight = getHeight();
}
/**
* Hauptprogramm der Anwendung. Es werden keine Eingabeparameter benötigt
* @param args dieser Parameter wird nicht ausgewertet
*/
public static void main(String[] args) {
WeckerUhr dieUhr = new WeckerUhr();
dieUhr.tickTack();

}
/**
* Diese Methode verwaltet den Zeitgeber-thread. Dieser Thread belegt
* die statischen Variablen der Uhrzeit neu
*/
public void tickTack() {
try {
boolean blinken = false;
while (true) {
Thread.sleep(250); // Schlafe x Millisekunden
// Hole Systemzeit und belege statische Variablen
Calendar call = Calendar.getInstance();
tzString = call.getTimeZone().getDisplayName();
sekunde = call.get(Calendar.SECOND);
minute = call.get(Calendar.MINUTE);
stunde = call.get(Calendar.HOUR);
klingeln = wz.klingeln(stunde,minute,sekunde);
if (blinken){
klingelRadius=100;
}
else {
klingelRadius=30;
}
blinken = !blinken;
repaint();
}
} catch (InterruptedException e) {
System.out.println(
"Die Anwendung wird wegen einer Ausnahme beendet");
}
}
/**
* Überladene Paintmethode. Sie führt alle Zeichenoperationen im Panel aus
* @param g vom Laufzeitsystem übergebenes Graphikobjekt.
*/
@Override
public void paint(Graphics g) {
super.paint(g);
zoom = (float)getHeight()/(float)initialHeight;
int maxRadius = Zeiger.maxRadius;
int xCenter = (int)(maxRadius*zoom) + 40;
int yCenter = (int)(maxRadius*zoom) + 20;
float fontSize = g.getFont().getSize2D();
int charCenterOffSet = (int)(fontSize/2);
String timeString = stunde + ":" + minute + ":" + sekunde
+ " " + tzString;
String klingelString;
klingelString = wz.wzh +":"+wz.wzm + ":" +wz.wzs +" klingeln";
if (klingeln) {
g.setColor(Color.red);
g.fillOval(xCenter-((int)(zoom*klingelRadius/2)),
yCenter-((int)(zoom*klingelRadius/2)),
(int)(klingelRadius*zoom),
(int)(klingelRadius*zoom));
}
// Zeichne Uhrenhintergrung und Koordinatensystem
g.setFont(g.getFont().deriveFont(fontSize));
g.setColor(Color.BLACK); // Farbe
g.drawArc(xCenter - 5, yCenter - 5, 10, 10, 0, 360);
g.drawLine(xCenter, yCenter, xCenter + 40, yCenter);
g.drawLine(xCenter, yCenter, xCenter, yCenter + 40);
g.drawString("X", (int)(xCenter + 45*zoom),
yCenter + +charCenterOffSet);
g.drawString("Y", xCenter - charCenterOffSet,
(int)(yCenter + 55*zoom));
g.drawString("12",xCenter - charCenterOffSet,
(int)(yCenter - maxRadius*zoom));
g.drawString("3", (int)(xCenter + maxRadius*zoom),
yCenter + charCenterOffSet);
g.drawString("6", xCenter - charCenterOffSet,
(int)(yCenter + 2*charCenterOffSet+maxRadius*zoom));
g.drawString("9", (int)(xCenter - maxRadius*zoom - charCenterOffSet),
yCenter + charCenterOffSet);
// Zeichne aktuelle Zeit zum Debuggen
g.drawString(timeString, 0,
(int)(yCenter + maxRadius*zoom));
// Zeichne Weckzeit zum Debuggen
g.drawString(klingelString, 0, (int)(yCenter + maxRadius*zoom + 25));
// Zeichne Stundenzeiger
g.setColor(Color.BLACK);
g.drawLine(xCenter, yCenter,
(int)(xCenter + Zeiger.stundeX(stunde)*zoom),
(int)(yCenter + Zeiger.stundeY(stunde)*zoom));
g.drawString("h["
+ Zeiger.stundeX(stunde)
+ "," + Zeiger.stundeY(stunde) + "]",
0, (int)(yCenter + maxRadius*zoom - (3*fontSize)));
// Zeichne Minutenzeiger
g.setColor(Color.RED);
g.drawLine(xCenter, yCenter,
(int)(xCenter + Zeiger.minuteX(minute)*zoom),
(int)(yCenter + Zeiger.minuteY(minute)*zoom));
g.drawString("m["
+ Zeiger.minuteX(minute) + ","
+ Zeiger.minuteY(minute) + "]", 0,
(int)(yCenter + maxRadius*zoom - (2*fontSize)));
// Zeichne Sekundenzeiger
g.setColor(Color.BLUE);
g.drawLine(xCenter, yCenter,
(int)(xCenter + Zeiger.sekundeX(sekunde)*zoom),
(int)(yCenter + Zeiger.sekundeY(sekunde)*zoom-fontSize));
g.drawString("s["
+ Zeiger.sekundeX(sekunde) + ","
+ Zeiger.sekundeY(sekunde) + "]", 0,
(int)(yCenter + maxRadius*zoom - fontSize));
}
}

 

 

3.4 Lösungen

3.4.1 Schleifenterminierung

Welche der folgenden Schleifen terminieren?
 
a)  Terminiert
    int i = 1, j = 1;
    do {
      i = i + j;
      j++;
    } while (i < 200);
b)  Terminiert: Schleife wird nicht durchlaufen
    int i = 1, j = 20;
    while (i + j < i) {
      i = i + 2;
      j--;
    }
c)  Terminiert
    int i = 1, j = 20;
    while (i + j > i) {
      i = i + 2;
      j--;
    }
d)  Terminiert
    int i = 100, j = 27;
    while (i != j) {
      i = i / 2;
      j = j / 3;
    }

3.4.2 Ziffern einer Zahl

public class Main {
    public static void main(String[] args) {
    int firstArg = 0;
    int stellen = 0;
    int a;
    if (args.length > 0) {
        try {
            firstArg = Integer.parseInt(args[0]);
        } catch (NumberFormatException e) {
            System.err.println("Argument muss Ganzzahl sein");
            System.exit(1);
            }
        System.out.println("Die Zahl firstArg = " + firstArg + " wurde eingelesen");
        a = firstArg;
         while (a !=0) {
               stellen++;
               a/=10;    
           }
        }
        System.out.println(" Die Zahl " + firstArg + " hat " + stellen + " Stellen!");
    }
}

3.4.3 Quersumme

package block3;
public class Quersumme {

    public static void main(String[] args) {
        int firstArg = 0;
        int querSumme = 0;
        int a;
        if (args.length > 0) {
            try {
                firstArg = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                System.err.println("Argument muss Ganzzahl sein");
                System.exit(1);
                }
            System.out.println("Die Zahl " + firstArg + " wurde eingelesen");
            a = firstArg;
            while (a !=0) {
                querSumme+= a%10;
                a/=10;
            }
        System.out.println("Die Zahl " + firstArg + " hat die Quersumme " + querSumme + "!");
        }
    }
}

3.4.4 Schleifentransformation

package block3;
import java.io.IOException;

public class Schleifentransformation {
    public static void main(String[] args) {
        int s = 0;
        int x = 0;

        while(true) { //Option 1
            try {
                x = System.in.read(); //liest ein Zeichen von der Konsole
            } catch (IOException ex) {
                System.out.println(" Java IO Exception");
            }
            if (x < 0) break;
            s = s + x;
        }
        do { //Option 2
            try {
                x = System.in.read(); //liest ein Zeichen von der Konsole
            } catch (IOException ex) {
                System.out.println(" Java IO Exception");
            }
            if (x < 0) break;
            s = s + x;
        } while(true);
        }
}

3.4.5 Zahlenstatistik

package block3;
public class Zahlenstatistik {
    public static void main(String[] args) {
        int feld[];
        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;
        float average = 0;
        if (args.length > 0) {
            feld = new int[args.length];
            try {
                for (int i=0;i<args.length;i++) {
                    feld[i] = Integer.parseInt(args[i]);
                    if (feld[i] < min) {min=feld[i];}
                    if (feld[i] > max) {max=feld[i];}
                    average += feld[i];
                    }
            } catch (NumberFormatException e) {
                System.err.println("Argument muss Ganzzahl sein");
                System.exit(1);
                }
            average= average/(float)args.length;
            System.out.println("Anzahl eingelesene Werte: " + args.length);
            System.out.println("Kleinster Wert: " + min);
            System.out.println("Größter Wert: " + max);
            System.out.println("Durchschnitt: " + average);
    }
}
}

3.4.6 Primzahlzerlegung

package block3;
public class Primzahlzerlegung {
    public static void main(String[] args) {
        int firstArg = 0;
        int p=1;
        long time;
        time= System.nanoTime();
        if (args.length > 0) {
            try {
                p = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                System.err.println("Argument muss Ganzzahl sein");
                System.exit(1);
                }
            System.out.println("Eingelesene Zahl: " + p); 
            while (p>1) {
                for (int i=2; i<= p; i++) {
                    while (p%i == 0) {
                        System.out.println("Primfaktor: " + i);
                        p /= i;
                    }
                }
            }
        }
    time= System.nanoTime() - time;
    System.out.println("Zeit in m: "+ time);
    }
}

Optimiert:

// Gute Kandidaten
// 2^30-1 = 1073741823
// 2^31-1 = 2147483647
public class Main {

    public static void Primzahlzerlegung(String[] args) {
        int firstArg = 0;
        long p = 1;
        long time;
        time = System.nanoTime();
        if (args.length > 0) {
            try {
                p = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                System.err.println("Argument muss Ganzzahl sein");
                System.exit(1);
            }
            long i = 2;
            System.out.println("Eingelesene Zahl: " + p);
            while ((p > 1) && (i <= p)) {
                if (p % i == 0) {
                    System.out.println("Primfaktor: " + i);
                    p /= i;
                    i = 2;
                } 
                else {
                    if (i > Math.sqrt(p)) {
                        // Beende Suche wenn Wurzel von P erreicht
                        i = p;
                    } else {
                        i++;
                    }
                }
            }
        }
        time = System.nanoTime() - time;
        System.out.println("Zeit in m: " + time);
    }
}

3.4.7 Codevereinfachung

a.)

// Annahme: j >= 0
    i = j;
b)  
a = (a<b) ? b : a;
b = (a<b) ? a : b;
c = (a<b) ? a : c;

3.4.8 Wochentagberechnung

Hier wurde nur der julianische Kalender implementiert nicht der (aktuelle) gregorianische Kalender.

package block3;
public class WochentagBerechnung {
    public static void main(String[] args) {
       int monatOffSet[] = new int[13];
       int tag = 0;
       int monat = 0;
       int jahr=0;
       int wochentag0101startJahr= 6;
       int jahresOffSet; // Bedeutung siehe Feld tagText
       String[] tagText = {"Sonntag", "Montag", "Dienstag", "Mittwoch",
                            "Donnerstag", "Freitag", "Samstag"};
int wochentag; int startJahr = 2010; int anzSchaltJahre; tagText[0] = "Sonntag"; tagText[1] = "Montag"; tagText[2] = "Dienstag"; tagText[3] = "Mittwoch"; tagText[4] = "Donnerstag"; tagText[5] = "Freitag"; tagText[6] = "Samstag"; if (args.length > 1 ) { try { tag = Integer.parseInt(args[0]); monat = Integer.parseInt(args[1]); jahr = Integer.parseInt(args[2]); wochentag0101startJahr = Integer.parseInt(args[3]); } catch (NumberFormatException e) { System.err.println("Argument muss Ganzzahl sein"); System.exit(1); } monatOffSet[1] = 0; monatOffSet[2] = (monatOffSet[1]+31)%7; monatOffSet[3] = (monatOffSet[2]+28)%7; monatOffSet[4] = (monatOffSet[3]+31)%7; monatOffSet[5] = (monatOffSet[4]+30)%7; monatOffSet[6] = (monatOffSet[5]+31)%7; monatOffSet[7] = (monatOffSet[6]+30)%7; monatOffSet[8] = (monatOffSet[7]+31)%7; monatOffSet[9] = (monatOffSet[8]+31)%7; monatOffSet[10] = (monatOffSet[9]+30)%7; monatOffSet[11] = (monatOffSet[10]+31)%7; monatOffSet[12] = (monatOffSet[11]+30)%7; jahresOffSet = (monatOffSet[12]+31)%7; if (monat>2) anzSchaltJahre=jahr/4-startJahr/4; else anzSchaltJahre=(jahr-1)/4-startJahr/4; wochentag = (monatOffSet[monat] + tag - 1 + wochentag0101startJahr + (jahr-startJahr)*jahresOffSet + anzSchaltJahre )%7; System.out.println ("Der 1.1."+ startJahr+" war ein " + tagText[wochentag0101startJahr]); System.out.println ("Der "+ tag + "."+monat+"."+jahr+ " ist ein " + tagText[wochentag]); // Optional: Datumsbestimmung mit Hilfe der Java Infrastruktur Calendar myCal = new GregorianCalendar(jahr,monat+1,tag); System.out.println ("Gregorianischer Java Kalender:"); System.out.println ("Der " + tag + "." + monat + "." + jahr+ " ist ein " + tagText[myCal.get(Calendar.DAY_OF_WEEK)]); } } }

3.4.9 Überlauf

Bei der Addition von Ganzzahlen gibt es keine Überlaufsprüfung.

package block3; 
public class Ueberlauf {
  public static void main(String[] args) {
       int a = 0;
       int b = 0;
       int result;
       if (args.length > 1 ) {
        try {
           a = Integer.parseInt(args[0]);
           b = Integer.parseInt(args[1]);
        } catch (NumberFormatException e) {
            System.err.println("Argument muss Ganzzahl sein");
            System.exit(1);
            }
        }
       System.out.println("Eingelesene Werte: " + a + ", " + b);
       result = a + b;
       if ((result < a) || (result < b))
           System.out.println("Überlauf!");
       else
           System.out.println(a + " + " + b + " = " + result);
    }
}

3.4.10 Schnitt zweier Linien

Eingabereihenfolge der Parameter ist: a1, a2, b ,c , d1, d2

package block3;
public class GeradenSchnitt {

    public static void main(String[] args) {
        double a1 = 0.0;
        double a2 = 0.0;
        double b  = 0.0;
        double c  = 0.0;
        double d1 = 0.0;
        double d2 = 0.0;
        double tmp;
        if (args.length > 5) {
            try {
                a1 = Double.parseDouble(args[0]);
                a2 = Double.parseDouble(args[1]);
                b  = Double.parseDouble(args[2]);
                c  = Double.parseDouble(args[3]);
                d1 = Double.parseDouble(args[4]);
                d2 = Double.parseDouble(args[5]);
            } catch (NumberFormatException e) {
                System.err.println("Argument muss Fließkommazahl sein");
                System.exit(1);
            }
        }
        System.out.println("Linie 1: (" + a1 + "," + b + ") bis ("
                + a2 + "," + b + ")");
        System.out.println("Linie 2: (" + c + "," + d1 + ") bis ("
                + c + "," + d2 + ")");
        // Schnittpunkt ist (c,b)
        // Sortieren von a1, a2 .
        if (a1 > a2) {
            tmp = a1; a1 = a2; a2 = tmp;}
        // Sortieren von d1, d2 .
        if (d1 > d2) {
            tmp = d1;
            d1 = d2;
            d2 = tmp;
        }
        System.out.println("Nach sortieren...");
        System.out.println("Linie 1: (" + a1 + "," + b + ") bis ("
                + a2 + "," + b + ")");
        System.out.println("Linie 2: (" + c + "," + d1 + ") bis ("
                + c + "," + d2 + ")");
        if ((a1 <= c) && (c <= a2) && (d1 <= b) && (b <= a2)) 
            System.out.println("Die beiden Strecken schneiden sich");
        else
          System.out.println("Die beiden Strecken schneiden sich nicht");
    }
}

Beispielläufe

java block3.GeradenSchnitt 3.1 8.2 4.0 5.2 11.1 4.9

Linie 1: (3.1,4.0) bis (8.2,4.0)
Linie 2: (5.2,11.1) bis (5.2,4.9)
Nach sortieren...
Linie 1: (3.1,4.0) bis (8.2,4.0)
Linie 2: (5.2,4.9) bis (5.2,11.1)
Die beiden Strecken schneiden sich nicht

java block3.GeradenSchnitt 3.1 8.2 6.0 5.2 11.1 4.9

Linie 1: (3.1,6.0) bis (8.2,6.0)
Linie 2: (5.2,11.1) bis (5.2,4.9)
Nach sortieren...
Linie 1: (3.1,6.0) bis (8.2,6.0)
Linie 2: (5.2,4.9) bis (5.2,11.1)
Die beiden Strecken schneiden sich

3.4.11 Codevereinfachung

a)

a = a * b + 2 * c;

b)

Wahrheitstafel
  x<0 x>=0
y<0 a=x*y a=x*(-y)
y>=0 a = x*(-y) a=(-x)*(-y)

 

y = ((x<0) && (y<0)) ?  y : -y;
x = ((y>=0) && (x>=0))? -x :  x;
a = x * y;;

3.4.12 Dreiecksbestimmung

package block3;
public class Dreiecksvergleich {
public static void main(String[] args) {
    float x[] = new float[3];
    int j;
    if (args.length > 2 ) {
        try {
           float a;
           for (int i=0; i <3; i++) {
               x[i] = Float.parseFloat(args[i]);
               j=i; // Sortiere Eingabe in steigender Reihenfolge
               while (j>0)
                   if (x[j] < x[j-1]) {
                        a = x[j-1];
                        x[j-1] = x[j];
                        x[j]= a;
                        j--; }
                    else j=0;
                   }
               }
        catch (NumberFormatException e) {
            System.err.println("Argument muss Fließkommazahl sein");
            System.exit(1);
            }
        System.out.println("Eingebene Werte: " + x[0] + "; "
                                               + x[1] + "; "
                                               + x[2]);
        // Die folgenden Vergleiche verlassen sich darauf, dass das Feld
        // aufsteigend sortiert ist.
        if (x[0]+x[1]<=x[2])
            System.out.println("Dreieck ist ungültig");
        else if (x[0] == x[2])
            System.out.println("Dreieck ist gleichseitig (und gleichschenkelig)");
        else {
            if ((x[0] == x[1]) || (x[1] == x[2]))
                System.out.println("Dreieck ist gleichschenkelig (nicht gleichseitig)");
            if (x[0]*x[0]+x[1]*x[1]==x[2]*x[2])
                System.out.println("Dreieck ist rechtwinklig");
        }

    }
}
}

3.4.13 Sortieren

package block3;
public class Sortieren {
public static void main(String[] args) {
       int a = 0;
       int b = 0;
       int c = 0;

       if (args.length > 2 ) {
        try {
           a = Integer.parseInt(args[0]);
           b = Integer.parseInt(args[1]);
           c = Integer.parseInt(args[2]);
        } catch (NumberFormatException e) {
            System.err.println("Argument muss Ganzzahl sein");
            System.exit(1);
            }
        }
       System.out.println("Eingelesene Werte: " + a + ", " + b + ", " + c);
       // alle Moeglichkeiten testen
       if ((a <= b) && (b<=c))
           System.out.println("Sortierte Werte: " + a + ", " + b + ", " + c);
       else if ( (a <= c) && (c <= b) )
           System.out.println("Sortierte Werte: " + a + ", " + c + ", " + b);
       else if ( (b <= a) && (a <= c) )
           System.out.println("Sortierte Werte: " + b + ", " + a + ", " + c);
        else if ( (b <= c) && (c <= a) )
           System.out.println("Sortierte Werte: " + b + ", " + c + ", " + a);
       else if ( (c <= a) && (a <= b) )
           System.out.println("Sortierte Werte: " + c + ", " + a + ", " + b);
       else if ( (c <= b) && (b <= a) )
           System.out.println("Sortierte Werte: " + c + ", " + b + ", " + a);
}
}

3.4.14 Plausibilitätsprüfung

package block3;
public class Plausibilitaetspruefung {
    public static void main(String[] args) {
       int monatTage[] = {0, 31, 28, 31, 30, 31, 30,
                             31, 31, 30, 31, 30, 13};
int tag = 0; int monat = 0; int jahr = 0; if (args.length > 2 ) { try { tag = Integer.parseInt(args[0]); monat = Integer.parseInt(args[1]); jahr = Integer.parseInt(args[2]); } catch (NumberFormatException e) { System.err.println("Argument muss Ganzzahl sein"); System.exit(1); } System.out.println("Eingabe ist: " + tag + "." + monat + "." + jahr); if ((monat>0) && (monat < 13) && (tag>0)) if (monatTage[monat] >= tag) System.out.println("Datum ist gültig"); else if ((monat == 2) && (tag == 29) && (jahr%4 == 0) && ((jahr%100 !=0) || (jahr%400==0)) ) System.out.println("Datum ist gültig (Schalttag)"); else System.out.println("Datum ist ungültig"); } } }

3.4.15 Textverschlüsselung

Die vorgestellte Lösung ist eine naive, aber in diesem Fall korrekte Lösung da hier der implementierte Wert eines Zeichen im Vordergrund steht und nicht die Bedeutung des Zeichens.

Sie basiert auf der naiven Annahme, dass die den Zeichen (Buchstaben) zugeordnete Zahlenwerte der Sortierreihenfolge der Buchstaben entspricht. Diese Annahme ist für lateinische Zeichensätze meißten, mehr oder weniger richtig. Im Allgemeinen sollte man bei Programmentwicklung an die Internationalisierung denken. Hier gilt:

  • Die lexikografische Ordnung (Sortierreihenfolge) ist länderabhängig und hängt von der Landeseinstellung (locale, Codepage) der eingestellten Umgebung ab.
    • Beispiel: Die Buchstaben ß, ö, Ö, ü etc haben in Unicode oder ISO 8852 Werte die nicht der deutschen lexikographischen Ordnung entsprechen. 
  • Die von Java verwendete Unicodecodierung erlaubt die Codierung der meisten Zeichen der Welt gleichzeitig. Die Sortierreihenfolge ist hier nicht garantiert. 
    • Beispiel: Es gibt Zeichen die in der japanischen sowie in der chinesichen Sprache verwendet werden. Diese Zeichen sind identisch, haben jedoch eine unterschiedliche Bedeutung. Sie werden daher in China und Japan unterschieldlich sortiert.
package block3;
public class Textverschluesselung {
    public static void main(String[] args) {
    String myText = "";
    int offSet = 0;
    char c;
 
    if (args.length > 1 ) {
        offSet = Integer.parseInt(args[0]);
        myText = args[1];
        }
    System.out.println("Eingabe: <<" + myText + ">>, Verschiebung: " + offSet);
    System.out.print("Ausgabe: <<");
    offSet = offSet%26; // Bei 26 Buchstaben kann der Versatz nur module 26 sein
    for (int i=0;i < myText.length(); i++) {
        // Lese jeden Wert der Zeichenkette aus und erhöhe den Zähler im Feld
        c = myText.charAt(i);
        if ((c>='A') && (c<='Z')) {
            c = (char)(c + offSet);
            if (c > 'Z') c-=(char)26; //Ziehe vom Ergebnis 26 ab da es jenseits von "Z" liegt.
            System.out.print(c);
            }
        }
        System.out.println(">>");
 }       
}

3.4.16 Verzweigung

package block3;
public class Verzweigung {
public static void main(String[] args) {
    int x[] = new int[3];
    if (args.length > 2 ) {
        try {
           for (int i=0; i<3; i++)
               x[i]= Integer.parseInt(args[i]);
           }
        catch (NumberFormatException e) {
            System.err.println("Argument muss Ganzzahl sein");
            System.exit(1);
            }
    }
    System.out.println("Eingabe: " + x[0] +", " + x[1] + ", "+ x[2]);
    if ((x[0]==x[1]) && (x[1]==x[2]))
        System.out.println("Alle Werte sind gleich.");
    if ((x[0]==x[1]) || (x[1]==x[2]) || (x[0]==x[2]))
        System.out.println("Mindestens zwei Werte sind gleich.");
    if ((x[0]!=x[1]) && (x[1]!=x[2]) && (x[0]!=x[2]))
        System.out.println("Alle Werte sind verschieden.");
}
}

3.4.17 Häufigkeit von Zeichen

package block3;
public class Haeufigkeitzeichen {
public static void main(String[] args) {
    String myText="";
    char c;
    int histogram[] = new int[Character.MAX_VALUE];

    if (args.length > 0 ) {
        myText=args[0];
        }
    System.out.println("Eingabe: <<" + myText + ">>");
    for (int i=0;i < myText.length(); i++) {
        // Lese jeden Wert der Zeichenkette aus und erhöhe den Zähler im Feld
        c = myText.charAt(i);
        histogram[c]++;
        }
    for (int i=0; i < Character.MAX_VALUE; i++)
        if (histogram[i]!= 0) {
            // Wichtig: unterdrücke alle leeren Einträge.
            // Das Feld hat ~65000 Zellen!
            System.out.print((char)i + ": ");
            for (int j=0; j< histogram[i]; j++)
                System.out.print('*');
            System.out.println();
        }
    }
}

 3.4.18 Weckzeiten implementieren

public class WeckzeitLoesung {
int wzh = 0; // Weckzeit in Stunden
int wzm = 0; // Weckzeit in Minuten
int wzs = 0; // Weckzeit in Sekunden
public void setzeWeckzeit(int hh, int mm, int ss) {
if (korrekteWeckzeit(hh,mm,ss)) {
wzh = hh;
wzm = mm;
wzs = ss;
}
}
public boolean korrekteWeckzeit(int h, int m, int s) {
boolean result;
// benutzen die Variablen h,m,s um eine gültige Zeit zu bestimmen
result = ((h>=0) && (h<=12 && (m>=0) && (m<=59)&& (s>=0) && (s<=59)));
return result;
}
public boolean klingeln(int aktH, int aktM, int aktS) {
boolean result;
// benutzen die Variablen der aktuellen Zeit aktH (Stunde),
// aktM (Minute), aktS (Sekunde) und die Weckzeit wzmh, wzm, wzs
// um zu bestimmern ob der Wecker klingeln soll
// Bestimme aktuelle Zeit in Sekunden
int aktZeit = aktH*3600 + aktM*60+aktS;
// Bestimme Weckzeit in Sekunden
int weckZeit = wzh *3600 + wzm *60+wzs;
// Ist die aktuelle Zeit größer aber nicht größer als 10 Sekunden?
result = (aktZeit-weckZeit>=0) && (aktZeit-weckZeit<10);
return result;
}

}

 

3.5 Lernziele

Am Ende dieses Blocks können Sie:

  • ... Befehlsblöcke im Javaquellcode erkennen und erstellen
  • ... die allgemeine Syntax von if, while, do while und for Kommandos und Schleifen nennen
  • ... in einer mehrfach, geschachtelten if Anweisung einen else Block der zugehörigen if-Bedingung zuordnen
  • ... die Typen nennen die man bei Mehrfachverzweigungen benutzen kann
  • ... den Ablauf einer Mehrfachverzweigung mit case, break und default Schlüsselwörten analysieren
  • ... for Schleifen in while bzw. do while Schleifen transformieren
  • ... die Syntax einer for-Schleife beschreiben

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten:

4. Prozedurales, modulares Programmieren, Unterprogramme, Funktionen, Methoden

4.1 Methoden

Methoden

Methoden sind ein wichtiges Strukturierungs- und Modularisierungsmittel.

Die Grundidee einer Methode ist wiederkehrende Aufgaben an einer Stelle zu implementieren und damit Redundanz zu vermeiden.

Ein zweiter wichtiger Aspekt ist die Senkung der Komplexität. Dem Benutzer muss die Implementierung einer Methode nicht bekannt sein um sie zu benutzen. 

Der in Java verwendete Begriff wird in anderen Programmiersprachen auch wie folgt bezeichnet:

  • Prozedur
  • Unterprogramm
  • Funktion

In Java wurden alle diese Begriffe der Übersichtlichkeit wegen im Begriff der Methode zusammen gefasst. Methoden gehören immer zu Klassen. Man kann in Java keine alleinstehenden Funktionen, Prozeduren etc. implementieren

Implementierung einer Methode

Methoden bestehen aus

  • einem Methodenkopf bestehend aus der Deklaration
    • Namen
    • Signatur: Aufrufargumente, Rückgabewert
  • Methodenrumpf
    • der Rumpf wird von geschweiften Klammern eingeschlossen
    • der Rumpf enthält die Implementierung der Methode

Zum Aufruf der Methode ist nur die Kenntnis der Deklaration notwendig

Methodenkopf

Methodenkopf

Durch ihn wird die Methode deklariert. Das bedeutet, das hiermit eine Schnittstelle festgelegt wird die alle Informationen enthält die ein Benutzer der Methode kennen muss um sie zu benutzen.

public static int multiplizieren( int faktor1, int faktor2);
   ^      ^    ^          ^           ^
   1.     2.   3.         4.          5.

1. Zugriffsspezifikation; die Sichtbarkeit der Methode

  • public : Die Methode darf von überall aufgerufen werden
  • private: Die Methode darf nur innerhalb der Klasse verwendet werden
  • protected: Die Methode darf nur innerhalb der Klasse oder von abgeleiteten Klassen verwendet werden

2. Statische Methoden: Das Schlüsselwort static erlaubt den Aufruf der Methode auch wenn es für die Klasse keine Instanz gibt. Das Konzept von Klassen, Objekten und Instanzen wird erst später eingeführt.

3. Rückgabetyp: Der Typ des Ergebnis welche die Methode zurückliefert. Es gibt zwei Möglichkeiten:

  • Die Methode liefert ein Ergebnis zurück. Der Typ ist ein beliebiger Typ
  • Die Methode liefert kein Ergebnis zurück. Hierzu wird das Schlüsselwort void verwendet.

4. Methodenname: Hiermit werden Methoden in einer Klasse unterschieden.

5. Parameterliste: Sie enthält den Typ und den Namen aller übergebenen Werte innerhalb von runden Klammern.

  • Die übergebenen Werte werden in der Methode als Variablen verwendet.
  • Die übergebenen Parameter werden mit Komma getrennt.
  • Sollen einer Methode keine Parameter mitgegeben werden, so wird ein leeres Klammernpaar ohne Werte verwendet. Bsp. ()
  • Die übergebenen Parametervariablen sind immer Kopien der aufrufenden Variablen. Dies bedeutet, dass man in der Methode eine Parametervariable ändern kann, ohne dass die Variable im äusseren Block die den Wert übergeben hat geändert wird.

Methodenrumpf

In Methodenrümpfe kann man:

  • Variablen und Konstanten deklarieren
  • Anweisungen und Schleifen ausführen
  • Methoden der gleichen oder anderer Klassen aufrufen
  • die Übergabeparameter nutzen

In Methoden können die im Methodenkopf deklarierten Parameter direkt verwendet werden. Bei einem gegebenen Rückgabeparameter muss dieser mit dem Schlüsselwort return zurückgegeben werden. Mit final bezeichnete Parameter dürfen im Methodenrumpf nicht mehr verändert werden. Beispiel

public static String textverkettung (String text, final int anzahl) {
   String s="";
   for (int i=0; i<anzahl; i++) {
      s = s + text;
   }
   return s; }

Die Rückgabe von Werten mit Hilfe des Schlüsselwort return funktioniert nicht nur mit Variablen, man kann auch Ausdrücke zurückgeben die den passenden Typ haben.

Wie zum Beispiel in der Methode textRahmen() die noch ein Textrahmen von jeweils 3 Zeichen um das Ergebnis legt:

public static String textRahmen (String text) {
   return ">>>" + text + "<<<";
}

Bei einem gegebenen Rückgabeparameter kann der Rumpf an verschiedenen Stellen verlassen werden.

Siehe Beispiel:

public class Textverkettung {
public static void main(String[] args) {
System.out.println(textverkettung("Sonnenschein", 3)); System.out.println(textverkettung("Sonnenschein", 0));
}
public static String textverkettung(String text, final int anzahl) {
String s="";
if (anzahl < 1) {
return "Gewählte Anzahl ist zu klein";
} else {
for (int i = 0; i < anzahl; i++) {
s = s + text;
}
return s;
}
}
}

Der Typ des Rückgabeparameter muss vom Typ her zum deklarierten Rückgabetyp der Methode passen.

Die Methode kann auch ohne einen Rückgabewert definiert werden. Im folgenden Beispiel wird sie den Text direkt selbst ausdrucken:

public class TextverkettungDrucken {
public static void main(String[] args) {
textverkettungDrucken("Sonnenschein", 3);
textverkettungDrucken("Sonnenschein", 0);
}
public static void textverkettungDrucken(String text, final int anzahl) {
String s="";
if (anzahl >0)
for (int i = 0; i < anzahl; i++)
s = s + text;
System.out.println("Ergebnis: " + s);
}
}

Aufruf von Methoden

Direkter Aufruf: Aufruf nur durch Methodenname und aktuelle Parameter

  • Die aufgerufene Methode gehört zur gleichen Klasse oder einer Oberklasse (Fall 1 im Beispiel)
  • Die Methode ist nicht statisch und wird auf das gleiche Objekt wie die umgebende Methode angewendet (Fall 1 im Beispiel)
  • Die Methode ist statisch und kann ohne die Existenz eines Objekts verwendet werden (Fall 2 im Beispiel)

Aufruf mit vorgestelltem Objekt oder Klassenname (statische Methode)

  • Die Methode soll für ein bestimmtes Objekt aufgerufen werden. Sie ist nicht statisch (Fall 3 im Beispiel)
  • Die Methode ist statisch und gehört zu einer anderen Klasse. (Fall 4 im Beispiel)

Beispiel:

public class Flugzeug {
    String kennzeichen;
    int leerGewicht;
    int besatzung; 
public void drucken () {
System.out.println(kennzeichen + " , Crew: " + besatzung + ", "+leerGewicht + "kg");
}
public static void main(String[] args) {
Flugzeug f = new Flugzeug();
f.drucken();
f.kennzeichenAusgeben(); // Fall 3: Aufruf einer Methode für ein Objekt
eigenschaft(); // Fall 2: Aufruf einer statischen Methode der gleichen Klasse
Flughafen.eigenschaft(); // Fall 4: Aufruf einer statischen Methoden die zu einer anderen Klasse gehören kann double pi = Math.sqrt(2.0); // Fall 4: Aufruf einer statischen Methoden die zu einer anderen Klasse gehört
}
public void kennzeichenAusgeben() {
drucken(); // Fall 1: Nicht statischer Methodenaufruf einer Methode der gleichen Klasse
}
public static void eigenschaft() {
System.out.println("kann fliegen"); } } public class Flughafen { public static void eigenschaft() { System.out.println("ist laut und schafft Arbeitsplätze"); } }

Anwenden von Methoden

Methoden mit Eingabeparametern können bei jedem Aufruf mit anderen Werten arbeiten. Wichtig ist hierbei zu wissen, dass es bei mehreren Parameter auf die Reihenfolge der Parameter ankommt. Sie müssen beim Aufruf, in ihrer Reihenfolge, zur Deklaration passen. Der Übersetzer wird Parameter mit nicht passenden Typen als Fehler anzeigen. Sind die Typen mehrerer Parameter gleich oder kompatibel, muss der Entwickler selbst auf die Reihenfolge achten!

Definition
Formalparameter von Methoden
Die in der Deklaration verwendeten Parameter einer Methode werden Formalparameter genannt

 

Definition
Aktualparameter von Methoden
 Die beim Aufruf einer Methoden verwendeten Parameter werden Aktualparameter (aktuelle Parameter) genannt.

 

Die oben gezeigte Methode textverkettung kann bei jedem Aufruf einen anderen Text verketten. Man kann die Methode wie folgt aufrufen.

public class Textverkettung {
public static void main(String[] args) {
String result;
String text1 = "Mustertext";
result= textverkettung("PingPong", 2);
System.out.println("Ergebnis: " + result); result= textverkettung(text1, 3); System.out.println("Ergebnis: " + result);
}
public static String textverkettung(String text, final int anzahl) {
String s;
s = text;
if (anzahl < 1) {
return "Gewählte Anzahl ist zu klein";
} else {
for (int i = 1; i < anzahl; i++) {
s = s + text;
}
return s;
}
}
}

Das oben gezeigte Codestück sollte auf der Konsole das folgende Ergebnis ausdrucken:

Ergebnis: PingPongPingPong
Ergebnis: MustertextMustertextMustertext

Der Aufruf von Methoden ohne Rückgabeparameter ist noch einfacher. Im folgenden Beispiel wird wie Methode textverketttungDrucken() aufgerufen:

... 
String result;
String text1 = "Mustertext";
textverkettungDrucken("PingPong", 2); textverkettungDrucken(text1, 3);
...

Man kann die Methode ohne eine Ergebnisvariable mit einer vereinfachten Syntax aufrufen. Ruft man Methoden mit Ergebnisvariablen in der oben gezeigten Form, ohne Zuweisung des Ergebnis zu einer Variablen, auf, so geht das Ergebnis verloren. Die Implementierung wird jedoch übersetzt und abgearbeitet. Es tritt keine Fehlermeldung auf.

Verschachtelte Methodenaufrufe und Methodenaufrufe innerhalb von Methoden

Methodenaufrufe mit Ergebnissen können überall benutzt werden wo in einem Ausdruck oder einer Zuweisung der entsprechende Typ gelesen werden soll.

Im folgenden Beispiel wird die Methode textverkettung() verschachtelt mit der Methode zur Rahmenerzeugung textRahmen() aufgerufen. Der geschachtelte Aufruf erfolgt in der Methode testTextRahmen():

public static String textverkettung(String text, final int anzahl) {
String s="";
if (anzahl < 1) {
return "Gewählte Anzahl ist zu klein";
} else {
for (int i = 0; i < anzahl; i++) {
s = s + text;
}
return s; }
}
public static String textRahmen(String s) {
return "<< " + s + " >>";
}
public static void testTextRahmen() {
String s = "Inhalt";
String result = textRahmen(textverkettung(s,2));
System.out.println(result);
}

Ein Aufruf der Methode testTextRahmen() ergibt die Ausgabe:

<< InhaltInhalt >>

Methoden können nicht nur wie eben beschrieben geschachtelt werden. Man kann innerhalb eines Methodenrumpfes auch eine andere Methode aufrufen. Dies geschah oben in der Methode testTextRahmen.

Überladen von Methoden

Java erkennt eine Methode einer Klasse an den folgenden Merkmalen:

  • Name der Methode
  • Reihenfolge und Typ der Eingabeparameter

Man kann also den gleichen Namen einer Methode wiederverwenden solange sich die Typen der Eingabeparameter unterscheiden. Hiermit kann man Methoden implementieren, die "ähnliches" implementieren. Die "Ähnlichkeit" kann man mit dem gleichen Namen dokumentieren, die Parameterlisten müssen sich aber unterscheiden.

Definition

Überladene Methoden
Überladene Methoden einer Klasse sind Methoden die den gleichen Namen besitzen und Formalparameterlisten mit unterscheidlichen Typen bzw. unterschiedlicher Anzahl von Parametern besitzen

Überladene Methoden sind nützlich wenn man den prinzipiell gleichen Algorithmus für unterschiedliche Datentypen oder unterschiedliche Parameterkombinationen ausführen will.

Wichtig: Der Rückgabeparameter einer Methode wird in Java nicht bei der Unterscheidung überladener Methoden beachtet!

Methoden mit Rückgabeparameter können auch ohne Zuweisung ihres Rückgabewertes an eine Variable aufgerufen werden.
In diesem Fall kann der Übersetzer nicht feststellen welche Methode benutzt werden soll, wenn es mehrere Methoden gibt die sich nur im Rückgabewert unterscheiden.

Ein typisches Beispiel ist das drucken von unterschiedlichen Werten. Anbei vier überladene Methoden einer Klasse;

public class drucker {

public void drucken(String s) {
   System.out.println("String:"+ s);
   }

public void drucken(int i) {
   System.out.println("Zahl: " + i);
   }

public void drucken(String s, int i) {
   System.out.println("String/Zahl" + s + "/" + i);
   }

public void drucken(int i, String s) {
   System.out.println("Anders: String/Zahl" + s + "/" + i);
   }
}

Die folgenden zwei Methoden sind keine erlaubten (zusätzlichen) überladene Methoden:

public void drucken(String z, int a) {
System.out.println("String/Zahl" + z + "/" + a);
} public int drucken(String s, int i) {
System.out.println("String/Zahl" + s + "/" + i); return 2*i;
}

Diese erste Methode besitzt die gleichen Formalparametertypen wie die dritte Methode. Die Namen der Parameter sind nicht relevant.

Die zweite Methode hat zwar einen anderen Rückgabeparameter, jedoch die gleichen Formalparameter wie die dritte Methode. Java ignoriert die Rückgabeparameter und verweigert das Übersetzen der Methode.

Entwurfsprinzipien für Methoden

  • Methoden sollten eine klar definierte Teilaufgabe lösen
  • Wird eine Methode groß und unübersichtlich sollte man sie in sinnvolle Teilmethoden zerlegen
  • Gute Methoden werden an verschiedenen Stellen wieder verwendet
  • Methoden sollten möglichst gekapselt sein
    • Die Übergabeparameterlisten sollten so kurz wie möglich sein
    • Methoden sollten möglichst wenig globale Variablen verwenden. Dem Verwender ist das nicht unbedingt bewusst!
    • Ergebnisse sollten wenn möglich als Rückgabewert ausgegeben werden. Das Ändern von globalen Variablen sollte man wenn möglich vermeiden.
  • Methoden und ihre Funktionen und Seiteneffekte sollten immer gut dokumentiert sein. Ein Verhältnis von Kommentar zu Quellcode von 1:1 ist durchaus vernünftig!

Programmstrukturierung mit Hilfe von Methoden

Methoden sind einer der Bausteine die die Entwicklung komplexer Anwendungen erlauben. Sie geben Strukturierungsmöglichkeiten um die Gesamtkomplexität von Anwendungen zu reduzieren:

  • Abstraktion: Verwender einer Methode müssen nicht wissen wie sie intern funktioniert. Sie können sich auf die Beschreibung und die korrekte Ausführung der Methode verlassen
  • Codereduktion: Das wieder verwenden von Methoden erlaubt es mit deutlich weniger Programmcode auszukommen. Die Kosten für die Pflege der Quellen und die Fehlerhäufigkeit der Anwendung sinkt. Bei replizierten Codestücken müssten alle Codestücke gefunden und individuell angepasst werden.
  • Strukturierung und Hierarchisierung: Das explizite Zusammenfassen von Codestücken die eine bestimmte Aufgabe lösen erleichtert Verständnis und Pflege der Anwendung. Methoden erlauben durch Aufruf von anderen Methoden die Strukturierung in vielschichtige Hierarchien.
  • Teile und Herrsche: Das abstrahieren und Verbergen der Implementierung (Information hiding) erlaubt die Zusammenarbeit in Teams und die Entwicklung komplexer Anwendungen. Das Konzept der Schnittstelle ist ein "Vertrag" zwischen Anbieter und Konsument bei dem nur die semantische Wirkung der Methode verstanden sein muss.

Methoden sind nur das erste von mehreren Strukurierungshilfsmittel der Sprache Java die im Rahmen dieser Vorlesung vorgestellt werden. Klassen, Pakete, Vererbung und globale vs. lokale Variablen sind andere Möglichkeiten.

 

4.2 Variablen, (Sichtbarkeit lokal, global)

Variablen sind immer nur in einem gewissen Umfang sichtbar. Sichtbar bedeutet hier benutzbar, gültig.

Die Sichbarkeit von Variablen hängt vom Kontext, dem Block ab in dem sie deklariert worden sind.

Blöcke können sein:

  • Programmcode zwischen geschweiften Klammern
  • Programmcode innerhalb einer Schleife, Ausführungsektionen (z.Bsp. if Bedingung)
  • Bereich einer Methode
  • Bereich einer Klasse
  • Global

Es gibt hier abhängig von der Sichtbarkeit verschiedene Bereiche

  • Blocklokale Variablen
  • Methodenlokale Variablen
  • Klassenlokale Variablen

Die Gültigkeit von Variablen steht im Zusammenhang mit dem Stapel (Stackkonzept) des Laufzeitsystems.

Das Laufzeitsystem legt beim Beginn eines jeden Blocks die neuen Variablen hinter den Variablen des gerade aktuellen Blocks an.

Nach dem Verlassen eines Blocks wird der Zeiger auf das obere Ende des Stapels wieder auf die Variablen des letzen Blocks zurückgesetzt.

Der Speicher der Variablen des verlassenen Blocks ist hierdurch zur Wiederverwendung wieder freigegeben worden.

Das Verfahren eines Stapels entspricht der üblichen Handhabung von Tellern im Schrank.

  • Neue Teller (hier Blöcke) werden immer oben an den Stapel angefügt
  • Nicht mehr benötigte Teller werden nur von oben entfernt

Das Stackkonzept des Laufzeitssystems:

4.3 Konstruktoren (1)

Konstruktoren sind spezielle Methoden mit denen Objekte einer Klasse initialisert werden. Das Java Laufzeitsystem legt automatisch einen Standardkonstruktor an wenn kein klassenspezifischer Konstruktor implementiert wird.

Konstruktoren haben die folgende Eigenschaften

  • Der Konstruktor hat immer den Namen der Klasse als Methodenname
  • Konstruktoren haben keine Rückgabewerte (auch kein Schlüsselwort void!)
  • Die Auswahl des passenden Konstruktur erfolgt über die Typen der Eingabeparameter
  • Konstruktoren können auch parameterlos sein
  • Besitzt eine Klasse einen Konstruktor, so muss einer der Konstruktoren benutzt werden. Man kann die Klasse dann nicht mehr mit dem trivialen (System-)Konstruktor initialisieren.

Beispiel:

public class Employee {
public String surName;
public String firstName;
public int employeeId;
public double salary;
/**
* Der Konstruktor initialisert die Attribute für Employee
* @param ln Nachname
* @param fn Vorname
* @param id Mitarbeiternummer
* @param sal Gehalt. Gehalt wird im Konstruktor auf 100000 begrenzt
*/
public Employee (String ln, String fn, int id, double sal) {
surName = ln;
firstName = fn;
employeeId = id;
if (sal > 100000) salary = 100000;
else salary= sal;
}
public void printRecord() {
System.out.print(employeeId + ", " + surName + " " + firstName);
System.out.println(",Salary :" + salary);
}
/**
* Teste die Konstruktoren
* @param args werden nicht benutzt
*/
public static void main(String[] args) {
Employee ceo = new Employee("Doe","John",1,80000.0);
Employee cio = new Employee("Doe","Jane",1,70000.0);
ceo.printRecord();
cio.printRecord();
}
}

 

4.4 Iteration und Rekursion

In den vorangegangen Abschnitten wurden verschiedene Schleifenkonstrukte vorgestellt mit denen man Codestrecken wiederholt durch laufen kann. Dieses Verfahren nennt man Iteration.

Methoden können aber nicht nur andere Methoden aufrufen, sie können auch sich selbst aufrufen. Dieses Verfahren nennt man Rekursion. Beide Verfahren sind von der Theorie her gleichwertig. Sie können wechselseitig eingesetzt werden. Im folgenden Beispiel wird die Multiplikation durch fortgesetzte Addition nach dem folgenden Prinzip iterativ und rekursiv gelöst.

Rekursive Algorithmen wenden das "Teile und Herrsche" Prinzip an indem Sie ein gegebenes Problem zerlegen in

  • ein trivial lösbares Problem
  • ein Restproblem welches gleich dem ursprünglichen Problem strukturiert ist (und einfacher ist!)

Die rekursive Methode fib() basiert auf den folgenden Definition von Fibonaccifolgen

fib(0) = 0 (ein trivial lösbares Problem)
fib(1) = 1 (ein trivial lösbares Problem)
für alle n > 1
fib(n) = fib(n-1) + fib(n-2) (das einfachere Restproblem)

Fibonacciberechnung
von fib(0) bis fib(10):
fib(0)= 0
fib(1)= 1
fib(2)= 1
fib(3)= 2
fib(4)= 3
fib(5)= 5
fib(6)= 8
fib(7)= 13
fib(8)= 21
fib(9)= 34
fib(10)= 55 

Beispielprogramm:

package block4;

public class Fibonacci {

public static long fib(int f) {
long ergebnis=0;
switch (f) {
case 0: { ergebnis = 0;break;} // Triviales Problem. Keine Rekursion
case 1: { ergebnis = 1;break;} // Triviales Problem. Keine Rekursion
default: { // Die Rekursion
ergebnis = fib(f - 1) + fib(f - 2);
break;
}
}
return ergebnis;
}

public static void main(String[] args) {
int a = 10; //Anzahl der berechneten Fibonaccizahlen
System.out.println("Fibonacciberechnung von fib(0) bis fib(" + a + ")");
for (int i=0; i<=a; i++)
System.out.println("fib("+i+")= " + fib(i));
}
}

Anmerkung: Diese naive Implementierung ist sehr ineffizient. Das Programm berechnet zu jeder Fibonaccizahl die beiden vorhergehenden Zahlen neu. Der Aufwand zur Berechnung der Fibonaccizahlen steigt daher exponentiell mit der Potenz 2. Dies macht den hier gewähltenAlhorithmus, zur Berechnung für größerer Fibonaccizahlen, ungeeignet.

Beispiel: Quersummenberechnung

Die Methode berechne() berechnet eine Quersumme iterativ mit Hilfe einer while Schleife. Die Methode berechneRekursiv() berechnet das Ergebnis rekursiv (mit Selbstaufruf).

public class Quersumme {
/**
* Hauptprgramm zum Berechnen der Quesummer
* @param args wird nicht benutzt
*/
public static void main (String[] args) {
long eing = 12345678;
long ausg = berechne(eing);
System.out.println ("Ausgabe:" + ausg + "; Eing: " +eing);
System.out.println("Berechne rekursiv");
ausg = berechneRekursiv(eing);
System.out.println ("Ausgabe:" + ausg + "; Eing: " +eing);
}
/**
* Iterative Berechnung einer Quersumme mit einer while Schleife
* @param eing
* @return ausgabe
*/
public static long berechne(long eing){
long a = 0;
while (eing>0) { //Abbruch bei Null
a += eing%10; //Aufaddieren der letzen Stelle
eing /= 10; //Teilen der Zahl durch 10
}
return a;
}
/**
* Rekursive Berechnung einer Quersumme
* @param eing
* @return ausgabe
*/
public static long berechneRekursiv (long eing){
long a = 0;
if (eing>0) a = (eing%10)+berechneRekursiv(eing/10);
else a = 0; // Triviale Loesung. Nicht rekursiv.
return a;
}
}

Beispiel: Die Türme von Hanoi

Die Türme von Hanoi sind ein einfaches Solitärspiel bei dem die folgenden Regeln gelten:

  • Es gibt drei Stapel.
  • Ausgangssituation: Auf dem ersten Stapel sind alle Scheiben zu einer Pyramide aufgetürmt.
  • Es müssen immer kleinere Scheiben auf größeren Scheiben liegen
  • Es darf immer nur eine Scheibe von einem Stapel zu einem anderen bewegt werden
  • Alle Scheiben sollen auf einen Zielstapel bewegt werden

Siehe Wikipedia für weiterführende Erklärung und Animation. Eine detaillierte Diskussion des Problemes mit vielen Beispielen ist auch unter mister-mueller verfügbar.

Die Strategie

  • Sind Null Scheiben zu bewegen muss nichts getan werden (trivial)
  • Sind n Scheiben (mit n>0) vom Startstapel zum Zielstapel bewegen, so sind die folgenden Schritte auszuführen:
    • Bewege n-1 Scheiben vom Startstapel zum Hilfsstapel
    • Bewege (verbleibende) Scheibe vom Startstapel zum Zielstapel
    • Bewege n-1 Scheiben vom Hilfsstapel zum Zielstapel

Lösung für 3 Scheiben

Implementierung der Türme von Hanoi in Java

package block4;
public class Hanoi {
    public static void bewegeScheiben(int scheiben, 
            String von,
            String nach, 
            String hilfsstab){
        if (scheiben >0) {
            bewegeScheiben(scheiben-1, von, hilfsstab,nach);
            System.out.println(scheiben + ".te Scheibe von " + von + " nach " + nach );
            bewegeScheiben(scheiben-1, hilfsstab, nach, von);
            }
        }

    public static void main(String[] args) {
     bewegeScheiben(3, "links", "mitte", "rechts");
    }

}

Ablauf des Programmes

Konsolenausgabe

1.te Scheibe von links nach mitte
2.te Scheibe von links nach rechts
1.te Scheibe von mitte nach rechts
3.te Scheibe von links nach mitte
1.te Scheibe von rechts nach links
2.te Scheibe von rechts nach mitte
1.te Scheibe von links nach mitte

Rekursive Aufrufe der Methoden

 Rejursive Methodenaufrufe

 

4.5 Übungen

Duke als Boxer

4.5.1 Übung: Arithmetikmethoden

Implementieren Sie eine einfache Arithmetik für den Ganzzahltyp int sowie wie den Fließkommatyp double. Implementieren Sie für beide Typen die folgenden 4 Methoden:

  • add: Addition
  • sub: Subtraktion
  • mul: Multiplikation
  • div: Division

Benutzen Sie die unten aufgeführte Klasse Main mit dem gegebenen Hauptprogramm um einige Tests auszuführen.

Was geschieht wenn man die Operationen von verschiedenen Typen mischt?

public class Rechenarten {
    /* Implementierung */

     public static void main(String[] args) {
double ergebnis1;
int ergebnis2; ergebnis1 = add(5.0, 4.0); System.out.println(" 5.0 + 4.0 = " + ergebnis1); ergebnis1 = div(9.0, 4.0); System.out.println(" 9.0 / 4.0 = " + ergebnis1); ergebnis1 = sub(9.0, 4.0); System.out.println(" 9.0 - 4.0 = " + ergebnis1); ergebnis1 = add(div(9.0, 4.0), 3.0); System.out.println(" 9.0 / 4.0 + 3.0 = " + ergebnis1); ergebnis2 = add(5, 4); System.out.println(" 5 + 4 = " + add(5, 4)); ergebnis2 = div(9, 4); System.out.println(" 9 / 4 = " + div(9, 4)); ergebnis2 = sub(9, 4); System.out.println(" 9 - 4 = " + sub(9, 4)); ergebnis2 = add(div(9, 4), 3); System.out.println(" 9 / 4 + 3 = " + add(div(9, 4), 3)); } }

4.5.2 Übung: Flächenberechnung von Dreiecken

Implementieren Sie eine Klasse mit Namen Dreieck. Die Klasse soll Methoden zu Flächenberechnung enthalten. Implementieren sie einzelne Methoden zur Berechnung der folgenden Dreieckstypen. Passen Sie die Anzahl der Parameter an die Gegebenheiten an

  • beliebiges Dreieck: 3 Seiten gegeben
  • gleichschenkliges Dreieck; 2 Seiten gegeben
  • gleichseitiges Dreieck: 1 Seite gegeben
  • rechtwinkliges Dreieck:
    • 2 Katheten gegeben
    • 1 Hypotenuse, 1 Kathete gegeben

Nutzen Sie wenn möglich schon implementierte Methoden zur Berechnung der Fläche. Dies bedeutet, dass man ein gleichschenkliges Dreieck für das man nur zwei Eingabeparameter benötigt mit Hilfe der Methode zur Berechnung eines allgemeinen Dreiecks berechen kann. Das gleichschenklige Dreieck ist ein Spezialfall des allgemeinen Dreiecks.

Tipp: 

  1. Nutzen Sie den Satz von Heron
  2. Benutzen Sie die Systemklasse Math für das Ziehen der benötigten Wurzel. Dies geschieht mit Math.sqrt().
    • Hinweis. Durch das vorranstellen von Math. benötigen Sie keine Importdeklaration für das Math-Paket.

4.5.3 Übung: Rekursive Addition und Multiplikation

1. Übersetzen Sie das gegebene Beispielprogramm und testen Sie die Funktionsweise

  • Tipp: Entfernen Sie den Importbefehl in der Zeile "package block4;" falls Sie mit Kommandozeilenwerkzeugen arbeiten und Packageverzeichnisse vermeiden wollen
  • Vereinfachung: Die Anwendung muss nur für nicht negative Zahlen funktionieren
public class Arithmetik {    

/**
* Liest von der Kommazeile zwei Ganzzahlen ein und multipliziert sie
* @param args
*/
public static void main(String[] args) {
int a=5;
int b=8;
int c=0;

if (args.length > 1) {
try {
a = Integer.parseInt(args[0]);
b = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.err.println("Argument muss Ganzzahl sein");
System.exit(1);
}
}
// Kontrolle der Eingaben
System.out.println("Eingabe a: " + a +"; b: " +b);

c = a *b;

// Ergebnisprüfung
System.out.print("Ergebnis c: " + c);
if (c == a*b)
System.out.println(" (korrekt)");
else
System.out.println(" (falsch. Richtig ist " + (a*b)+")");
}
}

2. Benutzen einer for-Schleife mit Addition

Ersetzen Sie die Anweisung:

c = a*b;

durch eine for Schleife in der nur noch addiert wird

3. Einführen einer Methode mult()

Ersetzen Sie die Berechnung des Ergebnisses mit einer for-Schleife durch einen Methodenaufruf:

c = mult(a,b);

Implementieren Sie eine dazu passende Methode mult() die die Multplikation mit der for-Schleife durchführt

4. Eine rekursive Methode mult()

Ersetzen Sie die for-Schleife in der Methode mult() durch einen rekursiven Aufruf.

Tipps:

  • Sie benötigen eine Bedingung für das Rekursionsende
  • Definition des zu selbst zu lösenden Teilproblems
  • Delegation des Restproblems durch rekursiven Aufrufs

5. Ersetzen der Addition durch eine rekursive Additionsmethode add() die nur die Inkrementfunktion benutzt

In gesamten Anwendung sollte jetzt kein * oder + Operator mehr vorkommen...

 4.5.4 Fragen

Typische klausurrelevante Fragen:

  • Aus welchen 2 Teilen besteht eine Methode?
  • Aus welchen Teilen besteht ein Methodenkopf?
  • Welches Schlüsselwort wird verwendet wenn kein Rückgabewert existiert?
  • Wie sieht die Parameterliste des Methodenkopfes aus falls es keine Eingabeparameter gibt?
  • Nenne ein Beispiel für eine überladene Methode.

4.5.5 Beispiel aus der Vorlesung

Diese Klassen liegen im Paket block4. Entfernen Sie den ersten Befehl package block4; oder Erzeugen Sie das benötigte Pakte block4!

Klasse Flugzeug

package block4;

/**
*
* @author stsch
*/
public class Flugzeug {
final static double durchschnittsgewicht = 75;
String kennzeichen;
int passagiere;
private double maximalesGewicht;
double minimalGewicht;

Flugzeug(double minGewicht, double maxGewicht) {
System.out.println("Hallo, ich baue ein Flugzeug");
maximalesGewicht = maxGewicht;
// Kontrolle des Minimalgewichts
if ((minGewicht > 0) && (minGewicht <= maximalesGewicht)) {
minimalGewicht = minGewicht;
} else {
minimalGewicht = 5;
}
// Eine schwachsinnige Initialisierung
passagiere = 1;
}

public double maxG() {
return maximalesGewicht;
}

public void einsteigen() {
passagiere++;
}

public void einsteigen(int einsteiger) {
passagiere = passagiere + einsteiger;
}

double gewicht() {
double ergebnis;
ergebnis = minGewicht + passagiere * durchschnittsgewicht;
return ergebnis;
}

}

Klasse Flughafen

package block4;

/**
*
* @author stsch
*/
public class Flughafen {

String name;
Flugzeug gate1;
Flugzeug gate2;
Flugzeug gate3;
Flugzeug gate4;
Flugzeug gate5;
Flugzeug gate6;

public static void main(String[] args) {
Flughafen pb;
pb = new Flughafen();
pb.name = "Paderborn";

Flugzeug lh1 = new Flugzeug(222.0D,100000D);
lh1.kennzeichen = "D-A123";
lh1.passagiere = 23;

Flugzeug lh2 = new Flugzeug(3333.0D,100000D);
lh2.kennzeichen = "D-A456";
lh2.passagiere = 11;

pb.gate1 = lh1;
lh1.einsteigen();
lh1.einsteigen();
double meinGewicht = lh1.gewicht();
lh1.gewicht();

pb.gate2 = lh2;
lh2.einsteigen(88);


System.out.println("Mein Flughafen: " + pb.name);
System.out.println("Gate 1: " + pb.gate1.kennzeichen +
", Passagiere: " + pb.gate1.passagiere +
", akt. Gew.: " + pb.gate1.gewicht());
System.out.println("Gate 2: " + pb.gate2.kennzeichen +
", Passagiere: " + pb.gate2.passagiere);
if (pb.gate3 == null) {
System.out.println("Gate 3: leer");
} else {
System.out.println("Gate 3: " + pb.gate3.kennzeichen);
}
}
}
 

 

4.6 Lösungen

4.6.1 Arithmetikmethoden

public class Rechenarten {

    public static double add(double a, double b) {
        return (a + b);
    }

    public static double sub(double a, double b) {
        return (a - b);
    }

    public static double mul(double a, double b) {
        return (a * b);
    }

    public static double div(double a, double b) {
        return (a / b);
    }

    public static int add(int a, int b) {
        return (a + b);
    }

    public static int sub(int a, int b) {
        return (a - b);
    }

    public static int mul(int a, int b) {
        return (a * b);
    }

    public static int div(int a, int b) {
        return (a / b);

    }

    public static void main(String[] args) {
double ergebnis1;
int ergebnis2; ergebnis1 = add(5.0, 4.0); System.out.println(" 5.0 + 4.0 = " + ergebnis1); ergebnis1 = div(9.0, 4.0); System.out.println(" 9.0 / 4.0 = " + ergebnis1); ergebnis1 = sub(9.0, 4.0); System.out.println(" 9.0 - 4.0 = " + ergebnis1); ergebnis1 = add(div(9.0, 4.0), 3.0); System.out.println(" 9.0 / 4.0 + 3.0 = " + ergebnis1); ergebnis2 = add(5, 4); System.out.println(" 5 + 4 = " + add(5, 4)); ergebnis2 = div(9, 4); System.out.println(" 9 / 4 = " + div(9, 4)); ergebnis2 = sub(9, 4); System.out.println(" 9 - 4 = " + sub(9, 4)); ergebnis2 = add(div(9, 4), 3); System.out.println(" 9 / 4 + 3 = " + add(div(9, 4), 3)); } }

4.6.2 Flächenberechnung vom Dreieck

public class Dreiecksflaeche {  
/**
* Berechnung der Fläche eines Deiecks mit drei beliebigen Seiten.
* Es wird nicht geprüft ob die Seitenlängen ein korrektes Dreieck ergeben
* @param a Länge Seite 1
* @param b Länge Seite 2
* @param c Länge Seite 3
* @return Fläche des Dreiecks
*/
public static double flaeche(double a, double b, double c) {
double s = (a+b+c)/2;
return Math.sqrt(s*(s-a)*(s-b)*(s-c));
}
/**
* Berechnung der Fläche eines gleichschenkligen Deiecks.
* Es wird nicht geprüft ob die Seitenlängen ein korrektes Dreieck ergeben
* @param gleicherSchenkel die Länge der beiden gleichlangen Seiten
* @param basis Länge der Basis
* @return
*/
public static double flaeche(double gleicherSchenkel, double basis) {
return flaeche(gleicherSchenkel,gleicherSchenkel,basis);
}
/**
* Berechnung der Fläche eines gleichschenkligen Deiecks
* @param gleicheSeite Länge der Seite
* @return
*/
public static double flaeche(double gleicheSeite) {
return flaeche(gleicheSeite,gleicheSeite);
}
/**
* Berechnung der Fläche eines rechtwinkligen Dreiecks mit zwei Katheden
* @param k1 Länge erste Kathede
* @param k2 Länge zweite Kathede
* @return Fläche
*/
public static double flaecheKathedenDreieckeck(double k1, double k2){
return flaeche(Math.sqrt(k1*k1+k2*k2),k1,k2);
}
/**
* Berechnung der Fläche eines rechtwinkligen Dreiecks mit einer
* Hypotenuse und einer Kathede
* @param h Länge Hypothenuse
* @param k Länge Kathede
* @return Fläche
*/
public static double flaecheKathedeHypothenuseDreieck(double h, double k){
return flaecheKathedenDreieckeck(k,Math.sqrt(h*h-k*k));
}
/**
* Hauptprogrsam zum Testen
* @param args keine Eingabeparameter
*/
public static void main(String[] args) {
double aa = 3.0d;
System.out.println(flaeche(aa,4D,5D));
System.out.println(flaeche(5D,4D));
System.out.println(flaeche(3D));
System.out.println(flaecheKathedenDreieckeck(3D,4D));
System.out.println(flaecheKathedeHypothenuseDreieck(5D,4D));
}

}

4.6.3 Rekursive Addition und Multiplikation

Multiplikation mit Hilfe einer addierenden for-Schleife:

public class Arithmetik1 {


public static void main(String[] args) {
int a=5;
int b=8;
int c=0;

if (args.length > 1) {
try {
a = Integer.parseInt(args[0]);
b = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.err.println("Argument muss Ganzzahl sein");
System.exit(1);
}
}
System.out.println("Eingabe a: " + a +"; b: " +b);

for (int i=0; i<b; i++) {
c += a;
}



System.out.print("Ergebnis c: " + c);
if (c == a*b)
System.out.println(" (korrekt)");
else
System.out.println(" (falsch. Richtig ist " + (a*b)+")");

}

}

Delegation an eine Methode

public class Arithmetik2 {


public static void main(String[] args) {
int a=5;
int b=8;
int c=0;

if (args.length > 1) {
try {
a = Integer.parseInt(args[0]);
b = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.err.println("Argument muss Ganzzahl sein");
System.exit(1);
}
}
System.out.println("Eingabe a: " + a +"; b: " +b);

c = mult(a,b);

System.out.print("Ergebnis c: " + c);
if (c == a*b)
System.out.println(" (korrekt)");
else
System.out.println(" (falsch. Richtig ist " + (a*b)+")");

}

public static int mult(int x, int y) {
int ergebnis=0;
for (int i=0; i<x; i++) {
ergebnis += y;
}
return ergebnis;
}

}

Eine rekursive Multiplikation

public class Arithmetik3 {


public static void main(String[] args) {
int a=5;
int b=8;
int c=0;

if (args.length > 1) {
try {
a = Integer.parseInt(args[0]);
b = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.err.println("Argument muss Ganzzahl sein");
System.exit(1);
}
}
System.out.println("Eingabe a: " + a +"; b: " +b);

c = mult(a,b);


System.out.print("Ergebnis c: " + c);
if (c == a*b)
System.out.println(" (korrekt)");
else
System.out.println(" (falsch. Richtig ist " + (a*b)+")");

}

/**
* Diese Methode multipliziert zwei Zahlen rekursiv.
* Sie funktioniert nur für x Werte die nicht negativ sind!
* @param x darf nicht negativ sein
* @param y
* @return Ergebnis einer Multiplikation
*/
public static int mult(int x, int y) {
int ergebnis=0;
if (x==0)
ergebnis=0;
else
ergebnis=mult(y,(x-1))+y;

return ergebnis;
}

}

Multiplikation mit rekursiver Addition und Multiplikation

public class Arithmetik4 {


public static void main(String[] args) {
int a=5;
int b=8;
int c=0;

if (args.length > 1) {
try {
a = Integer.parseInt(args[0]);
b = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.err.println("Argument muss Ganzzahl sein");
System.exit(1);
}
}
System.out.println("Eingabe a: " + a +"; b: " +b);

c = mult(a,b);


System.out.print("Ergebnis c: " + c);
if (c == a*b)
System.out.println(" (korrekt)");
else
System.out.println(" (falsch. Richtig ist " + (a*b)+")");

}

/**
* Diese Methode multipliziert zwei Zahlen rekursiiv.
* Sie funktioniert nur für x Werte die nicht negativ sind!
* @param x darf nicht negativ sein
* @param y
* @return Ergebnis einer Multiplikation
*/

public static int mult(int x, int y) {
int ergebnis=0;
if (x==0)
ergebnis=0;
else
ergebnis=add(mult(y,(x-1)),y);

return ergebnis;
}

/**
* Diese Methode addiert zwei Zahlen rekursiv.
* Sie funktioniert nur für x Werte die nicht negativ sind!
* @param x darf nicht negativ sein
* @param y
* @return Ergebnis einer Multiplikation
*/

public static int add(int x, int y) {
int ergebnis=0;
if (y==0)
ergebnis=x;
else {
ergebnis=add(x,(y-1));
ergebnis++;
}

return ergebnis;
}


 

4.7 Programmieren mit Java: Testfragen (1-4)

 Die folgenden Fragen können auch in der Klausur gestellt werden.

im Test werden 24 Punkte vergeben. Sie haben 24 Minuten zum Lösen der Aufgaben. In der Klausur werden 100 Punkte in 90 Minuten vergeben.

4.8 Lernziele

Am Ende dieses Blocks können Sie:

  • .... in Java Methoden erkennen, erklären und anwenden.
    • Sie können die verschiedenen syntaktischen Bestandteile nennen und beschreiben
  • ...formale von aktuellen Parametern unterscheiden
  • ...wissen Sie wann Sie Methoden zur Programmstruktierung einsetzen
  • ...den Geltungsbereich und die Geltungsdauer von Variablen in Blöcken und Methoden bestimmen
  • ...können das Konzept eines Programmstacks (Programmstapel) im Kontext von Methoden und Variablen erklären
  • ...kennen semantischen Möglichkeiten die Ihnen Konstruktoren bieten
  • ...können die syntakischen Bestandteile von Konstruktoren nennen und den Unterschied zu gewöhnlichen Methoden eklären
  • ...sind in der Lage zu erkennen ob ein Javaimplementierung auf einem rekursiven oder iterativen Ansatz beruht

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten:

5. Konzepte objektorientierter Programmierung

Duke in 3D

Die objektorientierte Programmierung hilft große Softwareentwicklungsprojekte überschaubarer und handlicher zu machen.

Die Grundidee besteht darin die zu verarbeitenden Daten und die Algorithmen (Methoden) mit denen die Daten verarbeitet werden zu handlichen, wartbaren Einheiten zusammenzufassen.

Objektorientierte Programmierung fasst man mit den folgenden Konzepten zusammen:

5.1 Einführung Objektorientierung

"Information Hiding" und Datenkapselung im abstrakten Datentyp

Ein Bestandteil der Objektorientierung ist das "information Hiding" welches schon von den abstrakten Datentypen her bekannt ist. Der Zustand des Objekts wird durch seine Attribute bestimmt. Die Attribute sollen aber nicht beliebig geändert werden können. Die Methoden agieren als Wächter für die Zustandsübergänge und "bewachen" so zu sagen die Attribute des Objekts. Dies hat zwei wesentliche Vorteile

  • Der Entwickler kann denn Zustand seines Objekts bzw. Datentyps immer genau kontrollieren
  • Der Entwickler kann die interne Implementierung des Objekts an neue Anforderungen anpassen ohne, dass er dies mit den Benutzern des Objekts kommunizieren muss. Die Methoden bilden hierdurch eine Schnittstelle zwischen der Implementierung und der externen Sicht des Objekts

Methoden erfüllen in diesem Kontext mehrere Aufgaben:

  • Sie lesen die internen, geschützten Datenstrukturen aus
  • Sie ändern die internen Datenstrukturen
  • Sie können komplexe Berechnungen durchführen
  • Sie könne wiederum andere Objekte manipulieren (von denen der Benutzer nichts weiß)

Information Hiding: Ein Teilsystem darf nichts von der Implementierung eines anderen Teilsystems wissen

Klasse

Nach Wikipedia:

Unter einer Klasse versteht man in der objektorientierten Programmierung ein abstraktes Modell bzw. einen „Bauplan“ für eine Reihe von ähnlichen Objekten.

Die Klasse dient als Bauplan für Abbildung von realen „Objekten“ in Softwareobjekten und enthält Attribute (Eigenschaften) und Methoden (Verhaltensweisen) der Objekte. Verallgemeinernd könnte man auch sagen, dass eine Klasse dem Datentyp eines Objekts entspricht.

Klassen

  • sind eine Menge von gleichartigen, individuellen Objekten
  • sind ein schematische Modell
  • beschreiben
    • Eigenschaften (die Attribute einer Klasse)
    • Verhalten (Methoden)

Objekt

Nach Wikipedia:

Ein Objekt bezeichnet in der objektorientierten Programmierung (OOP) ein Exemplar eines bestimmten Datentyps oder einer bestimmten Klasse (auch „Objekttyp“ genannt). In diesem Zusammenhang werden Objekte auch als „Instanzen einer Klasse“ bezeichnet. Objekte sind also konkrete Ausprägungen („Instanzen“) eines Objekttyps.

5.1.1 Datenkapselung

Das erste Konzept, die Datenkapselung, kann man als eine technische Weiterentwicklung der abstrakten Datentypen sehen.

  1. Primitive Datentypen (z.Bsp. int x = 10;)
  2. Strukturierte Datentypen wie Felder oder struct in der Programmiersprache C erlauben mehrere Typen in einer Struktur zusammenzufassen.
  3. Klassen, die es erlauben mehrere Datentypen (diese werden Attribute genannt) mit den dazugehörigen Methoden zusammenzufassen und zu kapseln.
    • Beispiel: Eine Linie, die aus zwei Punkten besteht und den Zugriff auf die Punkte nur nach bestimmten Vorschriften in Methoden kapselt.

Datenkapselung ist das Verbergen von Attributen und Methoden durch Einschränkung des Zugriffs von Aussen.

Durch dieses Prinzip ergeben sich beim Entwurf und bei der Wartung von Anwendungen eine Reihe von Vorteilen:

  • Divide et impera (Teile und herrsche): Benutzer und Implementierer einer Klasse können unabhängig von einander entwickeln. Der Entwickler muss nur darauf achten die externen Schnittstellen (öffentliche Methoden und Attribute) stabil zu halten.
  • Integrität: Unbeabsichtigte Zustandsänderungen werden unterbunden. Beim Setzen eines Attributs müssen eventuell noch andere Operationen durchgeführt werden.

Datenkapselung ist die Voraussetzung zur Implementierung von Schnittstellen:

Schnittstelle
Die Gesamtheit der öffentlichen Strukturen einer Datenstruktur(Klasse) mit der Konsumenten(Verwender) einer Datenstruktur interagieren können. Schnittstellen stellen die Funktionalität einer Datenstruktur(Klasse) nach außen zur Verfügung.

 Entwickler sollten beim Anwendungsentwurf eine:

  • maximale Datenkapselung und
  • minimale Schnittstelle

einplanen. Klassen sollten möglichst autark sein und auf möglichst wenig andere Schnittstellen und Klassen zugreifen.

Sichtbarkeitssteuerung in Java mit Hilfe der Schlüsselwörter public und private

Java erlaubt den Zugriff auf Methoden und Attribute mit Hilfe der Schlüsselwörter private und public vor dem Namen des Attributs oder Methode zu steuern:

  • public: Methoden und Attribute einer Klasse können
    • von Methoden der Klasse selbst genutzt werden
    • von Methoden andere Klassen genutzt werden (externe Benutzung)
  • private: Methoden und Attribute einer Klasse können
    • von Methoden der Klasse selbst genutzt werden
    • nicht von Methoden ausserhalb der Klasse genutzt werden

Diese Zugriffssteuerung erlaubt die Implementierung der folgenden Empfehlung

  • Eine Klasse sollte nur die Attribute und Methoden veröffentlichen die externe Konsumenten unbedingt brauchen
  • Man sollte als Standard alle Attribute als private deklarieren
  • Man sollte als Standard Zugriff auf Attribute immer nur indirekt über öffentliche Methoden (public) gewährleisten

 Attribute und Methoden ohne ein Schlüsselwort werden in Java als public Attribute und Methoden behandelt.

Java verfügt auch über das Schlüsselwort protected welches den Zugriff nur innerhalb eines Pakets erlaubt. Pakete werden erst in der weiterführenden Vorlesung eingeführt werden.

Zugriffssteuerung mit Get- und Set- Methoden

Es ist eine Konvention (und guter Programmierstil) in Java den Zugriff auf private Attribute mit Methoden zu gewähren denen man vor dem Attributnamen get- bzw. set- voranstellt:

class Person() {
   private String name;
   public void setName(String n) { name = n;}
   public String getName() { return name;}
}
getter und setter Methoden

Dieser Programmierstil bietet eine Reihe von Vorteilen:

  • zukünftige Wartungsaufwände und Erweiterungen können leichter implementiert werden, da externe Benutzer ihre Implementierung nicht ändern müssen
    • Die interne Implementierung kann vollständig geändert werden solange die Signatur der öffentlichen Methoden unverändert bleibt.
  • get-, set- Methoden erlauben das Implementieren weitergehender Konsistenzprüfungen.

Anmerkung: Laufzeiteinbußen durch solche Trivialmethoden sind in der Regel nicht zu befürchten. Der Java "Just in Time" (JIT) Übersetzer des Laufzeitsystems wird mit hoher Wahrscheinlichkeit solche Methoden zur Laufzeit durch Methoden-inlining wegoptimieren.

5.1.2 Architekturprinzipien

Trennung der Zuständigkeiten

Trennung von Zuständigkeiten(Separation of Concerns) ist ein Konzept welches auf Datenkapselung aufbaut.

Die Idee besteht darin Zuständigkeiten Klassen zu zuordnen.

Alle Aufgaben die in einen Bereich fallen sollen möglichst von genau einer Klasse implementiert werden. Ziel ist es, dass unabhängige Dinge auch in der Implementierung unabhängig voneinander bleiben. Hierdurch

  • sinkt die Gesamtkomplexität und Systeme sind einfacher zu verstehen 
  • unterschiedliche Komponenten können unabhängig von einander gepflegt werden
  • Fehler in einem Bereich sollen sich möglichst nicht in einem anderen Bereich bemerkbar machen

Ziel ist es Klassen so zu modellieren, dass sie möglichst in sich abgeschlossen sind.

Ein Beispiel hierfür:

  • Die Webarchitektur
    • html (Hypertext Markup Language) ist die Seitenbeschreibungssprache
      • css (Cascading Style Sheets) ist die Sprache zur Beschreibung des Layouts der Seite (getrennt vom Inhalt)
    • http ist das Protokoll zur Übertragung der html Seiten (getrennt vom Inhalt)

Entwurfsmuster "Model View Controller" (MVC)

MVC ist ein Entwurfmuster welches aus den folgenden drei Einheiten besteht:

  • Model (Datenmodell) Alle Aspekte die die Daten betreffen (Integrität, Speicherung, Konvertierung etc.)
  • View (Präsentation): typischerweise die Benutzerschnittstelle einer Anwendung mit allen graphischen und interaktiven Komponenten
  • Controller(Programmsteuerung): Die Ablauflogik und Steuerung eines Programmes
MVC pattern

Die Einteilung einer Anwendung in die folgenden drei Bereich ist oft vorteilhaft da häufig

  • die Benutzerschnittstellen angepasst oder ausgetauscht werden müssen. Das Datenmodell ist dann nicht betroffen. Die Benutzerschnittstelle sollte unabhängig von den anderen Komponenten sein um unterschiedliche Technologien (Web, rich client, OS spezifische Bibliotheken) nutzen zu können
  • das Datenmodell auf andere Datenbankprodukte angepasst werden muss ohne das andere Komponenten davon betroffen sein sollen
  • die Ablauflogik angepasst werden muss und die Änderungen in den Benutzerschnittstellen minimal gehalten werden sollen

Wichtig ist zu verstehen, dass die drei Komponenten zusammenarbeiten und idealerweise unabhängig voneinander sind. Das Model sollte zum Beispiel auf Aufrufe von View und Controller reagieren, jedoch nicht selbst diese Komponenten aufrufen.

Schichtenarchitektur

Schichtenarchitekuren sind eine andere Ausprägug der "Separation of Concerns".

Java selbst und Javaanwendungen basieren auf dem Schichtenmodell.

Eine Javaanwendung soll nur auf die Dienste der Java Runtime zurückgreifen. Die Java Runtime bietet hierfür eine reichhaltige Infrastruktur für GUI Programmierung, Datenbankzugriff, Netzwerkkommunikation, Dateizugriff etc.

Beschränkt man sich auf die von der Java Runtime angebotenen Dienste wird man unabhängig vom Betriebssystem und der darunter liegenden Hardware.

Ziel bei der Entwicklung einer Anwendung sollte es sein unterschiedliche Schichten zu identifizieren und von Klassen nur auf die eigene Schicht oder die darunter liegende Schicht zu beschränken.

5.2 Objektorientierung in Java

Duke in 3D

5.2.1 Javaklassen und -objekte

Bisher wurden Klassen nur benutzt um mit Methoden zu arbeiten. Die main() Methode wurde immer als Hauptprogramm genutzt um Methoden aufzurufen. Klassen sind jedoch Strukturen die auch Variablen und Konstanten in Form von Attributen aufnehmen können.

Javaklasse

Javaklassen bestehen aus

 

Im folgenden Beispiel wird ein Kraftwagen modelliert:

class Kraftwagen {
   public String nummernschild;
   public double leergewicht;
   public int neupreis;

   /**
   * Ein selbstgewählter Konstruktor
   * @param kennzeichen Das Kennzeichen des Fahrzeugs
   */
   public Kraftwagen(String kennzeichen) {
       nummernschild = kennzeichen;
   }

   public void drucken() {
      System.out.println("Nummernschild: " + nummernschild);
      System.out.println("Leergewicht: " +  leergewicht );
      System.out.println("Neupreis: " +  neupreis );
   }
}

Die Klasse Kraftwagen unterscheidet sich in einer Reihe von Aspekten von den bisher benutzten Klassen:

  • Sie besitzt keine main() Methode. Sie ist nicht als Hauptprogramm ausführbahr.
  • die drucken() Methode is nicht als static deklariert
  • die Klasse verfügt über Attribute

Die Klasse Kraftwagen kann nun von anderen Klassen verwendet werden:

class Fuhrpark{
   public static void main( String[] args ) {
      Kraftwagen wagen1 = new Kraftwagen("M-O-4711");
      Kraftwagen wagen2 = new Kraftwagen("MA-Y-11");
      Kraftwagen wagen3 = new Kraftwagen("S-AY-7");
      wagen1.leergewicht = 890.0d;
      wagen1.neupreis = 20000;
   
      wagen2.leergewicht = 1050.0d;
      wagen2.neupreis = 15000;

      wagen3.leergewicht = 1250.0d;
      wagen3.neupreis = 28000;

      wagen1.drucken();
      wagen2.drucken();
      wagen3.drucken();

   }
}

Die Methode main() der Klasse Fuhrpark legt hier drei Objekte (Instanzen) der Klasse Kraftwagen an. Die Attribute der drei Objekten können mit individuellen Werten belegt werden. Die Attribute wagen1wagen2 und wagen3 nennt man Objektvariablen. Objektvariablen können auf Objekte eines bestimmten Typs zeigen. Im Beispiel oben können sie auf ein beliebiges Objekt vom Typ Kraftwagen zeigen.

Die Beziehung zwischen einer Klasse und ihren Instanziierungen, den Objekten beschreibt man in UML wie folgt:

Instanzen

Erzeugen von Objekten

Die Instanzen von Klassen, also die Objekte, haben einen anderen Lebenszyklus als normale Variablen mit einfachen Typen. Instanzen (Objekte) können auch ausserhalb eines Blocks oder einer Methode existieren. Sie liegen nicht auf dem Stapel (Stack). Sie werden dynamisch im (prozess-)globalen Adressraum angelegt und sie bestehen so lange sie von Objektvariablen referenziert werden.

Mit dem new() Operator werden neue Objekte von Klassen angelegt. Beim Anlegen eines Objekts werden alle Attribute durch die Konstruktoren initialisiert. Im Beispiel oben wird ein Konstruktor mit einem Übergabeparameter benutzt. In diesem Fall wird der selbst definierte Konstruktor mit einem Parameter der Klasse zur Initialisierung aufgerufen. Existiert dieser Konstruktor nicht, so wird ein Standardkonstruktor aufgerufen.

Der Standardkonstruktor initialisiert alle Basistypen auf Nullwerte. Nach der Initialisierung steht das Objekt zur Verfügung und belegt Platz im Prozessspeicher.

Die erzeugten Objekte liegen auf dem "Heap", dem Freispeicher. Im Gegensatz zu Variablen können mehrere Referenzvariablen auf das gleiche Objekt zeigen wie im folgenden Beispiel zu sehen ist:

Kraftwagen wagen1 = new Kraftwagen ("M-O-4711)";
Kraftwagen wagen2 = wagen1;
Kraftwagen wagen3 = new Kraftwagen("S-AY-7");

Benutzen der Objektvariablen und Methoden (Punktoperator)

Wie im Beispiel oben gezeigt kann man in der Klasse Fuhrpark mit der Objektvariablen wagen1 auch auf die Attribute und Methoden der Klasse Kraftwagen zugreifen. Hierzu dient der Punktoperator:

      ...
      wagen1.leergewicht = 980.0d;
      wagen1.drucken();
      ...

Man kann mit dem Punktoperator

  • auf Attribute (hier leergewicht) zugreifen oder man kann
  • Methoden (hier drucken() ) aufrufen.

Information Hiding in Java mit Hilfe des Schlüsselworts "private"

Java erlaubt es den Zugriff auf Methoden und Variablen mit Hilfe des Schlüsselworts private zu beschränken. Ist eine Objektvariable oder eine Methode mit private gekennzeichnet, so kann man sie nur innerhalb der Klasse benutzen. Der Zugriff ist dann nur noch über öffentlich zugängliche Methoden möglich.

Mit dem Schlüsselwort public wird der öffentliche Zugriff explizit gewährt. Ein Schlüsselwort das den Zugriff regelt ist protected. Es wird später behandelt.

Im folgenden Beispiel sieht man wie man die Variable neupreis der Klasse Kraftwagen vor dem Zugriff der Klasse Fuhrpark schützt. Die Methode getNeupreis erlaubt den Zugriff von ausserhalb der Klasse.

class Kraftwagen {
   public String nummernschild;
   public double leergewicht;
   private int neupreis;

   public int getNeupreis() { return neupreis;}

   ...
}

In UML werden private Attribute mit einem vorgestellten Minus "-" gekennzeichnet. Öffentliche Attribute erhalten ein vorgestelltes Plus "+":

Klassenvariablen

  • Klassenvariablen sind Variablen die zur Klasse gehören und nicht zu einem einzelnen Objekt.
  • Klassenvariablen sind Variablen die für alle Objekte einer Klasse gelten.
  • Klassenvariablen können auch ohne die Existenz von Objekten einer Klasse verwendet werden
  • Klassenvariablen werden mit dem Schlüsselwort static gekennzeichnet.

Beispiel:

class Fuhrpark{
   static int anzahlFahrzeuge;
   public static void main( String[] args ) {
      int a = Fuhrpark.anzahlFahrzeuge;   // Aufruf ausserhalb der Klasse Fuhrpark
          a = anzahlFahrzeuge;            // Aufruf innerhalb der Klasse Fuhrpark
      ...
   }
}

Hinweis: Die "normalen" nicht mit static gekennzeichneten Attribute einer Klasse nennt man zur Unterscheidung auch Instanzvariablen, da sie nur im Kontext einer Instanz (Objekt) existieren können.

Klassenmethoden

Klassenmethoden werden ebenfalls mit dem Schlüsselwort static gekennzeichnet. Sie können wie normale Methoden im Kontext von Objekten aufgerufen werden.

Unterschied zu normalen Methoden: Sie können jedoch auch ohne die Existenz von Objekten benutzt werden.

Will man eine Methode einer Klasse ohne eine existierende Instanz aufrufen, muss die Methode vorher mit static gekennzeichnet worden sein.

Im Falle der Klasse Fuhrpark kann man sie zum Beispiel zum Auslesen der Anzahl der Fahrzeuge benutzen:

class Fuhrpark{
   private static int anzahlFahrzeuge;
   public  static int getAnzahlFahrzeuge() {return anzahlFahrzeuge;}
      ...
   }
}

Die main() Methode sowie die Methoden der Klasse Math sind typische Methoden, die mit static deklariert sind, da sie ohne Objektinstanzen auskommen (müssen).

5.2.2 this Referenz

Java verfügt über das Schlüsselwort this um auf die aktuelle Instanz innerhalb eines Methodenrumpfes zu referenzieren. Mit Hilfe der folgenden Notation kann man mit dem Schlüsselwort  this die Methoden und Attribute der eigenen Klasse referenzieren:

  • this.Attributname
  • this.methodenName()

Hinweis: Die this Referenz ist final. D.h. man kann ihr keinen anderen Wert zuweisen.

Es gibt eine Reihe von typischen Anwendungsfällen:

Einfache Selbstreferenz

Eine Methode einer Klasse soll die Instanz der Klasse einem Konsumenten bekannt machen. Zum Beispiel:

  • Selbst Hinzufügen bzw. Registrieren zu einem Addressbuch innerhalb des Konstruktors der Klasse Person
  • Methoden oder Attributaufruf (siehe Aufruf von setNachName(nn) im Konstruktor)
  • Rückgabe einer Referenz auf sich selbst (siehe setNachName() )
import class AddressBuch;
class Person {
   private String nachName;
   private String vorName;
...
   public Person (String vn, String nn) {
      this.setNachName(nn);
      vorName  = vn;
      Adressbuch.eintragen(this);
      ...
   }
   public Person setNachName(String nn) {
      nachName = nn;
      return this;
   }
}

Auflösen von Mehrdeutigkeiten

Die this Referenz erlaubt auch das Auflösen von Mehrdeutigkeiten innerhalb eines Namensraumes. Im folgenden Beispiel verdecken die Übergabeparameter nachName, vorName im Konstruktor der Klasse Person die gleichnamigen Attribute:

import class AddressBuch;
class Person {
   private String nachName;
   private String vorName;
...
   public Person (String vorName, String nachName) {
      this.nachName = nachName;
      this.vorName  = vorName;
      Adressbuch.eintragen(this);
      ...
   }
}

Aufruf alternativer Konstruktoren mit this()

Das Schlüsselwort this kann auch als Methodenaufruf this() verwendet werden. Dies erlaubt den Aufruf von Konstruktoren der eigenen Klasse.

Diese Technik ist sehr nützlich um bei mehreren Konstruktoren die Replikation von Code zu vermeiden. Dies geschieht im folgenden Beispiel der Klasse Person() bei einem Konstruktor ohne Parameter. Er benutzt den Konstruktor mit Parameter zum Setzen des Namens indem er einen Standardnamen verwendet:

import class AddressBuch;
class Person {
   private String nachName;
   private String vorName;
...
   public Person (String vorName, String nachName) {
      this.nachName = nachName;
      this.vorName  = vorName;
      Adressbuch.eintragen(this);
      ...
   }
   public Person() {
      /* Rufe den Konstruktor Person(String vorName, String nachName) auf
         Dieser Kommentar ist das einzige Kommando welches vor einem this()
         Aufruf stehen darf! 
      */
      this("John","Doe");
   }
}

Der Vorteil dieser Programmiertechnik liegt in der Vermeidung von Redundanzen. Beim Aufruf des Konstruktors ohne Parameter wird durch den Aufruf des Konstruktors mit Name, Vorname zum Beispiel auch das Addressbuch gepflegt.

5.2.3 Konstruktoren (2)

Die schon früher angesprochenen Konstruktoren sind eine wichtige Komponente der Datenkapselung, da man mit ihnen eine komplexe und korrekte Initialisierung von Objekten erzwingen kann.

Konstruktoren mit Parametern

Konstruktoren können wie Methoden Übergabeparameter enthalten. Sie werden vom new() Operator erfasst, der dann nach dem Initialisieren der Datenstrukturen den entsprechenden Konstruktor aufruft.

Punkt p1 = new Punkt (3.3D, 4.1D);

Hier wird ein Konstruktor der Klasse Punkt aufgerufen der folgende Methodenparameter hat

class Punkt {
   private double x;
   private double y;

   public Punkt (double xKoord, double yKoord) {
      x = xKoord;
      y = yKoord;
   }
}

Das Schreiben von Konstruktoren ist optional. Es können mehrere Konstruktoren implementiert werden. Alle Konstruktoren müssen sich jedoch in der Parameterliste unterscheiden. Hier zählt die Reihenfolge und Anzahl der Parametertypen, jedoch nicht der Name der Parametervariablen. Der Übersetzer braucht diese Information um die unterschiedlichen Konstruktoren auszuwählen.

Überladene Konstruktoren

Überladen von Methoden und Konstruktoren
Das Implementieren von mehreren namensgleichen Methoden oder Konstruktoren mit unterschiedlichen Eingabe-Parameterlisten nennt man überladen.

Java unterscheidet die unterschiedlichen Methoden und Konstruktoren an den Eingabelisten der Parameter jedoch nicht am Rückgabeparameter!

Es kann sehr nützlich sein mehrere Konstruktoren zur Initialisierung einer Klasse zur Verfügung zu stellen wie man am Beispiel der Klasse Punkt sehen kann. Die Klasse Punkt erlaubt hier die folgenden Initialisierungsarten:

  • Initialisierung mit Nullpunkt(0,0)
  • Initialisierung mit x,y Koordinate
  • Initialisierung mit den Werten eines anderen Punkts
class Punkt {
   private double x;
   private double y;

   public Punkt (double xKoord, double yKoord) {
      x = xKoord;
      y = yKoord;
      }

   public Punkt (Punkt p) { this(p.x,p.y);}

   public Punkt () { this(0,0); }
}
...
Punkt p1 = new Punkt();        // Initialisierung mit (0,0)
Punkt p2 = new Punkt(1.1D,2.2D); // Initialisierung mit (1.1,2.2)
Punkt p3 = new Punkt(p2);      // Initialisierung mit (1.1,2.2) durch Punkt p2
...

Der Default-Konstruktor (ohne Parameter) wurde hier selbst implementiert. Er ruft mit Hilfe des Schlüsselworts this den Konstruktor mit den beiden Fliesskommazahlen als Parameter auf. Er initialisiert die Datenstruktur nicht selbst, sondern er delegiert die Initialisierung an einen anderen Konstruktor. Dies ist nicht notwendig aber üblich.

Der Konstruktor mit der Parameterliste Punkt verfährt ähnlich.

Aufrufe von Konstruktoren durch Konstruktoren (der gleichen Klasse)

Konstruktoren können andere Konstruktoren der gleichen Klasse mit this(parameter-liste) aufrufen.

Wichtig: Der this() Aufruf muss jedoch der erste Befehl in einem Konstruktor sein.

Ein optionaler this() Aufruf muss das erste Kommando im Codeblock des Konstruktors sein um die Integrität des mit this() aufgerufenen Konstruktors zu gewährleisten.

Ein Konstruktor ist die erste Methode die ein Objekt initialisiert. Das ist aber nicht mehr für den aufgerufenen Konstruktor geährleistet, wenn dem aufrufenden Konstruktor schon Objektmanipulationen vor dem this() Aufruf erlaubt sind.

Regeln zum Aufruf von Konstruktoren

Java stellt einen Standardkonstruktor (Default-Konstruktor) ohne Parameter zur Verfügung der alle Attribute mit Standardwerten belegt. Implementiert man eigene Konstruktoren gelten die folgenden Regeln für die Benutzer von Konstruktoren:

  • Wurde kein Konstruktor implementiert, generiert Java einen Default-Konstruktor ohne Parameter.
  • Wird mindestens ein Konstruktor selbst implementiert so generiert Java zur Laufzeit keinen Standardkonstruktor. Benutzer müssen einen der selbst implementierten Konstruktoren verwenden.
    • Wird ein Konstruktor ohne Parameter implementiert wird dieser anstatt eines Standardonstruktor benutzt, da er für den Anwender die gleiche Syntax hat.

Dieses Vorgehen ist notwendig um eine Datenkapselung zu erzwingen.

Bei einer Implementierung mit eigenen Konstruktoren aber ohne einen Default-Konstruktor führt der folgende Konstruktoraufruf zu einem Übersetzungsfehler:

class Punkt {
   private double x;
   private double y;

   public Punkt (double xKoord, double yKoord) {...}
   public Punkt (Punkt p) { this(p.x,p.y);}
...
}

...
Punkt p1 = new Punkt();        // Initialisierung mit Default-Konstruktor ist nicht möglich
...

Erklärung der von Java geforderten Semantik:

  • Hat ein Entwickler keinen Konstruktor implementiert, so war ihm die Initialisierung des Objekts nicht wichtig. 
    • Das Objekt wird von einem automatisch generierten Konstruktor mit Nullwerten initialisiert
    • Der Standardkonstruktor wird benutzt wie ein Konstruktor ohne Parameter
  • Hat der Entwickler mindestens einen Konstruktor selbst implementiert, so ist eine spezielle Initialisierung des Objekts gewünscht
    • Ein Konsument muss das Objekt mit Hilfe einer der selbstimplementierten Konstruktoren initialisieren
    • Würde das Java einen zusätzlichen Standardkonstruktor generieren könnte man die vom Entwickler gewünschte Initialisierung umgehen. Dies ist nicht gewünscht.

Verbieten des Instanziieren von Objekten einer Klasse

Java bietet die Möglichkeit einen Konstruktor als private zu deklarieren. Hiermit kann niemand ausserhalb der Klasse Instanzen erzeugen. Diese Möglichkeit erlaubt die Anzahl von Instanzen bei Bedarf genau zu kontrollieren. Ein Anwendungsfall ist ist das Benutzen einer statischen Methode und eines privaten Kontruktors:

class Punkt {
   private double x;
   private double y;

   public static Punkt createPunkt (double xKoord, double yKoord) {
      Punkt pp = new Punkt(xKoord, yKoord);
      return pp;
      }

   private Punkt (double xx, double yy) { x=xx; y=yy;}

}
...
Punkt p1 = Punkt.createPunkt(1.1D,2.2D); 
Punkt p2 = new Punkt(4.4D,5.5D);      // Fehler!!
...

Ein anderer Anwendungsfall ist die Benutzung des Entwurfsmodell "Singleton" d.h. einer Klasse von der es genau eine Instanz gibt. Ein Beispiel ist die Klasse Addressbuch:

class Addressbuch {
   private static Addressbuch myInstance;

   private Addressbuch() {
      // Initialisiere Addressbuch
      }

   public static Addressbuch getAddressbuch() {
      if (myInstance == null) myInstance = new Addressbuch();
     return myInstance; 
   }
}
...
Addressbuch ab = getAddressbuch ();
...

Mit dieser Implementierung wird beim ersten Anfordern eines Addressbuchs genau einmal ein Addressbuch angelegt.

5.2.4 Ablauf der Initialisierung einer Klasse

 Begriffsbestimmung:

Instanziierung einer Klasse
Erzeugen eines neuen Objekts einer Klasse auf dem Java Heap (Freispeicher)

 

Initialisierung (von Datenfeldern)
Das Belegen von existierenden Datenfeldern mit Standardwerten oder mit von Konstruktoren individuell implementierten Werten

Beim Erzeugen eines Javaobjekts wie zum Beispiel einer Instanz der Klasse Punkt:

class Punkt {
   private double x;
   private double y;

   public Punkt (double xKoord, double yKoord) {
      x = xKoord;
      y = yKoord;
      }
}
...
Punkt p2 = new Punkt(1.1,2.2);
...

Mit der Variable p2 l laufen die folgenden Schritte beim Aufruf der Programmzeile Punkt p2=new Punkt() ab:

  1. Die Referenzvariable p2 wird angelegt und mit dem Wert null belegt
  2. Der new Operator der Klasse Punkt wird aufgerufen. Auf dem Java-Heap wird der Platz für ein Objekt der Klasse Punkt zur Verfügung gestellt. Es wird instanziiert.
  3. Die Datenfelder des Objekts werden initialisiert. Dies geschieht je nach Typ mit den Belegungen: null, false, 0, 0f etc.
  4. Der Methodenblock des entsprechenden Konstruktors wird ausgeführt.
  5. Der new Operator gibt die Referenz auf das Objekt zurück welches in p2 gespeichert wird.

5.3 Übungen

Duke als Boxer

5.3.1 Übung: Vektorrechnung

Ein Vektor im zweidimensionalen Raum kann durch 2 reelle Zahlen dargestellt werden.
Erstellen Sie eine Klasse "Vektor", die Operationen(Methoden) für
  • die Addition von Vektoren,
  • die Multiplikation eines Vektors mit einem Skalar,
  • die Bildung des Skalarprodukts zweier Vektoren (d.h. die Summe der Produkte der entsprechenden Komponenten) anbietet.
Schreiben Sie auch einen geeigneten Konstruktor und implementieren Sie eine Ausgabemethode drucken().
Normalisieren Sie die Vektoren nach jeder Berechnung, so dass ihr Betrag den Wert 1 hat.
 


5.3.2 Übung: Komplexe Zahlen

Eine komplexe Zahl (z.B. 3.2 + i1.75) besteht aus einem reellen und einem imaginären Teil, beide vom Typ double. Erstellen Sie eine Klasse Complex, die komplexe Zahlen implementiert. Als Operationen (Methoden) sollen die vier Grundrechenarten sowie ein geeigneter Konstruktor angeboten werden. Hinweis:

(a + ib) + (c + id) = (a + c) + i(b + d)
(a + ib) - (c + id) = (a - c) + i(b - d)
(a + ib) * (c + id) = (a*c - b*d) + i(a*d + b*c)
(a + ib) / (c + id) = (a*c + b*d)/(c*c + d*d) + i(b*c - a*d)/(c*c + d*d)

Benutzen Sie die Klasse Main als Hauptprogramm um auf die Klasse Complex zuzugreifen:

public class Main {
    public static void main(String[] args) {
        Complex a = new Complex (1.0,2.0);
        Complex b = new Complex (3.0,4.0);
        Complex c,d,e,f;

        c = a.add(b);
        d = a.sub(b);
        e = a.mul(b);
        f = a.div(b);

        System.out.println (" a = " + a.toString());
        System.out.println (" b = " + b.toString());
        System.out.println (" c = " + c.toString());
        System.out.println (" d = " + d.toString());
        System.out.println (" e = " + e.toString());
        System.out.println (" f = " + f.toString());
    }
}
komplexe Zahl

Die Klasse Complex soll neben den Methoden add()sub()mul()div() auch eine Methode toString() besitzen, die eine Zeichenkette mit dem Wert der komplexen Zahl im Format "(1.1 + i 2.2)" für einen Realteil von 1.1 und einem Imaginärteil von 2.2 ausgibt.

5.3.3 Übung: Modellierung der Datenkapselung

Erweitern Sie eine Klasse Flugzeug.java von einer einfachen Klasse mit einem öffentlichen Attribut zu einer objektorientierten Klasse mit geschützten Attributen und Zugriffsmethoden.

Vorsicht: Der unten aufgeführte Quellcode liegt nicht im default package. Der Quellcode liegt in package block5! Achten Sie auf das korrekte Paket beim Anlegen der Klassen.

UML Diagramm eines Flugzeugs

 

  1. Kopieren Sie sich die beiden Klassen Flugzeug.java und FlugzeugTest.java auf Ihren Rechner. Die Klasse FlugzeugTest dient zum Testen und Starten der Anwendung.
  2. (Schritt 1-5) Implementieren die notwendigen Attribute als geschützte Attribute. Die Namen der Attribute sind auch in der drucken() Methode zu finden
  3. (Schritt 6) Implementieren Sie die Methode zur Berechnung des aktuellen Gewichts
  4. (Schritt 7): Entfernen Sie die Kommentare in der drucken() Methode.
  5. (Schritt 8): Implementieren Sie einen Konstruktor für die Klasse Flugzeug. Der Konstruktor soll die Eingaben auf unvernünftige Werte prüfen und die Eingaben korrigieren (Werte kleiner Null, Maximalgewicht kleiner Leergewicht etc.)
  6. (Schritt 9):  Schalten Sie im Hauptprogramm FlugzeugTest.main() den Aufruf der Methode phase1() frei und testen Sie das Programm durch den Aufruf der Klasse FlugzeugTest
  7. (Schritt 10-13): Implementieren Sie alle benötigten Methoden (siehe Diagramm)
  8. (Schritt 14): Schalten Sie im Hauptprogramm FlugzeugTest.main() den Aufruf der Methode phase2() frei und testen Sie das Programm durch den Aufruf der Klasse FlugzeugTest
  9. (Schritt 15): Schalten Sie im Hauptprogramm FlugzeugTest.main() den Aufruf der Methode phase3() frei und testen Sie das Programm durch den Aufruf der Klasse FlugzeugTest. Analysieren Sie die Umsteigeimplementierung in phase3(). Testen Sie einige Sonderfälle durch Veränderung der Passagierzahlen. Beispiel: Was geschieht wenn das defekte Flugzeug mehr Passagiere hat als das Ersatzflugzeug?
  10. (Schritt 16): Implementieren Sie die Methode phase4() und schalten Sie im Hauptprogramm FlugzeugTest.main() den Aufruf der Methode phase4() frei. Testen Sie das Programm durch den Aufruf der Klasse FlugzeugTest.
    • Implementieren Sie in Phase das Umsteigen von einem Airbus mit 560 Passagieren in zwei kleinere Jumbo Jets.
    • Verändern Sie die Ausgangssituation derart, dass in den Jumbo Jets schon zu viele Passagiere eingestiegen sind und nicht alle Passagiere des Airbus untergebracht werden können. Werden stehen gelassene Passagiere auf der Konsole gemeldet?

Gegeben:

Klasse Flugzeug

package block5;
public class Flugzeug {

public String kennzeichen; // Ein Attribut vom Typ einer Zeichenkette
// 1. Privates Attribut zur Verwaltung der Passagierkapazität
// Tipp: Untersuchen Sie die Druckmethode zur Wahl der
// Variablennamen (1-5)!

// 2. Privates Attribut zur Verwaltung der aktuellen Pasagiere

// 3. Leergewicht in privates Attribut ändern
// Ein Attribut vom Typ einer Ganzzahl
// 4. Maximales Gewicht des Flugzeugs

// 5. Öffentliche Konstante für durchschn. Passagiergewicht
/**
* 8. Konstruktor implementieren
* Konstruktor der Klasse Flugzeug
* @param kennz Kennzeichen des Flugzeugs
* @param kapazitaet Passagierkapazität
* @param leergew Leergewicht in kg
* @param maxgew Maximalgewicht in kg
*/
/**
* einsteigen()
* 10. Fügt einen Passagier zum aktuellen Flugzeug hinzu
*/
/**
* aussteigen()
* 11. Entfernt einen Passagier des aktuellen Flugzeugs
*/

/**
* anzahlPassagiere()
* 12. Ausgabe der aktuellen Anzahl der Passagiere
* @return aktuelle Anzahl der Passagiere
*/


/**
* gewicht()
* 6. Berechnen des aktuellen Gewichts
* @return aktuelles Gewicht
*/

/**
* passagierkapazität()
* 13. Ausgabe der maximalen Anzahl der Passagiere
* @return Maximale Anzahl der Passagiere
*/

/**
* Eine Methode zum Drucken der Attributbelegung des Objekts
* Die Methode erfordert keine Eingaben. Sie erzeugt keine
* Aufgaben
*/
public void drucken() {
// 7. Vervollständigen der Druckmethode
System.out.println("*****************************");
System.out.println("Kennzeichen: " + kennzeichen);
//System.out.println("Leergewicht: " + leergewicht + "kg");
//System.out.println("Maximalgewicht: " + maxgewicht + "kg");
//System.out.println("Aktuelles Gewicht : " + gewicht() + "kg");
//System.out.println("Passagiere: " + passagiere);
//System.out.println("Maximal Anzahl P.: " + maxPassagiere);
System.out.println("*****************************");
}

}

Klasse FlugzeugTest.java

package block5;

public class FlugzeugTest {
public static Flugzeug jumbo;
public static Flugzeug a380;

/**
* Die Methode main() wir zum Starten des Programms benutzt
* @param args Übergabe von Konsolenparameter. Hier nicht genutzt
*/
public static void main(String[] args) {
//phase1(); // 9. Phase 1 testen
//phase2(); // 14. Phase 2 testen
//phase3(); // 15. Phase 3 testen
//phase4();
}

/* Entfernen zum Testen von Phase 1
public static void phase1() {
System.out.println(" Phase 1: 2 Flugzeuge");
// Erzeugen zweier Objekte
jumbo = new Flugzeug("D-ABYT",360,191000,400000);
a380 = new Flugzeug("D-AIMD",560,286000,500000);

// Drucken der beiden Objekte auf der Konsole
jumbo.drucken();
a380.drucken();
}
*/
/* Entfernen zum Testen von Phase 2
public static void phase2() {

// 7. Testen des vorangehenden Hauptprogramms
System.out.println(" Phase 2: Einsteigen mit Überbuchung");

System.out.println("Ein Passagier in Jumbo einsteigen");
jumbo.einsteigen();
jumbo.drucken();
System.out.println("300 Passagiere in Jumbo einsteigen");
for (int i=0; i<300; i++) jumbo.einsteigen();
jumbo.drucken();
System.out.println("200 Passagiere aus Jumbo aussteigen");
for (int i=0; i<200; i++) jumbo.aussteigen();
jumbo.drucken();
System.out.println("200 Passagiere aus Jumbo aussteigen");
for (int i=0; i<200; i++) jumbo.aussteigen();
jumbo.drucken();

}
*/
/* Entfernen zum Testen von Phase 3
public static void phase3() {

System.out.println(" Phase 3: Jumbo Flugzeugdefekt");
Flugzeug jumboAlt = new Flugzeug("D-ABYU",360,191000,400000);
Flugzeug a380Neu = new Flugzeug("D-AIME",560,286000,500000);
jumboAlt.drucken();
a380Neu.drucken();

System.out.println("300 Passagiere in JumboAlt einsteigen");
for (int i=0; i<300; i++) jumboAlt.einsteigen();
System.out.println("100 Passagiere in Airbus 380 Neu einsteigen");
for (int i=0; i<100; i++) a380Neu.einsteigen();
jumboAlt.drucken();
a380Neu.drucken();
System.out.println("Jumbo ist defekt. Alle in Airbus umsteigen");
while (jumboAlt.anzahlPassagiere()> 0) {
jumboAlt.aussteigen();
a380Neu.einsteigen();
}
System.out.println("Alle umgestiegen. Bereit zum Start");
jumboAlt.drucken();
a380Neu.drucken();
}
*/
/* Entfernen zum Testen von Phase 4
public static void phase4() {

System.out.println(" Phase 3: A380 Flugzeugdefekt mit 560 Passagieren");
Flugzeug jumbo1 = new Flugzeug("D-ABYV",360,191000,400000);
Flugzeug jumbo2 = new Flugzeug("D-ABYW",360,191000,400000);
Flugzeug a380Defekt = new Flugzeug("D-AIME",560,286000,500000);
jumbo1.drucken();
jumbo2.drucken();
a380Defekt.drucken();
System.out.println("50 Passagiere in Jumbo 1 und 2 einsteigen");
// 17. Lassen Sie 200 Passagiere in jeden Jumbo einsteigen
// Hiermit ist nicht mehr Platz für alle Airbuspassagiere
// Testen Sie den Fall der Überbuchung
for (int i=0; i<50; i++) {
jumbo1.einsteigen();
jumbo2.einsteigen();
}
System.out.println("560 Passagiere in Airbus 380 (defekt) einsteigen");
for (int i=0; i<560; i++) a380Defekt.einsteigen();
jumbo1.drucken();
jumbo2.drucken();
a380Defekt.drucken();


System.out.println("Airbus ist defekt. Alle in Jumbos umsteigen");
// 16. Implementieren Sie das Evakuieren des Airbus
// Beide Jumbos sollen falls notwendig benutzt werden
// Drucken Sie eine Warnung aus falls Sie einen Passagier
// nicht umsteigen lassen.


System.out.println("Airbus evakuiert. Bereit zum Start");
jumbo1.drucken();
jumbo2.drucken();
a380Defekt.drucken();
}
*/

}

5.3.4 Klassenvariablen (static variables)

Die Übung baut auf der vorherigen Übung aus. Bitte benutzen Sie die Musterlösung falls Sie die vorherige Übung nicht gelöst haben.

Implementieren Sie einen Zähler für jedes Flugzeugobjekt der Klasse Flugzeug welches erzeugt wird.

Empfohlenes Vorgehen:

  • Implementieren Sie eine statische, geschützte Variable in der Klasse Flugzeug
  • Inkrementieren Sie diese Variable bei jedem Konstruktoraufruf der Klasse Flugzeug
  • Erweitern Sie die Methode drucken() und geben Sie die aktuelle Gesamtzahl von Flugzeugen aus
  • Implementieren Sie eine öffentliche, statische Methode anzahlFlugzeuge() die den Zähler ausgibt
  • Benutzen Sie das existierende Programm in der Klasse FlugzeugTest zum Testen Ihrer Implementierung

Lernziel: Unterscheiden der Variablen die objektspezifisch sind und derer die klassenspezifisch sind.

5.3.5 Überladene Methoden

Die Übung baut auf der vorherigen Übung aus. Bitte benutzen Sie die Musterlösung falls Sie die vorherige Übung nicht gelöst haben.

Implementieren sie überladene Methoden um das Einsteigen und Aussteigen in der Klasse Flugzeug zu vereinfachen. Die neuen Methoden haben

  • die gleichen Namen wie die Methoden zum Ein- und Aussteigen eines einzelnen Passagiers,
  • sie haben einen Übergabeparameter der die Anzahl der Passagiere zum Ein- und Aussteigen angibt
  • es werden nur nicht negative Werte beachtet
  • die Operation wird nur ausgeführt falls ALLE Passagiere in der aktuellen Anforderung ein- oder aussteigen können.

 Testen Sie die Implementierung mit der Klasse FlugzeugTest:

  • Kopieren Sie die Methoden phase3() und phase4(). Benennen Sie diese um in phase3a(), phase4a()
  • Ändern Sie die Implementierung deart, dass anstatt der for-Schleifen einzelne Aufrufe der neuen Methoden durchgeführt werden
    • Tipp: Ändern Sie die Flugzeugkennzeichen. Es werden neue Objekte angelegt. Es ist daher nützlich nicht zwei Flugzeuge mit dem gleichen Kennzeichen zu benutzen.
  • Fügen die Methoden phase3a() und phase4a() in die main() Methode ein.
     

 

5.3.6 Teamübung: Fuhrparkmanagement

Die folgende Übung kann in einem Team von drei Parteien ausgeführt werden. Jede Partei implementiert eine Klasse. Die Übung findet in den folgenden Phasen statt.

  1. Jedes Team entwirft die entsprechende Klasse und stellt die Klasse und alle öffentlichen Methoden und Variablen vor
  2. Jedes Team implementiert seine Klasse und schreibt in der eigenen main() Methode einige Tests.
  3. Alle Teams integrieren die Klassen zu einer lauffähigen Anwendung und testen sie
  4. Jeweils 2 Teams stellen die Implementierungen wechselseitig vor und kontrollieren sie.

Anforderung an alle: Implementieren Sie sinnvolle Konstruktoren

Klasse 1: Kraftwagen

Anforderungen

  • verwalten von
    • Verkaufspreis
    • Einkaufspreis
    • Kennzeichen
  • Schutz der Variablen gegen inkonsistente Änderungen mit Hilfe von Zugriffs- und Auslesemethoden
  • Methode drucken() zum Drucken der Attribute eines Wagens
  • Zugriffsmethoden (Lesen/Schreiben) die alle drei Attribute kapseln und einfache Konsistenzchecks durchführen
  • Implementierung eines Konstruktors der alle drei Parameter erfasst
    • Konsistenzcheck für die Preise: Ist der Verkaufspreis niedriger als der Einkaufspreis so werden die Werte getauscht.
  • Zum Testen
    • Methode public static void main(String[] args) Anlegen von mehreren Kraftwagen mit verschiedenen Werten. Testen der Konsistenzprüfungen und Ausgaben der Werte

Klasse 2: Verkaeufer

Anforderungen

  • verwalten von mindestens 2 Kraftwagen pro Verkäufer
  • Verkäuferattribute und Methoden
    • Name
    • bisher gemachter Gewinn
    • gebundene Mittel (Summe aller Einkaufspreise aller Wagen)
    • Verkauf eines Wagens zu gegebenem Preis
      • Austragen aus dem Fahrzeugbestand
      • Pflegen des Gewinn
    • Aufnehmen eines Wagens in den persönlichen Bestand
      • Existierende Wagen im Bestand können ersatzlos ersetzt werden
    • Abfrage der Anzahl der Wagen im Bestand
    • Abfrage eines Kraftwagens aus dem Bestand
    •  Abfrage der gebundenen Mittel
    • Abfrage des geplanten Umsatzes
    • Methode drucken() zum Ausdrucken aller Daten des Verkäufers und aller Daten des Fahrzeugbestands
  • Warnen bei Verkauf eines Wagen unter Einkaufspreis (Konsolenausgabe)
  • Konstruktor zum Anlegen eines Verkäufers mit einem Namen. Der Namen darf nicht mehr geändert werden.
  • Zum Testen:
    • Methode public static void main(String[] args) : Anlegen von drei Verkaeufern mit verschiedenen Werten und Fahrzeugen.
    • Verkauf diverser Fahrzeuge und Abfrage des Status mit Hilfe der Methode drucken()
    • Testen gegen diverse Fehlerfälle beim Verkauf

Klasse 3: Haendler

Anforderungen

  • Verwalten von mindestens 3 Verkäufern
  • Methoden
    • Einstellen von drei Verkäufern mit gegebenem Namen
    • Geschäftseröffnung mit Einstellen von 6 Fahrzeugen und beliebige Verteilung auf 3 Verkäufer.
    • Zugriff auf jeden der drei Verkäufer
    • gesamter Wert des Fuhrparks
    • gesamter Gewinn
    • gesamter Umsatz
    • verkaufe Fahrzeug für gegebenen Verkäufer (index) und Wagen des Verkäufers (index) zu gegebenen Preis
    • stelle neues Fahrzeug für Verkäufer (index) auf einer bestimmten Position des Verkäufers ein
    • suche des Verkäufers mit den größten Gewinn
    • Methode drucken() zur Ausgabe aller Daten des Unternehmens
  • Zum Testen im Hauptprogramm public static void main(String[] args)
    • Ausdruck des Zustandes des gesamten Unternehmens
    • Berechnen der geplanten Erlöse des Fuhrparks
    • Berechnen des gesamten Werts der Flotte
    • Verkäufer mit dem meisten Umsätzen finden
    • Verkauf eines Wagens
    • Einstellen eines neuen Wagens

Hinweis: Die Spezifikation ist nicht vollständig. Das Team muss auf die folgenden Dinge selbst achten:

  • Zeitplanung (Integrationstests benötigen viel Zeit!)
  • Übergang der Phasen (inklusive notwendige Rückschritte)
  • Gegenseitige Überprüfung der Spezifikation und sinngemässe Ergänzung
    • Man darf sich von anderen Klassen zusätzliche Methoden wünschen wenn es sinnvoll ist.. Man sollte dann auch zur Implementierung beitragen...
  • Gegenseitige Hilfe bei der Implementierung ist erlaubt und erwünscht (Die einzelnen Teilaufgaben können unterschiedlich aufwendig sein!)

5.3.7 Mehrere Konstruktoren

Benutzen Sie das Beispiel der Klasse Flugzeug und der Klasse FlugzeugTest aus der Übung 5.4.5 Überladenen Methode.

Fügen zur Klasse Flugzeug einen weiteren Konstruktor hinzu. Der Konstruktor soll im Gegensatz zum existierenden Konstruktor keinen Parameter zur Passagierkapazität besitzen.

  • Berechnen Sie die Passagierkapazität aus der Differenz des Maximal- und Leergewicht. Teilen Sie die Differenz durch das durchschnittliche Passagiergewicht.
  • Sie müssen nicht alle Zuweisungen und Kontrollen des alten Konstruktors neu implementieren!
  • Rufen Sie den alten Konstruktor mit dem this() Schlüsselwort auf.

Der Dokumentationskommentar zum zu implementierenden Konstruktor

     /**
* Konstruktur der Klasse Flugzeug
* Berechnet Passagierkapazität automatisch
* @param kennz Kennzeichen des Flugzeugs
* @param leergew Leergewicht in kg
* @param maxgew Maximalgewicht in kg
*/

Hinweis:

  1. Die Konstante PASSAGIERGEWICHT muss bei der Deklaration in der Klasse Flugzeug mit dem Schlüsselwort static versehen werden. Man darf zu diesem Zeitpunkt im Konstruktor noch keine Objektvariablen verwenden.
  2. Vorsicht beim Kopieren. Die Klasse Flugzeug befindet sich im Paket block5. Kopieren Sie die Klasse Flugzeug in das Paket block6 oder verwenden Sie import Anweisungen!

Testen Sie das Programm mit der Routine phase6a() der Klasse FlugzeugTest:

package block6;
 
public class FlugzeugTest {
 
    /**
     * Die Methode main() wir zum Starten des Programms benutzt
     * @param args Übergabe von Konsolenparameter. Hier nicht genutzt
     */
 
    public static void main(String[] args) {
        phase6a(); 
        
    }
   
   /**
     * Testen des überladenen Konstruktors
     */
    public static void phase6a() {        
        Flugzeug b737_500 = new Flugzeug("D-ABIAA", 31900, 52000);
        Flugzeug b737_300 = new Flugzeug("D-ABIAB", 12815, 56470);
        System.out.println("Kapazität Boing 737-300: " + b737_300.passagierkapazitaet());
        System.out.println("Kapazität Boing 737-500: " + b737_500.passagierkapazitaet());
    }
}

Warum ist die automatisch berechnete Sitzkapazität der Boing 737 viel zu groß?

Was wurde bei der Modellierung der Klasse nicht berücksichtigt?

5.3.8 Flughafen Beispiel

Klasse Flugzeug

package block6;

/**
* Die Klasse Flugzeug dient zur Modellierung von Flugzeugen
* und vielem mehr...
* @author stsch
* @version 2.0
* @see Flughafen
*/
public class Flugzeug {

final static double durchschnittsgewicht = 75;
String kennzeichen;
/**
* aktuelle Anzahl der Passagiere
*/
int passagiere;

public int getPassagiere() {
return passagiere;
}

public void setPassagiere(int passagiere) {
this.passagiere = passagiere;
}
/**
* das aktuelle Gewicht. Bitte nicht mit rumspielen
*/
private double maximalesGewicht;
double minimalGewicht;

/**
* Konstruktor mit Kennzeichen vom Leitwerk. Z.Bsp. D-ABYD
* @param kennz Das amtliche Kennzeichen des Flugzeugs
*/
public Flugzeug(String kennz) {
kennzeichen = kennz;
System.out.println("Hallo, ich baue ein Flugzeug mit Namen " + kennzeichen);
}

/**
* Konstruktor mit Kennzeichen vom Leitwerk. Z.Bsp. D-ABYD
* @param minGewicht Minimalgewicht
* @param maxGewicht Maximalgewicht
*/
Flugzeug(String kennzei, double minGewicht, double maxGewicht) {
this(kennzei);
System.out.println("Hallo, ich baue ein Flugzeug mit Gewicht");
maximalesGewicht = maxGewicht;
// Kontrolle des Minimalgewichts
if ((minGewicht > 0) && (minGewicht <= maximalesGewicht)) {
minimalGewicht = minGewicht;
} else {
minimalGewicht = 5;
}
// Eine schwachsinnige Initialisierung
passagiere = 1;

}

public double maxG() {

/*
Das ist ein mehrzeiliger Kommentar
Hier auch auch noch
*/
double x = 17.1D;
x = Double.MAX_VALUE -10000D;
x = Double.MIN_VALUE;
if ( x == Double.POSITIVE_INFINITY) {
System.out.println("Oops. Unendlich");
}

return maximalesGewicht;
}

public void einsteigen() {
passagiere++;
}

public void einsteigen(int einsteiger) {
passagiere = passagiere + einsteiger;
}

/**
* aktuelles Gewicht des Flugzeug
* @return das aktuelle Gewicht in Kilogramm
*/
double gewicht() {
double ergebnis;
ergebnis = 1000 + passagiere * durchschnittsgewicht;
return ergebnis;
}

}

Klasse Flughafen

package block6;

/**
*
* @author stsch
*/
public class Flughafen {

String name;
Flugzeug gate1;
Flugzeug gate2;
Flugzeug gate3;
Flugzeug gate4;
Flugzeug gate5;
Flugzeug gate6;

public static void main(String[] args) {
Flughafen pb;
pb = new Flughafen();
pb.name = "Paderborn";

Flugzeug lh1 = new Flugzeug("D-A123");
lh1.passagiere = 23;

Flugzeug lh2 = new Flugzeug("D-A456",3333.0D,100000D);
lh2.passagiere = 11;

pb.gate1 = lh1;
lh1.einsteigen();
lh1.einsteigen();
double meinGewicht = lh1.gewicht();
lh1.gewicht();

pb.gate2 = lh2;
lh2.einsteigen(88);


System.out.println("Mein Flughafen: " + pb.name);
System.out.println("Gate 1: " + pb.gate1.kennzeichen +
", Passagiere: " + pb.gate1.passagiere +
", akt. Gew.: " + pb.gate1.gewicht());
System.out.println("Gate 2: " + pb.gate2.kennzeichen +
", Passagiere: " + pb.gate2.passagiere);

if (pb.gate3 == null) {
System.out.println("Gate 3: leer");
} else {
System.out.println("Gate 3: " + pb.gate3.kennzeichen);
}

Flughafen fra;
fra = new Flughafen();
fra.name = "Rhein/Main";

}

}
 

5.4 Lösungen

5.4.1 Vektorrechnung

public class Vektor {

   private double x;
    privatedouble y;

    Vektor(double xx, double yy) {
        x = xx;
        y = yy;
        normalisierung();
    }

    public void normalisierung() {
        double laenge = Math.sqrt(x*x+y*y);
        x = x/laenge;
        y = y/laenge;
    }

    public void addition(Vektor v) {
        x = v.x + x;
        y = v.y + y;
        normalisierung();
    }
     public void multi(double skalar) {
        x = x * skalar;
        y = y * skalar;
        // normalisierung();
    }
     public void skalarproduct(Vektor v) {
        x = x * v.x;
        y = y * v.y;
        normalisierung();
    }
     public void drucken() {
     System.out.println("(" + x + "," + y + ")");
     }

}

public class Main {

    public static void main(String[] args) {
        Vektor a = new Vektor(1.0, 2.0);
        Vektor b = new Vektor(2.0, 1.0);
        Vektor c = new Vektor(3.0, 3.0);

        a.drucken();
        b.drucken();
        c.drucken();

        a.addition(b);
        a.drucken();
        a.multi(10);
        a.drucken();
        b.skalarproduct(c);
        b.drucken();
    }
}

5.4.2 Komplexe Zahlen

public class Complex {

    private double re; // Realteil der Zahl
    private double im; // Imaginaerteil der Zahl (i)

    public Complex(double r, double i) {
        re = r;
        im = i;
    }

    public Complex add(Complex z) {
        Complex k = new Complex(re+z.re,im+z.im);
        return k;
    }

    public Complex sub(Complex z) {
        Complex k = new Complex(re-z.re,im-z.im);
        return k;
    }

    public Complex mul(Complex z) {
        Complex k = new Complex(re*z.re-im*z.im,re*z.im+im*z.re);
        return k;
    }

    public Complex div(Complex z) {
        Complex k = new Complex((re*z.re+im*z.im)/(z.im*z.im+z.im*z.im),
                                (im*z.re-im*z.im)/(z.im*z.im+z.im*z.im));
        return k;
    }

     public String toText() {
        String s = "(" + re + " + i" + im + ")";
        return s;
    }
}

5.4.3 Übung: Modellierung der Datenkapselung

Klasse FlugzeugTest.java

package block5;

public class FlugzeugTest {
public static Flugzeug jumbo;
public static Flugzeug a380;


/**
* Die Methode main() wir zum Starten des Programms benutzt
* @param args Übergabe von Konsolenparameter. Hier nicht genutzt
*/
public static void main(String[] args) {
phase1(); // 9. Phase 1 testen
phase2(); // 14. Phase 2 testen
phase3(); // 15. Phase 3 testen
phase4();
}

public static void phase1() {
System.out.println(" Phase 1: 2 Flugzeuge");
// Erzeugen zweier Objekte
jumbo = new Flugzeug("D-ABYT",360,191000,400000);
a380 = new Flugzeug("D-AIMD",560,286000,500000);

// Drucken der beiden Objekte auf der Konsole
jumbo.drucken();
a380.drucken();
}

public static void phase2() {
// 7. Testen des vorangehenden Hauptprogramms
System.out.println(" Phase 2: Einsteigen mit Überbuchung");
System.out.println("Ein Passagier in Jumbo einsteigen");
jumbo.einsteigen();
jumbo.drucken();
System.out.println("300 Passagiere in Jumbo einsteigen");
for (int i=0; i<300; i++) jumbo.einsteigen();
jumbo.drucken();
System.out.println("200 Passagiere aus Jumbo aussteigen");
for (int i=0; i<200; i++) jumbo.aussteigen();
jumbo.drucken();
System.out.println("200 Passagiere aus Jumbo aussteigen");
for (int i=0; i<200; i++) jumbo.aussteigen();
jumbo.drucken();
}

public static void phase3() {
System.out.println(" Phase 3: Jumbo Flugzeugdefekt");
Flugzeug jumboAlt = new Flugzeug("D-ABYU",360,191000,400000);
Flugzeug a380Neu = new Flugzeug("D-AIME",560,286000,500000);
jumboAlt.drucken();
a380Neu.drucken();

System.out.println("300 Passagiere in JumboAlt einsteigen");
for (int i=0; i<300; i++) jumboAlt.einsteigen();
System.out.println("100 Passagiere in Airbus 380 Neu einsteigen");
for (int i=0; i<100; i++) a380Neu.einsteigen();
jumboAlt.drucken();
a380Neu.drucken();
System.out.println("Jumbo ist defekt. Alle in Airbus umsteigen");
while (jumboAlt.anzahlPassagiere()> 0) {
jumboAlt.aussteigen();
a380Neu.einsteigen();
}
System.out.println("Alle umgestiegen. Bereit zum Start");
jumboAlt.drucken();
a380Neu.drucken();
}

public static void phase4() {
System.out.println(" Phase 4: A380 Flugzeugdefekt mit 560 Passagieren");
Flugzeug jumbo1 = new Flugzeug("D-ABYV",360,191000,400000);
Flugzeug jumbo2 = new Flugzeug("D-ABYW",360,191000,400000);
Flugzeug a380Defekt = new Flugzeug("D-AIME",560,286000,500000);
jumbo1.drucken();
jumbo2.drucken();
a380Defekt.drucken();
System.out.println("50 Passagiere in Jumbo 1 und 2 einsteigen");
// 17. Lassen Sie 200 Passagiere in jeden Jumbo einsteigen
// Hiermit ist nicht mehr Platz für alle Airbuspassagiere
// Testen Sie den Fall der Überbuchung
for (int i=0; i<50; i++) {
jumbo1.einsteigen();
jumbo2.einsteigen();
}
System.out.println("560 Passagiere in Airbus 380 (defekt) einsteigen");
for (int i=0; i<560; i++) a380Defekt.einsteigen();
jumbo1.drucken();
jumbo2.drucken();
a380Defekt.drucken();

System.out.println("Airbus ist defekt. Alle in Jumbos umsteigen");
// 16. Implementieren Sie das Evakuieren des Airbus
// Beide Jumbos sollen benutzt werden falls notwendig
// Drucken Sie eine Warnung aus falls Sie einen Passagier
// nicht umsteigen lassen.
while (a380Defekt.anzahlPassagiere()> 0) {
if (jumbo1.anzahlPassagiere() < jumbo1.passagierkapazität()) {
a380Defekt.aussteigen();
jumbo1.einsteigen();
}
else // Jumbo 1 is voll...
if (jumbo2.anzahlPassagiere() < jumbo2.passagierkapazität()) {
a380Defekt.aussteigen();
jumbo2.einsteigen();
}
else // Beide Jumbos sind voll
{
a380Defekt.aussteigen();
System.out.println("Ein Passagier bleibt zurück...");
}

}
System.out.println("Airbus evakuiert. Bereit zum Start");
jumbo1.drucken();
jumbo2.drucken();
a380Defekt.drucken();
}
}

Klasse Flugzeug.java

package block5;

public class Flugzeug {
public String kennzeichen; // Ein Attribut vom Typ einer Zeichenkette
// 1. Privates Attribut zur Verwaltung der Passagierkapazität
// Tipp: Untersuchen Sie die Druckmethode zur Wahl der
// Variablennamen (1-5)!
private int maxPassagiere;
// 2. Privates Attribut zur Verwaltung der aktuellen Pasagiere
private int passagiere;
// 3. Leergewicht in privates Attribut ändern
public int leergewicht; // Ein Attribut vom Type einer Ganzzahl
// 4. Maximales Gewicht des Flugzeugs
private int maxgewicht;
// 5. Öffentliche Konstante für durchschn. Passagiergewicht
public final static int PASSAGIERGEWICHT = 85;

/**
* 8. Konstruktor implementieren
* Konstruktur der Klasse Flugzeug
* @param kennz Kennzeichen des Flugzeugs
* @param kapazitaet Passagierkapazität
* @param leergew Leergewicht in kg
* @param maxgew Maximalgewicht in kg
*/
public Flugzeug(String kennz, int kapazitaet, int leergew, int maxgew) {
kennzeichen = kennz;
// Prüfen ob Kapazität größere Null ist
if (kapazitaet >= 0) {
maxPassagiere = kapazitaet;
} else {
maxPassagiere = 0;
}
// Prüfen ob Leergewicht größer Null ist
if (leergew > 0) {
leergewicht = leergew;
} else {
leergewicht = 0;
}
// Prüfen ob Maximalgewicht größer-gleich Leergeicht ist.
if (maxgew > leergewicht) {
maxgewicht = maxgew;
} else {
maxgewicht = leergewicht; // Viel Spass...
}
}

/**
* 10. Fügt einen Passagier zum aktuellen Flugzeug hinzu
*/
public void einsteigen() {
if (passagiere < maxPassagiere) {
passagiere++;
}
}

/**
* 11. Entfernt einen Passagier des aktuellen Flugzeugs
*/
public void aussteigen() {
if (passagiere > 0) {
passagiere--;
}
}

/**
* 12. Ausgabe der aktuellen Anzahl der Passagiere
* @return aktuelle Anzahl der Passagiere
*/
public int anzahlPassagiere() {return passagiere;}


/**
* 6. Berechnen des aktuellen Gewichts
* @return aktuelles Gewicht
*/
public int gewicht() {
return (leergewicht+ passagiere*PASSAGIERGEWICHT);}

/**
* 13. Ausgabe der maximalen Anzahl der Passagiere
* @return Maximale Anzahl der Passagiere
*/
public int passagierkapazitaet() {return maxPassagiere;}

/**
* Eine Methode zum Drucken der Attributbelegung des Objekts
* Die Methode erfordert keine Eingaben. Sie erzeugt keine
* Ausgaben
*/
public void drucken() {
// 7. Vervollständigen der Druckmethode
System.out.println("*****************************");
System.out.println("Kennzeichen: " + kennzeichen);
System.out.println("Leergewicht: " + leergewicht + "kg");
System.out.println("Maximalgewicht: " + maxgewicht + "kg");
System.out.println("Aktuelles Gewicht : " + gewicht() + "kg");
System.out.println("Passagiere: " + passagiere);
System.out.println("Maximal Anzahl P.: " + maxPassagiere);
System.out.println("*****************************");
}

}

5.4.4 Lösung: Statische Klassenattribute

Klasse Flugzeug

Erweitert um die statische Variable

package block5;
public class Flugzeug {

public String kennzeichen; // Ein Attribut vom Typ einer Zeichenkette
// 1. Privates Attribut zur Verwaltung der Passagierkapazität
// Tipp: Untersuchen Sie die Druckmethode zur Wahl der
// Variablennamen (1-5)!
private int maxPassagiere;
// 2. Privates Attribut zur Verwaltung der aktuellen Pasagiere
private int passagiere;
// 3. Leergewicht in privates Attribut ändern
public int leergewicht; // Ein Attribut vom Type einer Ganzzahl
// 4. Maximales Gewicht des Flugzeugs
private int maxgewicht;
// 5. Öffentliche Konstante für durchschn. Passagiergewicht
public final int PASSAGIERGEWICHT = 85;

// Anzahl aller erzeugten Flugzeuge
private static int objekte;


/**
* 8. Konstruktor implementieren
* Konstruktur der Klasse Flugzeug
* @param kennz Kennzeichen des Flugzeugs
* @param kapazitaet Passagierkapazität
* @param leergew Leergewicht in kg
* @param maxgew Maximalgewicht in kg
*/
public Flugzeug(String kennz, int kapazitaet, int leergew, int maxgew) {
kennzeichen = kennz;
objekte++;
// Prüfen ob Kapazität größere Null ist
if (kapazitaet >= 0) {
maxPassagiere = kapazitaet;
} else {
maxPassagiere = 0;
}
// Prüfen ob Leergewicht größer Null ist
if (leergew > 0) {
leergewicht = leergew;
} else {
leergewicht = 0;
}
// Prüfen ob Maximalgewicht größer-gleich Leergeicht ist.
if (maxgew > leergewicht) {
maxgewicht = maxgew;
} else {
maxgewicht = leergewicht; // Viel Spass...
}
}

/**
* 10. Fügt einen Passagier zum aktuellen Flugzeug hinzu
*/
public void einsteigen() {
if (passagiere < maxPassagiere) {
passagiere++;
}
}

/**
* 11. Entfernt einen Passagier des aktuellen Flugzeugs
*/
public void aussteigen() {
if (passagiere > 0) {
passagiere--;
}
}

/**
* 12. Ausgabe der aktuellen Anzahl der Passagiere
* @return aktuelle Anzahl der Passagiere
*/
public int anzahlPassagiere() {return passagiere;}


/**
* 6. Berechnen des aktuellen Gewichts
* @return aktuelles Gewicht
*/
public int gewicht() {
return (leergewicht+ passagiere*PASSAGIERGEWICHT);}

/**
* 13. Ausgabe der maximalen Anzahl der Passagiere
* @return Maximale Anzahl der Passagiere
*/
public int passagierkapazitaet() {return maxPassagiere;}

/**
*
* @return Anzahl aller erzeugten Objekte der Klasse Flugzeug
*/
public int anzahlFlugzeuge() {return objekte;}

/**
* Eine Methode zum Drucken der Attributbelegung des Objekts
* Die Methode erfordert keine Eingaben. Sie erzeugt keine
* Ausgaben
*/
public void drucken() {
// 7. Vervollständigen der Druckmethode
System.out.println("*****************************");
System.out.println("Kennzeichen: " + kennzeichen);
System.out.println("Leergewicht: " + leergewicht + "kg");
System.out.println("Maximalgewicht: " + maxgewicht + "kg");
System.out.println("Aktuelles Gewicht : " + gewicht() + "kg");
System.out.println("Passagiere: " + passagiere);
System.out.println("Maximal Anzahl P.: " + maxPassagiere);
System.out.println("******************** " + objekte + " Flugz.");
}

}

5.4.5 Überladene Methoden

Klasse Flugzeug

package block5;
public class Flugzeug { public String kennzeichen; // Ein Attribut vom Typ einer Zeichenkette

// 1. Privates Attribut zur Verwaltung der Passagierkapazität
// Tipp: Untersuchen Sie die Druckmethode zur Wahl der
// Variablennamen (1-5)!
private int maxPassagiere;
// 2. Privates Attribut zur Verwaltung der aktuellen Pasagiere
private int passagiere;
// 3. Leergewicht in privates Attribut ändern
public int leergewicht; // Ein Attribut vom Type einer Ganzzahl
// 4. Maximales Gewicht des Flugzeugs
private int maxgewicht;
// 5. Öffentliche Konstante für durchschn. Passagiergewicht
public final int PASSAGIERGEWICHT = 85;

// Anzahl aller erzeugten Flugzeuge
private static int objekte;

/**
* 8. Konstruktor implementieren
* Konstruktur der Klasse Flugzeug
* @param kennz Kennzeichen des Flugzeugs
* @param kapazitaet Passagierkapazität
* @param leergew Leergewicht in kg
* @param maxgew Maximalgewicht in kg
*/
public Flugzeug(String kennz, int kapazitaet, int leergew, int maxgew) {
kennzeichen = kennz;
objekte++;
// Prüfen ob Kapazität größere Null ist
if (kapazitaet >= 0) {
maxPassagiere = kapazitaet;
} else {
maxPassagiere = 0;
}
// Prüfen ob Leergewicht größer Null ist
if (leergew > 0) {
leergewicht = leergew;
} else {
leergewicht = 0;
}
// Prüfen ob Maximalgewicht größer-gleich Leergeicht ist.
if (maxgew > leergewicht) {
maxgewicht = maxgew;
} else {
maxgewicht = leergewicht; // Viel Spass...
}
}

/**
* 10. Fügt einen Passagier zum aktuellen Flugzeug hinzu
*/
public void einsteigen() {
if (passagiere < maxPassagiere) {
passagiere++;
}
}

/**
*
* @param anzahl Anzahl der Passagiere die einsteigen sollen
*/
public void einsteigen(int anzahl) {
if ((anzahl >0) && (passagiere+anzahl) <= maxPassagiere) {
passagiere+= anzahl;
}
}


/**
* 11. Entfernt einen Passagier des aktuellen Flugzeugs
*/
public void aussteigen() {
if (passagiere > 0) {
passagiere--;
}
}

/**
*
* @param anzahl Anzahl der Passagiere die aussteigen sollen
*/
public void aussteigen(int anzahl) {
if ((anzahl >0) && (passagiere-anzahl) >=0) {
passagiere-= anzahl;
}
}


/**
* 12. Ausgabe der aktuellen Anzahl der Passagiere
* @return aktuelle Anzahl der Passagiere
*/
public int anzahlPassagiere() {return passagiere;}


/**
* 6. Berechnen des aktuellen Gewichts
* @return aktuelles Gewicht
*/
public int gewicht() {
return (leergewicht+ passagiere*PASSAGIERGEWICHT);}

/**
* 13. Ausgabe der maximalen Anzahl der Passagiere
* @return Maximale Anzahl der Passagiere
*/
public int passagierkapazitaet() {return maxPassagiere;}

/**
*
* @return Anzahl aller erzeugten Objekte der Klasse Flugzeug
*/
public static int anzahlFlugzeuge() {return objekte;}

/**
* Eine Methode zum Drucken der Attributbelegung des Objekts
* Die Methode erfordert keine Eingaben. Sie erzeugt keine
* Ausgaben
*/
public void drucken() {
// 7. Vervollständigen der Druckmethode
System.out.println("*****************************");
System.out.println("Kennzeichen: " + kennzeichen);
System.out.println("Leergewicht: " + leergewicht + "kg");
System.out.println("Maximalgewicht: " + maxgewicht + "kg");
System.out.println("Aktuelles Gewicht : " + gewicht() + "kg");
System.out.println("Passagiere: " + passagiere);
System.out.println("Maximal Anzahl P.: " + maxPassagiere);
System.out.println("******************** " + objekte + " Flugz.");
}

}

Klasse FlugzeugTest

package block5;

public class FlugzeugTest {

public static Flugzeug jumbo;
public static Flugzeug a380;

/**
* Die Methode main() wir zum Starten des Programms benutzt
* @param args Übergabe von Konsolenparameter. Hier nicht genutzt
*/
public static void main(String[] args) {
phase1(); // 9. Phase 1 testen
phase2(); // 14. Phase 2 testen
phase3(); // 15. Phase 3 testen
phase4();
phase3a();
phase4a();

}

public static void phase1() {
System.out.println(" Phase 1: 2 Flugzeuge");
// Erzeugen zweier Objekte
jumbo = new Flugzeug("D-ABYT", 360, 191000, 400000);
a380 = new Flugzeug("D-AIMD", 560, 286000, 500000);

// Drucken der beiden Objekte auf der Konsole
jumbo.drucken();
a380.drucken();
}

public static void phase2() {

// 7. Testen des vorangehenden Hauptprogramms
System.out.println(" Phase 2: Einsteigen mit Überbuchung");

System.out.println("Ein Passagier in Jumbo einsteigen");
jumbo.einsteigen();
jumbo.drucken();
System.out.println("300 Passagiere in Jumbo einsteigen");
for (int i = 0; i < 300; i++) {
jumbo.einsteigen();
}
jumbo.drucken();
System.out.println("200 Passagiere aus Jumbo aussteigen");
for (int i = 0; i < 200; i++) {
jumbo.aussteigen();
}
jumbo.drucken();
System.out.println("200 Passagiere aus Jumbo aussteigen");
for (int i = 0; i < 200; i++) {
jumbo.aussteigen();
}
jumbo.drucken();

}

public static void phase3() {
System.out.println(" Phase 3: Jumbo Flugzeugdefekt");
Flugzeug jumboAlt = new Flugzeug("D-ABYU", 360, 191000, 400000);
Flugzeug a380Neu = new Flugzeug("D-AIME", 560, 286000, 500000);
jumboAlt.drucken();
a380Neu.drucken();

System.out.println("300 Passagiere in JumboAlt einsteigen");
for (int i = 0; i < 300; i++) {
jumboAlt.einsteigen();
}
System.out.println("100 Passagiere in Airbus 380 Neu einsteigen");
for (int i = 0; i < 100; i++) {
a380Neu.einsteigen();
}
jumboAlt.drucken();
a380Neu.drucken();
System.out.println("Jumbo ist defekt. Alle in Airbus umsteigen");
while (jumboAlt.anzahlPassagiere() > 0) {
jumboAlt.aussteigen();
a380Neu.einsteigen();
}
System.out.println("Alle umgestiegen. Bereit zum Start");
jumboAlt.drucken();
a380Neu.drucken();
}

public static void phase4() {
System.out.println(" Phase 4: A380 Flugzeugdefekt mit 560 Passagieren");
Flugzeug jumbo1 = new Flugzeug("D-ABYV", 360, 191000, 400000);
Flugzeug jumbo2 = new Flugzeug("D-ABYW", 360, 191000, 400000);
Flugzeug a380Defekt = new Flugzeug("D-AIME", 560, 286000, 500000);
jumbo1.drucken();
jumbo2.drucken();
a380Defekt.drucken();
System.out.println("50 Passagiere in Jumbo 1 und 2 einsteigen");
// 17. Lassen Sie 200 Passagiere in jeden Jumbo einsteigen
// Hiermit ist nicht mehr Platz für alle Airbuspassagiere
// Testen Sie den Fall der Überbuchung
for (int i = 0; i < 50; i++) {
jumbo1.einsteigen();
jumbo2.einsteigen();
}
System.out.println("560 Passagiere in Airbus 380 (defekt) einsteigen");
for (int i = 0; i < 560; i++) {
a380Defekt.einsteigen();
}
jumbo1.drucken();
jumbo2.drucken();
a380Defekt.drucken();


System.out.println("Airbus ist defekt. Alle in Jumbos umsteigen");
// 16. Implementieren Sie das Evakuieren des Airbus
// Beide Jumbos sollen falls notwendig benutzt werden
// Drucken Sie eine Warnung aus falls Sie einen Passagier
// nicht umsteigen lassen.
while (a380Defekt.anzahlPassagiere() > 0) {
if (jumbo1.anzahlPassagiere() < jumbo1.passagierkapazitaet()) {
a380Defekt.aussteigen();
jumbo1.einsteigen();
} else // Jumbo 1 is voll...
if (jumbo2.anzahlPassagiere() < jumbo2.passagierkapazitaet()) {
a380Defekt.aussteigen();
jumbo2.einsteigen();
} else // Beide Jumbos sind voll
{
a380Defekt.aussteigen();
System.out.println("Ein Passagier bleibt zurück...");
}
}
System.out.println("Airbus evakuiert. Bereit zum Start");
jumbo1.drucken();
jumbo2.drucken();
a380Defekt.drucken();
}

public static void phase3a() {
System.out.println(" Phase 3a: Jumbo Flugzeugdefekt");
Flugzeug jumboAlt = new Flugzeug("D-ABYA", 360, 191000, 400000);
Flugzeug a380Neu = new Flugzeug("D-AIMF", 560, 286000, 500000);
jumboAlt.drucken();
a380Neu.drucken();

System.out.println("300 Passagiere in JumboAlt einsteigen");
jumboAlt.einsteigen(300);
System.out.println("100 Passagiere in Airbus 380 Neu einsteigen");
a380Neu.einsteigen(100);
jumboAlt.drucken();
a380Neu.drucken();
System.out.println("Jumbo ist defekt. Alle in Airbus umsteigen");
int warteRaum = jumbo.anzahlPassagiere();
jumboAlt.aussteigen(jumbo.anzahlPassagiere());
// Zu diesem Zeitpunkt hat man ohne warteRaum die Passagiere des
// Jumbos vergessen!
a380Neu.einsteigen(warteRaum);
System.out.println("Alle umgestiegen. Bereit zum Start");
jumboAlt.drucken();
a380Neu.drucken();
}

public static void phase4a() {

System.out.println(" Phase 4a: A380 Flugzeugdefekt mit 560 Passagieren");
Flugzeug jumbo1 = new Flugzeug("D-ABYX", 360, 191000, 400000);
Flugzeug jumbo2 = new Flugzeug("D-ABYY", 360, 191000, 400000);
Flugzeug a380Defekt = new Flugzeug("D-AIMG", 560, 286000, 500000);
jumbo1.drucken();
jumbo2.drucken();
a380Defekt.drucken();
System.out.println("50 Passagiere in Jumbo 1 und 2 einsteigen");
jumbo1.einsteigen(50);
jumbo2.einsteigen(50);
System.out.println("560 Passagiere in Airbus 380 (defekt) einsteigen");
a380Defekt.einsteigen(560);
jumbo1.drucken();
jumbo2.drucken();
a380Defekt.drucken();

System.out.println("Airbus ist defekt. Alle in Jumbos umsteigen");
// 16. Implementieren Sie das Evakuieren des Airbus
// Beide Jumbos sollen falls notwendig benutzt werden
// Drucken Sie eine Warnung aus falls Sie einen Passagier
// nicht umsteigen lassen.

// Alle aus Airbus in den Warteraum
int warteRaum = a380.anzahlPassagiere();
int freiJumbo1 = jumbo1.passagierkapazitaet() - jumbo1.anzahlPassagiere();
a380Defekt.aussteigen(a380.anzahlPassagiere());

if (warteRaum <= freiJumbo1) { // Alle Passagiere passen in jumbo1
jumbo1.einsteigen(warteRaum);
} else { // Nur ein Teil der Passagiere kann mitgenommen werden
jumbo1.einsteigen(freiJumbo1); // Jumbo ist voll
warteRaum -= freiJumbo1; // warteRaum reduziert
// Nutzen des zweiten Jumbo...
int freiJumbo2 = jumbo2.passagierkapazitaet() - jumbo2.anzahlPassagiere();
if (warteRaum <= freiJumbo2) { // Alle Passagiere passen in jumbo2
jumbo2.einsteigen(warteRaum);
} else { // Nur ein Teil der Passagiere kann mitgenommen werden
jumbo2.einsteigen(freiJumbo2); // jumbo2 ist jetzt voll
warteRaum -= freiJumbo2; // warteRaum reduziert
System.out.println(warteRaum + " Passagiere können nicht umbebucht werden.");
}
}
System.out.println("Airbus evakuiert. Bereit zum Start");
jumbo1.drucken();
jumbo2.drucken();
a380Defekt.drucken();
}

}

5.4.6 Fuhrparkmanagement

Klasse Kraftwagen

public class Kraftwagen {

    private double verkaufsPreis;
    private double einkaufsPreis;
    private String kennzeichen;

    public Kraftwagen(double ek, double vk, String nummernSchild) {

        // Initialisierung der Variablen mit einfachen Konsistenzprüfungen
        verkaufsPreis = (ek < vk) ? vk : ek;
        einkaufsPreis = (ek < vk) ? ek : vk;
        kennzeichen = nummernSchild;
    }

    public double get_Epreis() {
        return einkaufsPreis;
    }

    public void set_Epreis(double pr) {
        einkaufsPreis = pr;
    }

    public double get_Vpreis() {
        return verkaufsPreis;
    }

    public void set_Vpreis(double pr) {

        verkaufsPreis = pr;
    }

    public String get_kennzeichen() {
        return kennzeichen;
    }

    public void set_kennzeichen(String s) {
        kennzeichen = s;
    }


    public void drucken() {
        System.out.println("Einkaufspreis: " + einkaufsPreis);
        System.out.println("Verkaufspreis: " + verkaufsPreis);
        System.out.println("Kennzeichen: " + kennzeichen);
    }

    public static void Main (String args) {
        
    }
   
}

Klasse Verkaeufer

public class Verkaeufer {

    private String name;
    private double gewinn;
    private double gebundeneMittel;
    private Kraftwagen[] meineWagen;

    public Verkaeufer(String nam) {
        meineWagen = new Kraftwagen[2];
        name = nam;
        gewinn = 0.0;
    }

    public void neuerWagen(int pos, Kraftwagen w) {
        if ((pos >= 0) && (pos < 2)) {
            meineWagen[pos] = w;
        }

    }

    public Kraftwagen imBestand(int pos) {
        if ((pos >= 0) && (pos < 2)) {
            return meineWagen[pos];
        } else {
            return null;
        }
    }

    public Kraftwagen verkaufeFahrzeug(int pos, int erloes) {
        Kraftwagen result;
        if (erloes < meineWagen[pos].get_Vpreis()) {
            System.out.println("Wagen mit Kennzeichen " +
                    meineWagen[pos].get_kennzeichen() +
                    " sollte unter Einkaufspreis verkauft werden.");
            result = null;
        } else {
            gewinn += erloes-meineWagen[pos].get_Epreis();
            result = meineWagen[pos];
            meineWagen[pos] = null; // Wagen gehört nicht mehr zum Bestand
        }
        return result;
    }

    public int fahrzeugeInFlotte() {
        int result = 0;
        int i = 0;
        while (i < meineWagen.length) {
            if (meineWagen[i] != null) {
                result++;
            }
            i++;
        }
        return result;
    }

    public double gebundeneMittel() {
        double result = 0.0;
        for (int i = 0; i < meineWagen.length; i++) {
            if (meineWagen[i] != null) {
                result += meineWagen[i].get_Epreis();
            }
        }
        return result;
    }

    public double geplanterUmsatz() {
        double result = 0.0;
        for (int i = 0; i < meineWagen.length; i++) {
            if (meineWagen[i] != null) {
                result += meineWagen[i].get_Vpreis();
            }
        }
        return result;
    }

    public void drucken() {
        Kraftwagen w1 = imBestand(0);
        Kraftwagen w2 = imBestand(1);

        System.out.println("<*** Verkäufername : " + name + " ***>");
        System.out.println("Erlös bisher: " + gewinn);
        System.out.println("Gebundene Mittel: " + gebundeneMittel());
        System.out.println("Geplanter Umsatz: " + geplanterUmsatz());
        System.out.println("Fahrzeuge in Flotte: " + fahrzeugeInFlotte());
        if (w1 != null) w1.drucken();
        if (w2 != null) w2.drucken();
        System.out.println("<*********************************>");
    }

    public static void main(String[] args) {
        Kraftwagen w1 = new Kraftwagen(20000.00, 30000.00, "M-S 1234");
        Kraftwagen w2 = new Kraftwagen(10000.00, 15000.00, "B-A 5678");
        Kraftwagen w3 = new Kraftwagen(5000.00, 10000.00, "D-T 8901");

        Verkaeufer v1 = new Verkaeufer("Hans");
        v1.neuerWagen(0, w1);
        v1.neuerWagen(1, w2);
        v1.drucken();

        Kraftwagen verkWagen = v1.verkaufeFahrzeug(0, 50000);
        System.out.println("Verkaufter Wagen: " + verkWagen.get_kennzeichen());
        v1.drucken();
    }
}

Klasse Haendler

public class Haendler {
    private Verkaeufer[] team;
    private String name;

    public Haendler(String n ) {
    name = n;
    team = new Verkaeufer[3];
    }

    public void einstellenTeam(String name1, String name2, String name3) {
        team[0] = new Verkaeufer(name1);
        team[1] = new Verkaeufer(name1);
        team[2] = new Verkaeufer(name1);
    }


    public void geschäftsEröffnung(Kraftwagen[] k) {
        team[0].neuerWagen(0, k[0]);
        team[1].neuerWagen(0, k[1]);
        team[2].neuerWagen(0, k[2]);
        team[0].neuerWagen(1, k[3]);
        team[1].neuerWagen(1, k[4]);
        team[2].neuerWagen(1, k[5]);
    }

    public double gesamtGewinn() {
        double g =0;
        for (int i=0; i

5.4.7 Mehrere Konstruktoren

Klasse Flugzeug

package block6;
/**
* Eine Flugzeug mit Gewicht und Passagieren
* Am besten mit {@link block6.FlugzeugTest#main FlugzeugTest} testen
* @author sschneid
*/
public class Flugzeug {
public String kennzeichen; // Ein Attribut vom Typ einer Zeichenkette
private int maxPassagiere;
private int passagiere;
public int leergewicht; // Ein Attribut vom Type einer Ganzzahl
private int maxgewicht;
public static final int PASSAGIERGEWICHT = 85;
// Anzahl aller erzeugten Flugzeuge
private static int objekte;

/**
*
* Konstruktur der Klasse Flugzeug
* @param kennz Kennzeichen des Flugzeugs
* @param kapazitaet Passagierkapazität
* @param leergew Leergewicht in kg
* @param maxgew Maximalgewicht in kg
*/
public Flugzeug(String kennz, int kapazitaet, int leergew, int maxgew) {
kennzeichen = kennz;
objekte++;
// Prüfen ob Kapazität größere Null ist
if (kapazitaet >= 0) {
maxPassagiere = kapazitaet;
} else {
maxPassagiere = 0;
}
// Prüfen ob Leergewicht größer Null ist
if (leergew > 0) {
leergewicht = leergew;
} else {
leergewicht = 0;
}
// Prüfen ob Maximalgewicht größer-gleich Leergeicht ist.
if (maxgew > leergewicht) {
maxgewicht = maxgew;
} else {
maxgewicht = leergewicht; // Viel Spass...
}
}

/**
* Konstruktur der Klasse Flugzeug
* Berechnet Passagierkapazität automatisch
* @param kennz Kennzeichen des Flugzeugs
* @param leergew Leergewicht in kg
* @param maxgew Maximalgewicht in kg
*/
public Flugzeug(String kennz, int leergew, int maxgew) {
this(kennz,(maxgew-leergew)/PASSAGIERGEWICHT,leergew,maxgew);
}


/**
* Fügt einen Passagier zum aktuellen Flugzeug hinzu
*/
public void einsteigen() {
if (passagiere < maxPassagiere) {
passagiere++;
}
}

/**
* @param anzahl Anzahl der Passagiere die einsteigen sollen
*/
public void einsteigen(int anzahl) {
if ((anzahl >0) && (passagiere+anzahl) <= maxPassagiere) {
passagiere+= anzahl;
}
}

/**
* Entfernt einen Passagier des aktuellen Flugzeugs
*/
public void aussteigen() {
if (passagiere > 0) {
passagiere--;
}
}

/**
* Entfernt Passagiere des aktuellen Flugzeugs
* @param anzahl Anzahl der Passagiere die aussteigen sollen
*/
public void aussteigen(int anzahl) {
if ((anzahl >0) && (passagiere-anzahl) >=0) {
passagiere-= anzahl;
}
}

/**
* Ausgabe der aktuellen Anzahl der Passagiere
* @return aktuelle Anzahl der Passagiere
*/
public int anzahlPassagiere() {return passagiere;}


/**
* Berechnen des aktuellen Gewichts
* @return aktuelles Gewicht
*/
public int gewicht() {
return (leergewicht+ passagiere*PASSAGIERGEWICHT);}

/**
* Ausgabe der maximalen Anzahl der Passagiere
* @return Maximale Anzahl der Passagiere
*/
public int passagierkapazitaet() {return maxPassagiere;}

/**
*
* @return Anzahl aller erzeugten Objekte der Klasse Flugzeug
*/
public static int anzahlFlugzeuge() {return objekte;}

/**
* Eine Methode zum Drucken der Attributbelegung des Objekts
* Die Methode erfordert keine Eingaben. Sie erzeugt keine
* Ausgaben
*/
public void drucken() {
// 7. Vervollständigen der Druckmethode
System.out.println("*****************************");
System.out.println("Kennzeichen: " + kennzeichen);
System.out.println("Leergewicht: " + leergewicht + "kg");
System.out.println("Maximalgewicht: " + maxgewicht + "kg");
System.out.println("Aktuelles Gewicht : " + gewicht() + "kg");
System.out.println("Passagiere: " + passagiere);
System.out.println("Maximal Anzahl P.: " + maxPassagiere);
System.out.println("******************** " + objekte + " Flugz.");
}
}

 

5.5 Lernziele

Am Ende dieses Blocks können Sie:

  • ....die wichtigsten Aspekte der Objektorientierung anwenden und erklären.
  • ...an einfachen Beispielen das Konzept der Datenkapselung in Java anwenden indem Sie eine Klasse mit öffentlichen Attributen mit Hilfe von Methoden kapseln.
  • ...den Unterschied einer Klasse zu einem Objekt in Java eklären
  • ...Objektvariablen benutzen und erkennen ob Objektvariablen auf das gleiche oder ein anderes Objekt zeigen
  • ...Klassvariablen und Klassenmethoden anhand des Schlüsselwort static erkennen.
  • ...den Unterschied von Klassenvariablen und Klassenmethoden zu normalen Methoden (Objektmethoden) und Variablen(Objektvariablen) erklären
  • .... den Begriff der Datenkapselung erläutern und anwenden
  • ... Beispiele für die Trennung von Zuständigkeiten nennen
  • ... den Begriff der Schichtenarchitektur erklären
  • ... die this Referenz nutzen und erklären
  • ... Konstruktoren mit Parametern benutzen und überladene Konstruktoren erkennen und die Aufrufreihenfolge der Konstruktoren interpretieren
  • ... das Entwurfsmuster eines Singleton (Einzelstücks) in Java mit Hilfe von Konstruktoren implementieren
  • ... den Unterschied zwischen Instanziieren und Initialisieren erklären

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten:

6. Referenzen, strukturierte Datentypen

Objektvariablen enthalten Referenzen auf Objekte. Sie unterscheiden sich von Variablen primitiver Datentypen in einer Reihe von Eigenschaften:

  Variablen primitiver Datentypen Objektvariablen
Initialisierung Standardwert des Typs; direkt benutzbar Null Zeiger, Objekt muss separat erzeugt werden
Exklusivität Variable gehört exklusiv zum Objekt referenziertes Objekt gehört nicht exklusiv zum umgebenden Objekt
Lebensdauer die des umgebenden Objekts referenziertes Objekt existiert so lange es von irgendeiner Referenz referenziert wird
Ausnahmebehandlung nicht relevant Der Versuch eine Methode oder Objektvariable mit einer Referenz auf Nulll zu benutzen führt zu einer Ausnahme
Parameterübergabe bei Methoden Kopie wird angelegt Objekt wird nicht kopiert. Nur Referenz wird kopiert

 

Neue Objekte werden im Adressraum nur mit dem new() Operator angelegt. Referenzen speichern nur die eindeutige Identität unter der das Objekt zu erreichen ist.

Die in der Referenz verwaltete eindeutige Objektidentität bleibt für die gesamte Lebensdauer des Objekts konstant. Sie ist eine logische Kennung. Das unten aufgeführte Programmbeispiel mit Diagramm ist jedoch nur als Gedankenmodell zu verstehen. Die konkrete Implementierung in einer virtuellen Java Maschine kann durchaus anders aussehen. Sie ist für den Javaentwickler transparent.

Kraftwagen wagen1 = new Kraftwagen ("0-1");
Kraftwagen wagen2 = wagen1;
Kraftwagen wagen3 = new Kraftwagen("0-2");

Das Referenzkonzept von Java ist die einzige Möglichkeit auf Objekte zuzugreifen.

Das Referenzenkonzept von Java ist syntaktisch sehr ähnlich zu dem Pointerkonzept von C und C++. Es gibt jedoch wichtige Unterschiede mit signifikanten Auswirkungen auf die Stabilität der Anwendungen:

  Java C/C++
Begriff Referenz (Reference) Zeiger (Pointer)
Implementierung abstrakter Datentyp (Klasse) Speicherbereich
direkter Speicherzugriff nein ja
Typcheck zum Übersetzungszeitpunkt ja ja (Normalerweise)
Zugriffskontrolle auf eine nicht initialisierte Referenz Check mit eventuellem Wurf einer behandelbaren Ausnahme kein Check auf Existenz oder Gültigkeit des Objekts

 

Bemerkung: Der neue C++11 Standard bietet viele Verbesserungen dieser historisch bedingten Defizite von C/C++. Da C++ oft für Anwendungen deren Leistung sehr wichtig ist (Systemprogrammierung) benutzt wird, sind alle zusätzlichen Checks von C++11 immer nur optional.

6.1 Die "null" und "this" Referenz

6.1.1 Die "null" Referenz

Java verfügt über eine "null" Konstante mit deren Wert man Referenzen belegen kann um:

  • ein Objekt zu dereferenzieren weil die Anwendungslogik dies erfordert. Das dereferenzierte Objekt wird (irgendwann) gelöscht nachdem niemand mehr auf es zeigt
  • Referenzen zu initialisieren.

Im folgenden Beispiel ist zu sehen was geschieht wenn wagen1 und wagen2 mit der null Referenz belegt werden:

package block7.reference;

public class Main {

public static void main(String[] args) {
    Kraftwagen wagen1 = new Kraftwagen("0-1");
    Kraftwagen wagen2 = wagen1;
    Kraftwagen wagen3 = new Kraftwagen("0-2");
    wagen2 = null;
    wagen3 = null;
   }
}

Der Wagen"0-2" wird ein Kandidat zum Löschen. Der Wagen "0-1" wird noch über die Referenz wagen1 referenziert.

Enthält eine Referenz eine Null Variable so zeigt sie nicht mehr auf ein Objekt und man kann nicht mehr die Methoden und Datenfelder einer Instanz aufrufen. Mit static deklarierte Methoden und Attribute sind hiervon ausgenommen.

Das Javalaufzeitsystem wird in diesem Fall eine NullPointerException werfen und das Programm beenden. Das folgende Beispiel zeigt den Fall einer unbehandelten Ausnahme sowie die Behandlung einer Ausnahme:

package block7.reference;

public class Main {

public static void main(String[] args) {
    Kraftwagen wagen1 = new Kraftwagen("O-1");
    Kraftwagen wagen2 = wagen1;
    Kraftwagen wagen3 = new Kraftwagen("O-2");
    wagen2 = null;
    wagen3 = null;
        
    try{
        System.out.println(wagen2.getKennzeichen()); // Erste Ausnahme
        }
    catch (NullPointerException e)
        {
            System.out.println("Variable wagen2 ist eine Null Referenz");
        }
    System.out.println(wagen3.getKennzeichen()); // Zweite Ausnahme
    //Das Programm wurde wegen der unbehandelten Ausnahme beendet
    // Die folgende Zeile nicht nicht mehr erreicht
    System.out.println("Diese Zeile des Programms wird nicht erreicht..");
    }
}

Das Programm wird bei der Ausführung die folgenden Ausgaben auf der Konsole erzeugen;

Variable wagen2 ist eine Null Referenz
Exception in thread "main" java.lang.NullPointerException
at block7.reference.Main.main(Main.java:20)

Die erste Ausnahme, der Aufruf von wagen2.getKennzeichen() wird mit mit einem try-catch Block aufgefangen. Nach der Behandlung im catch-Block wird die Methode weiter ausgeführt.

Der Aufruf von wagen3.getKennzeichen() wird nicht aufgefangen und führt zu einer Ausnahme in Zeile 20 die das Programm beendet. Ausnahmen und Ausnahmebehandlungen werden in einem späteren Abschnitt, im Kurs 2 behandelt.

6.1.2 Die Eigenreferenz mit dem Schlüsselwort this

Es gibt Fälle in denen man als Parameter einer aufzurufenden Methode einer anderen Klasse einen Zeiger auf die aktuelle Instanz, in deren Methoden man gerade arbeitet, mitgeben möchte. Hierzu dient das Schlüsselwort this. Es kann benutzt werden um auf die aktuelle Instanz zu zeigen, solange man sich nicht in einer static Methode befindet. static Methoden können auch ohne eine existierende Instanz benutzt werden. Eine Objektinstanz existiert daher nicht unbedingt.

Beispiel:

class Person {
... 
   public void sitzPlatzZuweisen (Reservierung r) {
      ...
      r.buchung(this);
      ...
   }
}
...
// Beliebige andere Klasse
Person p = new Person();
Reservierung buchung;
buchung = new Reservierung("LH454","17B"); 
// Zeitpunkt 1
p.sitzPlatzZuweisen(buchung);
// Zeitpunkt 2
...

 

6.2 Heap-Größe und Freigabe von Speicher (Garbage Collection)

Die Java Objekte werden innerhalb des Javaprozesses in einem Speicherbereich mit dem Namen "Java Heap" verwaltet. In diesem Speicherbereich werden alle Datenstrukturen mit einer nicht festen Größe verwaltet. Diese Datenstrukturen sind:

  • Objekte (Instanzen von Klassen)
  • Felder von Basistypen oder Objekten
  • Zeichenketten (Sonderfall da Zeichenketten nicht mit dem new Operator angelegt werden müssen)

Dieser Bereich

  • hat eine initiale Größe
  • kann bis zu einem gegeben Limit wachsen
  • zwingt die Java Laufzeitumgebung eine OutOfMemory Ausnahme zu werfen wenn der Speicherbereich voll gelaufen ist.

Java VM Optionen

Optionen zum Heapmanagement(siehe auch Java Optionen (Referenzdokumentation)

  • initiale Heapgröße -Xms
  • maximale Heapgröße -Xmx
  • Protokollieren von Garbagekollektorläufen: -XX:+PrintGC oder -XX:+PrintGCDetails

Beispiel: Starten einer Anwendung Main.class mit 500 Megabytes initialem Heap (Freispeicher), 800 Megabytes maximalem Heap und Protokollierung von Garbagekollektorläufen:

java -Xms500m -Xmx800m -XX:+PrintGC Main

Nicht mehr referenzierte Objekte werden von der JavaVM automatisch, im Hintergrund von einem "Garbage Collector" gelöscht.

Dieser "Garbage Collector" (GC) kann manuell getriggert werden. Dies sollte man aber nicht in einem produktiven Programm durchführen. Der explizit angestossene GC bringt die Anwendung während seiner Arbeit zum Stehen! Automatisch ausgeführte GCs bewirken dies (normalerweise) nicht. Das explizite Anstoßen geschieht mit der statischem Methode System.gc(). Man kann sie wie folgt aufrufen:

System.gc();

Die Konsolenausgabe eines Programms welches mit der Option -XX:+PrintGC sieht wie folgt aus:

[GC 38835K->38906K(63936K), 0.1601889 secs]
[GC 39175K(63936K), 0.0050223 secs]
[GC 52090K->52122K(65856K), 0.1452102 secs]
[GC 65306K->65266K(79040K), 0.1433074 secs]

Sie zeigt an wieviel Speicher die Objekte von der "Garbage Collection" vorher und nachher belegen. Die benötigte Zeit wird ebenfalls angezeigt.

Die Arbeit des "Garbage Collector" und die Füllstände der Pools kann man in den Videos des Java Performance Primer's sehen.

Verwalten von Objekten im Freispeicher (Heap)

Der Freispeicher (Heap) kann je nach Konfiguration eine konstante Größe haben oder er kann dynamisch bis zu einer vorgegebenen maximalen Größe wachsen. Der Entwickler ist daran interessiert, dass seine Anwendung alle benötigten Objekte im Freispeicher (Heap) verwalten kann. Kann das Javalaufzeitsystem keine neuen Objekte mehr anlegen wird es die Anwendung mit einer OutOfMemoryError Ausnahme beenden.

Dies wird normalerweise durch den Garbage-Collector der automatisch alle Objekte löscht die nicht mehr referenziert werden vermieden.

Nicht referenzierte Objekte

Nicht referenzierte Objekte können weder von der Anwendung noch vom Javalaufzeitsystem mit Hilfe von Referenzen erreicht werden. Dies bedeutet es gibt keine Kette von Referenzen zu einem Objekt die an den folgenden Orten beginnt:

  • von einer lokalen Referenzvariable im Systemstack
  • keine static Referenzvariable

Nicht referenzierte Objekte werden vom Javalaufzeitsystem bei Bedarf zu einem beliebigen Zeitpunkt gelöscht. Der genaue Zeitpunkt des Löschens ist nicht für den Entwickler vorhersehbar.

 

Beispiel

Im folgenden Programm werden eine Reihe von Personen erzeugt, die über eine Vater und Mutterbeziehung aufeinander referenzieren können.

package dereferenzieren;
 
public class Person {
 
    public Person vater;
    public Person mutter;
 
    public static void main (String[] args ) {
        Person p1 = new Person();
        Person p2 = new Person();
// Zeitpunkt 1
        p1.vater = p2;
        p2 = null;
// Zeitpunkt 2
        aufruf(p1);
        //Zeitpunkt 5
    }
 
    public static void aufruf(Person p) {
        Person[] persFeld = new Person[2];
// Zeitpunkt 3
        persFeld[1] = p;
        persFeld[0] = new Person();
        persFeld[0].vater = new Person();
        persFeld[0].mutter = new Person();
        // Zeitpunkt 4
    }
 
}

Das Hauptprogramm main() erzeugt zwei Instanzen der Klasse Person und ruft dann die Methode aufruf() auf die ein kleines Feld und einige weitere Instanzen erzeugt. 

Hinweis: Im folgenden Beispiel wird ein Feld verwendet. Javafelder werden im Detail im folgenden Kapitel erklärt.

Zum Zeitpunkt 1 sind in der main() Methode die folgenden Objekte erzeugt:

Zum Zeitpunkt 2 wurde in der main() Methode bereits die Vater-Referenz und der ursprüngliche Zeiger p2 mit null dereferenziert.

Zum Zeitpunkt 3 wurde die Methode aufruf() aufgerufen und das Feld persFeld mit zwei Feldern auf dem Heap angelegt.

Zum Zeitpunkt 4 sind drei weitere Personen angelegt worden. Die drei neuen Personen sind über den Index 0 von der Variablen persFeld erreichbar.
Zu diesem Zeitpunkt können alle Objekte auf dem Heap direkt, oder indirekt von Datenstrukturen auf dem Stapel (stack) erreicht werden.

Zum Zeitpunkt 5 wurde die Methode aufruf() bereits verlassen. Die lokalen Variablen der Methode aufruf() wurden vom Stack gelöscht und stehen nicht mehr zur Verfügung.

Hierdurch können eine Reihe von Instanzen der Klasse Person und das Feld auf dem Heap (Freispeicher) nicht mehr erreicht werden:

Die nicht mehr erreichbaren Objekte sind Müll (Garbarge) geworden und belegen verfügbaren Speicherplatz. Sie werden bei Bedarf vom Garbage-Collector (GC) gelöscht. Der Garbage-Collector wird alle Objekte löschen die vom Stack und statischen Variablen nicht mehr erreichbar sind. Dies hat für die Anwendung keine Implikationen da die Objekte auch von der Anwendung nicht mehr erreichbar sind. 

"Memory Leak"(engl. wikipedia)

Objekte die versehentlich bzw. ungewollt referenziert werden können nicht gelöscht werden. Diese Objekte können nach und nach den Heap füllen und zu einem Programmabbruch mangels Hauptspeicher (OutOfMemoryError Ausnahme) führen. Diesen Zustand der früher oder später zum ungewollten Abbruch eines Programms führen kann, nennt man im englischen "Memory Leak" (Speicherleck). Da man über die Zeit nutzbaren Speicher verliert wie ein Tank Wasser durch ein Leck verlieren kann.

Modifiziertes Beispiel (Memory Leak)

Ein Speicherleck kann durch eine minimale Änderung im vorhergehenden Beispiel entstehen. Gibt die Methode aufruf() als Ergebnis einen Zeiger auf das Feld von Personen zurück werden die drei Personen und das Feld nicht dereferenziert.

package dereferenzieren;
 
public class Person {
 
    public Person vater;
    public Person mutter;
 
    public static void main (String[] args ) {
        Person p1 = new Person();
        Person p2 = new Person();
        p1.vater = p2;
        p2 = null;
        Person[] f = aufruf(p1);
        //Zeitpunkt 7
    }
 
    public static Person[] aufruf(Person p) {
        Person[] persFeld = new Person[2];
        persFeld[1] = p;
        persFeld[0] = new Person();
        persFeld[0].vater = new Person();
        persFeld[0].mutter = new Person();
        // Zeitpunkt 6
return persFeld;
    }
}

Zum Zeitpunkt 6 sieht Objektmodell noch aus wie im vorhergehenden Beispiel:

 

Durch die Rückgabe der Referenz auf das Feld beim Beenden der Methode aufruf(), ist das Feld und die drei Objekte noch vom Stack erreichbar:

 

Die Variable f in main() referenziert das Feld. Das Feld wiederum referenziert 3 weitere Objekte.

Hier liegt ein Speicherleck nur vor, wenn der Entwickler nicht davon ausgeht, dass das Feld und alle referenzierten Objekte noch erreichbar sind. Ein Speicherleck ist kein Problem des Laufzeitsystems, da das Laufzeitsystem nicht zwischen noch benötigten und nicht mehr benötigten Objekten unterscheiden kann.

Dieses Problem wird vom Javaentwickler durch das Dereferenzieren von Objekten vermieden.

Dereferenzieren von Objekten

Der Javaentwickler muss nicht (und kann nicht) wie in anderen Programmiersprachen nicht mehr benötigte Objekte selbst löschen. Es verbleibt jedoch die Aufgabe sicherzustellen, dass nicht mehr benötigte Objekte nicht mehr referenziert werden um ein vollaufen des Heap zu vermeiden.

Implizites Deferenzieren

Zeigt eine lokale Variable auf ein Objekt, so verschwindet die Referenz auf das Objekt mit dem Verlassen des Blocks in dem die lokale Referenzvariable definiert war.

Im der unten aufgeführten Methode warePrüfen() gibt es zwei Referenzvariablen w und w1 die auf eine Instanz einer Ware zeigen

public void warePrüfen(Ware w) {
   Ware w1 = w;
   ....
}

Beim Aufruf dieser Methode erhöht sich die Anzahl der Referenzen auf eine bestimmte Instanz der Klasse Ware um zwei Referenzen. Da die beiden Variablen aber am Ende des Blocks beim Verlassen der Methode wieder gelöscht werden erniedrigt sich die Anzahl der Referenzen auf ein gegebenes Objekt um zwei.

"Memory Leaks" entstehen daher typischerweise nicht durch lokale Variablen. Werden Methoden jedoch sehr spät verlassen (main() Methode!) werden auch die entsprechenden lokalen Referenzevariablen erst sehr spät gelöscht.

Beispiel

Die erste Variante des Beispiels in dem das Feld auf Personen nur innerhalb des Methodenblocks verwendet wurde ist ein Fall von impliziten Dereferenzieren

Explizites Dereferenzieren

Entwickler sollten Referenzvariablen auf Objekte explizit mit dem null belegen wenn Sie wissen, dass ein Objekt sicher nicht mehr benötigt wird.

o.ref = a-reference;
...
o.ref = null;

Das Derefenzieren einer Referenzvariable und den null Wert ist insbesondere wichtig wenn die Variable ein Attribut einer Klasse ist. Die Lebensdauer des Objekts o ist nicht unbedingt ersichtlich für den Entwickler. Das Setzen der Referenzvariablen o.ref auf null gewährleistet, dass das Objekt auf das mit der Variable a-reference gezeigt wird bei Bedarf gelöscht werden kann.

Beispiel

Das Speicherleck im zweiten Beispiel kann durch zwei verschiedene Änderungen vermieden werden:

1. Möglichkeit: Dereferenzieren  der Variable persFeld in der Methode aufruf()

public static Person[] aufruf(Person p) {
        Person[] persFeld = new Person[2];
        persFeld[1] = p;
        persFeld[0] = new Person();
        persFeld[0].vater = new Person();
        persFeld[0].mutter = new Person();
        // Zeitpunkt 1
        persFeld = null;
    return persFeld;
    }

2. Möglichkeit: Dereferenzieren der Variable f in der main() Methode:

public static void main (String[] args ) {
        Person p1 = new Person();
        Person p2 = new Person();
        p1.vater = p2;
        p2 = null;
        Person[] f = aufruf(p1);
        //Zeitpunkt 2
        f = null;
    }

 

 

6.3 Kopieren von Objekten

 Objekte kopieren ist nicht trivial. Die folgende Java-Anweisung

Person p1;
Person p2;
p1 = new Person("John", "Doe");
p2=p1;

dupliziert nicht das erzeugte Objekt, es dupliziert nur die Referenz auf das gleiche Objekt. Dies bedeutet, dass man das Objekt über p1 sowie über p2 erreichen und modifizieren kann.

Das duplizieren von Objekten muss vom Entwickler explizit implementiert werden. Dies geschieht typischer in einem eigen Konstruktor dem "Copy Construktor". Der typische "Copy Constructor" wird wie folgt implementiert:

  • Der Eingabeparameter für den Konstruktor ist das zu duplizierende Objekt
  • Alle Attribute des zu kopierenden Objekts werden dupliziert

Das Vorgehen beim Kopieren der Objektvariablen obliegt dem Implementierer. Er hat hier zwei Möglichkeiten mit verschiedener Semantik:

  • Die Referenz wird umkopiert: Das geklonte Objekt zeigt auf die gleichen Objekte wie das ursprüngliche Objekt. Die referenzierten Objekte werden geteilt.
  • Das referenzierte Objekt wird ebenfalls geklont. Jetzt hat das neue Objekt einen exklusiven Satz von Objekten. Hierbei ist zu beachten, dass man bei diesem Vorgang keinen endlos rekursiven Kopiervorgang anstößt.

Das rekursive Kopieren von Objekten wird im Englischen auch als "deep copy" bezeichnet. 
Das einfache wiederbenutzen der Objekte ist im folgenden Beispiel implementiert:

public class Person {
   Person vater;
   Person mutter;
   String name;
...
   public Person (Person orig) {
      vater  = orig.vater;
      mutter = orig.mutter;
      name   = orig.name;
    }
    public Person(String n){
       name = n;
    }
}
...
Person p1 = new Person("John Doe");
p1.mutter = new Person("mum");
p1.vater = new Person("dad");
Person p2 = new Person(p1);

Das Kopieren einer Person "John Doe" ohne das Duplizieren der referenzierten Objekte führt zum folgenden Objektmodell:

Das tiefe, rekursive Klonen (Kopieren) wird wie folgt implementiert:

public class Person {
   Person vater;
   Person mutter;
   String name;
...
   public Person (Person orig) {
      if (orig.vater != null)
         vater  = new Person(orig.vater);     
      if (orig.mutter != null)
         mutter = new Person(orig.mutter);
      name   = orig.name;
   }
   public Person(String n){
      name = n;
   }
}
...
Person p1 = new Person("John Doe");
p1.mutter = new Person("mum");
p1.vater  = new Person("dad");
Person p2 = new Person(p1);

6.4 Vergleiche zwischen Objektvariablen

 In Java muss man bei Vergleichen die folgenden, unterschiedlichen Fälle unterscheiden:

  • Vergleiche von primitiven Typen
  • Vergleiche von Objekten
    • Vergleich auf Identität (gleiche Objektinstanz)
    • Vergleich auf Inhalt (enthalten zwei Instanzen die gleichen Informationen)

Vergleiche mit den == und != Operatoren

Bei primitiven Typen ist der Vergleich recht einfach

int i = 1;
int j = 2;
if (i ==j) ...

Beim Vergleich von Objekten ist hier jedoch Vorsicht geboten. Beispiel:

class Person {
   String name;
   public Person (String nameparam) { ...}
}

   ...
   Person p1 = new Person ("John Doe");
   if (p1 == "John Doe") 
   ...

 p1 ist eine Referenz auf ein Objekt. p1 wird hier direkt mit dem Zeichenliteral "John Doe" verglichen. Dieser Vergleich ist erlaubt, er führt jedoch nicht zum gewünschten Ergebnis. Die Zeichenkette "John Doe" hat einen anderen Typ und ein anderes Objekt als p1.

Vergleiche mit der equals() Methode

In Java erben alle Klassen die Methoden der Basisklasse Object. Alle Klassen erben eine Implementierung der equals() Methode von der Klasse Object.

Diese erlaubt das vergleichen von Objekten wie im folgenden Beispiel

Person p1;
Person p2;
...
if (p1.equals(p2)) ...

Die Methode equals()

  • kann auf mit null belegte Referenzen aufgerufen werden (Das Ergebnis ist immer false)
  • vergleicht zuerst auf Identität
  • vergleicht in der vererbten Implementierung der Klasse Object nur auf Identität
  • kann für jede eigene Klasse selbst mit einer komplexen Logik implementiert werden

Vorsicht: Implementiert man diese Methode selbst, dann sollte auch die Methode hashCode() selbst implementiert (überschrieben) werden. Sie wird von vielen Hilfsklassen im Zusammenhang mit equals() gleichzeitig benutzt.

Beispiel:

class Person {
   String name;
...
     public boolean equals(Object obj) {
         boolean result;
         Person target = (Person) obj;
         result = (name.equals(target.name));
     return result;
     }
}

6.5 Das Schlüsselwort "final" und Objektvariablen

 Das Schlüsselwort final erzwingt man, dass Variablen nur genau einmal belegt werden dürfen (Siehe Javaspezifikation).

Variablen dürfen dann später nicht mehr modifiziert werden.

Siehe Beispiel:

public class Person {
    ...
    final String name;
    ...
    final Adresse geburtsort;

Das Attribut name ist eine Zeichenkette. Man kann genau einmal eine Zeichenkette zuweisen. Eine spätere Änderung ist nicht mehr möglich.

Der Modifizierer final (englisch modifier) hat bei Referenztypen (hier die Klasse Adresse) eine besondere Wirkung:

  • Man kann auf genau ein Objekt referenzieren
  • Man kann nicht später auf ein anderes Objekt referenzieren
  • Man kann jedoch die Attribute des referenzierten Objekts gemäß seiner Implementierung verändern!

6.6 Zeichenketten (Strings)

 Zeichenketten werden in Java mit der Klasse String verwaltet. Sie sind Objekte.

Sie wurden bisher wie die Basistypen Character, Integer oder Float behandelt. Zeichenketten werden jedoch ähnlich wie Referenzen behandelt da Zeichenketten unterschiedlich lang sein können. Man kann Zeichenketten daher nicht die Zahlentypen mit fester Größe einfach in den Speicherbereich eines Objekts einbetten.

Zeichenketten werden bei Bedarf im Hauptspeicher (Heap) angelegt und referenziert. Die Referenz auf die Zeichenkette selbst hat eine konstante Größe.

Zeichenketten können mit einer direkten Zuweisung erzeugt werden. Sie können jedoch auch gleichwertig mit dem new() Operator und dem Konstruktor der Klasse String angelegt werden. Beide Varianten sind nur in der Syntax unterschiedlich. Der einzige Unterschied besteht darin, dass das Objekt "John" schon beim Laden der Klasse angelegt wird. Das Objekt "Doe" wird erst beim Aufruf des Befehls angelegt.

String a = "John";
String b = new String("Doe");
Drei Referenzen auf zwei Zeichenketten

 a sowie b sind Objektreferenzen nicht aber der direkte Datenbehälter!

Zeichenketten(Strings) können einfach mit dem plus "+" Operator verkettet werden. Der plus Operator konvertiert automatisch andere Basistypen in Zeichenketten bevor die neue verkettete Zeichenkette zugewiesen wird:

String a = "Das Ergebnis ist ";
double pi = 3.1415;
String b = "***";
String c = a + pi + b;

Tipp: Die Klasse String verfügt über eine ganze Reihe von hilfreichen Methoden. Hier eine Auswahl:

Zeichenketten werden von der virtuellen Maschine als unveränderbare ("immutable") Objekte gehandhabt. Sie können daher nicht überschrieben oder verändert werden. Die Implikationen können am folgenden Beispiel gezeigt werden.

Schritt 1: Anlegen der Zeichenketten

String a = "John";
String b = a;
String c = " Doe";
Drei Referenzen auf zwei Zeichenketten

Schritt 2: Verkettung und Objektallokation

Eine Verkettung der Zeichenketten mit dem plus Operator ergibt das folgende Speicherabbild

c = a + c;

Die Variable c zeigt jetzt auf ein neues Objekt. Die Zeichenkette " Doe" ist eventuell nicht mehr referenziert und wird dann vielleicht irgendwann gelöscht. Bei der oben gezeigten Verkettung entstehen typischerweise sehr viele neue Objekte und es werden sehr viele Stringobjekte zum Löschen freigesetzt! 

Drei Referenzen auf zwei Zeichenketten

Vergleich

Da ein Objekt vom Typ String eine Referenz auf eine Zeichenkette ist, muss man beim Vergleich von Zeichenketten unterscheiden, ob man die Referenz oder den Inhalt der Zeichenkette vergleichen möchte um Fehler zu vermeiden.

Der Javaübersetzer und das Javalaufzeitsystem speichern Literale wenn möglich nur einmal im Hauptspeicher. Konstante Literale die man zur Übersetzungszeit erkennen kann werden in Referenzen auf ein einziges Objekt zusammengefasst. Dynamisch erzeugte Zeichenketten werden in eigenen Objekten verwaltet. Die genaue Semantik ist in der Java Sprachspezifikation (3.te Version von 2005, Paragraph 3.10.5 String Literals) beschrieben.

Wichtig: Identische Zeichenketten haben nicht unbedingt die gleiche Objektidentität. Ein Vergleich mit Hilfe der Objektidentität ist daher im Normalfall nicht ratsam.

String a = "John";
String b = a;
String c = "John";
String d = "Jo"+"hn"; // Der konstante Ausdruck 
      // wird schon bei der Übersetzung aufgelöst!
String e = new String("John");

das Speicherabbild zum Quellcode links:

4 Referenzen auf zwei Zeichenketten

Ein Vergleich mit dem == Operator vergleicht nur die Adressen der referenzierten Objekte nicht aber deren Inhalt. Zum Vergleich der Inhalte wird die .equals() Methode benötigt. Siehe

if (a == b) System.out.println(" a und b zeigen auf das gleiche Objekt");
else System.out.println(" a und b zeigen nicht auf das gleiche Objekt");
if (a == c) System.out.println(" a und c zeigen auf das gleiche Objekt");
else System.out.println(" a und c zeigen nicht auf das gleiche Objekt");
if (a == d) System.out.println(" a und d zeigen auf das gleiche Objekt");
else System.out.println(" a und d zeigen nicht auf das gleiche Objekt");
if (a == e) System.out.println(" a und e zeigen auf das gleiche Objekt");
else System.out.println(" a und e zeigen nicht auf das gleiche Objekt");
if (a.equals(b)) System.out.println(" a und b sind gleiche Zeichenketten");
else System.out.println(" a und b sind nicht gleiche Zeichenketten");
if (a.equals(c)) System.out.println(" a und c sind gleiche Zeichenketten");
else System.out.println(" a und c sind nicht gleiche Zeichenketten");
if (a.equals(d)) System.out.println(" a und d sind gleiche Zeichenketten");
else System.out.println(" a und d sind nicht gleiche Zeichenketten");
if (a.equals(e)) System.out.println(" a und e sind gleiche Zeichenketten");
else System.out.println(" a und e sind nicht gleiche Zeichenketten");

Führt zum Ergebnis:

 a und b zeigen auf das gleiche Objekt
a und c zeigen auf das gleiche Objekt
a und d zeigen auf das gleiche Objekt
a und e zeigen nicht auf das gleiche Objekt
a und b sind gleiche Zeichenketten
a und c sind gleiche Zeichenketten
a und d sind gleiche Zeichenketten
a und e sind gleiche Zeichenketten

Zeichenkettenverwaltung im "Stringpool"

Der javac Übersetzer erkennt Literale schon beim Übersetzen einer Klasse und speichert sie nur einmal in der .class Datei mit dem Binärcode. Zur Laufzeit eines Javaprogramms wird die entsprechende Klasse bei der ersten Benutzung dynamisch geladen und die konstanten Zeichenketten (Literale) zu einem "Stringpool" hinzugefügt, wenn sie vorher noch nicht im Stringpool existierten. Dieses Verfahren minimiert den Speicherverbrauch und steigert den Durchsatz da Zeichenketten, in der Regel, kleine Objekte sind, die sonst den Freispeicher (Heap) dauerhaft belegen und fragmentieren.

Der Stringpool ist ein spezialisierter Bereich des Freispeichers. Objekte im allgemeinen Freispeicher und im Stringpool verhalten sich gleich. Der einzige Unterschied besteht darin, dass alle Literale im Stringpool nur genau einmal vorkommen.

Wird eine Zeichenkette dynamisch mit new String() angelegt so wird sie wie ein normales Objekt auf dem Freispeicher (Heap) angelegt und nicht im Stringpool. Die Klasse String hat jedoch eine Methode String.intern() die immer auf das kanonisierte Literal im Stringpool zeigt.

Der Zusammenhang zwischen Stringobjekten im Freispeicher wird durch das folgende Beispielprogramm deutlich:

String a = new String("Test");
String b = a.intern();
String c = new String("Test");
String d = c.intern();
String e = "Test";
if (a == c) System.out.println
("a,c sind die gleichen Objekte");
if (b == d) System.out.println
("b,d sind die gleichen Objekte"+
" im Stringpool");
if (e == d) System.out.println
("e,d sind die gleichen Objekte"+
" im Stringpool");

Ausgabe:

b,d sind die gleichen Objekte im Stringpool
d,e sind die gleichen Objekte im Stringpool
Stringobjekte und Stringpoolbeispiel

 

 

Effiziente Zeichenverwaltung mit der Klasse Stringbuffer

Die Klasse StringBuffer dient dem effizienten Handhaben von Zeichenketten. Ihre wesentlichen Eigenschaften sind:

  • "Threadsave" beim parallelen Ausführen von Threads in einem Javaprogramm werden Zeichenketten nicht versehentlich zerstört. Die Klasse ist hiermit sicher jedoch langsamer als eine nicht threadsichere Klasse (aber immer noch schneller als die Klasse String!)
  • Die Zeichenketten sind veränderbar

Wichtige Methoden Methoden der Klasse Stringbuffer sind:

Die Klasse StringBuffer verfügt über viele weitere Methoden zur Manipulation von Zeichenketten die in der API Dokumentation beschrieben werden.

Tipp: Seit JDK 5.0 steht die Klasse StringBuilder für Anwendungen die extreme Leistung benötigen zur Verfügung. Sie bietet jedoch keine Synchronisation zwischen Threads.

 

6.7 Übungen

Duke als Boxer

6.7.1 Referenzieren, Dereferenzieren und GC

Implementieren Sie die Klasse Person mit folgenden Eigenschaften:

  • Attribute für Vater und Mutter vom Typ Person
  • Statisches Attribut zum Zählen aller erzeugten Instanzen
  • Konstruktor
    • Eingabeparameter Anzahl der Vorfahrengenerationen
    • Erzeugen der Vorfahren durch Belegen der Attribute Vater, Mutter
    • Erhöhen Sie den Personenzähler
    • Drucken Sie einen Punkt pro tausend erzeugter Objekte
    • Tipp: Der Konstruktor ist rekursiv!

Hauptprogramm mit folgenden Eigenschaften:

  • Eingabe von n Generationen
  • Eingabe ob explizite GC erfolgen soll
  • Anlegen einer Person p1 mit n Generationen
  • Anstoßen einer Garbage Collection falls gewünscht
  • Anlegen einer neuen Person auf p1 mit n Generationen
Person mit Vorfahren

 

Testen des Programms mit den Optionen -Xms -Xmx -XX:+PrintGC

Beispiel:

java -Xms64m -Xmx256m -XX:+PrintGC block7.gc.Main

Dies bewirkt das Starten einer Javaanwendung mit

  • 64 Megabyte initialem Freispeicher (-Xms64m)
  • 256 Megabyte maximalem Freispeicher (-Xmx256m)
  • Protokollieren auftretender Garbagecollections (-XX:+PrintGC)

Allgemeine Überlegungen:

  • Wie kann man erzwingen das die erste Person und Ihre Vorfahren zum Löschen freigegeben werden?
  • Fügen die unten aufgeführte Kontrollmethode zum Auslesen der Speicherdaten an interessanten Stellen aus.

Tipp: Benutzen die folgende Beispielmethode zum Auslesen des Speicherverbrauchs.

Aufgaben

Variieren Sie den initialen, maximalen Speicher sowie die Anzahl der Generationen um

  • Implizite GCs zu provozieren, zu vermeiden
  • Eine OutOfMemory Ausnahme zu provozieren
  • Erhöhen Sie Ihren Speicherverbrauch bis Sie an die Grenzen Ihres Systems kommen. Vorsicht! Nähern Sie sich Ihren Systemgrenzen in kleinen Schritten. Ihr System kann für eine gewisse Zeit durch Überlastung unbenutzbar werden!

Ein Testprogramm

Anbei ein einfaches Testprogramm welches eine Person ohne Vorfahren erzeugt und dann in einer Schleife neue Personen mit immer mehr Vorfahren erzeugt.

Die Konstante mit den Generationen kann bis zu einem Wert von etwa 20 erhöht werden.

package block6.gc;
public class Test {
/**
* Zu erzeugende Generationen
*/
public static int generationen = 3; // Initial testen
//public static int generationen = 19; // mit -XX:+printGC
//public static int generationen = 22; // mit -Xmx500m -XX:+printGC
//public static int generationen = 23; // mit -Xms500m -Xmx1024m -XX:+printGC
public static void main(String[] args) {
Person p;
systemStatus();
for (int i=0; i<= generationen; i++) {
System.out.println("*** Erzeuge " + i + " Generationen ***");
// Der alte Personenbaum wird implizit dereferenziert
p = new Person(i);
// Verlängern der Laufzeit. Dies erlaubt eine bessere Beobachtung mit jonsole
// Der alte Vorfahrenbaum wird durch die Zuweisung dereferenziert
//p = new Person(i);
//p = new Person(i);
systemStatus();
System.out.println("*** Ende. Erzeuge " + i + " Generationen ***");
}
}
public static void systemStatus(){
Runtime r = Runtime.getRuntime();
System.out.println("*** Systemstatus ***");
System.out.println("* Prozessoren : " + r.availableProcessors());
System.out.println("* Freier Speicher: " + r.freeMemory());
System.out.println("* Maximaler Speicher: " + r.maxMemory());
System.out.println("* Gesamter Speicher: " + r.totalMemory());
System.out.println("*** Ende Systemstatus ***");
}
}

6.7.2 Tiefes Kopieren

Benutzen Sie den Quellcode der vorhergehenden Übung.

Schreiben Sie eine Klasse mir dem Namen Adresse und den folgenden Eigenschaften

  • öffentliche Attribute ort, plz, strasse, hausnummer
  • einen öffentlichen Konstruktor der alle Attribute erfasst
  • einen "Copy Constructor" für die Klasse

Erweitern Sie die Klasse Person wie folgt:

  • Attribut wohnort vom Typ Adresse
  • "Copy Constructor" für die Klasse

Wenden Sie den "Copy Constructor" im Hauptprogramm an.

Optional

Schreiben Sie einen "Copy Constructor" der maximal n Generationen kopiert.

6.7.3 Tiefes Vergleichen

Erweitern Sie die Klassen aus den vorgehenden Beispielen wie folgt:

Klasse Person

  • Fügen Sie einen Konstruktor hinzu der Name und alle vier Parameter für einen Geburtsort erfasst
  • Implementieren eine equals() Methode die den Vergleich basierend auf Namen und den Inhalten des Geburtsort ausführt

Klasse Adresse

  • Implementieren Sie eine  equals() Methode die auf alle Attribute der Klasse prüft

Hauptprogramm

  • Entwickeln Sie Testroutinen die die unterschiedlichen möglichen Fälle abprüfen

Tipp Die equals() Methode muss aus Typisierungsgründen ein Objekt vom Typ Object übernehmen. Mit dem unten aufgeführten Codestück kann man testen ob der EIngabeparameter vom gleichen Typ wie das aktuelle Objekt ist. Ist dies nicht der Fall so können die Objekte nicht gleich sein.

public boolean equals(Object obj) {
         ...
         if (this.getClass() == obj.getClass() )

6.7.4 "Walkthrough" Beispiel

Die hier gezeigte Übung wird interaktiv in der Vorlesung entwickelt. Die Arbeitsanweisungen sind im Quellcode enthalten.

Der Ausgangspunkt ist die Klasse Ware und die Klasse Lager die als Hauptprogramm dient:

KLasse Ware mit den erzeugten Instanzen

Ziel ist es eine Lösung zu entwickeln in der die Klasse Ware mit einem Lager verwendet werden kann:

Lager und Waren in UML

Klasse Ware

package block6;
/**
* Dies ist die Dokumentation der Klasse Ware. Ware dient zum Verwalten von Gütern
* mit Preisen und Namen in einem Lager.
* @author Stefan Schneider
* @version 1.1
* @see Lager
*/
public class Ware {
/*
* 7. Anlegen eine Copy Constructor
* 7.1 Alle Werte des Objekts werden kopiert
* 7.2 Es wird bei Bedarf eine neue Empfehlung angelegt
*/
/**
* Der aktuelle Mehrwertsteuersatz 2010.
* Er liegt zur Zeit bei {@value}.
* @since 1.0
* @version 1.0
*/ public static final double mws = 0.19;
private double nettoPreis; //Deklaration
public boolean halbeMws;
private String name;
public Ware empfehlung;
/**
* Konstruktor fuer die Klasse Ware
* @param n der Name der Ware
* @param np der Nettorpreis
* @param hmws halber Mehrwertsteuersatz für Ware gueltig
*/
public Ware(String n, double np, boolean hmws) {
name = n;
nettoPreis = np;
halbeMws = hmws;
}
/**
* Liefert den Namen einer Ware zurueck.
* @return Name der Ware
*/ public String get_name() {
return name; }
/** * Setzen eines neuen Nettopreis
* @param npr der neue Nettopreis
*/
public void set_nettoPreis(double npr) {
nettoPreis = npr;
}
/**
* Ausdrucken aller Werte auf der Konsole
*/
public void drucken() { drucken(0); }
/**
* Ausdrucken aller Werte auf der Konsole mit vorgebener Einrueckung
* für Empfehlungen
* @param einruecken eingerueckte Stellen für Empfehlungen
*/
private void drucken(int einruecken) {
String leerStellen = "";
for (int i = 0; i < einruecken; i++) {
leerStellen = leerStellen + " ";
}
System.out.println(leerStellen + "Name: " + name);
System.out.println(leerStellen + "netto: " + nettoPreis);
System.out.println(leerStellen + "Brutto: " + bruttoPreis());
System.out.println(leerStellen + "Halbe Mws:" + halbeMws);
if (empfehlung != null) { // Empfohlene Bücher werden eingerückt
empfehlung.drucken(einruecken + 2);
}
}
/**
* Ausgabe des Nettopreis
* @return der Nettopreis
*/ public double nettoPreis() {
return nettoPreis; }
/**
* Ausgabe des Bruttopreis
* @return der Bruttopreis
*/ public double bruttoPreis() {
double bp; //temporaere Variable; keine Klassenvariable
if (halbeMws) {
bp = Math.round(nettoPreis * (mws / 2 + 1) * 100) / 100;
} else {
bp = Math.round(nettoPreis * (mws + 1) * 100) / 100;
}
return bp;
}
}

Klasse Lager 

package block6;
/** * Die Klasse Lager verwaltet eine bestimmte Anzahl von Waren
* @author sschneid
*/
public class Lager {
/* * Aufgaben
* 1. Verwalten von n Waren in einem Feld
* 1.1 Deklarieren eines privaten Feldes
* 1.2 Zugriffsmethoden zum Setzen und Auslesen des Feldes
* 2. Implementieren eines Konstruktors der das Lager
* für n Waren initialisiert * 3. Methode zum Ausräumen des Lagers
* 4. Erzeugung eines Singletons zum Erzeugen genau eines Lagers
* 5. Anlegen einer neuen Klasse MainLager
* 5.1 Umkopieren der main() Methode aus der Klasse Lager in die Klasse
* MainLager.main()
* 8. Testen des Copy Constructors
* 8.1 Belegen Sie die ersten 500 Lagerpositionen mit Waren
* 8.2 Klonen Sie die ersten 500 Lagerpositionen und belegen Sie die
* folgenden 500 Lagerpositionen mit Ihnen
* 8.3 Löschen Sie Ihr Lager indem Sie alle Postionen mit null belegen
* 8.4 Implementieren Sie eine Schleife die einige Minuten läuft
* und testen Sie den Speicherverbrauch mit jconsole oder
*/
public static void main(String[] args) {
Ware ware1 = new Ware("Zeitung",12.12,true);
System.out.println("Ware1 ohne Empfehlung:");
ware1.drucken();
double p = ware1.nettoPreis();
Ware ware2 = new Ware("Potter Bd1",31.12,false);
Ware ersterBand = ware2;
ware1.empfehlung= ware2;
System.out.println("Ware1 mit Empfehlung:");
ware1.drucken();
// Erzeugen einer Ware mit 7 verketteten Empfehlungen
Ware ware3;
Ware hp1=ware2;
for (int i=2; i<= 7; i++) {
ware3 = new Ware("Potter Bd" + i,31.25+i,false);
ware2.empfehlung = ware3;
ware2 = ware3;
}
System.out.println("Alle Harry Potter Baende drucken");
hp1.drucken();
}
}

Klasse MainLager

package block6;
/**
* * Eine Hilfsklasse zur Implementierung eines Hauptprogramms
*
* @author sschneid
* @version 1.0
*/
public class MainLager {
/*
* 6. Testen der Klasse Main * 6.1 Aufruf des Singleton der Klasse Lager * 6.2 Einfügen zweier Waren * 6.3 Drucken zweier Waren
*/
public static void main(String[] args) {
Ware ware1 = new Ware("Zeitung", 12.12, true);
ware1.drucken();
double p = ware1.nettoPreis();
Ware ware2 = new Ware("Potter Band 1", 31.12, false);
Ware ersterBand = ware2;
Ware ware3;
for (int i = 2; i <= 7; i++) {
ware3 = new Ware("Potter Band" + i, 31.12, false);
ware2.empfehlung = ware3;
ware2 = ware3;
}
}
}

 

6.8 Lösungen

6.8.1 Referenzieren, Derefenzieren und GC

Klasse Main

Das folgende Programm wird wie folgt gestartet

java block6.gc.Main 3 gc

Es erwartet zwei Kommandozeilenargumente. Das erste Argument beschreibt die Anzahl der zu erzeugenden Generationen.

Das zweite Argument is optional. Steht hier GC wird eine System Garbarge Collection angestoßen

package block6.gc;

public class Main {

    /**
    * Auslesen von Systemparametern
    */
    public static void systemStatus(){
        Runtime r = Runtime.getRuntime();
        System.out.println("*** System Status ***");
        System.out.println("Prozessoren :       " + r.availableProcessors());
        System.out.println("Freier Speicher:    " + r.freeMemory());
        System.out.println("Maximaler Speicher: " + r.maxMemory());
        System.out.println("Gesamter Speicher:  " + r.totalMemory());
        System.out.println("***  ***");
    }

    public static void main(String[] args) {
        int vorfahren = 0;
        boolean systemCollection=false;
        Person p1;

        systemStatus();
        // Parsen des ersten Arguments
        // Diese Zeichenkette enthält die ANzahl der Generationen
        if (args.length > 0 ) { // Gibt es ein erstes Argument?
        try { vorfahren = Integer.parseInt(args[0]);}
        catch (NumberFormatException e) {
            System.err.println("Argument muss Ganzzahl sein");
            System.exit(1);
            }
        }
        // Auslesen des zweiten Arguments
        // Steht hier die Zeichenkette GC in Klein- oder Grossschreibung
        if (args.length > 1 ) // Gibt es ein zweites Argument
            systemCollection = ((args[1].equalsIgnoreCase("gc"));

        p1 = new Person(vorfahren);
        //p1 = null;
        System.out.println();
        System.out.println("Erster Schritt erledigt: " + Person.zaehler +
                " Instanzen erzeugt");
        systemStatus();
        if (systemCollection)  {
            System.out.println("Starte System GC");
            System.gc();
        }
        p1 = new Person(vorfahren);
        System.out.println();
        System.out.println("Zweiter Schritt erledigt: " + Person.zaehler +
                " Instanzen erzeugt");
        systemStatus();
    }

}

Klasse Person

package block6.gc;
public class Person {
    Person vater;
    Person mutter;
    static int zaehler=0;

    public Person (int vorfahren)
    {
        if ( vorfahren>0) {
            vater = new Person(vorfahren-1);
            mutter = new Person(vorfahren-1);
        }
        zaehler++;
        if (zaehler%1000 == 0) System.out.print(".");

    }
}

6.8.2 "Copy Constructor" Klasse

Klasse Main

package block6.gc;

public class Main {

    public static void systemStatus(){
        Runtime r = Runtime.getRuntime();
        System.out.println("*** System Status ***");
        System.out.println("Prozessoren :       " + r.availableProcessors());
        System.out.println("Freier Speicher:    " + r.freeMemory());
        System.out.println("Maximaler Speicher: " + r.maxMemory());
        System.out.println("Gesamter Speicher:  " + r.totalMemory());
        System.out.println("***  ***");
    }

    public static void main(String[] args) {
        int vorfahren = 0;
        boolean systemCollection=false;
        Person p1;

        systemStatus();
        if (args.length > 0 ) {
        try { vorfahren   = Integer.parseInt(args[0]);}
        catch (NumberFormatException e) {
            System.err.println("Argument muss Ganzzahl sein");
            System.exit(1);
            }
        }
        if (args.length > 1 )
            systemCollection = (args[1].equalsIgnoreCase("gc"));

        p1 = new Person(vorfahren);
        //p1 = null;
        System.out.println();
        System.out.println("Erster Schritt erledigt: " + Person.zaehler +
                " Instanzen erzeugt");
        systemStatus();
        if (systemCollection)  {
            System.out.println("Starte System GC");
            System.gc();
        }
        p1 = new Person(p1);
        System.out.println();
        System.out.println("Zweiter Schritt erledigt: " + Person.zaehler +
                " Instanzen erzeugt");
        systemStatus();
    }

}

Klasse Adresse

package block6.gc;

public class Adresse {
    public String ort;
    public String plz;
    public String strasse;
    public String hausnr;

    public Adresse (String o, String p, String s, String h){
        ort     = o;
        plz     = p;
        strasse = s;
        hausnr  = h;
    }

    public Adresse (Adresse orig) {
        ort     = orig.ort;
        plz     = orig.plz;
        strasse = orig.strasse;
        hausnr  = orig.hausnr;
    }
}

Klasse Person

package block6.gc;
public class Person {
    Person vater;
    Person mutter;
    String name;
    Adresse wohnort;
    static int zaehler=0;

    public Person (int vorfahren)
    {
        if ( vorfahren>0) {
            vater  = new Person(vorfahren-1);
            mutter = new Person(vorfahren-1);
        }
        wohnort = new Adresse("Berlin", "10117","Platz der Republik","1");
        name = "John Doe";
        zaehler++;
        if (zaehler%1000 == 0) System.out.print(".");
    }

    public Person (Person orig) {
         if (orig.vater != null) vater  = new Person(orig.vater);
         if (orig.mutter != null) mutter = new Person(orig.mutter);
         if (orig.wohnort != null) wohnort = new Adresse(orig.wohnort);
         name   = orig.name;
         zaehler++;
         if (zaehler%1000 == 0) System.out.print(".");
    }
}

Optional: "Copy Constructor mit begrenzter Kopiertiefe

    public Person (Person orig, int vorfahren) {
        if (vorfahren > 0) {
            if (orig.vater != null)
                vater  = new Person(orig.vater,  vorfahren-1);
            if (orig.mutter != null)
                mutter = new Person(orig.mutter, vorfahren-1);
            }
        if (orig.wohnort != null) wohnort = new Adresse(orig.wohnort);
        name   = orig.name;
        zaehler++;
        if (zaehler%1000 == 0) System.out.print(".");
    }

6.8.3 Tiefes Vergleichen Klasse Person

Klasse Person

class Person {
...
    public Person (String name1, String gort, String gplz,
            String gstrasse, String gnr){
        name = name1;
        geburtsort = new Adresse(gort,gplz,gstrasse,gnr);
        zaehler++;
        if (zaehler%1000 == 0) System.out.print(".");
   }
...
 public boolean equals(Object obj) {
         Person target;
         // Type checking is optional for this level of skills
         if (this.getClass() == obj.getClass() ) {
            target = (Person) obj;                
            return (name.equals(target.name) && 
                      (geburtsort.equals(target.geburtsort)));
            }
         else return false;
     }
...
}

Klasse Adresse

public boolean equals(Object obj) {
         Adresse target;
         // Type checking is optional for this level of skills
         if (this.getClass() == obj.getClass() ) {
            target = (Adresse) obj;                
            return (ort.equals(target.ort) && 
                plz.equals(target.plz) &&
                strasse.equals(target.strasse) &&
                hausnr.equals(target.hausnr));
            }
         else return false;
}

6.8.4 Ware/Lager Beispiel

Klasse MainLager

package block6;
/**
* * Eine Hilfsklasse zur Implementierung eines Hauptprogramms
*
* @author sschneid
* @version 1.1
*/
public class MainLager {
/*
* 5. Anlegen einer neuen Klasse MainLager * 5.1 Umkopieren der main() Methode
* aus der Klasse Lager in die Klasse MainLager.main()
*/
/*
* 8. Testen des Copy Constructors
* 8.1 Belegen Sie die ersten 500
* Lagerpositionen mit Waren
* 8.2 Klonen Sie die ersten 500 Lagerpositionen und belegen Sie die
* folgenden 500 Lagerpositionen mit Ihnen
* 8.3 Löschen Sie Ihr Lager indem Sie alle Postionen mit null belegen
* 8.4 Implementieren Sie eine Schleife die einige Minuten läuft und testen Sie
* den Speicherverbrauch mit jconsole und jps
*/

public static void main(String[] args) {
Lager lager1 = Lager.dasLager();
int position;
for (int j = 0; j < 100000; j++) {
for (int i = 0; i < 500; i++) {
position = lager1.einlagern(new Ware("Buch der Zahl " + i, 2 * i, false));
//System.out.println("Ware auf Position" + position + " eingelagert");
}
for (int i = 0; i < 500; i++) {
position = lager1.einlagern(new Ware(lager1.holen(i)));
//System.out.println("Ware auf Position" + position + " eingelagert");
}
lager1.ausraeumen();

System.out.println("Schleife " + j + " von 100000");
}
}
}

Klasse Ware

package block6.loesung;
/**
* Dies ist die Dokumentation der Klasse Ware. Ware dient zum Verwalten von Gütern
* mit Preisen und Namen in einem Lager.
* @author Stefan Schneider
* @version 1.1
* @see Lager
*/
public class Ware {
/**
* Der aktuelle Mehrwertsteuersatz 2010.
* Er liegt zur Zeit bei {@value}.
*/
public static final double mws = 0.19;
private double nettoPreis; //Deklaration
public boolean halbeMws;
private String name;
public Ware empfehlung;
/**
* Konstruktor fuer die Klasse Ware
* @param n der Name der Ware
* @param np der Nettorpreis
* @param hmws halber Mehrwertsteuersatz für Ware gueltig
*/
public Ware(String n, double np, boolean hmws) {
name = n;
nettoPreis = np;
halbeMws = hmws;
}
/*
* 7. Anlegen eine Copy Constructor
* 7.1 Alle Werte des Objekts werden kopiert
* 7.2 Es wird bei Bedarf eine neue Empfehlung angelegt
*/
/**
* Copy-Konstruktor fuer die Klasse Ware
* @param w die zu klonende Ware
*/
public Ware(Ware w) {
name = w.name;
nettoPreis = w.nettoPreis;
halbeMws = w.halbeMws;
if (w.empfehlung != null)
empfehlung = new Ware(w.empfehlung);
}
/**
* Liefert den Namen einer Ware zurueck.
* @return Name der Ware
*/ public String get_name() {
return name; }
/** * Setzen eines neuen Nettopreis
* @param npr der neue Nettopreis
*/
public void set_nettoPreis(double npr) {
nettoPreis = npr;
}
/**
* Ausdrucken aller Werte auf der Konsole
*/
public void drucken() {
System.out.println("Name: " + name);
System.out.println("netto: " + nettoPreis);
System.out.println("Brutto: " + bruttoPreis());
System.out.println("Halbe Mws:" + halbeMws);
if (empfehlung != null) {
empfehlung.drucken(2);
}
}
/**
* Ausdrucken aller Werte auf der Konsole mit vorgebener Einrueckung
* für Empfehlungen
* @param einruecken eingerueckte Stellen für Empfehlungen
*/ public void drucken(int einruecken) {
String leerStellen = "";
for (int i = 0; i < einruecken; i++) {
leerStellen = leerStellen + " ";
}
System.out.println(leerStellen + "Name: " + name);
System.out.println(leerStellen + "netto: " + nettoPreis);
System.out.println(leerStellen + "Brutto: " + bruttoPreis());
System.out.println(leerStellen + "Halbe Mws:" + halbeMws);
if (empfehlung != null) {
empfehlung.drucken(einruecken + 2);
}
}
/**
* Ausgabe des Nettopreis
* @return der Nettopreis
*/ public double nettoPreis() {
return nettoPreis; }
/**
* Ausgabe des Bruttopreis
* @return der Bruttopreis
*/ public double bruttoPreis() {
double bp; //temporaere Variable; keine Klassenvariable
if (halbeMws) {
bp = Math.round(nettoPreis * (mws / 2 + 1) * 100) / 100;
} else {
bp = Math.round(nettoPreis * (mws + 1) * 100) / 100;
}
return bp;
}
}

Klasse Lager

package block6;
/** * Die Klasse Lager verwaltet eine bestimmte Anzahl von Waren
* @author sschneid
* @version 1.1
*/
public class Lager {
/*
* Aufgaben
* 1. Verwalten von n Waren in einem Feld
* 1.1 Deklarieren eines privaten Feldes
*/
private int lagerGroesse = 1000;
private Ware[] warenLager;

// 1.2 Zugriffsmethoden zum Setzen und Auslesen des Feldes
public Ware holen(int i) {
if ((i<0) || (i>=lagerGroesse)) return null;
else return warenLager[i];
} public int einlagern(Ware w) { int i=0; while ((i<lagerGroesse) && (warenLager[i] != null)) i++; if (i<lagerGroesse) { warenLager[i] = w; return i; } else return -1; } // 3. Methode zum Ausräumen des Lagers public void ausraeumen() { for (int i=0; i<lagerGroesse; i++) warenLager[i]=null; }

/*
* 2. Implementieren eines Konstruktors der das Lager
* für n Waren initialisiert
*/
private Lager() {
// Es wird ein Feld von Referenzen auf Waren angelegt
// Es werden keine Waren angelegt!
warenLager = new Ware[lagerGroesse];
}
/*
* 4. Erzeugung eines Singletons zum Erzeugen genau eines Lagers
*/
private static Lager meinLager; // Eine Objektvariable die auf das Lager zeigt
static public Lager dasLager() {
if (meinLager== null) meinLager= new Lager();
return meinLager;
}
/*
* 5. Anlegen einer neuen Klasse MainLager
* 5.1 Umkopieren der main() Methode aus der Klasse Lager in die Klasse
* MainLager.main()
*/
// 6. Testen der Factory
/*Lager lager1 = Lager.dasLager();
int position;
for (int i=0; i<500; i++)
position = lager1.einlagern(new Ware("Buch " +i, 2*i, false));
for (int i=0; i<500; i++) {
Ware w = lager1.holen(i);
System.out.println("Position " + i);
w.drucken();
}
*/
/*
* 8. Testen des Copy Constructors
* 8.1 Belegen Sie die ersten 500 Lagerpositionen mit Waren
* 8.2 Klonen Sie die ersten 500 Lagerpositionen und belegen Sie die
* folgenden 500 Lagerpositionen mit Ihnen
* 8.3 Löschen Sie Ihr Lager indem Sie alle Postionen mit null belegen
* 8.4 Implementieren Sie eine Schleife die einige Minuten läuft
* und testen Sie den Speicherverbrauch mit jconsole oder
*/
public static void main(String[] args) {
Ware ware1 = new Ware("Zeitung",12.12,true);
System.out.println("Ware1 ohne Empfehlung:");
ware1.drucken();
double p = ware1.nettoPreis();
Ware ware2 = new Ware("Potter Bd1",31.12,false);
Ware ersterBand = ware2;
ware1.empfehlung= ware2;
System.out.println("Ware1 mit Empfehlung:");
ware1.drucken();
// Erzeugen einer Ware mit 10 verketteten Empfehlungen
Ware ware3;
Ware hp1=ware2;
for (int i=2; i<= 7; i++) {
ware3 = new Ware("Potter Bd" + i,31.25+i,false);
ware2.empfehlung = ware3;
ware2 = ware3;
}
System.out.println("Alle Harry Potter Baende drucken");
hp1.drucken();
// ersterBand.drucken();
}
}

 

6.9 Lernziele

Am Ende dieses Blocks können Sie:

  • .... den Unterschied zwischen Variablen primitive Datentypen und Objektvariablen erkennen, erklären und anwenden
  • ... die "null" Referenz anwenden um den Hauptspeicherverbrauch (Java-Heap) zu managen
  • ... Beispiele für die Verwendung von Eigenreferenzen (this Referenz) nennen
  • ... die this Referenz nutzen und erklären
  • ... die wichtigsten Optionen des Kommando java zur Konfiguration des Java-Heap nennen und anwenden
  • ... Objekte identifizieren die in einer Anwendung nicht mehr referenziert werden
  • ... erkennen ob Objekte oder nur die Referenzen auf die Objekte kopiert werden
  • ... den Unterschied zwischen dem == bzw. != Operator und der equals() Methode beim Vergleichen von Objekten erklären und anwenden
  • ...die Besonderheiten des final Modifizierer bei Objektvariablen erklären
  • ...mit der Javaklasse String umgehen, kennen die wichtigsten Methoden und können deren Verwaltung im Speicher erklären

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten:

7. Felder

Definition:

Felder sind Javaobjekte und werden inm Freispeicher (Heap) verwaltet, da Felder variable Größen haben. Aus diesem Grund können sie nicht statisch allokiert werden. Anbei die Speichersicht, die ähnlich zu Strings ist. Das abgebildete Diagramm zeigt ein Feld mit Platz für 6 Ganzzahlen:

Felder sind häufig verwendete Datenstrukturen da man sehr einfach mit Schleifen über den Index alle Feldelemente erreichen kann.

Der Zugriff auf Feldelemente erfolgt über eine Syntax mit Verwendung von rechteckigen [index] Klammern wie man im folgenden Beispiel sehen kann:

int[] d1 = { 1000, 100, 10};
int a = d1[2]; // Auslesen von 10 aus Position 2
d1[0]= 99; // Zuweisen des Werts 99 auf Position 0

Weitere Quellen

Oracle Tutorial: Arrays (in Englisch)

7.1 Eindimensionale Felder

Das Definieren und Erzeugen von eindimensionalen Feldern erfolgt nach ähnlichen Prinzipien wie die Verwaltung von Strings.

Es gibt hier zwei Varianten.

Anlegen und befüllen mit new() Operator

Die Deklaration anhand eines Feldes von Ganzzahlen mit Hilfe des new() Operators in 3 Schritten:

int[] array1;

Hiermit wurde nur die Referenz auf ein (zukünftiges) Feld von Ganzzahlen angelegt. Siehe unten: Mit Hilfe des new() Operators wird ein leeres Feld angelegt Nach dem Anlegen des Feldes kann man die Größe des Feldes mit dem Attribut length abfragen.:

array1 = new int[6];
int s = array1.length;

Das Definieren der Variablen array1 und das Aufrufen des new() Operators hätte auch in einem Schritt erfolgen können

int[] array1 = new int[6];

Jetzt kann das Feld mit Werten belegt werden. Zum Beispiel mit Vielfachen der Zahl 17:

for ( int i=0; i<s; i++) {array[i]  = i*17;}

Aufzählende Wertzuweisung

Java erlaubt auch die 3 Schritte von oben in einer einzigen Operation auszuführen. Hier können alle Werte als Aufzählung innerhalb von geschweiften Klammern direkt zugewiesen werden:

int[] array1 = {0,17,34,51,68,85};

 Zuweisungen zwischen Arrayvariablen

Arrayvariablen verhalten sich wie Referenzen bezüglich Zuweisungen. Das Zuweisen des Nullwerts löscht die Referenz auf ein Feld(Array). Es dann ein Kandidat zum Löschen falls keine andere Referenz auf das Feld vorhanden ist. Das folgende Beispiel zeigt eine Zuweisung zwischen den Feldvariablen.

int[] array1 = {0,17,34,51,68,85};
int[] array2 = array1;
if (array1[2] == array2[2]) 
   System.out.println("Dieser Text sollte " +
      "ausgedruckt werden!");

Beide Variablen array1 und array2 zeigen auf dasselbe Feld

Durch das Zuweisen des null Werts werden die Referenzen gelöscht. Das Feld bleibt bestehen. Es wird jedoch gelöscht, wenn es keine andere Referenz mehr auf das Feld gibt.

array1 = null;
array2 = null;

Kopieren von Feldern

Das naive Kopieren von Feldern mit vollständiger Replikation kann man mit der erweiterten Notation der for Schleife leicht selbst implementieren:

int[] array1 = {0,17,34,51,68,85};
int[] array2 = new int[array1.length];
for (int i=0; i<array1.length; i++) 
   {array2[i]= array1[i];}

Alle Elemente des Felds werden einzelnen kopiert. Die erweiterte Notation erlaubt es die Anweisung ohne explizite Verwendung der Feldgröße anzugeben. Das Ergebnis hat eine Speicherstruktur wie folgt:

Eine effizientere Möglichkeit bildet die Methode System.arraycopy() aus der System Klasse. Diese Methode hat die folgende Signatur:

public static void arraycopy(Object src,
                             int srcPos,
                             Object dest,
                             int destPos,
                             int length)

Sie erlaubt:

  • von einem Feld src ab der Position srcPos
  • in ein Feld dest an die Position destPos
  • die Anzahl length Objekte zu kopieren

Das oben aufgeführte Beispiel würde wie folgt mit der Methode arrayCopy() implementiert:

int[] array1 = {0,17,34,51,68,85};
int[] array2 = new int[array1.length];
System.arrayCopy(array1,0,array2,0,array1.length);

Ausnahmen und Fehler bei Arbeiten mit Arrays (Feldern)

Beim Lesen und Manipulieren von Feldern können unterschiedliche Fehler auftreten. Sie können beim Über- oder Unterlauf eines Feldes auftreten oder bei dem Versuch Typen zuzuweisen die das Feld nicht erlaubt. Die folgenden Ausnahmen (Exceptions) können auftreten: 

 

7.2 Felder als Parameter von Methoden

Felder(Arrays) können wie Basistypen oder Referenzen als Übergabe- und Rückgabeparameter von Methoden verwendet werden.

Ein typisches Beispiel ist:

public static void main(String[] args) {
    ...
      if (args != null)
       if (args.length > 2 ) {
        try {
           tag   = Integer.parseInt(args[0]);
           monat = Integer.parseInt(args[1]);
           jahr = Integer.parseInt(args[2]);
   ...

Die beim Programmstart mitgegebenen Argumente werden in Form eines Feldes von Zeichenketten übergeben. Man erkennt beim Beispiel oben, dass im Quellcode eine Reihe von Fehlerfällen geprüft werden bevor die Anwendung auf das "unbekannte" Feld zugreift und die Zeichenketten in Zahlen verwandelt:

  • args ist eventuell ein null Zeiger
  • die Anzahl der Argumente ist unbekannt. Nur wenn mehr als zwei Elemente im Feld sind werden die ersten 3 Elemente ausgelesen.

Hierbei ist wichtig, dass die Felder nach dem gleichen Konzept wie Referenzen übergeben werden:

  • Call by Reference: Das Feld wird nicht dupliziert. Es steht der Methode nicht unbedingt exklusiv zur Verfügung. Es wird nur eine Referenz übergeben.

Entsprechend können Felder auch als Rückgabeparameter dienen. Der Rückgabeparameter muss mit Typ und rechteckiger Klammer genannt werden. Hier int[]:

public static int[] createRandom(int size, int range) {
   int[] array1 = new int[size];
   for (int i=0; i<size;i++) 
       array1[i] = (int)(Math.random()*range);
   return array1;

Methoden mit variabler Argumentenanzahl

Es gibt Anwendungsfälle bei bei denen es wünschenswert ist eine variable Anzahl von Parameter zu benutzen. Seit Java 5 muss man hier nicht mehr ein Feld übergeben. Für sogenannte "varargs" Methoden gibt es ein "..." Konstrukt (drei Punkte) mit dem man die variable Anzahl der Parameter eines bestimmten Typs deklarieren kann. 

Anbei das Beispiel einer Methode, die die Namen von Haustieren entgegen nimmt:

public void hausTiereMelden(String ... tierNamen) {
   for ( String t : tierNamen) System.out.println("Haustier: " +t);
}
public static void test () {
   hausTiereMelden("Waldi","Bello","Rufus");
   hausTiereMelden("Mikesh", "Napoleon");
   hausTiereMelden(); // Keine Parameter!
}

Hinweis: "varargs" Methoden dürfen auch ganz ohne Parameter aufgerufen werden. Die Implementierung der Methode muss daher auch mit einer null Belegung umgehen können!

7.3 Mehrdimensionale Felder

Eindimensionale Felder entsprechen Listen mit einem wahlfreien Zugriff. Java erlaubt auch die Benutzung von zwei- und mehrdimensionalen Feldern.

Zweidimensionale Felder

Ein zweidimensionales Feld besteht entsprechend aus Zeilen und Spalten. Die Java Syntax hierzu ist eine Konkatenierung von eckigen Klammern. Zur Erzeugung eines Feldes mit 3 Zeilen und 6 Spalten nutzt man die folgende Notation:

int[][] array1 = new int[3][6];

Hiermit ergibt sich eine Speicherstruktur die aus einem Spaltenfeld besteht welches alle Zeilenfelder enthält um eine zweidimensionale Struktur aufzuspannen. Man kann hier jetzt 3*6=18 Werte speichern:

 

Das Setzen und Lesen von Werten geschieht wie folgt:

int k = 222;
array2[2][5]= k;
int n = array2[0][0];

Aufzählende Initialisierung mehrdimensionaler Felder

Auch bei mehrdimensionalen Feldern ist eine aufzählende Initialisierung möglich:

int[][] array1 = {{1,2,3,4,5,6},{5,10,15,20,25,30},{10,20,30,40,50,60}}

Hiermit ergibt sich eine zweidimensionale Tabelle mit der folgenden Belegung:

 

Bestimmung der Größe mehrdimensionaler Felder

Das Attribut length erlaubt auch bei mehrdimensionalen Feldern die Bestimmung der Größe. Hier liefert das Attribut jedoch nicht die Gesamtgröße der Datenstruktur sondern nur die Größe einer bestimmten Dimension.

int d =array1.length;

liefert im oben gezeigten Beispiel eine 3 für die erste Dimension, die Zeilen.

int d = array1[2].length;

gibt eine 6 zurück. Da die dritte Zeile 6 Elemente hat.

Anmerkung: Höher dimensionale Felder haben nicht unbedingt in allen Dimensionen die gleiche Größe. Dies bedeuted, dass ein zweidimensionales Feld muss nicht rechteckig sein muss!

Die folgende Implementierung erzeugt ein "dreieckiges" zweidimensionales Feld:

int[][] array1;
array1 = new int[3][];
array1[0] = new int[1];
array1[1] = new int[2];
array1[2] = new int[3];

Die Speicherstruktur zu diesem Feld sieht wie folgt aus:

Eine aufzählende Initialisierung ist auch möglich:

int[][] array1 = {{1},{11,22},{111,222,333}};

Das length Attribut liefert bei dieser Struktur die unterschiedlichsten Ergebnisse.

Höherdimensionale Felder

Nach dem gleichen Verfahren können dreidimensionale oder noch höherdimensionale Felder erzeugt und verwaltet werden.

Person[][][] Mitarbeiter = new Person[10][10][10];
Mitarbeiter[7][8][9] = new Person ("Jane","Doe");

Im vorliegenden Beispiel handelt es sich um ein dreidimensionales Feld mit einem Objekttyp (Person). Bei Objektfeldern werden nur die Felder angelegt nicht aber die zugehörigen Objekte. Sie müssen individuell erzeugt werden. Nach der initialen Felderzeugung sind alle Felder mit Nullreferenzen belegt. Mit dieser Belegung lässt sich normalerweise schlecht arbeiten. Bei Basistypen ist dies anders. Sie werden auch auf Null initialisiert und können direkt verwendet werden. Bei Feldern mit Referenzen werden jedoch nicht direkt die benötigten Objekte angelegt.

7.4 Übungen

Duke als Boxer

 7.4.1 Telefonbuchanwendung (1)

Implementieren Sie eine Telefonbuchanwendung die es erlaubt die folgenden Datensätze zu verwalten:

  • Name String
  • Vorname String
  • Telefonnummer Ganzzahl

 Benötigte Zeit: 30-60 Minuten für einen gübten Programmierer

Die Klasse Telefonbuch soll die folgenden Eigenschaften haben:

  • Verwaltung des Namens, Vornamens, Telefonnummer in drei Feldern mit den entsprechenden Typen
  • Eine Methode die das gesamte Telefonbuch ausdruckt
  • Suche nach allen drei Attributen mit drei verschiedenen Methoden
  • Eine Methode zum "bevölkern" des Telefonbuchs mit mindestens 10 Namen
    • Alle Datensätze seien unterschiedlich
  • Das Telefonbuch soll initial Felder für 4 Einträge besitzen. Beim Vollaufen des Telefonbuchs sollen neue Felder angelegt werden die um 50% größer sind.
  • Kapseln Sie die gesamte Implementierung der Felder innerhalb der Klasse
  • Implementieren Sie eine Methode zum Löschen eines gesuchten Datensatzes
    • Beim Löschen sollen keine leeren Einträge in den Feldern entstehen
  • Implementieren Sie eine Testmethode die
    • 10 Adressen einträgt
    • 2 Adressen löscht
    • 1 Adresse hinzufügt

Hinweise:

  • Benutzen Sie Methoden für alle sinnvollen Teilaufgaben
  • Das Telefonbuch ist nicht notwendigerweise sortiert. Man muss alle Datensätze durchsuchen

Tipp: Die Suchanfragen lassen sich mit wenig Aufwand vom einer grafischen Swingoberfläche steuern. Ein Beispielprogramm finden Sie hier.

7.4.2 "Objektorientierte" Telefonbuchanwendung (2)

Überarbeiten Sie die Telefonbuchanwendung aus der vorhergehenden Aufgabe derart, dass Sie:

  • Eine Klasse Person mit den drei Attributen nutzen
  • Nur ein Feld vom Typ Person in dem alle Daten verwaltet werden

Welche der beiden Lösungen gefällt Ihnen besser? Warum?

7.4.3 Zufallszahlengenerator

Schreiben Sie ein Programm welches die Zuverlässigkeit des Java Zufallszahlengenerator prüft.

  • Erzeugen Sie ein Feld mit 1 Million (Anzahl konfigurierbar) Zufallszahlen im Bereich von 0 bis 999. Nutzen Sie die Methode Math.random() und das existierende Beispielprogramm.
  • Erzeugen Sie ein dreidimensionales Feld für Ganzzahlen mit einer Größe von 10*10*10 Einträgen (Index jeweils 0..9).
    • Speichern Sie in diesem Feld die Häufigkeit einer vorgekommenen Zahl.
    • Bsp: Erhöhen sie den Zähler der Position [5][4][3] um wenn Sie eine Zufallszahl "534" gefunden haben. Die Zelle [5][4][3] speichert die Anzahl der gefundenen Zahlen "542".
  • Zählen Sie die Zufallszahlen und tragen Sie sie in das dreidimensionale Feld ein
    • Beispiel: Inkrementieren den Wert der Zelle array[2][4][5] für jedes Vorhandensein der Zahl 245
  • Schreiben Sie eine Methode zum Ausdrucken des Feldes
  • Schreiben Sie Methoden die die folgenden Fragen beantworten
    • Welche Zahl kommen am häufigsten vor?
    • Welche Zahl kommen am seltensten vor?
  • Optional (keine Musterlösung):
    • Gibt es lokale Häufungen?
      • Welche Einer-, Zehner, Hunderterziffer ist am häufigsten?
      • Haben Zellen mit über/unterdurchschnittlich vielen Einträgen auch Nachbarn mit über/unterdurchschnittlich vielen Einträgen?
      • Eine Zelle array[x][y][z] hat meistens 8 Nachbarn array[x+/-1][y+/-1][z+/-1]

7.4.4 Conway: Das Spiel des Lebens

Das "Spiel des Lebens" wurde 1970 vom Mathematiker John Horton Conway 1970 entworfen.

Das Simulationsspiel basiert auf auf einem zweidimensionalen zellulären Automaten. Die Regeln des Automaten sind im Wikipediaartikel zu finden.

Die gelöste Aufgabe kann man in der unten gennaten Anwendung sehen.

  • Das Setzen von Elementen ist mit Mausklicks auf der entsprechenden Zelle möglich
  • Eine neue Generation wird mit dem Button ">" berechnet
  • Der Button ">>>" erlaubt das automatische Erzeugen von neuen Generationen. Mehrfaches Klicken halbiert die Wartezeit zwischen Generationen.
  • Der Button "Stop" beendet den automatischen Modus

Laden Sie das jar-Archiv Conway.jar und starten Sie es im gleichen Verzeichnis mit dem Kommando

java -jar Conway.jar

Bsp. Conway Anwendung

Aufgabe

Vervollständigen die Klasse Generation.java. Nutzen Sie das Hauptprogramm SpielDesLebens.java zum Testen Ihrer Klasse.

Die Interaktion der Klasse Generation mit dem Rahmenprogramm ist im folgenden Diagramm dargestellt:

Interaktion der Klassen

Klasse Generation.java

Hinweise:

  • Das Hauptprogramm erwartet die Klasse Generation mit den vorgegebenen Methoden im gleichen Paket
  • Sie können weitere Methoden wenn nötig implementieren
  • Das Hauptprogramm wird genau eine Instanz der Klasse Generation erzeugen.

Beim Berechnen der Nachbarn eines Feldes ist auf die Sonderfälle am Rand zu achten:

Anzahl NAchbarn

Weitere Hilfestellungen sind den Kommentaren zu entnehmen. Die Klasse kann natürlich auch ohne die Hilfestellung entwickelt werden. Das Feld kann initial zum Testen sehr klein (2) sein. Die Buttons werden dann erst nach dem Vergrößern des Fenster sichtbar. Eine Größe von 50x50 ist für aktuelle Rechner ausführbar. Pro Zelle werden etwa 10 Pixel benötigt. 

package block7;

public class Generation {

// Hier ein Feld für alten Zustand deklarieren
// Hier ein Feld für neuen Zustand deklarieren
// die Felder muessen zweidimensional, vom Typ boolean sein, quadratisch sein
/**
* Groesse des quadratischen Feldes
*/
// Variable für Groesse des Feldes anlegen. Empfohlen 50 ==> GUI benötigt dann etwa 500 Pixel

/**
* Anlegen aller benoetigten Felder mit Initialwerten
* Alle Feldelemente sollen mit dem Zustand "false" = leer versehen sein
*/
public Generation() {
// Initialisieren sie die beiden Felder
// alle Felder sollen den Zustand "false" haben. Dies ist ein leeres Feld
}

/**
*
* @return Kantenlaenge des quadratischen Felds
*/
public int groesse() {return 0;} //Richtigen Wert zurueckgeben!!

/**
* Berechnen einer neuen Generation.
* Legen Sie ein neues Feld an. Berechnen Sie den neuen Zustand
* aller Feldelement aus dem alten Feld
*/
void neueGeneration() {
// Tipps:
// Weisen Sie die aktuelle Generation auf die alte zu
// Erzeugen oder wiederverwenden Sie ein Feld für eine neue Generation
// Nutzen Sie eine doppelt geschachtelte Schleife zum Ueberstrichen des aktuellen Felds
// Zaehlen Sie die Nachbarn der aktuellen Position in der alten Generation
// Achten Sie auf die Feldgrenzen!!
// Setzen Sie den Wert des aktuellen Felds auf "true" falls ein Objekt erhalten oder erzeugt werden soll
// Setzen Sie dem Wert des aktuellen Felds auf "false" falls kein Objekt in der neuen Generation existieren soll

}

/**
* Das Feld mit den aktuellen Werten
* @return
*/
public boolean[][] status() {return null;} // Hier das aktuelle Feld zurückgeben

}

Klasse SpielDesLebens.java

Das Hauptprogramm. Achten Sie auf die Paketstruktur!

Beim vorgebenen Paket kann das Programm mit dem folgenden Befehl gestartet werden

$ java block7.SpielDesLebens

 

package block7;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.JButton;
import javax.swing.Icon;
import javax.swing.*;

public class SpielDesLebens extends JApplet implements Runnable {
private int size;
private int xRaster=10;
private int yRaster=10;
private Generation gen;
private JButton[][] buttonFeld;
private static boolean appletMode = true;
private static boolean autoMode = false;
private ImageIcon belegtIcon;
private ImageIcon freiIcon;
private static SpielDesLebens myself;
private int sleeptime = 2000; // Millisekunden im Automode

public class Zelle extends JButton {
public int x;
public int y;

public Zelle (Icon ic, int x, int y) {
super(ic);
this.x=x;
this.y=y;
}
}
/**
* Der Konstruktor ohne Argumente wird nur beim einem Start als Applet
* benutzt. Hier wird ein Applet mit einem Grid erzeugt.
*/
public SpielDesLebens() {
erzeugeIcons();
myself=this;
gen = new Generation();
size = gen.groesse();
JFrame f = null;
if (!appletMode) f = new JFrame("Game");
JPanel jp = new JPanel();
jp.setLayout(new GridLayout(size, size));
buttonFeld = new JButton[size][size];
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
buttonFeld[i][j] = createButton(i, j);
jp.add(buttonFeld[i][j]);
}
}
JButton naechste = new JButton(">");
naechste.setToolTipText("Erzeuge nächste Generation");
naechste.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) { nextGen();}
} // Ende innere Klasse
);
JButton auto = new JButton(">>>");
auto.setToolTipText("Starte Film");
auto.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
sleeptime /=2; // Verdoppeln der Geschwindigkeit
if (!autoMode) {
autoMode=true;
Thread t1 = new Thread(SpielDesLebens.myself);
t1.start();
}
}
} // Ende innere Klasse
);
JButton stop = new JButton("Stop");
stop.setToolTipText("Stoppe Film");
stop.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
autoMode=false;
sleeptime=4000;
}
} // Ende innere Klasse
);
JPanel buttonPanel = new JPanel();
buttonPanel.add(naechste);
buttonPanel.add(auto);
buttonPanel.add(stop);
Container co;

if (!appletMode) co =f;
else co=this;

co.setLayout(new BorderLayout());
co.add(jp,BorderLayout.CENTER);
co.add(buttonPanel,BorderLayout.SOUTH);
co.setPreferredSize(new Dimension(size * (xRaster+3),size * (yRaster+3)));

if (!appletMode) {
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
}
/**
* Starten der Anwendung als eigenständiges Programm
* @param args
*/
public static void main(String[] args) {
appletMode = false;
SpielDesLebens k = new SpielDesLebens();
}
/**
* Erzeugen eines JButtons für jede Zelle des Feldes
* @param xx x Koordinate im Feld
* @param yy y Koordinate im Feld
* @return
*/
private JButton createButton(int xx, int yy) {
JButton myButton = new Zelle(freiIcon,xx,yy);
myButton.setToolTipText(("("+xx+","+yy+")"));
myButton.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent ae) {
if(!autoMode) {
Zelle f = (Zelle) ae.getSource();
//System.out.println("Action auf" +f.x + " " + f.y);
boolean[][] g = gen.status();
if (g[f.x][f.y]) {
f.setIcon(freiIcon);
g[f.x][f.y]=false;
}
else {
f.setIcon(belegtIcon);
g[f.x][f.y]=true;
}
f.updateUI();
}
}
} // Ende innere Klasse
);
return myButton;
}
/**
* Erzeuge die beiden Ikonen für eine freies und ein belegtes Feld
*/
public void erzeugeIcons() {
BufferedImage belegt =
new BufferedImage(xRaster, yRaster, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g = belegt.createGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, xRaster-1, yRaster-1);
g.setColor(Color.black);
g.fillOval(1, 1, xRaster-2, yRaster-2);
g.dispose();
belegtIcon = new ImageIcon(belegt);
BufferedImage frei =
new BufferedImage(xRaster, yRaster, BufferedImage.TYPE_4BYTE_ABGR);
g = frei.createGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, xRaster-1, yRaster-1);
g.dispose();
freiIcon = new ImageIcon(frei);
}

/**
* Erzeugen einer neuen Generation und Abgleich der JButtons mit neuen
* Ikonen
*/
private void nextGen() {
gen.neueGeneration();
boolean[][] stat = gen.status();
for (int i = 0; i < size; i++)
for (int j = 0; j < size; j++)
if (stat[i][j]) buttonFeld[i][j].setIcon(belegtIcon);
else buttonFeld[i][j].setIcon(freiIcon);
}

/**
* Lasse neue Generationen automatisiert in einem eigenen Thread
* erzeugen
*/
public void run() {
try {
while (autoMode) {
Thread.sleep(sleeptime);
nextGen();
}
} catch (InterruptedException e) {
}
}
}

Lösung der Aufgabe in Zwischenschritten

1. Die Anwendung übersetzt und läuft fehlerfrei

  • Variable für Feldgröße als Objektvariable dekarieren und mit Konstante belegen 
    • Wert 5 wählen; Fenster per Hand vergrößern
  • Methode zur Rückgabe der Feldgröße implementieren
  • Zweidimensionales Feld als Objektvariable für aktuelles Feld deklarieren. Benutzen Sie den Typ boolean.
  • Im Konstruktor alle Zellen des Felds mit doppelt geschachtelter Schleife initialisieren
  • Methode zur Rückhabe des Felds implementieren
  • Methode neueGeneration() implementieren:
    • Trivialimplementierung die genau ein beliebiges aber festes Feldelement auf "true" setzt
  • Testen der Implementierung: Initialiserung des aktuellen Feldes kann so getestet werden

Ergebnis

Es wurde als Feldgröße 20 verwendet. Es wurde die Zelle [3,3] auf true gesetzt

Bildschirmausgabe
1. Nach Starten des Programms 2. Nach Klicken des ">" Button 3. Nach mehrfachem Kicken auf Zellen
nach Start Nach erstem Klick Nach dem Klicken auf Zellen

Der Benutzer sollte in der Lage sein, den Zustand von Zellen durch einfaches anklicken zu invertieren. Hier wurde im Schritt 3 ein "Happy Face" gezeichnet.

Die Position [3,3] sollte man jetzt auch mit einem Mausklick löschen können. Nach Klicken des ">" Button sollte das Pixel wieder gezeigt werden weil die Methode neueGeneration() aufgerufen wurde.

2. Zellen abhängig von anderen Zellen belegen: Methode neueGeneration()

Erweitern Sie die Methode neueGeneration()

  • Setzen Sie jedes Feldelement (x,y) auf "true" wenn das Element (x+1.y+1) belegt ("true") war. Hierdurch werden aus Punkten Striche die sich nach rechts unten fortpflanzen.
  • Sie benötigen hierfür eine doppelt geschachtelte Schleife die alle Feldelemente abarbeitet.
  • Achten Sie darauf, dass Sie keine Elemente auf Positionen die größer als die Feldgrenze sind!
  • Testen Sie die Anwendung: Jeder Punkt sollte in der Nachfolgegeneration einen neuen links oberhalb erhalten. Es steht pro Generation eine neue belegte Zelle links oberhalb.

Ergebnis

Es wurde als Feldgröße 20 verwendet. Es wurden Zellen nach dem Start des Programms mit der Maus markiert:

Bildschirmausgabe
1. Nach Starten des Programms und Markieren zweier Zellen 2. Nach Klicken des ">" Button 3. Nach zweitem Klicken des ">" Buttons
nach Start Nach erstem Klick Nach dem Klicken auf Zellen

3. Berechnen einer neuen Generation aus einer alten Generation

Man benötigt zu Lösung der Aufgabe zwei Felder. Das erste Feld enthält die Zellen der alten Generation. Das zweite Feld wird für die Belegung der neuen Generation benötigt. Mit nur einem Feld würde man nicht zwischen alter und neuer Generation entscheiden können. Man würde Zellen inkorrekt berechnen und belegen.

  • Deklarieren Sie eine weitere Objektvariable die auf das alte Feld zeigt
  • Initialisieren Sie das Feld in der gleichen Größe im Konstruktor. Belegen Sie alle Zellen mit dem Wert "false";
  • Erweitern Sie die Methode neueGeneration()
    • Referenzieren Sie mit dem Zeiger der alten Generation die aktuelle Generation.
    • Erzeugen sie ein Feld für die neue (aktuelle) Generation
    • Initialiseren Sie das neue Feld mit vernünftigen Werten.
    • Belegen Sie jedes Feld der neuen Generation mit "true" wenn der rechte, untere Nachbar der Vorgängergeneration existiert. Dieses Problem wurde schon in der vorgehenden Phase gelöst.
  • Testen Sie die Anwendung: Sie soll die gleichen Ausgaben produzieren
  • Setzen Sie die Größe des Felds wieder auf 50 (oder mehr)

Jetzt sollte das Umkopieren von neuen auf alte Generationen funktionieren. Alle Schleifen sollten fehlerfrei laufen.

4. Berechnen der korrekten Nachbarschaftsbeziehung in der Methode neueGeneration()

Erweitern Sie die Methode neueGeneration()

  • Zählen Sie für jedes Feld die Anzahl der Nachbarn. Achten Sie auf die Feldgrenzen. Prüfen Sie keine Feldelemente ab, die ausserhalb des Feldes liegen.
  • Bestimmen Sie anhand der Nachbarn und es dem Wert der alten Zelle den Zustand der aktuellen Zelle.

Ergebnis

Es wurde als Feldgröße 20 verwendet. Nach dem Start wurde mit der Maus ein "Glider", ein senkrechter Dreierbalken und ein Rechteck aus sechs Zellen gezeichnet.

Bildschirmausgabe
1. Nach Starten des Programms und Markieren diverser Zellen 2. Nach Klicken des ">" Button 3. Nach zweitem Klicken des ">" Buttons
nach Start Nach erstem Klick Nach dem Klicken auf Zellen

 7.4.5 Flughafenbeispiel mit Feldern

Klasse Flughafen

package airline.block7;

/**
*
* @author stsch
*/
public class Flughafen {
String name;
Flugzeug[] gate;
double treibstoffLager;

public Flughafen(int anzahlGates) {
gate = new Flugzeug[anzahlGates];
}

public void drucken() {
System.out.println("*** Unser Flughafen ***");
System.out.println("Flughafen " + name);
for (int i =0;i<gate.length;i++)
System.out.println("Am Gate " + i + ": " + gate[i]);
System.out.println("Treibstoff: " + treibstoffLager);
System.out.println("***********************");
}

public static void main(String[] args) {

Flughafen pad = new Flughafen(6);
pad.name="Paderborn";
pad.treibstoffLager = 1000000;

pad.drucken();

// Boeing 747, https://de.wikipedia.org/wiki/Boeing_747#747-400
Flugzeug lh1 = new Flugzeug(40000, 400000);
lh1.kennzeichen ="D-ABTL";
lh1.einsteigen(3);
System.out.println("Unsere grosse Gesellschaft" + Flugzeug.meineLG());

double aktuellesGewicht = lh1.gewicht();
System.out.println("aktuelle Gewicht von lh1: " + aktuellesGewicht);

pad.gate[1] = lh1;

// Airbus A380 https://de.wikipedia.org/wiki/Airbus_A380#A380-800
Flugzeug lh2 = new Flugzeug(40000, 400000, "D-AIMA");

lh2.einsteigen(11);
lh2.einsteigen();
lh2.einsteigen(4);
// Wir wollen wieder austteigen
lh2.aussteigen(5);

pad.gate[2] = lh2;


System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate[1].kennzeichen +
" Passagiere: " + pad.gate[1].getPassagiere() +
" akt. Gewicht: " + pad.gate[1].gewicht());
System.out.println("Am Gate 2: " + pad.gate[2].kennzeichen +
" Passagiere: " + pad.gate[2].getPassagiere() +
" akt. Gewicht: " + pad.gate[2].gewicht());
pad.drucken();

}
}

Klasse Flugzeug

package airline.block7;

/**
* Das ist ein Flugzeug des 7.ten Blocks
* @author Rudi Ratlos
*/
public class Flugzeug {

final static double durchschnittsgewicht = 75;
String kennzeichen;
private int passagiere;
int maxPassagiere = 100;
final double maximalesGewicht;
final double leerGewicht;

public int getPassagiere() {
return passagiere;
}

public Flugzeug(double minGewicht, double maxGewicht) {
System.out.println("Ich baue jetzt ein Flugzeug");
if (minGewicht > 0) leerGewicht = minGewicht;
else leerGewicht = 0;
if ((maxGewicht > 0) && (maxGewicht>=leerGewicht))
maximalesGewicht = maxGewicht;
else maximalesGewicht = leerGewicht;
passagiere = 0;
}

public Flugzeug(double minGewicht, double maxGewicht, String kennz) {
this(minGewicht,maxGewicht);
kennzeichen = kennz;
}

/**
* Einsteigen eines einzelnen Passagiers
*/
public void einsteigen() {
if (passagiere >= maxPassagiere) { // Zuviele Einsteiger
System.out.println("Fehler: Einsteigen verweigert ");
} else { // passt schon
passagiere++;
}
}

/**
* Einsteigen einer vorgegebenen Anzahl Passagiere.
* und sie funktioniert auch!
* Wenn zuviele einsteigen möchten, darf keiner einsteigen!
* @param einsteiger Anzahl der Passagiere die einsteigen.
*/
public void einsteigen(int einsteiger) {
if (passagiere+einsteiger > maxPassagiere) {
System.out.println("Fehler: Einsteigen verweigert ");
} else {
System.out.println("Ich lasse " + einsteiger + " Passagiere eingestiegen");
einsteigen(); // Einer steigt ein
if (einsteiger > 1)
einsteigen(einsteiger-1); //Der Rest steigt ein
}
}

public void aussteigen(final int aussteiger) {
if (passagiere >= aussteiger) {
passagiere -= aussteiger;
} else {
System.out.println("Fehler: Flugzeug ist leer... ");
}
} // Ende der Methode aussteigen

/**
* berechnet das aktuelle Gewicht des Flugzeugs
* @return aktuelle Gewicht
*/
public double gewicht() {
//double g = leerGewicht+passagiere*durchschnittsgewicht;
return leerGewicht+passagiere*durchschnittsgewicht;
}

public static String meineLG() { return "Lufthansa";}

}
 

 

7.5 Lösungen

7.5.1 Telefonbuchanwendung (1)

package block7;
/**
*
* @author stsch
*/
public class Telefonbuch1 {
private String[] vorname;
private String[] nachname;
private String[] telefonnummer;
/**
* Anlegen eines Telefonbuchs für 4 EInträge
*/
public Telefonbuch1 () {
// Telefonbuch mit initial 4 Einträgen
vorname = new String[4];
nachname = new String[4];
telefonnummer = new String[4];
}
/**
* Einfügen einer neuen Telefonnummer mit automatischem
* Vergrössern um 50% des Telefonbuchs
* @param vn Vorname
* @param nn Nachname
* @param nr Telefonnummer
*/
public void einfuegen(String vn, String nn, String nr) {
//Leere Einträge haben keine Telefonummer!
int pos=0; //Suchposition
while ((pos<telefonnummer.length)
&& !(telefonnummer[pos]==null)
) pos++;
if (pos>=telefonnummer.length) // kein freier Eintrag!
{ // Feld um 50% vergroessern und alles umkopieren
String[] tempFeld;
tempFeld = new String[vorname.length*3/2];
System.arraycopy(vorname,0,tempFeld,0,vorname.length);
vorname = tempFeld;

tempFeld = new String[nachname.length*3/2];
System.arraycopy(nachname,0,tempFeld,0,nachname.length);
nachname = tempFeld;

tempFeld = new String[telefonnummer.length*3/2];
System.arraycopy(telefonnummer,0,tempFeld,0,telefonnummer.length);
telefonnummer = tempFeld;
// Jetzt ist Platz um etwas pos zu speichern!
}
vorname[pos]= vn;
nachname[pos]= nn;
telefonnummer[pos] = nr;
}
/**
* Loeschen eines Datensatzes wenn alle Parameter identisch sind
* @param vn vorname
* @param nn
* @param nr
*/
public void loesche(String vn, String nn, String nr) {
//Leere Einträge haben keine Telefonummer!
int pos=0; //Suchposition
while ((pos<telefonnummer.length) && (telefonnummer[pos]!=null)
&& !(telefonnummer[pos].equals(nr))
&& !(vorname[pos].equals(vn))
&& !(nachname[pos].equals(nn))
) pos++;
if (pos<telefonnummer.length) // Kandidat zum loeschen steht aus pos
{ // Suche hoechsten Eintrag
int loesche = pos;
while ((pos<telefonnummer.length)
&& (telefonnummer[pos]!=null)
) pos++;
pos--; // Einmal zurücksetzen
// Höchsten Datensatz umkopieren und dann ausnullen
// Der alte Datensatz wird dereferenziert
vorname[loesche] = vorname[pos];
vorname[pos] = null;
nachname[loesche] = nachname[pos];
nachname[pos] = null;
telefonnummer[loesche] = telefonnummer[pos]; telefonnummer[pos]=null;
}
}
/**
* Ausdrucken des Telefonbuchs
*/
public void drucken() {
System.out.println("Telefonbuch. Groesse: " + telefonnummer.length);
for (int i=0; i<telefonnummer.length; i++ ){
System.out.print("[" + i + "]: ");
System.out.print( vorname[i] + " | ");
System.out.print( nachname[i] + " | ");
System.out.println( telefonnummer[i] + " |");
}
}
/**
* Testroutine
*/
public static void test() {
Telefonbuch1 buch = new Telefonbuch1();
System.out.println("*** Leeres Telefonbuch ***");
buch.einfuegen("Manuel","Neuer","0171 1");
buch.einfuegen("Philipp","Lahm","0171 2");
buch.einfuegen("Jérome","Boateng","0171 3");
buch.einfuegen("Mats","Hummels","0171 4");
buch.einfuegen("Benedikt","Höwedes","0171 5");
buch.einfuegen("Christoph","Kramer","0171 6");
buch.einfuegen("Bastian","Schweinsteiger","0171 8");
buch.einfuegen("Thomas","Müller","0171 9");
buch.einfuegen("Toni","Kroos","0171 10");
buch.einfuegen("Per","Mertesacker","0171 11");
buch.einfuegen("Miroslav","Klose","017 12");
//
System.out.println("*** Ganze Mannschaft im Telefonbuch ***");
buch.drucken();
System.out.println("*** Kramer raus, Schürrle rein ***");
buch.loesche("Christoph","Kramer","0171 6");
buch.einfuegen("André","Schürrle","0171 7");
buch.drucken();
System.out.println("*** Klose raus, Götze rein ***");
buch.einfuegen("Miroslav","Klose","017 12");
buch.einfuegen("Mario","Götze","0171 13");
buch.drucken();
System.out.println("... und Weltmeister");
}
/**
* Hauptprogramm
* @param args
*/
public static void main(String[] args) {
test();
}
}

7.5.2 Telefonbuch (2)

Klasse Person

package block7;
/**
*
* @author stsch
*/
public class Person {
final public String vorname;
final public String nachname;
final public String telefonnummer;
/**
* Der Konstruktor erlaubt das Belegen der Attribute. Sie können
* spaeter nicht mehr geändert werden
* @param vn Vorname
* @param nn Nachname
* @param nr Telefonnummer
*/
public Person (String vn, String nn, String nr) {
vorname = vn;
nachname = nn;
telefonnummer = nr;
}
/**
* Standardisierte Vergeleichsoperation in Java
* @param o Das Objekt mit dem verglichen werden soll
* @return wahr wenn Objekte gleich sind
*/
@Override
public boolean equals(Object o) {
Person p = (Person) o;
return ((vorname.equals(p.vorname))
&&(nachname.equals(p.nachname))
&&(telefonnummer.equals(p.telefonnummer)));
}
/**
* Standardisierte Methode zum Konvertieren eines Objekts in eine
* Zeichenkett
* @return Das Objekt in einer Repräsentation als Zeichenkette
*/
@Override
public String toString(){
return (" " +vorname + " | " + nachname + " | " + telefonnummer +" |");
}
}

Klasse Telefonbuch2 (Hauptprogramm)

package block7;
/**
 *
 * @author stsch
 */
public class Telefonbuch2 {
    private Person[] leute;
    /**
     * Anlegen eines Telefonbuchs für 4 EInträge
     */
    public Telefonbuch2 () {
        // Telefonbuch mit initial 4 Einträgen
        leute = new Person[4];
    }
    /**
     * Einfügen einer neuen Telefonnummer mit automatischem
     * Vergrössern um 50% des Telefonbuchs
     * @param p Person
     */
    public void einfuegen(Person p) {
        //Leere Einträge haben keine Telefonummer!
        int pos=0; //Suchposition
        while ((pos<leute.length)
               && (leute[pos]!=null)
                ) pos++;
        if (pos>=leute.length) // kein freier Eintrag!
        { // Feld um 50% vergroessern und alles umkopieren
            Person[] tempFeld; 
            tempFeld = new Person[leute.length*3/2];
            System.arraycopy(leute,0,tempFeld,0,leute.length);
            leute = tempFeld;
            // Jetzt ist Platz um etwas pos zu speichern!
        }
        leute[pos]= p;
    }
    /**
     * Loeschen eines Datensatzes wenn alle Parameter identisch sind
     * @param p zu löschende Person
     */
    public void loesche(Person p) {
        //Leere Einträge haben keine Telefonummer!
        int pos=0; //Suchposition
        while ((pos<leute.length) // Noch nicht am Ende des Feldes
                && (leute[pos]!=null) // Es gibt einen Eintrag
                && !(leute[pos].equals(p)) ) // Er passt nicht
            pos++;
        if (pos<leute.length) // Kandidat zum loeschen steht aus pos
        { // Suche hoechsten Eintrag
            int loesche = pos;
            while ((pos<leute.length)
               && (leute[pos]!=null)
                    ) pos++;
            pos--; // Einmal zurücksetzen
            // Höchsten Datensatz umkopieren und dann ausnullen
            // Der alte Datensatz wird dereferenziert
            leute[loesche] = leute[pos];
            leute[pos] = null;
        }
    }
    /**
     * Ausdrucken des Telefonbuchs
     */
    public void drucken() {
        System.out.println("Telefonbuch. Groesse: " + leute.length);
        for (int i=0; i< leute.length; i++)
            System.out.println("[" + i + "]: " +leute[i]);
    }
    /**
     * Testroutine
     */
    public static void test() {
        Telefonbuch2 buch = new Telefonbuch2();
        System.out.println("*** Leeres Telefonbuch ***");
        buch.einfuegen(new Person("Manuel","Neuer","0171 1"));
        buch.einfuegen(new Person("Philipp","Lahm","0171 2"));
        buch.einfuegen(new Person("Jérome","Boateng","0171 3"));
        buch.einfuegen(new Person("Mats","Hummels","0171 4"));
        buch.einfuegen(new Person("Benedikt","Höwedes","0171 5"));
        buch.einfuegen(new Person("Christoph","Kramer","0171 6"));
        buch.einfuegen(new Person("Bastian","Schweinsteiger","0171 8"));
        buch.einfuegen(new Person("Thomas","Müller","0171 9"));
        buch.einfuegen(new Person("Toni","Kroos","0171 10"));
        buch.einfuegen(new Person("Per","Mertesacker","0171 11"));
        buch.einfuegen(new Person("Miroslav","Klose","017 12"));
        //
        System.out.println("*** Ganze Mannschaft im Telefonbuch ***");
        buch.drucken();
        System.out.println("*** Kramer raus, Schürrle rein ***");
        buch.loesche(new Person("Christoph","Kramer","0171 6"));
        buch.einfuegen(new Person("André","Schürrle","0171 7"));
        buch.drucken();
        System.out.println("*** Klose raus, Götze rein ***");
        buch.einfuegen(new Person("Miroslav","Klose","017 12"));
        buch.einfuegen(new Person("Mario","Götze","0171 13"));
        buch.drucken();
        System.out.println("... und Weltmeister");
    }
    /**
     * Hauptprogramm
     * @param args 
     */
    public static void main(String[] args) {
    test();
    }

7.5.3 Zufallszahlengenerator

/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package block7;


/**
*
* @author stsch
*/
public class Zufallszahlen {
/**
* Drucke dreiminensionale Felder
* @param feld
*/
public static void feldDrucken(int[][][] feld) {
for (int i=0; i<feld.length; i++) {
System.out.println("*** Kubusebene["+i+"] ***");
feldDrucken(feld[i]);
}
}
/**
* Drucke zweidinensionale Felder
* @param feld
*/
public static void feldDrucken(int[][] feld) {
for (int i=0; i<feld.length; i++) {
System.out.print("["+i+"]: ");
feldDrucken(feld[i]);
System.out.println(""); // Zeilenumbruch provozieren
}
}
/**
* Drucke eindinensionale Felder
* @param feld
*/
public static void feldDrucken(int[] feld) {
for (int i=0; i<feld.length; i++) {
System.out.print(" "+feld[i]+" |"); // kein Zeilenumbruch
}
}
/**
* Suche die am häufigsten vorkommende Zahl
* @param feld Feld mit Häufigkeiten
* @return
*/
public static int haeufigsteZahl(int[][][] feld) {
int max = -1;
int ergebnis = 0;
for (int i=0; i<feld.length; i++)
for (int j=0; j<feld[i].length; j++)
for (int k=0; k<feld[i][j].length;k++)
if (feld[i][j][k] > max) {
max = feld[i][j][k];
ergebnis=i*100+j*10+k;
}
System.out.println("Häufigste Vorkommen: " + max);
return ergebnis;
}
/**
* Suche die am seltensten vorkommende Zahl
* @param feld Feld mit Häufigkeiten
* @return
*/
public static int seltensteZahl(int[][][] feld) {
int min = Integer.MAX_VALUE;
int ergebnis = 0;
for (int i=0; i<feld.length; i++)
for (int j=0; j<feld[i].length; j++)
for (int k=0; k<feld[i][j].length;k++)
if (feld[i][j][k] < min) {
min = feld[i][j][k];
ergebnis=i*100+j*10+k;
}
System.out.println("Seltenstes Vorkommen: " + min);
return ergebnis;
}
public static void main(String[] args) {
int sampleSize = 1000000;
int[] sample = new int[sampleSize];
System.out.println("Feld mit " + sampleSize + " Zellen angelegt." );
for (int i=0; i<1000000; i++)
sample[i] = (int)(Math.random()* 1000D);
System.out.println("Feld mit " + sampleSize + " Zufallszahlen belegt." );
int[][][] verteilung = new int[10][10][10];
for (int wert: sample) {
verteilung[(wert/100)%10][(wert/10)%10][wert%10]++;
}
System.out.println("Verteilungsfeld belegt mit " + sampleSize + " Zufallszahlen." );
feldDrucken(verteilung);
System.out.println("Suche häufigst vorkommende Zahl");
System.out.println("Häufigste Zahl " + haeufigsteZahl(verteilung));
System.out.println("Suche seltenst vorkommende Zahl");
System.out.println("Seltenste Zahl " + seltensteZahl(verteilung));
}
}

7.5.4 Conway's Spiel des Lebens

package block7;

public class Generation {
private boolean[][] alt;
private boolean[][] aktuell;
/**
* Groesse des quadratischen Feldes
*/
private int size = 50;;

/**
* Anlegen aller benoetigten Felder mit Initialwerten
* Alle Feldelemente sollen mit dem Zustand "false" = leer versehen sein
*/
public Generation() {
aktuell= new boolean[size][size];
alt = new boolean[size][size];
for (int i=0; i<size; i++)
for (int j=0; j<size; j++) {
aktuell[i][j]= false;
alt[i][j]= false;
}
}

/**
*
* @return Kantenlaenge des quadratischen Felds
*/
public int groesse() {return size;}

/**
* Berechnen einer neuen Generation.
* Legen Sie ein neues Feld an. Berechnen Sie den neuen Zustand
* aller Feldelement aus dem alten Feld
*/
void neueGeneration() {
alt=aktuell;
aktuell= new boolean[size][size];
for (int i=0; i<size; i++)
for (int j=0; j<size; j++) {
// Zaehle Nachbarn
int nachbar=0;
if ((i>0) && alt[i-1][j]) nachbar++; //links
if ((i+1<size)&& alt[i+1][j]) nachbar++; //rechts
if ( (j>0) && alt[i][j-1]) nachbar++; //oben
if ( (j+1<size)&& alt[i][j+1]) nachbar++; //unten
if ((i>0) &&(j>0) && alt[i-1][j-1])nachbar++; //links,oben
if ((i>0) &&(j+1<size) && alt[i-1][j+1])nachbar++; //links, unten
if ((i+1<size)&&(j>0) && alt[i+1][j-1])nachbar++; //rechts, oben
if ((i+1<size)&&(j+1<size) && alt[i+1][j+1])nachbar++; //rechts, unten

// Übernehmen des alten Status als Default
aktuell[i][j] = alt[i][j];
// Geburt einer neuen Zelle
if((!alt[i][j]) && (nachbar==3)) aktuell[i][j]=true;
// Tod einer alten Zelle wegen Einsamkeit
if((alt[i][j]) && (nachbar<2)) aktuell[i][j]=false;
// Tod einer alten Zelle wegen Überbevölerung
if((alt[i][j]) && (nachbar>3)) aktuell[i][j]=false;
}

}

/**
* Das Feld mit den aktuellen Werten
* @return
*/
public boolean[][] status() {return aktuell;}

}

 

7.6 Lernziele

Am Ende dieses Blocks können Sie:

  • ... die Javasyntax zum Umgang mit ein- und mehrdimensionalen Feldern beschreiben und anwenden.
  • ... die Lebensdauer eines Feldes und seiner Unterobjekte bestimmen.
  • ... die Index eines Felds benutzen um Daten zu lesen und zu schreiben.
  • ... die Länge eines Felds bstimmern und Unter- bzw. Überläufe beim Zugriff auf das Feld vermeiden.
  • ... das Objektmodell von Feldern bei Zuweisungen und Kopien von Feldern und Feldvariablen erklären und anwenden.
  • ... Felder als Parameter von Methoden benutzen und das "Call by Reference" Prinzip bei deren Übergabe erklären.
  • ... Methoden implementieren und nutzen die eine variable Anzahl von Argumenten besitzen
  • ... mit mehrdimensionalen Feldern umgehen (initialisieren, auslesen, belegen, Über- und Unterläufe vermeiden)

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten:

8. Vererbung

Vererbung ist ein wichtiger Bestandteil der objektorientierten Programmierung. Vererbung ergänzt die bisher vorgestellten Konzepte der

um ein weiteres Strukturierungsmittel.

Der erste Abschnitt beschäftigt sich mit dern Konzepten der Vererbung, die folgenden beschreiben die Implementierung in Java.

8.1 Das Konzept der Vererbung

Die Ziele die mit Vererbung in der objektorientierten Programmierung verfolgt werden sind:

  • Wiederverwendung
    • eine Klasse vererbt Eigenschaften an eine andere.
    • Dies bedeutet: Man nutzt Daten und Methoden einer Klasse 
  • Modellierung in Hierarchien
    • Hierarchien sind gängige Konzepte des Alltagsleben und finden sich in der Begrifflichkeit des Alltagslebens wieder. Eine Modellierung die Hierarchien unterstützt und vereinfacht das Verständnis und die Kommunikation.
    • Hierarchien sind Konzepte die stark bei der Abgrenzung zwischen ähnlichen Dingen helfen

Beispiel einer Vererbungshierarchie:

Klassenhierarchie

Die gleiche Begriffshierarchie lässt sich auch als Mengendiagramm darstellen:

Mengendiagramm

Alle Mitglieder einer Teilmenge haben die gleichen Eigenschaften.

Das Mengendiagramm verdeutlicht, dass es sich bei der Vererbungshierarchie um eine "Ist-ein" Relation handelt.

 Vererbung und Klassen

Die Konzepte der Vererbungshierarchien lässt sich leicht auf Klassen abbilden

  • Unterklassen können von Oberklassen Methoden und Attribute erben und direkt in Ihrer Implementierung benutzen
  • Typischerweise haben Unterklassen nur direkten Zugriff auf öffentliche Methoden und Attribute
  • Unterklassen können selbst zusätzliche Attribute und Methoden implementieren und so spezialisiertere Aufgaben übernehmen
  • Instanzen von Unterklassen müssen sich wie Instanzen der Oberklassen verhalten. Da die Instanzen der Unterklasse immer eine Teilmenge der Instanzen der Oberklasse sind. Sie dürfen jedoch zusätzliche Eigenschaften implementieren

Das Diagramm rechts verdeutlicht die Beziehungen zwischen den Klassen, ihrer Bedeutung und der Typisierung. 

Sub- Supertypbeziehung

Ein- und Mehrfachvererbung

Programmiersprachen wie C++ erlauben die Benutzung von Mehrfachvererbung. Die Sprache Java erlaubt jedoch nur eine einfache Vererbung.

  • Einfachvererbung: Eine Unterklasse erbt von genau einer Oberklasse
  • Mehrfachvererbung: Eine Unterklasse erbt von mehreren Oberklassen

Das Konzept der Mehrfachvererbung durchbricht zu einem gewissen Grad das Konzept der Spezialisierung-Generalisierung. Bei der Einfachvererbung sind Personen Mitarbeiter oder Berater aber nicht beides gleichzeitig. Bei der Mehrfachvererbung mit einer einzelnen Basisklasse werden Unterklassen zuerst als disjunkte Klassen definiert. Später sollen Unter-Unterklassen jedoch die gleichen Eigenschaften über verschieden Vererbungspfade erben.

Anmerkung

Mehrfachvererbung erhöht die Mächtigkeit sowie die Komplexität einer Sprache erheblich. Es kann hier leicht zu Effekten kommen die ein Entwickler nicht als intuitiv empfindet. Das Risiko des Fehlverhaltens einer Anwendung steigt hierdurch.

Beim Entwurf der Sprache Java wurde bewusst auf Mehrfachvererbung verzichtet um die Sprache einfach zu halten und um die Implementierung eines effizienten Laufzeitsystems zu vereinfachen.

Die Java Interface (Schnittstellen) Technologie bietet eine "Hintertür" zur Implementierung von Fällen in denen eine Mehrfachvererbung wünschenswert ist.

8.2 Vererbung in Java

Die Vererbungsbeziehung wird in Java mit dem Schlüsselwort extends beschrieben. Beispiel:

class Mitarbeiter extends Person {
   /* Klassenimplementierung */
   }

Die Klasse Mitarbeiter ist eine Spezialisierung der Klasse Person. Sie erbt die öffentlichen Attribute und Methoden der Klasse Person.

Alle Klassen in Java erben direkt oder indirekt von der Java Basisklasse Object. Wird bei einer Klassendeklaration keine extends Klausel angegeben so wird die Klasse automatisch von der Klasse Object abgeleitet.

Die Klasse Object

... stellt für alle Javaklassen eine minimale Infrastruktur zur Verfügung:

  • die Möglichkeit dynamisch Objekte auf dem Heap anzulegen
  • eine Identität und die damit verbundene Möglichkeit zum Vergleich von Objekten
  • die Möglichkeit des automatischen Löschens wenn keine Referenz mehr auf eine Instanz zeigt.
  • Möglichkeit der Instrospektion (Dynamisches Erkunden der Struktur der Klasse)

Zugriff auf geerbte Methoden und Attribute

Oberklassen vererben öffentliche (public, protected) Methoden und Attribute an Unterklassen. Das heißt, dass sie wie eigene Attribute verwendet werden können.

Die Beispielklasse Person verfügt über:

  • private Attribute für Vor- und Nachnamen
  • öffentliche Methode zum Auslesen des Gesamtnamens
  • öffentliche Methode zum Setzen des Namens
  • ein öffentliches Attribut zum Verwalten des Alters
  • Einen Konstruktor zum Setzen des Namens

Die Oberklasse Person

public class Person {
    private String name;
    private String firstName;
    public int age;
    
    public Person(String ln, String fn) {
        name = ln; firstName = fn; }
    
    public Person() { this("Doe","John");}
    
    public void setName(String ln, String fn) {
        name = ln; firstName = fn; }
    public String fullName() {return (name + " " + firstName);}    
}

Die Klasse Employee soll jetzt die öffentlichen Eigenschaften der Oberklasse Person nutzen. So kann eine redundante Programmierung der schon existierenden Funktionalität vermieden werden.

Die Unterklasse Employee

public class Employee extends Person {
    private String employeeId;
    public Employee(String ln, String fn, String EmpId, int a) {
        super(ln,fn); // Java ruft hier den Konstruktor der Oberklasse auf
        employeeId = EmpId;
        age = a;
    }
    public String getEmployeeId() {return employeeId;}
    public String printAll() {
        return (
                fullName() + " " +
                employeeId + " " +
                age);}
    }

Benutzung der Klasse

Die so implementierte Klasse kann wie folgt verwendet werden:

public class MainEmployee {
    public static void main(String[] args) {
        Employee ceo = new Employee("Doe","Jane", "1", 25);
        Employee cto = new Employee("Miller","John","2", 30);

        cto.age++;
        System.out.println(ceo.printAll());
        System.out.println(cto.printAll());
    }
}

Dies ergibt die Konsolenausgabe:

Doe Jane 1 25
Miller John 2 31 

Alle öffentlichen Attribute und Methoden (Beispiel age) der Klasse Person können in der Klasse Employee genutzt werden als ob es eigene/lokale sind.

Die Vererbung funktioniert jedoch nicht rückwärts. Man kann einer Instanz von Person keine Angestelltennummer (employeeId) zuweisen. Sie existiert in der Klasse Person nicht.

"Vertragsrecht": Alle Instanzen von Employee müssen sich auch genau wie Instanzen von Person verhalten. Sie sind auch Instanzen der Klasse Person. Andere Konsumenten im Programm verlassen sich auf dieses Verhalten, diesen "Vertrag". Dies bedeutet:

  • Die Unterklasse muss eventuell erzwungene Intialisierungen der Oberklasse achten in dem Sie rekursiv deren Konstruktoren aufruft.
  • Veröffentlichte Attribute und Methoden können/sollen nicht mehr blockiert werden. Beispiel: In einem Unternehmen darf das Alter aufgrund von rechtlichen Bestimmungen nicht veröffentlicht werden

Vererbung in UML

Implementierung ohne Vererbung Implementierung mit Vererbung

Hier eine Implementierung der Klasse Employee ohne Vererbung. Die Implementierung entspricht der rechten Implementierung mit Vererbung. Die Methoden der Klasse haben hier aber direkten Zugriff auf die privaten Attribute name, firstName.

 

Eine Implementierung der Klasse Employee mit Vererbung der Klasse Person Vererbung

Vererbung privater Attribute und Methoden

Private Attribute werden nicht vererbt!

Sie sind nur innerhalb der Klasse sichtbar. Eine Unterklasse hat keinen priviligierten Zugriff auf private Methoden. Sie werden nicht vererbt. Eine Unterklasse muss wie jede andere Klasse über vorhandene öffentliche Zugriffsmethoden auf die privaten Attribute zugreifen.

Vererbung und Konstruktoren

Konstruktoren werden in Java nicht vererbt. Man Sie nur indirekt über den new() Operator für eine gegebene Klasse aufrufen.

Konstruktoren müssen für jede Unterklasse implementiert werden. Die Konstruktoren der Unterklasse müssen jedoch in der Lage sein existierende Konstruktoren der Oberklasse aufzurufen, da sie für die Initialisierung der Oberklasse notwendig sind. Hierzu wird das Schlüsselwort super() verwendet. 

Hiermit kann man jetzt die Klasse Employee() eleganter und robuster Initialisieren

public class Employee extends Person {
    private String employeeId;
    public Employee(String ln, String fn, String EmpId, int a) {
        super(fn,ln); // Java ruft hier den Konstruktor der Oberklasse auf
        employeeId = EmpId;
        age = a;
    }
    public void printAll() {
        System.out.println (
                fullName() + " " +
                employeeId + " " +
                age);
    }

Wichtig: Der Aufruf des Konstruktors der Oberklasse muss als erster Befehl im Konstruktor der Unterklasse stehen. Die Attribute der Oberklasse müssen als erstes initialisiert werden, bevor die Attribute der Unterklasse initialisiert werden. Nur so kann gewährleistet werden dass sich Instanzen der Oberklasse korrekt verhalten. Würde der Aufruf des Konstruktors der Oberklasse nicht als erstes geschehen, könnte die Unterklasse die Oberklasse inkorrekt manipulieren.

Unterklassenkonstruktoren ohne super() Aufruf

Konstruktoren müssen nicht einen bestimmten Konstruktor der Oberklasse explizit aufrufen.

Wird kein super() Aufruf implementiert, so generiert der Übersetzer automatisch einen super() Aufruf der den Standard(default)konstruktor aufruft.

Hiermit wird immer ein vollständiges Durchlaufen der Klassenhierachie beim Initialisieren gewährleistet.

Besitzt die Oberklasse nur Konstruktoren mit Übergabeparameter so muss einer dieser Konstruktoren über super() aufgerufen werden.

Im folgenden Beispiel kann man die Initialisierungskette einer Klasse C sehen die aus B abgeleitet wird. B wird aus A abgeleitet:

public class A {
    public A() {System.out.println("Constructor A called");}
}

public class B extends A{
    public B() {System.out.println("Constructor B called");}
}

public class C extends B {
    public C() {System.out.println("Constructor C called");}
}

Das erzeugen einer Instanz von C:

C c = new C();

Triggert die folgenden Ausgaben:

Constructor A called
Constructor B called
Constructor C called

 

8.3 Überschreiben (Overriding)

Das im vorgehenden Abschnitt vorgestellte Konzept des Vererbens ging immer von einer Erweiterung der Klasse aus. Das heißt mehr und mehr Attribute und Methoden kommen hinzu.

Es kommt jedoch vor, das eine spezialisierte Klasse eine existierende Methode der Oberklasse "verfeinern" möchte. Dieses Konzept der Implementierung einer Methode oder Attributs welches eine Methode oder Attribut aus der Oberklasse neu implementiert, wird in der objektorientierten Programmierung Überschreiben (Overriding) genannt.

Überschreiben von Methoden

Java erlaubt die Methode einer Oberklasse zu überschreiben, das bedeutet sie zu verdecken, wenn die folgenden Bedingungen für die Deklaration der Methode erfüllt sind

  • Namensgleicher Methodenname
  • Eingabeparameter sind von Anzahl, Reihenfolge und Typ identisch
  • Rückgabeparameter ist identisch
  • Zugriffsrechte der Methode der Oberklasse (public, protected, private) werden nicht eingeschränkt

Der Methodenrumpf kann mit einer beliebigen Implementierung ausgeführt werden. Mit dieser Technik kann eine Unterklasse eine eigene Implementierung für eine allgemeinere Methode einer Überklasse zur Verfügung stellen.

Im vorhergehenden Fall der Klasse Employee kann man eine spezialisierte Klasse Manager implementieren, die ein zusätzliches Budget verwalten kann. Das Budget soll ebenfalls mit der Methode printAll() ausgeben werden.

public class Manager extends Employee {
    public int budget;

    public Manager(String ln, String fn, String EmpId, int a, int b) {
        super(ln, fn,EmpId,a);
        budget = b;
    }
    public String printAll()
        { return (
                fullName() + " " +
                getEmployeeId() + " " +
                age + " " +
                budget); }
}

Das Hauptprogramm:

public class Main {
    public static void main(String[] args) {
        Employee ceo = new Employee("Doe","Jane", "1", 25);
        Employee cto = new Employee("Miller","John","2", 30);
        Employee man1 = new Manager("Johnson","Susan","3", 29, 30000);

        cto.age++;
        System.out.println(ceo.printAll());
        System.out.println(cto.printAll());
        System.out.println(man1.printAll());;
    }
}

...erzeugt die folgenden Ausgaben:

Doe Jane 1 25
Miller John 2 31
Johnson Susan 3 29 30000

Überschriebene Methoden sind miteinander verwandt und eine Benutzung der Methode der Oberklasse ist gewünscht um Codereplikation und Redundanz zu vermeiden. Hierfür existiert das Schlüsselwort super. Es erlaubt das Aufrufen der überschriebenen Methode mit der folgenden Syntax:

super.MethodenName(para_1, ..,para_n)

Im Fall der Klasse Manager kann man die printAll() Methode mit dem Schlüsselwort super vereinfachen:

public class Manager extends Employee {
    public int budget;

    public Manager(String ln, String fn, String EmpId, int a, int b) {
        super(ln, fn,EmpId,a);
        budget = b;
    }
    public String printAll()
        { return ( super.printAll() + " " + budget); }
}

Hinweis: Die Syntax  super.super.methodenname() ist nicht möglich. Man kann nicht die Methode einer Ober-Oberklasse unter Auslassung der Oberklasse aufrufen.

Suchalgorithmus der Laufzeitumgebung

Da in Java alle Klassen einzeln übersetzt werden können, kann man erst zur Laufzeit entscheiden welche Methode aufgerufen werden muss (dynamic invocation). Die Laufzeitumgebung geht bei jedem Aufruf wie folgt vor

  • Bestimme Typ des Objekts
  • Versuche Methode zum passenden Typ (Klasse) auszuführen
  • Versuche rekursiv in der Oberklasse die Methode auszuführen

Überschreiben von Attributen

Für das Überschreiben von Attributen gelten die gleichen Regeln wie für das Überschreiben von Methoden:

  • Namensgleicher Attributname
  • Zugriffsrechte des Attributs der Oberklasse (public, protected) werden nicht eingeschränkt

Private Attribute werden nicht vererbt. Sie sind der Unterklasse weder bekannt noch zugänglich!

Der Zugriff auf ein überschriebenes Attribut der Oberklasse geht symmetrisch zu den Methoden mit dem super Schlüsselwort. Beispiel:

a = super.a * 2;

Die @Overriding Annotation

Java kennt seit JDK 1.5 Annotationen. Annotationen sind Hinweise für den Übersetzer.

Es ist guter Stil wenn man einer Methode die Überschrieben wird die Annotation

@Overriding

voranstellt. @Overriding ist eine fest eingebaute Annotation, die den Übersetzer zu einer Fehlermeldung zwingt wenn die aufgewählte Methode nicht die Methode der Oberklasse überschreibt (Siehe Oracle Tutorial Annotationen).

Javaentwicklungsumgebungen tendieren eine Warnung zu Erzeugen, falls eine überschriebene Methode nicht mit dieser Annotation erzeugt wurde.

 

8.4 Abstrakte und finale Klassen

Finale Klassen und Methoden

Java bietet die Möglichkeit Klassen und Methoden zu implementieren die nicht mehr abgeleitet oder überschrieben werden dürfen. Hierzu dient das Schlüsselwort final.

Finale Methoden

Mit dem Schlüsselwort final man das Überschreiben der Methode in einer Unterklasse unterbinden. Ein typsiches Beispiel hierfür ist:

public final void print() { /* Methodenrumpf */}

Unterklassen mit überschriebenen Methoden sind ein sehr mächtiges Konzept für den Entwickler einer Unterklasse. Eine Unterklasse kann inhaltlich eine Oberklasse in weiten Bereichen neu implementieren, solange die Signaturen der Methoden gewahrt bleiben und ein Konstruktor der Oberklasse benutzt. Die Kontrolle liegt fast vollständig in den Händen der Unterklasse. das Schlüsselwort final erlaubt dem Entwickler der Oberklasse

  • das Überschreiben zu verbieten
  • und damit die Sichtbarkeit der Methode der Oberklasse für beliebige Konsumenten der Unterklasse zu garantieren

Dem Entwickler der Unterklasse steht es nach wie vor frei Methoden mit anderen Signaturen zu verwenden.

Finale Klassen

Finale Klassen verbieten die Spezialisierung einer Klasse durch eine Unterklasse.

Das Schlüsselwort final kommt im folgenden Beispiel zum Einsatz:

final class chiefExecutiveOfficer extends Manager { /* Klassen Implementierung */} 

Beispiel

  • Systemklasse String: Die Klasse String ist final, da Zeichenketten hochoptimiert und nicht veränderbar sind. Das Javalaufzeitsystem verwendet spezielle Optimierungen wie Stringpools und es nutzt die Unveränderbarkeit von Zeichenketten um dem Anwender Zugriff auf spezielle Zeichenketten des Laufzeitsystems zu geben. Die Möglichkeit diese Klasse zu spezialisieren wurde unterbunden um Sicherheit und Leistung zu garantieren.

Abstrakte Klassen und Methoden

Abstrakte Klassen

Abstrakte Klassen und Java-Schnittstellen (Stoff des zweiten Semester) dienen in Java zur Modularisierung und damit oft zur Arbeitsteilung zwischen Entwicklern. Abstrakte Klassen erlauben es dem Entwickler eine Oberklasse zu implementieren die nicht selbst instanziiert werden darf. Dieses Konzept erlaubt es Klassen als Vorlagen zu implementieren und gleichzeitig zu erzwingen, dass die Entwickler der Unterklasse fehlende oder eigene Methoden und Attribute zur Verfügung stellen. Hiermit lassen sich allgemeine Konzepte implementieren die zu allgemein sind um Instanziierungen zu erlauben, Dies erfolgt mit dem Schlüsselwort abstract bei der Spezifikation der Klasse.

Die Klasse Person im vorhergehenden Beispiel ist eine Klasse von der man nicht erlauben möchte, dass sie instanziiert wird. Dies geschieht zum Beispiel so:

public abstract class Person {
    private String name;
    private String firstName;
    public int age;

    public Person(String ln, String fn) {
        name = ln; firstName = fn; }

    public Person() { this("Doe","John");}

    public void setName(String ln, String fn) {
        name = ln; firstName = fn; }
    public String fullName() {return (name + " " + firstName);}
}

Versucht man nun eine Person zu instanziieren:

Person p1 = new Person("persona","nongrata");

...führt dies beim Übersetzen zu folgendem Fehler:

javac Main.java 
Main.java:8: Person is abstract; cannot be instantiated
        Person p1 = new Person("persona","nongrata");
                    ^
1 error

Abstrakte Klassen haben die typischen Eigenschaften von normalen Javaklassen. Sie haben Attribute und Methoden die vererbt werden können.

Wichtige Eigenschaften abstrakter Klassen
  • Sie können nicht instanziiert werden.
  • Sie können nicht final sein.

Beispiel

Die Systemklasse Number: Die Klasse Number enthält gemeinsame Methoden für zahlenartige Typen. Man kann sie jedoch nicht direkt instanziieren. Die daraus abgeleiteten Klassen Byte, Float etc. sind final. Sie sind hochoptimiert und sollen aus Performance- und Sicherheitsgründen nicht spezialisiert werden.

Abstrake und finale Klassen

Abstrakte Methoden

Normale Methoden und Attribute werden an die Unterklasse vererbt und können auch bei Bedarf überschrieben werden.

Wird jedoch eine Methode mit dem Schlüsselwort abstract gekennzeichnet so muss sie von einer nicht abstrakten Unterklasse implementiert und überschrieben werden.

Beispiel

Die Klasse Person kann so erzwingen, dass eine printAll() Methode für alle Unterklassen implementiert muss, ohne sie selbst zu implementieren.

public abstract class Person {
    private String name;
    private String firstName;
    public int age;

    public Person(String ln, String fn) {
        name = ln; firstName = fn; }

    public Person() { this("Doe","John");}

    public void setName(String ln, String fn) {
        name = ln; firstName = fn; }
    public String fullName() {return (name + " " + firstName);}
    public abstract String printAll();
}

Regel: Eine Unterklasse einer abstrakten Klasse muss:

  • alle abstrakten Methoden implementieren um nicht "abstrakt" zu sein

oder

  • selbst eine abstrakte Klasse sein, falls sie nicht alle oder keine der abstrakten Methoden implementiert.

 

8.5 Entwurfsüberlegungen zur Vererbung

Die Möglichkeiten der Vererbung sind sehr ausdrucksstark, haben viele Vorteile, bergen aber auch Risiken. Hierbei muss man mindestens drei verschieden Entwicklerrollen beachten:

  • Implementierer der Oberklasse
    • entwirft ein semantischen Konzept für eine Klassenhierarchie
    • implementiert allgemeingültige Methoden und Attribute
    • gibt Rahmenwerk durch Zugriffsmethoden, Zugriffskontrolle, explizite Möglichkeit zum Vererben und Überschreiben vor
  • Implementierer der Unterklasse
    • nutzt Infrastruktur
    • muss sich an Syntax und Zugriffskontrollen halten
    • macht Annahmen über Semantik der Oberklasse
    • verfeinert und verändert Funktionalität
  • Benutzer der Klassenhierarchie
    • macht Annahmen zur gesamten Klassenhierarchie
      • nutzt Syntax
      • erschliesst Semantik aus Klassendeklaration, Dokumentation und experimenteller Benutzung
    • muss sich an Syntax und Zugriffskontrollen aller Klassen halten

Semantische Integrität von Unterklassen

Unterklassen müssen Spezialisierungen der Oberklassen sein. Sie müssen sich in allen semantischen Belangen wie die Oberklasse verhalten.

Vorteile

  • Existierende Methoden können
    • benutzt werden
    • komplett überschrieben werden
    • überschrieben und die Methode der Oberklasse rekursiv genutzt werden
  • Existierende Funktionalität von Oberklassen können sehr einfach genutzt werden

Risiken

  • Überschriebene Methoden haben eine Semantik die nicht vom Entwickler der Oberklasse oder dem Benutzer der Unterklasse verstanden werden
    • Das heißt, dass die Methoden eventuell unter falschen Annahmen benutzt werden und entsprechenden Schaden anrichten
  • Unterklassen erben unnötige Infrastruktur (Methoden) die den Pflegeaufwand in die Höhe treiben oder externe Benutzer verwirren

Ändern von Oberklassen

Änderungen an Oberklassen sind heikel, da sie die Implementierungen der Unterklassen invalidieren können:

  • Entfernen von öffentlichen Methoden oder Attributen wird die Unterklassen invalidieren. Überschriebene Methoden werden plötzlich zu regulären. Der Polymorphismus gilt nicht für identische Methoden zweier Unterklassen wenn die Methode nicht in der Oberklasse als abstrakte Methode implementiert ist
  • Nicht abstrakte Methoden abstrakt machen invalidiert die Unterklassen. Alle Unterklassen müssen die abstrakte Methode implementieren
  • Hinzufügen von Methoden oder Attributen birgt das Risiko, dass sie Aufgrund zufälliger Namensgleichheit in den Unterklassen zu überschriebenen Methoden führen

Wichtig: Die Modellierung einer Klassenhierarchie muss sehr sorgfältig geschehen. Das möglichst exakte Abbilden realer Hierarchien ist für eine langlebige und wartbare Implementierung sehr wichtig.

Alternativen zur Vererbung

Vererbung birgt auch Risiken da eine Klassenhierarchie relativ starr ist und individuelle Änderungen weitreichende Auswirkungen haben können.

Vererbung sollte immer dann eingesetzt werden wenn man eine "ist-ein" Beziehung modellieren möchte.

In vielen Fällen liegt aber auch eine "hat-ein" Beziehung vor, eine Assoziation. Hier sollte man erwägen zwei Objekte zu erzeugen und sie über eine Referenz zu verwalten.

Im vorhergehen Beispiel ist es eine Überlegung wert, das Budget der Klasse Manager als Referenz von der Klasse Employee auf eine Klasse Budget zu implementieren. Hierbei sind die folgenden Dinge abzuwägen:

  • Nachteile der Assoziation
    • Höherer initialer Implementierungsaufwand zur Datenkaspelung
      • Referenz + Zugriffsmethoden + Belegung im Konstruktor mit Objekterzeugung des Budgets
    • Jeder zusätzliche Aspekt der Klasse muss als Assoziation implementiert werden
    • Zusätzliche Aspekte der Klasse sind nicht in der Klassenhierarchie sichtbar
    • Zusätzlicher Speicherverbrauch durch zusätzliche Objekte
  • Vorteile der Assoziation
    • Zukünftige Änderungen der assoziierten Klasse haben weniger Einfluss auf die gesamte Klassenhierarchie
    • Der Implementierungs-Overhead einer eigenen Klasse entfällt (Konstruktoren, überschriebene Methoden etc.)

 

8.6 Übungen

Duke als Boxer

8.6.1 Aufruf von überschriebenen Methoden

Implementieren sie die Klasse TopClass mit den folgenden Eigenschaften

  • statische, geschütztes (protected) Attribut zaehlerTop zum Zählen der erzeugten Instanzen der Klasse TopClass
  • parameterlosen Konstruktor der den Instanzenzähler inkrementiert.
  • eine statische geschützte Methode getZaehler() die das Attribut zaehlerTop ausgibt.

Implementieren Sie die Klasse LowClass welche aus TopClass abgeleitet mit den folgenden Eigenschaften 

  • statische, geschütztes (protected) Attribut zaehler zum Zählen der erzeugten Instanzen der Klasse LowClass
  • parameterlosen Konstruktor der den Instanzenzähler inkrementiert
  • eine statische geschützte Methode getZaehler() die das Attribut zaehler ausgibt.
  • eine Methode test() die
    • die lokale Methode getZaehler() aufruft und das Ergebnis auf der Konsole ausgibt
    • die Methode getZaehler() der Oberklasse aufruft und das Ergebnis auf der Konsole ausgibt

Implementieren Sie ein Hauptprogramm, dass folgende Befehle ausführt

  • Anlegen eines Feldes der Klasse TopClass mit n Elementen
  • Anlegen eines Feldes der Klasse LowClass mit n Elementen
  • Erzeugen von jeweils n Elementen der Klasse TopClass und Einfügen in das zugehörige Feld
  • Aufruf der getZaehler() Methode der Klasse TopClass und Ausgabe des Wertes auf der Konsole
  • Erzeugen von jeweils n Elementen der Klasse LowClass und Einfügen in das zugehörige Feld
  • Aufruf der getZaehler() Methode der Klasse LowClass und Ausgabe des Wertes auf der Konsole
  • Aufruf der getZaehler() Methode der Klasse TopClass und Ausgabe des Wertes auf der Konsole
UML Diagramm TopClass, LowClass

Zu beobachten:

  • Wieviel Instanzen welcher Klasse (TopClass, LowClass) wurden erzeugt?
  • Wieviel Instanzen wurden insgesamt erzeugt?
  • Was geschieht wenn das Attribut zaehler der Klasse LowClass gelöscht wird?

Klasse MainTop zum Testen

package block9;
/**
*
* @author sschneid
*/
public class MainTop {
private static int size = 3;
public static void main(String[] args) {
TopClass[] feldT = new TopClass[size];
LowClass[] feldL = new LowClass[size]; for ( int i=0; i<size; i++) feldT[i]= new TopClass(); for ( int i=0; i<size; i++) {

feldL[i]= new LowClass();
feldL[i].test();
}
System.out.println(TopClass.getZaehler() + " Instanzen TopClass generiert, "+
LowClass.getZaehler() + " Instanzen LowClass generiert");
}
}

8.6.2 Vererbung und Assoziation

Implementieren Sie eine Klasse Point mit den folgenden Eigenschaften:

  • private Fliesskommaattribute für x und y Koordinate
  • öffentlicher Konstruktor mit x und x als Parameter
  • öffentliche setXY(x,y) Methode
  • Auslesemethoden getX(), getY()
  • print() Methode die eine Zeichenkette mit allen Werten liefert
  • überschreiben die die toString() Methode von Object so das sie alle Werte für Point ausgibt.

Implementieren Sie eine Klasse CircleIsPoint die die Eigenschaften der Klasse Point hat und zusätzlich einen Radius verwalten kann mit folgenden Eigenschaften:

  • erbt von Point
  • das des Zentrum des Kreises sind die (x,y) Koordinaten von Point 
  • privates Fliesskomma Attribut für den Radius radius
  • getRadius() und setRadius(r) Methoden zum Pflegen des Attributs
  • öffentlicher Konstruktor für (x,y) Zentrum und Radius
  • print() Methode die eine Zeichenkette mit allen Werten liefert
  • überschreiben die die toString() Methode von Object so das sie alle Werte für Point ausgibt.

Implementieren Sie eine Klasse CircleHasPoint die die Eigenschaften der Klasse Point hat und zusätzlich einen Radius verwalten kann mit folgenden Eigenschaften:

  • erbt nicht von Point
  • benutzt privates Attribut vom Referenztyp auf Point
  • das des Zentrum des Kreises sind die (x,y) Koordinaten von Point 
  • alle öffentlichen Methoden sind identisch zu CircleIsPoint!

 Was unterscheidet beide Implementierungen? Welche ist die bessere Implementierung?

Point und Circle, Delegation versus Assoziation

Klasse Main

Die drei obigen Klassen sollten mit der folgenden main() Methode funktionieren:

package block9;
public class Main {

    public static void main(String[] args) {
        Point p1 = new Point (2.2, 3.3);
        Point p2 = new Point (2.22, 3.33);
        CircleIsPoint cip1 = new CircleIsPoint(4.4,5.5,6.6);

        p1.print();
        cip1.print();

        CircleHasPoint chp1 = new CircleHasPoint(44.4,55.5,66.6);
        chp1.print();
    }
}

8.6.3 Flughafenbeispiel

UML Diagramm Flughafenbeispiel mit Vererbung

Klasse Flughafen

package airline.block8;

 

/**
*
* @author Otto Lilienthal
*/
public class Flughafen {
String name;
Passagierflugzeug[] gate;
Flugzeug[] parkposition;
double treibstoffLager;

/**
* Anlegen eines Flughafens
* @param anzahlGates Anzahl der Gates eines Flugzeugs
* @param anzahlParkposition Anzahl der Parkpositionen
*/
public Flughafen(int anzahlGates, int anzahlParkposition) {
gate = new Passagierflugzeug[anzahlGates];
parkposition = new Flugzeug[anzahlParkposition];
treibstoffLager = 10000000D;
}

/**
* Betanken eines Flugzeugs aus dem Treibstofflage
* Flugzeug wird nicht betankt wenn das Volumen im Tank nicht ausreicht!
* @param kg zu tankendes Volumen
* @param f Flugzeug welches betankt werden soll
*/
public void tanken(double liter, Flugzeug f) {
if ((liter <= treibstoffLager) && (liter >=0)) {
treibstoffLager -= liter;
f.tanken(liter);
}
else
System.out.println("Nicht genug Treibstoff im Depot zum Tanken");
}

/**
* Drucke den Zustand des Flughafens auf die Konsole
*/
public void drucken() {
System.out.println("*** Unser Flughafen ***");
System.out.println("Flughafen " + name);
for (int i =0;i<gate.length;i++)
System.out.println("Am Gate " + i + ": " + gate[i]);
for (int i =0;i<parkposition.length;i++)
System.out.println("Auf Parkposition " + i + ": " + parkposition[i]);
System.out.println("Treibstoff: " + treibstoffLager);
System.out.println("***********************");
}

/**
* Baue einen Flughafen und teste alle Klassen
* @param args nicht benoetigt
*/
public static void main(String[] args) {

Flughafen pad = new Flughafen(6, 10);
pad.name="Paderborn";
pad.treibstoffLager = 1000000;

pad.drucken();

// Boeing 747, https://de.wikipedia.org/wiki/Boeing_747#747-400
Passagierflugzeug lh1 = new Passagierflugzeug(40000, 400000);
lh1.kennzeichen ="D-ABTL";
lh1.einsteigen(3);
System.out.println("Unsere Gesellschaft" + Flugzeug.meineLG());

double aktuellesGewicht = lh1.gewicht();
System.out.println("aktuelle Gewicht von lh1: " + aktuellesGewicht);

pad.gate[1] = lh1;

// Airbus A380 https://de.wikipedia.org/wiki/Airbus_A380#A380-800
Passagierflugzeug lh2 = new Passagierflugzeug(40000, 400000, "D-AIMA");

// 11 +1 +4 Passagiere steigen ein
lh2.einsteigen(11);
lh2.einsteigen();
lh2.einsteigen(4);
// 5 Passagiere wollen wieder austteigen
lh2.aussteigen(5);

// lh2 wird an Gate 2 angedockt
pad.gate[2] = lh2;

Frachtflugzeug meinFrachter = new Frachtflugzeug(10000D,300000D,"N-1997A");
meinFrachter.laden(1000D);
meinFrachter.entladen(500D);
// Betanke mein Flugzug
pad.tanken(200D,meinFrachter);
pad.parkposition[2] = meinFrachter;
pad.parkposition[3] = new Passagierflugzeug(100000D,500000D,"D-AAAA");

// Hier steht ein Frachtflugzeug. Das ist bekannt!!
// Durch den "Cast" kann man man die Methode laden() für f aufrufen
Frachtflugzeug f = (Frachtflugzeug) pad.parkposition[2];
f.laden(500D);
System.out.println(pad.parkposition[2].kennzeichen + " "
+ pad.parkposition[2].gewicht());


// Hier wird die überladene Methode gewicht() aufgerufen
// Das Programm ruft hier zwei verschiedene Methoden auf
// welche Methode aufgerufen wird wird erst zur Laufzeit entschieden!
System.out.println("Gewicht Parkposition 2 " + pad.parkposition[2].gewicht());
System.out.println("Gewicht Parkposition 3 " + pad.parkposition[3].gewicht());

System.out.println("Flughafen " + pad.name);
System.out.println("Am Gate 1: " + pad.gate[1].kennzeichen +
" Passagiere: " + pad.gate[1].getPassagiere() +
" akt. Gewicht: " + pad.gate[1].gewicht());
System.out.println("Am Gate 2: " + pad.gate[2].kennzeichen +
" Passagiere: " + pad.gate[2].getPassagiere() +
" akt. Gewicht: " + pad.gate[2].gewicht());
pad.drucken();

}
}

 

Klasse Flugzeug

package airline.block8;

/**
* Das ist ein Flugzeug des 8.ten Blocks
* @author Otto Lilienthal
*/
public class Flugzeug {

/**
* Kennzeichen des Flugzeugs
*/
String kennzeichen;
final double maximalesGewicht;
final double leerGewicht;
private double tankinhalt;

/**
* Initialiseren eines Flugzeugs mit Leer- und Maximalgewicht
* @param minGewicht Minimalgewicht in kg
* @param maxGewicht Maximalgewicht in kg
*/
public Flugzeug(double minGewicht, double maxGewicht) {
System.out.println("Ich baue jetzt ein Flugzeug");
if (minGewicht > 0) leerGewicht = minGewicht;
else leerGewicht = 0;
if ((maxGewicht > 0) && (maxGewicht>=leerGewicht))
maximalesGewicht = maxGewicht;
else maximalesGewicht = leerGewicht;
tankinhalt = 0;
}

/**
* Initialiseren eines Flugzeugs mit Leer- und Maximalgewicht, Kennzeichen
* @param minGewicht Minimalgewicht in kg
* @param maxGewicht Maximalgewicht in kg
* @param kennz Kennzeichen
*/
public Flugzeug(double minGewicht, double maxGewicht, String kennz) {
this(minGewicht,maxGewicht);
kennzeichen = kennz;
}

/**
* berechnet das aktuelle Gewicht des Flugzeugs
* @return aktuelle Gewicht
*/
public double gewicht() {
//double g = leerGewicht+passagiere*durchschnittsgewicht;
return leerGewicht+ tankinhalt;
}

/**
* betanken eines Flugzeugs. Vorsicht: Hier wird das Gewicht eingegeben
* @param t kg Treibstoff
*/
public void tanken(double t) {
if (t >= 0)
tankinhalt += t;
else
System.out.println("Versuch mit negativer Literanzahl: " +t);
}

/**
* Aktuelle Tankfüllung in kg
* Vorsicht: Es geht hier um das Gewicht des Treibsstoffs
* @return Gewicht des aktuellen Treibstoffs
*/
public double tankfuellung() {return tankinhalt;}

/**
* Die Fluggesellschaft der Flugzeuge. Dies ist eine static Variable
* D.h. alle Flugzeuge gehören zu einer Fliggesellschaft
* @return Name der Fluggesellschaft
*/
public static String meineLG() { return "Lufthansa";}

}

 

 

Klasse Frachtflugzeug


package airline.block8;

/**
*
* @author Otto Lilienthal
*/
public class Frachtflugzeug extends Flugzeug {

private double ladung;

/**
* Initialisieren eines Frachtflugzeugs mit Initial- und Maximalgewicht,
* Kennzeichen
* @param leer Leergewicht
* @param max Maximalgewicht
* @param kennz Kennzeichen
*/
public Frachtflugzeug(double leer, double max, String kennz) {
super(leer, max, kennz);
ladung = 0;
}

/**
* Laden von Fracht in kg
* @param l zu ladende Fracht in kg
*/
public void laden(double l) {
if (gewicht() + ladung <= maximalesGewicht) {
ladung += l;
} else {
System.out.println("Kann nicht " + l + " kg laden...");
}
}

/**
* Entladen von Fracht in kg
* @param l zu entladende Fracht in kg
*/
public void entladen(double l) {
if (ladung >= l) {
ladung -= l;
} else {
System.out.println("Kann nicht " + l + " kg entladen (Ist zuviel)");
}
}

/**
* Gewicht eines Frachtflugzeugs in kg
* @return Gewicht in kg
*/
@Override
public double gewicht() {
return super.gewicht() + ladung;
}

}

 

Klasse Passagierflugzeug 

package airline.block8;

/**
*
* @author stsch
*/
public class Passagierflugzeug extends Flugzeug {

final static double durchschnittsgewicht = 75;
private int passagiere;
int maxPassagiere = 100;

/**
* gibt die aktuelle Anzahl der Passagiere zurück
* @return Anzahl der Passagiere
*/
public int getPassagiere() {
return passagiere;
}

/**
* Initialisert ein Flugzeug mit Minimal- und Maximalgewicht
* @param minGewicht Minimalgewicht in kg
* @param maxGewicht Maximalgewicht in kg
*/
public Passagierflugzeug(double minGewicht, double maxGewicht) {
super(minGewicht,maxGewicht);
passagiere = 0;

}

/**
* Initialisiert ein Flugzeug mit Minimal- und Maximalgewicht und Kennzeichen
* @param minGewicht Minimalgewicht in kg
* @param maxGewicht Maximalgewicht in kg
* @param kennz Kennzeichen des Flugzeugs
*/
public Passagierflugzeug(double minGewicht, double maxGewicht, String kennz) {
super(minGewicht,maxGewicht,kennz);
passagiere = 0;
}

/**
* Einsteigen eines einzelnen Passagiers
*/
public void einsteigen() {
if (passagiere >= maxPassagiere) { // Zuviele Einsteiger
System.out.println("Fehler: Einsteigen verweigert ");
} else { // passt schon
passagiere++;
}
}

/**
* Einsteigen einer vorgegebenen Anzahl Passagiere.
* und sie funktioniert auch!
* Wenn zuviele einsteigen möchten, darf keiner einsteigen!
* @param einsteiger Anzahl der Passagiere die einsteigen.
*/
public void einsteigen(int einsteiger) {
if (passagiere+einsteiger > maxPassagiere) {
System.out.println("Fehler: Einsteigen verweigert ");
} else {
System.out.println("Ich lasse " + einsteiger + " Passagiere eingestiegen");
einsteigen(); // Einer steigt ein
if (einsteiger > 1)
einsteigen(einsteiger-1); //Der Rest steigt ein
}
}

/**
* Aussteigen einer Anzahl von Pasagieren
* @param aussteiger ANzahl der aussteigenden Passagiere
*/
public void aussteigen(final int aussteiger) {
if (passagiere >= aussteiger) {
passagiere -= aussteiger;
} else {
System.out.println("Fehler: Flugzeug ist leer... ");
}
} // Ende der Methode aussteigen

/**
* berechnet das aktuelle Gewicht des Flugzeugs
* @return aktuelles Gewicht
*/
@Override
public double gewicht() {
return super.gewicht()+passagiere*durchschnittsgewicht;
}

}


 

 

8.7 Lösungen

8.7.1 Vererbung und Überschreiben

Klasse TopClass

public class TopClass {
    protected static int zaehler;

    public TopClass() {zaehler++;}

    protected static int getZaehler() {return zaehler;}

}

Klasse LowClass

public class LowClass extends TopClass {
    protected static int zaehler;

    public LowClass() {zaehler++;}

    protected static int getZaehler() {return zaehler;}

    protected void test (){
        System.out.println( getZaehler() + " Instanzen der Klasse LowClass erzeugt");
        System.out.println( TopClass.getZaehler() + " Instanzen der Klasse TopClass erzeugt");
    }
}

Klasse Main

/**
*
* @author sschneid
*/
public class Main {
private static int size = 3;
public static void main(String[] args) {
TopClass[] feldT = new TopClass[size];
LowClass[] feldL = new LowClass[size];

for ( int i=0; i<size; i++) feldT[i]= new TopClass();

for ( int i=0; i<size; i++) {
feldL[i]= new LowClass();
feldL[i].test();
}
System.out.println(TopClass.getZaehler() + " Instanzen TopClass generiert, "+
LowClass.getZaehler() + " Instanzen LowClass generiert");
}
}

Ausgaben (mit Attribut zaehler in Klasse LowClass)

1 Instanzen der Klasse LowClass erzeugt
4 Instanzen der Klasse TopClass erzeugt
2 Instanzen der Klasse LowClass erzeugt
5 Instanzen der Klasse TopClass erzeugt
3 Instanzen der Klasse LowClass erzeugt
6 Instanzen der Klasse TopClass erzeugt
6 Instanzen TopClass generiert, 3 Instanzen LowClass generiert

Ausgaben (ohne Attribut zaehler in Klasse LowClass)

5 Instanzen der Klasse LowClass erzeugt
5 Instanzen der Klasse TopClass erzeugt
7 Instanzen der Klasse LowClass erzeugt
7 Instanzen der Klasse TopClass erzeugt
9 Instanzen der Klasse LowClass erzeugt
9 Instanzen der Klasse TopClass erzeugt
9 Instanzen TopClass generiert, 9 Instanzen LowClass generiert

8.7.2 Vererbung und Assoziation

Klasse Point

package block9;
public class Point {
private double x;
private double y;
public Point(double xx, double yy) {
x = xx;
y = yy;
}
public void setXY(double xx, double yy) {
x = xx;
y = yy;
}
public double getX() { return x;}
public double getY() { return y;}
public void print() {System.out.println(toString());}
public String toString() {return ("x: " + x + " y: " + y);}
}

Klasse CircleIsPoint

package block9;
public class CircleIsPoint extends Point{
private double radius;
public CircleIsPoint(double xx, double yy, double r) {
super(xx,yy);
radius=r;
}
public double getRadius() {return radius;}
public void setRadius(double r) {radius=r;}
public String toString() {return (super.toString() + " , r: " + radius);}
}

Klasse CircleHasPoint

package block9;
public class CircleHasPoint {
private double radius;
private Point p;
public CircleHasPoint(double xx, double yy, double r) {
p = new Point(xx,yy);
radius=r;
}
public double getRadius() {return radius;}
public void setRadius(double r) {radius=r;}
public void setXY(double xx, double yy) { p.setXY(xx,yy);}
public double getX() { return p.getX();}
public double getY() { return p.getY();}
public void print() {System.out.println(toString());}
public String toString() {return ("x: " + p.getX() + " y: " +
p.getY() + " r: "+ radius);}
}

Klasse Main

package block9;
public class Main {

    public static void main(String[] args) {
        Point p1 = new Point (2.2, 3.3);
        Point p2 = new Point (2.22, 3.33);
        CircleIsPoint cip1 = new CircleIsPoint(4.4,5.5,6.6);

        p1.print();
        cip1.print();

        CircleHasPoint chp1 = new CircleHasPoint(44.4,55.5,66.6);
        chp1.print();
    }
}

Ausgabe:

x: 2.2 y: 3.3
x: 4.4 y: 5.5 radius: 6.6
x: 44.4 y: 55.5 radius: 66.6

8.7.3 Beispiel der Vorlesung

Schritt 1: Klonen der Klasse Ware zu Buch und Anpassen

Ware, Lager, Buch, ohne Vererbung

Klasse Buch (nicht von Ware abgeleitet!)

package block9;
/**
* Dies ist die Dokumentation der Klasse Ware. Ware dient zum Verwalten von Gütern
* mit Preisen und Namen in einem Lager.
* @author Stefan Schneider
* @version 1.2
* @see Lager
*/
public class Buch {
/*
* 1. Erzeugen Sie eine Klasse Buch in dem Sie die Klasse
* Ware mit Ihrem Inhalt kopieren
* 1.1 Anpassen Name
* 1.2 Anpassen Konstruktoren
* 1.3 Anpassen equals Methode
* 1.4 Anlegen einer ISBN Nummer
* 1.5 Anlegen einer hardcodierten halben Mehrwertsteuer
* 1.6 Referenz auf eine private, optionale (ältere) Auflage
* Zugriffsmethoden anlegen
* 1.7 Anpassen der Druckenmethode
* 4. Ableiten der Klasse aus der Klasse aus der Klasse Ware
* 4.1 extends Schlüsselwort benutzen
* 4.2 Löschen von allem redundanten Code
* 6. Erzeugen er Klasse MusikCD durch Kopieren der Klasse Ware
* 6.1 Mehrwertsteuer auf vollen Mehrwertsteuer hart kodieren
* 7. Die Klasse Ware soll nicht mehr instanziiert werden. Sie soll
* abstrakt werden
*/
/**
* Der aktuelle Mehrwertsteuersatz 2010.
* Er liegt zur Zeit bei {@value}.
*
* @since 1.0
*/
public static final double mws = 0.19;
private double nettoPreis; //Deklaration
private boolean halbeMws;
private String name;
public Buch empfehlung;
private String isbn;
private Buch alteAuflage;

/**
* Referenz aus alte Auflage
* @return
*/
public Buch getAlteAuflage() { return alteAuflage; }
/**
* zuweisen einer alten Auflage
* @param alteAuflage
*/
public void setAlteAuflage(Buch alteAuflage) {
this.alteAuflage = alteAuflage;
}
/**
* Auslesen der ISBN-Nummer (eine Zeichenkette)
* @return
*/
public String getISBN() { return isbn; }
/**
* Konstruktor fuer die Klasse Ware
* @param n der Name der Ware
* @param np der Nettorpreis
* @param hmws halber Mehrwertsteuersatz für Ware gueltig
*/

public Buch(String n, double np, String myISBN) {
name = n;
nettoPreis = np;
halbeMws = true; //Bei Büchern immer der Fall
isbn=myISBN;
// zusätzliche Initialiserungen....
}
/**
* tiefes Vergleichen. Berücksichtigt beim Namen nicht
* die Groß/kleinschreinung. Berücksichtigt rekursiv die
* Empfehlungen
* @param dieAndereWare
* @return wahr wenn beide Waren dem Vergleich entsprechen.
*/
public boolean equals(Buch dieAndereWare) {
boolean result;
result = getName().equalsIgnoreCase(dieAndereWare.getName())
&& (getNettoPreis() == dieAndereWare.getNettoPreis())
&& (getHalbeMws() == dieAndereWare.getHalbeMws())
&& (((getEmpfehlung() == null) && (dieAndereWare.getEmpfehlung() == null))
|| ((getEmpfehlung() != null)
&& (dieAndereWare.getEmpfehlung() != null)
&& (getEmpfehlung().equals(dieAndereWare.getEmpfehlung()))
)
&& (isbn.equals(dieAndereWare.isbn))
&&
(((alteAuflage == null) && (dieAndereWare.alteAuflage == null))
|| ((alteAuflage != null)
&& (dieAndereWare.alteAuflage != null)
&& (alteAuflage.equals(dieAndereWare.alteAuflage))
)
)

);
return result;
}
/**
* Liefert den Namen einer Ware zurueck.
* @return Name der Ware
*/
public String getName() {
return name;
}
/**
* Setzen eines neuen Nettopreis
* @param npr der neue Nettopreis
*/
public void setNettoPreis(double npr) {
nettoPreis = npr;
}

/**
* liefere wahr zurück wenn Mwssatz reduziert ist
* @return
*/
public boolean getHalbeMws() {return halbeMws;}

public Buch getEmpfehlung() { return empfehlung; }

public void setEmpfehlung(Buch empfehlung) {
this.empfehlung = empfehlung;
}
/**
* Ausdrucken aller Werte auf der Konsole
*/
public void drucken() { drucken(0); }
/**
* Ausdrucken aller Werte auf der Konsole mit vorgebener Einrueckung
* für Empfehlungen
* @param einruecken eingerueckte Stellen für Empfehlungen
*/
private void drucken(int einruecken) {
String leerStellen = "";
for (int i = 0; i < einruecken; i++) {
leerStellen = leerStellen + " ";
}
System.out.print(leerStellen + "Name: " + getName());
System.out.print(" ;Netto: " + getNettoPreis());
System.out.print(" ;Brutto: " + getBruttoPreis());
if (halbeMws)
System.out.println(" (Halbe Mws)");
else
System.out.println(" (Volle Mws)");
if (empfehlung != null) {
System.out.println(leerStellen + "Empfehlung:");
empfehlung.drucken(einruecken + 2);
}
System.out.println(leerStellen + "ISBN:" +isbn);
if (alteAuflage != null) {
System.out.println(leerStellen + "Alte Auflage:");
alteAuflage.drucken(einruecken + 2);
}

}
/**
* Ausgabe des Nettopreis
* @return der Nettopreis
*/
public double getNettoPreis() {
return nettoPreis;
}
/**
* Ausgabe des Bruttopreis
* @return der Bruttopreis
*/
public double getBruttoPreis() {
double bp; //temporaere Variable; keine Klassenvariable
if (halbeMws) {
bp = Math.round(nettoPreis * (mws / 2 + 1) * 100) / 100;
} else {
bp = Math.round(nettoPreis * (mws + 1) * 100) / 100;
}
return bp;
}
/**
* Erzeugt verkette Liste einer Ware
* @param Anzahl der zu verkettenden Waren
*/
public void generiereEmpfehlungen(int empf) {
Buch ware1 = this;
Buch ware2;
for (int i = 1; i <= empf; i++) {
ware2 =
new Buch(ware1.getName() + " besser" + i, 31.12, isbn+"-1");
ware1.setEmpfehlung(ware2);
ware1 = ware2;
}
}
}

 Klasse Main

package block9;
/**
* Eine Hilfsklasse zur Implementierung eines Hauptprogramms
* @author sschneid
* @version 1.1
*/
public class Main {
/**
* Die Methode wareTesten testet die Implementierung der
* von Waren mit tiefem Kopieren, tiefem Vergleichen.
* Sie nutzt nicht die Vererbung aus.
*/
public static void warenTesten() {
Ware ware1, ware2;
Lager dasLager;
// Testen der Klasse Ware
ware1 = new Ware("Zeitung", 12.12, true);
ware1.drucken();
double p = ware1.getNettoPreis();
// Generieren von Empfehlungen
ware2 = new Ware("Potter Band 1", 31.12, false);
ware2.generiereEmpfehlungen(7);
// Abfrage des Lagers
dasLager = Lager.getLager();
dasLager.einlagern(ware1, 0);
dasLager.einlagern(ware2, 1);
// Prüfen der Lagerbestände mit tiefem Vergleichen
Ware testWare = dasLager.holen(0);
if (testWare == ware2)
System.out.println("testware und ware2 sind identisch (gut)");
if (testWare.equals(ware2))
System.out.println("testware und ware2 sind inhaltsgleich (gut)");
// vollständiges Befüllen des Lager
for (int i = 0; i < 1000; i++) {
ware2 = new Ware("Band" + i + "B", 12.12, true);
ware2.generiereEmpfehlungen(7);
dasLager.einlagern(ware2);
}
System.out.println("Lager vollständig gefüllt mit "
+ dasLager.lagerGroesse() + " Waren.");
for (int i = 0; i < 1000; i++) {
ware2 = new Ware("Volume" + i + "B", 12.12, true);
ware2.generiereEmpfehlungen(7);
dasLager.einlagern(ware2,i);
}
System.out.println("Lager erneut vollständig gefüllt mit "
+ dasLager.lagerGroesse() + " Waren.");
}
/**
* Diese Methode dient zum Testen der Klasse Buch.
* Sie nutzt die Veererbung in Java aus.
*/
public static void buecherTesten() {
/*
* 2. Testen: Anlegen einer Testmethode für Bücher
* Erzeugen von Büchern
* Anlegen einer Referenz auf eine alte Auflage
* Drucken zweier Bücher
* 3. Frage: Wie kann man Bücher in das Lager einfügen?
* 5. Einfügen der Bücher in das Lager
* 8. Anpassen der Hauptroutine
* 8.1 Alle Instanzen vom Typ Ware sollen MusikCDs werden da die Klasse
* Ware jetzt abstrakt ist.
*/
Lager dasLager;
dasLager = Lager.getLager();
Buch buch1 = new Buch("Das Grauen",22.22,"9876543");
Buch buch2 = new Buch("Das Kapital",33.33,"9876543");
buch1.setAlteAuflage(buch2);
buch1.drucken();

//dasLager.einlagern(buch1);
//dasLager.einlagern(buch2);
dasLager.drucken();
}
/**
* Das Hauptprogramm
*
* @param args
*/
public static void main(String[] args) {
//warenTesten();
buecherTesten();
}
}

 Schritt 2: Die Klasse Buch wird aus der Klasse Ware abgeleitet

Klasse Buch ist von der Klasse Ware abgeleitet

Klasse Buch

package block9;
/**
* Dies ist die Dokumentation der Klasse Ware. Ware dient zum Verwalten von Gütern
* mit Preisen und Namen in einem Lager.
* @author Stefan Schneider
* @version 1.2
* @see Lager
*/
public class Buch extends Ware {
/*
* 1. Erzeugen Sie eine Klasse Buch in dem Sie die Klasse
* Ware mit Ihrem Inhalt kopieren
* 1.1 Anpassen Name
* 1.2 Anpassen Konstruktoren
* 1.3 Anpassen equals Methode
* 1.4 Anlegen einer ISBN Nummer
* 1.5 Anlegen einer hardcodierten halben Mehrwertsteuer
* 1.6 Referenz auf eine private, optionale (ältere) Auflage
* Zugriffsmethoden anlegen
* 1.7 Anpassen der Druckenmethode
* 4. Ableiten der Klasse aus der Klasse aus der Klasse Ware
* 4.1 extends Schlüsselwort benutzen
* 4.2 Löschen von allem redundanten Code
* 6. Erzeugen er Klasse MusikCD durch Kopieren der Klasse Ware
* 6.1 Mehrwertsteuer auf vollen Mehrwertsteuer hart kodieren
* 7. Die Klasse Ware soll nicht mehr instanziiert werden. Sie soll
* abstrakt werden
*/ /** * Der aktuelle Mehrwertsteuersatz 2010. * Er liegt zur Zeit bei {@value}. * * @since 1.0 */

private String isbn;
private Buch alteAuflage;
/**
* Referenz aus alte Auflage
* @return
*/
public Buch getAlteAuflage() { return alteAuflage; }
/**
* zuweisen einer alten Auflage
* @param alteAuflage
*/
public void setAlteAuflage(Buch alteAuflage) {
this.alteAuflage = alteAuflage;
}
/**
* Auslesen der ISBN-Nummer (eine Zeichenkette)
* @return
*/
public String getISBN() { return isbn; }
/**
* Konstruktor fuer die Klasse Ware
* @param n der Name der Ware
* @param np der Nettorpreis
* @param hmws halber Mehrwertsteuersatz für Ware gueltig
*/
public Buch(String n, double np, String myISBN) {
super(n,np,true);
isbn=myISBN;
// zusätzliche Initialiserungen....
}
/**
* tiefes Vergleichen. Berücksichtigt beim Namen nicht
* die Groß/kleinschreinung. Berücksichtigt rekursiv die
* Empfehlungen
* @param dieAndereWare
* @return wahr wenn beide Waren dem Vergleich entsprechen.
*/
public boolean equals(Buch dieAndereWare) {
boolean result;
result = super.equals((Ware)dieAndereWare)
&& (isbn.equals(dieAndereWare.isbn))
&& (((alteAuflage == null) && (dieAndereWare.alteAuflage == null))
|| ((alteAuflage != null)
&& (dieAndereWare.alteAuflage != null)
&& (alteAuflage.equals(dieAndereWare.alteAuflage))
)
);
return result;
}
/**
* Ausdrucken aller Werte auf der Konsole mit vorgebener Einrueckung
* für Empfehlungen
* @param einruecken eingerueckte Stellen für Empfehlungen
*/
protected void drucken(int einruecken) {
super.drucken(einruecken);
String leerStellen = "";
for (int i = 0; i < einruecken; i++) {
leerStellen = leerStellen + " ";
}
System.out.println(leerStellen + "ISBN:" +isbn);
if (alteAuflage != null) {
System.out.println(leerStellen + "Alte Auflage:");
alteAuflage.drucken(einruecken + 2);
}
}
}

Klasse Main

Die Instanzen der Klasse Buch können jetzt in das Lager eingefügt werden

package block9;
/**
* Eine Hilfsklasse zur Implementierung eines Hauptprogramms
* @author sschneid
*/
public class Main { /** * Die Methode wareTesten testet die Implementierung der * von Waren mit tiefem Kopieren, tiefem Vergleichen. * Sie nutzt nicht die Vererbung aus. */

public static void warenTesten() {
Ware ware1, ware2;
Lager dasLager;
// Testen der Klasse Ware
ware1 = new Ware("Zeitung", 12.12, true);
ware1.drucken();
double p = ware1.getNettoPreis();
// Generieren von Empfehlungen
ware2 = new Ware("Potter Band 1", 31.12, false);
ware2.generiereEmpfehlungen(7);
// Abfrage des Lagers
dasLager = Lager.getLager();
dasLager.einlagern(ware1, 0);
dasLager.einlagern(ware2, 1);
// Prüfen der Lagerbestände mit tiefem Vergleichen
Ware testWare = dasLager.holen(0);
if (testWare == ware2)
System.out.println("testware und ware2 sind identisch (gut)");
if (testWare.equals(ware2))
System.out.println("testware und ware2 sind inhaltsgleich (gut)");
// vollständiges Befüllen des Lager
for (int i = 0; i < 1000; i++) {
ware2 = new Ware("Band" + i + "B", 12.12, true);
ware2.generiereEmpfehlungen(7);
dasLager.einlagern(ware2);
}
System.out.println("Lager vollständig gefüllt mit "
+ dasLager.lagerGroesse() + " Waren.");
for (int i = 0; i < 1000; i++) {
ware2 = new Ware("Volume" + i + "B", 12.12, true);
ware2.generiereEmpfehlungen(7);
dasLager.einlagern(ware2,i);
}
System.out.println("Lager erneut vollständig gefüllt mit "
+ dasLager.lagerGroesse() + " Waren.");
}
/**
* Diese Methode dient zum Testen der Klasse Buch.
* Sie nutzt die Veererbung in Java aus.
*/
public static void buecherTesten() {
/*
* 2. Testen: Anlegen einer Testmethode für Bücher
* Erzeugen von Büchern
* Anlegen einer Referenz auf eine alte Auflage
* Drucken zweier Bücher
* 3. Frage: Wie kann man Bücher in das Lager einfügen?
* 5. Einfügen der Bücher in das Lager
* 8. Anpassen der Hauptroutine
* 8.1 Alle Instanzen vom Typ Ware sollen MusikCDs werden da die Klasse
* Ware jetzt abstrakt ist.
*/
Lager dasLager;
dasLager = Lager.getLager();
Buch buch1 = new Buch("Das Grauen",22.22,"9876543");
Buch buch2 = new Buch("Das Kapital",33.33,"9876543");
buch1.setAlteAuflage(buch2);
buch1.drucken();
dasLager.einlagern(buch1);
dasLager.einlagern(buch2);
dasLager.drucken();

}

/**
* Das Hauptprogramm
*
* @param args
*/
public static void main(String[] args) {

//warenTesten();
buecherTesten();
}
}

Stufe 3: Klasse MusikCD

Klasse MusikCD

package block9;
/**
* Dies ist die Dokumentation der Klasse Ware. Ware dient zum Verwalten von Gütern
* mit Preisen und Namen in einem Lager.
* @author Stefan Schneider
* @version 1.3
* @see Lager
*/
public class MusikCD extends Ware {
/*
* 6. Erzeugen er Klasse MusikCD durch Kopieren der Klasse Ware
* 6.1 Mehrwertsteuer auf vollen Mehrwertsteuer hart kodieren
* 7. Die Klasse Ware soll nicht mehr instanziiert werden. Sie soll
* abstrakt werden
*/
/**
* Konstruktor fuer die Klasse Ware
* @param n der Name der Ware
* @param np der Nettorpreis
* @param hmws halber Mehrwertsteuersatz für Ware gueltig
*/
public MusikCD(String n, double np) {
super(n,np,false);
}
/**
* tiefes Vergleichen. Berücksichtigt beim Namen nicht
* die Groß/kleinschreinung. Berücksichtigt rekursiv die
* Empfehlungen
* @param dieAndereWare
* @return wahr wenn beide Waren dem Vergleich entsprechen.
*/
public boolean equals(MusikCD dieAndereWare) {
return super.equals((Ware)dieAndereWare);
}
}

 Klasse Main

package block9.stufe3;
/**
* Eine Hilfsklasse zur Implementierung eines Hauptprogramms
* @author sschneid
* @version 1.3
*/
public class Main {
/**
* Die Methode wareTesten testet die Implementierung der
* von Waren mit tiefem Kopieren, tiefem Vergleichen.
* Sie nutzt nicht die Vererbung aus.
*/
public static void warenTesten() {
Ware ware1, ware2;
Lager dasLager;
// Testen der Klasse Ware
ware1 = new Ware("Zeitung", 12.12, true);
ware1.drucken();
double p = ware1.getNettoPreis();
// Generieren von Empfehlungen
ware2 = new Ware("Potter Band 1", 31.12, false);
ware2.generiereEmpfehlungen(7);
// Abfrage des Lagers
dasLager = Lager.getLager();
dasLager.einlagern(ware1, 0);
dasLager.einlagern(ware2, 1);
// Prüfen der Lagerbestände mit tiefem Vergleichen
Ware testWare = dasLager.holen(0);
if (testWare == ware2)
System.out.println("testware und ware2 sind identisch (gut)");
if (testWare.equals(ware2))
System.out.println("testware und ware2 sind inhaltsgleich (gut)");
// vollständiges Befüllen des Lager
for (int i = 0; i < 1000; i++) {
ware2 = new Ware("Band" + i + "B", 12.12, true);
ware2.generiereEmpfehlungen(7);
dasLager.einlagern(ware2);
}
System.out.println("Lager vollständig gefüllt mit "
+ dasLager.lagerGroesse() + " Waren.");
for (int i = 0; i < 1000; i++) {
ware2 = new Ware("Volume" + i + "B", 12.12, true);
ware2.generiereEmpfehlungen(7);
dasLager.einlagern(ware2,i);
}
System.out.println("Lager erneut vollständig gefüllt mit "
+ dasLager.lagerGroesse() + " Waren.");
}
/**
* Diese Methode dient zum Testen der Klasse Buch.
* Sie nutzt die Veererbung in Java aus.
*/
public static void buecherTesten() {
/*
* 2. Testen: Anlegen einer Testmethode für Bücher
* Erzeugen von Büchern
* Anlegen einer Referenz auf eine alte Auflage
* Drucken zweier Bücher
* 3. Frage: Wie kann man Bücher in das Lager einfügen?
* 5. Einfügen der Bücher in das Lager
* 8. Anpassen der Hauptroutine
* 8.1 Alle Instanzen vom Typ Ware sollen MusikCDs werden da die Klasse
* Ware jetzt abstrakt ist.
*/
Lager dasLager;
dasLager = Lager.getLager();
Buch buch1 = new Buch("Das Grauen",22.22,"9876543");
Buch buch2 = new Buch("Das Kapital",33.33,"9876543");
buch1.setAlteAuflage(buch2);
buch1.drucken();
dasLager.einlagern(buch1);
dasLager.einlagern(buch2);
dasLager.drucken();
}
/**
* Diese Methode dient zum Testen der Klasse Buch.
* Sie nutzt die Veererbung in Java aus.
*/
public static void CDsTesten() {
/*
* 2. Testen: Anlegen einer Testmethode für Bücher
* Erzeugen von Büchern
* Anlegen einer Referenz auf eine alte Auflage
* Drucken zweier Bücher
* 3. Frage: Wie kann man Bücher in das Lager einfügen?
* 5. Einfügen der Bücher in das Lager
* 8. Anpassen der Hauptroutine
* 8.1 Alle Instanzen vom Typ Ware sollen MusikCDs werden da die Klasse
* Ware jetzt abstrakt ist.
*/
Lager dasLager;
dasLager = Lager.getLager();
MusikCD cd1 = new MusikCD("Thriller",8.88);
MusikCD cd2 = new MusikCD("Peter und der Wolf",9.99);
cd1.setEmpfehlung(cd2);
cd1.drucken();
dasLager.einlagern(cd1);
dasLager.einlagern(cd2);
dasLager.drucken();
}
/**
* Das Hauptprogramm
*
* @param args
*/
public static void main(String[] args) {
//warenTesten();
buecherTesten();
CDsTesten();
}
}

 

8.8 Lernziele

Am Ende dieses Blocks können Sie:

  • ... die Konzepte der Spezialisierung und Verallgemeinerung im Kontext der Vererbung erklären und anwenden
  • ... die Oberklasse einer Klasse mit Hilfe des Schlüsselworts extends identifizieren
  • ... alle Methoden und Attribute einer Klasse nennen
  • ... vererbte Methoden und Atribute einer Klasse von lokalen Methoden und Atrributen unterscheiden
  • ... in UML die Vererbungsbeziehung zweier Klassen erkennen
  • ... das Schlüsselwort super nutzen um gezielt einen Konstruktor der Oberklasse zu nutzen.
  • ... im Quellcode bei der Benutzung eines new Operators die Aufrufabfolge der beteiligten Konstruktoren analysieren und beschreiben
  • ... das Konzept des "Überschreibens" von Methoden und Attributen erklären, erkennen und nutzen
  • ... abstrakte und finale Klassen nutzen und Beispiele für eine vorteilhafte Anwendung nennen
  • ... das Zusammenspiel von abstrakten Methoden und abstrakten Klassen erklären und anwenden

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten:

9. Polymorphie

Der Begriff Polymorphie

Polymorphie nach Wikipedia: (gr. πολυμορφία Polymorphia) Vielgestaltigkeit

Durch das Konzept der Vererbung sollen Anwendungen mit Objekten von Unterklassen ohne explizites Wissen über die Unterklassen umgehen können.

Objekte von Unterklassen können sich aber anders verhalten wenn die Methoden der Oberklasse in den entsprechenden Unterklassen überschrieben und neu implementiert wurden. Objekte einer Klassenhierarchie können daher abhängig von der Unterklasse verschieden Methodenimplementierungen verwenden und sich polymorph (Vielgestaltig) verhalten.

Das "Liskov Substitution Principle"

Barbara Liskov führte 1987 das nach ihr benannte Prinzip welches sich mit der Substitution (Ersetzbarkeit) in der objektorientierten Programmierung beschäftigt:

Wenn eine Klasse S eine Unterklasse(Subtyp) von T ist, können alle Instanzen von T (Oberklasse) durch Instanzen von S (Unterklasse) ersetzt werden ohne dass dich das sich Verhalten des Programmes ändert.

 Eng mit diesem Prinzip ist auch Betrand Meyer's in der Programmiersprache Eiffel umgesetztes "Design by contract" verwandt.

9.1 Polymorphes Verhalten bei Vererbung und Erweiterung

 Der Aspekt der Erweiterung bei der Vererbung ermöglicht problemlos polymorphes Verhalten von Objekten.

Im unten gezeigten Beispiel wird eine Klassenhierarchie verwendet in der in jeder Unterklasse ein Attribut und eine Methode hinzugefügt wird

Ein Programm welches nur Instanzen der Klasse Kraftwagen kennt hat keine Probleme Instanzen von LastKraftwagen oder Bus zu verwenden. Die Instanzen besitzen alle ein Attribut nummernschild und eine Methode getKennZeichen().

Polymorphismus funktioniert in Java nur bei Attributen und Methoden die von einer gemeinsamen Basisklasse geerbt worden sind!

Klassen mit namensgleichen Attributen und Methoden profitieren nicht vom Polymorphismus. Der Übersetzer kontrolliert die Klassenhierarchie beim Übersetzen und unterbindet Zuweisungen zwischen Typen(Klassen) die namensgleiche Methoden besitzen, diese aber nicht von einer gemeinsamen Klasse erben.

Dieses Konzept lässt sich in Java wie folgt umsetzen:

import Kraftwagen;
import LastKraftwagen;
import Bus;
...
Kraftwagen[] flotte = new Kraftwagen[3];

flotte[0] = new Kraftwagen();
flotte[1] = new LastKraftwagen();
flotte[2] = new Bus();
...
for (i=0; i<3; i++) {
   // Das Programm kennt im Moment nur Kraftwagen!
   System.out.println(flotte[i].getKennZeichen());
   }
...

Das Programm kann innerhalb der for-Schleife mit Instanzen der Klasse LastKraftwagen und Bus umgehen. Es muss sich aber auf die in der Klasse Kraftwagen bekannten Eigenschaften des Objekts beschränken.

9.2 Polymorphie beim Überschreiben

Java erlaubt es Methoden von Oberklassen in Unterklassen zu verdecken und neu zu implementieren. Hierdurch können Methoden an die Anforderungen einer spezialisierten Klasse angepasst werden. Man spricht hier vom Überschreiben von Methoden wenn die folgende Definition gilt:

Definition
Überschreiben einer Methode
Bei einer überschriebenen Methode müssen die Signatur und der Rückgabewert der überschriebenen Methode identisch mit der Signatur und dem Rückgabewert der Methode einer Oberklasse sein.

Beim Überschreiben von Methoden und Attributen ist das "Liskov Substitution Principle" nicht mehr automatisch garantiert. Hier wir die überschreibende Methode der Unterklasse an die Stelle der überschriebenen Methode der Oberklasse bei allen Instanzen der Unterklasse verwendet. Es wird ein anderer Code ausgeführt. Dieser Code könnte theoretisch auch zu einem anderen Verhalten führen.

Anbei das Beispiel einer Klasse Point, die einen zweidimensionalen Punkt modelliert und der Klasse CircleIsPoint die von Punkt erbt und zusätzlich ein Attribut zur Verwaltung des Radius hat:

Klasse Point

public class Point {
    private double x;
    private double y;

    public Point(double xx, double yy) {
        x = xx;
        y = yy;
    }
    public void setXY(double xx, double yy) {
        x = xx;
        y = yy;
    }
    public double getX() { return x;}
    public double getY() { return y;}

    public void print() {System.out.println(toString());}
    public String toString() {return ("x: " + x + " y: " + y);}
}

Klasse CircleIsPoint

public class CircleIsPoint extends Point{
    private double radius;

    public CircleIsPoint(double xx, double yy, double r) {
        super(xx,yy);
        radius=r;
    }
    public double getRadius() {return radius;}
    public void setRadius(double r) {radius=r;}

    public void print() { //Uberschriebene Methode
        System.out.println(super.toString() + " radius: " + radius);
    }
}

Ein Hauptprogramm

public class Main {
    public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1] = new Point (7.7, 8.8);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();
    }
}

Bei der Ausführung des Programms ergibt sich die folgende Objekstruktur:

Die Ausgabe ist die folgende:

x: 2.2 y: 3.3
x: 7.7 y: 8.8
x: 4.4 y: 5.5 radius: 6.6

Die überschriebene print() Methode erzeugt eine sinnvolle Ausgabe, die zusätzlich den Radius ausgibt ohne das das komsumierende Hauptprogramm "weiß", das es sich um eine überschriebene Methode handelt.

Da der Begriff einer "sinnvollen" Erweiterung nicht von Werkzeugen abgeprüft werden kann, ergibt sich ein Risiko des Missbrauchs. Ein Beispiel ist die folgende Implementierung der Methode print() in der Klasse CircleIsPoint().

public class CircleIsPoint extends Point{
    ...
    public void print() {
        System.out.println("Methode print() muss noch implementiert werden...");
    }
}

Der Unterschied zwischen der recht menschlichen Implementierung und der Implementierung die auch den Radius ausgibt ist nur gering.

Es gibt im schlimmsten Fall Programme, die diesen Text lesen und weder die zusätzlichen Information über den Radius interpretieren können, noch mit einem anderen Text umgehen können.

Ist eine Klasse und/oder die Methode nicht als final deklariert, muss ein Konsument dieser Klasse mit einem Überschreiben von Methoden rechnen!

Hiermit ergibt sich für den Polymorphismus beim Überschreiben die folgende Abschätzung:

Vorteil Nachteil
Kostensenkung Entwicklung: Existierender Code kann unmodifiziert mit neuen Unterklassen umgehen Kostensteigerung QA und Service: Der Polymorphismus kann durch Missbrauch zu unerwarteten Effekten führen, die erst zur Laufzeit auftreten.

Begriffsbestimmung: Überschriebene und überladene Methoden

Es gibt in Java überschriebene (englisch overriding) und überladene (englisch overloading) Methoden. Beide Begriffe werden leicht verwechselt!

  Überschriebene Methode Überladene Methode
Methodenname identisch identisch
Eingabeparameterliste identisch (Typen, Anzahl, Folge) unterschiedlich!
Rückgabewert identisch nicht relevant
Klassenbeziehung der Methoden Methoden gehören zu unterschiedlichen Klassen die aber in einer Ober- Unterklassebeziehung stehen Methoden gehören zur gleichen Klasse

Zur vollständigen Begriffsverwirrung: Methoden können andere Methoden der gleichen Klasse überladen während sie eine Methode der Oberklasse überschreiben.

Siehe die Methode mult() im folgenden Beispiel:

Überschriebene und überladene Methoden 

9.3 Casting: Typkompabilität bei Zuweisungen

Bisher wurden bereits implizit das Konzept von Referenzvariablen verwendet, die wechselseitig auf Objekte ihres Typs (Klasse) oder auf Objekte von Unterklassen gezeigt haben.

Java ist trotz des Polymorphismus eine streng typisierte Sprache. Das bedeutet:

Strenge Typisierung
Der Übersetzer erlaubt schon beim Übersetzen nur typsichere Zuweisungen und Operationen (und meldet unsichere Operationen als Fehler)

Dieses Konzept steigert die Qualität der ausgelieferten Anwendungen. Fehler aufgrund inkorrekter Typen können beim Anwender zur Laufzeit nicht mehr auftreten. Der Übersetzer zwingt den Entwickler nur typsichere Operationen durchzuführen. Eine typsichere Operation ist zum Beispiel das Aufrufen einer Methode von der man sicher weiß, das sie für eine gegebene Sprache existiert.

"Casting" in der Programmierung

engl. Casting hier: Formen, gießen, krümmen

Das implizite oder explizite Ändern des Typs eines Objekts oder Datenstruktur bei einer Zuweisung oder der Methodenauswahl

Der "Upcast"

Ein "Upcast"  ist eine Typkonversionen einer Instanz einer Unterklasse auf den Typ einer Oberklasse. Sie wurden bisher implizit in den vorhergehenden Beispielen verwendet, wie zum Beispiel bei der Klasse CircleIsPoint die aus der Klasse Point abgeleitet wurde:

import Point;
import CircleIsPoint;
...
 Point p1         = new Point (2.2, 3.3);
 CircleIsPoint c1 = new CircleIsPoint(4.4,5.5,6.6);

Point p2 = c1; //Das explizite Wissen über den Objekttyp CircleIsPoint geht verloren
...
p1.print();
p2.print();

Die Zuweisung von c1 auf p1 ist

  • sicher da sich alle Instanzen der Klasse CircleIsPoint wie Instanzen der Klasse Point verhalten
  • vom Javaübersetzer erlaubt da sicher!
Upcast
Der "Upcast" ist eine sichere Typkonversation da spezialisierte Klasseninstanzen auf Referenzen von allgemeineren Klassen zugewiesen werden.

 Der "Downcast" oder unsichere "Cast"

Das umgekehrte Prinzip gilt jedoch nicht.

Eine Instanz einer allgemeineren Klasse hat nicht alle Eigenschaften einer spezialisierten Klasse. Das folgende Beispiel wird nicht vom Javaübersetzer akzeptiert:

import Point;
import CircleIsPoint;
...
 Point p1         = new Point (2.2, 3.3);
 CircleIsPoint c1 = new CircleIsPoint(4.4,5.5,6.6);

CircleIsPoint c2 = p1;
...
double r1 = c1.getRadius();
double r2 = c2.getRadius();

Eine Instanz vom Typ Point hat nicht die Eigenschaften einer Instanz vom Type CircleIsPoint. Im UML Diagramm sieht der obige Versuch wie folgt aus:

 

Der Übersetzer erzeugt schon beim Versuch der Zuweisung eine Fehlermeldung:

Main.java:30: incompatible types
found   : l9vererbungassoziation.Point
required: l9vererbungassoziation.CircleIsPoint
CircleIsPoint c2 = p1;
1 error

Es gibt jedoch auch Fälle in denen eine Typkonversion erzwungen werden muss. Im verwendeten Beispiel ist dies der Fall wenn man einen Kreis in einem Feld von Point verwaltet. Wenn man die Referenz benutzt um den Radius auszulesen ergibt sich folgendes Problem wenn man zum Auslesen den Typ CircleIsPoint wie folgt verwendet:

public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1]  = new Point (2.22, 3.33);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();

        CircleIsPoint cip1 = pf[2];
        double r2 = cip1.getRadius();
    }

Der Übersetzer meldet den folgenden Fehler da die beiden Typen keine sichere Typkonversion zulassen:

Main.java:24: incompatible types
found   : l9vererbungassoziation.Point
required: l9vererbungassoziation.CircleIsPoint
        CircleIsPoint cip1 = pf[2];
1 error

Dieses Problem kann man versuchen zu Lösen in dem man die Variable cip1 mit dem Typ Point versieht:

public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1]  = new Point (2.22, 3.33);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();

        Point cip1 = pf[2];
        double r2 = cip1.getRadius();
}

Dies Maßnahme erlaubt das Auslesen der Feldvariable. Sie scheitert jedoch eine Zeile weiter beim Aufruf der Methode getRadius(). Die Klasse Point kennt keine Methode getRadius()...

Main.java:25: cannot find symbol
symbol  : method getRadius()
location: class l9vererbungassoziation.Point
        double r2 = cip1.getRadius();
1 error

Die Lösung besteht in einer expliziten Typkonversion (" cast")  mit Hilfe einer runden Klammer und Angabe des Typs:

public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1] = new Point (2.22, 3.33);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();

        CircleIsPoint cip1 = (CircleIsPoint)pf[2];
        double r2 = cip1.getRadius();
    }

Mit dieser Typkonversion zwingt der Entwickler den Übersetzer zur Zuweisung. Der Übersetzer kann sich dieses Wissen nicht von alleine herleiten. Die Verantwortung liegt jetzt beim Entwickler. Würde der Entwickler den folgenden Cast erzwingen, würde das Programm auch fehlerfrei übersetzen:

CircleIsPoint cip1 = (CircleIsPoint)pf[1];
double r2 = cip1.getRadius();

Beim Aufruf der Methode getRadius() würde jetzt jedoch zur Laufzeit eine Ausnahme (Exception) geworfen werden. Der Entiwckler hat in diesem Fall den Übersetzer zu einem inkorrekten Übersetzungsvorgang gezwungen.

Der Fehler tritt jetzt erst zur Laufzeit auf. Dies kann für den Entwickler sehr teuer werden, da die Software den Fehler eventuell erst beim Kunden zeigt.

Zusammenfassung explizite Typkonversion (Downcast)

Die Syntax 

KlasseC variable2;
...
KlasseA variable1 = (KlasseB) variable2;

erlaubt die Zuweisung einer Objektreferenz variable2 auf eine Objektreferenz variable1 solange der Typ KlasseB in der Klammer eine sichere Konvertierung von KlasseB auf KlasseA erlaubt. Diese Zuweisung funktioniert unabhängig vom tatsächlichen Typ (KlasseC) von variable2.

Explizite Typkonversionen sind in manchen Fällen aufgrund des Polymorphismus notwendig. Sie haben jedoch eine Reihe von Konsequenzen:

  • Der Entwickler übernimmt explizit die Verantwortung für eine korrekte Konversion. Diese hat normalerweise der Übersetzer!
  • Fehler werden nicht mehr zum Übersetzungszeitpunkt erkannt.
  • Fehler werden erst zur Laufzeit erkannt. Sie führen zur Ausnahmebehandlung zur Laufzeit. Die Anwendung beendet sich falls diese Ausnahmen nicht gefangen und behandelt werden.

Downcasts sind problematische, explizite Typkonvertierungen.

Ihre Verwendung sollte wenn möglich vermieden werden, da bestimmte Fehlerklassen zum Übersetzungszeitpunkt nicht geprüft werden können.

 

9.4 Sonderfall: überschriebene Attribute

Der Polymorphismus funktioniert in Java nicht im Fall von überschriebenen Attributen die einen unterschiedlichen Typ besitzen.

Bei überschriebenen Attributen wendet Java den Typ der Referenz an um das Attribut zu bestimmen.

Im Beispiel der Klassen Point und CircleIsPoint wird dies mit Hilfe des Attributs layer gezeigt:

Klasse Point

public class Point {
    ...
    public double layer = 5D;
    ...
    public void print() {System.out.println(toString());}
    public String toString() {return ("x: " + x + " y: " + y);}
}

Klasse CircleIsPoint

public class CircleIsPoint extends Point{
    ...
    public int layer = 1;
    ...
    public void print() {
        System.out.println(super.toString() + " radius: " + radius);
    }
}

Hauptprogramm

public class Main {
    public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1] = new Point (2.22, 3.33);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();

        Point p2 = pf[2];
        p2.print(); // toString von CircleIsPoint wird benutzt
        double t1 = 1000.0 + p2.layer; // layer von Point wird benutzt
        System.out.println(t1);

        CircleIsPoint cip = (CircleIsPoint)pf[2];
        cip.print(); // toString von CircleIsPoint wird benutzt
        double t2 = 1000.0 + cip.layer; // price von CircleIsPoint wird benutzt
        System.out.println(t2);
    }
}

Hiermit ergeben sich die folgenden Belegungen:

Konsolenausgabe

x: 2.2 y: 3.3
x: 2.22 y: 3.33
x: 4.4 y: 5.5 radius: 6.6
x: 4.4 y: 5.5 radius: 6.6
1005.0
x: 4.4 y: 5.5 radius: 6.6
1001.0

Im ersten Fall (p2) wird das Attribut layer vom Typ double benutzt. Im Fall der Variable cip vom Typ CircleIsPoint wird die Variable layer vom Typ int benutzt.

Es kommt kein Polymorphismus zum Tragen, die Auswahl des Attributs richtet sich nach dem Typ der Referenz!

Tipp: Durch das strikte Anwenden von Datenkapselung greift man nur noch über Zugriffsmethoden auf Attribute zu. Hierdurch wird der Polymorphismus auch beim Zugriff auf Attribute (intuitiv) gewährleistet. Das Ausnutzen des nicht polymorphen Verhalten betrifft dann nur noch Sonderfälle in denen man dies explizit wünscht.

9.5 instanceof-Operator

Normalerweise sind zur Laufzeit eines Programmes bereits alle Typen bekannt und wurden vom Übersetzer schon beim Übersetzen des Programms kontrolliert.

Durch die Möglichkeit der Typkonvertierung durch "casten", und "Upcasting" kann man die genaue Information über einen Typ einer Instanz zur Laufzeit verlieren. Das bedeutet, sie ist im Kontext einer Methode oder Klasse nicht mehr bekannt.

Zur Bestimmung eines Typs zur Laufzeit bietet Java den instanceof Operator an mit dem man Instanzen auf Ihren Typ prüfen kann.

Im vorhergehenden Beispiel mit den Klassen Point und CircleIsPoint kann man den instanceof Operator verwenden um einen Laufzeitfehler bei einer cast-Operation zu vermeiden:

public class Main {

    public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1]  = new Point (2.22, 3.33);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();

        double r2 = 0;
        CircleIsPoint cip1;
        if (pf[2] instanceof CircleIsPoint) {
            cip1 = (CircleIsPoint)pf[2];
            r2 = cip1.getRadius();
            }
    }
}

Weiterführend: getClass() Methode

Die Javalaufzeitumgebung verfügt über ein vollständiges, dynamisches System zur Verwaltung von Metainformationen inklusive der Informationen über eine Klasse.

Hierzu dient die Klasse Class die man über die Methode Object.getClass() benutzen kann. Die Methode getClass() wird an alle Klassen vererbt. Hiermit kann man Informationen über die Klasse selbst, wie zum Beispiel ihren Name und die Oberklasse auslesen:

public class Main {

    public static void main(String[] args) {
        Point[] pf = new Point[3];
        pf[0] = new Point (2.2, 3.3);
        pf[1]  = new Point (2.22, 3.33);
        pf[2] = new CircleIsPoint(4.4,5.5,6.6);

        pf[0].print();
        pf[1].print();
        pf[2].print();

        double r2 = 0;
        CircleIsPoint cip1;
        Class myClass = pf[2].getClass();
        Class mySuperClass = myClass.getSuperclass();
        System.out.println("pf[2] Klassenname: " + myClass.getSimpleName());
        System.out.println("pf[2] Oberklasse: "  + mySuperClass.getSimpleName());
        if (pf[2] instanceof CircleIsPoint) {
            cip1 = (CircleIsPoint)pf[2];
            r2 = cip1.getRadius();
            }
    }
}

Die Konsolenausgabe des Programmes ist die Folgende:

x: 2.2 y: 3.3
x: 2.22 y: 3.33
x: 4.4 y: 5.5 radius: 6.6
pf[2] Klassenname: CircleIsPoint
pf[2] Oberklasse: Point

9.6 Übungen

Duke als Boxer

9.6.1 Haustierpolymorphismus

Haustierklassen

Klasse Haustier

  • abstrakte Klasse
  • Attribute
    • privat: name
    • privat: steuerpflichtig
    • privat: jahreskostenTierarzt
  • Methoden
    • Lese und Schreibmethode für Namen
    • Lesemethode für Steuerpflicht
    • Konstruktor der Namen, Steuerpflicht und Jahreskosten Tierarzt erfasst
    • Methode beschreibung() Typ String. Gibt Text mit Namen und eventueller Steuerpflicht zurück.

Klasse Hund

  • abgeleitet aus Haustier
  • Hunde sind steuerpflichtig
  • privates Attribut: rasse
  • Methoden
    • Konstruktor der Namen, Jahreskosten Tierarzt und Rasse erfasst
    • Lesemethode für Rasse
    • Überschriebene Methode beschreibung() die zusätzlich hundespezifische Daten zurück gibt

Klasse Katze

  • abgeleitet aus Haustier
  • Katzen sind nicht steuerpflichtig
  • privates Attribut: lieblingsVogel (Referenz auf Vogel)
  • Methoden
    • Konstruktor der Namen des Katze, Jahreskosten Tierarzt und die Referenz des Lieblingsvogels erfasst
    • vogel() Ausgabe ist der Name des Vogels als Zeichenkette
    • Überschriebene Methode beschreibung() die zusätzliche katzenspezifische Daten zurück gibt.
    • setzen des Lieblingsvogelattribut

Klasse Vogel

  • abgeleitet aus Haustier
  • Vögel sind nicht steuerpflichtig
  • privates Attribut: singvogel (boolscher Wert)
  • Methoden
    • Konstruktor der Namen des Vogels, JahresKosten Tierarzt und Singvogeleigenschaft erfasst
    • Lesemethode für Singvogeleigenschaft
    • Überschriebene Methode beschreibung() die zusätzliche vogelspezifische Eigenschaften ausgibt

Klasse TierTest

Schreiben Sie ein Hauptprogramm das folgende Aufgaben ausführt:

  • Verwaltung von mindestens 2 Haustieren pro Tierart einem gemeinsamen statischen Feld vom Typ Haustier
  • Methoden
    • populate(): Anlegen von mindestens 2 Tieren pro Tierart im statischen Haustierfeld
    • neuerLieblingsvogel(): Benutzen Sie einen zweiten Vogel, der aus dem Haustierfeld geholt und weisen Sie ihn einer Katze aus dem Haustierfeld zu
      • prüfen Sie vor den Zuweisungen, dass Sie eine Katze, beziehungsweise einen Vogel aus dem Feld ausgelesen haben
    • iterate(): Itererieren über das Feld und folgende Information ausdrucken
      • Typ des Tieres
      • Inhalt von beschreibung()
      • Summerien Sie die Tierarztkosten auf und geben Sie sie am Ende aus.
  • Benutzen Sie den unten angebenen Rumpf für Ihre Implementierung

package block9;
 
public class TierTest {
 
    private static Haustier hausTiere[];
 
    public static void main(String[] args) {
        populate();
        neuerLieblingsvogel();
        iterate();
    }
 
    public static void populate() {
        hausTiere = new Haustier[6];
 
        /* Implementierung */
    }
 
    public static void neuerLieblingsvogel() {
        /* Implementierung */
    }
 
    public static void iterate() {
        /* Implementierung */
}
}
 

Optional

Versuchen Sie eine Zuweisung einer Referenz eines inkompatiblen Typen als Lieblingvogel zu einer Katze. Die Routine soll durch casten übersetzen. Zur Laufzeit soll die Zuweisungweisungsmethode der Katze den Fehlversuch auf der Konsole dokumentieren

Tipp:

Die Methode .getClass() der Klasse Object liefert eine Referenz auf die Beschreibung der Klasse. Man kann diese Referenz direkt ausdrucken. Es wird der Name der Klasse inklusive eventueller Paketzugehörigkeit ausgedruckt.

Will man nur den Klassennamen ausdrucken, muss man von einer Instanz vom Typ Class die Methode .getSimpleName()aufrufen.

9.7 Lösungen

 9.7.1 Haustierpolymorphismus

Klasse Haustier

 
package block10.TierVererbung;
public abstract class Haustier {
 
    private String name;
    private boolean steuerpflichtig;
    private double kostenTierarzt;
 
    /**
     * Get the value of kostenTierarzt
     *
     * @return the value of kostenTierarzt
     */
    public double getKostenTierarzt() {
        return kostenTierarzt;
    }
 
    /**
     * Set the value of kostenTierarzt
     *
     * @param kostenTierarzt new value of kostenTierarzt
     */
    public void setKostenTierarzt(double kostenTierarzt) {
        this.kostenTierarzt = kostenTierarzt;
    }
 
 
    /**
     * Get the value of steuerpflichtig
     *
     * @return the value of steuerpflichtig
     */
    public boolean getSteuerpflichtig() {
        return steuerpflichtig;
    }
 
    /**
     * Set the value of steuerpflichtig
     *
     * @param steuerpflichtig new value of steuerpflichtig
     */
    public void setSteuerpflichtig(boolean steuerpflichtig) {
        this.steuerpflichtig = steuerpflichtig;
    }
 
 
    /**
     * Get the value of name
     *
     * @return the value of name
     */
    public String getName() {
        return name;
    }
 
    /**
     * Set the value of name
     *
     * @param name new value of name
     */
    public void setName(String name) {
        this.name = name;
    }
 
    public Haustier(String name, boolean steuerpflichtig, double kostenTierarzt) {
        this.name = name;
        this.steuerpflichtig = steuerpflichtig;
        this.kostenTierarzt = kostenTierarzt;
    }
 
    public String beschreibung() {
        String stpf = (steuerpflichtig) ? ", " : ", nicht ";
        String b = "Name :" + name
                + stpf + "steuerpflichtig, Kosten: "
                + kostenTierarzt;
        return b;
    }
 
 
}
 

Klasse Hund

 
package block10.TierVererbung;
 
/**
 *
 * @author sschneid
 */
public class Hund extends Haustier{
 
    private String rasse;
 
    /**
     * Get the value of rasse
     *
     * @return the value of rasse
     */
    public String getRasse() {
        return rasse;
    }
 
 
    public Hund(String name, double kostenTierarzt, String rasse) {
        super(name,true,kostenTierarzt);
        this.rasse = rasse;
 
    }
 
    public String beschreibung() {
        return super.beschreibung() + ", Rasse: " + rasse;
    }
}
 

Klasse Katze

package block10.TierVererbung;
 
public class Katze extends Haustier {
 
    private Vogel lieblingsVogel;
 
    public String vogel() {
        String vname;
        if (lieblingsVogel != null)
            vname = lieblingsVogel.getName();
        else vname = "keinen Vogel";
        return vname;
    }
/**
 * 
 * @param v setzen des Lieblingsvogel
 */
    public void setVogel(Vogel v) { lieblingsVogel=v;}
 
    public Katze(String name, double kostenTierarzt, Vogel lieblingsVogel) {
        super(name, false, kostenTierarzt);
        if ((lieblingsVogel !=null) && (lieblingsVogel instanceof Vogel))
            this.lieblingsVogel = lieblingsVogel;
 
    }
 
    public String beschreibung() {
        return super.beschreibung() + ", mag " + vogel();
    }
}
 

Klasse Vogel

package block10.TierVererbung;
 
/**
 *
 * @author sschneid
 */
public class Vogel extends Haustier{
 
    private boolean singvogel;
 
    /**
     * Get the value of singvogel
     *
     * @return the value of singvogel
     */
    public boolean getSingvogel() {
        return singvogel;
    }
 
    public Vogel(String name, double kostenTierarzt, boolean singvogel) {
        super(name, false, kostenTierarzt);
        this.singvogel = singvogel;
    }
 
   public String beschreibung() {
        String saenger = (singvogel) ? "ein" : "kein";
        return super.beschreibung() + ", ist "
                + saenger + " Singvogel";
    }
 
}
 

Klasse TierTest

package block10.TierVererbung;
 
public class TierTest {
 
    private static Haustier hausTiere[];
 
    public static void main(String[] args) {
        populate();
        neuerLieblingsvogel();
        iterate();
    }
 
    public static void populate() {
        hausTiere = new Haustier[6];
 
        hausTiere[0] = new Vogel("Hansi", 50.55f, true);
        hausTiere[1] = new Vogel("Piep", 50.44f, false);
        hausTiere[2] = new Hund("Waldi", 222.22f, "Dackel");
        hausTiere[3] = new Hund("Fiffi", 202.22f, "Terrier");
        hausTiere[4] = new Katze("Isis", 88.88f, (Vogel) hausTiere[0]);
        hausTiere[5] = new Katze("Napoleon", 77.77f, null);
    }
 
    public static void neuerLieblingsvogel() {
        Vogel v;
        Katze k;
        if ((hausTiere[1] instanceof Vogel)
                && (hausTiere[4] instanceof Katze)) {
            v = (Vogel) hausTiere[1];
            k = (Katze) hausTiere[4];
            k.setVogel(v);
        }
    }
 
    public static void iterate() {
        double kosten = 0;
        for (int i = 0; i < hausTiere.length; i++) {
            kosten += hausTiere[i].getKostenTierarzt();
            System.out.println(
                    "Art: " + hausTiere[i].getClass().getSimpleName()
                    + "; " + hausTiere[i].beschreibung());
        }
System.out.println("Gesamtjahrekosten "+ kosten +" Euro");
    }
}

Ausgaben:

Art: Vogel; Name :Hansi, nicht steuerpflichtig, Kosten: 50.55, ist ein Singvogel
Art: Vogel; Name :Piep, nicht steuerpflichtig, Kosten: 50.44, ist kein Singvogel
Art: Hund; Name :Waldi, steuerpflichtig, Kosten: 222.22, Rasse: Dackel
Art: Hund; Name :Fiffi, steuerpflichtig, Kosten: 202.22, Rasse: Terrier
Art: Katze; Name :Isis, nicht steuerpflichtig, Kosten: 88.88, mag Piep
Art: Katze; Name :Napoleon, nicht steuerpflichtig, Kosten: 77.77, mag keinen Vogel
Gesamtjahrekosten 692.08 Euro

9.8 Lernziele

Am Ende dieses Blocks können Sie:

  • ... den Begriff der Polymorphie und das "Liskov Substitution" Prinzip im Javakontext erklären und anwenden
  • ... erkennen wenn zwei Javaklassen polymorphe Methoden haben
  • ... das Konzept des Überschreibens in Java erklären und anwenden
  • ... überschriebene Methoden und überladene Methoden unterscheiden (Anmerkung: Fällt auch mir immer wieder schwer...)
  • ... erkennen was erfüllt sein muss um eine Methode zu überschreiben
  • ... die Typkonversionen "Upcasts" und "Downcasts" in Java erkennen und erklären welche Zuweisungen sicher sind und welche nicht
  • ... erklären was überschriebene Attribute von überschreibenen Methoden unterscheidet
  • ... verschiedene Methoden zur Bestimmung des Typs eines Objekts nennen.

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten:

 

10. Begleitende Themen

 Java und andere Frameworks

10.1 Java API

Das Java API ist eine plattformunabhängige Bibliothek die zum Javalaufzeitsystem gehört. Das Java API ist ein wichtiger Grund für die Popularität von Java. Man findet hier tausende von Klassen die bekannte Probleme korrekt und effizient lösen. Hieraus ergeben sich eine Reihe von Vorteilen

  • die Produktivität steigt
  • der selbstimplementierte Code wird geringer
    • die Qualität steigt
    • Testaufwände sinken
    • der Transport des Codes vereinfacht sich
  • die Klassen im Java API sind hocheffizient implementiert und untertützen oft zusätzliche Konzepte wie
    • Internationalisierung
    • Multithreading (Nebenläufigkeit)
  • Die Verwendung dieser Klassen garantiert auch, dass die Anwendungen auf den unterschiedlichsten Plattformen laufen.

Der beste Zugang zum Java API findet über die online Dokumentation von JDK Standard Edition 7  statt.

Vorsicht: Will man Anwendungen schreiben, die auch mit älteren JDKs funktionieren sollen so sollte man sich an die Schnittstellen der Klassen der älteren JDKs halten.

Das API wächst von Version zu Version und enthält eventuell Klassen und Methoden die in einer älteren Version von Java nicht enthalten sind.

Im Folgenden werden einige ausgewählte Klassen und Methoden des Java API vorgestellt. Die Liste ist nicht vollständig, sie enthält nützliche Klassen die im Rahmen dieses Kurses verwendet werden:

10.1.1 Konsolen Ein- und Ausgabe

Konsolenausgabe mit System.out

Die Klasse System gehört zum Paket java.lang und muss nicht mit einem Importbefehl deklariert werden. Man kann sie direkt benutzen. das Attribut System.out ist eine Instanz der Klasse PrintStream. Es erlaubt Ausgaben auf der Konsole in den folgenden Varianten:

  • System.out.println(a): a kann ein beliebiger Typ sein. Am Ende der Zeile wird ein Zeilenumbruch eingefügt. Die nächste Konsolenausgabe wird in einer neuen Zeile beginnen.
  • System.out.print(a): a kann ein beliebiger Typ sein. Am Ende der Zeile wird kein Zeilenumbruch angefügt. Man kann durch mehrere Aufrufe Ausgaben in der gleichen Zeile durchführen.
  • System.out.printf(formatString,a): a kann ein beliebiger Typ sein.Der String formatString erlaubt die Formatierung des Objekts a.
  • System.out.flush(): Stellt sicher das die gesamte Ausgabe auch gedruckt wird. Daten werden oft in einem "Stream" gepuffert da einzelnes abarbeiten der Daten ineffizient ist.

Konsoleneingaben mit System.in

Einlesen eines Texts mit Hilfe von System.in und eines InputStreamReader. Dies geschieht am besten mit einer eigenen Methode:

import java.io;
...
public static String StringLesen (){
     // liest einen vom Benutzer eingegebenen Text (String) ein
      BufferedReader keyboard = 
         new BufferedReader( new InputStreamReader(System.in));
      try {
         return keyboard.readLine();
      } catch (IOException e) {
         throw new RuntimeException( e );
      }
}

Konsoleneingaben mit java.util.Scanner

Die Klasse Scanner erlaubt das einfache lesen von primitiven Typen und Zeichenketten mit Hilfe regulärer Ausdrücke.

Man kann z.Bsp eine Ganzzahl(Integer) wie folgt einlesen:

Scanner sc = new Scanner(System.in);
int i = sc.nextInt();

Der Scanner kann auch mit anderen Begrenzungen als Leerzeichen umgehen. Hier ein Beispiel mit dem Text "Trenner" als Begrenzung:

String input = "47 Trenner 11 Trenner Montag Trenner Dienstag Trenner";
     Scanner s = new Scanner(input).useDelimiter("\\s*Trenner\\s*");
     System.out.println(s.nextInt());
     System.out.println(s.nextInt());
     System.out.println(s.next());
     System.out.println(s.next());
     s.close();

Wird das folgende Ergebnis liefern:

47
11
Montag
Dienstag

Parsen von Übergabeparametern mit einer eigenen Methode

Im folgenden Beispiel wird gezeigt wie man eine beliebige Anzahl von Eingabeparameter in einer eigenen Methode einliest und in Ganzzahlen umwandelt.

public class MethodenDemo {

    public static void main(String[] args) {

        int[] zahlenFeld;
        zahlenFeld = ganzZahlenEingabe(args);
        System.out.println(args.length + " arguments found");
        for (int i = 0; i < zahlenFeld.length; i++) {
            System.out.println("zahlenFeld[" + i + "] = " + zahlenFeld[i]);
        }
    }

    public static int[] ganzZahlenEingabe(String[] myArgs) {
        int[] z = new int[myArgs.length];
        if (myArgs.length > 0) {
            try {
                for (int i = 0; i < myArgs.length; i++) {
                    z[i] = Integer.parseInt(myArgs[i]);
                }
            } catch (NumberFormatException e) {
                System.err.println("Input Error: " + e.getMessage());
                System.exit(1);
            }
        }
        return z;
    }

}

Das Programm erzeugt die folgenden Ausgaben:

$ java MethodenDemo 33 22 11
3 arguments found
zahlenFeld[0] = 33
zahlenFeld[1] = 22
zahlenFeld[2] = 11

10.1.2 Swing

 Die folgenden Seiten zeigen einen stark vereinfachten Überblick über die wichtigsten Swing Klassen und Methoden. Es werden hier nur die Klassen und Methoden dokumentiert, die im Rahmen des Kurses benötigt werden.

JFrame

 Die Klasse JFrame erlaubt das Erzeugen von eigenen Fenstern für eine Javaanwendung.

Die API Dokumentation zur Klasse JFrame enthält die vollständige Dokumentation aller Methoden dieser Klasse.

Im Folgenden werden die wichtigsten Methoden (für den Kontext des Kurses) in ihrer natürlichen Benutzungsreihenfolge vorgestellt.

JFrame Übersicht
Methode Beschreibung Geerbt von
Konstruktor: JFrame() Erzeugen eines (noch) unsichtbaren Fensters ohne einen Titel -
Konstruktor: JFrame(String title) Erzeugen eines (noch) unsichtbaren Fensters mit einem Titel -
void setSize(int breite, int hoehe) Setzen der Fenstergröße durch Breite und Höhe in Pixel java.awt.Window
setLayout(LayoutManager manager) Durch das Setzen eines Layout Managers wird die Anordnung der Komponenten vorbestimmt  -
Component add(Component comp) Hinzufügen einer Komponente zum Container  java.awt.Container 
setJMenuBar(JMenuVar  menubar) Hinzufügen einer Menüleiste zum Fenster -
setDefaultCloseOperation(int Operation) Aktion die beim Schließen des Fensters geschieht. Mögliche Konstanten als Parameter: DO_NOTHING_ON_CLOSE, HIDE_ON_CLOSE, DISPOSE_ON_CLOSE, EXIT_ON_CLOSE -
pack() Berechnen des Layouts des Fensters java.awt.Window 
setVisible(boolean visible) Erlaubt ein Fenster anzuzeigen oder auszublenden java.awt.WIndow

 

Beispiel

... eines sehr einfachen Fensters welches mit der Klasse JFrame erzeugt wurde:

import javax.swing.JFrame;
import javax.swing.JLabel; ... JFrame hf = new JFrame();
// 1. Gewünschte Größe setzen
// 1. Parameter: horizontale Größe in Pixel
// 2. Parameter: vertikale Größe
hf.setSize(220,230); // Setzen des Titels
hf.setTitle("Java Anwendung");
// Setzen des Feldes mitdem Text "Mustertext"
hf.add(new JLabel("Mustertext")); // Beim Schliessen wird das Programm beendet
hf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

// Sas JFrame sichtbar machen
hf.setVisible(true);

Ergibt das folgende Fenster:

Anmerkung: Das hier gezeigte Fenster benutzt das Standard "Look and Feel" von MacOS. Die Rahmendekoration sieht bei anderen Betriebssystemen bzw. anderen "Loak and Feel" Themen anders aus. Der oben gezeigt Code ist jedoch plattformneutral.

JMenuBar, JMenu, JMenuItem

 Die Klassen JMenuBar, JMenu, JMenuItem , erlauben Konfiguration von Menüleisten, Pulldownmenüs und Menülisteneinträge für Fenster die mit JFrame erzeugt wurden.

Im Folgenden werden die wichtigsten Methoden (für den Kontext dieses Kurses) aufgeführt. Die obengenannten Klassen sind sehr viel mächtiger als hier beschrieben. Sie haben zusätzliche Methoden für:

  • Lokalisierung
  • unterschiedliche Arten von Menüeinträgen
  • konfigurierbares Look and Feel
  • Untermenüs
  • markieren von Einträgen
  • Anklicken durch Buchstaben Abkürzungen der Tastatur
  • etc.

Die Klasse JMenuBar

JMenuBar
Methode Beschreibung Geerbt von
Konstruktor: JMenuBar() Erzeugen einer Menüleiste für ein JFrame Fenster -
JMenu add(JMenu c) Hinzufügen eines Menüs zur Leiste -

Die Klasse JMenu

JMenu
Methode Beschreibung Geerbt von
Konstruktor: JMenu() Erzeugen einer Menüspalte am Ende der Menüleiste -
Konstruktor: JMenu(String title) Erzeugen einer Menüspalte mit Titel am Ende der Menüleiste -
JMenuItem add(JMenuItem c) Hinzufügen eines Menüeintrags am Ende des Menüs -

 

Die Klasse JMenuItem

JMenu
Methode Beschreibung Geerbt von
Konstruktor: JMenuItem(String text) Erzeugen eines Menüeintrags mit einem gegebenen Text -
void addActionListener(ActionListener l) Registrieren eines Listeners der aufgerufen wird wenn der Menülisteneintrag geklickt wird java.swingx.AbstractButton
void  setEnabled(boolean) Anzeigen oder ausblenden eines Menülisteneintrags -

Beispiel

... Menüs mit einem einzigen Eintrag :

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;    
import javax.swing.JMenu;  
import javax.swing.JMenuBar;  
import javax.swing.JMenuItem;
... 
JFrame hf = new JFrame();
JMenuBar menueleiste;
JMenu ablageMenue;
JMenuItem beendenMenueEintrag;
menueleiste = new JMenuBar();
ablageMenue = new JMenu("Ablage");
beendenMenueEintrag = new JMenuItem("Beenden");
beendenMenueEintrag.addActionListener(this);
beendenMenueEintrag.setEnabled(true);

ablageMenue.add(beendenMenueEintrag);
menueleiste.add(ablageMenue);

hf.setJMenuBar(menueleiste);
hf.setVisible(true);

Einen einfachen Actionlistener kann man als Klasse mit der folgenden Methode implementieren:


    public void actionPerformed(ActionEvent e) {
JMenuItem source = (JMenuItem) (e.getSource());
                if (source == beendenMenueEintrag)
                    System.exit(0);
}

beendenMenueEintrag muss hier auf das gleiche Objekt wie bei der obigen Erzeugung der Menüleiste zeigen. Der Actionlistener beendet die Anwendung bei Aufruf!

Hierdurch ergibt sich das folgende Fenster:

Anmerkung: Das hier gezeigt Fenster benutzt das Standard "Look and Feel" von MacOS. Die Rahmendekoration sieht bei anderen Betriebssystemen bzw. anderen "Look and Feel" Themen anders aus. Der oben gezeigt Code ist jedoch plattformneutral.

10.1.3 GUI IO (Swing)

Java bietet mit AWT (Another Window Tool Kit) und Swing reichhaltige Bibliotheken zum Entwurf von graphischen Benutzeroberflächen.

Hinweis: "Swing" (Schaukel) ist der Projektname. Swing ist Teil der Java Foundation Classes (JFC).

Einfache grafische "Swing" Oberfläche

Das folgende Beispiel dient als einfaches Rahmenprogramm für Text Ein- und Ausgaben.

Es übernimmt die Erzeugung eines Fensters mit:

  • Eingabefeld für einen Text
  • Ausgabefeld für einen Text
  • 3 Buttons zum Verarbeiten des Texts in drei Methoden

Der eigene Algorithmus wird in der Methode public String executeOperation1(String) oder der beiden anderen executeOpteration Methoden implementiert.

Hierzu werden die folgenden Swing-Klassen verwendet

  • JFrame: Erzeugen eines Fensters
  • JLabel: Erzeugen eines festen Texts
  • JTextField: Textfelder zur Ein- und Ausgabe von Texten
  • JButton: Knopf(Button) zum Triggern einer Aktion
  • ActionListener:Ausführen einer Aktion. Hier als anonyme Klasse implementiert.

Das erscheinende Fenster sieht wie folgt aus:

Das Fenster nach einer Texteingabe ("Eingabe"):

Das Fenster nach dem Drücken des Knopf "Do twice". Es wurde die Methode executeOperation2() aufgerufen:

Die Methoden executeOperation1(), executeOperation2(), executeOperation3() erlauben eine Implementierung des eigenen Algorithmus. Der erforderliche String in der Rückgabe wird dann im Fenster angezeigt.

Anbei das Beispielprogramm mit der Klasse DemoFrame:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;

public class DemoFrame  extends javax.swing.JFrame {
    private javax.swing.ButtonGroup buttonGroup1;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JTextField jTextField1;
    private javax.swing.JTextField jTextField2;
    private javax.swing.JButton jButton1;
    private javax.swing.JButton jButton2;
    private javax.swing.JButton jButton3;

                      
    private void initComponents() {
        jLabel1 = new javax.swing.JLabel();
        jTextField1 = new javax.swing.JTextField();
        jTextField2 = new javax.swing.JTextField();

        jButton1 = new javax.swing.JButton();
        jButton2 = new javax.swing.JButton();
        jButton3 = new javax.swing.JButton();
        
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("My first JFrame");
        jLabel1.setText("Input Text:");
        jTextField1.setText("Input");
        jTextField2.setText("Output");
        jTextField2.setEditable(false);

        jButton1.setText("Do once:");
        jButton2.setText("Do twice:");
        jButton3.setText("Do three times:");

        JPanel radioPanel = new JPanel(new GridLayout(1, 0));
        radioPanel.add(jButton1);
        radioPanel.add(jButton2);
        radioPanel.add(jButton3);

        jButton1.addActionListener(new ActionListener()
                {
                public void actionPerformed(ActionEvent e) {
                    if(e.getSource() == jButton1)
                        jTextField2.setText(
                                executeOperation1(jTextField1.getText()));
                    }
                }
          );
          jButton2.addActionListener(new ActionListener()
                {
                public void actionPerformed(ActionEvent e) {
                    if(e.getSource() == jButton2)
                        jTextField2.setText(
                                executeOperation2(jTextField1.getText()));
                    }
                }
          );
          jButton3.addActionListener(new ActionListener()
                {
                public void actionPerformed(ActionEvent e) {
                    if(e.getSource() == jButton3)
                        jTextField2.setText(
                                executeOperation3(jTextField1.getText()));
                    }
                }
          );

        this.setBounds(300, 300, 200, 30);
        setMinimumSize(new Dimension(200,30));
        getContentPane().add(jLabel1, BorderLayout.WEST);
        getContentPane().add(jTextField1, BorderLayout.CENTER);
        getContentPane().add(radioPanel, BorderLayout.NORTH);
        getContentPane().add(jTextField2, BorderLayout.SOUTH);
        pack();
   }
    public DemoFrame() {
        initComponents();
    }

    public static void main(String[] args) {
 
        DemoFrame f1 = new DemoFrame();
        f1.setVisible(true);

    }

    public String executeOperation1(String s) {
        // Add Application logic here:
        String result = "Button 1:" + s;
        return (result) ;
    }
    public String executeOperation2(String s) {
        // Add Application logic here:
        String result = "Button 2:" + s + s;
        return (result) ;
    }
    public String executeOperation3(String s) {
        // Add Application logic here:
        String result = "Button 3:" + s + s +s;
        return (result) ;
    }

}

Als Download

Eine Swingoberfläche zum Erfassen von drei Texten

Die Klasse DemoFrame1 stellt drei Eingabefelder zum Erfassen von Texten zur Verfügung und erlaubt das Starten von 3 Operationen:

Implementierung der Klasse DemoFrame 1

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

/**
 *
 * @author sschneid
 */
public class DemoFrame1 extends javax.swing.JFrame {

    private ButtonGroup buttonGroup1;
    private JPanel jPanel1;
    private JLabel jLabel1;
    private JTextField jTextFieldin[];
    private JTextField jTextField2;
    private JButton jButton1;
    private JButton jButton2;
    private JButton jButton3;
    /**
     * Anzahl der Eingabefelder
     */
    private int inFields = 3;

    /**
     * Initialisieren aller Komponenten
     */
    private void initComponents() {
        jLabel1 = new JLabel();
        jTextFieldin = new JTextField[inFields];
        for (int i = 0; i < inFields; i++) {
            jTextFieldin[i] = new JTextField();
        }
        jTextField2 = new JTextField();

        jButton1 = new JButton();
        jButton2 = new JButton();
        jButton3 = new JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("My second JFrame");

        jLabel1.setText("Input Text:");

        // Belegen alle Eingabefelder mit Standwerten
        for (int i = 0; i < inFields; i++) {
            jTextFieldin[i].setText("Input" + i);
        }
        // Belegen des Augabefeldes mit Standartwert
        jTextField2.setText("Output");
        jTextField2.setEditable(false);

        // Erzeugen dreier Buttons
        jButton1.setText("Do once:");
        jButton2.setText("Do twice:");
        jButton3.setText("Do three times:");

        // Ezeugen einer Datenstruktur(Panel) die drei Buttons aufnimmt
        JPanel radioPanel = new JPanel(new GridLayout(1, 0));
        radioPanel.add(jButton1);
        radioPanel.add(jButton2);
        radioPanel.add(jButton3);

        // Führe Operation 1 aus wenn Button 1 gedrückt wird
        jButton1.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                if (e.getSource() == jButton1) {
                    jTextField2.setText(executeOperation1(
                            jTextFieldin[0].getText(),
                            jTextFieldin[1].getText(),
                            jTextFieldin[2].getText()));
                }
            }
        });

        // Führe Operation 2 aus wenn Button 2 gedrückt wird
        jButton2.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                if (e.getSource() == jButton2) {
                    jTextField2.setText(executeOperation2(
                            jTextFieldin[0].getText(),
                            jTextFieldin[1].getText(),
                            jTextFieldin[2].getText()));
                }
            }
        });

        // Führe Operation 3 aus wenn Button 3 gedrückt wird
        jButton3.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                if (e.getSource() == jButton3) {
                    jTextField2.setText(executeOperation3(
                            jTextFieldin[0].getText(),
                            jTextFieldin[1].getText(),
                            jTextFieldin[2].getText()));
                }
            }
        });

        // Allgemeine Konfiguration des Fensters
        this.setBounds(300, 300, 200, 30);
        setMinimumSize(new Dimension(200, 30));
        // Einfügen der Eingabeaufforderung links
        getContentPane().add(jLabel1, BorderLayout.WEST);
        // Panel zum verwalten von mehreren Eingabefelder
        JPanel inputPanel = new JPanel(new GridLayout(inFields, 0));
        for (int i = 0; i < inFields; i++) {
            inputPanel.add(jTextFieldin[i]);
        }
        // Einfügen des Panels mit Eingabefeldern in der Mitte
        getContentPane().add(inputPanel, BorderLayout.CENTER);
        // Einfügen des Panels mit Buttons oben
        getContentPane().add(radioPanel, BorderLayout.NORTH);
        // Einfügen der Ausgabezeile unten
        getContentPane().add(jTextField2, BorderLayout.SOUTH);
        pack();
    }

    /**
     * Initialierung alle Komponenten
     */
    public DemoFrame1() {
        initComponents();
    }

    /**
     * Hauptprogramm: Erzeuge eine Instanz von DemoFrame1 und zeige sie an
     * @param args
     */
    public static void main(String[] args) {

        DemoFrame1 f1 = new DemoFrame1();
        f1.setVisible(true);
    }

    /**
     * Führe Operation 1 aus (Button 1 wurde gedrückt)
     * @param s1
     * @param s2
     * @param s3
     * @return
     */
    public String executeOperation1(String s1, String s2, String s3) {
        // Add Application logic here:
        String result = "Button 1:" + s1 + s2 + s3;
        return (result);
    }

    /**
     * Führe Operation 2 aus (Button 2 wurde gedrückt)
     * @param s1
     * @param s2
     * @param s3
     * @return
     */
    public String executeOperation2(String s1, String s2, String s3) {
        // Add Application logic here:
        String result = "Button 2:" + s1 + s2 + s3;
        return (result);
    }

    /**
     * Führe Operation 3 aus (Button 3 wurde gedrückt)
     * @param s1
     * @param s2
     * @param s3
     * @return
     */
    public String executeOperation3(String s1, String s2, String s3) {
        // Add Application logic here:
        String result = "Button 3:" + s1 + s2 + s3;
        return (result);
    }
}

10.1.4 Math

 Die Math Klasse enthält alle mathematischen Hilfsroutinen wie z.Bsp.

public static double random(): Zufallszahlengenerator

...erzeugt zufällige Fliesskommazahlen im Intervall von 0.0 bis 1.0

Die folgende Methode erzeugt ein beliebig großes Feld von zufälligen Ganzzahlen in einem bestimmten Bereich (range)

public static int[] createRandom(int size, int range) {
   int[] array1 = new int[size];
   for (int i=0; i<size;i++) 
       array1[i] = (int)(Math.random()*range);
   return array1;
}

10.1.5 Systemresourcencheck mit der Klasse Runtime

 Die Java Runtime bietet die Möglichkeit die folgenden Systemwerte auszulesen:

  • verfügbare Prozessoren
  • freier Speicher
  • maximal verfügbarer Speicher
  • gesamter allokierter Speicher der VM

Die Klasse bietet ebenfalls die Möglichkeit eine Reihe von Operationen zu starten:

  • expliziter Aufruf eines "Garbage Collectors"
  • expliziter Aufruf des Finalizers
  • externer Aufruf eines Betriebssystemkommandos
  • beenden des Prozesses
  • laden von dynamischen Bibliotheken

Warnung: Alle aufgeführten Operationen haben erheblichen Einfluss auf die gesamte VM. Im normalen Entwicklungsumfeld größerer Anwendungen führen die oben genannten Operationen in der Regel zu erheblichen negativen Seiteneffekten für Sicherheit, Stabilität und Performanz von Anwendungen.

Die folgende Methode nutzt die Klasse Runtime um die wichtigsten Werte des Prozesses auszulesen:

public static void systemStatus(){
        Runtime r = Runtime.getRuntime();
        System.out.println("*** System Status ***");
        System.out.println("Prozessoren :       " + r.availableProcessors());
        System.out.println("Freier Speicher:    " + r.freeMemory());
        System.out.println("Maximaler Speicher: " + r.maxMemory());
        System.out.println("Gesamter Speicher:  " + r.totalMemory());
        System.out.println("***  ***");
    }

10.1.6 Collator (Vergleichen von Zeichenketten)

Der Vergleich von Zeichenketten hängt oft von der lexikographischen Ordnung ab. Die lexikographische Ordnung selbst hängt wieder von der Region ab. Java bietet hierfür die Klasse Collator (Collation engl. : Der Textvergleich) an, die Zeichenketten nach bestimmten Kriterien sortieren kann. Die Methode compare() erlaubt das Vergleichen zweier Zeichenketten.

Zur Bestimmung der Sortierreihenfolge wird die Klasse java.util.Locale benötigt, die die entsprechende Region und deren Sprache bestimmt.

Das Vorgehen zum Konfigurieren und Vergleichen von Zeichenketten ist das folgende:

  • Auswahl der gewünschten Region und Sprache (Locale). Z.Bsp. Locale.GERMANY
  • Bestimmung der gewünschten Sortierstärke für den Collator ( setStrength() )
    • Collator.PRIMARY : nur die grundlegende Abfolge  wie z.Bsp 'a' und 'b' werden berücksichtigt
    • Collator.SECONDARY: Unterschiede wie z.Bsp. 'a' und 'ä' werden berücksichtigt falls nach den Regeln der Kategorie "Primary" kein Unterschied existiert.
    • Collator.TERTIARY: Unterschiede wie z. Bsp. 'a' und 'A' werden berücksichtigt falls nach den Regeln der Kategorie "Secondary" kein Unterschied existiert
  • Vergleichen der Zeichenketten mit compare() oder equals()

Beispiel

Vergleichen von Vor- und Nachnamen einer Person nach deutschen Sortierregeln inklusive besonderer deutscher Zeichen mit der Methode compare() der Klasse Collator.

import java.text.Collator;
import java.util.Locale;
 
public class Person{
   private String nachname;
   private String vorname;
private static Locale myLocale = Locale.GERMANY;
 
public boolean istKleinerAls(Person p) {
boolean kleiner = false;
   Collator myCollator = Collator.getInstance(myLocale);
// Beim Vergleich auch Gross- und Kleinschreibung unterscheiden
   myCollator.setStrength(Collator.TERTIARY);
   // Konfiguriere die Sortierordnung
   if (myCollator.compare(nachname, p.nachname) < 0) {
      kleiner = true;
   } else if (myCollator.compare(nachname, p.nachname) == 0) {
      kleiner = myCollator.compare(vorname, p.vorname) < 0;
}
   return kleiner;
}
}

Lauffähiges Programm zum Testen von Locales und Sortierordnungen

Das Programm Lexikographisch akzeptiert die folgende Kommandozeilensyntax:

java Kurs2.Sort.Lexikographisch String1 String2 [[German|French|UK|US] [PRIMARY|SECONDARY|TERTIARY]]

Beispiele

$ java Kurs2.Sort.Lexikographisch SPULE Spüle German PRIMARY
SPULE ist gleich Spüle
$ java Kurs2.Sort.Lexikographisch SPULE Spüle German SECONDARY
SPULE vor Spüle
$ java Kurs2.Sort.Lexikographisch Schmidt SCHMIDT German PRIMARY
Schmidt ist gleich SCHMIDT
$ java Kurs2.Sort.Lexikographisch Schmidt SCHMIDT German SECONDARY
Schmidt ist gleich SCHMIDT
$ java Kurs2.Sort.Lexikographisch Schmidt SCHMIDT German TERTIARY
Schmidt vor SCHMIDT

Quellcode

package Kurs2.Sort;
import java.text.Collator;
import java.util.Locale;
/**
 *
 * @author sschneid
 */
public class Lexikographisch {
    public static void main(String[] args) {
        String s1 = "Test1";
        String s2 = "Test2";
        Locale myLocale = Locale.GERMANY;
        int strength = Collator.TERTIARY;
        if (args.length < 2) {
            System.out.println("Erforderliche Mindestparameter: String1 String2");
            System.out.println("Syntax: java Kurs2.Sort.Lexikographisch "
                    + "String1 String2 "
                    + "[[German|French|UK|US] [PRIMARY|SECONDARY|TERTIARY]]");
} else { s1 = args[0]; s2 = args[1]; } if (args.length >= 3) { String loc = args[2]; if (loc.equalsIgnoreCase("German") || loc.equalsIgnoreCase("Germany")) { myLocale = Locale.GERMAN; } if (loc.equalsIgnoreCase("France") || loc.equalsIgnoreCase("French")) { myLocale = Locale.FRENCH; } if (loc.equalsIgnoreCase("US")) { myLocale = Locale.US; } if (loc.equalsIgnoreCase("UK") || loc.equalsIgnoreCase("English")) { myLocale = Locale.UK; } // Add more locales here... } if (args.length >= 4) { String s = args[3]; if (s.equalsIgnoreCase("PRIMARY")) { strength = Collator.PRIMARY; } if (s.equalsIgnoreCase("SECONDARY")) { strength = Collator.SECONDARY; } if (s.equalsIgnoreCase("TERTIARY")) { strength = Collator.TERTIARY; } } vergleich(s1, s2, myLocale, strength); } private static void vergleich(String s1, String s2, Locale myLocale, int strength) { Collator myCollator = Collator.getInstance(myLocale); // Beim Vergleich auch Gross- und Kleinschreibung unterscheiden // Konfiguriere die Sortierordnung myCollator.setStrength(strength); if (myCollator.compare(s1, s2) == 0) { System.out.println(s1 + " ist gleich " + s2); } if (myCollator.compare(s1, s2) < 0) { System.out.println(s1 + " vor " + s2); } if (myCollator.compare(s1, s2) > 0) { System.out.println(s2 + " vor " + s1); } } }

10.2 Kodierstil

Duke auf Motorrad

Es gibt ein paar wichtige Regeln um Code auch in Zukunft noch verstehen zu können. In der Javawelt gelten die folgenden Konventionen:

  • Camelcase verwenden
  • Klassennamen groß schreiben
  • Methodennnamen klein schreiben
  • Variablen klein schreiben
  • Konstanten werden vollständig in Großbuchstaben geschrieben
  • An schwierigen Stellen Kommentare schreiben
  • Code einrücken bei jedem neuen Block
    • Geschweifte Blockklammern {} übereinander scheiben wenn möglich

Siehe auch Wikibooks: Standard für Variablen und Bezeichner

10.3 Entwicklungsumgebungen

Hier werden nur kostenlose Entwicklungsumgebungen vorgestellt.

Das Ziel ist es den Studenten eine Orientierung für den Programmierkurs zu geben. Die Leistung kommerziell verfügbarer Werkzeuge soll hiermit nicht geschmälert werden. 

wikipedia.org verfügt über eine gute Sammlung von Java Entwicklungsumgebungen.

gedit: Ein intelligenter Texteditor

  • Text Editor des Gnome Projekt
  • kostenlos
  • Eigenschaften
    • Syntax Highlighting
  • Der Editor ist keine eigentliche Javaentwicklungsumgebung da er kein Übersetzen, debuggen etc. unterstützt
  • Für den Kurs sehr gut geeignet

 gedit Screen Shot

bluej.org

Eclipse

Netbeans

10.3.1 Netbeans: Java API Definitionen im Editor einblenden

Netbeans (Version 7.1) bietet dem Entwickler die Vervollständigung von Methoden- und Variablennamen von Klassen automatisch an.

Diese Information holt sich Netbeans automatisch aus dem für das Projekt konfigurierten Bibliotheken und den Laufzeitbibliotheken des gewählten JRE/JDK. Netbeans kann aber auch automatisch die Erklärung der entsprechenden Methoden und Variablen anzeigen. Man muss dies aber explizit konfigurieren,

Das folgende Bild zeigt die Eingabe des Texts System.out.prin im Javaeditor von Netbeans. Netbeans wird normalerweise einen Auswahldialog mit den sinnvollen Vervollständigungen erzeugen.

In diesem Fall sind es alle Methoden des Attributs out vom Typ PrintStream die mit prin beginnen. Dieses Verhalten ist Standard in Netbeans. Man kann aber Netbeans auch so konfigurieren, dass man die Bedeutung einer ausgewählten Methode sehen kann. Dies geschieht hier im zweiten Fenster unterhalb der "prin" Liste. Netbeans zeigt die Javadokumentation der Methode java.io.PrintStream() an, da System.out auf ein Objekt der Klasse PrintStream zeigt.

Beispiel einer Dokumentation

Die Erklärung der Methoden bzw. Variablen fehlt jedoch in der Standardkonfiguration. Netbeans kann hier nicht automatisch auf die entsprechenden Daten zugreifen. Man muss Netbeans zeigen wo diese Daten zu finden sind.

Eine Möglichkeit ist in Netbeans den URL auf die Oracle Javadokumentation im Internet zu konfigurieren. Die geschieht mit den folgenden Schritten:

Auswahl der Plattform

Die Dokumentation ist eine Eigenschaft der gewählten Javaplattform.

Wählen Sie im Menü "Tools" den Eintrag "Java Platforms".

Auswahl von Tools->Java Platforms

Auswahl

Im Fenster "Java Platform Manager" muss man die Karteikarte "Javadoc" auswählen:

Auswahlfeld Javaplattform in Netbeans

Hier kann man dann URLs oder Verweise auf lokale Dateien konfigurieren.

Im Bild unten wird auf die Dokumentation von Java 6 gezeigt, da als gewählte Plattform auch Java 6 gewählt wurde

 Auswahl von Javadoc

Hinweis

Die gezeigten Fenster stammen von MacOS. Die Dekorationen der Fenster werden auf anderen Plattformen (Windows, Linux, Solaris etc.) unterschiedlich aussehen. Inhalte und Auswahlmöglichkeiten sollten aber auf anderen Plattformen gleich sein (Stand Netbeans 7.0.1; Oktober 2011),

10.3.2 Entscheidungshilfe: Eclipse oder Netbeans

 Anbei eine Entscheidungshilfe für die Teilnehmer der Vorlesung der dualen Hochschule.

Grundsätzliches:

  1. Man benötigt für den Kurs nicht unbedingt eine hochentwicklete Enwicklungsumgebung. Man benötigt streng genommen nur die Installation des Java JDK, ein Texteditor und Kommandozeilenzugriff
  2. Jede Entwicklungsumgebung die zumindestens Pakete unterstützt ist ausreichend für den Kurs
  3. Es ist durchaus ein gute Idee sich mehrere Entwicklungsumgebungen anszuschauen um die zu wählen die einem am besten gefällt.
  4. Die Benutzung der graphischen Oberflächen von Eclipse und Netbeans im Rahmen des Kurses ist fast identisch.

Typischerweise stellt sich für fast jeden Javaentwickler irgendwann die Frage:

Netbeans oder Eclipse?

Diese Frage können sie recht gut nach den folgenden Kriterien beantworten:

Ich verwende schon Entwicklungsumgebung XY

Bleiben Sie bei Ihrer Entwicklungsumgebung XY.

Mein Unternehmen verwendet bevorzugt die Entwicklungsumgebung XY

Benutzen Sie die Umgebung XY und fragen Sie einen Kollegen nach der firmenspezifischen Installation (Erweiterungen!). Sie werden wahrscheinlich später in diesem Kontext arbeiten.

Ich habe keine Präferenzen und ich bin Windowsbenutzer

Erwägen Sie Eclipse. Eclipse ist sehr gut an Windows angepasst. Eclipse ist der Marktführer.

Ich habe keine Präferenzen und ich bin MacOS Benutzer

Erwägen Sie Netbeans. Netbeans bettet sich natürlicher in MacOS ein. Der Referent benutzt ebenfalls Netbeans auf MacOS.

Softwareentwicklung ist ein völlig neues Thema für mich

Erwägen Sie Netbeans

  • Sie sind "click" kompatibel zum Referenten
  • Der Referent kann Ihnen leichter helfen
  • Netbeans exponiert nicht so viele Expertenfunktionen. Die sind für Anfänger oft verwirrend.
  • Der syntaxsensitive Editor scheint etwas besser zu sein. Schreibfehler werden etwas zuverlässiger und schneller angezeigt.

Zusammenfassung

Beide Produkte sind kostenlos und für den Kurs gleichwertig. Sie installieren sich am besten in der zweiten Kurshälfte das andere Produkt und entscheiden dann selbst.

10.3.3 Shortcuts (Befehlsabkürzungen)

Anbei einige Befehlsabkürzungen in Eclipse und Netbeans

Abkürzung Netbeans Eclipse
 Referenzen

Keyboard Shortcuts (Spickzettel)

Menü->Preferences->Editor->Coe templates

Menü:preferences ->Java->Editor->Templates
System.out.println("") sout<tab> sysout<ctrl><Leertaste>
String St<tab>  
switch sw<tab> switch<ctrl><Leertaste>
main  psvm<tab> main<ctrl><Leertaste>

 

10.3.4 Netbeans: Hochauflösende Windows 8 Monitor

 Problem: Netbeans 8 erscheint zu klein auf hochauflösenden Windows 8 Monitoren.

Lösung:

  • Öffnen Sie die Datei C:\Program Files\NetBeans 8.0\etc\netbeans.conf in einem Editor
  • Ändern Sie den Eintrag: -J-Dsun.java2d.dpiaware=true zu -J-Dsun.java2d.dpiaware=false

Gefunden in einem Stackoverflow Forum

 

10.4 Entwurfsmuster (Design Patterns)

Duke mit Blueprint

(Lizenz

Nach Wikipedia:

Entwurfsmuster

Entwurfsmuster (engl. design patterns) sind bewährte Lösungs-Schablonen für wiederkehrende Entwurfsprobleme in der Softwarearchitektur und -entwicklung.

Sie stellen damit eine wiederverwendbare  Vorlage zur Problemlösung dar, die in einem bestimmten Zusammenhang einsetzbar sind

Im Rahmen der Vorlesung werden einige wenige, ausgewählte Entwurfsmuster vorgestellt:

Singleton (Einzelstück)

Es gibt Anwendungsfälle in denen es gewünscht ist genau ein Objekt global zur Verfügung zu stellen. Dieser Anwendungsfall wird mit dem Entwurfsmuster "Singleton" (Einzelstück) beschrieben. Beispiele für solche Anwendungsfälle sind:

  • Implementierung eines seriellen logging Mechanismus
  • Implementierung eines Puffers für einen Drucker

Verwendung

Ein Einzelstück

  • verwaltet genau ein eine Klasse mit genau einem Objekt
  • unterbindet die Erzeugung von anderen Objekten einer Klasse
  • erlaubt einfachen Zugriff auf ein solches Objekt

Ein Singleton (Einzelstück) implementiert eine ähnliche Struktur wie eine globale Variable.

Kategorie

Das Einzelstück (Singleton) gehört zur Kategorie der Erzeugungsmuster (Creational Pattern).

UML Diagramm

Naive Javaimplementierung (Lazy initialization)

/**
 * Einfache Implementierung des Einzelstueck (Singleton
 */
public class Einzelstueck {
    private static Einzelstueck instanz = null;
     /**
     * privater Konstruktor der nur innerhalb der Klasse
     * aufgerufen werden kann
     */
    private Einzelstueck() {
      // Individuelle Initialisierung erfolgt hier
    }
     /**
     * Erzeugen des einzigen Objekts falls noch keines existiert.
     * Rückgabe des Objekts falls es schon existiert
     * Diese Methode ist statisch. Sie kann auch ohne die Existenz einer Instanz aufgerufen werden.
     * Die Methode ist die einzige öffentliche Methode
     */    
   public static Einzelstueck getInstance() {
        if (instanz == null) {
            instanz = new Einzelstueck();
        }
        return instanz;
    }}

Das gezeigte Beispiel verwendet eine "Lazy initialization". Das Objekt wird erst erzeugt wenn es auch wirklich benötigt wird. Hierdurch kann das unnötige Allokieren von Ressourcen vermieden werden.

Der Nachteil dieser Implementierung besteht darin, dass sie nicht threadsicher ist. In einem Javaprogramm mit mehreren Threads (Ausführungseinheiten) können zwei Threads gleichzeitig ein Objekt erzeugen und damit das gewünschte Ziel des Einzelstücks durchkreuzen.

Threadsichere Javaimplementierung

/**
* Threadsichere Implementierung des Entwurfsmuster Einzelstueck (Singleton)
*/
public class Einzelstueck {
    private static Einzelstueck instanz = new Einzelstueck();
     /**     
     * privater Konstruktor der nur innerhalb der Klasse
     * aufgerufen werden kann
     */
    private Einzelstueck() {
      // Individuelle Initialisierung erfolgt hier
    }
     /**
     * Diese Methode ist statisch. Sie kann auch ohne die Existenz einer Instanz aufgerufen werden.
     * Die Methode ist die einzige öffentliche Methode
     */
    public static Einzelstueck getInstance() {
        return instanz;
    }
}

Die hier gezeigte Implementierung ist threadsicher da die Instanz schon beim Laden der Klasse erzeugt wird.

 

10.5 Dokumentieren von Javaprogrammen (javadoc)

Die Dokumentation von Methoden, Variablen und Klassen ist Teil der Sprache und wird von Standardwerkzeugen des JDK unterstützt.

Die genauen Spezifikationen zum Dokumentieren von Javaklassen sind im Oracle Dokument "Requirements for Writing Java API Specifications" beschrieben. Nach diesen Richtlinien können auch eigene Javaklassen dokumentiert werden. Oracle bietet hierzu ein recht gutes Tutorial an.

Konzept

  • Dokumentation zu Variablen und Methoden werden in den Javaquelldateien als Javakommentar in einem besonderen Format beschrieben
  • Das Hilfsprogramm javadoc liest Javaquelldateien und erzeugt html Seiten mit der passenden Dokumentation einer Klasse.

Das Format von Kommentaren für Dokumentation

Das Format für die Dokumentation ist ein Sonderfall des mehrzeiligen Javakommentars. Es sieht wie folgt aus:

/**
  * Hier steht Dokumentationskommentar
  * Hier steht eine weitere Zeile mit Dokumentationskommentar
*/

Javakommentare können mit html formatiert werden.

Zusammenfassung für eine Klasse

Beginnen Sie die Dokumentation einer Klasse mit einer Zusammenfassung ihrer Funktion.

Für die Klasse Ware.java kann das wie folgt aussehen:

    /**
    * Ware dient zum Verwalten von Guetern mit Preisen und Namen in einem Lager.
    * @author  Stefan Schneider
    * @version 1.1
    * @see     Lager
    */
    public class Ware {
     ...
    }

Die Klassendokumentation erlaubt die Verwendung von Kennzeichnungen (englisch "Tags") mit denen man weitere Informationen beisteuern kann. Man kann für Klassen die folgenden Kennzeichnungen "Tags" verwenden:

Dokumentation von Klassenvariablen

Zur Dokumentation von Attributen wird die Dokumentation der Deklaration vorangestellt. Bei der Klasse Ware kann die zum Beispiel wie folgt geschehen:

public class Ware {
...
    /**
     * Der aktuelle Mehrwertsteuersatz 2010.
     * Er liegt zur Zeit bei {@value} .
     *
     * @since 1.0
     */
    public static final double mws = 0.19;
...
}

Bei Klassenvariablen können die folgenden Kennzeichnungen "Tags" verwendet werden:

Dokumentation von Konstruktoren und Methoden

Die Dokumentation von Konstruktoren und Methoden wird ebenfalls direkt der Implementierung der entsprechenden Methode als Javakommentar vorangestellt. Hiermit kann man neben der Bedeutung der Methode auch die Eingabe- und Ausgabeparameter dokumentieren. Siehe folgendes Beispiel:

    /**
     * Liefert den Namen einer Ware zurueck.
     * @return    Name der Ware
     */
    public String get_name(){return name;}

    /**
     * Setzen eines neuen Nettopreis
     * @param npr   der neue Nettopreis
     */
    public void set_nettoPreis(int npr) {
    ...
    }

Bei Methoden und Konstruktoren sind die folgenden Tags möglich:

javadoc: Der Java API Generator

Das JDK Programm javadoc (Oracle Dokumentation) erzeugt aus Javaquelldateien Java API Beschreibungen im html Format.

In seiner einfachsten Form kann man eine Java API Dokumentation mit dem folgenden Kommando erzeugen:

$ javadoc JavaQuelldatei.java ... JavaQuelldatei1.java

Das Kommando javadoc hat zahlreiche Optionen (siehe Oracle Dokumentation) die direkt nach dem Kommando eingefügt werden können. Die wichtigsten sind:

  • -author Generierung der Dokumentation unter Berücksichtigung des @author tag
  • -d Verzeichnis Generiert die Dokumentation in dem angegeben Verzeichnis
  • -help zeigt die online Hilfe
  • -private generiert Dokumentation auch für private Attribute
  • -sourcepath sourcepathlist Liste der Verzeichnisse in denen nach Quelldateien gesucht wird
  • -version Generierung der Dokumentation unter Berücksichtigung des @version tag

Beispiel

Für eine Klasse Ware.java mit allen Komponenten:

/**
 * Dies ist die Dokumentation der Klasse Ware. Ware dient zum Verwalten von Gütern
 * mit Preisen und Namen in einem Lager.
 * @author  Stefan Schneider
 * @version 1.1
 * @see     Lager
 */
public class Ware {

    /**
     * Der aktuelle Mehrwertsteuersatz 2010.
     * Er liegt zur Zeit bei {@value}.
     *
     * @since 1.0
     */
    public static final double mws = 0.19;
    private double nettoPreis; //Deklaration
    public boolean halbeMws;
    private String name;

    /**
     * Konstruktor fuer die Klasse Ware
     * @param n der Name der Ware
     * @param np der Nettorpreis
     * @param hmws halber Mehrwertsteuersatz für Ware gueltig
     */
    public Ware(String n, double np, boolean hmws) {
        name = n;
        nettoPreis = np;
        halbeMws = hmws;
    }

    /**
     * Liefert den Namen einer Ware zurueck.
     * @return    Name der Ware
     */
    public String get_name() {
        return name;
    }

    /**
     * Setzen eines neuen Nettopreis
     * @param npr  der neue Nettopreis
     * @see "Der kleine Kaufmann. BWL für Einzelhändler"
     */
    public void set_nettoPreis(double npr) {
        nettoPreis = npr;
    }

    /**
     * Ausdrucken aller Werte auf der Konsole
     */
    public void drucken() {
        System.out.println("Name: " + name);
        System.out.println("netto: " + nettoPreis);
        System.out.println("Brutto: " + bruttoPreis());
        System.out.println("Halbe Mws:" + halbeMws);
    }

    /**
     * Ausgabe des Nettopreis
     * @return der Nettopreis

     */
    public double nettoPreis() {
        return nettoPreis;
    }

    /**
     * Ausgabe des Bruttopreis
     * @return der Bruttopreis
     */
    public double bruttoPreis() {
        double bp; //temporaere Variable; keine Klassenvariable
        if (halbeMws) {
            bp = Math.round(nettoPreis * (mws / 2 + 1) * 100) / 100;
        } else {
            bp = Math.round(nettoPreis * (mws + 1) * 100) / 100;
        }
        return bp;
    }
}

Die Dokumentation kann mit dem Kommando javadoc generiert werden. Für das oben gezeigte Beispiel werden zwei Optionen zur Generierung des Autors und der Version benötigt. Die Optionen erlauben die Informationen über Autoren und Versionen auf Wunsch wegzulassen:

 $ javadoc -author -version  Ware.java 

Das Kommando erzeugt eine Reihe von html Dateien im gleichen Verzeichnis. Die generierte Datei index.html sieht wie folgt aus (Screen shot):






10.5.1 Lernziele

Am Ende dieses Blocks können Sie:

  • .... die Notations eines Dokumentationskommentars von anderen Javakommentaren unterscheiden
  • ... können die Tags zur Dokumentation von Eingabeparametern und Ausgabeparametern nutzen
  • ... können den Dokumentationsgenerator für einfache Klassen aufrufen und kennen die Optionen zum Generieren von zusätzlichen Informationen im Kommentar

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten:

10.6 Packen mit jar

Einführung jar: Java Archive Tool

Javaprogramme können aus sehr vielen Klassen und externen Bibliotheken bestehen. "jar" Dateien erlauben das Bündeln von Klassendateien und anderer Dateien um Installation und die Verteilung von Javaprogrammen zu vereinfachen. Mit jar Dateien werden die folgenden Ziele verfolgt:

  • Bessere Wartbarkeit: Bündelung von zusammengehörigen Klassen und Dateien um Fehler bei der Anwendung zu vermeiden
  • Schnellerer download von Applet der Java Web Start Anwendungen durch Kompression und Begrenzung des Downloads auf eine einzelne Datei
  • Gewährleistung der Integrität und Authentizität  von Anwendungen: jar Dateien können signiert werden
  • Vereinfachte Softwaredistribution und Installation durch Beschränkung auf eine einzige Datei

jar Dateien werden mit dem gleichnamigen Kommando jar des JDK manipuliert. jar steht für Java ARchive.

Das jar Format ist plattformunabhängig und ist daher auf allen Javaplattformen verfügbar.

Hinweis: Das jar Format basiert auf dem zip Dateiformat. Das heißt, dass man auch mit zip,unzip Inhalte von jar Dateien inspizieren kann! Zum Erzeugen von jar Dateien ist zip jedoch nicht geeignet da jar zusätzliche Informationen wie Manifestinformationen etc. erzeugt.

Die wichtigsten jar Operationen
Operation Befehl
Erzeugen einer jar Datei jar cf jar-Datei liste-Dateien
Auflisten des Inhalts einer jar Datei jar tvf jar-Datei
Auslesen einer jar Datei jar xf jar-Datei 
Auslesen bestimmter Dateien einer jar Datei jar xf jar-Datei auszupackende-Datei
Starten einer Javaanwendung mit Hilfe einer jar Datei java -jar anwendung.jar

 

Erzeugen einer jar Datei

Das allgemeine Format zum Erzeugen einer Datei mit einem jar-Archiv ist:

jar cf jar-Datei Eingabedatei(en)

Die hier verwendeten Optionen und Argumente bedeuten

  • c Option: Erzeugen (c = create) einer Datei
  • f Option: Erzeugen einer Datei (anstatt Ausgabe des Ergebnis auf der Standardausgabe bzw. Konsole)
  • jar-Datei: Name der zu erzeugenden Datei. Die Extension *.jar ist nicht zwingend, sie ist jedoch üblich.
  • Eingabedatei(en): Eine oder mehrere Dateien die in das Archiv aufgenommen werden sollen.
    • Bei mehr als einer Datei werden die Dateien durch Leerstellen getrennt
    • Werden Verzeichnisse angegeben so wird der vollständige Inhalt der Verzeichnisse rekursiv in das Archiv aufgenommen
    • Es können mit dem "Wildcard"-Operator mehrere Dateien aufgenommen werden. Beispiel *.class um alle Javabytecodedateien eines Verzeichnisses aufzunehmen.

Beim Erzeugen einer jar Datei legt das Kommando jar immer eine Manifest Datei MANIFEST.MF im folgenden Verzeichnis des Archivs an:

META-INF/MANIFEST.MF

Der Inhalt dieser Datei ist im einfachsten Fall:

Manifest-Version: 1.0
Created-By: 1.6.0 (Sun Microsystems Inc.)

Im Manifest des Archivs werden Metadaten wie zum Beispiel Signaturen, das gewählte Hauptprogramm zum Starten, oder Urheberansprüche verwaltet.

Weitere Optionen:

  • v (verbose) detaillierte Informationen während der Ausführung des Kommandos
  • 0 keine Kompression verwenden
  • M kein Standardmanifest generieren
  • m Einfügen einer eigenen Manifestdatei

Beispiel

Gegeben sei eine Anwendung bei der drei Klassen Database.classMain.class und GUI.class und alle Bilddateien im Verzeichnis bilder in das jar Archiv gepackt werden sollen:

Dies geschieht mit dem Befehl:

$ jar cvf appl.jar *.class bilder
Manifest wurde hinzugefügt.
Hinzufügen von: Database.class (ein = 10240) (aus = 27) (komprimiert 99 %)
Hinzufügen von: GUI.class (ein = 10240) (aus = 27) (komprimiert 99 %)
Hinzufügen von: Main.class (ein = 10240) (aus = 27) (komprimiert 99 %)
Hinzufügen von: bilder/ (ein = 0) (aus = 0) (gespeichert 0 %)
Hinzufügen von: bilder/Bild1.jpg (ein = 10240) (aus = 27) (komprimiert 99 %)
Hinzufügen von: bilder/Bild2.jpg (ein = 10240) (aus = 27) (komprimiert 99 %)
Hinzufügen von: bilder/Bild3.jpg (ein = 10240) (aus = 27) (komprimiert 99 %)

Inspektion eines jar Archivs

jar Archive können mit der der jar Option t inspiziert werden:

jar tvf jar-Datei

Die hier verwendeten Optionen und Argumente bedeuten:

  • t Option: Ausgabe des Inhaltsverzeichnis ( t = table of contents)
  • f Option: Das zu inspizierende Archiv ist eine Datei
  • jar-Datei: Die zu inspizierende Datei falls die Option t gewählt wurde
  • v Option: (verbose) zusätzliche Informationen wie Dateigrößen und Änderungsdatum der Archivdateien

Beispiel

Für das oben angeführte Beispiel ergibt sich der folgende Befehl

$ jar tf appl.jar
META-INF/
META-INF/MANIFEST.MF
Database.class\r\nGUI.class
nMain.class
bilder/
bilder/Bild1.jpg
bilder/Bild2.jpg
bilder/Bild3.jpg

Einen detaillierten Überblick kann man mit der v Option (v: verbose, engl. "ausführlich") gewinnen

$ jar tvf appl.jar
     0 Sun Dec 12 16:27:56 CET 2010 META-INF/
    60 Sun Dec 12 16:27:56 CET 2010 META-INF/MANIFEST.MF
 10240 Sun Dec 12 16:26:10 CET 2010 Database.class
 10240 Sun Dec 12 16:26:00 CET 2010 GUI.class
 10240 Sun Dec 12 16:25:50 CET 2010 Main.class
     0 Sun Dec 12 16:27:12 CET 2010 bilder/
 10240 Sun Dec 12 16:27:02 CET 2010 bilder/Bild1.jpg
 10240 Sun Dec 12 16:27:04 CET 2010 bilder/Bild2.jpg
 10240 Sun Dec 12 16:27:12 CET 2010 bilder/Bild3.jpg

Extrahieren eines jar Archivs

jar Archive werden mit dem folgenden Befehl ausgepackt (ausgelesen);

$ jar xvf jar-Datei [archivierte-Datei(en)]

Die hier verwendeten Optionen und Argumente bedeuten:

  • x Option: Extrahiere Dateien aus einem jar Archiv
  • f Option: Extrahiere Dateien aus einer Datei (und nicht von der Standardeingabe)
  • jar-Datei: die Datei mit dem zu extrahierenden jar Archiv
  • archivierte-Datei(en): eine optionale, mit Leerzeichen separierte Liste von Dateien die extrahiert werden sollen. jar wird alle Dateien des Archivs extrahieren falls diese Liste nicht angegeben wird.

Der jar Befehl wird beim Extrahieren

  • existierende Dateien überschreiben
  • bei Bedarf neue Unterverzeichnisse anlegen
  • die ursprüngliche Archivdatei nicht verändern.

Beispiel

Auspacken des jar Archivs mit ausführlicher Protokollierung:

$ jar xvf appl.jar
     erstellt: META-INF/
dekomprimiert: META-INF/MANIFEST.MF
dekomprimiert: Database.class
dekomprimiert: GUI.class
dekomprimiert: Main.class
     erstellt: bilder/
dekomprimiert: bilder/Bild1.jpg
dekomprimiert: bilder/Bild2.jpg
dekomprimiert: bilder/Bild3.jpg

Hinzufügen von Dateien zu jar Archiven

Die Option u (update) erlaubt das Hinzufügen von Dateien zu Archiven mit der folgenden Syntax

$ jar uf jar-Archiv Datei(en)

Benutzen von jar Archiven beim Ausführen von Programmen

Das Javalaufzeitsystem sucht beim Aufruf mit Hilfe des "Classpath" (Pfad zu den Klassen) nach ausführbaren Dateien mit der Endung .class .

Wird kein expliziter "Classpath" angegeben, so wird  im aktuellen Verzeichnis und den darunterliegenden Verzeichnissen gesucht. Unterverzeichnisse können Pakete mit deren Klassen enthalten.

Mit Hilfe der Option -cp oder -classpath kann man die Suche nach Klassendateien steuern. Man kann hier eine Liste der folgenden Dinge angeben:

  • Verzeichnisse
  • jar Dateien
  • zip Dateien

Die Elementeliste der jar Archive und Suchverzeichnisse mit mit dem Zeichen ":" getrennt. Hiermit kann man ein Javaprogramm mit einem jar Archiv starten:

$ java -cp appl.jar Main

Starten von Programmen aus jar Archiven

jar Archive können benutzt werden um direkt Programme aus ihnen heraus anzustarten. Dies geschieht mit der Option -jar im Kommando java:

$ java -jar jar-Datei

Hierzu muss in der Manifestdatei des Archivs ein einzeiliger Eintrag mit der Klasse stehen deren Methode main() aufgerufen werden soll. Dieser Eintrag muss im folgenden Format geschehen:

Main-Class: klassenname

Der obige Eintrag muß mit einem "Carriage return" (Zeilenumbruch) abgeschlossen werden, da er sonst nicht korrekt ausgelesen wird (Siehe Oracle Tutorial).

Zum Erzeugen des Manifesteintrags gibt es eine Reihe von Möglichkeiten

Option m (Manifest): Übergabe einer Manifestdatei mit Startklasse

Eine Manifestdatei mit den gewünschten Einträgen wird selbst erstellt und dann beim Erzeugen des Archivs mit Hilfe der m-Option mit angegeben.

Beispiel:

$ jar cfm appl.jar Manifest.txt *.class bilder

Option e (Entrypoint): Angabe der zu startenden Klasse

Die Klasse mit der main() Methode wird direkt angegeben.

Beispiel:

$ jar cfe appl.jar Main *.class bilder

Referenzen

 

10.7 Java unter Windows

Konfigurieren des Suchpfads für Kommandozeileneingaben

Im Kurs ist es notwendig Javakommandos auf der Kommandozeile aufzurufen. Auf Windows muss man entweder immer den vollen Pfad (z.Bsp. C:\Program Files\Java\jdk1.8.0_60\bin\java ) eingeben. Oder man konfiguriert Windows so, dass es im entsrpechenden Verzeichnis nach diesen Befehlen sucht. Nach erfolgreicher Konfiguration kann man die Befehle java, javac, jar etc. wie folgt verwenden:

Kommanozeilenfenster

Für MacOs Anwender gibt es nichts zu tun!

Als erstes muss man das das bin Verzeichnis des JDK finden. In ihm stehen alle Programme des JDK. Man kann es an den Dateien javac.exe und javadoc.exe erkennen Ein typischer Ort kann zum Beispiel sein:

C:\Program Files\Java\jdk1.8.0_60\bin\

Dieses Verzeichnis muss zum Standardsuchpfad des Benutzers hinzugefügt werden. Bei Windows geschieht das wie folgt:

Öffnen des Control Panels

Bei Windows 2012R2 finden Sie einen Eintrag bei den "Apps" den Anwendungen

Ikone Control Panel

 

Öffnen der Systemsteuerung

Klicken Sie dann auf "System and Security"

Systemsteuerung

Klicken Sie auf "System"

Windows Systemsauswahl

 

Klicken Sie auf "Advanced system settings"

Advanced System settings Windows

Ein Dialog mit dem Titel "System Properties" erscheint.

System Properties 

 Wählen Sie hier den Button "Environment Variables"

Environment Windows

Suchen Sie hier den Eintrag "Path" in "System variables" und klicken Sie dann den "Edit" Button

Pfad variable

 

Beim Einpflegen des zusätzlichen Suchpfades ist das Folgende zu beachten

  • Die verschiedenen Verzeichnisse im Suchpfad werden mit Semikolons abgetrennt
  • Der Pfad zu den Java JDK-kommandos wird hinter den existierenden Suchverzeichnissen eingetragen. So werden Javakommandos nur dann ausgeführt wenn sie in keinem anderen Verzeichnis gefunden wurden.

Vorsicht:

  • Dies ist eine systemweite Einstellung. Eine Fehlkonfiguration wird alle Anwendungen auf dem System betreffen!
  • Ist im Pfad schon ein anderes JDK konfiguriert, können Konflikte auftreten.

Editieren PATH

 Kontrolle

Bei einem erlfogreich gepflegten Pfad kann man in einem neuen Fenster zum Beispiel die Javaversion mit dem Befehl "java -version" kontrollieren.

Kontrolle 

 Funktioiert dieser Befehl ist sichergestellt, dass ein Java JRE oder JDK installiert ist. Zum Sicherstellen, dass das JDK garantiert installiert und konfiguriert ist, muss man "javac -version" aufrufen. Der Javaübersetzer ist nur Teil des JDK.

10.8 Typische Fehler

System.out.print() und System.out.println()

Beide Befehle erzeugen einer Konsolenausgabe von Texten.

  • Die Methode print() lässt den Schreibzeiger an der aktuellen Position stehen. Die nächste Konsolenausgabe erfolgt in der gleichen Zeile
  • Die Methode println() (ln= Line) erzeugt am Ende des ausgegebenen Texts einen Zeilenumbruch. Die nächste Konsolenausgabe erfolgt in der nächsten Zeile.

Beispiele

Quellcode Ausgabe
System.out.print("Person: ");
System.out.println("John Doe");
System.out.println("****************");
Person: John Doe
****************

aber:

Quellcode Ausgabe
System.out.println("Person: ");
System.out.println("John Doe");
System.out.println("****************");
Person: 
John Doe
****************

String ist eine Klasse und kein Basistyp

Java kann Zeichenketten nicht als Basistypen verwalten. Hierzu verwendet Java eine Klasse mit dem Namen String. Zeichenketten werden wie Objekte behandelt

Die hat eine Reihe von Konsequenzen:

  • String ist kein Schlüsselwort
  • Variablen vom Typ String sind nur Zeiger auf Zeichenketten
    • Es gibt Situationen in denen zwei Variablen auf zwei Zeichenketten mit dem gleichen Inhalt zeigen. Dies können eventuell verschiedene Objekte sein.

Verwechslung von Zuweisungsoperator (=) und Vergleichsoperator (==)

Mit

i=j;

wird der Wert von j auf i zugewiesen. Mit

if(i==j)

werden zwei Variablen verglichen.

Zuweisungen tauchen nicht in boolschen Termen auf!

Die folgende Syntax ist korrekt:

int i=0;
int j=1;
boolean b = (i==j);

Die Boolsche Variable b wird mit false belegt da i ungleich j ist.

10.9 Lernziele

Die klausurrelevanten Abschnitte dieses Kapitels sind das Packen mit dem Java-archive Werkzeug jar und der Befehl javadoc

Am Ende dieses Blocks können Sie:

  • ... Dateien mit Hilfe einer Manifestdatei zu einem jar Archiv packen
  • ... jar Archive erstellen die beim Aufruf von java mit einer automatisch vorkonfigurieten Klasse starten
  • ... jar Archive Packen,  Auslesen und Entpacken
  • ... Dokumentationskommentare von normalen Kommentaren unterscheiden
  • ... die Tags zur Dokumentation der Eingabevariablen und Rückgabewerte benutzen um eine Methode zu dokumentieren
  • ... mit Hilfe der Anwendung javadoc eine html Dokumentation für eine oder mehrere Klassen erzeugen

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten:

11. Ressourcen und Quellen

Duke reading a book

 

Lernen

Javabilder

Javakurse

Javaprogrammierkurs Dr. Manfred Jackel, Uni Koblenz

12.1 Quellcode der in der Vorlesung verwendeten Applets

Hier finden Sie den Quellcode der in dieser Vorlesung verwendeten Applets.

Die meißten in diesem Skript integrierten Applets kann man auch als reguläre Programme starten.

Alle Applets stehen Ihnen unter der sehr liberalen BSD Lizenz zur Verfügung.

Die Applets wurden entwickelt um die Vorlesung interaktiver zumachen. Der Quellcode wurde nicht auf den besten Programmierstil optimiert.

Ich freue mich daher über Feedback und verbesserte Versionen.

Die hier verwendeten Applets sind sicher nicht perfekt, sie wurden mit den folgenden Zielen implementiert

  • Wenig Code:
    • Copy und Paste der Quellen soll einfach sein
    • Hierdurch werden leider auch viele Fehlerfälle nicht behandelt
  • Alles in einer Klasse: Vereinfacht Copy und Paste für den Anwender
  • Beschränkung auf Konzepte die in der Vorlesung behandelt werden
    • die Implementierungen sollen am Ende der Vorlesung nachvollziehbar sein
  • Entwicklungszeit: Auch kleine Applets brauchen Ihre Zeit. Man kann sicherlich Dinge eleganter lösen.

12.1.1 Klasse block1.Kubikwuzel

Das Applet

Starten des Applets als Hauptprogramm

Laden Sie die jar Datei Kurs1Applet.jar.

Die Anwendung kann von der Kommandozeile mit dem folgenden Kommando gestartet werden.

java -cp Kurs1Applet.jar block1.Kubikwurzel

Die Datei Kurs1Applet.jar muss im gleichen Verzeichnis sein 

Der Quellcode

package block1;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
*
* @author sschneid
* @version 1.3
*
* Copyright (c) 2016, Dr. Stefan Schneider, schneider4me.de All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of the FreeBSD Project.
*/
public class Kubikwurzel extends JApplet {
private int versuche = 0;
private int richtig = 0;
private int falsch = 0;
private int wurzel = 0;
private int potenz = 0;
final private JTextField eingabeFeld;
private String statusText;
final private JLabel status;
final private JLabel challenge;
/**
* Innere Klasse zum Behandeln der GUI Eingaben
*/
private class MyListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
int eing = 0;
boolean ergebnis;
try {
eing = Integer.decode(eingabeFeld.getText());
} catch (NumberFormatException n) {
ergebnis = false;
}
ergebnis = (eing == wurzel);
versuche++;
if (ergebnis) {
eingabeFeld.setBackground(Color.green);
richtig++;
} else {
eingabeFeld.setBackground(Color.red);
falsch++;
}
statusText = (ergebnis ? "Richtig! " : "Falsch! ")
+ versuche + " Versuche, "
+ richtig + " richtig, "
+ falsch + " falsch";
status.setText(statusText);
status.setBackground(Color.RED);
neueWurzel();
challenge.setText("Kubikwurzel von " + potenz + " ?");
eingabeFeld.setText("");
}
}
/**
* Konstruktor der Klasse. Hier werden alle graphischen
* Objekte konfiguriert.
*/
public Kubikwurzel() {
neueWurzel();
JFrame f;
Container co;
JPanel meinPanel = new JPanel();
meinPanel.setLayout(new BorderLayout());
Kubikwurzel.MyListener ml = new Kubikwurzel.MyListener();
eingabeFeld = new JTextField(4);
eingabeFeld.setBackground(Color.white);
eingabeFeld.addActionListener(ml);
challenge = new JLabel("Kubikwurzel von " + potenz + " ?");
JButton enter = new JButton("Enter");
enter.addActionListener(ml);
status = new JLabel("0 Versuche. 0 richtig. 0 falsch");
status.setBackground(Color.pink);
meinPanel.add(challenge, BorderLayout.WEST);
meinPanel.add(eingabeFeld, BorderLayout.CENTER);
meinPanel.add(enter, BorderLayout.EAST);
meinPanel.add(status, BorderLayout.SOUTH);
this.add(meinPanel);
}
/**
* Starten der Anwendung als eigenständiges Programm
*
* @param args
*/
public static void main(String[] args) {
JFrame f = new JFrame("Kubikwurzel");
f.add(new Kubikwurzel());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
/**
* Berechnen eines neuen Tupels von Kubikwurzel
* und dazu gehöriger dritter Potenz
*/
public final void neueWurzel() {
wurzel = (int) (Math.random() * 100D);
potenz = wurzel * wurzel * wurzel;
}
/**
* Berechnet die Kubikwurzel eine positiven Zahl
*
* @param k Zahl von der die Kubikwurzel berechnet werden soll.
* @return ergebnis die Kubikwurzel
*/

public static int kubikwurzelVon(int k) {
int ergebnis=1;
do {
int f = primfaktor(k);
ergebnis = ergebnis*f;
k = k / (f*f*f);
} while (k>1);
return ergebnis;
}
/**
* Diese Methode berechnet einen Primfaktor des Werts k
* Es wird 1 zurückgegeben wenn es keine anderen Primfaktoren
* gibt
* @param k Die Zahl von der ein Primfaktor berechnet wird
* @return f ein Primfaktor der Zahl oder 1 falls keiner existiert
*/
public static int primfaktor(int k) {
int f = 1;
do { f++;
}
while ((k/f*f!=k)&&(f<k));
if (f==k) f=1;
return f;
}
}
 

 

12.1.2 Klasse block2.BinaerIntApplet

Das Applet

Starten des Applets als Hauptprogramm

Laden Sie die jar Datei Kurs1Applet.jar.

Die Anwendung kann von der Kommandozeile mit dem folgenden Kommando gestartet werden.

java -cp Kurs1Applet.jar block2.BinaerIntApplet

Die Datei Kurs1Applet.jar muss im gleichen Verzeichnis sein 

Der Quellcode

package block2;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
/**
*
* @author sschneid
* @version 1.2
*
* Copyright (c) 2013, Dr. Stefan Schneider, schneider4me.de
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those
* of the authors and should not be interpreted as representing official policies,
* either expressed or implied, of the FreeBSD Project.
*/
public class BinaerIntApplet extends JApplet implements ActionListener {
private JTextField eingabeText;
private JButton enterButton;
private JRadioButton byteButton;
private JRadioButton charButton;
private JRadioButton shortButton;
private JRadioButton intButton;
private JRadioButton longButton;
private int[] bits;
private String eingabeWert = "0";
private String typeTxt = "int";
/**
* Konstruktor der Klasse. Hier werden alle graphischen
* Objekte angeliegt.
*/
public BinaerIntApplet() {
JPanel buttonPanel;
JFrame f;
Container co;
bits = new int[32];
// Erzeugen der Buttons und Texteingabefeld
JLabel eingabeLabel = new JLabel("Eingabewert: ");
eingabeText = new JTextField(eingabeWert);
eingabeText.setPreferredSize(new Dimension(100, 20));
enterButton = new JButton("Enter");
enterButton.addActionListener(this);
byteButton = new JRadioButton("byte");
byteButton.setMnemonic(KeyEvent.VK_B);
charButton = new JRadioButton("char");
charButton.setMnemonic(KeyEvent.VK_C);
shortButton = new JRadioButton("short");
shortButton.setMnemonic(KeyEvent.VK_S);
intButton = new JRadioButton("int");
intButton.setMnemonic(KeyEvent.VK_I);
intButton.setSelected(true);
longButton = new JRadioButton("long");
longButton.setMnemonic(KeyEvent.VK_L);
ButtonGroup intType = new ButtonGroup();
intType.add(byteButton);
intType.add(charButton);
intType.add(shortButton);
intType.add(intButton);
intType.add(longButton);
JPanel typePanel = new JPanel();
typePanel.setLayout(new BoxLayout(typePanel, BoxLayout.Y_AXIS));
typePanel.add(byteButton);
typePanel.add(charButton);
typePanel.add(shortButton);
typePanel.add(intButton);
typePanel.add(longButton);
// Einfügen der drei Komponenten in ein Panel
// Das Gridlayout führt zum Strecken der drei Komponenten
buttonPanel = new JPanel();
buttonPanel.add(eingabeLabel);
buttonPanel.add(eingabeText);
buttonPanel.add(enterButton);
JPanel centerPanel=new JPanel();
centerPanel.setSize(500, 500);
Container myPane = getContentPane();
myPane.add(buttonPanel, BorderLayout.SOUTH);
myPane.add(typePanel, BorderLayout.EAST);
myPane.add(centerPanel, BorderLayout.CENTER);
}
/**
* Dies Methode erlaubt das Malen der Vektorgraphik
* auf dem Hintergrund der graphischen Komponente
* @param g
*/
@Override
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.blue);
g.drawString(typeTxt + ", " +bits.length + " bits, "
+eingabeWert +": ", 20, 25);

int rows=0;
int cols=0;
for (int i = (bits.length - 1); i >= 0; i--) {
if (i == bits.length - 1) {
g.setColor(Color.red);
} else {
g.setColor(Color.black);
}
g.drawString(Integer.toString(bits[i]),
20 + 10 * cols,
50+ 25*rows);
if (cols!=15) cols++;
else {cols=0; rows++;}
}
}
/**
* Diese Methode wird nach einer Eingabe aufgerufen.
* Sie dekodiert die Eingabe des Benutzers
* @param e
*/
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
try {
eingabeWert = eingabeText.getText();
if (source == enterButton) { //enter Button aufgerufen
if (byteButton.isSelected()) {
typeTxt = "byte";
byte wert = Byte.parseByte(eingabeWert);
bits = decode(wert);
}
if (shortButton.isSelected()) {
typeTxt = "short";
short wert = Short.parseShort(eingabeWert);
bits = decode(wert);
}
if (charButton.isSelected()) {
typeTxt = "char";
char wert = eingabeWert.charAt(0);
bits = decode(wert);
eingabeWert = eingabeWert.substring(0, 1);
}
if (intButton.isSelected()) {
typeTxt = "int";
int wert = Integer.parseInt(eingabeWert);
bits = decode(wert);
}
if (longButton.isSelected()) {
typeTxt = "long";
long wert = Long.parseLong(eingabeWert);
bits = decode(wert);
}
}
} catch (java.lang.NumberFormatException ex) {
// Fehlerbehandlung bei fehlerhafter Eingabe
eingabeWert="Fehler";
eingabeText.setText(eingabeWert);
bits = decode((byte)0);
}
repaint();
}
public static int[] decode(short s) {
int size = 16;
int[] stellen = new int[size];
for (int i = 0; i < size; i++) {
stellen[i] = (1 & s);
s >>>= 1;
}
return stellen;
}
public static int[] decode(byte s) {
int size = 8;
int[] stellen = new int[size];
for (int i = 0; i < size; i++) {
stellen[i] = (1 & s);
s >>>= 1;
}
return stellen;
}

public static int[] decode(char s) {
return decode((short) s);
}

public static int[] decode(int s) {
int size = 32;
int[] stellen = new int[size];
for (int i = 0; i < size; i++) {
stellen[i] = (1 & s);
s >>>= 1;
}
return stellen;
}
public static int[] decode(long s) {
int size = 64;
int[] stellen = new int[size];
for (int i = 0; i < size; i++) {
stellen[i] = (int) (1 & s);
s >>>= 1;
}
return stellen;
}
/**
* Starten der Anwendung als eigenständiges Programm
*
* @param args
*/
public static void main(String[] args) {
// Es wird ein JFrame benötigt, in das das Applet als Komponente
// gesteckt wird.
JFrame f = new JFrame("BinaerIntApplet-Standalone");
f.add(new block2.BinaerIntApplet());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}
/**
* Berechnen eines neuen Tupels von Kubikwurzel
* und dazu gehöriger dritter Potenz
*/
}
 

 

12.1.3 Klasse block2.BinaerFloatApplet

Das Applet

 

Starten des Applets als Hauptprogramm

Laden Sie die jar Datei Kurs1Applet.jar.

Die Anwendung kann von der Kommandozeile mit dem folgenden Kommando gestartet werden.

java -cp Kurs1Applet.jar block2.BinaerFloatApplet

Die Datei Kurs1Applet.jar muss im gleichen Verzeichnis sein 

Der Quellcode

package block2;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

/**
*
* @author sschneid
* @version 1.2
*
* Copyright (c) 2013, Dr. Stefan Schneider, schneider4me.de
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those
* of the authors and should not be interpreted as representing official policies,
* either expressed or implied, of the FreeBSD Project.
*/
public class BinaerFloatApplet extends JApplet implements ActionListener {

private JTextField eingabeText;
private JButton enterButton;
private JButton nanButton;
private JButton negativeInfinityButton;
private JButton positiveInfinityButton;
private JButton maxButton;
private JButton minButton;
private int[] bits;
private String eingabeWert = "1";
private int vorzeichen;
private float mantisse;
private int exponent;
float wert = 0;
public BinaerFloatApplet() {
JPanel buttonPanel;
bits = new int[32];
bits = decode(1.0f);
// Erzeugen der Buttons und Texteingabefeld
JLabel eingabeLabel = new JLabel("Eingabewert: ");
eingabeText = new JTextField(eingabeWert);
eingabeText.setPreferredSize(new Dimension(100, 20));
enterButton = new JButton("Enter");
enterButton.addActionListener(this);
nanButton = new JButton("N.a.N.");
nanButton.addActionListener(this);
negativeInfinityButton = new JButton("-Infinity");
negativeInfinityButton.addActionListener(this);
positiveInfinityButton = new JButton("+Infinity");
positiveInfinityButton.addActionListener(this);
maxButton = new JButton("MAX_VALUE");
maxButton.addActionListener(this);
minButton = new JButton("MIN_VALUE");
minButton.addActionListener(this);
JPanel optPanel = new JPanel();
optPanel.setLayout(new BoxLayout(optPanel, BoxLayout.Y_AXIS));
optPanel.add(nanButton);
optPanel.add(minButton);
optPanel.add(maxButton);
optPanel.add(negativeInfinityButton);
optPanel.add(positiveInfinityButton);
buttonPanel = new JPanel();
buttonPanel.add(eingabeLabel);
buttonPanel.add(eingabeText);
buttonPanel.add(enterButton);
JPanel centerPanel = new JPanel();
centerPanel.setSize(500, 500);
Container myPane = getContentPane();
myPane.add(buttonPanel, BorderLayout.SOUTH);
myPane.add(optPanel, BorderLayout.EAST);
myPane.add(centerPanel, BorderLayout.CENTER);
}
@Override
public void paint(Graphics g) {
super.paint(g);
int hoffSet = 8; // Ziffernbreite
int voffset = 15; // Zeilenabstand
int binaerStart = 100; // Horizontaler Offset für Bitsdarstellung
int dezimalStart = hoffSet * 2;
g.setColor(Color.blue);
g.drawString("32 Bit float:", dezimalStart, 25);
g.drawString(eingabeWert + " = ", dezimalStart, 45);
dezimalStart += 10+(eingabeWert.length()) * hoffSet;
g.drawString("(-1)", dezimalStart, 45);
dezimalStart += 3 * hoffSet;
g.drawString(Integer.toString(bits[31]), dezimalStart, 40);
dezimalStart += 1 * hoffSet;
g.drawString("*2", dezimalStart, 45);
dezimalStart += 2 * hoffSet;
g.drawString("(", dezimalStart, 40);
dezimalStart += 1 * hoffSet;
g.drawString(Integer.toString(exponent), dezimalStart, 40);
dezimalStart += Integer.toString(exponent).length() * hoffSet;
g.drawString("-127)", dezimalStart, 40);
dezimalStart += 5 * hoffSet;
g.drawString("*", dezimalStart, 45);
dezimalStart += 1 * hoffSet;
g.drawString(Float.toString(mantisse), dezimalStart, 45);
int rows = 0;
int cols;
g.setColor(Color.black);
g.drawString("Vorzeichen : ",20,60 + voffset * rows);
String vorz = "-1*";
if (bits[31] == 0) {
vorz = "1*";
}
g.drawString(Integer.toString(bits[31]),
binaerStart,
60 + voffset * rows);
rows++;
g.setColor(Color.black);
g.drawString("Exponent : ",
20,
60 + voffset * rows);
cols = 0;
for (int i = 30; i >= 23; i--) {
g.drawString(Integer.toString(bits[i]),
binaerStart + 10 * cols,
60 + voffset * rows);
if (cols != 7) {
cols++;
} else {
cols = 0;
rows++;
}
}
g.drawString("Mantisse :",20,60 + voffset * rows);
cols = 0;
g.setColor(Color.blue);
g.drawString("1",
binaerStart + 10 * cols,
60 + voffset * rows);
g.setColor(Color.black);
cols++;
for (int i = 22; i >= 0; i--) {
g.drawString(Integer.toString(bits[i]),
binaerStart + 10 * cols,
60 + voffset * rows);
if (cols != 7) {
cols++;
} else {
cols = 0;
rows++;
}
}
g.setColor(Color.blue);
g.fill3DRect(20,(70 + voffset * rows), 200, 40, rootPaneCheckingEnabled);
rows++;
g.setColor(Color.black);
g.drawString("Die führende 1 der Mantisse ist",
20,70 + voffset * rows);
rows++;
g.drawString("nicht Teil der Datenstruktur!",
20, 70 + voffset * rows);

}
@Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source == nanButton) { //>> Button aufgerufen
eingabeWert = eingabeText.getText();
wert = Float.NaN;
}
if (source == minButton) {
wert = Float.MIN_VALUE;
}
if (source == maxButton) {
wert = Float.MAX_VALUE;
}
if (source == negativeInfinityButton) {
wert = Float.NEGATIVE_INFINITY;
}
if (source == positiveInfinityButton) {
wert = Float.POSITIVE_INFINITY;
}
if (source == enterButton) {
try {
eingabeWert = eingabeText.getText();
wert = Float.parseFloat(eingabeWert);
} catch (java.lang.NumberFormatException ex) {
// Fehlerbehandlung bei fehlerhafter Eingabe
eingabeText.setText("Fehler");
wert = 0;
}
}
bits = decode(wert);
eingabeText.setText(Float.toString(wert));
eingabeWert = Float.toString(wert);
repaint();
}
public int[] decode(float s) {
int size = 32;
int t = Float.floatToRawIntBits(s);
mantisse = Float.intBitsToFloat((t & 0x007FFFFF) | 0x40000000);
mantisse /= 2;

System.out.println("Binär Mantisse: "
+ Integer.toBinaryString(Float.floatToRawIntBits(mantisse)));
exponent = (t & 0x7F800000) >> 23;
vorzeichen = (t & 0x80000000) >> 31;
System.out.println("Mantisse:" + mantisse);
System.out.println("Exponent:" + (exponent - 127));
System.out.println("Vorzeichen:" + vorzeichen);
int[] stellen = new int[size];
for (int i = 0; i < size; i++) {
stellen[i] = (1 & t);
t >>>= 1;
}
return stellen;
}
public int[] decode(double s) {
int size = 64;
long t = Double.doubleToRawLongBits(s);
int[] stellen = new int[size];
for (int i = 0; i < size; i++) {
stellen[i] = (int) (1 & t);
t >>>= 1;
}
return stellen;
}
/**
* Starten der Anwendung als eigenständiges Programm
*
* @param args
*/
public static void main(String[] args) {
// Es wird ein JFrame benötigt, in das das Applet als Komponente
// gesteckt wird.
JFrame f = new JFrame("BinaerFloatApplet-Standalone");
f.add(new block2.BinaerFloatApplet());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setSize(400, 300);
f.setVisible(true);
}
}

 

12.1.4 Klasse BinaerCastApplet

Das Applet

 

Starten des Applets als Hauptprogramm

Laden Sie die jar Datei Kurs1Applet.jar.

Die Anwendung kann von der Kommandozeile mit dem folgenden Kommando gestartet werden.

java -cp Kurs1Applet.jar block2.BinaerCastApplet

Die Datei Kurs1Applet.jar muss im gleichen Verzeichnis sein 

Der Quellcode

package block2;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
/**
*
* @author sschneid
* @version 1.2
*
* Copyright (c) 2013, Dr. Stefan Schneider, schneider4me.de
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those
* of the authors and should not be interpreted as representing official policies,
* either expressed or implied, of the FreeBSD Project.
*/
public class BinaerCastApplet extends JApplet implements ActionListener {
private JTextField eingabeText;
private JButton enterButton;
private JRadioButton byteButton;
private JRadioButton charButton;
private JRadioButton shortButton;
private JRadioButton intButton;
private JRadioButton longButton;
private JRadioButton vonByteButton;
private JRadioButton vonCharButton;
private JRadioButton vonShortButton;
private JRadioButton vonIntButton;
private JRadioButton vonLongButton;
private int[] vonBits;
private int[] zuBits;
private String eingabeWert = "0";
private String typeTxt = "int";
private String vonTypeTxt = "int";
private String zuString = "0";
private String vonString = "0";
public BinaerCastApplet() {
JPanel buttonPanel;
vonBits = new int[32];
zuBits = new int[32];
// Erzeugen der Buttons und Texteingabefeld
JLabel eingabeLabel = new JLabel("Eingabewert y: ");
eingabeText = new JTextField(eingabeWert);
eingabeText.setPreferredSize(new Dimension(100, 20));
enterButton = new JButton("Enter");
enterButton.addActionListener(this);
byteButton = new JRadioButton("byte");
byteButton.setMnemonic(KeyEvent.VK_B);
charButton = new JRadioButton("char");
charButton.setMnemonic(KeyEvent.VK_C);
shortButton = new JRadioButton("short");
shortButton.setMnemonic(KeyEvent.VK_S);
intButton = new JRadioButton("int");
intButton.setMnemonic(KeyEvent.VK_I);
intButton.setSelected(true);
longButton = new JRadioButton("long");
longButton.setMnemonic(KeyEvent.VK_L);
vonByteButton = new JRadioButton("byte");
vonCharButton = new JRadioButton("char");
vonShortButton = new JRadioButton("short");
vonIntButton = new JRadioButton("int");
vonIntButton.setSelected(true);
vonLongButton = new JRadioButton("long");
ButtonGroup intType = new ButtonGroup();
intType.add(byteButton);
intType.add(charButton);
intType.add(shortButton);
intType.add(intButton);
intType.add(longButton);
JPanel typePanel = new JPanel();
typePanel.setLayout(new BoxLayout(typePanel, BoxLayout.Y_AXIS));
typePanel.add(new JLabel(" x"));
typePanel.add(byteButton);
typePanel.add(charButton);
typePanel.add(shortButton);
typePanel.add(intButton);
typePanel.add(longButton);
ButtonGroup vonIntType = new ButtonGroup();
vonIntType.add(vonByteButton);
vonIntType.add(vonCharButton);
vonIntType.add(vonShortButton);
vonIntType.add(vonIntButton);
vonIntType.add(vonLongButton);
JPanel vonTypePanel = new JPanel();
vonTypePanel.setLayout(new BoxLayout(vonTypePanel, BoxLayout.Y_AXIS));
vonTypePanel.add(new JLabel(" y"));
vonTypePanel.add(vonByteButton);
vonTypePanel.add(vonCharButton);
vonTypePanel.add(vonShortButton);
vonTypePanel.add(vonIntButton);
vonTypePanel.add(vonLongButton);
// Einfügen der drei Komponenten in ein Panel
// Das Gridlayout führt zum Strecken der drei Komponenten
buttonPanel = new JPanel();
buttonPanel.add(eingabeLabel);
buttonPanel.add(eingabeText);
buttonPanel.add(enterButton);
JPanel centerPanel=new JPanel();
centerPanel.setSize(500, 500);
Container myPane = getContentPane();
myPane.add(buttonPanel, BorderLayout.SOUTH);
myPane.add(vonTypePanel, BorderLayout.EAST);
myPane.add(typePanel, BorderLayout.WEST);
myPane.add(centerPanel, BorderLayout.CENTER);
}
@Override
public void paint(Graphics g) {
super.paint(g);
int offSetLinks = 80;
g.setColor(Color.blue);
g.drawString(vonTypeTxt + " y = "+vonString +"; ", offSetLinks, 20);
g.drawString(typeTxt + " x = ("+typeTxt +") y;", offSetLinks, 40);
g.setColor(Color.black);
g.drawString(" y: " + vonString +"; binär:",offSetLinks, 65);
int rows=0;
int cols=0;
for (int i = (vonBits.length - 1); i >= 0; i--) {
if (i == vonBits.length - 1) {
g.setColor(Color.red);
} else {
g.setColor(Color.black);
}
g.drawString(Integer.toString(vonBits[i]),
offSetLinks + 10 * cols,
80+ 15*rows);
if (cols!=15) cols++;
else {cols=0; rows++;}
}
if (cols==8) rows++; // habe ein byte gemalt.
cols=0;
g.drawString(" x: " + zuString +"; binär:",offSetLinks, 85+ 15*rows);
rows++;
for (int i = (zuBits.length - 1); i >= 0; i--) {
if (i == zuBits.length - 1) {
g.setColor(Color.red);
} else {
g.setColor(Color.black);
}
g.drawString(Integer.toString(zuBits[i]),
offSetLinks + 10 * cols,
85+ 15*rows);
if (cols!=15) cols++;
else {cols=0; rows++;}
}
}
@Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
try {
eingabeWert = eingabeText.getText();
if (source == enterButton) { //enter Button aufgerufen
if (byteButton.isSelected()) typeTxt = "byte";
if (shortButton.isSelected()) typeTxt = "short";
if (charButton.isSelected()) typeTxt = "char";
if (intButton.isSelected()) typeTxt = "int";
if (longButton.isSelected()) typeTxt = "long";
if (vonByteButton.isSelected()) {
vonTypeTxt = "byte";
byte wert = Byte.parseByte(eingabeWert);
vonBits = decode(wert);
vonString = Byte.toString(wert);
if (byteButton.isSelected()) {
byte zuWert = wert;
zuBits = decode(zuWert);
zuString = Byte.toString(zuWert);
}
if (shortButton.isSelected()) {
short zuWert = wert;
zuBits = decode(zuWert);
zuString = Short.toString(zuWert);
}
if (charButton.isSelected()) {
char zuWert = (char)wert;
zuBits = decode(zuWert);
zuString = "'"+Character.toString(zuWert)+"'";
}
if (intButton.isSelected()) {
int zuWert = wert;
zuBits = decode(zuWert);
zuString = Integer.toString(zuWert);
}
if (longButton.isSelected()) {
long zuWert = wert;
zuBits = decode(zuWert);
zuString = Long.toString(zuWert);
}
}
if (vonShortButton.isSelected()) {
vonTypeTxt = "short";
short wert = Short.parseShort(eingabeWert);
vonBits = decode(wert);
vonString = Short.toString(wert);
if (byteButton.isSelected()) {
byte zuWert = (byte)wert;
zuBits = decode(zuWert);
zuString = Byte.toString(zuWert);
}
if (shortButton.isSelected()) {
short zuWert = wert;
zuBits = decode(zuWert);
zuString = Short.toString(zuWert);
}
if (charButton.isSelected()) {
char zuWert = (char)wert;
zuBits = decode(zuWert);
zuString = "'"+Character.toString(zuWert)+"'";
}
if (intButton.isSelected()) {
int zuWert = wert;
zuBits = decode(zuWert);
zuString = Integer.toString(zuWert);
}
if (longButton.isSelected()) {
long zuWert = wert;
zuBits = decode(zuWert);
zuString = Long.toString(zuWert);
}
}
if (vonCharButton.isSelected()) {
vonTypeTxt = "char";
char wert = eingabeWert.charAt(0);
vonBits = decode(wert);
vonString = "'"+Character.toString(wert)+"'";
if (byteButton.isSelected()) {
byte zuWert = (byte)wert;
zuBits = decode(zuWert);
zuString = Byte.toString(zuWert);
}
if (shortButton.isSelected()) {
short zuWert = (short)wert;
zuBits = decode(zuWert);
zuString = Short.toString(zuWert);
}
if (charButton.isSelected()) {
char zuWert = wert;
zuBits = decode(zuWert);
zuString = "'"+Character.toString(zuWert)+"'";
}
if (intButton.isSelected()) {
int zuWert = wert;
zuBits = decode(zuWert);
zuString = Integer.toString(zuWert);
}
if (longButton.isSelected()) {
long zuWert = wert;
zuBits = decode(zuWert);
zuString = Long.toString(zuWert);
}
}
if (vonIntButton.isSelected()) {
vonTypeTxt = "int";
int wert = Integer.parseInt(eingabeWert);
vonBits = decode(wert);
vonString = Integer.toString(wert);
if (byteButton.isSelected()) {
byte zuWert = (byte)wert;
zuBits = decode(zuWert);
zuString = Byte.toString(zuWert);
}
if (shortButton.isSelected()) {
short zuWert = (short)wert;
zuBits = decode(zuWert);
zuString = Short.toString(zuWert);
}
if (charButton.isSelected()) {
char zuWert = (char)wert;
zuBits = decode(zuWert);
zuString = "'"+Character.toString(zuWert)+"'";
}
if (intButton.isSelected()) {
int zuWert = wert;
zuBits = decode(zuWert);
zuString = Integer.toString(zuWert);
}
if (longButton.isSelected()) {
long zuWert = wert;
zuBits = decode(zuWert);
zuString = Long.toString(zuWert);
}
}
if (vonLongButton.isSelected()) {
vonTypeTxt = "long";
long wert = Long.parseLong(eingabeWert);
vonBits = decode(wert);
vonString = Long.toString(wert);
if (byteButton.isSelected()) {
byte zuWert = (byte)wert;
zuBits = decode(zuWert);
zuString = Byte.toString(zuWert);
}
if (shortButton.isSelected()) {
short zuWert = (short)wert;
zuBits = decode(zuWert);
zuString = Short.toString(zuWert);
}
if (charButton.isSelected()) {
char zuWert = (char)wert;
zuBits = decode(zuWert);
zuString = "'"+Character.toString(zuWert)+"'";
}
if (intButton.isSelected()) {
int zuWert = (int)wert;
zuBits = decode(zuWert);
zuString = Integer.toString(zuWert);
}
if (longButton.isSelected()) {
long zuWert = wert;
zuBits = decode(zuWert);
zuString = Long.toString(zuWert);
}
}
}
} catch (java.lang.NumberFormatException ex) {
// Fehlerbehandlung bei fehlerhafter Eingabe
eingabeWert="Fehler";
eingabeText.setText(eingabeWert);
vonBits = decode((byte)0);
}
repaint();
}
public static int[] decode(short s) {
int size = 16;
int[] stellen = new int[size];
for (int i = 0; i < size; i++) {
stellen[i] = (1 & s);
s >>>= 1;
}
return stellen;
}
public static int[] decode(byte s) {
int size = 8;
int[] stellen = new int[size];
for (int i = 0; i < size; i++) {
stellen[i] = (1 & s);
s >>>= 1;
}
return stellen;
}
public static int[] decode(char s) {
return decode((short) s);
}
public static int[] decode(int s) {
int size = 32;
int[] stellen = new int[size];
for (int i = 0; i < size; i++) {
stellen[i] = (1 & s);
s >>>= 1;
}
return stellen;
}
public static int[] decode(long s) {
int size = 64;
int[] stellen = new int[size];
for (int i = 0; i < size; i++) {
stellen[i] = (int) (1 & s);
s >>>= 1;
}
return stellen;
}
/**
* Starten der Anwendung als eigenständiges Programm
*
* @param args
*/
public static void main(String[] args) {
// Es wird ein JFrame benötigt, in das das Applet als Komponente
// gesteckt wird.
JFrame f = new JFrame("BinaerCastApplet-Standalone");
f.add(new block2.BinaerCastApplet());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setSize(400, 300);
f.setVisible(true);
}
}

 

12.1.5 Klasse IntShiftApplet

Das Applet

Starten des Applets als Hauptprogramm

Laden Sie die jar Datei Kurs1Applet.jar.

Die Anwendung kann von der Kommandozeile mit dem folgenden Kommando gestartet werden.

java -cp Kurs1Applet.jar block2.IntShiftApplet

Die Datei Kurs1Applet.jar muss im gleichen Verzeichnis sein 

Der Quellcode

package block2;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
/**
*
* @author sschneid
* @version 1.2
*
* Copyright (c) 2013, Dr. Stefan Schneider, schneider4me.de
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those
* of the authors and should not be interpreted as representing official policies,
* either expressed or implied, of the FreeBSD Project.
*/
public class IntShiftApplet extends JApplet implements ActionListener {

private JTextField eingabeText;
private JButton enterButton;
private JButton leftShiftNullFillButton;
private JButton rightShiftButton;
private JButton rightShiftNullFillButton;
private JButton maxButton;
private JButton minButton;
private int[] bits;
private String eingabeWert = "1";
private int vorzeichen;
private float mantisse;
private int exponent;
int wert = 1;
public IntShiftApplet() {
JPanel buttonPanel;
bits = new int[32];
bits = decode(1);
// Erzeugen der Buttons und Texteingabefeld
JLabel eingabeLabel = new JLabel("Eingabewert: ");
eingabeText = new JTextField(eingabeWert);
eingabeText.setPreferredSize(new Dimension(100, 20));
enterButton = new JButton("Enter");
enterButton.addActionListener(this);
leftShiftNullFillButton = new JButton("a << 1");
leftShiftNullFillButton.addActionListener(this);
rightShiftButton = new JButton("a >> 1");
rightShiftButton.addActionListener(this);
rightShiftNullFillButton = new JButton("a >>> 1");
rightShiftNullFillButton.addActionListener(this);
maxButton = new JButton("MAX_VALUE");
maxButton.addActionListener(this);
minButton = new JButton("MIN_VALUE");
minButton.addActionListener(this);
JPanel optPanel = new JPanel();
optPanel.setLayout(new BoxLayout(optPanel, BoxLayout.Y_AXIS));
optPanel.add(minButton);
optPanel.add(maxButton);
optPanel.add(leftShiftNullFillButton);
optPanel.add(rightShiftButton);
optPanel.add(rightShiftNullFillButton);
buttonPanel = new JPanel();
buttonPanel.add(eingabeLabel);
buttonPanel.add(eingabeText);
buttonPanel.add(enterButton);
JPanel centerPanel = new JPanel();
centerPanel.setSize(500, 500);
Container myPane = getContentPane();
myPane.add(buttonPanel, BorderLayout.SOUTH);
myPane.add(optPanel, BorderLayout.EAST);
myPane.add(centerPanel, BorderLayout.CENTER);
}
@Override
public void paint(Graphics g) {
super.paint(g);
int hoffSet = 8; // Ziffernbreite
int voffset = 15; // Zeilenabstand
int binaerStart = 80; // Horizontaler Offset für Bitsdarstellung
int dezimalStart = hoffSet * 2;
int vertikal = 25;
g.setColor(Color.blue);
g.drawString("32 Bit Integer", dezimalStart, vertikal);
g.setColor(Color.black);
vertikal += hoffSet*2;
g.drawString("Dezimal: a = "+ eingabeWert, dezimalStart, vertikal);
vertikal += hoffSet*2;
g.drawString("Binär: a =", dezimalStart, vertikal);

int rows = 0;
int cols = 0;
for (int i = (bits.length - 1); i >= 0; i--) {
if (i == bits.length - 1) {
g.setColor(Color.red);
} else {
g.setColor(Color.black);
}
g.drawString(Integer.toString(bits[i]),
binaerStart + 10 * cols,
vertikal+ 15*rows);
if (cols != 7) cols++;
else {cols=0; rows++;}
}
}
/**
* Diese Methode decodiert Benutzer
* @param e
*/
@Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source == leftShiftNullFillButton) {
eingabeWert = eingabeText.getText();
wert = wert << 1;
}
if (source == minButton) {
wert = Integer.MIN_VALUE;
}
if (source == maxButton) {
wert = Integer.MAX_VALUE;
}
if (source == rightShiftButton) {
wert = wert >> 1;
}
if (source == rightShiftNullFillButton) {
wert = wert >>> 1;
}
if (source == enterButton) {
try {
eingabeWert = eingabeText.getText();
wert = Integer.parseInt(eingabeWert);
} catch (java.lang.NumberFormatException ex) {
// Fehlerbehandlung bei fehlerhafter Eingabe
eingabeText.setText("Fehler");
wert = 0;
}
}
bits = decode(wert);
eingabeText.setText(Integer.toString(wert));
eingabeWert = Integer.toString(wert);
repaint();
}
public static int[] decode(int s) {
int size = 32;
int[] stellen = new int[size];
for (int i = 0; i < size; i++) {
stellen[i] = (1 & s);
s >>>= 1;
}
return stellen;
}
/**
* Starten der Anwendung als eigenständiges Programm
*
* @param args
*/
public static void main(String[] args) {
// Es wird ein JFrame benötigt, in das das Applet als Komponente
// gesteckt wird.
JFrame f = new JFrame("IntShiftApplet-Standalone");
f.add(new block2.IntShiftApplet());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setSize(400, 300);
f.setVisible(true);
}
}