Penggunaan Metode Default Java

13

Selama beberapa dekade itu menjadi hal interface yang hanya hanya (hanya) untuk menentukan metode tanda tangan. Kami diberitahu bahwa ini adalah "cara yang tepat untuk melakukan sesuatu ™".

Kemudian Java 8 keluar dan berkata:

Nah, eh, eh, sekarang Anda bisa mendefinisikan metode default. Harus lari, bye.

Saya ingin tahu bagaimana ini dicerna oleh pengembang Java berpengalaman dan mereka yang memulai baru-baru ini (beberapa tahun terakhir) mengembangkannya. Saya juga bertanya-tanya tentang bagaimana ini cocok dengan ortodoksi dan praktik Jawa.

Saya sedang membangun beberapa kode eksperimental dan ketika saya sedang melakukan beberapa refactoring, saya berakhir dengan antarmuka yang hanya memperluas antarmuka standar (Iterable) dan menambahkan dua metode standar. Dan saya akan jujur, saya merasa sangat senang tentang hal itu.

Saya tahu ini sedikit terbuka tetapi sekarang sudah ada waktu untuk Java 8 digunakan dalam proyek nyata, apakah ada ortodoksi seputar penggunaan metode standar? Apa yang saya lihat ketika dibahas adalah tentang cara menambahkan metode baru ke antarmuka tanpa merusak konsumen yang ada. Tetapi bagaimana dengan menggunakan ini dari awal seperti contoh yang saya berikan di atas. Adakah yang mengalami masalah dengan menyediakan implementasi di antarmuka mereka?

JimmyJames
sumber
Saya juga tertarik dengan perspektif ini. Saya kembali ke Jawa setelah 6 tahun di dunia .Net. Tampaknya bagi saya bahwa ini mungkin jawaban Java untuk metode ekstensi C #, dengan sedikit pengaruh dari metode modul Ruby. Saya belum bermain dengan itu, jadi saya tidak yakin.
Berin Loritsch
1
Saya merasa seperti alasan mengapa mereka menambahkan metode default sebagian besar sehingga mereka dapat memperluas antarmuka pengumpulan tanpa harus membuat antarmuka yang sama sekali berbeda
Justin
1
@Justin: lihat java.util.function.Functionuntuk penggunaan metode default di antarmuka baru.
Jörg W Mittag
@ Justin Tebakan saya adalah bahwa ini adalah driver utama. Saya harus benar-benar mulai memperhatikan proses itu lagi karena mereka mulai benar-benar melakukan perubahan.
JimmyJames

Jawaban:

12

Sebuah kasus penggunaan yang bagus adalah apa yang saya sebut antarmuka "tuas": antarmuka yang hanya memiliki sejumlah kecil metode abstrak (idealnya 1), tetapi memberikan banyak "daya ungkit" karena mereka memberi Anda banyak fungsi: Anda hanya perlu menerapkan 1 metode di kelas Anda tetapi mendapatkan banyak metode lain "gratis". Pikirkan antarmuka koleksi, misalnya, dengan abstrak tunggal foreachmetode dan defaultmetode seperti map, fold, reduce, filter, partition, groupBy, sort, sortBy, dll

Berikut ini beberapa contohnya. Mari kita mulai java.util.function.Function<T, R>. Ini memiliki metode abstrak tunggal R apply<T>. Dan ia memiliki dua metode default yang memungkinkan Anda menyusun fungsi dengan fungsi lain dengan dua cara berbeda, baik sebelum atau sesudah. Kedua metode komposisi tersebut diimplementasikan hanya dengan menggunakanapply :

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

Anda juga bisa membuat antarmuka untuk objek yang sebanding, seperti ini:

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

Atau kerangka kerja koleksi yang sangat disederhanakan, tempat semua operasi koleksi kembali Collection, terlepas dari apa jenis aslinya:

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

Ini menjadi sangat menarik dalam kombinasi dengan lambdas, karena antarmuka "tuas" seperti itu dapat diimplementasikan oleh lambda (ini adalah antarmuka SAM).

Ini adalah kasus penggunaan yang sama dengan Metode Ekstensi ditambahkan untuk di C♯, tetapi metode default memiliki satu keuntungan berbeda: mereka adalah metode contoh yang "tepat", yang berarti mereka memiliki akses ke detail implementasi pribadi dari antarmuka ( privatemetode antarmuka akan datang di Jawa 9), sedangkan Metode Ekstensi hanya gula sintaksis untuk metode statis.

Jika Java pernah mendapatkan Interface Injection, itu juga akan memungkinkan tipe-safe, scoped, modular monyet-patching. Ini akan sangat menarik bagi pelaksana bahasa di JVM: saat ini, misalnya, JRuby mewarisi dari atau membungkus kelas Java untuk memberi mereka semantik Ruby tambahan, tetapi idealnya, mereka ingin menggunakan kelas yang sama. Dengan Injeksi Antarmuka dan Metode Default, mereka dapat menyuntikkan misalnya RubyObjectantarmuka java.lang.Object, sehingga Java Objectdan Ruby Objectadalah hal yang sama persis .

Jörg W Mittag
sumber
1
Saya tidak sepenuhnya mengikuti ini. Metode default pada antarmuka harus didefinisikan dalam hal metode lain pada antarmuka atau metode yang didefinisikan dalam Objek. Bisakah Anda memberikan contoh bagaimana Anda membuat antarmuka metode tunggal yang bermakna dengan metode default? Jika Anda memerlukan sintaks Java 9 untuk menunjukkan, itu bagus.
JimmyJames
Sebagai contoh: sebuah Comparableantarmuka dengan abstrak compareTometode, dan standar lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, isBetween, dan clampmetode, semua dilaksanakan dalam hal compareTo. Atau, lihat saja java.util.function.Function: ia memiliki applymetode abstrak dan dua metode komposisi standar, keduanya diimplementasikan dalam hal apply. Saya mencoba memberikan contoh Collectionantarmuka, tetapi menjadikannya semua tipe-aman itu rumit dan terlalu panjang untuk jawaban ini - saya akan mencoba untuk memberikan versi non-tipe-aman, non-tipe-melestarikan tembakan. Tetap disini.
Jörg W Mittag
3
Contohnya membantu. Terima kasih. Saya salah mengerti apa yang Anda maksud dengan antarmuka metode tunggal.
JimmyJames
Apa yang dimaksud dengan metode standar adalah bahwa antarmuka metode abstrak tunggal tidak lagi harus menjadi antarmuka metode tunggal ;-)
Jörg W Mittag
Saya sedang memikirkan hal ini dan terpikir oleh saya bahwa AbstractCollection dan AbstractList pada dasarnya adalah apa yang Anda bicarakan di sini (2 metode, bukan 1 tetapi saya tidak berpikir itu penting.) Jika ini disusun kembali sebagai antarmuka dengan metode defualt, itu akan menjadi super sederhana untuk mengubah iterable menjadi koleksi dengan menambahkan ukuran dan membuat daftar dari apa pun juga mudah jika Anda dapat mengindeks dan mengetahui ukurannya.
JimmyJames