(1) Vererbung (Inheritance) Reale Welt: Dinge kommen in verschiedenen Varianten vor, die sich hierarchisch klassifizieren lassen Kennzeichen: Dinge die hierarchisch tiefer stehen, sind speziellere Varianten der übergeordneten Dinge (Spezialisierungshierarchie) ⇒ besitzen deren Eigenschaften + noch weitere spezifische Eigenschaften Vererbung: Klassen können Methoden + Attribute anderer Klassen übernehmen, aber auch noch zusätzliche Methoden + Attribute enthalten Eine Klasse erbt von einer übergeordneten Klasse + enthält weitere Methoden + Attribute Tier Reptil Säugetier …….. ……. © H.Neuendorf ……. Nagetier .... ..... .... ..... ....... Huftier Pferd ...... ........ Spezielisierung Fisch Verallgemeinerung Vererbungshierarchie Relation : "Ist ein ..." Ein Nagetier ist ein Säugetier Ein Säugetier ist ein Tier Wichtig : Nur wenn eine durchgängige "Ist-Ein"Relation besteht, macht die Vererbungshierarchie Sinn! Vererbung (2) Kann sich über viele Ebenen erstrecken ⇒ Hierarchie Angestellter TarifAngestellter Unterklassen Arbeiter Oberklassen Mitarbeiter AußertarifAngestellter Darstellung im Klassendiagramm (UML) : Pfeilrichtung → von der speziellen zur allgemeinen Klasse, von der Geschäftsführer Unterklasse zur Oberklasse Java : Eine Unterklasse kann nur eine direkte Oberklasse haben = Einfachvererbung (keine Mehrfachvererbung in Java !) Syntax : Schlüsselwort extends in Klassendeklaration © H.Neuendorf Semantisch sinnvoll nur als Ist-Ein Beziehung! Alles andere führt zur Verwirrung !! Vererbung : Java → Schlüsselwort extends UML : Klassenname Methoden() Attribute (3) Person getName() name Franzose getGruss() getGruss() Aufbau Hierarchie : Bayer getGruss() setLieblingsbier() lieblingsbier Obere Klassen sollen Methoden + Attribute bereitstellen / enthalten, die auch in unteren Klassen "Sinn machen", dort verwendbar sind ( "Jeder hat einen Namen ....." ) Untere Klassen können Inhalt der oberen Klassen spezifisch erweitern durch zusätzliche eigene Methoden + Attribute, die nur für sie sinnvoll sind / eine besondere Ausprägung annehmen müssen. ( "Der Gruß lautet in jeder Sprache anders ...." ) Unterklasse (subtype) ist spezielle Abart der Oberklasse (supertype) : AttributeOber ⊆ AttributeUnter MethodenOber ⊆ MethodenUnter © H.Neuendorf Spezielisierungshirarchie Deutscher class Deutscher extends Person { .......................... } Vererbung - Schlüsselwort extends : In Unterklassen dadurch alle public Methoden + Attribute der Oberklasse verfügbar / ansprechbar ⇓ Objekte der Klassen Franzose und Deutscher haben geerbte public Methode getName( ) und geerbtes public Attribut name Oberklasse Person liefert public Elemente, die auch in Unterklassen direkt verwendbar sind ! Erweiterung in Unterklassen : Spezifische Methode zum Grüßen: getGruss( ) ; © H.Neuendorf (4) class Person { public Person( ) { name = "Anonym" ; } public String getName( ) { return name ; } public String name ; } class Franzose extends Person { public Franzose( String nn ) { name = nn ; } public String getGruss( ) { return "Bonjour " + getName() ; } } class Deutscher extends Person { public Deutscher( String nn ) { name = nn ; } public String getGruss( ) { return "Mahlzeit " + getName() ; } } (5) Vererbung class Person { public Person( ) { name = "Anonym" ; } public String getName( ) { return name ; } public String name; public int alter ; } class Test { public static void main( String[] args ) { Vorteil : Konzentration von Code in einer Oberklasse, kein Duplizieren von Codestrecken. Zentrales Testen, Fehlersuchen, Warten ... Franzose f = new Franzose( "Jean" ) ; IO.writeln( f.getGruss( ) ) ; IO.writeln( f.getName( ) ) ; f.alter = 25 ; class Franzose extends Person { public Franzose( String nn ) { name = nn ; } public String getGruss() { String s = "Bonjour " + getName( ) ; return s ; } } IO.writeln( f.name + " : " + f.alter ) ; } } Public Attribute + Methoden von "Person" stehen in der Klasse "Franzose" und für Objekten vom Typ "Franzose" zur Verfügung ⇒ direkt aufrufbar / ansprechbar ! Speziell: Auch statische public Attribute und Methoden werden vererbt und © H.Neuendorf stehen in der Unterklasse als statische Elemente zur Verfügung Vererbung In Unterklassen alle public Methoden + Attribute der Oberklasse verfügbar / direkt ansprechbar Aber : Auf private Attribute + Methoden der Oberklasse hat auch Unterklasse keinen Zugriff ! Ebensowenig wie alle anderen Klassen ! Kein Unterlaufen der Kapselung durch Vererbung !! Weitere Zugriffsspezifikation : protected © H.Neuendorf (anders als in C++ !! ) class Person { public Person( ) { name = "Anonym" ; } public String getName( ) { return name ; } public String name; public int alter ; private String ort; } class Franzose extends Person { public Franzose( String nn ) { name = nn ; } public String getGruss( ) { return "Bonjour" + getName( ) ; // Nicht erlaubt : // return "Wohnhaft: " + ort ; } public int getAlter( ) { alter = 25 ; return alter ; // ok! } } (6) (7) Vererbung In Unterklassen kann durchaus ein indirekter Zugriff auf private Elemente der Oberklasse möglich sein : Via öffentlicher Methoden der Oberklasse ! ⇓ Mit den Unterklassenobjekten werden auch entsprechende Oberklassenobjekte mit ihren Daten im Speicher gehalten ! ⇓ Werden automatisch zusammen mit Unterklassenobjekten erzeugt ⇓ Objekterzeugung in Klassenhierachien muss noch gesondert untersucht werden ….. Anm: Öffentliche statische Attribute der Oberklasse (zB counter) sind durch alle Unterklassen + ihre Objekte manipulierbar – dabei wird für alle ein einheitlicher Wert vorgehalten. © H.Neuendorf class Person { public Person( ) { // ……………. } public String getName( ) { return name ; } public void setName( String n ) { name = n ; } private String name = "Anonym"; } class Franzose extends Person { public Franzose( String nn ) { setName( "Müller" ) ; } public String getGruss( ) { return "Bonjour" + getName( ) ; } // ………………….. } (8) Überschreiben von Methoden Spezialisieren geerbter Methoden in Unterklasse : Klasse Bayer erbt von Person + von Deutscher Bayer hat eigene Methode getGruss(), obwohl von Deutscher solche Methode geerbt wurde ⇒ Für Bayer-Objekte wird bei getGruss()-Aufruf die eigene getGruss()Methode aufgerufen - nicht geerbte getGruss()-Methode von Deutscher !! Überschreiben (Overriding) : Deklaration einer Methode in Unterklasse mit gleicher Schnittstelle / Signatur (Name, Parameter, Rückgabewerte) wie in Oberklasse, jedoch anderer Implementierung Regeln : 1. Kein Einschränken der Sichtbarkeit beim Überschreiben, dh public nicht durch protected oder private überschreibbar ! 2. Nicht-statische Methode nur durch nicht-statische Methode, statische Methode nur durch statische Methode überschreibbar! Überschreiben: ≈ "Verdecken" der geerbten Methoden Überladen: ≈ "Nebeneinander" von gleichnamigen Methoden mit unterschiedlichen Parameterlisten © H.Neuendorf class Deutscher extends Person { public Deutscher( String nn ) { name = nn ; } public String getGruss( ) { return "Mahlzeit"; } } class Bayer extends Deutscher { public Bayer( String nn ) { name = nn ; } public String getGruss( ) { return "Grüß Gott!" ; } // ............................ } Sinn : Flexibles Anpassen geerbter Methoden an spezifische Bedürfnisse der Unterklassen, Einbau zusätzlicher Prüfungen, Verwenden anderer interner Datenformate …… (10) Überschreiben von Methoden – Einhalten Regeln class Deutscher extends Person { Spezielle Anmerkung : public Deutscher( String nn ) { name = nn ; } Modifikatoren synchronized, native und strictfp sind Teil der Implementierung - nicht des Vertrags. public String getGruss( ) { return "Mahlzeit"; } Ebenso Kennzeichnung von Methodenparametern als final. } Folglich können sie alle beim Überschreiben uneingeschränkt verändert werden … class Bayer extends Deutscher { public Bayer( String nn ) { name = nn ; } @Override public String getGruss( ) { return "Servus!" ; } // OK ! // .................................. private String getGruss( ) { return "Servus" ; } // Fehler – Einschränken Zugriffsrechte ! public int getGruss( ) { return 11111 ; } // Fehler – Ändern return-Typ ! public String getGruss( int n ) { return "Servus!" + n ; } // OK – aber kein Überschreiben sondern Überladen // Geerbte Methode getGruss() wurde überladen Geerbte throws-Exception-Klauseln dürfen nicht durch zusätzliche Exceptions ergänzt werden – nur durch speziellere Variante der Oberklassen-Exception ersetzt werden Sinn : Einhalten Vertrag der Oberklasse // Klasse Bayer hat nun beide Varianten // ⇓ // Kombination von Überladen und Geerbten : public String getGruss( int n ) { return getGruss() + n ; } } © H.Neuendorf Annotation @Override bewirkt CompilerPrüfung, ob wirklich überschrieben oder (unabsichtlich) überladen wird ! @Override public String getGruss( int n ) { /* … */ } Gibt Compilerfehler ! (11) Super-Aufrufe für nicht-statische Methoden Schlüsselwort super.Methodenname(....) Aufruf einer geerbten Methode der direkten Oberklasse in der Unterklasse Anwendung : Unterklasse überschreibt geerbte Methode der Oberklasse. Dennoch soll auch explizit die geerbte Methode der Oberklasse gerufen werden. ⇓ Auch nach Überschreiben geerbter Methoden können diese geerbten Methoden der Oberklasse weiterhin in Unterklasse genutzt werden In statischen Methoden steht super. nicht zur Verfügung ⇒ Überschriebene statische Methoden + Attribute können in Unterklassen nur aus nicht-statischen Methoden gerufen werden © H.Neuendorf class Person { public Person( ) { name = "Anonym" ; } public String getName( ) { return name ; } public String name ; } class Franzose extends Person { public Franzose( String nn ) { name = nn ; } public String getName( ) { String n = super.getName( ) ; n = "Name ist: " + n ; return n ; } ........................................... } (12) Begriffe beim Vererbungsmechanismus Oberklasse Klassenhierachie in Projekt wird dargestellt durch hier Basisklasse Strg + T Attribute Methoden Unterklasse Unterklasse Unterklasse Unterklasse Abgeleitete Klasse Abgeleitete Klasse Abgeleitete Klasse Abgeleitete Klasse Neue oder überschriebene Attribute und Methoden Neue oder überschriebene Attribute und Methoden Neue oder überschriebene Attribute und Methoden Neue oder überschriebene Attribute und Methoden Einfachvererbung Mehrfachvererbung Unterklasse Unterklasse Abgeleitete Klasse Abgeleitete Klasse Neue oder überschriebene Attribute und Methoden Neue oder überschriebene Attribute und Methoden © H.Neuendorf (13) Methodenauswahl durch JVM class GrussAusgabe { Finden der richtigen Person auszuführenden Methode für Objekte in getName() Vererbungshierarchie : public static void main( String[] args ) { Bayer sepp = new Bayer( "Sepp" ) ; name IO.writeln( sepp.getName( ) ) ; IO.writeln( sepp.getGruss( ) ) ; Deutscher getGruss() } } Bayer getGruss() getGruss() setLieblingsbier() getName() lieblingsbier Unterklassenobjekte führen Methoden aus, die in verschiedenen Ebenen der Vererbungshierarchie angesiedelt sind ! Such-Prinzip : getName( ): aus Klasse Person Methode in Klasse des beauftragten Objekts vorhanden ? getGruss( ): aus Klasse des Obj. Ja ⇒ Nein ⇒ © H.Neuendorf dort deklarierte Methode ausführen ! Suche in nächsthöherer Klasse der Hierarchie fortsetzen ! Überschreiben von Attributen (14) (seltener) Auch gleichnamige geerbte Attribute können überschrieben werden : Primitive Typen ebenso wie Objekttypen Nicht-statische ebenso wie statische Attribute Deklaration gleichnamiger Attribute in Unterklasse "verdeckt" Deklaration der geerbten Attribute der Oberklasse. class Ober { public Ober ( ) { testvar = true ; } public boolean testvar ; } class Unter extends Ober { ⇓ public Unter ( long a ) { testvar = a ; super.testvar = false ; } Bei Wertzuweisung wird das eigene Attribut der Unterklasse belegt. Auf gleichnamige geerbte Attribute der Oberklasse kann jedoch weiterhin mittels : // Eigene Variable "testvar" // verdeckt die von Oberklasse geerbte Variable super.Variablenname zugegriffen werden. Beim Attribut-Überschreiben darf Sichtbarkeit eingeschränkt werden ! Typ kann sich ändern ! © H.Neuendorf private long testvar ; } (15) Vorgang beim Erzeugen von Unterklassen-Objekten Mit Unterklassen-Objekt werden auch automatisch alle zugehörigen Oberklassen-Objekte im Speicher angelegt, um geerbte Methoden + Attribute zugreifbar zu machen RAM RAM private-Anteile public-Anteile Klasse Person Klasse Franzose Objekterzeugung Unterklassenobjekt Franzose f = new Franzose( "Jean" ) ; Klasse Person Klasse Franzose Automatisch erzeugtes Personen Objekt Objekt f Franzosen-Objekt f hat Zugriff auf die öffentlichen Attribute und Methoden der Klasse Person. Kann mit diesen arbeiten, als hätte es sie selbst in seiner Klasse implementiert ............ © H.Neuendorf Kann geerbte public Attribute direkt mit Werten füllen – aber auch private Attribute indirekt mittels geerbter public set-/getMethoden manipulieren Konstruktoren in Klassenhierarchien Aufruf Oberklassenkonstruktor im Konstruktor der Unterklasse möglich – oft sogar erforderlich : Anweisung super( Parameterliste ) ; Regeln : 1. Bei Anlegen Unterklassen-Objekt wird immer auch Konstruktor der Oberklasse aufgerufen, da auch Oberklassen-Objekt erzeugt wird – nur Oberklasse weiß, wie ihr Zustand korrekt initialisiert wird 2. Konstruktoren werden nicht vererbt ⇒ Unterklassen benötigen eigene Konstruktoren 3. super( ) -Aufruf muss erste Anweisung im Konstruktor der Unterklasse sein, wenn explizit verwendet ! 4. Compiler setzt automatisch parameterlosen super()-Aufruf ein, wenn kein expliziter super()-Aufruf im Coding der Unterklasse enthalten ist ! ⇒ Aufruf parameterloser Oberklassen-Konstruktor ! 5. Exceptions aus Oberklassen-Konstruktor können in Unterklassen-Konstruktor nicht abgefangen werden ! 6. Oberklassen-Konstruktoren sollten keine überschreibbaren Methoden aufrufen - andernfalls dokumentieren ! © H.Neuendorf class Person { public Person( String nn ) { name = nn ; } public String getName( ) { return name ; } private String name ; } (16) class Franzose extends Person { public Franzose( String nn ) { super( nn ) ; } public String getGruss( ) { return "Bonjour" ; } } Analog : this(...) ; Auch this( ) muss erste Anweisung sein ⇒ kann nicht mit super( ) auftreten! (17) Konstruktoren in Klassenhierarchien Wenn expliziter super(...)-Aufruf in Unterklasse unterbleibt wird automatisch parameterloser Oberklassen-Konstruktor gerufen. Fälle : a) Oberklasse hat gar keinen Konstruktor : class Ober { public Ober ( int val ) { value = val ; } private int value ; } class Unter extends Ober { Parameterloser Standardkonstruktor der Oberklasse ist da + wird gerufen – ok ! public Unter ( int a , int b ) { super( a ) ; // !!!!!! number = b ; } private int number ; b) Oberklasse hat einen parametrisierten Konstruktor : Es muss parametrisierter Oberklassen-Konstruktor mit super(….) gerufen werden !! } Wenn parametrisierter Konstruktor vorhanden, dann gibt es keinen parameterlosen Standardkonstruktor ! Anmerkung : super(....) = Aufruf Oberklassen-Konstruktor super.Methode(...) = Aufruf ursprünglicher Oberklassenmethode © H.Neuendorf super. this. super(…) this(…) Beispiel Überschreibmechanismus → class Tank { private int stand ; private int kapazitaet ; public Tank( int kap ) { Spezialisierung (19) class SicherheitsTank extends Tank { public SicherheitsTank( int kap ) { super( kap ) ; } kapazitaet = kap ; public int fuelle( int menge ) { if( getStand() + menge > getKapazitaet() ) { return -1 ; // Exception werfen !! } else { return super.fuelle( menge ) ; } } } public int fuelle( int menge ) { stand = stand + menge; return stand ; } public int getStand( ) { return stand ; } } public int getKapazitaet( ) { return kapazitaet ; } } Überschreiben geerbter Methoden zur Einführung zusätzlicher semantischer oder technischer Checks, Konsistenzprüfungen in Unterklasse Vererbung nicht nur zur Wiederverwendung sondern auch zur Anpassung © H.Neuendorf Beispiel Überschreibmechanismus → class Oben { Inversion of Control (20) class Unten extends Oben { Möglichst nicht bei Konstruktoren verwenden ! public void teil1( ) { public void teil1( ) { IO.writeln( "oben 1" ) ; IO.writeln( "unten 1" ) ; } } public void teil2( ) { public void teil2( ) { IO.writeln( "oben 2" ) ; IO.writeln( "unten 2" ) ; } } public void tusOben( ) { // ………. teil1( ); teil2( ) ; } Klassen, die nicht als Oberklassen geeignet sind, sollten keine Aufrufe öffentlicher = überschreibbarer Methoden in ihrem eigenen Coding enthalten ! Statt dessen sollten in den öffentlichen Methoden dann nur private = nicht überschreibbare eigene Methoden aufgerufen werden ⇒ Keine Verhaltensänderungen in der Klasse durch Überschreiben ihrer öffentlicher Methoden } class Test { public static void main( String[] args ) { } Unten myU = new Unten( ) ; Grundsätzliche Abfolge der Operationen von tusOben() wird beibehalten, aber Details der Bearbeitung werden von Unterklasse angepasst. Grundidee des Template-Patterns. Dort jedoch sauberer umgesetzt (s.u.) myU.tusOben( ) ; } Ausgabe : unten 1 unten 2 } Unterklasse überschreibt geerbte Methoden, die in anderer Methode der Oberklasse verwendet werden. Wird diese geerbte Methode in Unterklasse aufgerufen, so werden darin die überschriebenen Methoden-Varianten der Unterklasse verwendet ! © H.Neuendorf (21) Polymorphie Person Vielgestaltigkeit getName() name Deutscher getGruss() Gleichnamige Methodenaufrufe für Objekte verschiedener Klassen einer Vererbungshierarchie liefern verschiedenes Verhalten Bsp : Aufruf der Methode getGruss( ) liefert je nach Objekttyp unterschiedliches Verhalten ! JVM stellt bei Methodenaufruf fest zu welchem Objekttyp die Methode gehört und führt dessen Coding aus. Bayer getGruss() getGruss() setLieblingsbier() getName() lieblingsbier Fundamentale Typkompatbilität : Semantisch : Ausnutzen der Ist-ein Beziehung in Vererbungshierarchie Jedes Unterklassenobjekt ist auch ein Vertreter der Oberklasse ⇒ kann diese vertreten Technisch : Objekt der Unterklasse ist typkompatibel mit Objekt der Oberklasse Person p ; p=b; © H.Neuendorf Bayer b = new Bayer( "Sepp" ) ; // zulässige Zuweisung ! → Upcast Nutzen Vererbung : Typkompatibilität zwischen Ober- und Unterklasse a) Übersichtlichkeit + "Schreibersparnis" : (22) Wiederverwendung von geerbtem Code b) Kompatibilität der Unterklasse zur Oberklasse : Ist-Beziehung, Teilmengen-Relation Jedes Programm, das in der Lage ist, mit Objekten der Oberklasse zu arbeiten, kann automatisch auch mit Objekten aller Unterklassen arbeiten ! Konsequenz : Programme "laufen" prinzipiell auch mit Objekten der spezialisierten Unterklassen, ohne angepasst werden zu müssen ! Man kann mit allgemeiner gehaltenen Klassen beginnen - und diese später zur Abdeckung spezieller Bedürfnisse via Vererbung verfeinern !! Person Deutscher Bayer "ist ein"-Beziehung : Franzose is_a is_kind_of Mengendiagramm © H.Neuendorf (23) Probleme Vererbung : 1. Unterklassen an Oberklassen gekoppelt : Bloße Erweiterung der Oberklasse ist meist unproblematisch : Hinzufügen weiterer Attribute + Methoden Vorsicht beim Weiterentwickeln der Oberklasse ! Unterklasse erbt Schnittstelle + Implementierung der Oberklasse ⇒ Invalidierungsgefahr : Jede Änderung von public + protected-Elementen der Oberklasse wirkt sich auf Unterklassen aus ! Einseitig : Nur Unterklasse kennt ihre Oberklassen, ist auf diese angewiesen, nicht umgekehrt ⇒ Oberklassen-Entwickler merkt nicht sofort, wenn invalidierende Änderung erfolgt ! Bsp : Oberklasse wird neue public-Methode hinzugefügt, die sich nur im return-Typ von Methode unterscheidet, die bereits in Unterklasse existiert ……. 2. Unterklasse erbt von Oberklasse auch deren Fehler + Schwächen : Fehler + Schwächen der öffentlichen Schnittstelle der Oberklasse wandern auch in die Unterklassen - und werden zu Fehlern + Schwächen der öffentlichen Schnittstelle der Unterklassen ! Dagegen lässt sich bei Assoziation (Attribute vom Typ der Oberklasse) eine Schnittstelle entwerfen, hinter der diese Fehler und Schwächen verborgen werden können ….. ⇒ Eine Klasse als potentielle Oberklasse zu entwerfen zwingt zu sehr sauberem Design ! ⇒ Wenn Klasse nicht dafür geeignet, dann Vererbung von ihr verhindern © H.Neuendorf = finale Klasse, s.u. (24) Typkompatibilität class GrussAusgabe2 { Typumwandlung : Java achtet streng auf Typkompatibilität ! double x = 3.3 ; int y = x ; public static void main( String[] args) { // Fehler! Franzose f = new Franzose( "Jean" ) ; Person p1= f ; // Upcast ! Zuweisungen erlaubt, wenn Daten den Anforderungen des neuen Typs genügen. Dann führt Java automatische, implizite Typumwandlung aus : int x = 15 ; double y = x ; Person p2= new Bayer( "Sepp" ) ; // erlaubt! Bayer b = (Bayer) p2 ; // Downcast ! b.setLieblingsbier( "Paulaner" ) ; Bei Objektreferenzen ebenso ! Automatische Typumwandlung bei Zuweisung an Objektvariable aus höherer Stufe der Klassenhierarchie = UPCAST : Person p1 = new Franzose( "Jean" ) ; Explizite Typumwandlung erforderlich bei Zuweisung an Objektvariable tieferer Stufe der Klassenhierarchie = DOWNCAST : Bayer b = (Bayer) p2 ; © H.Neuendorf } } Upcast ist immer unproblematisch : Ein Franzose ist immer auch eine Person ! Vom Compiler ohne expliziten Cast akzeptiert Downcast problematisch, fehleranfällig : Eine Person ist nicht immer ein Bayer ! Vom Compiler nur durch expliziten Cast akzeptiert ! Typkompatibilität: Upcast (safe cast) (25) Bayer Person name lieblingsbier getName( ) setLieblingsbier( ) getGruss( ) Bayer b = new Bayer( "Sepp" ) ; Person p = b ; // Upcast Compiler verlangt : Person-Objekt Compiler bekommt : Bayer-Objekt Korrekt ! 9 ⇒ Compiler bekommt "mehr", als er "verlangt" hat - aber jedenfalls "nicht zuwenig" ! ⇒ Compiler nimmt, was er benötigt + "ignoriert" / "verbirgt" das "Überflüssige" ????? ⇒ Macht aus Bayer-Objekt eine Person, "reduziert" Bayer-Objekt auf Person-Objekt Genug vorhanden, um Person-Objekt korrekt zu instanziieren Durch "Weglassen" kann man aus Bayern eine bloße Person machen ....... ⇒ Zuweisung ist korrekt und passiert die Typprüfung ! © H.Neuendorf (26) Typkompatibilität: Downcast (unsafe cast) Bayer Ausdruck markieren Person Strg + 1 name lieblingsbier getName( ) setLieblingsbier( ) Korrekturvorschlag getGruss( ) Person p = new Person( "Paula" ) ; Bayer b = (Bayer) p ; // Downcast Fehler ! ≠ Upcast: unkritisch, direkte Zuweisung Compiler verlangt: Bayer-Objekt Compiler bekommt: Person-Objekt Downcast: verbotene Operation ⇒ Compiler bekommt "weniger", als er "verlangt" hat – wird durch cast gezwungen ! ⇒ Das zugewiesene Person-Objekt enthält "zu wenig", um Bayer-Objekt zu "füllen" ⇒ Nicht "genug" im Person-Objekt, um Bayer-Referenz korrekt zu instanziieren Aus bloßer Person läßt sich kein Bayer machen - es "fehlt" etwas! ... ⇒ Zuweisung nicht korrekt - ClassCastException zur Laufzeit ! © H.Neuendorf (27) Typkompatibilität: Downcast Bayer Person name lieblingsbier getName( ) setLieblingsbier( ) getGruss( ) Bayer b1 = new Bayer( "Sepp" ) ; Person p = b1 ; // Upcast Hier : Korrekt ! 9 Bayer b2 = (Bayer) p ; // Downcast Compiler verlangt : Bayer-Objekt Compiler bekommt : Personen-Referenz - die zuvor mit Bayer-Objekt entstand ⇒ Kann aus dieser speziellen Personen-Referenz wieder Bayer-Objekt "hervorholen" ⇒ Genug "Inhalt" hinter dieser speziellen Person-Referenz, um Bayer zu instanziieren ⇒ Zuweisung korrekt - Typkonform zur Laufzeit ! Upcast: unkritisch, direkte Zuweisung Downcast: kritisch, explizite cast-Operation © H.Neuendorf (28) Typumwandlungen Casts UPCAST : Person p2 = new Bayer( "Sepp" ) ; class GrussAusgabe2 { Auf Objektreferenz p2 dürfen nur Methoden + Attribute angesprochen werden, die schon in Klasse Person bekannt sind ! public static void main( String[] args) { Werden Methoden in Klasse Bayer überschrieben, dann wird deren Version gerufen ! Person p1 = new Franzose( "Jean" ) ; Person p2 = new Bayer( "Sepp" ) ; Denn : p2 ist Referenz vom Typ Person, nicht Referenz vom Typ Bayer ! IO.writeln( p2.getName( ) ) ; // ok! p2.setLieblingsbier( "Paulaner" ) ; // Fehler!! Bayer-spezifische Methoden + Attribute sind nicht zugänglich ! ( (Bayer) p2 ).setLieblingsbier( "Paulaner" ) ; Bayer b1 = (Bayer) p2 ; // Downcast! b1.setLieblingsbier( "Erdinger" ) ; // ok! } DOWNCAST : (Bayer) p2 oder ..... Bayer b = (Bayer) p2 ; Cast von p2 zu Typ Bayer ⇒ Somit nun Methoden + Attribute zugreifbar, die spezifisch für Klasse Bayer sind, dh in Oberklasse Person nicht existieren. © H.Neuendorf } (29) Zuweisungen + Typumwandlungen Upcast : unkritisch, direkte Zuweisung class Zuweisung { Downcast : per expliziter cast-Operation Bayer b = (Bayer) p2 ; public static void main( String[] args) { // korrekt ?? Franzose f1 = new Franzose( "Jean" ) ; Bayer b1 = new Bayer( "Sepp" ) ; Aber : Wenn p2 nicht auf ein Bayer-Objekt zeigt, dann wird Laufzeitfehler ausgelöst ! Person p1, p2 ; Laufzeit-Typprüfung mit Operator : p1 = f1 ; p2 = b1 ; p2 instanceof Bayer ⇓ if ( p2 instanceof Bayer ) { // Downcast möglich - ok! Bayer b = (Bayer) p2 ; b.setLieblingsbier( "Paulaner" ) ; } else // ……. Prüfung, ob p2 zur Laufzeit ein Objekt vom Typ Bayer referenziert Anm: Ein Programm ist typsicher, wenn schon zur Compilezeit feststeht, dass kein Aufruf an ein Objekt erfolgt, für das das Objekt keine entsprechende Methode besitzt. Eine Programmiersprache ist typsicher, wenn alle erzeugbaren Programme typsicher sind – so dass keine LZ-Fehler durch falsche Datentyp-Operationen möglich sind – es sei denn, dies wird durch Casts erzwungen. © H.Neuendorf // Upcast - ok! // Upcast - ok! } } (30) Zuweisungen + Typumwandlungen class Zuweisung { Generell möglich : public static void main( String[] args) { Verwendung von Unterklassen-Referenzen anstelle von Oberklassen-Referenzen : Bayer b1 = new Bayer( "Sepp" ) ; ausgabe1( b1 ) ; // Korrekt !!!! a) bei Zuweisungen (s.o.) b) bei Parameterübergaben : Person p1 = new Person( "N.N." ) ; ausgabe2( p1 ) ; // Fehler! Wenn Methode als Parameter eine Referenz vom Typ Person erwartet, .............. ............ dann kann auch eine Referenz vom Typ Deutscher, Franzose, Bayer, d.h. eine Referenz auf Unterklassen-Objekte übergeben werden ! } public static void ausgabe1( Person p ) { IO.writeln( "Hallo" + p.getName( ) ) ; } Unterklassen-Objekte / -Referenzen sind typkompatibel zu Oberklassen-Objekten / -Referenzen !! public static void ausgabe2( Bayer b ) { IO.writeln( "Hallo" + b.getName( ) ) ; } } © H.Neuendorf (31) Polymorphie Verwendung : Methoden schreiben, die mit Objekten der Oberklasse arbeiten, zB : void ausgabe( Person p ) { IO.writeln( p.getGruss() ) ; } + Methodenaufrufe mit Objektreferenzen typkompatibler Unterklassen Beispiel : Klasse Person enthält nun Methode getGruss() . Diese wird in jeder der Unterklassen spezifisch überschrieben. © H.Neuendorf class Person { public Person( String nn) { name = nn ; } public String getName( ) { return name ; } public String getGruss( ) { return "Hallo" ; } private String name ; } class Franzose extends Person { public Franzose( String nn ) { super( nn ) ; } public String getGruss( ) { return "Bonjour" ; } } class Deutscher extends Person { public Deutscher( String nn ) { super( nn ) ; } public String getGruss( ) { return "Mahlzeit" ; } } class Bayer extends Deutscher { public Bayer( String nn ) { super( nn ) ; } public String getGruss( ) { return "Grüß Gott" ; } } (32) Polymorphie : Klasse Person enthält Methode getGruss( ) - in Unterklassen überschrieben. class GrussAusgabe2 { public static void main( String[] args ) { Dadurch kann polymorph auf Objekten der Unterklassen Methode getGruss( ) aufgerufen werden : Franzose f = new Franzose( "Jean" ) ; Deutscher d = new Deutscher( "Hans" ) ; Bayer b = new Bayer("Sepp" ); Methode ausgabe() ist für Objekte vom Typ Person definiert. Da deren Unterklassen typkompatibel sind, kann diese Methode auch mit allen Unterklassenreferenzen aufgerufen werden ! } JVM führt dabei Methode getGruss( ) der übergebenen Objekte aus ⇒ public static void ausgabe( Person p ) { IO.writeln( p.getGruss( ) ) ; } Es wird die spezifische getGruss()Methode der übergebenen Unterklassenobjekte ausgeführt ! © H.Neuendorf // polymorphe Aufrufe: ausgabe( f ) ; ausgabe( d ) ; ausgabe( b ) ; } Voraussetzung : Klasse Person muss auch eine Methode getGruss( ) besitzen (33) Polymorphie class GrussAusgabe2 { Typkompatible Unterklassenobjekte instanziiert public static void main( String[] args) { Franzose f = new Franzose( "Jean" ) ; Deutscher d = new Deutscher( "Hans" ) ; Bayer b = new Bayer( "Sepp" ) ; Ausführung erfolgt auf Unterklassenobjekten : // polymorphe Aufrufe: ausgabe( f ) ; ausgabe( d ) ; ausgabe( b ) ; zur Laufzeit Deren klassenspezifische Methode getGruss( ) wird ausgeführt ! Methodenaufruf ist für Objekte vom Typ Person formuliert Compilezeit © H.Neuendorf } public static void ausgabe( Person p ) { IO.writeln( p.getGruss( ) ) ; } } (34) Was bedeutet Polymorphie : Man schreibt Methoden mit Oberklassenreferenzen als Parameter - und kann diese mit Unterklassen-Objektreferenzen aufrufen. class GrussAusgabe2 { public static void main( String[] args) { Resultat : Franzose f = new Franzose( "Jean" ) ; Deutscher d = new Deutscher( "Hans" ) ; Bayer b = new Bayer( "Sepp" ) ; Je nachdem, welche Art von speziellem Unterklassenobjekt man beim Aufruf übergibt, verhält sich ein und dieselbe Methode anders dh: vielgestaltig = polymorph : // Drei polymorphe Aufrufe // derselben Methode ausgabe: ausgabe( f ) ; // 1. liefert: Bonjour ausgabe( d ) ; // 2. liefert: Mahlzeit ausgabe( b ) ; // 3. liefert: Grüß Gott Hier: Methode ausgabe( Person p ) Für Objekte vom Typ der Klasse Person geschrieben } public static void ausgabe( Person p ) { IO.writeln( p.getGruss( ) ) ; } Aufgerufen mit Objekten vom Typ der Unterklassen von Person : ⇒ Ergebnis der Aufrufe 1, 2, 3: Jedesmal ein anderes Verhalten ! Bonjour, Mahlzeit, Grüß Gott © H.Neuendorf } Nutzen Polymorphie : (35) Generische Programmierung Jedes "Programm" , das mit Objekten der Oberklasse arbeitet, kann auch mit Objekten der Unterklasse arbeiten ! Deshalb auch keine Einschränkung der Sichtbarkeit beim Überschreiben Konsequenz : Mit allgemeiner gehaltenen Klassen beginnen - diese zur Abdeckung spezieller Bedürfnisse via Vererbung verfeinern + anstelle der Oberklassenobjekte verwenden. Semantisches Prinzip für sinnvollen Einsatz von Polymorphie : Liskov Substitution Principle : Einhalten des "Kontrakts" der Oberklassen ..... "Subclasses must be usable through the superclass interface without the need for the user to know the difference" ( Einhalten der semantischen Integrität ..... ) Unterklassen sollen Semantik = inhaltlichen Sinn bewahren! Unterklasse ist Spezialisierung ihrer Oberklasse ⇒ soll ihre Oberklasse sinnvoll vertreten können ! Unterklassen sollen sich in überschriebenen Methoden semantisch verhalten wie ursprüngliche Oberklassen-Methoden. Änderung der Implementierung soll Vertrag nicht verletzen ! Objekte vom Typ Deutscher, Bayer, Franzose verhalten sich prinzipiell wie Objekte vom Typ Person : ⇒ Methode getGruss() liefert auch bei Ihnen einen speziellen Gruß zurück ...... Pacta sunt servanda : Das durch die Basisklasse zugesicherte Objektverhalten soll auch in ihren Unterklassen-Spezialisierungen eingehalten werden ! © H.Neuendorf Statischer + Dynamischer Typ class Zuweisung { Objektvariablen können Objekte verschiedenen Typs referieren (36) public static void main( String[] args ) { Person p1, p2 ; Statischer Typ : - Deklarierter Typ der Objektvariablen p1 = new Franzose( "Jean" ) ; p2 = new Bayer( "Sepp" ) ; - bestimmt, welche Methoden + Attribute via Variable überhaupt ansprechbar sind Dynamischer Typ : dynamische späte Bindung p1.getGruss( ) ; // ruft Methode getGruss( ) // der Klasse Franzose - Typ des zur Laufzeit referierten Objekts - durch Zuweisungen zur LZ jederzeit änderbar - bestimmt, welche Methoden aufgerufen werden p2.getGruss( ) ; // ruft Methode getGruss( ) // der Klasse Bayer p1 und p2 haben statischen Typ Person ⇒ Über p1, p2 nur Methoden + Attribute ansprechbar, die in Klasse Person deklariert sind } } p1 hat nach Zuweisung dynamischen Typ Franzose p2 hat nach Zuweisung dynamischen Typ Bayer ⇒ p1.getGruss( ) → getGruss() von Franzose p2.getGruss( ) → getGruss() von Bayer © H.Neuendorf Dynamische Bindung von Methodenaufrufen: Aufruf obj.m() führt zum Aufruf der m()-Methode, die zum dynamischen Typ von obj gehört ! (37) Statischer Typ - Dynamischer Typ : Methoden Bayer Person p = new Bayer( "Sepp" ) ; Von Person geerbt oder überschrieben In Klasse Bayer hinzugefügte, nicht von Person geerbte Attribute und Methoden Statischer Typ : name lieblingsbier getName( ) setLieblingsbier( ) getGruss( ) p Über Objektvariable p vom statischen Typ Person nicht zugreifbar ..... Statischer Typ von p ist Person ⇒ Nur schon in Klasse Person deklarierte Attribute und Methoden sind ansprechbar ! Dynamischer Typ von p ist Bayer ⇒ Die an Klasse Bayer vererbten und dort evtl. überschriebenen Methoden werden über p angesprochen ! © H.Neuendorf IO.writeln( p.getGruss( ) ) ; Deklarierter Variablen-Typ bestimmt, welche Methoden + Attribute ansprechbar sind Dynamischer Typ : Typ des Objekts, auf den Variable zur Laufzeit zeigt - bestimmt, welche Methoden aufgerufen werden Statischer Typ - Dynamischer Typ Überschriebene Attribute : * Aber : Wenn Unterklasse geerbte Attribute überschreibt, entscheidet der deklarierte statische Typ der Objektvariable, welches Attribut angesprochen wird ….. ⇒ Entscheidung über Attribut-Zugriff fällt zur Compile-Zeit mittels Referenz-Typ !! Methode user() ist geschrieben für Objekte vom Oberklassentyp Auto. Verlässt sich in ihrer Implementierung auf ein Attribut wert vom Typ int. Ist aufrufbar auch mit Unterklassen-Objekten vom Typ PKW. Soll aber dabei nicht invalidiert werden durch in Unterklasse PKW überschriebenes Attribut vom inkompatiblen Typ boolean !! (38) class Auto { public int wert = 10 ; public void m( ) { IO.writeln( "Auto: " ) ; } } class PKW extends Auto { public boolean wert = false ; // überschreibt ! public void m ( ) { IO.writeln( "PKW " ) ; } } class AttributTest { public static void main( String[] args ) { PKW p = new PKW( ) ; // Upcast user( p ) ; } public static void user( Auto a ) { a.m( ) ; // Typ Objekt entscheidend ! int x = 100 + a.wert ; // Typ Referenz entscheidend ! } ⇓ Nur durch Regel * kann die Unterklasse PKW dennoch den Kontrakt der Oberklasse Auto erfüllen !! © H.Neuendorf } (39) Oberklasse Zugriff Attribute + Methoden Nicht-statische Methode Statische Methode Unterklasse Überschreiben ohne Einschränkung Sichtbarkeit super-Zugriff Kein super-Zugriff in statischen Methoden *) Typ des referenzierten Objekts (= dynamischer Typ) entscheidet über Methodenauswahl Nicht-statisches Attribut Statisches Attribut Überschreiben auch mit Einschränkung Sichtbarkeit super-Zugriff Kein super-Zugriff in statischen Methoden *) Typ der Referenz / Objektvariablen (=statischer Typ) entscheidet über Attributzugriff *) Compiler : Cannot use super in a static context ! Weitere Anwendung : Generischer Datenbehälter © H.Neuendorf Person[] pArr = new Person[3] ; pArr[0] = new Person( "Hans" ) ; pArr[1] = new Franzose( "Jean" ) ; pArr[2] = new Bayer( "Sepp" ) ; Statischer Typ : Deklarierter Objekttyp der Variablen p Statischer + Dynamischer Typ class Zuweisung { Dynamischer Typ : Objekttyp, den Variable zur Laufzeit referiert - zur LZ änderbar ! public static void main( String[] args ) { ⇓ Person p ; // statischer Typ Person Erst zur LZ steht aktueller dynamischer Typ der Objektvariable fest. char c = IO.promptAndReadChar( "f/b?" ) ; // Festlegung dynamischer Typ gemäß // User-Eingabe erst zur Laufzeit !! if( c == ' f ' ) { p = new Franzose( "Jean" ) ; } else { p = new Bayer( "Sepp" ) ; } Auswahl auszuführender Methode erst zur LZ dynamisch anhand des aktuellen dynamischen Typs der Objektvariable - und nicht schon statisch zur Compilezeit statisch anhand des Typs der Objektvariable ⇓ Auszuführendes Methoden-Coding nicht schon zur Compilezeit festgelegt sondern erst zur Laufzeit : Dynamische, späte Bindung (late binding) ist technische Voraussetzung für Polymorphie © H.Neuendorf (40) p.getGruss( ) ; // welche Methode getGruss( ) gerufen wird // steht erst zur Laufzeit fest ! } } (42) Aufruf überladener Methoden Überladen auch bei Methoden mit Objektparametern möglich class GrussAusgabe { public static void main( String[] args ) { Person p = new Person( "Jemand" ) ; Regeln analog zum Überladen von Methoden mit primitiven Datentypen Deutscher d = new Deutscher( "Hans" ) ; ⇓ Bayer b = new Bayer( "Sepp" ) ; 1. Genau passende Methode wird aufgerufen begruesse( p ) ; 2. Wenn keine Methode genau passt, dann möglichst spezifische Methodenauswahl begruesse( d ) ; begruesse( b ) ; ⇓ } Aufruf mit Bayer-Objekt führt zum Aufruf der Methode mit Parameter vom Typ Deutscher, nicht zum Aufruf der allgemeineren Methode mit Parameter vom Typ Person public static void begruesse( Person p ) { IO.writeln( p.getGruss( ) ) ; } public static void begruesse( Deutscher d ) { Bei Methoden mit mehreren Objektparametern kann es zu Zweideutigkeiten kommen, die der Compiler moniert ….. IO.writeln( "Hier: " + d.getGruss( ) ) ; } } © H.Neuendorf (43) Abstrakte Klassen abstract Person Schlüsselwort abstract getName() abstract getGruss() Abstrakte Klassen (können) enthalten Methoden, die als abstrakt deklariert sind Methoden mit Methodenkopf deklariert , aber enthalten keine Implementierung name Von abstrakten Klassen können keine Objekte instanziiert werden ! Deutscher getGruss() Franzose Begriff Person ist abstrakt. Nicht bei Konstruktoren getGruss() Konkreter Gruß nur sinnvoll speziell für Deutsche, Franzosen, Bayern, ...... definierbar ! abstract class Person { Bayer public Person( String name) { this.name = name ; } public String getName( ) { return name ; } getGruss() setLieblingsbier() lieblingsbier Abstrakte Klasse stellt Methoden zum Überschreiben in Unterklassen zur Verfügung, deren konkrete Ausformulierung erst dort Sinn macht weil nur für spezialisierte Unterklassen semantisch klar ist, wie die Wirkung einer solchen Methode aussehen sollte ! Implementierung wird quasi "aufgeschoben" © H.Neuendorf abstract public String getGruss( ) ; private String name ; } Abstrakte Klassen abstract Person getName() abstract getGruss() name Von abstrakten Klassen können keine Objekte instanziiert werden ! (44) ... da nicht klar ist, wie sich Objekte bei Aufruf der abstrakten Methode verhalten sollten .... Können jedoch auch : a) implementierte Methoden enthalten b) statische impl. Methoden enthalten – sind auf abstrakter Klasse aufrufbar ! Deutscher getGruss() Bayer getGruss() setLieblingsbier() lieblingsbier Abstrakte Methoden können nicht static oder private sein ! © H.Neuendorf Unterklassen müssen alle geerbten abstrakten Methoden überschreiben + implementieren ..... Franzose Bsp: IO könnte abstract sein. Abstracte Klassen könnten nicht-abstrakte statische Factory-Methoden enthalten getGruss() .... sonst sind sie auch abstrakt (nicht instanziierbar) und müssen als abstract gekennzeichnet werden ! Polymorphie funktioniert auch mit abstrakten Oberklassen ! abstract class Person { public Person( String name ) { this.name = name ; } public String getName() { return name ; } Unterklassen können konkrete Methoden abstrakt überschreiben In Unterklassen können zusätzliche abstrakte Methoden auftreten ⇒ abstract public String getGruss( ) ; Es darf weitere abstrakte Unterklassen in einer Klassenhierarchie geben Abstrakte Methoden der Oberklasse können nicht mit super. aufgerufen werden private String name ; } Abstrakte Klassen und Methoden abstract class Figur { abstract public flaeche( ) ; } class Kreis extends Figur { private double radius ; Kreis( double r ) { radius = r ; } public double flaeche( ) { return Math.PI * radius * radius ; } } Typisches Beispiel für abstrakte Oberklasse als gemeinsamer Typ - innerhalb nicht durchgängig kompatibler Vererbungshierarchie : Kreise und Rechtecke haben sonst nichts Gemeinsames …. Vorsicht : Wenn man abstrakter Oberklasse neue abstrakte Methode hinzufügt invalidiert man alle bisherigen Verwender Hinzufügen konkreter Methode mit Default-Implementierung jedoch meist problemlos © H.Neuendorf (45) Speziellere Unterklassen können durchaus auch weniger Attribute als ihre allgemeinere Oberklasse haben. Dafür hier zusätzliche Bedingung beim Quadrat : breite = laenge ! class Rechteck extends Figur { private double laenge ; private double breite ; Rechteck( double l, double b ) { laenge = l ; breite = b ; } public double flaeche( ) { return laenge * breite ; } } class Quadrat extends Rechteck { Quadrat( double kante ) { super( kante, kante ) ; } // Flächenberechnung funktioniert mit // geerbter Methode ! } (46) Finale Klassen und Methoden Konstanten : final class Muenchner extends Bayer { final int MAX = 5 ; Wert endgültig, nicht veränderbar ! Finale Methoden : Nicht bei Konstruktoren public Muenchner( String name ) { super( name ) ; } Sind "endgültig" : Dürfen in Unterklassen nicht überschrieben werden ! final public String getGruss( ) { return "Servus !!" ; } Dürfen nicht abstrakt sein - sonst blieben sie ohne Inhalt ! Finale Klassen : Sind "endgültig": Ende der Vererbungshierarchie Von ihnen kann nicht abgeleitet werden - es können keine Unterklassen aus ihnen gebildet werden Dürfen keine abstrakten Methoden enthalten ! } Finale Methoden : Dürfen in Unterklassen nicht überschrieben werden ! Vorteile : Geschwindigkeit - Compiler kann kompakteren Bytecode generieren, wenn klar ist, dass es keine weiteren polymorphen Unterklassen gibt : Keine dynamische Bindung bei finalen Methoden. Sicherheit - Unterlaufen von Regeln durch Überschreiben von Methoden in Unterklassen wird verhindert. Vererbung verhindern durch Klassen, die dafür nicht gedacht oder designed sind ! © H.Neuendorf ↑↓ Abstrakte Methoden : Sollen in Unterklassen überschrieben werden ! Wenn finale Methoden intern nicht-private Attribute oder nicht-finale, nicht-private Methoden verwenden, so kann sich ihr Verhalten doch indirekt in überschreibenden Unterklassen ändern → Template-Pattern (47) Klasse Object - Wurzel der Java-Klassenhierarchie Object Jede Java-Klassenhierarchie hat immer die Wurzel Object ! Methoden von Object : clone(), equals(), wait(), toString() .... ..... interessieren uns hier noch nicht .... abstract Person getName() abstract getGruss() Jede Klasse in Java erbt automatisch von Klasse Object = "Mutter aller Java-Klassen" Wichtig für generisches Programmieren mittels Upcast : (Datenabstraktion) Klasse Object als gemeinsamer Ober-Typ für Objekte aller Klassen ! name Deutscher getGruss() Bayer getGruss() setLieblingsbier() lieblingsbier © H.Neuendorf Franzose getGruss() class GenericArray { // generischer Datenbehälter - gut ?? public static void main( String[] args ) { Object[] container = new Object[3] ; container[0] = new Franzose( "Jean" ) ; container[1] = new StringBuffer( "Ein Wort" ) ; container[2] = new Integer( 156 ) ; } } (48) Design : Beziehungen zwischen Objekten Zwei grundsätzlich verschiedene semantische Beziehungen 1. Ist_Ein – Beziehung : Ein Franzose ist eine Person ⇒ Darstellung durch Vererbung Spezialisierung, Erweiterung class Person { public Person( String nn ) { name = nn ; } public String getName( ) { return name ; } private String name ; } Alles, was sich wesentlich in der Oberklasse findet, ist auch in der Unterklasse vorhanden. Vererbung stellt diese Verhältnisse class Franzose extends Person { public Franzose( String name) { super( name ) ; } public String getName( ) { String n = super.getName( ) ; n = "Name ist: " + n ; return n ; } // ........................................... semantisch korrekt dar! Unterklassen-Objekt kann OberklassenObjekt voll und ganz vertreten! 2. Hat_Ein – Beziehung ........ © H.Neuendorf } (49) Beziehungen zwischen Objekten 2. Hat_Ein-Beziehung : Eine Linie hat Anfangs- und Endpunkt ⇒ Darstellung als Assoziation class Point { public Point( double xk, double yk ) { x = xk ; y = yk ; } (nicht durch Vererbung!) private double x ; private double y ; Zusammenwirken gleichberechtigter Objekte Häufig auch: Ganzes-Teil-Beziehung } ⇒ Darstellung durch Vererbung wäre semantisch falsch ! Eine Linie ist keine spezielle Punkt-Version ! Keine Spezialisierung, sondern Verwendung class Linie { public Linie( Point pA, Point pB ) { pAnfang = pA ; pEnde = pB ; } einer Objektreferenz als Attribut in einer private Point pAnfang ; private Point pEnde ; .............................................. anderen Klasse } Anm: Die Welt der Vererbung + Polymorphie ist doch noch etwas komplizierter als hier dargestellt → siehe Invarianz, Kovarianz, Kontravarianz in Vererbungshierarchien als Stichworte zur Vertiefung ….. © H.Neuendorf (50) Warum keine Mehrfachvererbung ? Java : Nur Einfachvererbung = Jede Unterklasse hat nur eine direkte Oberklasse Realität : Häufig Mehrfachvererbung = Unterklassen können von mehreren Oberklassen erben (C++) Kind = Vater + Mutter Klavier = Musikinstrument + Möbel , ..... Grund: a) Mehrfachvererbung in Praxis eher selten verwendet, macht Programme unübersichtlicher b) Erhöhter Speicherbedarf + Verwaltungsaufwand für Halten der VMT ( virtual method table ) c) Rautenproblem : Mehrfachvererbung bedeutet für Klasse Unter_2 : Basis Erbt meth1() und wert1 gleich zweimal : Auf Weg über Klasse Unter_1a und auf Weg über Klasse Unter_1b meth1() wert1 Welcher Weg soll der gültige sein ? Unter_1a Unter_1b ................ ............... Methode meth1() könnte in Unter_1a und Unter_1b überschrieben werden ! Welche Methodenvariante soll dann beim Aufruf aus Unter_2 heraus gerufen werden ..... ??? ⇓ Lösung : Keine Mehrfachvererbung Unter_2 ............... © H.Neuendorf Ersatz : Interface-Konzept (51) Interface : Abstrakte Methoden + Konstanten Schnittstellenbeschreibung : Legt Schnittstelle (Typ) fest, nicht Implementierung Anforderungsbeschreibung von Eigenschaften / Fähigkeiten / Funktionalitäten Alle Methoden automatisch public abstract Alle Attribute automatisch ⇒ public static final Schlüsselwörter public, abstract und static final müssen nicht extra angegeben werden Konstanten - müssen initialisiert werden ! Interfaces können mehrere Interfaces erben ⇒ Mehrere Schnittstellenbeschreibungen können in einem Interface zusammengefasst werden Klassen können mehrere Interfaces implementieren : Schlüsselwort interface Bonus { public static final double MIN = 1000.0 ; public abstract void addBonus(double betrag) ; } class Konto implements Bonus { public Konto( ) { .......... } public double getSaldo( ) { return saldo ; } public void addBonus( double betrag ) { if ( saldo >= MIN ) { saldo = saldo + Betrag ; } } implements Implementierende Klasse ist typkonform zu Interface ! Klasse, die nicht alle Interfacemethoden implementiert, enthält abstrakte Methoden und ist abstract ! Implementierte Methoden müssen public sein ! private double saldo; } Keine Einsparung von "Tiparbeit" Bessere Strukturierung von Software ! © H.Neuendorf (52) Interfaces : Strukturierung + Modellierung Fahrzeugantrieb Nachbildung Mehrfachvererbung mittels Interfacehierarchie : Schlüsselwort extends Interfaces erlauben Modellierung von komplexen Zusammenhängen, die durch Einfachvererbung nicht abgebildet werden können : → Anforderungs-Spezifikation Durch Interface-Hierarchie wird Zusammenhang abgebildet + charakteristisches Verhalten festgelegt Keine Implementierung von Methoden, aber Modellierung + Strukturierung des Sachverhalts Klassen, die Interfaces implementieren, übernehmen dadurch Strukturierung des Sachverhalts und die Anforderungsbeschreibung Müssen Methoden nur noch ausimplementieren : Spezifikation + Modellierung ⇒ Interfaces Abstrakte Vertragsdefinition Implementierung © H.Neuendorf ⇒ Klassen Diesel Elektro Hybrid interface Fahrzeugantrieb { int getLeistung( ) ; int getGewicht( ) ; } interface Diesel extends Fahrzeugantrieb { float getHubraum( ) ; float getVerbrauch( ) ; } interface Elektro extends Fahrzeugantrieb { float getBatteriekapazität( ) ; } interface Hybrid extends Diesel , Elektro { String getKopplungsart( ) ; } class HybridAntrieb implements Hybrid { public int getLeistung( ) {............} public float getBatteriekapazität( ) {.......} // .............................. } (53) Interfaces Interfaces können nicht instanziiert werden, sondern müssen durch implementierende Klasse konkretisiert werden interface W {....... } interface X extends W {.....} interface Y extends W {.....} Unterschied Interface - abstrakte Klasse : 1. Abstrakte Klassen können Implementierung enthalten 2. Klassen können nur von einer abstrakten Oberklasse erben (Einfachvererbung) aber beliebig viele Interfaces implementieren. IFs erlauben Mehrfachvererbung class Z implements X , Y { /*…*/ } W X ⇓ Rautenproblem bei Methoden – Regeln : 1. Gleicher Methodenname, gleicher oder verschiedener Rückgabetyp, unterschiedliche Parameterlisten : ⇒ Klasse Z muss entsprechend viele gleichnamige überladene Methoden implementieren 2. Gleicher Methodenname, gleicher Rückgabetyp, gleiche Parameterlisten : ⇒ Klasse Z muss nur diese eine Methode implementieren 3. Gleicher Methodenname, gleiche Parameterlisten unterschiedlicher Rückgabetyp : ⇒ Nicht erlaubt - Compilerfehler ! Identische Konstanten-Namen in Interfaces : Konflikt durch Angabe des Interface-Namens aufgelöst © H.Neuendorf Y Z interface Skat { int kartenZahl = 32 ; } interface Poker { int kartenZahl = 52 ; } class Z implements Skat, Poker { /* …*/ Skat.kartenZahl /*…*/ /*… */ Poker.kartenZahl /*…*/ } // IF-Konstanten sind auch in nicht // implementierenden Klassen verwendbar : class A { /* …*/ Skat.kartenZahl /*…*/ /*… */ Poker.kartenZahl /*…*/ } Programmieren mit Interfaces Interface = Strukturierter Typ Auf diesen kann man sich (u.a.) bei Methodenimplementierung beziehen : Parameter, Rückgabewert, Variablen von Methoden können von Interface-Typ sein ! Interface-Referenz wird Objekt einer implementierenden Klasse zugewiesen *) Darauf nur die im IF definierten Methoden + Konstanten zugreifbar Erhöhte Typsicherheit & Flexibilität bei Verwendung, leichtere Austauschbarkeit der Implementierung ! ⇓ Programmierung via Interfaces - konkrete Aufrufe aber stets mit Objekten der implementierenden Klassen Vorsicht : Nachträgliche Änderungen an Interfaces erzwingen Quellcodeanpassungen in implementierenden Klassen !! © H.Neuendorf (54) interface IFEuroSF { public static final double kurs = 1.14 ; public abstract double inSF( double euroBetrag ) ; } ////////////////////////////////////////////////////////////////////////// class CIFUser { public static void rechne( IFEuroSF iE ) { double e = IO.promptAndReadDouble( "Euro : " ) ; double s = iE.inSF( e ) ; IO.writeln( "In SF sind das : " + s ) ; } Kennt Implementierer nicht ! } /////////////////////////////////////////////////////////////////////////// class CEurorechner implements IFEuroSF { public double inSF( double euroBetrag ) { double sf = euroBetrag * IFEuroSF.kurs ; return sf ; } Kennt User nicht ! } ///////////////////////////////////////////////////////////////////////////// class IFTest { public static void main( String[] args ) { IFEuroSF eR = new CEurorechner( ) ; // *) CIFUser u = new CIFUser( ) ; u.rechne( eR ) ; } } (55) Programmieren mit Interfaces Interfaces legen keine Implementierungsdetails fest : ⇒ Ihre Methoden können nicht mit native, synchronized oder strictfp modifiziert werden Würde Implementierung vorschreiben – dies ist allein Sache der impl. Klasse Interfaces dürfen keine Konstruktoren vorschreiben Methoden können nicht final sein (finale Methodeparameter möglich, aber Klasse nicht daran gebunden … ) ⇒ Denn sie müssen erst noch in einer Klasse implementiert werden Methoden können nicht static sein ⇒ Denn statische Methoden können nicht abstract sein Methoden dürfen jedoch mit throws-Klausel Exception-Verhalten vorgeben Interface-Konstanten können Objekte sein ! Extreme Form : class A { public void tuWas( ) {/* … */ } // ….. } interface IFEuro { public static final double kurs = 1.95583; public static final A a = new A( ) ; public abstract double inDM( double euros ) throws MyException ; } Marker-Interfaces ohne jeden Inhalt © H.Neuendorf Vertrag nur durch Dokumentation festgelegt Es wird auf Code-Ebene kein Verhalten definiert, dh keine Methoden + Konstanten. Gesamter Vertrag befindet sich in der Dokumentation, die die Erwartungen beschreibt, die eine implememtierende Klasse erfüllen muss Bsp : Serializable, Clonable, … Interfaces – Möglichkeiten class A { public void tuWas( ) { /* ... */ } } (56) interface IFDummy { public void tuWas() ; } class B implements IFDummy { class C1 implements IFTest, IFTest.IFInner { public void tuWas( ) { /* ... */ } public void m1( A aObj ){ } aObj.tuWas( ) ; interface IFTest { aFin.tuWas( ) ; // Zugriff auf Konstante // Parameter Typ andere Klasse : } public void m1( A aObj ) ; public void m2( IFDummy d ) { // Parameter Typ Interface : d.tuWas( ) ; // Aufruf Interface-Methode public void m2( IFDummy d ) ; } // Rückgabe Typ Interface : public IFDummy m3( ) { public IFDummy m3( ) ; return new B( ) ; // Rückgabe typkomp.Objekt // Konstanten Typ Klasse oder Interface : } public static final A aFin = new A( ) ; public void m4( int b ) {/* ... */ } public static final IFDummy ifd = new B( ) ; // Konstanten erst zur LZ festgelegt : public static final int d = IO.promptAndReadInt("?") ; // Inneres Interface : public interface IFInner { public void m4( int b ) ; } } © H.Neuendorf } Nicht alles technisch mögliche ist auch sinnvoll : Interfaces sollen keine Implementierungsdetails vorschreiben. Nicht das Wie? sondern das Was? zu beschreiben ist Aufgabe von Interfaces ⇒ Interfaces sollten nicht von Implementierungen (existierenden Klassen) abhängen ! Zusammenspiel : Interface – Abstrakte Klasse – Implementierende Klasse interface IF { m1( ) m3( ) (58) Formuliert Vertrag + Typ = Schnittstelle Framework : m2( ) → m4( ) Verlangt nach Typ IF m5( ) … } abstract class AbstractIF implements IF { abstract public void m1( ) ; Abstrakte skeletal-implementation-class abstract public void m2( ) ; als Implementierungshilfe : public void m3( ) { } Implementiert Vertrag bereits teilweise – public void m4( ) { } eventuell mit leerer Implementierung public void m5( ) { } Grundlegende Methoden bleiben abstrakt, …...... verwendende Methoden vorimplementiert } Instanzen entsprechen dem vom Framework geforderten Typ ! Bsp : java.util Collection Framework class MyVersionOfIF extends AbstractIF { public void m1( ) { } public void m2( ) { } User : Muss nur noch Teil der Methoden implementieren bzw. zur individuellen public void m4( ) { } Anpassung / Effizienzerhöhung etc. …………………… überschreiben } Weniger mühsam, als direkt mit IF zu beginnen © H.Neuendorf IF Collection AK AbstractCollection , AbstractList , ……. (59) Interfaces nicht als Konstantendeponie missbrauchen ! interface PhysicalConstants { public static final double NA = 6.022e23 ; package science ; // Bessere Lösung ! public class PhysicalConstants { // Utility-Klasse public static final double KB = 1.38e-23 ; private PhysicalConstants( ) { } // no objects! public static final double ME = 9.11e-31 ; public static final double NA = 6.022e23 ; public static final double KB = 1.38e-23 ; } public static final double ME = 9.11e-31 ; Interne Verwendung von Konstanten sollte ein Implementierungsdetail der verwendenden Klasse sein. Bei Implementierung eines entsprechenden "KonstantenInterfaces" werden die Konstanten jedoch Teil der öffentlichen Schnittstelle der implementierenden Klasse ("implementation details leak into the class's exported API"). Verwirrt Benutzer in der Regel, da er keinen Sinn in Werten sieht, die nur innerhalb von Methoden-Implementierungen verwendet werden. Der Namensraum der Klasse und all ihrer Unterklassen wird mit den IF-Konstantennamen verschmutzt. Zudem Bürde : Auch wenn in späteren Versionen der Klasse diese Konstanten intern nicht mehr benötigt werden, muss die Klasse doch weiterhin das IF implementieren, um typkompatibel zu Vorgängern bleiben. } import science.PhysicalConstants ; Nichtinstanziierbare Utility-Klasse ist viel bessere Lösung ! class Test { public double atoms( double mol ) { return PhysicalConstants.NA * mol ; } // ……… } Interfaces sollten nur verwendet werden, um Typen + Schnittstellen zu definieren ! Sie sollten nicht dem bloßen Export von Konstanten dienen ! © H.Neuendorf (60) Vorteile und Probleme von Interfaces lt is, generally speaking, impossible to add a method to a public interface without breaking all existing classes that implement the interface. Classes that previously implemented the interface will be missing the new method and won't compile anymore. You could limit the damage somewhat by adding the new method to the skeletal implementation at the same time as you add it to the interface, but this really wouldn't solve the problem. Any implementation that didn't inherit from the skeletal implementation would still be broken. Public interfaces, therefore, must be designed carefully. Once an interface is released and widely implemented, it is almost impossible to change. You really must get it right the first time. If an interface contains a minor flaw, it will irritate you and its users forever. If an interface is severely deficient, it can doom an API. The best thing to do when releasing a new interface is to have as many programmers as possible implement the interface in as many ways as possible before the interface is frozen. This will allow you to discover flaws while you can still correct them. To summarize, an interface is generally the best way to define a type that permits multiple implementations. An exception to this rule is the case where ease of evolution is deemed more important than flexibility and power. Under these circumstances, you should use an abstract cass to define the type, but only if you understand and can accept the limitations. If you export a nontrivial interface, you should strongly consider providing a skeletal implementation to go with it. Finally, you should design all of your public interfaces with the utmost care and test them thoroughly by writing multiple implementations. Joshua Bloch, Effective Java © H.Neuendorf S.97 Vorteile von Interfaces – Denken + Entwickeln via Schnittstellen 1. Interfaces fördern den modularen Aufbau von Systemen → Prinzip Separation of Concerns wird unterstützt – Reduktion der Abhängigkeiten → Wartbarkeit wird gefördert durch klar definierte funktionale Einheiten 2. Interfaces fördern das Information Hiding schon beim Entwurf von Systemen → Kein "Drauflos-Implementieren" sondern thematische Strukturierung → "Rauschen der Implementierung" kommt erst später → Konzentration auf die Sematik einer Schnittstelle → Konzentration auf saubere + sinnvolle Signaturen und Sinn von Schnittstellen Prinzip der Kohäsion: Vollständigkeit und funktionaler Zusammenhang 3. Interfaces verbessern die spätere Implementierung → Implementierung hat klare Basis und eindeutige semantische Zielrichtung → Lose Kopplung zwischen Schnittstelle und Implementierung → Leichtere Austauschbarkeit der Implementierung (Testen, Warten, Upgrade) → Open-Closed-Prinzip: Software soll offen sein für Erweiterungen aber abgeschlossen für Änderungen (Konstanz der Schnittstelle) © H.Neuendorf (61) Grundsätze Objektorientierten Designs Grundeinheit des OO-Programmierens ist die Klasse Aber Grundeinheit des OO-Entwurfs ist der Typ ! Abstrahiere von der konkreten Implementierung – denn diese ist austauschbar - Programmiere gegen Interfaces – nicht gegen konkrete Klassen - Abhängigkeiten von Abstraktionen herstellen – nicht von Konkretisierungen - Hohe Abstraktion statt vorschnelle Konkretisierung – alles Konkrete veraltet schnell - Implementierungen müssen austauschbar sein + Erweiterungen leicht möglich sein Denke in Verträgen + Service-Anforderungen – nicht in Details von Algorithmen - Entkopple Konsumenten und Produzenten durch Interfaces (Service-Verträge) - Garantiert werden keine Implementierungsdetails , sondern … … die Einhaltung von Service-Verträgen, die in Interfaces festgeschrieben sind Die Seele des Ganzen ist die sinnvolle Typisierung – nicht die konkrete Implementierung Erfinde das Rad nicht zweimal – orientiere dich an Design-Patterns Modellieren ist wichtiger als Implementieren → Model Driven Architecture (MDA) © H.Neuendorf (62) (63) Anwendung : Template - Pattern abstract class KoffeinhaltigesGetraenk { public final void zubereitungsRezept() { interface final class abstract extends .... Was geht damit ?? kocheWasser(); aufschütten(); inTasseSchütten(); if ( mitZutaten() ) { zutatenHinzu(); Zubereitung ist final ⇒ Am Rezept (Algorithmus / Reihenfolge der Schritte) können Unterklassen nichts ändern } } Der Algorithmus enthält abstrakte Methoden ⇒ public abstract void aufschütten(); Durch Überschreiben können Unterklassen doch indirekt eine Anpassung vornehmen public abstract void zutatenHinzu(); public void kocheWasser() { IO.writeln( "Koche Wasser" ); } Diese Methode ist ein " Hook " := public void inTasseSchütten() { Eine Methode mit Default-Implementierung oder sogar ohne Implementierung ⇒ IO.writeln( "Schütte in Tasse" ); } public boolean mitZutaten() { return true; } } © H.Neuendorf Die Unterklassen können sich durch Überschreiben der Methode hier "einhaken" – müssen es aber nicht ... (64) Anwendung : Template - Pattern public class Kaffee extends KoffeinhaltigesGetraenk { public void aufschütten() { IO.writeln( "Kaffee durch Filter" ); } public void zutatenHinzufügen() { IO.writeln( "Zucker + Milch" ); } public boolean mitZutaten() { char z = IO.promptAndReadChar( "Zutaten?" ); if ( z == 'j' ) { return true; } else { return false; } } Die Unterklassen passen grundlegendes Algorithmus (das Template ) durch Überschreiben an ihre Bedürfnisse an ! Viele weitere Getränke können implementiert werden – ohne an abstrakter Basisklasse und am Algorithmus selbst Änderungen vornehmen zu müssen ⇓ } public class Tee extends KoffeinhaltigesGetraenk { public void aufschütten() { IO.writeln( "Tee in Kanne" ); } Gemeinsame + unveränderliche Anteile in Basisklasse erfasst Template für Unterklassen public void zutatenHinzufügen() { IO.writeln( "Zitrone" ); } public boolean mitZutaten() { Design-Patterns ! char z = IO.promptAndReadChar( "Zutaten?" ); if ( z == 'j' ) { return true; } else { return false; } } } © H.Neuendorf Joshua Bloch, Effective Java S.234 Don't sacrifice sound architectural principles for performance. Strive to write good programs rather than fast ones. If a good program is not fast enough, its architecture will allow it to be optimized. Good programs embody the principle of information hiding: where possible, they localize design decisions within individual modules, so individual decisions can be changed without affecting the remainder of the system. (65) @Annotations : Meta-Informationen zu Klassen und Methoden Annotations do not directly affect program semantics, but they do affect the way programs are treated by tools and libraries […]. Annotations can be read from source files, class files, or reflectively at run time. Typical application programmers will never have to define an annotation type, but it is not hard to do so. […] As of release 5.0, the platform has a general purpose annotation facility that permits you to define and use your own annotation types. Marker-Auswertung durch Tools zur Erstellung von Ressourcen Teilweise durch Compiler gecheckt → @Override Javadoc Junit …. @SuppressWarnings("unused") Einkompiliert in Bytecode, zur LZ-Auswertung via Reflection-API Liefert Frameworks und Containern Informationen → → → Bei EJBs @Deprecated java.lang.annotation @Stateless @Remote …… Im Coding am gleichen Ort wie andere Modifizierer zulässig Parametrisierbar Können Meta-Annotationen besitzen @interface Test{ } Annotationen werden als spezielle Art von Interface definiert. Diese können Elemente enthalten, die bei Verwendung mit passenden Werten zu füllen sind. Elemente unterliegen Einschränkungen: @interface Copyright { Nur primitive Typen, Strings, Arrays, … String author( ) ; int year() ; Elemente haben keine Parameter und keine Exceptions Default-Werte können hinterlegt werden ….. } Anwendbarkeit durch Meta-Annotationen einschränkbar @Copyright( author="SAP AG" , year = 2009 ) → Gosling et al. The Java Programming Language, Ch15 public class SomethingNew { @Test void tuWas( ) { /* nur ein Test */ } } © H.Neuendorf Paket java.lang.annotation Enthält Meta-Annotationen, um LZReflection korrekt einzustellen (67) Pakete (Packages) Ordnung in großen Projekten : Strukturierung durch Klassen + Methoden Jedes Paket sollte genau definierte Zuständigkeiten + Aufgaben besitzen ! Allerdings zu feinkörnig ⇒ Gröberer Strukturierungsmechanismus : Paket : Zusammenfassung zu größerer Einheit = Sammlung zusammengehöriger Klassen und Interfaces JDK-Bibliothek besteht aus zahlreichen Paketen : u.a. java.lang : Standardpaket mit absolut erforderlichen Klassen ( String, StringBuffer, ...) wird in jedes Programm automatisch importiert java.io : Klassen zum Lesen + Schreiben von Datenströmen ( Dateien, Tastatur, Screen ) java.awt: Klassen für graphische Benutzeroberflächen ( Fenster, Buttons, Menues, ...) java.util: Diverse Datencontainer, Zufallszahlengenerator, .... Paket Arbeiten mit Paketen: 1. Anlegen eigener Pakete Schlüsselwort : package 2. Einbinden von Paketen Schlüsselwort : © H.Neuendorf import Klasse1 Klasse2 Methoden Attribute Methoden Attribute (68) Anlegen von Paketen Klassen können bei Deklaration eindeutig einem Paket zugeordnet werden : Am Anfang der Quelldatei (1.Zeile der Datei ! ) : package packageName einfügen ⇒ Alle Klassen in dieser Datei gehören dann zu diesem Paket Paket = logische Einheit - Klassen können physisch über verschiedene Dateien verteilt sein package graphics; package graphics; class Circle { class Rectangle { ......... ......... } } Datei Rectangle.java Datei Circle.java Paket graphics Circle Rectangle Relativ eindeutige Paketnamen mittels umgekehrter Domain : package de.dhbw-mosbach.graphics ; © H.Neuendorf Wenn keine Angabe einer packageZugehörigkeit erfolgt, gehören alle Klassen der Datei zum namenlosem Standardpaket. Werden dann im aktuellen Arbeitsverzeichnis gesucht. Jede Klasse gehört zu einem Paket, zumindest Standardpaket Paket = Sichtbarkeitsgrenze (69) Paketkonzept leistet : Alles, was zum Paket gehört, ist außerhalb Paket Deklaration öffentlicher Schnittstellen defaultmäßig unsichtbar, nicht zugreifbar ! Durchsetzung Geheimnisprinzip Dokumentation von Verwendungen im Code Einschränkung von Verwendungen Veröffentlichung nur durch expliziten Export ! ⇒ Klassen haben nur Zugriff auf andere Klassen (Attribute + Methoden) des gleichen Pakets ! Code des Pakets kann kooperieren, ohne dass externes Coding darauf Zugriff hätte ! ⇒ Klassen eines Pakete haben keinen Zugriff auf Klassen eines anderen Pakets ! Paket = Namensraum : In verschiedenen Paketen gleiche Namen verwendbar Nur innerhalb Paket müssen Klassennamen eindeutig sein package yourPack ; package myPack ; class C1 { int x; void m(){...} } class C2 { int y; void m() {...} } class C1 { int x; class C22 { int y; // Klassen C1 und C2 sind lokal zu Paket myPack © H.Neuendorf void m() {...} C2 c = new C2( ) ; // Fehler !! // können sich gegenseitig benutzen // aber nicht von Außen (= aus anderen Paketen) ansprechbar ! void m(){...} } // Klasse C2 hier unbekannt !! } Export von Paket-Inhalten : (70) Kennzeichnung als public Zusammenarbeit zwischen Paketen → Zugriff auf Klassen, Methoden, Attribute, IFs anderer Pakete Gewährung Zugriff → Durch Export von Paketbestandteilen Export → Deklaration von Klassen, Methoden, Konstruktoren, Attributen als public : "Was public sein soll / exportiert wird, bestimmt das Paket selbst ! " Voraussetzung für Sichtbarkeit von public-Methoden + public-Attributen : → Zugehörige Klasse selbst auch public deklariert ! Nur Interface-Attribute und Methoden sind defaultmäßig public, nicht aber Interface selbst Wenn Klasse nur nicht-public Konstruktoren hat, dann kann sie aus anderen Paketen nicht instanziiert werden Konstruktoren → nur public Konstruktoren aus Fremdpaketen aufrufbar ! package myPack ; // Auch ein Paket hat eine Schnittstelle !! public class C1 { // C1 wird exportiert public int x ; // Attribut x exportiert int y ; public void m1( ){...} // Methode m1( ) exportiert void m2( ) {...} public C1( int a ) { ... } // dieser Konstruktor wird exportiert C1( ) {...} } class C2 { ...... } // C2 von Außen nicht sichtbar ! © H.Neuendorf Aus Fremd-Paketen : public-Elemente von C1 zugreifbar Nicht-public-Elemente und ganze Klasse C2 jedoch verborgen ! Erlaubt: C1 obj = new C1( 4 ) ; Verboten: C1 obj = new C1() ; Implementierung von nichtpublic-Elementen des Pakets grundsätzlich änderbar ! Verwendung von Paket-Inhalten : (71) Import in andere Pakete Voraussetzung für Verwendung exportierter Klassen + ihrer Komponenten in anderen Paketen : → Ausdrücklicher Import der fremden Klassen in Paketen " Was von Außen importiert wird, bestimmt das verwendende Paket selbst ! " Sinn : Klare Festlegung, welche fremden Klassen in eigenem Paket / Programm verwendet werden ! → Verständlichkeit / Lesbarkeit / Durchschaubarkeit der Programme Mittels : a) Volle Paket-Qualifikation des Klassennamens ( Impliziter Import ) b) Import-Anweisung in Quellcode-Datei ( Expliziter Import ) package myPack ; package newPack ; public class C1 { ....} class C2 { ...... } package ourPack ; public class C1 { ....} class C2 { ...... } © H.Neuendorf // Impliziter Import : class C10 { myPack.C1 obj_1 = new myPack.C1( ) ; ourPack.C1 obj_2 = new ourPack.C1( ) ; .......................... } Gleichnamige Klassen aus verschiedenen Paketen durch Qualifikation nebeneinander verwendbar Eigene Klassen dürfen so heißen, wie implzit importierte Klassen Expliziter Import Nachteil : In Programmcode weniger deutlich, woher die Klassen wirklich stammen ! Import-Zeile in Quellcode-Datei (72) Varianten : a) Import einer einzelnen Klasse : import paketname.klassenname ; b) Import aller Klassen eines Pakets : import paketname.* ; c) Statischer Import = statischer Elemente einer bestimmten Paketklasse : import static java.lang.Math.* ; Regeln : Import-Anweisungen in Quellcode-Datei müssen direkt nach package-Anweisung stehen ! Import-Anweisungen gelten nur für jeweilige importierende Quell-Datei ! Alle in Import-Anweisungen angeführten Klassen müssen verschiedenen Namen haben ! Eigene Klassen dürfen nicht so heißen, wie explzit importierte Klassen ! → Namenskonflikt ⇒ Übergang zum impliziten Import mit Paket-Qualifikation Verwendung : In Coding Klassennamen somit ohne Paket-Qualifikation verwendbar package myPack ; package newPack ; import myPack.C1 ; // expliziter Import: In Quellcodedateien quasi schon enthalten : import java.lang.* ; public class C1 { …… } class C2 { ...... } © H.Neuendorf class C10 { C1 obj_1 = new C1( ) ; .......................... } Dadurch sind nötige "Spracherweiterungen" (String, Object, ....) verfügbar ! (73) Pakete und Verzeichnisse - Regeln Abbildung : Klassen → Dateien Pakete → Verzeichnisse + 1. Eine public Klasse C muss in Datei namens C.java implementiert werden ! 2. Alle Klassendateien eines Pakets P müssen in einem Verzeichnis namens P liegen ! 3. Eine Datei darf beliebig viele Klassen enthalten, aber nur eine davon darf public sein ! Name der public-Klasse bestimmt Dateinamen .java Andere Datei-Klassen werden von public-Klasse intern verwendet und nicht exponiert Innere Klassen können public sein und separat importiert werden Bsp: Paket P mit public Klassen A, B und C set CLASSPATH= ⇒ Verzeichnis P mit Dateien A.java B.java C.java P P → .jar .zip .class ….. A B C A.java Pakethierachie → Pakete aus Paketen = Schachtelung Bsp: Paket P1 + Paket P2 → Paket P3 Verzeichnis P3 enthält Unterverzeichnisse P1 + P2 In Unterverzeichnissen liegen einzelne .java-Dateien B.java C.java Benutzung → Angabe gesamter Paketpfad bis zur Klasse : import P3.P2.C1; import P3.P2.* // Import einer bzw aller Klassen aus P2 import P3.* // Import aller direkter Klassen aus P3 // nicht aber der Klassen aus P2, P1 !! © H.Neuendorf Keine automatische Sichtbarkeit zwischen inneren und äußeren Paketen ! (74) Pakete und Verzeichnisse - Regeln bei inneren Klassen Eine public top-level Klasse kann auch eine oder mehrere public innere Klassen haben. Diese können wahlweise importiert werden. Nicht statische innere Klassen jedoch nur, wenn auch äußere Klasse importiert wird. Erzeugung von inneren Instanzen folgt jedoch besonderer Syntax package myPack ; public class Outer { public int a; package newPack ; import myPack.Outer ; import myPack.Outer.Inner1 ; import myPack.Outer.Inner2 ; public class Inner1 { // …….. } public static class Inner2 { class C10 { Outer obj = new Outer( ) ; Inner1 drin1 = obj.new Inner1( ) ; // …….. } } class C2 { /* … */ } Inner2 drin2 = new Inner2( ); // …………….. } Abfrage von Pakteeigenschaften im Coding mittels java.lang.Package : Package p = Package.getPackage( "Paketname" ); © H.Neuendorf p.getName() ; // ….. (75) Pakete und Verzeichnisse in Eclipse Jede Klasse ist einem Package zugeordnet Default ist das default package = namenloses Standardpaket Neues Package anlegen : Eclipse legt pro Package Ordner mit allen Klassen an : Einbinden von IO in Eclipse-Projekte ohne physische Kopie : 1. IO.class als ZIP- oder JAR-File Zentral ablegen – zB Workspace-Ordner 2. Menü Project → Properties → Java Buid Path 3. Tabreiter Libraries wählen 4. Button Add External JARs … 5. Auf ZIP- / JAR-File mit IO.class verweisen 6. OK Referenz kann natürlich nachträglich wieder entfernt oder geändert werden …. © H.Neuendorf Zugriffsrechte auf Klassen in Java : public private protected Zugriffsrechte : public int x ; public : public void m( ) {...} In allen Klassen des eigenen Pakets sichtbar + In allen anderen Paketen, die die Klasse importieren protected int x ; protected void m( ) {...} protected : In Unterklassen der Klasse desselben Pakets + in Unterklassen der Klasse in anderen Paketen + in Fremdklassen desselben Pakets sichtbar ( Anders als in C++ definiert !! ) int x ; void m( ) {...} [ Keine Angabe ] Zugriff "package" (kein Schlüsselwort) : In allen Klassen des Pakets sichtbar, nicht in allen anderen Paketen private int x ; private void m( ) {...} private : Nur in eigener Klasse selbst sichtbar, nirgendwo sonst Zugriff durch auf Klasse mit Attribut: eigene Klasse Unterklasse fremde Klasse FremdPackage private X package X X X (gleiches Paket) protected X X X (gl. Paket) X (U-Kl.) public X X X X © H.Neuendorf (76) Zugriffsrecht protected in Java : Paket P1 Anders als C++ da mit Java-Paketkonzept verbunden (77) Paket P2 Oberklasse O : protected void m() In C++ werden durch protected die erbenden Unterklassen gegenüber den nicht erbenden Fremdklassen zugriffsmäßig privilegiert … Unterklasse U2 import P1.*; Unterklasse 1 Fremdklasse 1 Java : Paket stellt KlassenKomponenten zum Überschreiben in Unterklassen zur Verfügung Fremdklasse 2 Klasse Oberklasse O aus P1 habe ein protected Element m() Nur die nicht erbende Fremdklasse2 hat keinen Zugriff auf protected-Elemente der Oberklasse O aus P1 Zugriff auf m() jedoch nur im Coding der Unterklasse 2 : package P2 ; import P1.O ; Aber nicht auf deren Objekten : class U2 extends O { U2 u = new U2( ) ; u.m( ) ; © H.Neuendorf // Fehler !! Gilt auch für protected static Attribute + Methoden : Außerhalb Unterklasse auch auf Klassennamen nicht sichtbar ! public void test( ) { } m( ) ; } // ok! (78) Modifizierer von Klassen, Attributen, Methoden Nicht alle Modifizierer auf alles anwendbar : Modifizierer public Klasse Methode Konstruktor X X X protected X X X private X X X X X X X static X (IF) Attribut (X innere) final X abstract X (IF) IF-Methoden müssen stets public + abstract sein IF-Attribute müssen stets public + static + final sein X strictfp X native X dh : Sind es automatisch, auch wenn nicht explizit vermerkt ….. ........... Bei public- Paketklassen gilt noch viel schärfer als bei paket-internen nicht-public-Klassen das Prinzip : Keine public-Attribute sondern nur public-Zugriffsmethoden (Setter / Getter) ⇒ Nur dadurch bewahrt man sich die Flexibilität, die innere Datenrepräsentation der Klasse später noch verändern / anpassen zu können …. © H.Neuendorf (79) Dokumentation von Schnittstellen Interfaces, Klassen, Methoden, Attribute → Spezifikation von Funktionalität + Bedeutung JDK-Unterstützung → Tool : javadoc *.java java.sun.com/javase/6/docs/technotes/tools/ javadoc [options] {filename | packageName} Filenamen oder Paketname Optionen : -public -private Nur Dok zu public members Auch Dok zu private members -package Dok zu package, public, protected members -overview <file> -d path Überblicks-Doku zu Paket aus File einlesen Zielpfad für erzeugte Dateien ........... (u.v.a.m.) ⇒ HTML-Files mit Schnittstellendarstellung + Kommentaren aus Quellcode HTML auch direkt in DK-Text verwendbar In vorgebbaren Ordner angelegt /** ... */ Regeln für Dokkommentare (DK) : 1. Jeder DK beschreibt Bezeichner dessen Deklaration unmittelbar folgt 2. Der erste Satz des DK ist Zusammenfassung für den Bezeichner = Text bis erster Punkt mit Leerschritt 3. *-Zeichen am Anfang von DK-Zeilen werden ignoriert 4. Nur DKs direkt vor Interfaces, Klassen, Methoden und Attributen werden verarbeitet 5. Wenn geerbte Methode (zB aus Interface !) keinen eigenen DK bekommt, erbt sie DK des Obertyps 6. Wenn geerbte Methode sowohl aus Oberklasse als auch aus Interface DKs erbt, wird IF-DK verwendet © H.Neuendorf (80) Dokumentation von Schnittstellen : Annotationen @ /** Java-Doc-Tags : ... */ (u.a.) @param Spezikation einzelner Parameter. Erstes Wort ist Parametername, Rest seine Beschreibung @return Rückgabewert der Methode @author Autorenangabe @version Beliebige Versionsangabe java.sun.com/javase/6/docs/technotes/tools/ @throws Beschreibt Ausnahmebehandlung @deprecated Markiert als veraltet. Compiler markiert Feld als veraltet + erzeugt Warnung @see Querverweis auf andere Javadoc-Docu oder andere Files in doc-files Verzeichnis Interface / Klasse aus aktuellem Paket unqualifiziert angebbar, Typen anderer Paketen mit voll qualifizierten Namen anzusprechen. Attribute und Methoden werden mit # vor ihrem Namen angesprochen. Bsp: @see meineMethode(String, Object) @see java.lang.String @see java.lang.Math#PI @see <a href="doc-files/spec.html#attr>Attribute Spezifikation</a>" @see Made by Magic AG <img src="doc-files/magiclogo.gif"> {@link} Analog @see aber für Verweise innerhalb DK-Text Bsp: Ändert Aufrufwert auf {@link #getValue} wenn nötig. {@code} Erläuternde Code-Auszüge Bsp: © H.Neuendorf @throws IndexOutOfBoundsException bzgl. Index. ( {@code index <0 || index>=size()} ) (81) Java API - Dokumentation Dokumentation JDK auf Sun-Homepage : (einsehen oder downloaden) http://java.sun.com/javase/6/docs/api/ http://java.sun.com Æ Popular Downloads Æ Java SE 6 Documentation Æ Download In Eclipse : Namen markieren Kontextsensitiv - Positionieren auf ein Element + Navigate-Menü : zeigt API-Doku an, sofern JDK installiert © H.Neuendorf Strg + Leertaste (82) JavaDoc und JAR mit Eclipse Alle Tools auch direkt mit diversen Optionen auf Kommandozeile ausführbar JavaDoc 1. Ordner für die durch JavaDoc erstellten Files anlegen 2. Menü: Project → Properties → JavaDoc Location mittels Browse auf angelegten Ordner verweisen 3. Aufruf von JavaDoc : Project → Generate JavaDoc Alle JavaDoc-Optionen einstellbar Alternativ über Kontextmenü : Export → Java → Javadoc Kommandozeilenaufruf : jar cvf Demo.jar *.class JAR-Files Für geöffnetes Projekt Kontextmenü : Export → Java → JAR File File Demo.jar wird erstellt. Enthält alle .class-Files aus aktuellem Verzeichnis Im Wizzard alle Einstellungen bzgl. Ablageort und Inhalt (Manifest-Datei !) einstellbar Erzeugtes .jar-File mit Zip-Programmen einsehbar Ein .jar-File mit Manifest-Eintrag für main()-Klasse kann direkt ausgeführt werden Aufruf Launcher java mit Option jar auf Kommandozeile : Vorteil jar : C:\> java –jar Test.jar ↵ Organisation & Kompression Weitergabe eines ganzen Projekts als ein File statt in Vielzahl von Files Remote-Aufruf: Alle .class-Files lokal verfügbar - nicht einzeln übers Netz zu laden (Bsp → Applets) © H.Neuendorf Weitere Tools jps …. (83) im bin-Ordner der JDK-Installation (JVM Process Status Tool) Aufruf auf Kommandozeile → Anzeige laufender Java-Instanzen mit ProcessID jps jmap (JVM Memory Map) Aufruf auf Kommandozeile → Anzahl + Speicherverbrauch aller Objekte zu bestimmter PID jmap –histo 5460 jstack (JVM Runtime Stack) Aufruf auf Kommandozeile → Anzeige aller laufenden Threads und deren (Warte-)Zustand (Full Thread Dump) jstack 5460 jstat (JVM Statistics Monitoring Tool) Option –help zur ersten Orientierung Ausführliche Dokumentation aller Tools und ihrer Aufrufoptionen in JDK-Doku : java.sun.com/javase/6/docs/technotes/tools/ Aufruf auf Kommandozeile → Abfrage von Performance-Statistiken jstat –gcutil 4176 Kapselung von Java-Archiven in komprimiertes direkt ausführbares Programm für Windows, Linux, Mac, Solaris : launch4j http://launch4j.sourceforge.net/ © H.Neuendorf Weitere Tools (bin-Ordner der JDK-Installation) jconsole (JVM Management Konsole) Aufruf auf Kommandozeile → Prozess-Management-Konsole für wählbaren Java-Prozess © H.Neuendorf (84) (86) Klassen + Interfaces des Pakets java.lang der Java SE Alles was man grundsätzlich braucht, um JavaProgramme zu schreiben + ihre Fehler zu behandeln ..... java.lang Systemklassen System Runtime RuntimePermission Process ProcessBuilder Thread ThreadGroup ThreadLocal<T> InheritableThreadLocal<T> Thread.State ClassLoader SecurityManager Class<T> Compiler Package StackTraceElement Wrapper Boolean Byte Character Character.Subset Character.UnicodeBlock Double Float Integer Long Short Number Wichtigste Informationsquelle → API-Dokumentation von Sun : http://java.sun.com/javase/6/docs/api/ © H.Neuendorf Ausnahmebehandlung Throwable Exception NullPointerException ArrayIndexOutOfBounds-Exception ClassCastException ClassNotFoundException NumberFormatException SecurityException Error InternalError NoClassDefFoundException Sonstiges Object Math StrictMath String StringBuffer StringBuilder Void Enum ... und viele weitere ... Interfaces Appendable CharSequence Cloneable Comparable<T> Iterable<T> Readable Runnable Thread.UncaughtExceptionHandler Wichtiges Package : Wegen elementarer Bedeutung automatisch importiert ⇒ Kein explizites: import java.lang.* zur Verwendung erforderlich ! (87) Java SE - Fundamentale Pakete Java Standard Edition Basis java.lang java.lang.instrument java.lang.reflect java.util java.util.concurrent java.util.jar java.util.zip java.util.regex java.math javax.management Text java.text javax.swing.text javax.swing.text.html javax.swing.text.rtf Grafik java.awt java.awt.color java.awt.dnd java.awt.event java.awt.image java.awt.font java.awt.geom javax.swing javax.swing.event Komponenten java.beans java.lang.reflect ... und zahlreiche weitere spezielle Pakete ... © H.Neuendorf Ein-/ Ausgabe java.io jawa.awt.print java.awt.im javax.imageio javax.print javax.sound.midi Daten / SQL java.sql javax.sql javax.sql.rowset java.awt.datatransfer javax.crypto Netzwerk/ Web java.net javax.net java.nio java.applet java.rmi javax.rmi javax.rmi.ssl java.rmi.server java.security java.security.cert javax.naming XML javax.xml javax.xml.parsers javax.xml.transform javax.xml.validation javax.stax javax.xml.xpath org.w3c.dom org.xml.sax Java Enterprise Edition - Fundamentale Pakete Java Enterprise Edition Basiert aufJava SE ! Java Standard = Java SE 6 Servlets / JSP javax.servlet javax.servlet.jsp javax.servlet.http Einsatz auf Applikationsservern : Steuerung transaktionaler Client-ServerBusiness-Prozesse am Backend Enterprise Java Beans javax.ejb Grundlegende Überarbeitung durch Version EJB 3.0 ab Java EE 5 Aktuell : Java EE 6 Kein Produkt, sondern Spezifikation, die Applikations-Server erfüllen muss. Deutliche Vereinfachung der EJBImplementierung und Verteilung. Hersteller von Java EE-Servern müssen Schnittstellen implementieren, um von SUN zertifiziert zu werden. Mail javax.mail javax.mail.event javax.activation Message Queues javax.jms Sun © liefert freie Referenzimplementation : → java.sun.com/javaee/ OpenSource: GlassFish v3 OpenSource J2EE-Server JBoss : → www.jboss.com/downloads/index © H.Neuendorf (89) Kommunikation javax.ressource javax.xml.rpc javax.xml.rpc.handler javax.xml.rpc.server javax.xml.rpc.soap javax.xml.soap Datenzugriff javax.transaction javax.xml.namespace javax.xml.parsers javax.xml.rpc javax.xml.registry javax.xml.transform javax.xml.transform.dom javax.xml.sax management javax.management ... und weitere spezielle Pakete ... (90) Java Micro Edition - Fundamentale Pakete Einsatz der Java ME auf Geräten mit geringer Hardwareausstattung : Handys, Palmtops, Smartphones, Embedded Systems, ........ Java Micro Edition Basis Ein-/ Ausgabe java.lang java.util java.io Spezial-APIs javax.microedition.io javax.microedition.lcdui javax.microedition.rms Erweiterungen im Bereich GUI, Netz, ........ Basiert auf Miniversion der virtuellen Maschine = KVM Noch manches im Fluss ........ J2ME Wireless Toolkit = Umgebung KToolbar, Emulatoren, J2ME, Dokumentation Quelle: © H.Neuendorf java.sun.com /javame (91) Nützliche Klassen aus Java SE-Paketen Mathematische Funktionen und Konstanten Klasse : java.lang.Math java.lang.StrictMath Nur statische Konstanten und Methoden Gleiche Methoden wie in Math – aber als strictfp gekennzeichnet ⇒ Strenge Gleitkommaarithmetik garantiert reproduzierbare Ergebnisse Konstanten : Math.E Math.PI Methoden : teilweise überladene Fassungen für verschiedene primitive Datentypen abs() acos() asin() atan() atan2() cbrt() ceil() hypot() log() log10() log1p() max() min() pow() random() rint() sinh() sqrt() tan() tanh() toDegrees() cos() cosh() exp() floor() round() signum() sin() toRadians() ... Definitionen siehe JDK-Doku // import java.lang.Math; nicht erforderlich, da aus Standardpaket java.lang // import static java.lang.Math; // evtl. hilfreich – neues Feature in 5.0 public class Mathe { public static void main( String[] args) { IO.writeln( "Pi = " + Math.PI + " e = " + Math.E ); double d = Math.max( Math.PI, Math.E ); IO.writeln( "Cosinus(Pi) = " + Math.cos( Math.PI) ); IO.writeln( "Kubikwurzel von 27 = " + Math.cbrt(27) ); } } © H.Neuendorf expm1() (92) Nützliche Klassen: Zufallszahlen Klasse: import java.util.Random ; java.util.Random Erzeugung von Zufallszahlen Konstruktoren class Lotto { Random() Random ( long seed ) Ganzzahlige Zufallszahl im gesamten Wertebereich von int oder long : int nextInt() public static void main( String[] args) { int zahl ; Random generator = new Random( ) ; long nextLong() Zufallszahl zwischen 0 (incl) und n (excl) : zahl = generator.nextInt( 50 ) ; // liefert Zahlen im Bereich 0 bis 49 ! int nextInt( n ) Boolean : boolean nextBoolean() } Fließkomma-Zufallszahl im Wertebereich von } 0.0(f) bis excl. 1.0(f) : float nextFloat() Initialisieren des Generators: double nextDouble() Random( ) ; Gaußverteilte Fließkomma-Zufallszahlen zentriert um 0.0 mit SdAbw. 1.0 : nextGaussian() © H.Neuendorf → liefert stets andere Folge von Zahlen! Random( long seed ) ; Ändern des Startwerts des Generators void setSeed( long seed ) → intern mittels Systemzeit …… → explizit mittels Wert seed → liefert stets gleiche Folge von Zahlen! (93) Stoppuhr / Timer // import java.lang.* ; Klasse: java.lang.System Methode: System.currentTimeMillis( ) ; Anzahl Millisekunden seit 1.1.1970 als long-Wert public class Timer { public static void main( String[] args) { long start ; long end ; start = System.currentTimeMillis( ) ; Verstrichene Zeit durch Differenzbildung zweier Aufrufe int test = 0 ; for ( int i = 1; i<100000; i++ ) { test = test + i ; } Anwendung Testen von Algorithmen Evaluieren in welchen Programmteilen die meiste Zeit verbraucht wird end = System.currentTimeMillis( ) ; In J2SE 5.0 zusätzlich die Methode : long dauer = end - start ; System.nanoTime( ) ; Liefert Zeitwert des genauesten Systemzeitgebers in Nanosekunden. Allerdings Nanosekundengenauigkeit sicherlich nicht erreicht ..... © H.Neuendorf IO.writeln( "Dauer [ms] = " + dauer ) ; } } (94) Darstellung von Dezimalzahlen Klasse java.text.DecimalFormat 0 Ziffer, führende Nullen werden angezeigt . Dezimalpunkt (landesspezifisch) , Tausendertrennung (landesspezifisch) % Darstellung als Prozentzahl E Trennt Platzhalter für Mantisse und Exponent Alle anderen Zeichen werden direkt in den formatierten String übernommen ! Methoden : String format( double x ) String format( long x ) Gibt Inhalt von x als gemäß Pattern-Vorgabe formatierten String zurück. © H.Neuendorf } # Ziffer, führende Nullen werden nicht angezeigt import java.text.DecimalFormat ; class Format { public static void main( String[] args) { Erzeugt ein Objekt mit dem durch pattern vorgegebenen Format. String pattern kann sich zusammensetzen aus Platzhaltern für : } DecimalFormat( String pattern ) IO.writeln( s ) ; // Ausgabe ist 24.522,46 Konstruktor : DecimalFormat f = new DecimalFormat( "###,##0.00" ) ; String s = f.format( 24522.4567 ) ; Formatierung von Dezimalzahlen vor Ausgabe Darstellung von Dezimalzahlen java.text.DecimalFormat import java.text.DecimalFormat ; public class Format { public static void main( String[] args ) { DecimalFormat f1 = new DecimalFormat( "###,###.##" ) ; DecimalFormat f2 = new DecimalFormat( "Wert: 000,000.00000 Euro" ) ; DecimalFormat f3 = new DecimalFormat( "Prozente = ###.## %" ) ; DecimalFormat f4 = new DecimalFormat( "#.#E000" ) ; String s = f1.format( 24522.4567 ) ; IO.writeln( s ); s = f2.format( 98.765 ) ; IO.writeln( s ) ; s = f1.format( 98.765 ) ; IO.writeln( s ) ; s = f3.format( 55.123456 ) ; IO.writeln( s ) ; Ausgabe : 24.522,46 s = f4.format( 0.123456789 ) ; IO.writeln( s ) ; ausgabe( f4, 4568.56 ) ; Wert: 000.098,76500 Euro 98,77 } Prozente = 5512,35 % // Generische Methode zur formatierten Ausgabe : 1,2E-001 public static void ausgabe( DecimalFormat f, double d ) { IO.writeln( f.format( d ) ) ; } } © H.Neuendorf 4,6E003 (95) (96) Zerlegen von Strings Klasse java.util.StringTokenizer import java.util.StringTokenizer ; Zerlegung von Zeichenketten in definierte Einheiten = class Tokens { Token Definition der Tokens : Was soll als Trennzeichen zwischen zwei Tokens betrachtet werden ? public static void main( String[] args ) { Konstruktoren : String ein = "HalloÖihrÖBA-Öler" ; String trenn = "Ö" ; StringTokenizer( String eingabestring ) ; ⇒ Leerzeichen als Trennzeichen StringTokenizer st = new StringTokenizer( ein, trenn ); StringTokenizer( String eingabestring, String trennstring ) ; ⇒ trennstring als Trennzeichen int n = st.countTokens( ) ; IO.writeln( "Tokenanzahl = " + n ) ; Zugriff auf Tokens : (u.a.) Anzahl von Tokens, die noch im StringTokenizer-Objekt vorhanden sind : while( st.hasMoreTokens( ) ) { IO.writeln( st.nextToken( ) ); } int countTokens( ) ; Abfrage, ob noch Tokens vorhanden sind : boolean hasMoreTokens( ) ; } } Abgreifen des nächsten Tokens : String nextToken( ) ; © H.Neuendorf Wenn keine mehr vorhanden : NoSuchElementException Systemzugriff (97) java.lang.System Kapselt Funktionen der JVM Zugriff auf Standard-Ein- und -Ausgabe, Umgebungsvariablen, ... Von System können keine Objecte erzeugt werden - nur statische Klassenmethoden import java.util.Properties ; public class SysInfo { public static void main( String[] args ) throws Exception { // alle auf einmal ausgeben: Properties p = System.getProperties( ) ; p.list( System.out ) ; p.storeToXML( System.out, "Kommentar" ); void gc( ) Bittet JVM, Garbadge Collector zu starten // Spezielle Property : String key = "java.version" ; String prop = System.getProperty( key ) ; IO.writeln( prop ) ; void exit( int status ) Beendigung der laufenden JVM (und damit des Programms) mit dem Exit-Code status Properties getProperties( ) String getProperty( String key ) Abfrage der System-Properties, falls Zugriff darauf erlaubt Key ist Schlüssel der Properties - auch ausgegeben, wenn man über Property-Objekt alle zugänglichen Properties ausgibt. © H.Neuendorf } } Bsp : java.version java.vm.version java.class.version java.io.tmpdir os.name file.separator user.name java.vendor java.vm.vendor java.class.path java.compiler os.arch path.separator user.home java.home java.vm.name java.library.path java.ext.dirs os.version line.separator user.dir Browser / Editor / E-mail-Client aufrufen : Plattformunahhängige Programmierung des Starts von Applikationen / Viewern, die auf System bestimmten Dokumenten zugeordnet sind. Methoden der Klasse Desktop : class DesktopTest { public static void main( String[] args ) throws Exception mail( URI mailto) edit( File file ) ……….. © H.Neuendorf { Desktop myDesktop = Desktop.getDesktop( ) ; myDesktop.browse( new URI( "www.amazon.de" ) ) ; // öffnet Standardbrowser mit Webseite // ……… mail() print( File file ) (98) (seit Java 6) import java.awt.Desktop; import java.net.URI; import java.io.File; browse( URI uri ) open( File file ) java.awt.Desktop } } Systemzugriff : Start von Fremdprozessen + Abfrage Speichergröße : Runtime getRuntime( ) Liefert Runtime-Objekt für laufende JVM long totalMemory( ) Liefert Größe des Systemspeichers in Bytes Process p = rt.exec( "C:\\Windows\\notepad.exe" ) ; IO.promptAndReadString( "Drücke Taste!" ) ; p.destroy( ) ; Process exec( String cmd ) Vollständige Pfadangabe des aufzurufenden Programms/ System-Kommandos incl Parametern Klasse Process repräsentiert den gestarteten Prozess : void destroy( ) Beenden des gest. Prozesses © H.Neuendorf (99) class CTest { // Alles in java.lang public static void main( String[] args ) throws Exception { Runtime rt = Runtime.getRuntime( ) ; IO.writeln("Systemspeicher = " + rt.totalMemory( ) ) ; IO.writeln("Freier Speicher = " + rt.freeMemory( ) ) ; long freeMemory( ) Liefert Anzahl nichtbelegter Bytes Voraussetzung: Ausreichende Rechte !! java.lang.Runtime String[] a = new String[2] ; a[0] = "Hallo" ; a[1] = "ihr da"; Prog.main(a) ; // Prog.class reicht aus ! } } Aus einem Java-Programm können beliebig viele andere Java-Programme aufgerufen und String-Array als Parameter für main() mitgegeben werden : <Programmname>.main(args); Systemzugriff (100) java.lang.ProcessBuilder Start von Fremdprozessen mittels ProcessBuilder : ProcessBuilder( List<String> command ) ProcessBuilder( String ... command ) Erzeugen ProcessBuilder-Objekt mit Angabe Programmnamen und variabler Zahl von Aufrufargumenten ProcessBuilder command( List<String> command ) ProcessBuilder command( String ... command ) Setzen des Programmnamens und variabler Zahl von Aufrufargumenten List< String > command() Liefert gesetzten Programmnamen und Argumente als String-List ProcessBuilder directory( File directory) Setzen des Arbeitsverzeichnisses File directory( ) Liefert Arbeitsverzeichnis Auch bei ProcessBuilder repräsentiert die Klasse Process den gestarteten Prozess : void destroy( ) Beenden des gestarteten Prozesses Map< String, String > environment() Liefert betriebssystemspezifische Umgebungwerte des ProcessBuilders Process start() Start des Prozesses ... und weitere Methoden, zur Festlegung der Ausgabe von Fehlermeldungen ... © H.Neuendorf Systemzugriff (101) java.lang.ProcessBuilder Aufruf des notepad-Editors unter Windows mit zwei abgelegten Textfiles .... import java.io.File; public class Prozesse { public static void main( String[] args) throws Exception { ProcessBuilder pB = new ProcessBuilder( "notepad", "Demo1.txt" ) ; pB.directory( new File( "C:/WINNT/" ) ) ; IO.writeln( pB.command() ) ; IO.writeln( pB.directory() ) ; IO.writeln( "Umgebungsinfo: \n" + pB.environment().toString() ) ; Process p1 = pB.start() ; pB.command( "notepad", "Demo2.txt" ) ; // Für ProcessBuilder-Objekte darf start() mehrfach aufgerufen werden: Process p2 = pB.start() ; IO.promptAndReadString( "Drücke Taste!" ) ; p1.destroy() ; p2.destroy() ; } } Ausgabe: (nur teilweise .....) [notepad, Demo1.txt] C:\WINNT Umgebungsinfo: {PROCESSOR_ARCHITECTURE=x86, LOGONSERVER= // und noch vieles mehr ................ © H.Neuendorf