(74) Übergang zur Schnittstellenvererbung Einfachvererbung versus Mehrfachvererbung Interface = Schnittstellenspezifikation Mehrfachvererbung bei Interfaces Verwendungsregeln Polymorphie / Entkopplung / Design mit Interfaces Annotationen Bis Java 7 blieben Interfaces stabil. Java 8 führt dort (leider) Implementierungsaspekte ein … © H.Neuendorf (s.u.) Warum keine Klassen-Mehrfachvererbung ? Java : Klassen-Einfachvererbung - jede Unterklasse hat nur eine direkte Oberklasse Realität : Auch Mehrfachvererbung Kind → (Vater + Mutter) (75) aber … Mehrfachklassenvererbung macht logische + technische Strukturen komplexer / schwieriger Erhöhter Speicherbedarf + Verwaltungsaufwand für JVM → VMT ( virtual method tables ) Rautenproblem der OO : Basis Mehrfachvererbung für Klasse Unter_2 : m() Erbt m() und a "zweimal" : Über Klasse Unter_1a und über Unter_1b a Welcher Weg ist gültig ? Unter_1a Unter_1b m() m() m() in Unter_1a und Unter_1b überschreibbar ! Welche Variante bei Aufruf aus Unter_2 verwenden ? ⇓ Lösung : Keine Mehrfachvererbung Unter_2 ?? © H.Neuendorf Interface-Konzept ! Interface = Schnittstellenspezifikation (76) In Idealform enthält ein Interface ausschließlich abstrakte Methoden - basta !! (In Java können jedoch (leider?!) auch weitere Bestandteile aufgenommen werden …) Ein Interface definiert eine Schnittstelle = Typ - nicht seine Implementierung ! Liefert Spezifikation von Fähigkeiten / Verhaltensweisen = Vertrag Interfaces geben Verhalten (=Methoden) vor - keinen Zustand ! Denn sie enthalten keine nicht-statischen Attribute 1. Interfaces können von mehreren Interfaces erben ⇒ extends Mehrere Schnittstellenbeschreibungen können in einem Interface zusammengefasst werden 2. Klassen können mehrere Interfaces implementieren : Neues Schlüsselwort implements 3. Jede implementierende Klasse ist typkompatibel zu Interface ! Somit können ihre Objekte überall dort eingesetzt werden, wo der IF-Typ gefordert ist ! © H.Neuendorf (77) Interface = Schnittstellenspezifikation Inhalt bis incl. Java 7 : interface Konto { public static final double MIN = 10.0 ; Abstrakte Methoden + Statische Konstanten public abstract void einzahlen( double betrag ) ; public abstract void abheben( double betrag ) ; Alle Methoden public abstract Alle Attribute public static final } class Sparkonto implements Konto { private double saldo ; public Sparkonto( ) { /*…*/ } Schlüsselwörter public, abstract, static final müssen nicht explizit angegeben werden … public double getSaldo( ) { return saldo ; } public void einzahlen( double betrag ) { … vom Compiler automatisch gesetzt. saldo = saldo + betrag ; } Konstanten - müssen im IF initialisiert werden ! public void abheben( double betrag ) { if( saldo - betrag > MIN ) saldo = saldo - betrag ; Implementierte Methoden müssen public sein ! Eine vom IF erbende Klasse, die nicht alle Interfacemethoden implementiert, enthält abstrakte Methoden + ist somit abstract ! © H.Neuendorf else /* … */ } } Nachträgliche Änderungen an Interfaces erzwingen Anpassungen in allen implementierenden Klassen !! IF - Strukturierung + Modellierung Modellierung Mehrfachvererbung durch Interface-Hierarchie : extends (78) Fahrzeugantrieb Diesel Elektro Hybrid Interfaces erlauben Modellierung komplexer Zusammenhänge : → Anforderungs-Spezifikation IF-Hierarchie bildet Zusammenhang ab Keine Implementierung von Methoden - aber Modellierung + Strukturierung Klassen, die IF implementieren, übernehmen dadurch Strukturierung des Sachverhalts Reihenfolge - erst Erben / dann Implementieren : class A extends B implements IFC, IFD { /* … */ } © H.Neuendorf interface Fahrzeugantrieb { int getLeistung( ) ; int getGewicht( ) ; } interface Diesel extends Fahrzeugantrieb { double getHubraum( ) ; double getVerbrauch( ) ; } interface Elektro extends Fahrzeugantrieb { double getBatteriekapazität( ) ; } interface Hybrid extends Diesel , Elektro { String getKopplungsart( ) ; } class HybridAntrieb implements Hybrid { public int getLeistung( ) { … } public double getBatteriekapazität( ) { … } // … } Interfaces - Strukturierung + Modellierung Schnittstellenvererbung (79) Interfaces : statt Spezifikation + Modellierung Implementierungsvererbung Abstrakte Vertragsdefinition ⇓ Strukturierung von Software ! Klassen : Konkrete Implementierung Interfaces können nicht instanziiert werden Sollen durch implementierende Klasse konkretisiert werden Extreme Form : Marker-Interfaces Ohne jeden Inhalt - Vertrag nur durch ihre Dokumentation festgelegt Unterschied Interface - abstrakte Klasse : 1. Abstrakte Klassen können Implementierung enthalten 2. Abstrakte Klassen können private + finale Methoden enthalten 3. Klassen können nur von einer abstrakten Oberklasse erben Aber Klassen können beliebig viele Interfaces implementieren 4. IFs erlauben Mehrfachvererbung © H.Neuendorf Es wird kein Verhalten definiert - dh keine Methoden + Konstanten enthalten. Vertrag befindet sich in Dokumentation, die die Erwartungen beschreibt, die implememtierende Klasse erfüllen sollte Bsp : Serializable, Cloneable … Interfaces - Rautenprobleme Methoden – Regeln : (80) X (IF X,Y) Gleicher Methodenname, gleicher oder verschiedener Rückgabetyp, Y Z unterschiedliche Parameterlisten : Klasse Z muss entsprechend viele gleichnamige überladene Methoden implementieren Gleicher Methodenname, gleicher Rückgabetyp, gleiche Parameterlisten : Klasse Z muss nur diese eine Methode implementieren Gleicher Methodenname, gleiche Parameterlisten unterschiedlicher Rückgabetyp : Nicht erlaubt - Compilerfehler ! Identische Konstanten-Namen - Regeln : Konflikt durch Qualifikation des Interface-Namens aufgelöst interface Skat { int kartenZahl = 32 ; } Sinn der Konstanten: Vereinheitlichung der späteren Implementierungen durch Bezug auf einheitliche (z.B. n.w.) Konstanten // IF-Konstanten auch in nicht interface Poker { int kartenZahl = 52 ; } // implementierenden Klassen verwendbar : class Z implements Skat, Poker { class A { /* …*/ Skat.kartenZahl /* …*/ Skat.kartenZahl /*…*/ /*… */ Poker.kartenZahl /*…*/ /*… */ Poker.kartenZahl /*…*/ } © H.Neuendorf /*…*/ } Programmieren mit Interfaces (81) 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 implementierenden Klasse ! ⇒ Interfaces dürfen keine Konstruktoren vorschreiben ! Methoden können nicht final sein → 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 mit throws-Klausel Exception-Verhalten beschreiben Interface-Konstanten können Objekte sein … aber kein guter Stil, da dadurch IF von konkreter Implementierung abhängig class A { /* … */ } interface IFEuro { public static final A a = new A( ) ; public abstract double inDM( double euros ) throws MyException ; } © H.Neuendorf (83) Interfaces nicht als Konstantendeponie ! interface PhysicalConstants { public static final double NA = 6.022e23 ; package science ; // Bessere Lösung ! public final class PhysicalConstants { // Tool 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 Klasse sein. Bei Implementierung eines entsprechenden "Konstanten-Interfaces" werden die Konstanten jedoch Teil der öffentlichen Schnittstelle der implementierenden Klasse ("implementation details leak into the class's exported API"). Verwirrt Benutzer der Klasse, da er keinen Sinn in Werten sieht, die nur intern benötigt werden. Die Schnittstelle der Klasse und ihrer Unterklassen wird mit den IF-Konstanten verschmutzt. } import science.PhysicalConstants ; Nichtinstanziierbare Tool-Klasse ist bessere Lösung ! class Test { public double atoms( double mol ) { return PhysicalConstants.NA * mol ; } // … } Interfaces sollten verwendet werden, um Typen + Schnittstellen zu definieren ! Sie sollten nicht dem bloßen Export von Konstanten dienen ! © H.Neuendorf Interfaces : Entkopplung : Bedeutung als Entkoppler + Vermittler (84) durch Interfaces als Mittel der Abstraktion Interne Implementierungen interessieren nicht – nur die Aufruf-Schnittstellen ! Interface als Mittler zwischen Aufrufer (Client) und Gerufenem (Server) : Klasse A Klasse B Klasse A bildet mit Interface I eine syntaktisch korrekte kompilierbare Einheit – unabhängig von konkreter Implementierung der Klasse B Klasse B bildet mit Interface I eine syntaktisch korrekte kompilierbare Einheit – unabhängig von konkreten Aufrufen durch Klasse A Client Interface I Server Durch Interface I wurden Klasse A und Klasse B voneinander entkoppelt : Implementierung B ist austauschbar Indirekter Zugriff durch A Erst in der Anwendung werden die Typen miteinander verbunden - Einsetzen konkreter Objekte bei Aufrufen: I impl = new B( ); © H.Neuendorf A a = new A( ); a.m( impl ); Interfaces definieren Vertrag zwischen Konsumenten und Produzenten Programmieren mit IFs Interface = Typ Auf diesen kann man sich bei MethodenImplementierung beziehen : Parameter, Rückgabewert, Variablen 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 & flexiblere Austauschbarkeit der Implementierung ! ⇓ Programmierung via Interfaces Konkrete Aufrufe mit Objekten der implementierenden Klasse(n) © H.Neuendorf (85) interface IEuroSF { public static final double kurs = 1.14 ; public abstract double inSF( double euroBetrag ) ; } ////////////////////////////////////////////////////////////////////////// class User { // "Client" public static double rechne( IEuroSF iE , double e ) { double s = iE.inSF( e ) ; return s ; Kennt Implementierer nicht ! } } /////////////////////////////////////////////////////////////////////////// class Eurorechner implements IEuroSF { // "Server" public double inSF( double euroBetrag ) { return euroBetrag * IFEuroSF.kurs ; } Kennt User nicht ! } ///////////////////////////////////////////////////////////////////////////// class IFTest { // "Anwendung" public static void main( String[ ] args ) { IEuroSF eR = new Eurorechner( ) ; // * double d = User.rechne( eR , 200 ) ; } } Interfaces + Design (86) << interface>> Form double flaeche() Interface definiert Typ - auf den sich System bezieht Interface beschreibt Konzept - nicht konkrete Impl. Klassen implementieren das IF = den Typ. Das aufrufende System ist nur von der Schnittstelle abhängig – muss dank Polymorphie die Klassen der beteiligten Objekte nicht kennen. Kreis Viereck Dreieck Einbau neuer System-Features : Prinzip : Entkopplung von Schnittstelle von Implementierung Nicht durch Modifikation existierenden Codes, sondern … Entkopplung des "Was" vom "Wie" … durch Erweiterung mittels neuer Klassen Beliebige konkrete Implementierungen verwendbar Verbesserung der Entwurfsqualität : System nicht von Implementierungsdetails abhänig, sondern nur von grundlegenden Konzepten. © H.Neuendorf Polymorphie - statt bedingtem Verhalten mittels switch-case-Konstrukten : Je nach übergebenem Objekttyp zeigt Methode anderes Verhalten – ohne dass dies in der Methode durch verschiedene Cases explizit behandelt werden müsste Interfaces + Design (87) << interface>> Form double flaeche() // OCP : Stabiles Coding // Auch bei weiteren Implementierungen des // IFs Form ergeben sich hier keine Änderungen ! class Geometrie { private Form[ ] container = new Form[ 100 ] ; Kreis Viereck Dreieck private int pos = 0 ; public void add( Form frm ) { container[ pos ] = frm ; pos++ ; } public void ausgabe( ) { for( int i=0; i<container.length; i++ ) { Form frm = container[ i ] ; Einbau neuer System-Features : Modifikationsfreie Erweiterung des Systems mittels neuer Klassen (zB: Raute, Ellipse …) ⇒ Veränderungen durch Hinzufügen neuen Codes statt durch Änderung bestehenden Codes if( frm != null ) IO.writeln( "Flaeche = " + frm.flaeche( ) ) ; } OpenClosedPrinciple OCP : Systeme sollen offen für Erweiterungen sein … } … aber abgeschlossen gegenüber Änderungen // … Stabilität + Flexibilität : Kein Änderungsbedarf am Großteil existierenden Codings } © H.Neuendorf Pattern : Interface – Abstrakte Klasse – Implementierende Klasse interface IF { m1( ) m3( ) (88) Formuliert Vertrag + Typ = Verhalten = Schnittstelle m2( ) m4( ) Framework : → Verlangt nach Typ IF m5( ) … } abstract class AbstractIF implements IF { // abstract public void m1( ) ; // abstract public void m2( ) ; Abstrakte skeletal-implementation-class 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 : class MyVersionOfIF extends AbstractIF { public void m1( ) { } Konkrete Klasse : Muss nur noch Teil der public void m2( ) { } Methoden implementieren bzw. zur public void m4( ) { } individuellen Anpassung überschreiben … Weniger mühsam, als direkt mit IF zu beginnen } © H.Neuendorf java.util Collection Framework IF Collection AK AbstractCollection AbstractList … Pattern : Interface zur Formulierung von Factories interface Konto { /* … */ } class Giro implements Konto { /* … */ } class Sparkonto implements Konto { /* … */ } class Bauspar implements Konto { /* … */ } // … und viele andere mehr … abstract class KontoFactory { public static Konto createKonto( String type ) { if( type.equals("G") ) return new Giro( /* … */ ) ; if( type.equals("S") ) return new Sparkonto( /* … */ ) ; if( type.equals("B") ) return new Bauspar( /* … */ ) ; // … } } Das Interface als allgemeiner Typ Konkrete Klassen, die alle den Interface-Typ spezifisch implementieren Eine abstrakte Factory-Klasse, die passende Objekte liefert, ohne dass Namen und die Existenz der dahinterstehenden Klassen bekannt sein müssten ! Vorteil von Factories : Flexibel anpassbares Erzeugen von Instanzen, ohne Details zu exponieren. class Anwendung { public static void main( String[ ] args ) { String type = IO.promptAndReadString( "Kontotyp (S/B/G/…) ? ") ; Konto k = KontoFactory.createKonto( type ) ; // Objekt verwenden … } } © H.Neuendorf (89) Anwendung, die sich auf das beschreibende Interface und die Dienste der Factory verlässt, ohne die dahinterstehenden implementierenden Klassen zu kennen ! Beispiel: Interfaces java.lang.Comparable (90) java.util.Comparator Vergleichsmethoden : compareTo( ) und compare( ) implementieren eine totale Ordnungsrelation mit gleicher Semantik a.compareTo( b ) compare( a, b ) <0 =0 >0 bedeutet: a<b a gleichgroß b a>b Absolutwerte <0 oder >0 egal – keine weitere Semantik einbauen ! Totale Ordnung : Allerdings unterschiedliche Signatur der Vergleichsmethoden : public interface Comparable { public interface Comparator { public int compare( Object o1, Object o2 ) ; public int compareTo( Object o ) ; } // … weitere Details hier unwichtig Alle Elemente einer Menge können aufgrund einer Größer-GleichRelation linear angeordnet werden } Implementierung von Comparable bei "natürlicher" Ordnung als Teil der Klassenlogik (zB Bruch, Integer, Complex) Implementierung von Comparator für weitere Sortierkriterien In Klasse - oder durch separate Comparator-Klassen : Definieren weitere Kriterien - davon evtl. beliebig viele … Ab Java5 generisch für Referenztyp T : interface Comparable<T> interface Comparator<T> © H.Neuendorf Beispiel: Interfaces java.lang.Comparable java.util.Comparator import java.util.* ; import java.util.*; class Mitarbeiter implements Comparable { class Sorted { public int persNr ; // eindeutige Ordnung (91) public static void main( String[ ] args ) { public String name ; Mitarbeiter[ ] m = new Mitarbeiter[4] ; public Mitarbeiter( int nr, String n ) { /* … */ } m[0] = new Mitarbeiter( 11, "Anna" ) ; // … m[1] = new Mitarbeiter( 5, "Ursula" ) ; public int compareTo( Object o ) { m[2] = new Mitarbeiter( 7, "Wolf" ) ; Mitarbeiter m = (Mitarbeiter) o ; m[3] = new Mitarbeiter( 13, "Bert" ) ; return this.persNr – m.persNr ; // Unsortierte Ausgabe: System.out.println( Arrays.toString( m ) ) ; } } // Sortieren nach compareTo() // Weiteres Sortierkriterium: name Arrays.sort( m ) ; class NameComparator implements Comparator { System.out.println( Arrays.toString( m ) ) ; public int compare( Object p, Object q ) { String s1 = ( (Mitarbeiter) p ).name ; // Sortieren nach NameComparator String s2 = ( (Mitarbeiter) q ).name ; Arrays.sort( m, new NameComparator( ) ) ; return s1.compareTo( s2 ) ; System.out.println( Arrays.toString( m ) ) ; } } } © H.Neuendorf } Strategy-Pattern → Inversion of Dependencies Dependeny-Inversion Principle : policy should not depend on details (92) (R.C. Martin) 1. High-level modules should not depend on low-level modules. Both should depend on abstractions. 2. Abstractions should not depend on details. Details should depend on Abstractions. High level modules contain the important policy decisions and business models of an application. Prinzip : Abhängigkeit von konkreten (und somit veränderlichen) Klassen minimieren Veränderliche Klassen hinter grundlegenden Interfaces verbergen Client gibt erforderliches Interface vor – dieses ist von tieferen Schichten zu implementieren Prinzip: Entwerfe ein System Top Down (gemäß Vorgaben der höheren Schichten), statt Bottom Up (gemäß Vorgaben der tieferen Schichten) Keine triviale Forderung – denn meist ist es historisch gewachsen anders: Quelle: R.C.Martin, Agile Software Development, S.128f Höhere Schichten nutzen "traditionellerweise" direkt die Services niedrigerer Schichten, rufen deren Methoden direkt auf, hängen somit von diesen direkt ab … Deshalb die Forderung nach Inversion (Umkehr) der "üblichen" Abhängigkeiten ! © H.Neuendorf Beispiel: Schalter & Lampe (93) Schalter (policy) und Lampe (detail) in traditioneller Abhängigkeit : Schalter-Klasse hängt direkt von Klasse Lampe ab ⇒ Schalter kann nur Lampen-Objekte schalten - keine anderen Geräte. Jede Änderung in Klasse Lampe beeinflusst Klasse Schalter. Die Policy (high level) hängt von den Details (low level implementation) ab. class Schalter { Schalter + schalte() Lampe private Lampe meineLampe ; + schalteEin() + schalteAus() public Schalter( Lampe lp ) { meineLampe = lp ; } Ziel : Inversion der Abhängigkeiten : public void schalte( ) { Schalter-Klasse schreibt den Devices das geforderte Verhalten durch ein Interface vor ⇒ if( /* … */ ) meineLampe.schalteEin( ) ; else meineLampe.schalteAus( ) ; Die Devices hängen auf diese Weise von den Forderungen des Clients ab ! } } Analogie : Manger in Zentrale bekommt aus vielen Filialen Reports in jeweils anderem Format. Muss sich auf diese einstellen. class Lampe { public void schalteEin( ) { /* … */ } "Dreht nun den Spieß um", indem er den Filialen ein festes Berichtsformat ("Formular" = IF) vorschreibt, das diese einhalten müssen. Dadurch ist auch die Hinzunahme weiterer Filialen kein Problem mehr, erfordert keine konzeptionelle Zusatzarbeit ! © H.Neuendorf public void schalteAus( ) { /* … */ } } (94) Beispiel: Schalter & Lampe Schalter Inversion der Abhängigkeiten : + schalte( ) Schalter-Klasse fordert Devices-Verhalten durch Interface << interface>> Schaltbar + schalteEin( ) + schalteAus( ) Devices hängen nun von Forderungen des Clients ab Schalter kann alle Geräte kontrollieren, die das Interface (grundlegende Verhaltensabstraktion) implementieren – auch noch gar nicht "Erfundene" Lampe Motor Deren Details spielen für Systemstruktur keine Rolle mehr Filter Systemstruktur viel flexibler Änderung in Klasse Lampe beeinflusst Klasse Schalter strukturell nicht. class Schalter { private Schaltbar meinServer ; public Schalter( Schaltbar s ) { meinServer = s ; } public void schalte( ) { if( /* … */ ) meinServer.schalteEin( ) ; else meinServer.schalteAus( ) ; } } class Lampe implements Schaltbar { public void schalteEin( ) { /* … */ } public void schalteAus( ) { /* … */ } } class Motor implements Schaltbar { public void schalteEin( ) { /* … */ } public void schalteAus( ) { /* … */ } } Das Interface erlaubt, beliebige weitere nutzende Clients (Trigger, Hebel, Taster …) zu definieren. © H.Neuendorf Erweiterung vs. Verfeinerung von Interfaces interface S1 { /* Methoden m1, m2 */ } (95) interface S extends S1, S2, S3 { /* … */ } interface S2 { /* Methoden m4, m5 */ } interface S3 { /* Methoden m3 */ } Typmäßige Detailsichten auf Gesamttyp class A implements S { /* … */ } Der Gesamttyp und seine Implementierung Interface kann komplex sein und zahlreiche Methoden enthalten class UserX { // nutzt nur m1,m2 S1 view1 = new A( ) ; view1.m1( ) ; view1.m2( ) ; } class UserY { // nutzt nur m3 S3 view3 = new A( ) ; view3.m3( ) ; } © H.Neuendorf Entwickler interessiert sich jedoch evtl. nur für einen Teil davon möchte die anderen Methoden typmäßig gerne ausblenden, so dass diese gar nicht aufrufbar sind Durch Detail-Interfaces ("Sichten") wird die Gesamtschnittstelle für verschiedene Entwickler aufgeteilt, so dass nur die benötigten Methoden sichtbar sind. Um die große Schnittstelle kümmern sich nur wenige Experten. Jede Sicht ist ein eigenes Interface; die gesamte Schnittstelle S ist eine Erweiterung aller Detail-Sichten - und kann bei Bedarf zusätzliche Methoden enthalten, die zu keiner Sicht gehören. Vorteile und Probleme von Interfaces (96) 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. 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 (97) 1. Interfaces fördern den modularen Aufbau von Systemen → Prinzip Separation of Concerns unterstützt – Anforderungsverteilung auf verschiedene Interfaces → Wartbarkeit gefördert durch klar definierte konzeptionelle 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 Sinn und Semantik der Schnittstelle : Konzentration auf saubere + sinnvolle Signaturen 3. Interfaces sind die eigentlichen Träger der Softwarearchitektur Sie verbessern die spätere Implementierung → Implementierung hat klare Basis + eindeutige semantische Zielrichtung → Lose Kopplung zwischen Schnittstelle + 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 Grundsätze des OO-Designs Grundeinheit des OO-Programmierens ist die Klasse Aber Grundeinheit des OO-Entwurfs ist der Typ ! (98) 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 vorschneller 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 grundlegender als Implementieren © H.Neuendorf (…nicht mehr oder weniger wichtig) Kennzeichen echt Objektorientierter Sprachen 1. Klassenkonzept Zusammenfassung von Daten + Funktionalität in syntaktischer Einheit 2. Kapselung Kontrolle des Zugriffs mittels eingeschränkter Sichtbarkeit - public / private / etc. 3. Vererbung Implementierungsvererbung : Weitergabe von Implementierung von Ober- an Unterklassen 4. Polymorphie Schnittstellenvererbung : Weitergabe von Typisierung von Oberklassen an Unterklassen © H.Neuendorf (99) @Annotations : Meta-Informationen zu Klassen + Methoden (101) Annotations do not directly affect program semantics, but they do affect the way programs are treated by tools […]. 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. Als spezielle Art von "Interface" Teil von Java ⇒ Compiler prüft syntaktische Korrektheit @interface Test{ } Können Elemente enthalten, die bei Verwendung mit passenden Werten zu füllen sind : @interface Copyright { String author( ) ; int year( ) ; Nur primitive Typen, Strings, Arrays } Elemente ohne Parameter / Exceptions ! @Copyright( author="SAP AG" , year = 2013 ) Default-Werte können hinterlegt werden public class SomethingNew { @Test void tuWas( ) { /* nur Test */ } Annotierbarkeit : Zulässig nur vor Package, Klasse, Interface, Methode, Attribut, Parameter, lokale Variable © H.Neuendorf } @Annotations : Meta-Informationen zu Klassen + Methoden Marker-Quelltext-Auswertung durch Tools zur Erstellung von Ressourcen → Javadoc JUnit … Teilweise durch Compiler gecheckt → @Override @SuppressWarnings("unused") @Deprecated Einkompiliert in Bytecode - zur LZ-Auswertung via Reflection → java.lang.annotation Liefert Frameworks + Containern Informationen → EJB : @Stateless @Remote … Parametrisierbar → Annotationen können selbst Meta-Annotationen besitzen Auslesbarkeit / Anwendbarkeit / Übernahme in Javadoc / Vererbbarkeit / … durch Meta-Annotationen einschränkbar Paket java.lang.annotation Enthält zahlreiche Meta-Annotationen © H.Neuendorf (102) @Annotations : @interface Mark{ } (103) Weitere Techniken … // keine Elemente, reiner Marker // Alle Annotationen beziehen sich auf TestKlasse // oder auch : Mark( ) @Mark @interface Revision { @ClassVersionInfo( int major( ) default 1 ; // Hinterlegen Defaults created = "20.11.2013", int minor( ) default 0 ; createdBy = "H.Neuendorf", } lastModified = "22.11.2014", lastModifiedBy = "H.Neuendorf", @interface ClassVersionInfo { revision = @Revision( minor = 2 ) String created( ) ; String createdBy( ) ; ) String lastModified( ) ; @BugsFixed( String lastModifiedBy( ) ; // Defaults bugsID = { "ArrayIndex" , "NP", "IO-Exception" } Revision revision( ) ; ) } class TestKlasse { @interface BugsFixed { String[ ] bugsID( ) ; } © H.Neuendorf Für jede Annotation wird eigenes .classFile angelegt // … } Durch Annotationen kann man Intentionen ausdrücken, die von Tools / Frameworks berücksichtigt werden Ansätze der Aspekt-Orientierten Programmierung (104) Interface-Erweiterungen in Java 8 Aufnahme von Implementierungen Statische Implementierte Methode Default-Implementierungen Rautenproblem + andere Kollisionen In seiner Idealform enthält ein Interface ausschließlich abstrakte Methoden - basta !! Erweiterung des IF-Konzepts beruht nicht auf vormailger Unvollständigkeit, sondern wurde durch Erweiterungen bei Lambda-Expressions erzwungen ! © H.Neuendorf Java 8 Interfaces - Statische Methoden Interfaces können statische implementierte Methoden enthalten (Tool / Utility) Modifizierer static ist explizit anzugeben ! Statische Methoden des IFs haben Zugriff auf die Konstanten des IFs Die statischen Methoden sind nur auf dem Namen des Interfaces aufrufbar ! Aufruf auf implementierender Klasse und deren Objekten ist nicht zulässig ! interface IFPrice { public static final int MAX_PRICE = 1000 ; public static boolean isValid( double p ) { return p > 0 && p < MAX_PRICE ; } boolean b ; b = IFPrice.isValid( 850.0 ) ; // ok ! b = Price.isValid( 850.0 ) ; // Fehler ! Price pObj = new Price( ) ; b = pObj.isValid( 850.0 ) ; // Fehler ! public abstract double calcPrice( ) ; } Nun droht Mißbrauch des IF-Konzepts als Tooling-Deponie - besser : class Price implements IFPrice { /* …*/ } Finale Tool-Klasse mit privatem Konstruktor © H.Neuendorf (105) Java 8 Interfaces - Default Implementierungen Interfaces können nicht-statische Methoden mit Standard-Implementierung enthalten Default-Methoden machen gewöhnliche IFs zu Erweiterten Schnittstellen Verhalten sich wie Methoden in abstrakten Klassen hinsichtlich Zugriff auf IF-Elemente Sinn : Hinzufügen weiterer Methoden zu IF ohne Implementierer zu invalidieren Erweiterung von IFs (Retrofitting) jederzeit kompatibel möglich ! Implementierende Klassen können Default-Implementierung übernehmen Müssen diese nicht überschreiben - können es aber natürlich ! interface IFPrice { // Schlüsselwort / Modifizierer default abstract public double calcProfit( ) ; default public double calcPrice( ) { return 100.0 ; } default public void test( ) { } // void-Methode mit leerer Standard-Implementierung - ok ! default public int check( ) { throw new RuntimeException( "not implemented" ) ; } // ok ! default public boolean hasProfit( ) { return calcProfit( ) > 0 ; } // ok - Template-Muster ! } © H.Neuendorf (106) (107) Default Implementierungen - Mehrdeutigkeiten IF kann von anderem IF geerbte Default-Methoden durch eigene Variante überschreiben Implementierende Klasse kann von Oberklasse erben, die auch solche Methode enthält Regel : Die Oberklasse setzt sich gegen das Interface durch ! interface IFYes { default public boolean isOk( ) { return true ; } interface IFNo extends IFYes { class Checker implements IFNo { (extends kommt vor implements) } default public boolean isOk( ) { return false ; } /* … */ } } new Checker( ).isOk( ) ; // liefert eindeutig false class Yes { public boolean isOk( ) { return true ; } class Checker extends Yes implements IFNo { } /* … */ } new Checker( ).isOk( ) ; // liefert true !! IF kann von anderem IF geerbte Default-Methode abstrakt überschreiben = Implementierung entfernen Implementierende Klasse erbt dann nur abstrakte Methode ! interface IFYes { default public boolean isOk( ) { return true ; } interface IFNo extends IFYes { © H.Neuendorf abstract public boolean isOk( ) ; } } Default Implementierungen - Mehrdeutigkeiten Klasse kann nicht zwei IFs implementieren, die dieselbe Default-Schnittstelle enthalten ! interface IFYes { default public boolean isOk( ) { return true ; } } interface IFNo { default public boolean isOk( ) { return false ; } } class Checker implements IFYes, IFNo { /* … */ } // Fehler Klasse kann zwei IFs implementieren, die dieselbe DefaultSchnittstelle enthalten - wenn in der Klasse die geerbten Default-Implementierungen überschrieben werden ! interface IFYes { default public boolean isOk( ) { return true ; } interface IFNo { default public boolean isOk( ) { return false ; } } } class Checker implements IFYes, IFNo { // ok ! public boolean isOk( ) { return false ; } // oder: } © H.Neuendorf return IFYes.super.isOK( ) ; (108) (109) Default Implementierungen - super.- Zugriff Klasse kann geerbte Default-Implementierung überschreiben … … und trotzdem Orginal-Default-Implementierung des IFs nutzen Vorgehen : super.-Aufruf gegen IF Dabei explizite Nennung des IF-Namens erforderlich ! interface IFYes { default public boolean isOk( ) { return true ; } } class Checker implements IFYes { public boolean isOK( ) { return false ; } public boolean optimist( ) { return IFYes.super.isOK( ) ; } } analog zur Klassen-Vererbung … © H.Neuendorf Bedeutung für das Java-IF-Konzept Java-Klasse kann beliebig viele Erweiterte IFs implementieren ⇒ Java-Klasse kann beliebig viele IFs Default-Implementierungen erben ⇒ Default-Methoden führen teilweise Mehrfach-Vererbungs-Aspekte ein (Mixin) ( Allerdings können IFs keinen Zustand einbringen - sie besitzen keine nicht-statischen Attribute ) Abstrakte Klassen + deren Teil-Implementierungen verlieren an Bedeutung Standardverhalten kann durch Erweiterte IFs bausteinartig angeboten werden Strenge des IF-Konzepts und des Ist-Ein-Gedankens wird aufgeweicht Bleibende Unterschiede zwischen Erweiterten IFs und Abstrakten Klassen AK : AKs können nicht-public Elemente enthalten AKs können nicht-statische Attribute enthalten AKs können finale Methoden enthalten AKs können Konstruktoren enthalten © H.Neuendorf (110) (111) Paketkonzept von Java Pakete als Strukturierungsmittel Anlegen von Paketen Export von Paketinhalten - public versus nicht-public Import von Paketelementen - explizit / implizit Pakete und Filestruktur Bedeutung von public, (package), private, protected Design-Prinzipien für Paketstrukturen © H.Neuendorf (112) Einordnung in Strukturierungsebenen Service / App Modul Unit of deployment & management Paket Klasse jar Unit of state Unit of composition & test ?? © H.Neuendorf Java (113) Pakete - Packages Bislang Strukturierung durch Klassen + Methoden Zu feingranular Paket ⇒ Gröberer Strukturierungsmechanismus : : Logische + Physische Zusammenfassung in größerer Einheit = Sammlung zusammengehöriger Klassen + Interfaces JDK besteht aus zahlreichen Paketen – speziell : java.lang : Standardpaket mit erforderlichen Klassen ( String, Math ...) Nur dieses wird in jedes java-File automatisch importiert ! Arbeiten mit Paketen 1. Anlegen eigener Pakete Schlüsselwort : package 2. Einbinden von Paketen Schlüsselwort : © H.Neuendorf import Jedes Paket sollte genau definierte Zuständigkeiten + Aufgaben besitzen ! Paket Klasse1 KlasseN Methoden Attribute Methoden Attribute (114) Anlegen von Paketen Am Anfang der Quelldatei (1.Anweisung!) : ⇒ package packageName ; Alle Klassen / IFs dieser Datei gehören dann zu diesem einen 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 Eindeutige Paketnamen via umgekehrter Domain : package de.dhbw-mosbach.graphics ; © H.Neuendorf Ohne package-Angabe gehören alle Klassen + IF der Datei zum namenlosem Standardpaket. Werden im aktuellen Arbeitsverzeichnis gesucht. Paket = Sichtbarkeitsgrenze Alles, was zum Paket gehört, ist außerhalb Paket defaultmäßig nicht zugreifbar ! (115) Paketkonzept leistet : Deklaration öffentlicher Schnittstellen Einschränkung von Verwendungen Durchsetzung Geheimnisprinzip Dokumentation Verwendungen im Code Veröffentlichung nur durch expliziten Export ! ⇒ Klassen haben nur Zugriff auf andere IFs / Klassen des gleichen Pakets ! Paketcode kann intern kooperieren - ohne dass Fremdpaket darauf Zugriff hat ! ⇒ Klassen eines Pakets haben keinen Zugriff auf Klassen eines anderen Pakets ! Paket = Namensraum : Innerhalb Paket nur eindeutige Klassen-/ IF-Namen zulässig package yourPack ; package myPack ; class C1 { int x; void m( ){...} } class C1 { int x; class C2 { int y; void m( ) {...} } class C22 { void m( ){...} } // Klassen C1 und C2 sind lokal zu Paket myPack C2 c = new C2( ) ; // Fehler !! // können sich gegenseitig benutzen // Klasse C2 hier unbekannt !! // aber nicht von Außen (= aus anderen Paketen) ansprechbar ! © H.Neuendorf } (116) Export von Paket-Inhalten - public Gewährung Zugriff auf Interfaces, Klassen, Methoden, Attribute von Paketen Export von Paketbestandteilen durch public -Deklaration : "Was public ist / exportiert wird, bestimmt Paket selbst !" Voraussetzung für Sichtbarkeit : Zugehörige Klasse / IF selbst auch public deklariert ! Nur public Konstruktoren sind aus Fremdpaketen aufrufbar ! package myPack ; // Auch Paket hat eine Schnittstelle !! Wenn Klasse nur nicht-public Konstruktoren hat, dann kann sie aus anderen Paketen nicht instanziiert werden Zugriff aus Fremd-Paketen : public-Elemente von C1 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 exportiert C1( ) {...} } class C2 { … } // C2 von Außen nicht sichtbar ! © H.Neuendorf Nicht-public-Elemente von C1 und Klasse C2 jedoch verborgen ! Erlaubt: C1 obj = new C1( 4 ) ; Verboten: C1 obj = new C1( ) ; Nicht-public-Elemente sind nicht Teil des Vertrags – somit nicht als stabil garantiert Verwendung Paket-Inhalte : Import in andere Pakete (117) Voraussetzung für Verwendung exportierter Komponenten in anderen Paketen : Ausdrücklicher Import in verwendenden Paketen " Was von Außen importiert wird, bestimmt das verwendende Paket selbst ! " Sinn : Klare Festlegung, welche fremden Klassen in eigenem Paket verwendet werden ! 1. Volle Paket-Qualifikation des Klassennamens Impliziter Import 2. 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 gemeinsam verwendbar Eigene Klassen dürfen so heißen, wie implzit importierte Klassen (118) Expliziter Import Import-Zeile in Quellcode-Datei - Varianten : 1. Import einer einzelnen Klasse : import paketname.klassenname ; 2. Import aller Klassen eines Pakets : import paketname.* ; 3. Statischer Import statischer Elemente einer bestimmten Klasse : import static java.lang.Math.* ; Regeln : import definiert Suchpfad. Import-Anweisungen in Quellcode-Datei direkt nach package-Anweisung ! Bindet kein Coding ein, macht Bytecode nicht länger Import-Anweisungen gelten nur für importierende Quell-Datei ! Kein Linking in Java Alle Klassen müssen verschiedene Namen haben ! Verwendung * ohne Nachteile Eigene Klassen dürfen nicht so heißen, wie explzit importierte Klassen ! → Namenskonflikt ⇒ Impliziter Import In Coding Klassennamen ohne Paket-Qualifikation verwendbar package myPack ; public class C1 { … } class C2 { … } © H.Neuendorf package newPack ; import myPack.C1 ; // expliziter Import class C10 { C1 obj_1 = new C1( ) ; } (119) Pakete + 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 C bestimmt Dateinamen: C.java ● Nicht-public-Klassen von public-Klasse intern verwendbar - aber nicht exponiert ● Innere Klassen können public sein und separat importiert werden Paket P mit public Klassen A, B und C ⇒ Verzeichnis P mit Dateien A.java P A B.java C.java P B C A.java set CLASSPATH=… für .jar .zip .class © H.Neuendorf B.java C.java (120) Pakete + Verzeichnisse – Schachtelung Paket-Hierachie = Paket aus Paketen Paket P3 ← Paket P1 + Paket P2 Verzeichnis P3 enthält Unterverzeichnisse P1 + P2 mit deren .java-Dateien Keine automatische Sichtbarkeit zwischen inneren + äußeren Paketen ! Benutzung → Angabe gesamter Paketpfad bis zu referenzierten Klassen : import P3.P2.C ; import P3.P2.* ; import P3.* ; // Import aller Klassen direkt aus P3 - nicht aber der Klassen aus P2 und P1 ! P3 P1 A.java © H.Neuendorf B.java P2 C.java D.java N.java Pakete + Verzeichnisse in Eclipse (122) Jede Klasse ist einem Package zugeordnet Default ist das default package = namenloses Standardpaket Package anlegen : Eclipse legt pro Package Ordner mit Klassen an : Einbinden IO in Eclipse ohne physische Kopie : 1. IO.class als ZIP- oder JAR-File zentral ablegen 2. Menü Project → Properties → Java Buid Path 3. Tab Libraries wählen 4. Button Add External JARs … 5. Auf IO-ZIP- / -JAR-File verweisen 6. OK Referenz nachträglich entfernbar oder änderbar © H.Neuendorf Zugriffsrechte public protected package private public In allen Klassen des eigenen Pakets In allen anderen Paketen, die die Klasse importieren protected In Fremdklassen des selben Pakets - und deren Objekten In Unterklassen der Klasse des selben Pakets – und deren Objekten In Unterklassen der Klasse in anderen Paketen - jedoch nicht auf deren Objekten ! "package" keine Angabe In allen Klassen des eigenen Pakets - aber nicht in anderen Paketen private Nur in eigener Klasse selbst - nirgendwo sonst Zugriff durch Klasse mit Element: © H.Neuendorf Klasse Unterklasse FremdKlasse FremdPackage private X package X X X protected X X X X U-Klasse public X X X X (123) Zugriffsrecht protected : Paket P1 Mit Java-Paketkonzept verbunden (124) Paket P2 Oberklasse O : protected void m( ) und deren Unterkl. Unterklasse U2 import P1.* ; Unterklasse1 Fremdklasse1 Fremdklasse2 "Fast alle" haben Zugriff auf die protected-Methode m( ) der Oberklasse P1.O Nur die nicht erbende Fremdklasse2 hat keinen Zugriff auf protected-Elemente der Oberklasse O aus P1 C++ : Durch protected werden erbende Unterklassen gegenüber nicht erbenden Klassen generell privilegiert © H.Neuendorf Java : Paket stellt protected-Komponenten anderen Paketen zur Nutzung innerhalb deren Unterklassen z.Vfg. (125) Zugriffsrecht protected Situation für Unterklasse U2 aus anderem Paket P2 Zugriff auf m() nur im Coding der Unterklasse U2 - Nicht auf deren Objekten : package P2 ; U2 u = new U2( ) ; import P1.O ; u.m( ) ; // Fehler !! class U2 extends O { public void test( ) { m( ) ; } // ok! } Gilt auch für geerbte protected static Attribute + Methoden : Außerhalb der Unterklasse U2 auch auf Klassennamen nicht sichtbar ! U2.statischeMethode( ) ; // Fehler !! Denksport : protected = geschützt … …vor direktem Zugriff aus fremden Paketen © H.Neuendorf Oberklasse hat nur einen public / private / protected Konstruktor. Wer kann von ihr erben ? … Zusammenspiel Pakete + Interfaces : Verbergen von Konkretisierungen (126) Facade-Pattern : Nur Schnittstellentyp direkt sichtbar - nicht konkreter Implementierungstyp package Facade ; package Other ; public interface IData { int getData( ) ; } import Facade.* ; public class User { package Facade ; public static void main( String[ ] args ) { public abstract class DataFactory { // Naming + Factory : IData d = DataFactory.newIData( "One" ) ; public static IData newIData( String n ) { int n = d.getData( ) ; // ok ! switch( n ) { case "One" : return new DataProvider1( ) ; // Fehler - extern nicht sichtbar : case "Two" : return new DataProvider2( ) ; default : d = new DataProvider1( ) ; return null ; DataProvider d2 = … ; }} } Zugriff auf m() jedoch nur im Coding der Unterklasse 2 : aufImplementierungen deren Objekten : von Außen nicht sichtbar : //Nicht Konkrete } } class DataProvider1 implements IData { public int getData( ) { return 100 ; } } class DataProvider2 implements IData { public int getData( ) { return 200 ; } } © H.Neuendorf Zusammenspiel Pakete + Interfaces : (127) Verbergen von Konkretisierungen Design-Idee → minimale Kopplung - maximale Flexibilität : OCP ! Factory liefert konkrete Implementierung - jedoch nur über Interface-Typ ansprechbar Factory liefert Referenz auf Interface-Typ Nur gegen diesen Typ programmiert der Verwender Konkrete Implememtierungs-Typen (Klassen) bleiben paketintern (transparent) ⇒ Jederzeit austauschbar durch andere Implementierungen (Klassen - z.B. DataProviderXY) ⇒ Verwender nicht an bestimmte implementierende Klasse gekoppelt Völlige Entkopplung von Schnittstelle und Implementierung Umsetzung Information Hiding + Zugangskontrolle auf Paketebene ! Public-Elemente des Pakets sind nur Interfaces und Factories, die Zugang zur eigentlichen Implementierung vermitteln. Implementierungsklassen sind nicht öffentlich und nicht importierbar © H.Neuendorf (128) Modifizierer von Klassen, Attributen, Methoden Nicht alle Modifizierer auf alles anwendbar : Modifizierer Klasse Attribut Konstruktor X X X protected X X X private X X X X X X X public static X (IF) Methode (X innere) final X abstract X (IF) X strictfp X native X Speziell IFs : IF-Methoden müssen stets public sein IF-Attribute müssen stets public + static + final sein Sind es automatisch - auch wenn nicht explizit vermerkt IF selbst ist jedoch nicht automatisch als Ganzes public ! … Bei public- Paketklassen gilt noch viel schärfer als bei paket-internen nicht-public-Klassen : Keine public-Attribute - sondern nur public-Zugriffsmethoden (Setter / Getter) ⇒ Nur dadurch bewahrt man die Flexibilität, die innere Datenrepräsentation der Klasse später noch verändern / anpassen zu können … © H.Neuendorf (129) Package Design Principles Gute Struktur – keine Zyklen ! Problematische Struktur – Zyklen ! Änderung an zB MyDialogs beeinflusst nur die Pakete MyTasks und MyApplikation – nur dort muss entschieden werden, ob neue Version von MyDialogs verwendet wird. Klasse in MyDialogs benutzt nun Klassen aus MyApplikation. Zum Compilieren & Testen von MyDialogs ist nur Paket Windows erforderlich. Zum Compilieren & Testen von MyTasks ist Verfügbarkeit des gesamten Systems erforderlich. Compilieren + Testen des Gesamtsystems kann bottom up erfolgen – im Grunde ein Paket nach dem anderen. System praktisch nur noch als Ganzes testbar + compilierbar Fazit : Klar, einfach, übersichtlich ! Compilationszeiten wachsen stark an. Jeder Entwickler muss mit selben Releasestand aller anderen arbeiten – hoher Koordinationsaufwand. Tool zur Struktur-Analyse : JarAnalyzer Autor: Kirk Knoernschild © H.Neuendorf Testen von MyDialogs setzt Zugriff auf alle Pakete des System voraus! Paket MyTasks ist nun von allen Paketen im System abhängig. Aus einzelnen Paketen ist quasi ein Riesenpaket geworden, dessen Teile nicht mehr isolierbar sind. Fazit : Unübersichtlich, unflexibel, kaum handlebar ! ⇒ Zyklus muss aufgebrochen werden ! (130) Package Design Principles Aufbrechen des Zyklus : Anlegen eines neuen Pakets, von dem MyDialogs und MyApplication nun abhängen. Klassen, durch die die beiden Pakete zuvor direkt verbunden wurden, kommen ins neue Paket. Konsequenz : Paketstrukturen müssen an neue Design-Anforderungen angepasst werden. Wachsen des Systems + seiner Logik bedingt auch Wachsen + Wandeln der Paketstruktur. Paketstruktur somit nicht für allemal top down fixierbar - falls Wandelbarkeit, Testbarkeit, Erstellbarkeit des Systems nicht leiden sollen. Weitere Definition von Stabilität : Etwas, das nur mit großer Mühe zu verändern ist – und sich auch deshalb kaum ändert … Stable-Dependencies Principles : Depend in the direction of stability Unterscheiden zwischen Paketen, die sich oft ändern werden, sollen, dürfen bzw. leicht änderbar sind – und solchen Paketen, die sich nicht oft ändern werden, sollen, dürfen - bzw. nur mühsam ändern lassen. Faktoren: Größe, Komplexität, Sicherheitsrelevanz … … insbesondere aber: Zahl der Pakete, die davon abhängen – dh Verantwortlichkeit des Pakets. Leicht veränderliche Pakete sollen von stabilen, schwer veränderlichen Paketen abhängen – nicht umgekehrt ! … sonst würde das leicht veränderliche Paket nicht länger leicht veränderbar sein ! Bsp: Paket Windows sollte sehr stabil sein © H.Neuendorf Paket MyApplication darf sich oft ändern. (131) Dokumentation + weitere Tools Dokumentation von Schnittstellen - Tool javadoc DK-Annotationen javadoc + jar mit Eclipse-Unterstützung Weitere JDK-Tools aus bin-Ordner Schematischer Aufbau java.lang / Java SE / Java EE / Java ME © H.Neuendorf (132) Dokumentation von Schnittstellen Interfaces, Klassen, Methoden, Attribute JDK-Unterstützung → Tool : Aufruf auf Kommandozeile : ⇒ → Spezifikation Funktionalität + Bedeutung javadoc *.java javadoc [options] {filename | packageName} HTML-Files mit Schnittstellendarstellung + Kommentaren aus Quellcode HTML direkt in DK-Text verwendbar /** ... */ Regeln für Dockommentare (DK) : 1. Jeder DK beschreibt Bezeichner dessen Deklaration unmittelbar folgt 2. Der erste Satz des DK ist Zusammenfassung = 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 in Unterklasse hat, erbt sie DK des Obertyps … © H.Neuendorf Dokumentation : DK-Annotationen @ Java-Doc-Tags : (133) /** ... */ (u.a.) @param Spezikation einzelner Parameter. Erstes Wort Parametername, Rest Beschreibung @return Rückgabewert der Methode @author Autorenangabe @version Versionsangabe http://download.oracle.com/javase/8/docs/technotes/tools/ @throws Ausnahmebehandlung @deprecated Veraltet. Compiler markiert Feld als veraltet + erzeugt Warnung @see Querverweis auf andere Javadoc-Docu oder andere Files in doc-files Verzeichnis Attribute und Methoden anderer Klassen mit # vor ihrem Namen. Bsp: {@link} @see meineMethode(String, Object) @see java.lang.String @see java.lang.Math#PI 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()} ) (135) JavaDoc + 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 3. Aufruf von JavaDoc : Project → Generate JavaDoc Alternativ Kontextmenü : Alle JavaDoc-Optionen einstellbar Export → Java → Javadoc JAR-Files Projekt Kontextmenü : Export → Java → JAR file Wizzard erfagt Einstellungen bzgl. Ablageort + Inhalt → Runnable JAR File (Manifest-Datei !) Ein .jar-File mit Manifest-Eintrag für zentrale main( ) -Klasse ist direkt ausführbar Aufruf Launcher java mit Option -jar auf Kommandozeile : Vorteil jar : C:\> java –jar Test.jar ↵ Organisation & Kompression Weitergabe Projekt als ein File - statt Vielzahl von Files Remote-Aufruf : Alle .class-Files verfügbar - nicht einzeln übers Netz zu laden © H.Neuendorf Weitere Tools jps … im bin-Ordner der JDK-Installation (136) (JVM Process Status Tool) Anzeige laufender Java-Instanzen mit ProcessID (PID) jps jmap (JVM Memory Map) Anzahl + Speicherverbrauch aller Objekte zu bestimmter PID jmap –histo 5460 jstack (JVM Runtime Stack) Anzeige aller laufenden Threads und deren (Warte-)Zustand (Full Thread Dump) jstack 5460 jstat (JVM Statistics Monitoring Tool) Abfrage von Performance-Statistiken jstat –gcutil 4176 © H.Neuendorf Aufruf auf Kommandozeile Option –help zur Orientierung Ausführliche Dokumentation in JDK-Doku : http://download.oracle.com/javase/8/docs/technotes/tools/ Weitere Tools jconsole © H.Neuendorf im bin-Ordner JDK JVM Management Konsole für wählbaren Java-Prozess (137) Paket java.lang Java SE (138) Ausschnitt … java.lang Systemklassen Wrapper System Runtime RuntimePermission Process ProcessBuilder Thread ThreadGroup ThreadLocal<T> InheritableThreadLocal<T> Thread.State ClassLoader SecurityManager Class<T> Compiler Package StackTraceElement Boolean Byte Character Character.Subset Character.UnicodeBlock Double Float Integer Long Short Void Number © H.Neuendorf Ausnahmebehandlung Throwable Exception NullPointerException ArrayIndexOutOfBounds-Exception ClassCastException ClassNotFoundException NumberFormatException SecurityException Error InternalError NoClassDefFoundException … Sonstiges Object Math StrictMath String StringBuffer StringBuilder … Interfaces Appendable CharSequence Cloneable Comparable<T> Iterable<T> Readable Runnable Thread.UncaughtExceptionHandler Wegen elementarer Bedeutung automatisch importiert (139) Java SE Java SE 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 ... © H.Neuendorf Ein-/ Ausgabe Netz/ Web java.io jawa.awt.print java.awt.im javax.imageio javax.print javax.sound.midi 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 Daten / SQL java.sql javax.sql javax.sql.rowset java.awt.datatransfer javax.crypto XML javax.xml javax.xml.parsers javax.xml.transform javax.xml.validation javax.stax javax.xml.xpath org.w3c.dom org.xml.sax Java EE Applikationsserver : JEE Basiert aufJava SE ! Java Standard Servlets / JSP javax.servlet javax.servlet.jsp javax.servlet.http Client-Server-Transaktionalität Enterprise Java Beans javax.ejb Grundlegende Überarbeitung durch Version EJB 3.0 ab Java EE 5 Kein Produkt - sondern Spezifikation Java EE-Server müssen Spezifikation implementieren, um zertifiziert zu werden. Mail Message Queues javax.mail javax.mail.event javax.activation javax.jms Oracle-Referenzimplementation GlassFish OpenSource J2EE-Server JBoss © H.Neuendorf (141) Vereinfachung der EJBImplementierung + Verteilung. 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 ... (142) Java Micro Edition Einsatz Java ME auf Geräten mit geringer Hardwareausstattung : Java Micro Edition Basis Ein-/ Ausgabe java.lang java.util java.io Frühere Java-Handys … Spezial-APIs javax.microedition.io javax.microedition.lcdui javax.microedition.rms Basiert auf Miniversion der virtuellen Maschine = KVM Erweiterungen im Bereich GUI, Netz ... Aktuelle Bedeutung gering aufgrund Android-Framework Aussicht : Nutzung für embedded devices und Machine2MachineKommunikation im IoT © H.Neuendorf Java ME Wireless Toolkit IDE incl. Emulatoren (151) Aufzählungstypen - Enumerations Java5 Bedeutung Aufzählungstyp Workarounds vor Java 5 Enum-Konzept + Minimalstruktur Verwendungsregeln Verschieden komplexe Ausbaustufen Deutlich mächtiger als in C++ … © H.Neuendorf (152) Aufzählungstypen - Enumerations Aufzählung = Zur Compilezeit bekannte, begrenzte Menge benamter Konstanten Konventionelle Hilfskonstruktion : Klassen mit fester Menge von Konstanten final class Mengen { // zu simpel public static final int Klein = 0 ; final class Karten { // angemessen public static final int Mittel = 1 ; public static final int Gross = 2 ; public static final Karten HERZ = new Karten( "Herz", 1 ) ; public static final Karten KARO = new Karten( "Karo", 2 ) ; private Mengen( ) { } } public static final Karten PIK = new Karten( "Pik", 3 ) ; public static final Karten KREUTZ = new Karten( "Kreutz", 4 ) ; private Karten( String s, int n ) { name = s ; rang = n ; } private final String name ; private final int rang ; public String toString( ) { return name ; } public int getRang( ) { return rang ; } // … } © H.Neuendorf Realistische Nachbildung des enum-Konzepts durch Klassen mit festgelegter begrenzter Zahl konstanter, immutabler Objekte als statische Attribute Echte Enumerations Java 5 (153) class Preise { private double stueckPreis; Schlüsselwort enum private enum Mengen { Klein, Mittel, Gross ; } Konzept im objektorientierten Kontext public Preise( double p ) { stueckPreis = p; } public double gesamtPreis( int zahl ) { Java-Enums Mengen m = Mengen.Klein ; Besondere finale Klassen → double preis = 0.0 ; if( zahl > 100 ) Keine Objekte davon mit new direkt instanziierbar oder Unterklassen ableitbar m = Mengen.Mittel ; if( zahl > 1000 ) m = Mengen.Gross ; switch( m ) { case Klein : preis = 1.0*zahl*stueckPreis ; break; case Mittel : preis = 0.9*zahl*stueckPreis ; break; Inhalt : case Gross : preis = 0.8*zahl*stueckPreis ; break; Vorgegebene Menge von Objekten } Selbst von java.lang.Enum abgeleitet return preis ; } public static void main( String[ ] args ) { In Klassen oder separat anlegbar … Preise tester = new Preise( 100.0 ) ; IO.writeln( "Preis = " + tester.gesamtPreis( 780 ) ) ; } } © H.Neuendorf (154) Enum-Verwendungsregeln Deklaration + Aufzählungs-Inizialisierung : enum Mengen { Klein, Mittel, Gross ; } Klasse Mengen angelegt - enthält als statische Elemente die immutablen Objekte Klein, Mittel und Gross vom Enum-Typ Mengen. Jede Enum ist eigener Namensraum ⇒ gleiche Namen in verschiedenen enums ok : enum Mengen { Klein, Mittel, Gross ; } enum Gebinde { Klein, Mittel, Gross ; } 1. Nicht zu anderen Typen cast-bar 2. Typsicher – nicht mit anderen Typen verwechselbar : Mengen m = Mengen.Klein ; // OK Mengen m = Gebinde.Klein ; // Typ-Fehler m=2; // Typ-Fehler 3. Typsicherheit bei Methodenaufrufen © H.Neuendorf Methode : double preis( int zahl, Mengen mArt ) { … } Aufruf : preis( 100 , Mengen.Mittel ) ; // ok Aufruf : preis( Mengen.Mittel , 100 ) ; // Typ-Fehler (155) Enum-Verwendungsregeln Enums in switch-case verwendbar + iterierbar : for( Mengen m : Mengen.values( ) ) { /* … */ } Vergleichsoperationen : == und != zulässig … nicht aber <, >, <=, >= Mengen m = Mengen.Klein ; if( m == Mengen.Gross ) IO.writeln( "Grosse Menge" ) ; toString( ) liefert Name des enum-Objekts : Mengen m = Mengen.Klein ; IO.writeln( "Inhalt: " + m ) ; // Inhalt: Klein IO.writeln( "Inhalt: " + Mengen.Mittel + " " + Mengen.Gross ) ; // Inhalt: Mittel Gross Intern Ordnungsnummer gespeichert : Mengen m = Mengen.Mittel ; IO.writeln( "Inhalt: " + m.ordinal( ) ) ; IO.writeln( "Inhalt: " + Mengen.Gross.ordinal( ) ) ; Collections-Framework JDK 5 : enum-Container © H.Neuendorf // Inhalt: 1 // Inhalt: 2 java.util.EnumSet + EnumMap (156) Komplexe Enums class Preise { enum stellt Klasse dar ⇒ Kann Methoden, Konstruktoren, Attribute besitzen private double stueckPreis; public Preise( double p ) { stueckPreis = p ; } private enum Mengen { // Reihenfolge !! // Konstruktoraufrufe : Klein( 0 ) , Mittel( 100 ) , Gross( 1000 ) ; Konstruktoren müssen private sein : // Konstruktor : enum-Objekte nicht von außen erzeugbar private Mengen( int w ) { grenzWert = w ; } Via Konstruktor können Attributwerte belegt werden - durch Methodenaufrufe abfragbar KonstruktorAufrufe müssen vor KonstruktorDeklaration stehen ! // Attribut : private final int grenzWert ; // Methode : Attribute sollten private final sein public int getWert() { return grenzWert ; } Öffentliche Attribute sind zugreifbar public String toString( ) { return "Grenze: " + grenzWert ; } Methoden dürfen public sein - sind auf enum-Objekten aufrufbar } public double gesamtPreis( int stueck ) { Enum-Klasse kann IFs implementieren Jede enum ist Comparable & Serializable Mengen m = Mengen.Klein ; if( stueck > Mengen.Mittel.getWert( ) ) m = Mengen.Mittel ; if( stueck > Mengen.Gross.getWert( ) ) m = Mengen.Gross ; // … © H.Neuendorf Komplexe Enums enum = Klasse … kann für sich allein stehen ! Kann enum-Konstanten-spezifische Methoden-Implementierungen für abstrakte Methoden enthalten : Jede enum-Konstante muss abstrakte Methode in eigenem Block überschreiben Aufzählungskonstanten enthalten dadurch spezifische Funktionalität : double d = Operation.PLUS.apply( 3.2, 4.5 ) ; (157) enum Operation { PLUS( "+" ) { double apply( double x, double y ) { return x + y ; } }, MINUS( "-" ) { double apply( double x, double y ) { return x – y ; } }, TIMES( "*" ) { double apply( double x, double y ) { return x * y ; } }, DIVIDE( "/" ) { double apply( double x, double y ) { return x / y ; } }; private final String symbol ; private Operation( String symbol ) { this.symbol = symbol ; } public String toString( ) { return symbol ; } abstract double apply( double x, double y ) ; Kann eigene main( ) enthalten und noch manches mehr … public static void main( String[ ] args ) { double x = … ; double y = … ; for( Operation op : Operation.values( ) ) IO.writeln( "Res: " + x + op + y + "=" + op.apply( x, y ) ) ; } F.Esser "Java 5 im Einsatz", S.154 ff J.Bloch "Effective Java", S.147 ff © H.Neuendorf }