Utilizzo del metodo predefinito Java


13

Per decenni è stato il caso che le interfacce sono stati solo solo (solo) per specificare il metodo di firme. Ci è stato detto che questo era il "modo giusto di fare le cose ™".

Quindi uscì Java 8 e disse:

Bene, ora puoi definire metodi predefiniti. Devo correre, ciao.

Sono curioso di sapere come questo viene digerito sia da sviluppatori Java esperti che da coloro che hanno iniziato di recente (negli ultimi anni) a svilupparlo. Mi chiedo anche come questo si adatta all'ortodossia e alla pratica Java.

Sto costruendo un po 'di codice sperimentale e mentre stavo facendo un po' di refactoring, ho finito con un'interfaccia che estende semplicemente un'interfaccia standard (Iterable) e aggiunge due metodi predefiniti. E sarò onesto, mi sento dannatamente bene.

So che questo è un po 'a tempo indeterminato, ma ora che Java 8 ha impiegato del tempo per essere utilizzato in progetti reali, c'è ancora un'ortodossia sull'uso dei metodi predefiniti? Quello che vedo principalmente quando vengono discussi è su come aggiungere nuovi metodi a un'interfaccia senza rompere i consumatori esistenti. Ma che dire di usarlo dall'inizio come nell'esempio che ho dato sopra. Qualcuno ha riscontrato problemi nel fornire implementazioni nelle loro interfacce?


Sarei anche interessato a questa prospettiva. Sto tornando a Java dopo 6 anni nel mondo .Net. Mi sembra che questa potrebbe essere la risposta di Java per i metodi di estensione C #, con un po 'di influenza dai metodi del modulo di Ruby. Non ci ho giocato, quindi non posso esserne sicuro.
Berin Loritsch,

1
Sento che il motivo per cui hanno aggiunto metodi predefiniti è in gran parte in modo che possano estendere le interfacce di raccolta senza dover creare interfacce completamente diverse
Giustino

1
@Justin: vedi java.util.function.Functionper l'utilizzo dei metodi predefiniti in una nuovissima interfaccia.
Jörg W Mittag,

@Justin La mia ipotesi è che questo fosse il driver principale. Dovrei davvero ricominciare a prestare attenzione al processo da quando hanno iniziato a fare davvero delle modifiche.
JimmyJames,

Risposte:


12

Un grande caso d'uso sono quelle che io chiamo interfacce "leva": interfacce che hanno solo un piccolo numero di metodi astratti (idealmente 1), ma forniscono un sacco di "leva" in quanto forniscono molte funzionalità: solo tu devi implementare 1 metodo nella tua classe ma ottenere molti altri metodi "gratuitamente". Pensate ad un interfaccia per la raccolta, per esempio, con un unico astratto foreachmetodo e defaultmetodi come map, fold, reduce, filter, partition, groupBy, sort, sortBy, etc.

Qui ci sono un paio di esempi. Cominciamo con java.util.function.Function<T, R>. Ha un solo metodo astratto R apply<T>. E ha due metodi predefiniti che ti consentono di comporre la funzione con un'altra funzione in due modi diversi, prima o dopo. Entrambi questi metodi di composizione sono implementati usando soloapply :

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));
}

Puoi anche creare un'interfaccia per oggetti comparabili, qualcosa del genere:

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());
  }
}

O un framework di raccolte estremamente semplificato, in cui vengono restituite tutte le operazioni di raccolta Collection, indipendentemente dal tipo originale:

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)
  }
}

Questo diventa molto interessante in combinazione con lambda, perché una tale interfaccia "leva" può essere implementata da una lambda (è un'interfaccia SAM).

Questo è lo stesso caso d'uso in cui sono stati aggiunti i metodi di estensione in C♯, ma i metodi predefiniti hanno un netto vantaggio: sono metodi di istanza "appropriati", il che significa che hanno accesso ai dettagli dell'implementazione privata dell'interfaccia ( privatestanno arrivando i metodi di interfaccia in Java 9), mentre i metodi di estensione sono solo zucchero sintattico per metodi statici.

Se Java dovesse mai ricevere l'Injection Interface, consentirebbe anche patch di scimmia modulari, sicure e orientate al tipo. Questo sarebbe molto interessante per gli implementatori di linguaggio sulla JVM: al momento, ad esempio, JRuby eredita o esegue il wrapping delle classi Java per fornire loro una semantica Ruby aggiuntiva, ma idealmente vogliono usare le stesse classi. Con Interface Injection e Default Methods, potrebbero iniettare ad esempio RubyObjectun'interfaccia java.lang.Object, in modo che Java Objecte Ruby Objectsiano esattamente la stessa cosa .


1
Non lo seguo fino in fondo. Il metodo predefinito sull'interfaccia deve essere definito in termini di altri metodi sull'interfaccia o metodi definiti in Oggetto. Puoi fare un esempio di come creare un'interfaccia significativa a metodo singolo con un metodo predefinito? Se hai bisogno della dimostrazione della sintassi Java 9, va bene.
JimmyJames,

Ad esempio: Comparableun'interfaccia con un abstract compareTometodo e predefinito lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, isBetween, e clampmetodi, tutti implementati in termini di compareTo. Oppure, basta guardare java.util.function.Function: ha un applymetodo astratto e due metodi di composizione predefiniti, entrambi implementati in termini di apply. Ho provato a dare un esempio di Collectioninterfaccia, ma far sì che tutto sia sicuro per i tipi è complicato e troppo lungo per questa risposta: proverò a dare una versione non sicura per i tipi, senza preservare i tipi. Rimanete sintonizzati.
Jörg W Mittag,

3
Gli esempi aiutano. Grazie. Ho capito male cosa intendevi per interfaccia a metodo singolo.
JimmyJames,

Ciò che significa metodi predefiniti è che una singola interfaccia di metodo astratto non deve più essere una singola interfaccia di metodo ;-)
Jörg W Mittag,

Stavo pensando a questo e mi è venuto in mente che AbstractCollection e AbstractList sono fondamentalmente ciò di cui stai parlando qui (2 metodi invece di 1 ma non credo sia cruciale.) Se questi fossero rifusi come interfacce con metodi defualt, sarebbe essere super semplice trasformare un iterabile in una raccolta aggiungendo dimensioni e creando un elenco da qualsiasi cosa è anche un gioco da ragazzi se puoi indicizzare e conoscere le dimensioni.
JimmyJames,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.