{"id":1629,"date":"2012-12-23T13:09:24","date_gmt":"2012-12-23T11:09:24","guid":{"rendered":"http:\/\/www.tutego.de\/blog\/javainsel\/?p=1629"},"modified":"2012-12-23T13:09:24","modified_gmt":"2012-12-23T11:09:24","slug":"default-methoden-teil-2-default-methoden-zur-entwicklung-von-bausteinen-nutzen","status":"publish","type":"post","link":"https:\/\/www.tutego.de\/blog\/javainsel\/2012\/12\/default-methoden-teil-2-default-methoden-zur-entwicklung-von-bausteinen-nutzen\/","title":{"rendered":"Default-Methoden, Teil 2, Default-Methoden zur Entwicklung von Bausteinen nutzen"},"content":{"rendered":"<p>Bevor wir zu n\u00e4chsten Punkt kommen, m\u00fcssen wir noch einmal inne halten und uns fragen, was denn das Kernkonzept der objektorientierten Programmierung ist. Wohl ohne zu Z\u00f6gern k\u00f6nnen wir Klassen und Kapselung nennen. Klassen und Klassenbeziehungen das Ger\u00fcst jedes Java-Programms. Schauen wir uns Vererbung noch einmal genauer an, so wissen wir, das Unterklassen Spezialisierungen sind, und das Liskovsche Substitutionsprinzip gilt: Falls ein Typ gefordert ist, k\u00f6nnen wir auch einen Untertyp \u00fcbergeben. So sollte perfekte Vererbung aussehen: Eine Unterklasse sollte das Verhalten spezialisieren, aber nicht einfach von einer Klasse erben, weil sie n\u00fctzliche Funktionalit\u00e4t hat. Aber warum eigentlich nicht? Ein Problem ist, das uns die Einfachvererbung nur eine einzige Oberklasse erlaubt. Wenn eine Klasse so etwas N\u00fctzliches wie Logging anbietet, und unsere Klasse davon erbt, kann sie nicht gleichzeitig von einer anderen Klasse erben, um zum Beispiel Zust\u00e4nde in Konfigurationsdaten festzuhalten. Das Problem bei der \u201eFunktionalit\u00e4tsvererbung\u201c ist also, dass wir uns nur einmal festlegen k\u00f6nnen. Wenn eine Klasse eine gewisse Funktionalit\u00e4t einfach braucht, woher soll sie denn dann kommen, wenn nicht aus der Oberklasse? Eigentlich gibt es hier nur eine naheliegende Variante: Die Klasse greift auf andere Objekte zur\u00fcck per Delegation. Das ist interessant, aber auch nicht optimal, insbesondere gilt dann nicht die ist-eine-Art-von-Beziehung. Falls das nicht gew\u00fcnscht ist, ist das in Ordnung, doch wenn \u00fcber diesen Typ eine Abstraktion l\u00e4uft, ist das ung\u00fcnstig. <\/p>\n<p>Ein Dilemma. Gut w\u00e4re eine Technik, die einen Programmbaustein in eine Klasse setzen kann. Im Grunde so etwas wie Mehrfachvererbung, aber doch anders, weil die Bausteine nicht als komplette Typen auftreten \u2013 der Baustein selbst ist nur ein Implantat und alleine uninteressant. Auch ein Objekt kann von diesem Baustein-Typ nicht erzeugt werden.<\/p>\n<p>Am ehesten sind die Bausteine mit abstrakten Klassen vergleichbar, doch das w\u00e4ren Klassen und Nutzer k\u00f6nnten nur einmal von diesem Baustein erben. Mit Java 8 gibt es aber eine ganz neue M\u00f6glichkeit, und zwar mit den erweiterten Schnittstelle: Sie bilden die Bausteine, von denen Klassen Funktionalit\u00e4t bekommen k\u00f6nnen. Andere Programmiersprachen bieten so etwas \u00c4hnliches und das Konzept wird dort Mixin oder Trait genannt.<a href=\"file:\/\/\/C:\/Users\/Christian\/Dropbox\/Insel\/todo\/#_ftn1_7124\" name=\"_ftnref1_7124\">[1]<\/a> Diese Bausteine sind n\u00fctzlich, denn so l\u00e4sst sich ein Algorithmus in eine extra Compilationseinheit setzen und leichter wiederverwenden. Ein Beispiel.<\/p>\n<p>Nehmen wir zwei erweiterte Schnittelle an: PersistentPreference und Logged. Die erste erweiterte Schnittstelle soll mit store() Schl\u00fcssel\/Werte-Paare in die zentrale Konfiguration schreiben und get() soll sie auslesen:<\/p>\n<p>import java.util.prefs.Preferences;<\/p>\n<p>interface PersistentPreference {<\/p>\n<p> default void store( String key, String value ) {<\/p>\n<p>&#160; Preferences.userRoot().put( key, value );<\/p>\n<p> }<\/p>\n<p> default String get( String key ) {<\/p>\n<p>&#160; return Preferences.userRoot().get( key, &quot;&quot; );<\/p>\n<p> }<\/p>\n<p>}<\/p>\n<p>Die zweite erweiterte Schnittstelle ist Logged und bietet drei kompakte Logger-Methoden:<\/p>\n<p>import java.util.logging.*;<\/p>\n<p>interface Logged {<\/p>\n<p> default void error( String message ) {<\/p>\n<p>&#160; Logger.getLogger( getClass().getName() ).log( Level.SEVERE, message );<\/p>\n<p> }<\/p>\n<p> default void warn( String message ) {<\/p>\n<p>&#160; Logger.getLogger( getClass().getName() ).log( Level.WARNING, message );<\/p>\n<p> }<\/p>\n<p> default void info( String message ) {<\/p>\n<p>&#160; Logger.getLogger( getClass().getName() ).log( Level.INFO, message );<\/p>\n<p> }<\/p>\n<p>}<\/p>\n<p>Eine Klasse kann diese Bausteine nun einbauen:<\/p>\n<p>class Player implements PersistentPreference, Logged {<\/p>\n<p> \/\/ \u2026<\/p>\n<p>}<\/p>\n<p>Die Methoden sind nun Teil vom Player und k\u00f6nnen auch von Unterklassen \u00fcberschrieben werden. Als Aufgabe f\u00fcr den Leser bleibt, die Implementierung von store() im Player zu ver\u00e4ndern, dass der Schl\u00fcssel immer mit \u201eplayer.\u201c beginnt. Die Frage, die Leser beantworten sollten ist, ob store() von Player auf das store() von der erweiterten Schnittstelle zugreifen kann. <\/p>\n<p><strong>Default-Methoden weiter gedacht<\/strong><\/p>\n<p>F\u00fcr diese Bausteine, also die erweiterten Schnittstellen, gibt es viele Anwendungsf\u00e4lle. Da die Java- Bibliothek schon an die 20 Jahre als ist, w\u00fcrden heute einige Typen anders aussehen. Dass sich Objekte mit equals() vergleichen lassen k\u00f6nnen, k\u00f6nnte heute zum Beispiel in einer erweiterten Schnittstelle stehen, etwa so: interface Equals { boolean equals( Object that ) default { return this == that; } }. So m\u00fcsste java.lang.Object die Methode nicht f\u00fcr alle vorschreiben, wobei das sicherlich jetzt kein Nachteil ist. Nat\u00fcrlich gilt das gleiche auf f\u00fcr die hashCode()-Methode, die heutzutage aus einer erweiterten Schnittstelle Hashable stammen k\u00f6nnte.<\/p>\n<p>Und java.lang.Number ist ein weiters Beispiel. Die abstrakte Basisklasse f\u00fcr Werte-repr\u00e4sentierende Objekte deklariert die abstrakten Methoden doubleValue(), floatValue(), intValue(), longValue() und die konkreten Methoden byteValue() und shortValue(). Bisher erben <a href=\"http:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/util\/concurrent\/atomic\/AtomicInteger.html\">AtomicInteger<\/a>, <a href=\"http:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/util\/concurrent\/atomic\/AtomicLong.html\">AtomicLong<\/a>, <a href=\"http:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/math\/BigDecimal.html\">BigDecimal<\/a>, <a href=\"http:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/math\/BigInteger.html\">BigInteger<\/a>, <a href=\"http:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/lang\/Byte.html\">Byte<\/a>, <a href=\"http:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/lang\/Double.html\">Double<\/a>, <a href=\"http:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/lang\/Float.html\">Float<\/a>, <a href=\"http:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/lang\/Integer.html\">Integer<\/a>, <a href=\"http:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/lang\/Long.html\">Long<\/a>, <a href=\"http:\/\/docs.oracle.com\/javase\/7\/docs\/api\/java\/lang\/Short.html\">Short<\/a> von dieser Oberklasse. Auch diese Funktionalit\u00e4t lie\u00dfe sich mit einer erweiterten Schnittstelle umsetzen.<\/p>\n<p>Da Schnittstellen auch Generics haben k\u00f6nnen, werden Default-Methoden noch vielseitiger. Baustein k\u00f6nnen auch andere Bausteine erweitern, da eine Schnittstelle andere Schnittstellen extenden kann. Es ist dabei egal, ob die die Schnitten erweitert sind oder nicht.<\/p>\n<p><strong>Zustand in den Bausteinen?<\/strong><\/p>\n<p>Nicht jeder w\u00fcnschenswerte Baustein ist mit erweiterten Schnittstellen m\u00f6glich. Ein Grund ist, dass die Schnittstellen keinen Zustand einbringen k\u00f6nnen. Einen Baustein f\u00fcr einen Container k\u00f6nnen wir nicht so einfach implementieren, da ein Container Kinder verwaltet, und hierf\u00fcr ist eine Objektvariable f\u00fcr den Zustand n\u00f6tig. Schnittstellen haben nur statische Variablen und die sind f\u00fcr alle sichtbar und selbst wenn die Schnittstelle eine modifizierbare Datenstruktur referenzieren w\u00fcrde, w\u00fcrde jeder Nutzer des Container-Bausteins von den Ver\u00e4nderungen betroffen sein. Da es keinen Zustand gibt, existieren auch f\u00fcr Schnittstellen keine Konstruktoren und folglich auch nicht f\u00fcr solche Bausteine. Denn wo es keinen Zustand gibt, gib es nichts zu initialisieren. Wenn eine Default-Methode einen Zustand ben\u00f6tigt, m\u00fcssen sie selbst diesen Zustand erfragen. Wie das geht zeigt folgendes Beispiel.<\/p>\n<p>Repr\u00e4sentiert eine Klasse eine Menge von Objekten, die sich sortieren lassen k\u00f6nnen, k\u00f6nnen wir einen Baustein Sortable mit einer Methode sort() realisieren. Allerdings muss die Implementierung irgendwie an die Daten kommen und hier kommt der Trick ins Spiel: Zwar ist sort() eine Default-Methode, doch die erweiterte Schnittstelle besitzt Methoden, die die Klasse implementieren muss, die dem Sortierer die Daten geben. Im Quellcode sieht das so aus:<\/p>\n<p>Teil 1:<\/p>\n<p>import java.util.*;<\/p>\n<p>interface Sortable&lt;T extends Comparable&gt; {<\/p>\n<p>&#160; T[] getValues();<\/p>\n<p>&#160; void setValues( T[] values );<\/p>\n<p>&#160; default void sort() {<\/p>\n<p>&#160;&#160;&#160; T[] values = getValues();<\/p>\n<p>&#160;&#160;&#160; Arrays.sort( values );<\/p>\n<p>&#160;&#160;&#160; setValues( values );<\/p>\n<p>&#160; };<\/p>\n<p>}<\/p>\n<p>Damit sort() an die Daten kommt, erwartet Sortable von den implementieren Klassen eine Methode getValues(). Und damit die Daten nach dem Sortieren wieder zur\u00fcckgeschrieben werden k\u00f6nnen, eine zweite Methode setValues(\u2026). Der Clou ist, das die Klasse, die sp\u00e4ter Sortable realisieren wird, mit den beiden Methoden dem Sortierer Zugriff auf den Daten gew\u00e4hrt \u2013 allerdings auch jedem anderem St\u00fcck Code da die Methoden \u00f6ffentlich sind. Da bleibt ein Geschm\u00e4ckle.<\/p>\n<p>Ein Nutzer vor Sortable soll RandomValues sein; die Klasse erzeugt intern Zufallszahlen.<\/p>\n<p>Teil 2:<\/p>\n<p>class RandomValues implements Sortable&lt;Integer&gt;<\/p>\n<p>{<\/p>\n<p>&#160; private List&lt;Integer&gt; values = new ArrayList&lt;&gt;();<\/p>\n<p>&#160; public RandomValues() {<\/p>\n<p>&#160;&#160;&#160; Random r = new Random();<\/p>\n<p>&#160;&#160;&#160; for ( int i = r.nextInt( 20 ) + 1; i &gt; 0; i&#8211; )<\/p>\n<p>&#160;&#160;&#160; values.add( r.nextInt(10000) );<\/p>\n<p>&#160; }<\/p>\n<p>&#160; @Override public Integer[] getValues() {<\/p>\n<p>&#160;&#160;&#160; return values.toArray( new Integer[values.size()] );<\/p>\n<p>&#160; }<\/p>\n<p>&#160; @Override public void setValues( Integer[] values ) {<\/p>\n<p>&#160;&#160;&#160; this.values.clear();<\/p>\n<p>&#160;&#160; Collections.addAll( this.values, values );<\/p>\n<p>&#160; }<\/p>\n<p>}<\/p>\n<p>Damit sind die Typen vorbereitet und ein Demo schlie\u00dft das Beispiel ab:<\/p>\n<p>Teil 3:<\/p>\n<p>public class SortableDemo {<\/p>\n<p>&#160; public static void main( String[] args ) {<\/p>\n<p>&#160;&#160;&#160; RandomValues r = new RandomValues();<\/p>\n<p>&#160;&#160;&#160; System.out.println( Arrays.toString( r.getValues() ) );<\/p>\n<p>&#160;&#160;&#160; r.sort();<\/p>\n<p>&#160;&#160;&#160; System.out.println( Arrays.toString( r.getValues() ) );<\/p>\n<p>&#160; }<\/p>\n<p>}<\/p>\n<p>Aufgerufen kommt auf die Konsole zum Beispiel:<\/p>\n<p>[2732, 4568, 4708, 4302, 4315, 5946, 2004]<\/p>\n<p>[2004, 2732, 4302, 4315, 4568, 4708, 5946]<\/p>\n<p>So interessant diese M\u00f6glichkeit auch ist, ein Problem wurde schon angesprochen: Jede Methode in einer Schnittstelle ist public, ob sie nun eine abstrakte oder Default-Methode ist. Es w\u00e4re sch\u00f6n, wenn die Datenzugriffsmethoden nicht \u00f6ffentlich sein w\u00fcrden, aber das geht nicht.<\/p>\n<p>Wo wir gerade bei der Sichtbarkeit sind. Gibt es im Default-Code Code-Duplizierung, so kann der gemeinsame Code bisher nicht in private Methoden ausgelagert werden, da es private Operationen in Schnittstellen nicht gibt. Allerdings l\u00e4uft gerade ein Test, ob so etwas eingef\u00fchrt werden soll.<\/p>\n<p>Warnung!<\/p>\n<p>Nat\u00fcrlich l\u00e4sst sich mit Rumgetrickse ein Speicherort finden, der Exemplarzust\u00e4nde speichert. Es l\u00e4sst sich zum Beispiel in der Schnittstelle ein Assoziativspeicher referenzieren, der eine this-Instanz mit einem Objekt assoziiert. Ein Container-Baustein, der mit add() Objekte in eine Liste setzt und sie mit iterable() herausgibt, k\u00f6nnte so aussehen:<\/p>\n<p>interface ListContainer&lt;T&gt; {<\/p>\n<p> Map&lt;Object, List&lt;Object&gt;&gt; $ = new HashMap&lt;&gt;();<\/p>\n<p> default void add( T e ) {<\/p>\n<p>&#160; if ( ! $.containsKey( this ) )<\/p>\n<p>&#160;&#160; $.put( this, new ArrayList&lt;Object&gt;() );<\/p>\n<p> $.get( this ).add( e );<\/p>\n<p> }<\/p>\n<p> default public Iterable&lt;T&gt; iterable() {<\/p>\n<p>&#160; if ( ! $.containsKey( this ) )<\/p>\n<p>&#160;&#160; return Collections.emptyList();<\/p>\n<p>&#160; return (Iterable&lt;T&gt;) $.get( this );<\/p>\n<p> }<\/p>\n<p>}<\/p>\n<p>Nicht nur die \u00f6ffentliche Konstante $ ist ein Problem, sondern auch, dass es ein gro\u00dfartiges doppeltes Speicherloch ist. Ein Exemplar der Klasse, die diese erweitert Schnittstelle nutzt, kann nicht so einfach entfernt werden, denn in der Sammlung ist noch eine Referenz auf das Objekt, die das Garbage Collection verhindert. Und selbst wenn dieses Objekt weg w\u00e4re, h\u00e4tten wir noch all die referenzierten Kinder der Sammlung in der Map. Und das Problem ist nicht wirklich zu l\u00f6sen, und hier m\u00fcsste tief mit schwachen Referenzen in die Java-Voodoo-Kiste gegriffen werden. Alles in allem, keine gute Idee und Java-Chefentwickler Brian Goetz macht auch klar: \u201ePlease don&#8217;t encourage techniques like this. There are a zillion &quot;clever&quot; things you can do in Java, but shouldn&#8217;t. We knew it wouldn&#8217;t be long before someone suggested this, and we can&#8217;t stop you. But please, use your power for good, and not for evil. Teach people to do it right, not to abuse it.\u201d<a href=\"file:\/\/\/C:\/Users\/Christian\/Dropbox\/Insel\/todo\/#_ftn2_7124\" name=\"_ftnref2_7124\">[2]<\/a> Daher: Es ist eine sch\u00f6ne Spielerei, aber Zustand sollte eine Aufgabe der abstrakten Basisklassen oder vom Delegate sein.<\/p>\n<p><strong>Zusammenfassung<\/strong><\/p>\n<p>Was wir in den letzten Beispielen gemacht haben war, ein Standardverhalten in Klassen einzubauen, ohne das dabei der Zugriff auf die einmalige Basisklasse n\u00f6tig war und ohne das die Klasse an Hilfsklassen delegiert. In dieser Arbeitsweise k\u00f6nnen Unterklassen in jedem Fall die Methoden \u00fcberschreiben und spezialisieren. Wie haben es also mit \u00fcblichen Klassen zu tun und mit erweiterten Schnittstellen, die nicht selbst eigenst\u00e4ndige Entit\u00e4ten bilden. In der Praxis wird es immer F\u00e4lle geben, in denen f\u00fcr eine Umsetzung eines Problems entweder eine abstrakte Klasse oder eine erweiterte Schnittstelle in Frage kommt. Wir sollten und dann noch einmal an die Unterschiede erinnern: Eine abstrakten Klasse kann Methoden aller Sichtbarkeiten haben und sie auch final setzen, sodass sie nicht mehr \u00fcberschrieben werden k\u00f6nnen. Eine Schnittstelle dagegen ist mit puren virtuellen und \u00f6ffentlichen Methoden darauf ausgelegt, dass eben die Implementierung \u00fcberschrieben werden kann.<\/p>\n<hr size=\"1\" width=\"33%\" \/>\n<p><a href=\"file:\/\/\/C:\/Users\/Christian\/Dropbox\/Insel\/todo\/#_ftnref1_7124\" name=\"_ftn1_7124\">[1]<\/a> Siehe etwa <a href=\"http:\/\/scg.unibe.ch\/archive\/papers\/Scha02aTraitsPlusGlue2002.pdf\">http:\/\/scg.unibe.ch\/archive\/papers\/Scha02aTraitsPlusGlue2002.pdf<\/a>. <\/p>\n<p><a href=\"file:\/\/\/C:\/Users\/Christian\/Dropbox\/Insel\/todo\/#_ftnref2_7124\" name=\"_ftn2_7124\">[2]<\/a> <a href=\"http:\/\/mail.openjdk.java.net\/pipermail\/lambda-dev\/2012-July\/005166.html\">http:\/\/mail.openjdk.java.net\/pipermail\/lambda-dev\/2012-July\/005166.html<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Bevor wir zu n\u00e4chsten Punkt kommen, m\u00fcssen wir noch einmal inne halten und uns fragen, was denn das Kernkonzept der objektorientierten Programmierung ist. Wohl ohne zu Z\u00f6gern k\u00f6nnen wir Klassen und Kapselung nennen. Klassen und Klassenbeziehungen das Ger\u00fcst jedes Java-Programms. Schauen wir uns Vererbung noch einmal genauer an, so wissen wir, das Unterklassen Spezialisierungen sind, [&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-1629","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\/1629","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=1629"}],"version-history":[{"count":0,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/1629\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/media?parent=1629"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/categories?post=1629"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/tags?post=1629"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}