JDK8 Bestpractice: Wann Defaultmethoden nutzen

Hallo,

nachdem der aktuelle JBoss ja JDK8 vollständig unterstützt, habe ich endlich die Möglichkeit, mich mit Java 8 stärker zu beschäftigen.

Aktuell schaue ich mir Defaultmethoden genauer an. Nun kam ein Kollege auf die Idee eine eigene StringUtils (ja, ich weiß, dass es hier bereits Implementierungen gibt, aber das soll hier nicht Gegenstand sein) nun als Interface umzusetzen. Das entspricht nicht ganz meinem OO-Verständnis, aber auf die Weise lässt sich Mehrfachvererbung stärker etablieren.

Wie steht ihr dazu? Wann verwendet ihr Defaultmethoden? Habt ihr dazu Beispiele, wo es für Euch total sinnvoll ist?

Gruß Sym

Ich habe zwar mit dem Jdk 8 nur herumgespielt, wäre aber damit vorsichtig. Ich würde diese dann einsetzen, wenn ich ein Schnittstelle erweitern möchte - aber noch kompatibel zu älteren Versionen bleiben will.

Ein weiteres Szenario hab ich auch mal vor längerem hier beschrieben: http://forum.byte-welt.net/entries/24-Java-8-Lambda-und-Defaultmethoden
(Wobei ich skeptisch bin, ob man das wirklich einsetzen sollte. Ich denke, ich würde darauf eher verzichten)

Die Defaultmethoden wurdenursprünglich für die Evolution von Schnittstellen definiert durch Interfaces entwickelt. D.h. die Implementierungen sind nun nicht mehr gezwungen die Methode zu überschreiben wenn eine Defaultimplementierung vorliegt. Zu deiner Frage mit den “Util”-Klassen. Ich würde es davon abhängig machen ob die Util-Klasse auf Typen arbeitet die durch ein Interface definiert wurden, falls ja dann würde ich heute alle statischen util-Methoden in das Interface packen. Ist das nicht der Fall bevorzuge ich den alten Stil das in eine finale-Klasse mit statischen Methoden zu packen. Eigentlich statische Util-Methoden durch “default”-Methoden in einem Interface unterzubringen, eher nicht.

Aber was ist Deine Begründung. Warum sollte man damit vorsichtig sein. Viele Dinge, die man früher vielleicht in eigene Klassen ausgelagert hat, könnten durch dieses Pattern jetzt doch wieder Teile der eigentlichen Klasse sein und sind ebenso wiederverwendbar.

Ja, für mich würde ein StringUtils als Interface meist wenig Sinn haben, weil die Methoden (z.B. isEmpty) selten eine Methode der nutzenden Klasse sein sollten - zumindest aus OO-Sicht.

Ich habe mich über default-Methoden sehr gefreut und nutze sie auch für Utils-Methoden/Klassen. Dem liegt zu Grunde, dass ich gerne alles, was public ist, über Interfaces zur Verfügung stelle. Grund: Die Benutzer meiner API sind damit unabhängiger von meinen Implementierungen. So können sie die default-Utilmethoden zunächst so verwenden, wie von mir programmiert (eben die default-Implementierung). Wenn sie dann merken, dass meine Implementierung aus irgendeinem Grund schlecht ist, können sie sie sehr leicht austauschen. Bei einem direkten Aufruf einer statischen Methode ist das praktisch nicht möglich. Im Prinzip erleichere ich den Benutzern meiner API, sich vor externen Abhängigkeiten (in diesem Fall meinem Code) abzuschirmen.

Da Du nach Best Practices gefragt hattest: default-Methoden, die abstrakte Methoden desselben Interfaces aufrufen, sind meist “gute” default-Methoden.

Das verwende ich z.B. um Entity-Klassen mit mehr Funktionalität anzureichern als nur Gettern und Settern. Dazu ein Interface für die Entity-Klasse mit abstrakten Gettern, eine Erweiterung dieses Interfaces mit den zugehörigen Settern (dann hat man immutable und mutable schön sauber getrennt) und ein paar default-Methoden zur Überprüfung von Preconditions. Da muss man nur ein bischen aufpassen, dass man sich nicht dazu verleiten lässt, “Separation of Concerns” zu verletzten. Aber Definition und Überprüfung von Preconditions gehören für mich definitiv in den Scope einer Entity-Klasse.

Weil imho interfaces so wenig wie möglich über die Implementierung wissen sollten. Und dementsprechend würde ich auch nicht ein frisches Interface erstellen und darin gleich die Methoden implementieren. Dafür kann man dann gleich eine (abstrakte) Klasse verwenden.

Ich kann aber nur von einer abstrakten Klasse erben. Zumindest sieht Oracle das ja so, dass Defaultimplentierungen an Interfaces sinnvoll sind. Hast Du noch weitere Gründe, warum Du damit vorsichtig wärst?

@Sym

Ja, bei StringUtils sieht es ja eher so aus das man selbst keine Kontrolle über die Klasse String hat. Und um noch mal zu visualisieren was ich meinte

Früher

interface Typ{}
    
    public final class TypCompanion{
        private TypCompanion(){};
        public static void utilMethode1(Typ typ){}
        public static void utilMethode2(Typ typ){}
    }

Heute:

interface Typ{
        static void utilMethode1(Typ typ){}
        static void utilMethode2(Typ typ){}
    }

Und was ich nicht tun würde

    interface Typ{
        default void utilMethode1(Typ typ){}
        default void utilMethode2(Typ typ){}
    }

Ne, aber viel mehr Gedanken hab ich mir bisher dazu auch noch nicht gemacht. Ich sehe da einfach die Gefahr, dass man Interfaces als neue Klassen ansieht (z.B. von Leuten die aus dem C+±Bereich kommen und sich denken „ah super, so hab ich meine Mehrfachvererbung“). Ich würde nach wie vor einfach versuchen Interfaces als das anzusehen, was sie auch vor J8-Zeiten waren + eben die Möglichkeit seine Schnittstellen besser abwärtskompatibel zu halten.

Kann natürlich auch sein, dass ich hier ein wenig übervorsichtig bin.

Das war auch mein erster Einwand, nachdem ich mich in meinem letzten Post ja sehr für die Nutzung von default-Methoden auch bei Util-Klassen ausgesprochen hatte. Nachdem ich darüber etwas nachgedacht habe, denke ich, dass diese Einschränkung gerade bei Utils evtl. sogar erwünscht sein kann. Ich würde es nämlich als schlechten Stil ansehen, sowas zu machen wie z.B. „implements MathUtil, XMLUtil“. Mit abstrakten Klassen würde man das wirksam verhindern. Ich mag mich von dem radikalen Ansatz „only Interfaces public“ noch nicht ganz verabschieden, aber zumindest hat mich Tomate_Salat da etwas ins Wanken gebracht.

Moin,

ich habe mich mit den Defaultmethoden noch nicht ernsthaft beschäftigt, aber wie sieht das bei den zugehörigen Tests aus? Handelt man sich damit nicht schnell Abhängigkeiten zu Code ein, den man gar nicht testen will? Ich fände es z.B. schräg, wenn ein Fehler in der oben erwähnten StringUtils dazu führt, das hunderte Tests fehlschlagen, nur weil diese zufällig die StringUtils nutzen.

Und wie gehen die Mock Toolkits mit so einem Interface um? Wenn die sich ähnlich wie bei static verhalten, wird das doch schnell wieder ungemütlich. Und spätestens wenn Tests parallel laufen sollen, wird es unzuverlässig (oder man muss für jeden Test die JVM neu forken).

Ich implementiere solche Util Klassen inzwischen nur noch als normale Klassen und injiziere diese als singleton scope. Einfach zu schreiben, einfach zu testen. Und bei Klassen die diese verwenden sorgt das Mock Toolkit automatisch für Unabhängigkeit im entsprechenden Test.

Mein bisheriges Gefühl ist Defaultmethoden gar nicht zu verwenden. Sollte ich ein Interface erweitern müssen, gibt es eine neue Major Version. Alte Anwendungen nutzen weiterhin die alte, neue bekommen die neue als Dependency. Das schlimmste was dann passieren kann, ist das für ein Bugfix ein backport fällig wird.

Viele Grüße
Fancy

Bin ich mit plain Mockito schon drüber gestolpert. Ein Interface mit default-Methoden per „Mockito.mock“ mocken, kann man machen. Aber man darf von dem Mock dann nicht das Verhalten der default-Methoden erwarten. Mockito überschreibt diese nämlich, sodass man eine nichts-machen-und-null-returnen-Methode hat (was ja auch der Sinn eines Mocks ist). Hätte man durch Nachdenken drauf kommen können, dass das so sein wird. Ich musste es aber durch ausprobieren erfahren.

Je länger ich drüber nachdenke, desto eher neige ich dazu, für Utils doch wieder auf Klassen zurück zu gehen. Als einziges Pro-Argument bleibt nämlich nur noch übrig, dass man sich die eine Implementierungsklasse spart (das, was ThreadPool mit Companion beschrieben hat). …Verdammt!

Zu Fragen bzgl. der Tests gerade mit Mockito bin ich heute auch gekommen. Mein aktueller Stand ist, dass ich Util-Klassen meist weiter so verwenden würde, wie ich es aktuell auch schon tue. Ich habe ebenfalls Instanziierbare Klassen, die ich entweder direkt im Code instanziiere oder wenn möglich per DI verwende.

Allerdings habe ich in einem Bereich einen guten Ansatz gefunden: Ich habe selbstentwickelte Komponenten (Text, Number, Textarea, …). Die zugehörigen Renderer haben bisher immer andere Renderer instanziiert um Verhalten zu implementieren, die von mehreren Komponenten verwendet werden.

Beispiel:

TextRenderer - rendert ein Texteingabefeld
NumberRenderer - rendert ein Zahleingabefeld
PlaceHolderRenderer - rendert ein html5-placeholder-feature

Beide Komponenten haben bisher den PlaceHolderRenderer direkt instanziiert. Aber eigentlich sind diese PlaceHolderRenderer. Es gehört zu deren Verhalten. Bisher könnte ich das aber nur über abstrakte Klassen lösen. Da die Komponenten aber mehrere verschiedene Renderer nutzen und diese keine vollständige Überschneidung haben, habe ich von einer gemeinsamen Oberklasse abgesehen.

Also Code vorher:

    public void encode() {
        ...
        new PlaceHolderRenderer().encodePlaceHolder(...);
    }
}

public class NumberRenderer {
    public void encode() {
        ...
        new PlaceHolderRenderer().encodePlaceHolder(...);
    }
}```

Nun:
```public class TextRenderer implements PlaceHolderRenderer {
    public void encode() {
        ...
        this.encodePlaceHolder(...);
    }
}

public class NumberRenderer implements PlaceHolderRenderer {
    public void encode() {
        ...
        this.encodePlaceHolder(...);
    }
}```

Mit defaultMethods lies sich das nun elegant umgehen.

Das empfinde ich als wirklichen Vorteil. Wie seht ihr das?