11. Weitere Konzepte
11. Weitere Konzepte Stefan Schneider Sat, 11/17/2018 - 12:00- 1419 views
11.1 Schnittstellen (Interfaces)
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: |
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
- fördern modularen Aufbau auf Softwaresystemen
- 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.
- 14470 views
11.1.1 Beispiele Schnittstellenanwendung
11.1.1 Beispiele SchnittstellenanwendungSerialisierung
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
|
Ü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 s1.block11; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * * @author s@scalingbits.com */ 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 Stream der Objekte in einen Filestream leitet ObjectOutputStream out = new ObjectOutputStream(fileOut); // Schreibe ein beliebiges Objekt in diesen Objectstream out.writeObject(o); // Schliesse Stream out.close(); // Schliesse Datenstrom. 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 s1.block11; import java.io.Serializable; /** * * @author s@scalingbits.com */ public class Adresse implements Serializable { String stadt; public Adresse (String s) { stadt =s; } }
Klasse Person
package s1.block11; import java.io.Serializable; /** * * @author s@scalingbits.com */ 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
- 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?
- Was geschieht beim Übersetzen und Ausführen wenn man den Term "implements Serializable" in der Klasse Adresse oder Person weglässt?
- 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?
- Was muss man tun wenn man ein Attribut (zBsp. stadt) nicht speichern möchte? Suchen Sie in der Java-Spezifikation oder mit einer Suchmaschine...
- 4659 views
11.1.2 Übung: Schnittstellen und abstrakte Klassen
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 s1.block11;
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 s1.block11;/**
*
* @author s@scalingbits.com
* @ 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 s1.block11; import java.util.Arrays; /** * * @author s@scalingbits.com * @ 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);
}
}
- 11768 views
11.1.3 Lösung: Schnittstellen und abstrakte Klassen
11.1.3 Lösung: Schnittstellen und abstrakte KlassenLösung Übung 1: Klasse Dollar
package s1.block11; import static java.lang.Math.signum; /**
*
* @author s@scalingbits.com
* @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 s1.block11;
import static java.lang.Math.abs;
import static java.lang.Math.signum;
/**
*
* @author s@scalingbits.com
* @version 1.2
*/
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) {
// 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;
}
}
- 7265 views
11.1.4 Lernziele (Schnittstellen)
11.1.4 Lernziele (Schnittstellen)
Am Ende dieses Blocks können Sie:
|
Lernzielkontrolle
Sie sind in der Lage die folgenden Fragen zu beantworten: Fragen zu Java Schnittstellen (Interfaces)
- 3551 views
11.2 Assertions
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:
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.
- 6334 views
11.2.1 Übungen (Assertions)
11.2.1 Übungen (Assertions)Übung 1: Einfügen von Assertions
Nutzen Sie das Beispiel aus dem Abschnitt zu Schnittstellen.
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.
- 3104 views
11.2.2 Lösungen (Assertions)
11.2.2 Lösungen (Assertions)Übung 1: Einfügen von Assertions
Klasse Euro
package s1.block11; import static java.lang.Math.abs; import static java.lang.Math.signum; /** * * @author s@scalingbits.com * @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; } }
- 3034 views
11.3.3 Lernziele (Assertions)
11.3.3 Lernziele (Assertions)
Am Ende dieses Blocks können Sie:
|
Lernzielkontrolle
Sie sind in der Lage die folgenden Fragen zu beantworten: Fragen zu Annahmen (Assertions))
- 3175 views
11.3 Dokumentieren von Javaprogrammen (javadoc)
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:
@see
@since
@deprecated
@param
@return
@throws
und@exception
@serialData
{@link}
{@linkplain}
{@inheritDoc}
{@docRoot}
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 Nettopreis * @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):
- 58121 views
11.3.1 Lernziele
11.3.1 Lernziele
Am Ende dieses Blocks können Sie:
|
Lernzielkontrolle
Sie sind in der Lage die folgenden Fragen zu javadoc zu beantworten:
- Dokumentationskommentare und Datenkapselung (enthält auch Teilfragen anderer Bereiche)
- 13197 views
11.4 Packen mit jar
11.4 Packen mit jarEinfü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.
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.class, Main.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
- Oracle Tutorial: "Packaging Programs in jar Files" (englisch)
- Oracle Tutorial: "Signing JAR Files" (englisch)
- jar Referenzdokumentation für Windows
- 9909 views
11.4.1 Lernziele
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:
|
Lernzielkontrolle
Sie sind in der Lage die Fragen zu jar zu beantworten:
- 3335 views