11. Weitere Konzepte

11.1 Schnittstellen (Interfaces)

Javaklassen enthalten Methoden und Datenfelder. Die öffentlich zugänglichen Methoden und Datenfelder bilden die Schnittstellen einer Klasse. Schnittstellen sind wichtige Hilfsmittel bei Entwurf komplexer Softwaresysteme. Sie erlauben es unterschiedliche Subsysteme definiert zu koppeln (Coupling). Die Wechselwirkung zwischen diesen Subsystemen wird auch Coherence oder Cohesion genannt.

Schnittstellen unterstützen das Konzept des Information Hiding und sollten möglichst stabil sein. Stabile Schnittstellen erlauben es mehreren Teams unabhängig von einander zu arbeiten. Sie erlauben eine Trennung von

  • Spezifikation und
  • Implementierung

Schnittstellen (engl. Interfaces) sind ein wichtiges Sprachmittel beim Entwurf von Softwaresystemen.

Sie enthalten daher nur die für die Spezifikation wichtige Elemente:

  • Methodendeklarationen
  • öffentliche Attribute

Klassen hingegen enthalten die Spezifikation, alle Attribute sowie zusätzlich die Implementierung der Methoden durch die Methodenrümpfe.

Javaschnittstellen (Interfaces)

Java bietet im Gegensatz zu C++ das Schlüsselwort interface zur Beschreibung von Schnittstellen. Java-Interfaces muss man gedanklich als abstrakte Klassen verstehen, die selbst über keine Implementierung von Methoden verfügen.

Klassen enthalten die Spezifikation und zusätzlich die Implementierung der Methoden durch die Methoden Rümpfe/Blöcke. Schnittstellen in Java enthalten (in der Regel, siehe unten) nur die Spezifikation.

Eigenschaften Klasse abstrakte Klasse Schnittstelle
Öffentliche Attribute Ja Ja Ja
Private Attribute Ja Ja Nein
Methodenköpfe (die Spezifikation) Ja Ja Ja
Methodenrumpf Ja Nein (bei abstr. Meth.) Nein (in der Regel)
kann von Klasse erben Ja Ja Nein
kann von Schnittstelle erben Nein Nein Ja
kann eine oder mehrere Schnittstelle implementieren Ja Ja Nein

 Für Java-Schnittstellen gilt:

  • alle Attribute müssen öffentliche, finale und initialisierte Konstanten sein
  • alle Methoden sind abstrakt (abstract), öffentlich (public), nicht final und nicht statisch
  • sie geben keine Konstruktoren vor. Die Gründe sind:
    • Konstruktoren haben immer den Namen der aktuellen Klasse.
    • Ein Konstruktor in einer Implementierung kann nicht den Namen der implementierten Schnittstelle besitzen.

Notation einer Java-Schnittstelle

[public] interface Interface-name [extends Interface1, Interface2, ... ];
  • Das Schlüsselwort public erlaubt wie auch bei Klassen das Interface ausserhalb des zugehörigen Pakets zu benutzen
  • dem Schlüsselwort interface folgt der Name der Schnittstelle und
  • eine optionale Liste von Schnittstellen von denen die Schnittstelle erbt. Diese Liste von Schnittstellen wird mit dem Schlüsselwort extends eingeleitet

Schnittstellen können nur von Schnittstellen abgeleitet werden.

Klassen können nur von Klassen abgeleitet werden. Klassen implementieren jedoch beliebig viele Schnittstellen durch das Schlüsselwort implements.

[public|private|protected] class Klassenname1 extends Klassenname2 [implements Interface1, Interface2, ....]

Die Klasse Klassenname1 wird aus Klassenname2 abgeleitet und implementiert (Schlüsselwort implements) optional eines oder mehrere Interfaces.

Beispiel

Personen (class Person), sowie Lieferanten (class Supplier) implementieren das Interface BankAccount. BankAccount muss mit einer IBAN Nummer und dem Namen einer Bank umgehen können. Das Interface macht keinerlei Vorgaben zum Setzen dieses Daten. Das Erfassen dieser Daten obliegt dem Implementierer der Schnittstelle.

interface BankAccount {
   public long iban();
   public String bank();
}

class Person implements BankAccount {
   private String myIban;
   private String myBank;
   public name()  { // Implementierung
   }
   public long iban() {
      ...
      return myIban;
   }
   public String bank() {
      ...
      return myBank;
   }
}

class Supplier implements BankAccount {
   private String supplierIban;
   private String supplierBank;
   public String getLicense() { //Implementierung
      }
   public long iban() {
      ...
      return supplierIban;
   }
   public String bank() {
      return supplierBank;
   }
}
...
BankAccount b1 = new Person();
BankAccount b2 = new Supplier();
...
System.out.println(b1.bank());
System.out.println(b2.bank());

Die Klasse Person implementiert alle Methoden von BankAccount und verhält sich in allen Aspekten wie das Interface.

Für Klassen die Schnittstellen(Interfaces) implementieren gilt:

  • sie können beliebig viele Interfaces gleichzeitig implementieren.
  • sie implementieren alle Methoden und Attribute des Interface
    • implementieren sie nicht alle Aspekte des Interface müssen sie als abstrakte Klasse deklariert werden. Erst abgeleitete Klassen die alle Aspekte der Schnittstelle zur Verfügung stellen müssen nicht mehr abstrakt sein.

Schnittstellen und Vererbung

  • Schnittstellen können von Schnittstellen erben (Schlüsselwort extends)
    • Dies deutet für den Entwickler, dass er alle Methoden der Vererbungshierarchie implementieren muss. In diesen Aspekt verhalten sich Schnittstellenmethoden wie abstrakte Methoden.
  • Klassen können nicht von Schnittstellen erben. Sie implementieren Schnittstellen!

UML Notation

In UML Klassendiagrammen wird die Java Interface-beziehung als Verfeinerung mit gestrichelten Pfeilen gekennzeichnet: Alternativ kann man die zu implementierende Klasse auch mit einem Strich und einem Kreis darstellen:

UML Diagramm mit Java Schnittstellen

alternative Schnittstellenbeschreibung in UML

Anwendung von Schnittstellen (Interfaces)

Schnittstellen (Schlüsselwort interface) und Vererbung (Schlüsselwort extends) sind zwei Vererbungskonzepte in Java. Sie haben unterschiedliche Zielsetzungen bezüglich Strukturierung und Planung.

  • Schnittstellen (Interfaces)
    • fördern modularen Aufbau auf Softwaresystemen
      • erlauben unabhängige Implementierung und vereinfachen den Austausch von Schnittstellenimplementierungen
    • fordern nur die Implementierung bestimmter Methoden.
    • entkoppeln Systeme und fördern die genaue Spezifikation von benötigten Diensten (Verträge!)
    • schaffen Strukturen die in späteren Phasen der Softwareentwicklung die Komplexität der Implementierung senken
  • Vererbung
    • erleichert Codewiederverwendung
    • führt zu einer engeren Kopplung von Systemen.
      • Da immer eine Implementierung der Oberklasse genutzt werden muss. Man muss z.Bsp. den Konstruktor der Oberklasse immer nutzen
      • Eine Unterklasse muss immer alle Eigenschaften der Oberklasse aufweisen
  Vererbung (extends) Schnittstelle (interface)
Spezifikation wird von der Oberklasse vorgegeben wird vom Interface vorgegeben
Programmcode wird von der Oberklasse vorgegeben.
wird von einer abstrakten Oberklasse gefordert
Interface fordert eine Implementierung
Mehrfachvererbung/Implementierung nein ja
Abhängigkeit der Unterklasse/Interfaceimplementierung hängt stark von der Oberklasse ab hängt schwächer vom zu implementierenden Interface ab

Implikationen

Schnittstellen (Interfaces) müssen Aufgrund ihrer großen Bedeutung für den Entwurf von Softwaresystemen sehr sorgsam entworfen werfen. Änderungen in Schnittstellen führen dazu, dass alle Klassen die eine Schnittstelle  (Interface) implementieren, bei einer Änderung vom Laufzeitsystem nicht mehr akzeptiert werden. Bei einer Schnittstellenänderung müssen alle Klassen der neuen Schnittstelle angepasst und neu übersetzt werden. 

Schnittstellen versus Vererbung mit Polymorphismus

Schnittstellen sind oft eine bessere Lösung für langlebige Softwaresysteme. Die enge Kopplung an die vererbenden Basisklassen werden sich ändernden Anforderungen auch oft ein Hindernis. Der Vorteil alle ererbten Methoden direkt benutzen zu können, kann hier auch zum Nachteil werden, insbesondere wenn Klassenhierarchien tief und unübersichtlich sind.

Die Konzepte von

  • Assoziation und
  • Schnittstellen

führen oft zu einem initial höheren Implementierungsaufwand. Sie bieten jedoch eine bessere Entkopplung von Softwarekomponenten. 

Weiterführende Quellen: "Why extends is evil"

Genau hingeschaut: Methodenimplementierungen von Schnittstellen

Weiter oben wird behauptet, dass in der Deklaration einer Schnittstelle keine Implementierung von Methoden möglich sind. Diese Aussage ist seit Java 8 so nicht mehr korrekt. Sie gilt aber nach wie vor für sehr, sehr viele Anwendungsfälle.

Die neuen Möglichkeiten von "default" Methoden in Schnittstellen werden im Rahmen dieser Vorlesung nicht beachtet. Man sollte diesen Sonderfall kennen. Für das grundlegende Verständnis des Schnittstellenkonzepts ist dieser "exotische" Anwendungsfall aber nicht notwendig.

Hintergrund

Vor Java 8 waren alle Methoden eines Interfaces abstrakt. Das heißt, es lag nur der Methodenkopf mit der Signatur vor. Seit Java 8 kann man in Interfaces

  • default-Methoden implementieren
  • statische Methoden implementieren

Die Notwendigkeit dieser Konzepte entstanden durch die Integration von Lamdba-Ausdrücken (JSR 335) . Man benötigt diese Konzepte um die Rückwärtskompatibilität zu älteren Javaimplementierungen zu gewährleisten. Es beruht auf dem Problem der Java Interface-Evolution bei der Benutzung der Collection Klassen in Lamdba-Ausdrücken. Angelika Langer hat dieses Problem sehr gut in einem deutschsprachigen Internetartikel im Java Magazin beschrieben.

11.1.1 Beispiele Schnittstellenanwendung

 Serialisierung

Serialisierung ist ein Beispiel bei dem man gut sieht wie eine Klasse durch Implementierung einer Schnittstelle wichtige Eigenschaften gewinnen man. Man kann sie in einer Datei speichern oder in einen beliebigen Stream stecken!

Java bietet die Möglichkeit Objekte im Hauptspeicher in einen seriellen Datenstron zu konvertieren den man zum Beispiel in Dateien schreiben kann. Diese Technologie nennt man Serialisierung(engl. Serialization siehe Wikipedia). Java kann Klassen serialisieren die die Schnittstelle Serializable implementieren.

Java kann Objekte serialisieren wenn

  • die entsprechende Klasse die Schnittstelle Serializable implementiert und
  • wenn alle Attribute die auf Objekte zeigen auch die Schnittstelle Serializable implementieren

Das folgende Beispielprogramm ist in der Lage eine Person und ihre Adresse in eine Datei zu schreiben und wieder zu lesen.

Alles was man tun muss um die Klassen Person und Adresse serialisierbar zu machen ist die Schlüsselworte "implements Serializable" hinzuzufügen

 Heap Diagramm mit vier Objekten zur Serialisierung
  1. Im Hauptprogramm wird eine Person Urmel angelegt die auf eine Adresse Lummerland zeigt
  2. Die Methode Methode schreiben() schreibt alle von p referenzierten Objekte in eine Datei serialisiert.ser
  3. Die Methode lesen() liest die Person und alle referenzierten Objekte aus der Datei zurück
  4. p1 zeigt nun auf eine neue Person mit eigenen referenzierten Objekt Adresse

 

 Überlegungen:

  • Die Schnittstelle Serializable fordert keine Methoden zu implementieren!
  • Warum sind 4 Objekte in der Datei?
  • Was würde geschehen wenn die die Klasse Person mit Stammbaum aus der ersten Vorlesung serialisieren würde?
  • Was würde geschehen wenn man weitere Attribute mit Basistypen zu den Klassen hinzufügt?
  • Was geschieht wenn man auf eine Klasse referenziert die nicht serialisierbar ist?

Klasse Serialisierung

package Kurs2.Schnittstelle;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
*
* @author stsch
*/
public class Serialisierung {
/**
* Erzeuge eine Person und die Adresse. Schreibe sie in eine
* Datei und lese sie aus der Datei
* @param args
*/
public static void main(String[] args) {
final String meineDatei = "serialisiert.ser";
// Erzeuge das Objekt der Person und das Objekt mit der Adresse
Person p = new Person("Urmel", new Adresse("Lummerland"));
System.out.println("Zu schreibende Person " + p.name +
" wohnt in " + p.wohnort.stadt);
// Schreibe die Objekte in eine Datei
schreiben(p, meineDatei);
Person p1 = (Person) lesen(meineDatei);
System.out.println("Gelesene Person " + p1.name +
" wohnt in " + p1.wohnort.stadt);
System.out.println("Die geschrieben Adresse und die gelesene"+
" Adresse " + p.wohnort.stadt + " sind" +
(p.wohnort==p1.wohnort? " " : " nicht ") +
"identisch");
}
/**
* Diese Methode schreibt ein beliebiges serialisierbares Objekt
* in eine Datei
* @param o Objekt welches in eine Datei geschrieben wird.
* Es muss serialisierbar sein!
* @param datei Die Datei in die geschrieben werden soll
*/
public static void schreiben(Object o, String datei) {
try {
// Erzeuge einen Stream der in eine Datei geleitet wird
FileOutputStream fileOut = new FileOutputStream(datei);
// Erzeuge einen Strem der Objekte in einen Filestrem leitet
ObjectOutputStream out = new ObjectOutputStream(fileOut);
// Schreibe ein beliebiges Objekt in diesen Objectstream
out.writeObject(o);
// Schliesse Stream
out.close();
// Schliesse Dateistream. Die letzten Bytes werden so raus geschrieben
fileOut.close();
System.out.println("Serialisierte Daten sind gespeichert in Datei "
+ datei);
} catch (IOException i) { // Hier können Ausnahmen auftreten
i.printStackTrace();
}
}
/**
*
* @param datei die Datei aus der gelesen werden soll
* @return
*/
public static Object lesen(String datei) {
System.out.println("Lesen aus " + datei);
Object o;
try { // Hier können Ausnahmen Auftreten
// Öffne Datei aus der gelesen werden soll
FileInputStream fileIn = new FileInputStream(datei);
// Erzeuge Objectstream der aus Datei liest
ObjectInputStream in = new ObjectInputStream(fileIn);
//Lies Objekt aus Stream
o = in.readObject();
// Schließe Objectstream
in.close();
// Schließe Datei
fileIn.close();
} catch (IOException i) { // Wird ausgeführt wenn Probleme auftreten
i.printStackTrace();
return null;
} catch (ClassNotFoundException c) {
System.out.println("Gelesene Klasse nicht gefunden");
c.printStackTrace();
return null;
}
return o;
}

}

Klasse Adresse

package Kurs2.Schnittstelle;
import java.io.Serializable;
/**
 *
 * @author stsch
 */
public class Adresse implements Serializable {
    String stadt;
    public Adresse (String s) {
        stadt =s;
    }    
}

Klasse Person

package Kurs2.Schnittstelle;
import java.io.Serializable;
/**
 *
 * @author stsch
 */
public class Person implements Serializable {
    String name;
    Adresse wohnort;
    public Person(String n, Adresse w) {
        wohnort = w;
        name=n;
    }    
}

Weiterführende Übungen und Überlegungen zu diesem Beispiel

  1. Was geschieht wenn zwei Personen auf das gleiche Adressobjekt zeigen? Wird die Adresse zweimal gespeichert oder nur einmal? Selbst wenn die Adresse nur einmal gespeichert wird. Was geschieht beim Lesen der serialisierten Datei. Wird das Addressobjekt verdoppelt?
  2. Was geschieht beim Übersetzen und Ausführen wenn man den Term "implements Serializable" in der Klasse Adresse oder Person weglässt?
  3. Versioning Problem: Was geschieht wenn man die Objekte in eine Datei serialisiert. Dann ein Attribut zu einer Klasse hinzufügt oder weglässt. Dann die serialisierten Objekt wieder einliest?
  4. Was muss man tun wenn man ein Attribut (zBsp. stadt) nicht speichern möchte? Suchen Sie in der Java-Spezifikation oder mit einer Suchmaschine...

11.1.2 Übung: Schnittstellen und abstrakte Klassen

Übung 1: Implementiere die Klasse Dollar aus der abstrakten Klasse Number

Die Java Laufzeitumgebung bietet die abstrakte Klasse Number aus der alle Zahlentypen abgeleitet werden.

Implementieren Sie eine Klasse Dollar die aus der Klasse Number abgeleitetet wird.

  • Die Klasse soll aus Sicherheitsgründen mit unveränderbaren Attributen belegt sein. Der Wert des Objekts darf nur im Konstruktur gesetzt werden.
  • Im Konstruktor soll der Betrag nach Dollar und Centbeträgen getrennt erfasst werden. Unsinnige Centbeträge sollen auf Null Cent gesetzt werden.

Vorgehen:

  • Arbeiten Sie am besten im Paket Kurs2.Schnittstelle. Beim Kopieren sind dann keine Anpassungen notwendig.
  • Nutzen Sie die Klasse Kurs2.SchnittstelleTestDollar zum Testen Ihrer Implementierung.
  • Legen Sie eine Klasse Kurs2.Schnittstelle.Dollar an, die aus der Klasse Number abgeleitet wird.
  • Tipp: Verwaltung des Betrags. Es ist einfacher den gesamten Betrag als Centbetrag in einem long zu speichern
  • Geben Sie bei allen Methoden die ganze Zahlen implementieren nur den Dollarbetrag aus
  • Geben Sie bei den Fließkommamethoden den Dollar und Centbetrag auf 2 Nachkommastellen aus (Rundungsfehler können ignoriert werden)

Tipp: Vorsicht beim Berechnen des Centbetrag im Konstruktor bei negativen Werten. Es gilt:

  • $3.25 = 3 *100 + 25 cents
  • $-3.25 = -3 * 100 - 25 cents
  • daher gilt die Rechenregel cents = signum(dollar)*( abs(dollar)*100+abs(cents) )

Nutzen Sie hierfür die statischen Importdeklarationen:

import static java.lang.Math.abs;
import static java.lang.Math.signum;

Das Testprogramm TestDollar

package Kurs2.Schnittstelle;
public class TestDollar {
/**
*
* Das Hauptprogramm benötigt keine Parameter
* Es testet die wichtigsten Eigenschaften der Klasse Dollar
*/
public static void main(String[] args) {
System.out.println("Phase 1: Einfache Tests");
Dollar konto1 = new Dollar(172, 12);
long d1 = konto1.longValue();
System.out.println("Dollar.longValue().Sollwert 172; Istwert: " + d1);
double d2 = konto1.doubleValue();
System.out.println("Dollar.doubleValue().Sollwert 172.12; Istwert: " + d2);
float d3 = konto1.floatValue();
System.out.println("Dollar.floatValue().Sollwert 172.12; Istwert: " + d3);
System.out.println("Phase 2: Härtere Tests");
Dollar konto2 = new Dollar(-380, 25);
d1 = konto2.longValue();
System.out.println("Dollar.longValue().Sollwert -380; Istwert: " + d1);
d2 = konto2.doubleValue();
System.out.println("Dollar.doubleValue().Sollwert -380.25; Istwert: " + d2);
d3 = konto2.floatValue();
System.out.println("Dollar.floatValue().Sollwert -380.25; Istwert: " + d3);
Dollar konto3 = new Dollar (-382,225);
d1 = konto3.longValue();
System.out.println("Dollar.longValue().Sollwert -382; Istwert: " + d1);
d2 = konto3.doubleValue();
System.out.println("Dollar.doubleValue().Sollwert -382; Istwert: " + d2);
d3 = konto3.floatValue();
System.out.println("Dollar.floatValue().Sollwert -382; Istwert: " + d3);
}
}

Die Ausgaben sollten etwa wie folgt aussehen:

Phase 1: Einfache Tests
Dollar.longValue().Sollwert 172; Istwert: 172
Dollar.doubleValue().Sollwert 172.12; Istwert: 172.12
Dollar.floatValue().Sollwert 172.12; Istwert: 172.12
Phase 2: Härtere Tests
Dollar.longValue().Sollwert -380; Istwert: -380
Dollar.doubleValue().Sollwert -380.25; Istwert: -380.25
Dollar.floatValue().Sollwert -380.25; Istwert: -380.25
Dollar.longValue().Sollwert -382; Istwert: -382
Dollar.doubleValue().Sollwert -382; Istwert: -382.0
Dollar.floatValue().Sollwert -382; Istwert: -382.0

Übung 2: Implementieren von Schnittstellen

Implementieren Sie die Klasse Euro. Die Klasse Euro

  • wird aus der Klasse Number abgeleitet
  • implementiert die Schnittstelle Comparable
  • implementiert die SchnittStelle Waehrung
  • soll noch die Methode public String toString() überschreiben um einen formatierten Wert ausgeben zu können.

Hinweise zur Implementierung der abstrakten Klasse Number

Kopieren Sie sich alle abstrakten Methoden der Klasse Number in die Klasse Dollar! Somit stellen Sie sicher, dass die Signatur identisch ist und das Überschreiben funktioniert.

Hinweise zur Implementierung der Schnittstelle Waehrung

Geben Sie bei der Methode symbol() ein "€" Zeichen zurück.

Implementierung der Methode mult(double faktor).

Diese Methode dient der Zinsberechnung. Durch die Multiplikation mit dem Wert 1.03 soll man den Wert um 3% Vergrößeren oder Verkleinern können.

  • Erzeugen Sie ein neues Objekt vom Typ Euro und geben Sie es direkt zurück. Verändern Sie da aktuelle Objekt nicht.
  • Berechnen des Centbetrags: Nehmen Sie den Absolutwert ihres neu berechneten Centbetrags und dann davon den Rest einer Division durch 100 (Modulo). Negative Beträge werden so korrekt berechnet.

Hinweise zu Implementierung der Schnittstelle Comparable()

Diese Schnittstelle erlaubt vielen Hilfsklassen in Java eine Sortierung vorzunehmen. Die Methode gibt abhängig vom Vergleich der Objekte negative, postive oder einen Nullwert zurück. Die Logik des Vergleichs liegt in der Verantwortung des Programmieres.

Tipp: Sie müssen in der Lage sein einen Euro gegen ein beliebiges Objekt zu vergleichen!

  • Prüfen Sie den Typ des Objekts vor dem Vergleich (siehe Kapitel Polymorphismus)
    • Geben Sie einen konstanten Betrag (-1 oder 1) zurück wenn Sie die Typen nicht vergleichen können
  • Falls Sie sicher sind, dass Sie Euro mit Euro vergleichen, können das Signum der Differenz des Wert in Cent benutzen.

Die Schnittstelle Waehrung

Benutzen Sie dies Klasse

package Kurs2.Schnittstelle;
/**
*
* @author sschneid * @ version 1.1
*/
public interface Waehrung {
/**
*
* @return Währungssymbol
*/
public String symbol();
/**
* Multipliziert den Wert des Objekts mit der Fließkommazahl
*
* @param f
* @return neues Objekt welches das Produkt enthält
*/
public Waehrung mult(double f);
}

Ein Testprogramm TestEuro

package Kurs2.Schnittstelle;
import java.util.Arrays;
/**
*
* @author sschneid
* @version 1.1
*/
public class TestEuro {
public static void main(String[] args) {
System.out.println("Phase 1: Einfache Tests");
Euro konto1 = new Euro(172, 12);
long d1 = konto1.longValue();
System.out.println(" Euro.longValue().Sollwert 172; Istwert: " + d1);
double d2 = konto1.doubleValue();
System.out.println(" Euro.doubleValue().Sollwert 172.12; Istwert: " + d2);
float d3 = konto1.floatValue();
System.out.println(" Euro.floatValue().Sollwert 172.12; Istwert: " + d3);
System.out.println(" Euro.symbol().Sollwert €; Istwert: " + konto1.symbol());
System.out.println(" Euro.toString().Sollwert 172.12€; Istwert: " + konto1);
System.out.println("Phase 2: Härtere Tests");
Euro konto2 = new Euro(-380, 25);
d1 = konto2.longValue();
System.out.println(" Euro.longValue().Sollwert -380; Istwert: " + d1);
d2 = konto2.doubleValue();
System.out.println(" Euro.doubleValue().Sollwert -380.25; Istwert: " + d2);
d3 = konto2.floatValue();
System.out.println(" Euro.floatValue().Sollwert -380.25; Istwert: " + d3);
Euro konto3 = new Euro (-382,225);
d1 = konto3.longValue();
System.out.println(" Euro.longValue().Sollwert -382; Istwert: " + d1);
d2 = konto3.doubleValue();
System.out.println(" Euro.doubleValue().Sollwert -382; Istwert: " + d2);
d3 = konto3.floatValue();
System.out.println(" Euro.floatValue().Sollwert -382; Istwert: " + d3);
System.out.println("Phase 3: Multiplikation testen");
Waehrung konto10 = new Euro(1,23);
double m1 = 2;
Waehrung konto11 = konto10.mult(m1);
System.out.println(" Euro.mult(): "+konto10 +" * "+ m1 + " = " + konto11);
Waehrung konto20 = new Euro(1,98);
double m2 = 2;
Waehrung konto21 = konto20.mult(m2);
System.out.println(" Euro.mult(): "+konto20 +" * "+ m2 + " = " + konto21);
Waehrung konto30 = new Euro(-1,98);
double m3 = 2;
Waehrung konto31 = konto30.mult(m3);
System.out.println(" Euro.mult(): "+konto30 +" * "+ m3 + " = " + konto31);
Waehrung konto40 = new Euro(-1,98);
double m4 = -2;
Waehrung konto41 = konto40.mult(m4);
System.out.println(" Euro.mult(): "+konto40 +" * "+ m4 + " = " + konto41);
Waehrung konto50 = new Euro(10,10);
double m5 = -2.01;
Waehrung konto51 = konto50.mult(m5);
System.out.println(" Euro.mult(): "+konto50 +" * "+ m5 + " = " + konto51);
Waehrung konto60 = new Euro(10,1);
double m6 = -2.001;
Waehrung konto61 = konto60.mult(m6);
System.out.println(" Euro.mult(): "+konto60 +" * "+ m6 + " = " + konto61);
System.out.println("Phase 4: Einfache Tests für Comparable");
if (konto1.compareTo(konto2) >0)
{System.out.println(" "+konto1 + " ist groeßer als " + konto2);}
else
{System.out.println(" "+konto1 + " ist nicht groeßer als " + konto2);}
if (konto3.compareTo(konto2) >0)
{System.out.println(" "+konto3 + " ist groeßer als " + konto2);}
else
{System.out.println(" "+konto3 + " ist nicht groeßer als " + konto2);}
sortierTest();
}
public static void sortierTest() {
System.out.println("Phase 4: Testen der Schnittstelle Comparable");
Euro[] bank = {
new Euro(99,99),
new Euro(66,66),
new Euro(33,33),
new Euro(11,11),
new Euro(22,22),
new Euro(88,88),
new Euro(55,55),
new Euro(22,22),
new Euro(44,44),
new Euro(0,0)};
System.out.println(" Unsortiertes Feld:");
for (Euro konto : bank) System.out.println(" "+konto);
// Diese Methode sortiert ein Feld in seiner natürlichen Ordnung
// Die ntürliche Ordnung wird durch die Schnittstelle Comparable
// festgelegt
Arrays.sort(bank);
System.out.println(" Sortiertes Feld:");
for (Euro konto : bank) System.out.println(" "+konto);
}

11.1.3 Lösung: Schnittstellen und abstrakte Klassen

Lösung Übung 1: Klasse Dollar

package Kurs2.Schnittstelle;
import static java.lang.Math.signum;
/**
*
* @author sschneid
* @version 1.2
*/
public class Dollar extends Number{
public final long cents;
public Dollar(int dollars, int cents) {
// Ignoriere Centsbetrag wenn er nicht im richtigen Intervall ist
if ((cents<0) || (99<cents)) cents=0;
if (dollars == 0)
this.cents = cents;
else
// Signum ist notwendig da
// -2.20= -2 - 0.2 sind. Falsch: -2 + 0.2 ergibt -1.8!
this.cents = dollars*100+cents*(long)signum(dollars);
}
@Override
public int intValue() {
return (int)cents/100;
}
@Override
public long longValue() {
return cents/100;
}
@Override
public float floatValue() {
return cents/100f;
}
@Override
public double doubleValue() {
return cents/100d;
}
}

Lösung Übung 2: Klasse Euro

package Kurs2.Schnittstelle;
import static java.lang.Math.abs;
import static java.lang.Math.signum;
/**
*
* @author sschneid
* @version 1.2
*/
public class Euro extends Number implements Waehrung, Comparable{
/**
* Der gesamte Betrag wird intern in Vents verwaltet
*/
public final long cents;

public Euro(long euros, long cents) {
// Ignoriere Centsbetrag wenn er nicht im richtigen Intervall ist
if ((cents<0) || (99<cents)) cents=0;
if (euros == 0)
this.cents = cents;
else
// Signum ist notwendig da
// -2.20= -2 - 0.2 sind. Falsch: -2 + 0.2 ergibt -1.8!
this.cents = euros*100+cents*(long)signum(euros);
}
@Override
public int intValue() {
return (int)cents/100;
}
@Override
public long longValue() {
return cents/100;
}
@Override
public float floatValue() {
return cents/100f;
}
@Override
public double doubleValue() {
return cents/100d;
}
@Override
public String symbol() {
return "€";
}
@Override
public String toString() {
// Füge eine Null bei Centbeträgen zwischen 0 und 9 eine
String leerstelle = ((abs(cents)%100)<10) ? "0" : "";
return Long.toString(cents/100L) + "." + leerstelle +
Long.toString(abs(cents%100L)) + symbol();
}
@Override
public Waehrung mult(double d) {
long temp;
temp = (long)(cents *d);
return new Euro(temp/100L,abs(temp%100L));
}
@Override
public int compareTo(Object o) {
int result;
if (o instanceof Euro) {
Euro e = (Euro) o;
result = (int)(this.cents-e.cents);
}
else {result = -1;} // Alles was kein Euro ist, ist kleiner
return result;
}
}

11.1.4 Lernziele (Schnittstellen)

Am Ende dieses Blocks können Sie:

  • den Zusammenhang zwischen "Information Hiding" und Java Schnittstellen erklären
  • die in Java-Schnittstellem vorkommenden Komponenten von Klassen nennen
  • die Unterschiede und Gemeinsamkeiten von Javaklassen und Javaschnittstellen erklären
  • die Syntax einer Javaschnitstelle nennen
  • die UML Notation für Schnittstellen aufzeichnen und anwenden
  • beim Entwurf von Anwendungen Gründe für die Wahl von Vererbung mit Polymorphismus oder die Wahl von Schnittstellen nennen.

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten: Fragen zu Java Schnittstellen (Interfaces)

11.2 Assertions

 

 

Assertions (engl. Versicherung, Zusage) erlauben den Entwicklern für Testzwecke bestimmte Randbedingungen zu prüfen die immer erfüllt sein sollen.

Assertions geben dem Entwickler die Möglichkeit logische Bedingungen für die folgenden Fälle zu Programmieren:

  • Interne Invarianten
  • Invarianten im Kontrollfluß
  • Vorbedingungen, Nachbedingungen, Klasseninvarianten

Durch die Implementierung dieser Invarianten kann der Entwickler die Qualität seiner Implementierung erhöhen, da Bedingungen geprüft werden können die nie verletzt sein sollen.

Der Unterschied zu Ausnahmen (Exceptions) besteht darin, dass Assertions immer gelten sollten und diese daher nicht im Normalfall kontrolliert werden müssen, da sie den Programmablauf nur unnötig verlangsamen würden. Assertions haben eine große Ähnlichkeit mit Ausnahmen sie dienen jedoch unterschiedlichen Zwecken:

Vergleich Assertions und Ausnahmen
  Assertion Ausnahme (Exception)
Einsatzbereich Nur wenn die Logik des Programm in den Augen des Entwicklers verletzt wird Jederzeit da sie Teil der regulären Ablaufsteuerung sind
Auswirkungen bei der normalen Programmausführung Keine. Sie werden nicht geprüft! Können immer Auftreten.
Sichtbarkeit für Endanwender Nie: Wenn das Programm nicht explizit mit entsprechenden Optionen (-ea) gestartet wurde Nur wenn sie nicht abgefangen und behandelt werden
Zielgruppe Helfen dem Entwickler bei der Fehlersuche (auch beim Endanwender)

Zusammenarbeit der Entwickler: Teil der regulären externen Spezifikation von Klassen

Endanwender: Klassifikationschema für Fehlermeldungen bei Programmabbrüchen (Unbehandelte Ausnahmen)

Theoretischer Hintergrund Erlauben das Implementieren von Invarianten oder Vor- und Nachbedingungen von Schleifen und Routinen

Elegantes Konstrukt zum Verlassen von Blöcken zur Behandlung von seltenen Ereignissen.

Implementierter Code wird übersichtlicher da man nicht bei jeder Operation einzeln Sonderfäller prüfen muss

Assertions bieten die folgenden Vorteile für den Entwickler:

  • Der Entwickler kann Annahmen von denen er ausgeht als logische Ausdrücke implementieren und ist nicht auf Kommentare angewiesen.
  • Die Konsistenzprüfungen werden im Normalfall nicht abgearbeitet und produzieren daher keinerlei Laufzeitkosten für den Anwender
  • Sie geben dem Entwickler die Möglichkeit auch nach der Auslieferung seiner Anwendung zusätzliche Informationen durch Einschalten des Assertionchecking zu erhalten.
    • Hintergrund: Bei C und C++ Anwendungen werden bei Kundenproblemen oft spezielle Programme mit extra Debuginformationen ausgeliefert. Dies ist bei Java nicht nötig

Notation

Einfache Variante einer Assertion:

assert Ausdruck1;

Der Ausdruck Audruck1 wird ausgewertet. Hat das Ergebnis den Wert true (wahr) so ist die Zusage wahr und das Programm weiter ausführt. Trifft die Zusage (Assertion) nicht zu wird das Programm mit einem AssertionError abgebrochen (falls es den entspechenden Optionen zum Checken der Assertions aufgerufen wurde).

Eine zweite Syntaxvariante ist:

assert Ausdruck1: Ausdruck2;

Hier fährt das Programm wie im Fall zuvor mit der Ausführung fort wenn Ausdruck1 wahr ist. Ist Ausdruck1 jedoch unwahr wird Ausdruck2 ausgewertet und der entsprechenden Instanz von AssertionError als Parameter mitgegeben und dann in der Fehlermeldung mit ausgegeben. Dieser Rückgabewert unterstützt den Entwickler bei der Analyse des aufgetretenen Fehlerfalls. 

Einschalten der Prüfungen im Laufzeitsystem

Das Prüfen von Assertions kann beim Starten einer Javaanwendung mit den Optionen -ea bzw. -enableassertions eingeschaltet werden oder mit der Option -da bzw. -disableassertions ausgeschaltet werden. Die Option wird vor dem Klassennamen dessen main() Methode gestartet werden soll angegeben:

java -ea Klassenname1
java -ea paketname1... Klassenname1
java -ea paketname1.Klassename2 Klassenname1

java -da Klassenname1
java -da paketname1... Klassenname1 java -da paketname1.Klassename2 Klassenname1java 

 Wichtig: Die Notation mit den drei Punkten  paketname1... ist teil der Aufrufsyntax. Mit ihr werden die Assertions für alle Klassen in einem Paket angeschaltet.

Anwendungsbeispiele

Überprüfen korrekter Wertebereiche

Prüfen des Personenalters bei Rentenberechnungen

assert ((personenAlter>0) && (personenAlter<150)); 
assert (rentenEintrittsAlter>0); 

Die gleichen Assertions in der Variante mit einer Ausgabe für die Konsole

assert ((personenAlter>0) && (personenAlter<150)): personenAlter; 
assert (rentenEintrittsAlter>0): "negatives Renteneintrittsalter "+ rentenEintrittsAlter;  

Das Kontrollieren des Werts eines Monats:

int monat;
...
switch (monat)
{
  case 1: case 2: case 3: System.out.println("Q1");
      break;
  case 4: case 5: case 6: System.out.println("Q2");
      break;
  case 7: case 8: case 9: System.out.println("Q3");
      break;
  case 10: case 11: case 12: System.out.println("Q4");
      break;
  default: assert false;
}

Prüfen eines Kontrollflusses

In einen Programm soll eine der Bedingungen Ausdruck1 oder Ausdruck2 immer erfüllt sein.

void testMethode()
{
   for (int k = 0; k<= 99; k++)
   {
      if (k == 50)
         return;
   }
   assert false;
}

Die Assertion kann Prüfen ob ein Fehlerfall vorliegt.

Geschichtlicher Hintergrund

Assertions wurden in Java durch JSR 42 (A Simple Assertion Facility) in JDK 1.4 eingeführt. Dies führt zu einem gewissen Kompatiblitätsproblem:

  • Quellcode der das Schlüsselwort assert als normalen Bezeichner in JDK 1.3 verwendete wird in JDK 1.4 nicht übersetzen da das Schlüsselwort nicht als Namen akzeptiert wird
  • Quellcode der für JDK 1.4 geschrieben wurde wird nicht unter JDK 1.3 übersetzen, da javac in JDK 1.3 nicht der Syntax von assertions umgehen kann.

11.2.1 Übungen (Assertions)

Übung 1: Einfügen von Assertions

Nutzen Sie das Beispiel aus dem Abschnitt zu Schnittstellen.

UML Diagramm Euro

Modifizieren Sie die Klasse Euro so, daß

  • Beim Setzen des Centbetrags eine Assertion geworfen wird falls der Centbetrag nicht im korrekten Wertebereich ist. Nutzen Sie die erweiterte Syntax um eine vernünftige Fehlermeldung auszugeben.
  • Melden  Sie beim Multiplizieren eines Eurobetrags einen Faktor von Null (0) mit Hilfe einer Assertion. Nutzen Sie die erweiterte Syntax um eine Fehlermeldung auszugeben.

Starten Sie das Programm mit der Klasse TestEuro: Es sollte wie zuvor funktionieren

Starten Sie die Klasse TestEuro so, daß Assertions beachtet werden.

  • Welche Option muß man beim Programmstart einfügen?
  • Modifizieren Sie das Testprogramm so, dass die Assertion für die Multiplikation ausgelöst wird. Der Fall einer Multiplikation mit Null wird im aktuellen Testprogramm nicht getestet.

11.2.2 Lösungen (Assertions)

Übung 1: Einfügen von Assertions

Klasse Euro

package Kurs2.Schnittstelle;
import static java.lang.Math.abs;
import static java.lang.Math.signum;
/**
*
* @author sschneid
* @version 1.1
*/
public class Euro extends Number implements Waehrung, Comparable{
/**
* Der gesamte Betrag wird intern in Cents verwaltet
*/
public final long cents; public Euro(long euros, long cents) { assert ((cents>=0) && (cents < 101)): "Cents Bereichsverletzung";
// Ignoriere Centsbetrag wenn er nicht im richtigen Intervall ist
if ((cents<0) || (cents>=100))
cents=0;
this.cents = (abs(euros)*100+cents) *(long)signum(euros);
}
@Override
public int intValue() {
return (int)cents/100;
}
@Override
public long longValue() {
return cents/100;
}
@Override
public float floatValue() {
// Signum und Absolutwert sind notwendig
// da -2.20= -(2 + 0.2) sind. Falsch: -2 + 0.2 ergibt -1.8!
return (float)cents/100f;
}
@Override
public double doubleValue() {
// Signum und Absolutwert sind notwendig
// da -2.20= -(2 + 0.2) sind. Falsch: -2 + 0.2 ergibt -1.8!
return (double)cents/100d;
}
@Override
public String symbol() {
return "€";
}
@Override
public String toString() {
// Füge eine Null bei Centbeträgen zwischen 0 und 9 eine
String leerstelle = ((abs(cents)%100)<10) ? "0" : "";
return Long.toString(cents/100L) + "." + leerstelle +
Long.toString(abs(cents%100L)) + symbol();
}
@Override
public Waehrung mult(double d) { assert (d!=0): "Multplikation mit " + d + "nicht erlaubt";
long temp;
temp = (long)((double)cents *d);
return new Euro(temp/100L,abs(temp%100L));
}
@Override
public int compareTo(Object o) {
int result;
if (o instanceof Euro) {
Euro e = (Euro) o;
result = (int)(this.cents-e.cents);
}
else {result = -1;} // Alles was kein Euro ist, ist kleiner
return result;
}
}

 

11.3.3 Lernziele (Assertions)

Am Ende dieses Blocks können Sie:

  • ... mit Hilfe von Java-Assertions Invarianten der Anwendung implementieren
  • ... die Auswirkung von Annahmen (Assertions) auf die Wartbarkeit von Anwendungen beschreiben
  • ... Javaannahmen (Assertions) zur Laufzeit gezielt an und ausschalten
  • ... den Einsatz von Assertions (Annahmen) abwägen gegen die Verwendung von Ausnahmen (Exceptions)

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten: Fragen zu Annahmen (Assertions))

11.3 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):






11.3.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 javadoc zu beantworten:

11.4 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

 

11.4.1 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 Fragen zu jar zu beantworten: