Verwendung der Java-Standardmethode

13

Seit Jahrzehnten ist es der Fall gewesen , dass Schnittstellen war nur nur (nur) für die Angabe Methodensignaturen. Uns wurde gesagt, dass dies der "richtige Weg ist, Dinge zu tun ™".

Dann kam Java 8 heraus und sagte:

Nun können Sie Standardmethoden definieren. Ich muss rennen, tschüss.

Ich bin gespannt, wie dies sowohl von erfahrenen Java-Entwicklern als auch von jenen, die vor kurzem (in den letzten Jahren) damit begonnen haben, verarbeitet wird. Ich frage mich auch, wie dies in die Java-Orthodoxie und -Praxis passt.

Ich erstelle experimentellen Code und während ich einige Umgestaltungen durchführte, erhielt ich eine Schnittstelle, die einfach eine Standardschnittstelle (Iterable) erweitert und zwei Standardmethoden hinzufügt. Und ich werde ehrlich sein, ich fühle mich ziemlich gut dabei.

Ich weiß, dass dies ein wenig offen ist, aber da es nun einige Zeit für Java 8 gab, um in realen Projekten verwendet zu werden, gibt es noch eine Orthodoxie zur Verwendung von Standardmethoden? Was ich meistens sehe, wenn sie besprochen werden, ist, wie man einer Schnittstelle neue Methoden hinzufügt, ohne vorhandene Verbraucher zu zerbrechen. Aber wie wäre es damit, dies von Anfang an wie in dem Beispiel zu verwenden, das ich oben angegeben habe? Hat jemand Probleme mit der Bereitstellung von Implementierungen in ihren Schnittstellen?

JimmyJames
quelle
Diese Perspektive würde mich auch interessieren. Nach 6 Jahren in der .Net-Welt kehre ich zu Java zurück. Es scheint mir, dass dies Javas Antwort für C # -Erweiterungsmethoden sein könnte, mit ein wenig Einfluss von Rubys Modulmethoden. Ich habe nicht damit gespielt, also kann ich nicht sicher sein.
Berin Loritsch
1
Ich glaube, der Grund, warum sie Standardmethoden hinzugefügt haben, ist größtenteils, dass sie die Erfassungsschnittstellen erweitern können, ohne ganz andere Schnittstellen erstellen zu müssen
Justin
1
@Justin: Informationen java.util.function.Functionzur Verwendung von Standardmethoden in einer brandneuen Benutzeroberfläche finden Sie unter.
Jörg W Mittag
@ Justin Ich vermute, dass dies der Haupttreiber war. Ich sollte wirklich anfangen, dem Prozess wieder Aufmerksamkeit zu schenken, da sie wirklich anfingen, Änderungen vorzunehmen.
JimmyJames

Antworten:

12

Ein großartiger Anwendungsfall sind die von mir als "Hebel" bezeichneten Schnittstellen: Schnittstellen, die nur über eine geringe Anzahl abstrakter Methoden verfügen (idealerweise 1), aber eine Menge "Hebel" bieten, indem sie Ihnen eine Menge Funktionalität bieten: Sie allein Müssen Sie 1 Methode in Ihrer Klasse implementieren, aber erhalten Sie viele andere Methoden "kostenlos". Denken Sie an einer Sammlung Schnittstelle, zum Beispiel mit einem einzigen abstrakten foreachVerfahren und defaultMethoden wie map, fold, reduce, filter, partition, groupBy, sort, sortBy, usw.

Hier einige Beispiele. Beginnen wir mit java.util.function.Function<T, R>. Es gibt nur eine abstrakte Methode R apply<T>. Es gibt zwei Standardmethoden, mit denen Sie die Funktion mit einer anderen Funktion auf zwei verschiedene Arten zusammenstellen können, entweder vorher oder nachher. Beide dieser Kompositionsmethoden werden unter Verwendung von nur implementiertapply :

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    return (V v) -> apply(before.apply(v));
}

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    return (T t) -> after.apply(apply(t));
}

Sie können auch eine Schnittstelle für vergleichbare Objekte erstellen, etwa wie folgt:

interface MyComparable<T extends MyComparable<T>> {
  int compareTo(T other);

  default boolean lessThanOrEqual(T other) {
    return compareTo(other) <= 0;
  }

  default boolean lessThan(T other) {
    return compareTo(other) < 0;
  }

  default boolean greaterThanOrEqual(T other) {
    return compareTo(other) >= 0;
  }

  default boolean greaterThan(T other) {
    return compareTo(other) > 0;
  }

  default boolean isBetween(T min, T max) {
    return greaterThanOrEqual(min) && lessThanOrEqual(max);
  }

  default T clamp(T min, T max) {
    if (lessThan(   min)) return min;
    if (greaterThan(max)) return max;
                          return (T)this;
  }
}

class CaseInsensitiveString implements MyComparable<CaseInsensitiveString> {
  CaseInsensitiveString(String s) { this.s = s; }
  private String s;

  @Override public int compareTo(CaseInsensitiveString other) {
    return s.toLowerCase().compareTo(other.s.toLowerCase());
  }
}

Oder ein extrem vereinfachtes Auflistungsframework, in dem alle Auflistungsvorgänge Collectionunabhängig vom ursprünglichen Typ zurückgegeben werden:

interface MyCollection<T> {
  void forEach(java.util.function.Consumer<? super T> f);

  default <R> java.util.Collection<R> map(java.util.function.Function<? super T, ? extends R> f) {
    java.util.Collection<R> l = new java.util.ArrayList();
    forEach(el -> l.add(f.apply(el)));
    return l;
  }
}

class MyArray<T> implements MyCollection<T> {
  private T[] array;

  MyArray(T[] array) { this.array = array; }

  @Override public void forEach(java.util.function.Consumer<? super T> f) {
    for (T el : array) f.accept(el);
  }

  @Override public String toString() {
    StringBuilder sb = new StringBuilder("(");
    map(el -> el.toString()).forEach(s -> { sb.append(s); sb.append(", "); } );
    sb.replace(sb.length() - 2, sb.length(), ")");
    return sb.toString();
  }

  public static void main(String... args) {
    MyArray<Integer> array = new MyArray<>(new Integer[] {1, 2, 3, 4});
    System.out.println(array);
    // (1, 2, 3, 4)
  }
}

Dies wird in Kombination mit Lambdas sehr interessant, da eine solche "Hebel" -Schnittstelle von einem Lambda implementiert werden kann (es ist eine SAM-Schnittstelle).

Dies ist der gleiche Anwendungsfall, für den Erweiterungsmethoden in C♯ hinzugefügt wurden, aber Standardmethoden haben einen eindeutigen Vorteil: Sie sind "richtige" Instanzmethoden, dh sie haben Zugriff auf private Implementierungsdetails der Schnittstelle ( privateSchnittstellenmethoden werden bereitgestellt) in Java 9), während Erweiterungsmethoden nur syntaktischer Zucker für statische Methoden sind.

Sollte Java jemals Interface Injection bekommen, würde es auch typsicheres, modulares Affen-Patching ermöglichen. Dies wäre für Sprachimplementierer in der JVM sehr interessant: Im Moment übernimmt JRuby beispielsweise entweder Java-Klassen oder bricht sie um, um ihnen zusätzliche Ruby-Semantik bereitzustellen. Idealerweise möchten sie jedoch dieselben Klassen verwenden. Mit Injection - Schnittstelle und Standard Methods, könnten sie injizieren zB eine RubyObjectSchnittstelle in java.lang.Object, so dass ein Java Objectund Ruby Objectdie ist genau die gleiche Sache .

Jörg W. Mittag
quelle
1
Ich folge dem nicht ganz. Die Standardmethode für die Schnittstelle muss in Bezug auf andere Methoden für die Schnittstelle oder in Object definierte Methoden definiert werden. Können Sie ein Beispiel dafür geben, wie Sie mit einer Standardmethode eine aussagekräftige Einzelmethodenschnittstelle erstellen? Wenn Sie zur Demonstration die Java 9-Syntax benötigen, ist das in Ordnung.
JimmyJames
Zum Beispiel: eine ComparableSchnittstelle mit einer abstrakten compareToMethode und Standard lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, isBetween, und clampMethoden, die alle in Bezug auf die Umsetzung compareTo. Oder sehen java.util.function.FunctionSie sich nur an : Es gibt eine abstrakte applyMethode und zwei Standard-Kompositionsmethoden, die beide in Bezug auf implementiert sind apply. Ich habe versucht, ein Beispiel für eine CollectionSchnittstelle zu nennen, aber es ist schwierig und zu lang, alles typsicher zu machen. Ich werde versuchen, einer nicht typsicheren, nicht typerhaltenden Version eine Chance zu geben. Bleib dran.
Jörg W Mittag
3
Die Beispiele helfen. Vielen Dank. Ich habe falsch verstanden, was Sie mit Single Method Interface gemeint haben.
JimmyJames
Standardmethoden bedeuten, dass eine einzelne abstrakte Methodenschnittstelle keine einzelne Methodenschnittstelle mehr sein muss ;-)
Jörg W Mittag
Ich habe darüber nachgedacht, und mir ist aufgefallen, dass AbstractCollection und AbstractList im Grunde genommen das sind, worüber Sie hier sprechen (2-Methode anstelle von 1, aber ich denke nicht, dass das entscheidend ist.) Wenn diese als Schnittstellen mit defualt-Methoden neu formuliert würden, wäre dies der Fall Es ist sehr einfach, eine iterative Datei in eine Sammlung umzuwandeln, indem Sie Größe hinzufügen und eine Liste aus beliebigen Elementen erstellen. Dies ist auch ein Kinderspiel, wenn Sie die Größe indizieren und kennen.
JimmyJames