Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | NEWSLETTER | CONTACT | Twitter | Lanyrd | Linkedin
 
HOME 

  OVERVIEW

  BY TOPIC
    JAVA
    C++

  BY COLUMN
    EFFECTIVE JAVA
    EFFECTIVE STDLIB

  BY MAGAZINE
    JAVA MAGAZIN
    JAVA SPEKTRUM
    JAVA WORLD
    JAVA SOLUTIONS
    JAVA PRO
    C++ REPORT
    CUJ
    OTHER
 

GENERICS 
LAMBDAS 
IOSTREAMS 
ABOUT 
NEWSLETTER 
CONTACT 
Effective Java - Java 8 - Default Interface Methods

Effective Java - Java 8 - Default Interface Methods  
Java 8
Default-Methoden und statische Methoden in Interfaces
 

Java Magazin, Januar 2014
Klaus Kreft & Angelika Langer

Dies ist die Überarbeitung eines Manuskripts für einen Artikel, der im Rahmen einer Kolumne mit dem Titel "Effective Java" im Java Magazin erschienen ist.  Die übrigen Artikel dieser Serie sind ebenfalls verfügbar ( click here ).

Wir haben bereits in einem unserer vorhergehenden Beiträge (/ JAV8-1 /) kurz erwähnt, dass es ab Java 8 möglich sein wird, Methoden in Interfaces zu implementieren. Warum das so ist, wie es genau geht und welche Auswirkungen dies in Zukunft auf das Programmieren in Java hat, wollen wir uns in diesem Artikel genauer ansehen.

Was sich mit Java 8 bei Interfaces ändert

Bisher war es in Java so, dass Methoden in Interfaces abstrakt sein mussten.  Das heißt, eine Methode in einem Interface legte allein ihre Signatur und ihre allgemeine Semantik fest.  Eine Implementierung konnten sie nicht haben.  Die Implementierung wurde erst von den (nicht-abstrakten) Klassen, die von dem Interface abgeleitet waren, zur Verfügung gestellt. 
Dies ändert sich mit Java 8.  Eine Default-Methode kann eine Methode mit Implementierung in einem Interface zur Verfügung stellen. Diese Implementierung wird dann an alle abgeleiteten Interfaces und Klassen vererbt, sofern diese sie nicht mit einer eigenen Implementierung überschreiben.  Dies ist eine tiefgreifende Änderung beim Programmieren in Java.  Warum sie notwendig geworden ist und was sie im Detail bedeutet, sehen wir uns weiter unten genauer an.
Kommen wir hier erst noch zu einer weiteren Änderung.  Mit Java 8 wird es auch möglich sein, statische Methoden in Interfaces zu implementieren.
In beiden Fällen (Default-Methode und statische Methode) sind die Methoden automatisch  public , wie es bei abstrakten Methoden auch bisher schon war.  Es wurde diskutiert, andere Zugriff-Modifier wie z.B.  private zuzulassen.  Am Ende hat man dies aber aus Aufwandsgründen verworfen.  Es ist aber nicht ausgeschlossen, dass Zugriff-Modifier wie z.B.  private in einem zukünftigen Java Release nach Java 8 erlaubt sein werden.  Die Details der Diskussion zu diesem Thema finden sich hier: /PBK/.

Default-Methoden

Schauen wir uns die Default-Methoden nun etwas genauer an.  Überraschend ist, dass sie im Rahmen des JSR 335 (Lambda Expressions for the Java Programming Language) entwickelt wurde.  Auf den ersten Blick erschließt sich die Beziehung zwischen Lambda-Ausdrücken und Default-Methoden nicht.  Deshalb wollen wir uns zunächst diesen Zusammenhang ansehen.
Wie wir auch in einem vorhergehenden Artikel (/ JAVA8-1 /) bereits kurz erwähnt haben, werden die Collections im JDK 8 über zusätzliche Funktionalität für Bulk Operations bzw. interne Iterierung verfügen. Auch dies ist eine Entwicklung im Rahmen des JSR 335.  Schauen wir uns dazu ein Beispiel an.  Die Details finden sich in /BOP/.  Mit dem folgenden Code kann man alle Element einer Collection (im Beispiel eine  ArrayList<Integer> ) ausgeben:

 
 

              List<Integer> numbers = new ArrayList<>();

              ... populate list ...

              numbers.forEach(number -> System.out.println(number));
 
 

Zentrales Element des Beispiels ist die mit Java 8 neu hinzugekommene  forEach() Methode.  Ihr wird ein Lambda-Ausdruck übergeben, dessen Funktionalität dann im  forEach() auf jedes Element der Collection angewandt wird.  In unserem Beispiel besteht die Funktionalität des Lambda-Ausdrucks darin, das Element mit dem Aufruf von  System.out.println() auszugeben. 

Die funktionalen Erweiterungen der Collections sind ein spannendes Thema, dem wir uns in zukünftigen Artikeln noch ausführlich widmen werden.  Heute interessiert uns aber eher die Frage:  Wo und wie ist die neue  forEach() Methode definiert? 

Aus gutem Grund sind bisher in den Interfaces des JDK Collection Frameworks keine neuen Methoden hinzugefügt worden, denn die JDK-Entwickler haben immer empfohlen eigene, benutzer-spezifische Collections so zu implementieren, dass sie von den Interfaces der JDK Collection Frameworks ableiten (siehe /CCI/).  Neue Methoden in den JDK Collection Interfaces hätten deshalb immer bedeutet, dass sich benutzer-spezifische Collections, die davon abgeleitet sind, erst einmal nicht mehr übersetzen lassen. 

Etwas verallgemeinert betrachtet ist das Problem, dass Java Interface Evolution nicht besonders gut unterstützt hat: Wenn in einem Interface neue Methoden dazu kommen, müssen alle ableitenden Klassen diese implementieren, damit sie sich weiterhin übersetzen lassen.  Das ganze ist insbesondere dann ein Problem, wenn die Entwickler, die das Interface erweitern wollen, gar nicht die Möglichkeit haben, die abgeleiteten Klassen auch anzupassen.  Genau diese Situation haben wir in unserem Beispiel oben.  Die JDK-Entwickler wollen das  Collection Interface um die Methode  forEach() erweitern. Sie können aber nicht weltweit alle benutzer-spezifischen Collections, die davon ableiten, anpassen, damit diese sich weiter übersetzen lassen.

Die Lösung dieses Problems mit Java 8 ist nun, Interface Evolution mit Hilfe von Default-Methoden zu unterstützen.  Die neue Default-Methode im Interface bringt nämlich gleich ihre Implementierung mit.  Damit sind abgeleitete Klassen nicht mehr gezwungen, die neue Methode zu implementieren.  Sie können sie aber überschreiben, wenn sie eine passendere Implementierung anbieten wollen.
 
 

Schauen wir uns an, wie die konkret Lösung im Fall von  forEach() aussieht.  Die Default-Methode ist im generischen Interface  Iterable<T> (einem Super-Interface von  Collection<T> ) definiert.  Dies ist ihre Implementierung:
 
 

              public interface Iterable<T> {

                 Iterator<T> iterator();
 
 

                 default void forEach(Consumer<? super T> action) {

                    Objects.requireNonNull(action);
 
 

                    for(T t : this)

                       action.accept(t);

                 }

              }
 
 

Die Deklaration der Default-Methode beginnt mit dem Schlüsselwort  default .  Es gab Diskussionen, ob das Schlüsselwort  default überhaupt benutzt werden sollte und wo es stehen sollte.  Syntaktisch ist es für den Compiler nicht notwendig.  Er kann auch allein am Methoden-Body erkennen, dass es sich um eine Default-Methode handelt.  Am Ende hat man sich aus zwei Gründen für die Syntax entschieden, die oben im Beispiel zu sehen ist.  Zum einen ist das Schlüsselwort  default eine Absicherung dafür, dass der Methoden-Body nicht nur versehentlich da steht (zum Beispiel durch Copy-Paste der Methode aus einer Klasse).  Der andere Grund ist, dass das man beim Blick auf den Code einfacher erkennen kann, ob es sich um eine Default-Methode handelt oder nicht.  Dies ist übrigens auch der Grund, warum man sich dafür entschieden hat, das Schlüsselwort  default ganz nach vorne zu stellen. 

Nicht überraschend ist, dass die Default-Methode ihren Namen erst nach dieser Syntax-Entscheidung bekommen hat.  Denn erst zu diesem Zeitpunkt war klar, dass das Schlüsselwort  default benutzt wird.  Vorher hatte man die Namen virtual extension method bzw. defender method für das Feature vorgesehen.

Nun ist es ab Java 8 möglich, dass Methoden in Interfaces eine Implementierung haben.  Es ist aber weiterhin nicht erlaubt, dass man in Interfaces nicht-statische, nicht-final Felder definiert.  Die Konsequenz daraus ist, dass man für die Implementierung von Default-Methoden keine Felder definieren kann.  Das bedeutet, dass man für die Implementierung einer Default-Methode im Wesentlichen nur die Funktionalität der anderen (abstrakten) Methoden des Interfaces nutzen kann. 

Schauen wir uns unter diesem Aspekt die Implementierung von  forEach() in  Iterable an.  Sie besteht aus einer for-each-Schleife, in der auf jedes Element der Collection die Funktionalität des Parameters  action angewandt wird.  Dies geschieht durch den Aufruf von  accept( ) aus dem funktionalen Interface  Consumer .  Wie das mit Lambda-Ausdrücken und ihren korrespondierenden Functional Interfaces geht, haben wir ausführlich im vorhergehenden Artikel (/LAM/) besprochen

Eine Frage steht noch im Raum.  Wo wird bei der Implementierung von  forEach() die andere Methode (d.h.  iterator() ) aus dem Interface  Iterable genutzt?  –  Bei der for-each-Schleife!  Genaugenommen können wir diese Schleife nämlich nur hinschreiben, weil das  Iterable eben diese Methode  iterator() hat, mit deren Hilfe der Compiler die for-each-Schleife folgendermaßen umwandeln kann:

              Iterator iter =  iterator() ;

              while(iter.hasNext())

                 action.accept(iter.next());
 
 

Es ist übrigens Absicht, dass Default-Methoden keine neuen Felder in Interfaces definieren können und deshalb für die Implementierung von nicht-trivialer Funktionalität auf die anderen Methoden im Interface zurückgreifen müssen.  Den Grund dafür schauen wir uns weiter unten noch mal ausführlicher an.  Triviale Funktionalität, wie das Werfen einer Runtime-Exception oder  return null , lässt sich natürlich immer in einer Default-Methode implementieren.  Bei der Erweiterung der Collections für Java 8 brauchte man auf solche Tricks aber nicht zurückgreifen.  Wie in dem hier diskutierten Beispiel war immer eine sinnvolle Implementierung der Default-Methoden auf Basis der bereits vorhandenen abstrakten Methoden möglich. 

Mehrfachvererbung von Funktionalität

Bemerkenswert ist, dass Default-Methoden dazu führen, dass Java mit der Version 8 Mehrfachvererbung von Funktionalität ermöglicht.  Zu Mehrfachvererbung kommt es zum Beispiel, wenn eine Klasse mehrere Interfaces implementiert, die alle Default-Methoden enthalten.  Die Funktionalität aller dieser Default-Methoden ist dann natürlich in der abgeleiteten Klasse verfügbar.  Zusätzlich kann unsere Klasse natürlich zusätzlich von einer anderen Klasse abgeleitet sein und deren Funktionalität auch noch erben.  Damit ist es dann in Java 8 möglich, dass eine Klasse die Funktionalität von einer Super-Klasse und beliebig vielen Super-Interfaces (mit Default-Methoden) erbt.
Bemerkenswert ist die Mehrfachvererbung von Funktionalität deshalb, weil dieses Feature beim ursprünglichen Design von Java vor fast 20 Jahren bewusst ausgeschlossen wurde.  James Gosling hat das in einem damals veröffentlichten White Paper zu Java ganz klar zum Ausdruck gebracht:
Java omits many rarely used, poorly understood, confusing features of C++ that in our experience bring more grief than benefit. This primarily consists of operator overloading (although it does have method overloading), multiple inheritance, and extensive automatic coercions. (/JAO/)
Warum jetzt der Sinneswandel mit Java 8?  Der Grund ist wohl, dass Design immer etwas Zeitbehaftetes hat, selbst wenn es sich um eine Programmiersprache handelt.  Mitte der neunziger Jahre war auf Grund der Erfahrungen mit C++ die allgemeine Meinung, dass Mehrfachvererbung von Funktionalität ein Feature ist, das äußerst komplex ist und deshalb schwer zu verstehen und einzusetzen ist.  Also ist Java so entstanden, wie wir es heute kennen: mit Mehrfachvererbung von Interfaces, aber ohne Mehrfachvererbung von Funktionalität.  Über all die Jahre hat sich dann gezeigt, dass dieser Ansatz, gerade auch mit Blick auf die Probleme bei der Interface Evolution, vielleicht ein wenig zu zurückhaltend war.  Zumal es mittlerweile auch Programmiersprachen gibt, die Mehrfachvererbung von Funktionalität mit neuen Ansätzen ganz gut in den Griff bekommen haben.  Beispiele dafür sind Mixins in Ruby und Traits in Scala.
Als dann beim Design der Erweiterung der Collection Interfaces für Bulk Operations  die Einschränkungen bezüglich Interface Evolution wieder deutlich wurden, hat mich sich entschlossen, auf Basis von Default-Methoden Mehrfachvererbung von Funktionalität in Java einzuführen.  Damit hat Java eine relativ sichere Lösung bekommen, die es dem Programmierer einfach macht, das Feature zu nutzen.
Warum die Mehrfachvererbung von Funktionalität auf Basis von Default-Methoden relativ einfach und sicher zu benutzen ist, wollen wir uns jetzt genauer ansehen. Was sind eigentlich die Probleme bei der Mehrfachvererbung von Funktionalität, die dazu geführt haben, dass dieses Feature als so schwierig gilt?  Bei Programmiersprachen, die Mehrfachvererbung von Klassen unterstützen (typisches Beispiel: C++), ist es möglich, die Vererbung unter vier Klassen so zu implementieren, dass sie einen rautenförmigen Vererbungsgraphen bilden:
Abbildung 1: Mehrfachvererbung – „Deadly Diamond of Death"

 
 

Von der Klasse A erben die beiden Klassen B und C.  Die Klasse D ist via Mehrfachvererbung von diesen beiden Klassen abgeleitet.  Das Problem ist nun: Wie häufig sind die Attribute von A in D enthalten?  Einmal oder zweimal?  Ewas weniger formal ausgedrückt lautet die Frage:  Ist der A-Teil, den D über B und C erbt, der gleiche? (Dann sind die Attribute aus A nur einmal vererbt worden.)  Oder erbt D über B und C je einen anderen A-Teil? (Dann sind die Attribute aus A zweimal vererbt worden.)  Auf diese Frage gibt es keine allgemein richtige Antwort.  Vielmehr ist die richtige Antwort jeweils von der konkreten Situation abhängig.  Dies macht Mehrfachvererbung so schwierig.  Nicht umsonst nennt sich dieser Vererbungsgraph auch "Deadly Diamond of Death ".  C++ bietet die Möglichkeit, beide Varianten (mit virtueller bzw. nicht-virtueller Ableitung) zu implementieren, aber die Entscheidung, welches die richtige Antwort ist, muss der Programmierer selbst fällen.
 
 

Bei Javas Mehrfachvererbung von Funktionalität auf Basis von Default-Methoden stellt sich die Frage glücklicherweise erst gar nicht: 

Wenn D eine Klasse in Java ist, die von B und C Funktionalität erbt, dann muss mindestens einer der Typen B und C ein Interface sein, da es in Java auch weiterhin keine Mehrfachvererbung von Klassen gibt. 

Wenn aber mindestens B oder C ein Interface ist, dann muss auch A ein Interface sein, denn ein Interface kann in Java nicht von einer Klasse abgeleitet sein.

Wenn nun A ein Interface ist, dann kann es zwar Funktionalität in Form von Default-Methoden enthalten, trotzdem hat A keine Felder.  So dass sich die Frage, wie häufig dieFelder von A in D enthalten sein sollen, gar nicht stellt.
 
 

Aus diesem Grund ist die Mehrfachvererbung von Funktionalität in Java relativ einfach zu handhaben (im Vergleich zu C++).
 
 

Man sieht also: Es ist entscheidend, (nicht-statische, nicht-finale) Felder in Interfaces weiterhin zu verbieten, damit die Mehrfachvererbung in Java problemlos ist. Um einem möglichen Missverständnis vorzubeugen, sei darauf hingewiesen, dass dies nicht bedeutet, dass Default-Methoden stateless sein müssen.  Da Default-Methoden in ihrer Implementierung andere (zumeist abstrakte) Methode des Interfaces benutzen, können sie über diese Methoden sehr wohl in einer konkreten Klasse, in die sie vererbt werden, Attribute und damit den Zustand einer Instanz dieser Klasse verändern.  Das geht dann indirekt über die aufgerufenen Methoden.

Neue Syntax und Regeln

Wie wir bisher gesehen haben ist die Mehrfachvererbung von Funktionalität, die wir mit der Version 8 von Java bekommen, relativ einfach zu benutzen. Trotzdem ergeben sich aus der Mehrfachvererbung neue Programmier- und Fehlersituationen beim Programmieren mit Java.  Um mit diesen Situationen umgehen zu können, gibt mit der Version 8 von Java neue Syntax und Regeln. Im Folgenden wollen wir uns einige dieser neuen Situationen und Regeln ansehen.
Abbildung 2: Mehrfachvererbung, Situation 1

 
 

Was ist, wenn wie in Abbildung 2 gezeigt die Klasse C2 sowohl vom Interface I als auch von der Klasse C1 die gleiche Methode  foo() erbt?  Welche Implementierung erbt C2? Die von I oder die von C1?  Oder ist das sowieso ein Fehler?  Für diese Situation gibt es eine neue Regel: Die Methodenimplementierung aus der Super-Klasse C1 wird an C2 vererbt und die aus dem Super-Interface I spielt keine Rolle.

Abbildung 3: Mehrfachvererbung, Situation 2
 
 

Und wie ist das, wenn eine Klasse C von zwei Interfaces I1 und I2 erbt, wobei die Interface ihrerseits wieder von einander  abgeleitet sind (siehe Abbildung 3)?  Beide Interfaces habe eine Default-Implementierung für die gleiche Methode  foo() .  Welche vererbt sich an die Klasse C?  Auch für diese Situation gibt es eine neue Regel: Die Default-Implementierung des Interfaces, das der Klasse am nächsten ist, vererbt sich.  In unserm Fall ist das  I2.foo() .

Abbildung 4: Mehrfachvererbung, Situation 3
 
 

Wie ist das, wenn die Interfaces I1 und I2 nicht von einander abgeleitet sind, sondern beide direkte Super-Interfaces der Klasse C sind (siehe Abbildung 4).  In diesem Fall ist die Vererbung nicht automatisch geregelt.  Hier kommt es zu einem Compilier-Fehler.  Als Programmierer kann man nun explizit auswählen, welche Implementierung an die Klasse C vererbt werden soll.  Dafür gibt es eine neue Syntax, um ein Super-Interface zu benennen.  Damit sieht die Auswahl von  I2.foo() dann so aus:
 
 

              class C implements I1, I2 {

                 public void foo() {  I2.super () .foo(); }

              }
 
 

Das waren die wichtigsten neuen Situationen, die sich aus der Mehrfachvererbung von Funktionalität basierend auf Default-Methoden ergeben.  Natürlich lassen sich weitere Situationen finden.  Sie alle hier zu diskutieren, sprengt den Rahmen des Artikels.  Eine ausführliche Betrachtung weiterer Situationen findet sich hier / LAM /.

Default-Methoden nutzen

Wir haben oben bereits diskutiert, wie Default-Methoden für die Interface Evolution genutzt werden können.  Neue Default-Methoden können in bestehenden Interfaces definiert werden, ohne dass die direkt oder indirekt abgeleiteten Klassen angepasst werden müssen.
Auch wenn dies der wesentliche Grund ist für das neue Sprachmittel der Default-Methoden in Java 8 ist, gibt es weitere Gründe für die Benutzung von Default-Methoden.  Dies hat sich bei den Erweiterungen des JDK für die Version 8 schon gezeigt.  Schauen wir uns als Beispiel das funktionale Interface  Consumer<T> an, das wir als Parameter-Typ der Methode  forEach( Consumer<? super T> action ) bereits oben kennengelernt haben.   Consumer definiert die abstrakte Methode:   void accept(T t) , die die Signatur für die Lambdas festlegt, die an  forEach() übergeben werden. Daneben enthält  Consumer noch die folgende Default-Methode:
             
              default Consumer<T> andThen(Consumer<? super T> other) {
           Objects.requireNonNull(other);
           return (T t) -> { accept(t); other.accept(t); };
                  }
Wie der Name der Methode ( andThen ) schon sagt, verkettet die Methode zwei  Consumer zu einem neuen  Consumer .  Zur Implementierung dieser Funktionalität bedarf es keiner Felder.
Bemerkenswert daran ist, dass  Consumer ein Interface ist, dass neu in der Version 8 des JDK sein wird.  Das heißt, es geht nicht um ein schon existierendes, altes Interface.  Mit anderen Worte, Interface Evolution findet hier nicht statt. Vielmehr sehen wir, dass nicht-abstrakte Methoden bereits in neu definierten Interfaces Verwendung finden.  Die Voraussetzung ist, dass für ihre Implementierung keine eigenen Felder benötigt werden. 
Damit verändert sich die Art, wie ab Java 8 Klassenhierarchien designt und implementiert werden.  Die zu implementierende Funktionalität wandert den Hierarchie-Graphen hinauf:  Statt wie bisher in Klassen (also relativ weit unten) wird Funktionalität, soweit es möglich ist, bereits in den Interface implementiert (also relativ weit oben).

Statische Methoden in Interfaces

Wir haben es in der Einleitung bereits erwähnt:  Neben den Default-Methoden gibt es noch eine zweite Änderung bei den Interfaces.  Interface können ab Java 8 statische Methoden enthalten.  Statische Methoden in Interfaces verhalten sich im Wesentlichen so wie statische Methoden in Klassen, mit einem Unterschied:  Sie vererben sich nicht an abgeleitete Typen.  Das heißt, wenn von einem Interface I mit einer statischen Methode  foo() die Klasse C abgeleitet wird, so ist der Aufruf:
C.foo();
ungültig.  Auch der Aufruf von  foo() auf einem Objekt von C ist nicht möglich.   foo() kann nur als
               I.foo();
aufgerufen werden.

 
 

Der Grund für das andere Verhalten von statischen Methoden in Interfaces gegenüber statischen Methoden in Klassen liegt daran, dass man nicht durch das nachträgliche Einfügen von statischen Methoden in Interfaces den bisherigen Programmablauf ändern möchte.  Wie hätte das sein können?  Nehmen wir wieder an, wir haben ein Interface I, von dem eine Klasse C abgeleitet ist.  In C gibt es eine Methode

               public static void bar(long l) { … }

Irgendwo im Programm wird diese Methode genutzt

               C.bar(5);

Das funktioniert, obwohl hier  bar() mit einem  int -Parameter aufgerufen wird und  C.b ar() mit einem  long -Parameter definiert ist.  Der Compiler macht die notwendige Konvertierung, ohne dass wir im Programm explizit, etwa durch einen Cast, eingreifen müssen.  Nehmen wir weiter an, dass zu einem späteren Zeitpunkt in I die Methode

               public static void bar(int  i ) { … }

definiert wird.  Wenn sich die neue statische Methode im Interface I vererben würde, müsste der Compiler nun diese Methode, statt der aus C, aufrufen.  Denn es existiert nun eine Methode mit exakt passendem Parametertyp im Super-Interface I.  Auf diese Art würden sich existierende Programmabläufe verändern, ohne dass dies bei der Änderung des Sourcecodes offensichtlich wird.  Um solche Situationen zu vermeiden, hat man beschlossen, dass sich statische Methode aus Interfaces nicht vererben. Man war sich sogar einig darüber, dass das Vererben von statischen Methoden bei Klassen eigentlich auch nicht korrekt sei.  Aus Kompatibilitätsgründen lässt sich das heute aber natürlich nicht mehr ändern.

Statische Methoden in Interfaces nutzen

Wie und wofür das neue Feature der statischen Interface-Methoden verwendet werden kann oder soll, lässt sich derzeit noch nicht eindeutig beantworten.  Klar ist zumindest, dass das neue Feature "statische Methoden in Interfaces" insbesondere dann nützlich ist, wenn man zusätzliche Funktionalität im Kontext eines Interfaces implementieren will. Bisher hat man im JDK eine solche Situation dadurch gelöst, dass man neben dem Interface eine weitere Klasse mit einem  private Konstruktor und ausschließlich statischen Methoden implementiert hat.  Als Namen der Klasse hat man üblicherweise die Pluralform des Interface-Namens gewählt.  Ein Beispiel ist das Interface  Collection und die begleitende Klasse  Collections .
Collection / Collections
Mit dem neuen Sprachmittel der statischen Interface-Methoden könnte man nun in Java 8 versuchen, alle Methoden aus der Klasse  Collections als statische Methoden im Interface  Collection zu implementieren und die Klasse  Collections komplett wegfallen zu lassen.  Würde man das tun?  Nein, aus Kompatibilitätsgründen natürlich nicht.  Aber selbst wenn Kompatibilität irrelevant wäre, würde eine solche Reorganisation keinen Sinn ergeben.  Betrachten wir zum Beispiel die Adapter-Methoden in der Klasse  Collections wie  synchronizedList() synchronizedSet() , etc.  Sie verwenden nicht den Interface-Typ  Collection , sondern sie verwenden von  Collection  abgeleitete Interface-Typen wie  Set List , etc.  Wenn man diese statischen Adapter-Methoden in ein Interface verschieben wollte, dann müsste man sie logischerweise in die zugehörigen Interfaces  Set List , etc. verschieben, statt in das Interface  Collection .  Im Falle der  Collections würde man also auch weiterhin die statischen Methoden in der Klasse belassen, statt sie auf zahlreiche Interfaces zu verteilen.
Um einen Eindruck davon zu bekommen, wie das neue Sprachmittel in der Praxis benutzt wird, können wir uns seine Verwendung im JDK 8 ansehen.  Dort werden statische Interface-Methoden bereits verwendet.  Als Beispiel sehen wir uns die Interfaces  java.util.stream.Stream und  java.util.stream.Collector an.
Stream / Streams
Im Interface  java.util.stream.Stream gibt es eine Reihe von statischen Methoden.  Mit einer Ausnahme handelt es sich dabei um Factory-Methoden, die spezifische  Stream s erzeugen,  zum Beispiel einen leeren  Stream :
public static<T> Stream<T> empty()
Die Ausnahme unter den statischen Methoden im Interface  java.util.stream.Stream ist:
               public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
Sie ist keine Factory-Methode im engeren Sinne, sondern sie hängt zwei  Stream s ( a und  b ) hintereinander und erzeugt damit eine neue  Stream - Instanz, die als Ergebnis zurückgegeben wird. 
In Falle der Streams sind alle statischen Methoden im Interface  Stream angesiedelt und es gibt keine begleitende Klasse  Strea ms .  Sie war ursprünglich einmal vorgesehen, ist aber seit der Beta Version 97 (b97) des JDK 8 entfallen, weil alle anfangs in der Klasse  Stream s definierten Methoden nach und nach ins Interface  Stream verschoben worden sind.

Collector / Collectors

Anders sieht es im Fall von  java.util.stream.Collector (Interface) und  java.util.stream.Collectors (Klasse) aus.  Das Interface  Collector enthält zwei statischen Factory-Methoden und die Klasse  Collector s enthält über 30 statische Factory-Methoden, um alle möglichen Kollektoren zu erzeugen.  Warum sind die mehr als 30 Factory-Methoden nicht auch im Interface definiert?  

Der Grund ist, dass das Interface  Collector nur fünf abstrakte Instanz-Methoden enthält.  Wenn die mehr als 30 statischen Factory-Methoden auch noch in dem Interface definiert worden wären, wäre es für Benutzer des Interfaces schwierig geworden, die Instanz-Methoden zu finden, auf die es ja bei einem Interface im Wesentlichen ankommt.  (Im vorangegangenen Beispiel des Interfaces  Stream sind die Zahlenverhältnisse im Übrigen genau umgekehrt: auf knapp 30 abstrakten Instanz-Methoden kommen etwa sechs statische Methoden.)

Nun stellt sich die Frage: warum sind nicht alle statischen Factory-Methoden in der Klasse  Collector s definiert?  Was ist mit den beiden statischen Factory-Methoden, die im Interface Collector definiert sind?  Der Grund ist, dass sie anders sind als die übrigen 30 Factory-Methoden.  Sie sind deshalb etwas Besonderes, weil sie sehr eng und direkt mit dem Interface  Collector zusammen hängen.

Das Interface Collector hat fünf abstrakte Methoden, die jeweils Funktionalität zurückgeben für einen Supplier, einen Accumulator, usw.  Eine implementierende Klasse  Collector Impl könnte ganz einfach aussehen: sie hat fünf Felder für einen Supplier, einen Accumulator, usw. und implementiert die fünf abstrakten Interface-Methoden, indem sie die Werte der Felder zurückgibt. Die beiden speziellen statischen Factory-Methoden im Interface sind nun so angelegt, dass sie genau diese fünf Funktionsteile entgegen nehmen und daraus einen  Collector wie soeben skizziert konstruieren.

public interface Collector<T, A, R> {

    Supplier<A>          supplier();

    BiConsumer<A, T>     accumulator();

    BinaryOperator<A>    combiner();

    Function<A, R>       finisher();

    Set<Characteristics> characteristics();

    …

    public static<T, A, R> Collector<T, A, R> of(Supplier<A>        supplier,

                                                 BiConsumer<A, T>   accumulator,

                                                 BinaryOperator<A> combiner,

                                                 Function<A, R>     finisher,

                                                 Characteristics... characteristics) {

        return new CollectorImpl<>(supplier, accumulator, combiner, finisher, characteristics);

    }

}
 
 

Diese (und die hier nicht gezeigte zweite) statische Methode  of hängen so eng mit dem Interface Collector zusammen, dass man sie im Interface angesiedelt hat.

Im Unterschied dazu sind die mehr als 30 anderen statischen Factory-Methoden (in der Klasse  Collector s ) eher lösungsorientiert.  Zum Beispiel die Methode  toList in der Klasse  Collector s

public static <T> Collector<T, ?, List<T>> toList()
 
 

konstruiert einen  Collector , der die Elemente des Streams in einer  List speichert.  Diese Methoden hängen jetzt nicht so eng mit dem Interface zusammen; man hat sie deshalb (und wegen ihrer großen Anzahl) in die Klasse ausgelagert.
 
 

Wie man an den Beispielen sieht, wird nicht alles, was sich als statische Methode im Interface implementieren lassen könnte, auch tatsächlich so implementiert.  Zumindest gilt das für die Java-8-Erweiterungen im JDK.  Wie das neue Feature nun von der Java Community aufgenommen und benutzt wird, bleibt abzuwarten.  Oder wie Brian Goetz in einem Diskussionsbeitrag zu diesem Thema geschrieben hat:

So, while this gives API designers one more tool, there don't seem to be obvious hard and fast rules about how to use this tool yet, and the simple-minded "all or nothing" candidates are likely to give the wrong result. (/HCL/)

Zusammenfassung und Ausblick

Wir haben uns in diesem Beitrag die Java 8 Erweiterungen angesehen, die Interfaces betreffen: Default-Methoden und statische Methoden.  Für Design und Programmierung in Java sind vor allem Default-Methoden eine wichtige Änderung, weil mit ihnen Mehrfach-Vererbung von Funktionalität möglich wird.

 
 

Im nächsten Beitrag, dessen Erscheinungstermin in etwa mit dem Release Termin von Java 8 zusammenfällt, werden wir uns in alle Neuerungen von Java 8 im Überblick ansehen.

Literaturverweise

/BOP/
JEP 107: Bulk Data Operations for Collections
/CCI/ Josh Bloch
Lesson: Custom Collection Implementation
URL: http://docs.oracle.com/javase/tutorial/collections/custom-implementations/index.ht ml
/HCL/ Brian Goetz
Diskussionsbeitrag zum Thema: Helper Classes
URL: http://mail.openjdk.java.net/pipermail/lambda-dev/2013-April/009345.html
/JAO/ James Gosling
Java: an Overview
URL: http://www.cs.dartmouth.edu/~mckeeman/cs118/references/OriginalJavaWhitepaper.pdf
/PBK/ Brian Goetz
Some Pullbacks
URL: http://mail.openjdk.java.net/pipermail/lambda-spec-observers/2013-January/000190.html
/LAM/ Angelika Langer, Klaus Kreft
Lambda Tutorial
URL: http://www.AngelikaLanger.com/Lambdas/Lambdas.html

Die gesamte  Serie über Java 8:

/JAV8-0/ Neue Features in Java 8 - Überblick
Klaus Kreft & Angelika Langer, Java Magazin, März 2014
URL: http://www.angelikalanger.com/Articles/EffectiveJava/73.Java8.Overview/73.Java8.Overview.html
/JAV8-1/ Funktionale Programmierung in Java
Klaus Kreft & Angelika Langer, Java Magazin, September 2013
URL: http://www.angelikalanger.com/Articles/EffectiveJava/70.Java8.FunctionalProg/70.Java8.FunctionalProg.html
/JAV8-2/ Lambda-Ausdrücke und Methoden-Referenzen
Klaus Kreft & Angelika Langer, Java Magazin, Dezember 2013
URL: http://www.angelikalanger.com/Articles/EffectiveJava/71.Java8.Lambdas/71.Java8.Lambdas.html
/JAV8-3/ Default-Methoden und statische Methoden in Interfaces
Klaus Kreft & Angelika Langer, Java Magazin, Februar 2014
URL: http://www.angelikalanger.com/Articles/EffectiveJava/72.Java8.DefaultMethods/72.Java8.DefaultMethods.html
/JAV8-4/ Übersicht über das Stream API in Java 8
Klaus Kreft & Angelika Langer, Java Magazin, Mai 2014
URL: http://www.angelikalanger.com/Articles/EffectiveJava/74.Java8.Streams-Overview/74.Java8.Streams-Overview.html
/JAV8-5/ Stream-Erzeugung und Stream-Operationen
Klaus Kreft & Angelika Langer, Java Magazin, Juli 2014
URL: http://www.angelikalanger.com/Articles/EffectiveJava/75.Java8.Fundamental-Stream-Operations/75.Java8.Fundamental-Stream-Operations.html
/JAV8-6/ Stream-Kollektoren und die Stream-Operation collect()
Klaus Kreft & Angelika Langer, Java Magazin, September 2014
URL: http://www.angelikalanger.com/Articles/EffectiveJava/76.Java8.Stream-Collectors/76.Java8.Stream-Collectors.html
/JAV8-7/ Stateful Lambdas - Regeln für die Seiteneffekte in Lambda-Ausdrücken, die an Stream-Operationen übergeben werden
Klaus Kreft & Angelika Langer, Java Magazin, November 2014
URL: http://www.angelikalanger.com/Articles/EffectiveJava/77.Java8.Streams-and-Statefulness/77.Java8.Streams-and-Statefulness.html
/JAV8-8/ Das Date/Time API
Klaus Kreft & Angelika Langer, Java Magazin, Januar 2015
URL: http://www.angelikalanger.com/Articles/EffectiveJava/78.Java8.Date-Time-API/78.Java8.Date-Time-API.html
/JAV8-9/ CompletableFuture
Klaus Kreft & Angelika Langer, Java Magazin, März 2015
URL: http://www.angelikalanger.com/Articles/EffectiveJava/79.Java8.CompletableFuture/79.Java8.CompletableFuture.html
/JAV8-10/ Optional<T>
Klaus Kreft & Angelika Langer, Java Magazin, Mai 2015
URL: http://www.angelikalanger.com/Articles/EffectiveJava/80.Java8.Optional-Result/80.Java8.Optional-Result.html

 
 

If you are interested to hear more about this and related topics you might want to check out the following seminar:
Seminar
Lambdas & Streams - Java 8 Language Features and Stream API & Internals
3 day seminar ( open enrollment and on-site)
Java 8 - Lambdas & Stream, New Concurrency Utilities, Date/Time API
4 day seminar ( open enrollment and on-site)
Effective Java - Advanced Java Programming Idioms 
4 day seminar ( open enrollment and on-site)
 
Related Reading
Lambda & Streams Tutorial & Reference
In-Depth Coverage of all aspects of lambdas & streams
Lambdas in Java 8
Conference Presentation at JFokus 2012 (slides)
Lambdas in Java 8
Conference Presentation at JavaZone 2012 (video)
 

 
  © Copyright 1995-2016 by Angelika Langer.  All Rights Reserved.    URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/72.Java8.DefaultMethods/72.Java8.DefaultMethods.html  last update: 2 Aug 2016