Advanced   Java   Services Defaultmethoden in Interfaces Back Next Up Home


Defaultmethoden in Interfaces

Seit Java 8 sind nichtstatische auscodierte Methoden in Interfaces erlaubt. Sie benötigen das Schlüsselwort default vor dem Returntyp und nehmen wie normale nichtstatische Methoden an der Vererbung teil. Bestehender Code wird dadurch nicht zerstört, beim neuen Compilieren wird einfach die neue Methode ergänzt und nicht verwendet. So sieht das in Java 1.5 eingeführte Interface Iterable seit 1.8 folgendermaßen aus.

public interface Iterable
{
   Iterator<T> iterator()
   // abstrakte Methode, die zu implementieren ist

   default void   forEach(Consumer<? super T> action)
   {
      Objects.requireNonNull(action);
      for (T t : this)
      {
         action.accept(t);
      }
   }


   default Spliterator<T>  spliterator();
   {
      return Spliterators.spliteratorUnknownSize(iterator(), 0);
   }
}
1

Es gibt also neben der abstrakten Methode iterator() die zwei auscodierten Methoden forEach und spliterator.

Man betrachte hierzu etwa das Interface Comparator<T>, in dem es seit Java 8 neben den abstrakten Methoden int compare(T o1, T o2) und boolean equals(Object obj) noch 7 default- und 9 statische Methoden gibt.


this in Defaultmethoden

Da Defaultmethoden nichtstatische Methoden sind, müssen sie Zugriff auf das Objekt this haben. In diesem Fall ist this ein Objekt der implementierenden Klasse.

interface DefaultInterface
{
   default String getClassName()
   {
      return this.getClass().getName();
   }
}

class DefaultInterfaceImpl implements DefaultInterface
{
}

public class DefaultMethods
{
   public static void main(String[] args)
   {
      DefaultInterfaceImpl dfi = new DefaultInterfaceImpl();
      System.out.println(dfi.getClassName());
   }
}

Ausgabe

DefaultInterfaceImpl

Das Diamond-Problem

Im Grunde wird in Java mit diesem Feature die Mehrfachvererbung durch die Hintertür eingeführt. Wir wollen hier kurz das sog. Diamond-Problem erwähnen, das durch das neue Feature entsteht (siehe auch The_diamond_problem auf Wikipedia).

A, B, C und D sind dabei Klassen oder Interfaces. D erbt (oder in Java implementiert von zwei verschiedenen Seiten, was zu Konflikten führen kann.

diamond-problem.jpg

Im folgenden seien A, B und C Interfaces und D eine implementierende Klasse. Zunächst die drei Interfaces:

interface A
{
   void foo();
}

interface C extends A
{
   @Override
   default void foo()
   {
      System.out.println("foo von C");
   }
}

interface B extends A
{
   @Override
   default void foo()
   {
      System.out.println("foo von B");
   }
}

Die Interfaces B und C erben von A die Methode, überschreiben sie aber jeweils durch eine eigene Fassung.

Wir führen nun eine Klasse D ein, die die Interfaces B und C implementieren will

class D implements B, C
{
}

Die Klasse D würde dadurch zwei verschiedene Implementierung der Defaultmethode foo() erben, was nicht zulässig sein kann.

Der Compiler meldet sich daher mit dem Satz
Duplicate default methods named foo with the parameters () and () are inherited from the types C and B

Enthält die Klasse D jedoch eine eigene Implementierung der Methode foo() so kann kein Konflikt mehr auftreten weil dadurch beide Elternmethoden außer Kraft gesetzt werden.

class D implements B, C
{
   @Override
   public void foo()
   {
      System.out.println("implementation of foo in D");
   }
}

Mit Hilfe von super kann man sich dann sogar auf eine der beiden (odser auch auf beide) Elternimplementierungen beziehen.

class D implements B, C
{
   @Override
   public void foo()
   {
      B.super.foo();
      //C.super.foo();
   }
}

Bemerkung

Hier sieht man so nebenbei, daß man eine abstrakte Methode durch eine Defaultmethode in einem Interface implementieren kann. Das geht aber nicht immer, wie man weiter unten sieht.


Klassenimplementierung schlägt Defaultimplementierung

Wir lassen das Interface A weg, nehmen für B eine normale Klasse und für C ein Interface mit einer gleichlautenden Defaultmethode. D erbt also von B und implementiert C

diamond-problem2.jpg

class B
{
   public void someMethod()  // Vorsicht bei (default), protected und private
   {
      System.out.println("someMethod of B");
   }
}

interface C
{
   default void someMethod()
   {
      System.out.println("someMethod of C");
   }
}

class D extends B implements C
{
}


public class DefaultMethods
{
   public static void main(String[] args)
   {
      D  d = new D();
      d.someMethod();  // someMethod of B
   }
}

Diesmal geht alles gut und zur Laufzeit wird die Methode der Klasse genommen.
The inherited method B.someMethod() cannot hide the public abstract method in C

Hier ist allerdings Vorsicht geboten:

Ist die Methode in der Klasse B nicht public sondern package-private oder protected kommt es zu einer Fehlermeldung:
The inherited method B.someMethod() cannot hide the public abstract method in C.

Ist die Methode in der Klasse B private so bleibt der Compiler ruhig, aber wir bekommen einen Laufzeitfehler:
java.lang.IllegalAccessError: tried to access method B.someMethod()V from class DefaultMethods.


Grenzen für Defaultmethoden

Das folgende Codeschnipsel zeigt, daß man nicht alle Methoden mit Defaultmethoden implementieren kann.

interface Test
{
   default int hashCode()
   {
      return 1;
   }
}

Hier bringt uns Eclipse die folgende Fehlermeldung: A default method cannot override a method from java.lang.Object

Valid XHTML 1.0 Strict top Back Next Up Home