{"id":1621,"date":"2012-12-22T10:50:54","date_gmt":"2012-12-22T08:50:54","guid":{"rendered":"http:\/\/www.tutego.de\/blog\/javainsel\/?p=1621"},"modified":"2012-12-22T10:51:17","modified_gmt":"2012-12-22T08:51:17","slug":"default-methoden-teil-1-update-mit-aktueller-java-8-syntax","status":"publish","type":"post","link":"https:\/\/www.tutego.de\/blog\/javainsel\/2012\/12\/default-methoden-teil-1-update-mit-aktueller-java-8-syntax\/","title":{"rendered":"Default-Methoden, Teil 1, Update mit aktueller Java 8 Syntax"},"content":{"rendered":"<p>Ist eine Schnittstelle einmal verbreitet, so sollte es dennoch m\u00f6glich sein, Operationen hinzuzuf\u00fcgen. Java 8 bringt daf\u00fcr eine Sprach\u00e4nderung mit, die es Entwicklern erlaubt, neue Operationen einzuf\u00fchren, ohne dass Unterklassen verpflichtet werden, diese Methoden zu implementieren. Damit das m\u00f6glich ist, muss die Schnittstelle eine Standard-Implementierung mitbringen. Auf diese Weise ist das Problem gel\u00f6st, denn wenn eine Implementierung vorhanden ist, haben die implementierenden Klassen nichts zu meckern, und wenn sie das Standardverhalten \u00fcberschreiben m\u00f6chten, k\u00f6nnen sie das gerne machen. Oracle nennt diese Methoden in Schnittstelle mit vordefinierter Implementierung Default-Methoden<a href=\"file:\/\/\/C:\/Users\/Christian\/Dropbox\/Insel\/todo\/#_ftn1_4694\" name=\"_ftnref1_4694\">[1]<\/a>. Schnittstellen mit <a name=\"OLE_LINK1\">Default-Methoden <\/a>hei\u00dfen erweiterte Schnittstellen.<\/p>\n<p>Eine Default-Methode unterscheidet sich syntaktisch in zwei Aspekten von herk\u00f6mmlichen implizit abstrakten Methoden-Deklarationen.<\/p>\n<p>\u00b7 Die Deklaration einer Default-Methode beginnt mit dem Schl\u00fcsselwort default.<\/p>\n<p>\u00b7 Statt dass ein Semikolon das Ende der Deklaration anzeigt, steht bei einer Default-Methode stattdessen in geschweiften Klammen ein Block mit Implementierung. Die Implementierung wollen wir Default-Code nennen.<\/p>\n<p>Sonst verhalten sich erweiterte Schnittstellen wie normale Schnittstellen. Eine Klasse, die eine Schnittstelle implementiert, erbt alle Operationen, sei es die abstrakten Methoden oder die Default-Methoden. Falls die Klasse nicht abstrakt sein soll muss sie alle von der Schnittstelle geerbten abstrakten Methoden realisieren; sie kann die Default-Methoden \u00fcberschreiben, muss das aber nicht, denn eine Vorimplementierung ist ja schon gegeben.<\/p>\n<p>Realisieren wir dies in einem Beispiel. F\u00fcr Spielobjekte soll ein Lebenszyklus m\u00f6glich sein; der besteht aus start() und finish(). Der Lebenszyklus ist als Schnittstelle vorgegeben, die Spielobjektklasse implementieren k\u00f6nnen. Version 1 der Schnittstelle sieht also aus:<\/p>\n<p>interface GameLifecycle {<\/p>\n<p>void start();<\/p>\n<p>void finish();<\/p>\n<p>}<\/p>\n<p>Klassen wie Player, Room, Door k\u00f6nnen die Schnittstellen erweitern, und wenn sie dies tun, m\u00fcssen sie die beiden Methoden implementieren. Bei Spielobjekten, die diese Schnittstelle implementieren, kann unser Hauptprogramm, das Spiel, diese Methoden aufrufen und den Spielobjekten R\u00fcckmeldung geben, ob sie gerade in das Spiel gebracht wurden, oder sie aus dem Spiel entfernt wurden.<\/p>\n<p>Je l\u00e4nger Software lebt, desto mehr bedauern Entwickler Designentscheidungen. Die Umstellung einer ganzen Architektur ist eine Mammutaufgabe, einfache \u00c4nderungen wie das Umbenennen sind \u00fcber ein Refactoring schnell erledigt. Nehmen wir an, auch bei unserer Schnittstelle gibt es einen \u00c4nderungswunsch \u2013 nur die Initialisierung und das Ende zu melden reicht nicht. Geht das Spiel in einen Pausenmodus, soll ein Spielobjekt die M\u00f6glichkeit bekommen, im Hintergrund laufende Programme anzuhalten. Das soll durch eine zus\u00e4tzliche pause()-Methode in der Schnittstelle realisiert werden. Hier spielen uns die Default-Methoden perfekt in die Hand, denn wir k\u00f6nnen die Schnittstelle erweitern, aber eine leere Standardimplementierung mitgeben. So m\u00fcssen Unterklassen die pause()-Methode nicht implementieren, k\u00f6nnen dies aber; Version 2 der nun erweiterten Schnittstelle GameLifecycle:<\/p>\n<p>interface GameLifecycle {<\/p>\n<p>void start();<\/p>\n<p>void finish();<\/p>\n<p><b>default <\/b>void pause() <b>{}<\/b><\/p>\n<p>}<\/p>\n<p>Klassen, die GameLifecycle schon genutzt haben, bekommen von der \u00c4nderung nichts mit. Der Vorteil: Die Schnittstelle kann sich weiter entwickeln, aber alles bleibt bin\u00e4rkompatibel und nichts muss neu compiliert werden. Vorhandener Code kann auf die neue Methode zur\u00fcckgreifen, die automatisch mit der Implementierung vorhanden ist. Weiterhin verhalten sich Default-Methoden wie andere Methoden von Schnittstellen auch: es bleibt bei der dynamischen Bindung, wenn implementierende Klassen die Methoden \u00fcberschreiben. Wenn eine Unterklasse wie Flower zum Beispiel bei der Spielpause nicht mehr bl\u00fchen m\u00f6chte, so \u00fcberschreibt sie die Methode und l\u00e4sst den Timer pausieren. Eine T\u00fcr dagegen hat nichts zu stoppen und kann pause() mit dem Default-Code so \u00fcbernehmen.<\/p>\n<p><strong>Hinweis<\/strong><\/p>\n<p>Statt des leeren Blocks k\u00f6nnte der Rumpf auch throw new UnsupportedOperationException(&quot;Not yet implemented&quot;); beinhalten, um anzuk\u00fcndigen, dass es keine Implementierung gibt. So f\u00fchrt eine hinzugenommene Default-Methode zwar zu keinem Compilerfehler, aber zur Laufzeit f\u00fchren nicht \u00fcberschriebene Methoden zu einer Ausnahme. Erreicht ist das Gegenteil vom Default-Code, weil eben keine Logik standardm\u00e4\u00dfig ausgef\u00fchrt wird;das Ausl\u00f6sen einer Ausnahme zum Melden eines Fehlers wollen wir nicht als Logik ansehen.<\/p>\n<p><strong>Kontext der Default-Methoden<\/strong><\/p>\n<p>Default-Methoden verhalten sich wie Methoden in abstrakten Klassen und k\u00f6nnen alle Methoden der Schnittstelle (inklusive der geerbten Methoden) aufrufen. Die Methoden werden sp\u00e4ter dynamisch zur Laufzeit gebunden.<\/p>\n<p>Nehmen wir eine Schnittstelle Buyable f\u00fcr k\u00e4ufliche Objekte:<\/p>\n<p>interface Buyable {    <br \/>&#160; double price();     <br \/>}<\/p>\n<p>Leider schreibt die Schnittstelle nicht vor, ob Dinge \u00fcberhaupt k\u00e4uflich sind. Eine Methode wie isBuyable() w\u00e4re in Buyable ganz gut aufgehoben. Was kann aber die Default-Implementierung sein? Wir k\u00f6nnen auf price() zur\u00fcckgreifen und testen, ob die R\u00fcckgabe ein g\u00fcltiger Preis ist. Das soll gegeben sein, wenn der Preis echt gr\u00f6\u00dfer 0 ist.<\/p>\n<p>interface Buyable {    <br \/>&#160; double price();<\/p>\n<p><b>default <\/b>boolean isBuyable() <b>{ return price() &gt; 0; }<\/b>     <br \/>}<\/p>\n<p>Implementierende Klassen erben die Methode isBuyable() und beim Aufruf geht der interne Aufruf von price() an genau die Klasse, die Buyable und die Methode implementiert.<\/p>\n<p><strong>Hinweis<\/strong><\/p>\n<p>Eine Schnittstelle kann die Methoden der absoluten Oberklasse java.lang.Object ebenfalls deklarieren, etwa um mit Javadoc eine Beschreibung hinzuzuf\u00fcgen. Allerdings ist es <i>nicht<\/i> m\u00f6glich, mit Default-Code Methoden wie toString() oder hashCode() vorzubelegen.<\/p>\n<p>Neben der M\u00f6glichkeit auf Methoden zuzugreifen, steht auch die this-Referenz zur Verf\u00fcgung. Das ist sehr wichtig, denn so kann der Default-Code an Utility-Methoden weiterreichen und einen Verweis auf sich selbst \u00fcbergeben. H\u00e4tten wir zum Beispiel schon eine isBuyable(Buyable)-Methode in einer Utiltiy-Klasse PriceUtils implementiert, so k\u00f6nnte der Default-Code aus einer einfachen Weiterleitung bestehen:<\/p>\n<p>class PriceUtils {<\/p>\n<p>public static boolean <b>isBuyable( Buyable b )<\/b> { return b.price() &gt; 0; }<\/p>\n<p>}<\/p>\n<p>interface Buyable {<\/p>\n<p>&#160; double price();<\/p>\n<p>default boolean isBuyable() {<b> return PriceUtils.isBuyable( this )<\/b>; }     <br \/>}<\/p>\n<p>Dass die Methode PriceUtils.isBuyable(Buyable) f\u00fcr den Parameter den Typ Buyable vorsieht und sich der Default-Code mit this auf genau so ein Buyable-Objekt bezieht, ist nat\u00fcrlich kein Zufall, sondern bewusst gew\u00e4hlt. Der Typ der this-Referenz zur Laufzeit entspricht dem der Klasse, die die Schnittstelle implementiert hat und dessen Objektexemplar gebildet wurde.<\/p>\n<p>Haben die Default-Methoden weitere Parameter, so lassen sie auch diese weiter an die statische Methode reichen:<\/p>\n<p>class PriceUtils {<\/p>\n<p>public static boolean isBuyable( Buyable b ) { return b.price() &gt; 0; }<\/p>\n<p>public static double <b>defaultPrice( Buyable b, double defaultPrice )<\/b> {<\/p>\n<p>if ( b != null &amp;&amp; b.price() &gt; 0 )<\/p>\n<p>return b.price();<\/p>\n<p>return defaultPrice;<\/p>\n<p>}<\/p>\n<p>}<\/p>\n<p>interface Buyable {    <br \/>&#160; double price();<\/p>\n<p>default boolean isBuyable() { return PriceUtils.isBuyable( this ); }<\/p>\n<p>default double defaultPrice( double <b>defaultPrice<\/b> ) {<\/p>\n<p>return <b>PriceUtils.defaultPrice( this, defaultPrice )<\/b>; }     <br \/>}<\/p>\n<p>Es ist vorzuziehen, die Implementierung auszulagern, um die Schnittstellen nicht so Code-lastig werden zu lassen. Nutzt das JDK Default-Code, so gibt es in der Regel immer eine statische Methode in einer Utility-Klasse.<\/p>\n<p><strong>Neue M\u00f6glichkeiten mit Default-Methoden *<\/strong><\/p>\n<p>Default-Methoden geben Bibliotheksdesignern ganz neue M\u00f6glichkeiten. Heute ist noch gar nicht richtig abzusehen, was Entwickler damit machen werden und welche Richtung die Java-API einschlagen wird. Auf jeden Fall wird sich die Frage stellen, ob Standard-Implementierung als Default-Code in Schnittstellen wandert, oder wie bisher, Standard-Implementierungen als abstrakte Klasse bereitgestellt wird, von dem wiederum andere Klassen ableiten. Als Beispiel sei auf die Datenstrukturen verwiesen: Eine Schnittstelle Collection schreibt Standardverhalten vor, AbstractCollection gibt eine Implementierung soweit m\u00f6glich vor, und Unterklassen wie Listen setzen dann noch einmal auf diese Basisimplementierung auf. Erweiterte Schnittstellen k\u00f6nnen Hierarchien abbauen, denn auf eine abstrakte Basisimplementierung kann verzichtet werden. Auf der anderen Seite kann aber eine abstrakte Klasse Zustand \u00fcber Objektvariablen einf\u00fchren, was eine Schnittstelle nie k\u00f6nnte.<\/p>\n<p>Default-Methoden k\u00f6nnen aber noch etwas ganz anderes: Sie k\u00f6nnen als Bauelemente f\u00fcr Klassen dienen. Eine Klasse kann mehrere Schnittstellen mit Default-Methoden implementieren und erbt im Grunde damit Basisfunktionalit\u00e4t von verschiedenen Stellen. In anderen Programmiersprachen ist das als Mixin bzw. Trait bekannt.<\/p>\n<hr size=\"1\" width=\"33%\" \/>\n<p><a href=\"file:\/\/\/C:\/Users\/Christian\/Dropbox\/Insel\/todo\/#_ftnref1_4694\" name=\"_ftn1_4694\">[1]<\/a> Der Name hat sich w\u00e4hrend der Planung f\u00fcr dieses Feature mehrfach gewandelt. Ganz am Anfang war der Name \u201edefender methods\u201c im Umlauf, dann lange Zeit virtuelle Erweiterungsmethoden (engl. virtual extension methods).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ist eine Schnittstelle einmal verbreitet, so sollte es dennoch m\u00f6glich sein, Operationen hinzuzuf\u00fcgen. Java 8 bringt daf\u00fcr eine Sprach\u00e4nderung mit, die es Entwicklern erlaubt, neue Operationen einzuf\u00fchren, ohne dass Unterklassen verpflichtet werden, diese Methoden zu implementieren. Damit das m\u00f6glich ist, muss die Schnittstelle eine Standard-Implementierung mitbringen. Auf diese Weise ist das Problem gel\u00f6st, denn wenn [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","_links_to":"","_links_to_target":""},"categories":[11,66],"tags":[],"class_list":["post-1621","post","type-post","status-publish","format-standard","hentry","category-insel","category-java-8"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/1621","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/comments?post=1621"}],"version-history":[{"count":1,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/1621\/revisions"}],"predecessor-version":[{"id":1622,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/1621\/revisions\/1622"}],"wp:attachment":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/media?parent=1621"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/categories?post=1621"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/tags?post=1621"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}