Cách sử dụng phương thức mặc định của Java


13

Trong nhiều thập kỷ nó được các trường hợp đó giao diện là chỉ duy nhất (chỉ) để xác định chữ ký phương pháp. Chúng tôi đã nói rằng đây là "cách làm đúng đắn".

Sau đó, Java 8 xuất hiện và nói:

Vâng, er, uh, bây giờ bạn có thể xác định các phương thức mặc định. Phải chạy đi, tạm biệt.

Tôi tò mò về cách điều này đang được tiêu hóa bởi cả các nhà phát triển Java có kinh nghiệm và những người bắt đầu gần đây (vài năm trước) đang phát triển nó. Tôi cũng đang tự hỏi làm thế nào điều này phù hợp với chính thống và thực hành Java.

Tôi đang xây dựng một số mã thử nghiệm và trong khi tôi đang thực hiện một số tái cấu trúc, tôi đã kết thúc với một giao diện chỉ đơn giản là mở rộng một giao diện chuẩn (Iterable) và thêm hai phương thức mặc định. Và tôi sẽ thành thật, tôi cảm thấy khá tốt về điều đó.

Tôi biết đây là một kết thúc mở nhỏ nhưng bây giờ đã có một thời gian để Java 8 được sử dụng trong các dự án thực tế, đã có sự chính thống xung quanh việc sử dụng các phương thức mặc định chưa? Những gì tôi chủ yếu nhìn thấy khi họ được thảo luận là về cách thêm các phương thức mới vào giao diện mà không phá vỡ người tiêu dùng hiện tại. Nhưng những gì về việc sử dụng này từ đầu như ví dụ tôi đã đưa ra ở trên. Có ai gặp phải bất kỳ vấn đề nào với việc cung cấp triển khai trong giao diện của họ không?


Tôi cũng sẽ quan tâm đến quan điểm này. Tôi sẽ trở lại Java sau 6 năm trong thế giới .Net. Dường như với tôi rằng đây có thể là câu trả lời của Java cho các phương thức mở rộng C #, với một chút ảnh hưởng từ các phương thức mô-đun của Ruby. Tôi đã không chơi với nó, vì vậy tôi không thể chắc chắn.
Berin Loritsch

1
Tôi cảm thấy như lý do tại sao họ thêm các phương thức mặc định phần lớn là để họ có thể mở rộng các giao diện bộ sưu tập mà không phải thực hiện các giao diện hoàn toàn khác nhau
Justin

1
@Justin: xem java.util.function.Functionđể sử dụng các phương thức mặc định trong giao diện hoàn toàn mới.
Jörg W Mittag

@Justin Tôi đoán rằng đây là trình điều khiển chính. Tôi thực sự nên bắt đầu chú ý đến quá trình một lần nữa vì họ bắt đầu thực sự thay đổi.
JimmyJames

Câu trả lời:


12

Một trường hợp sử dụng tuyệt vời là những gì tôi gọi là giao diện "đòn bẩy": các giao diện chỉ có một số ít phương thức trừu tượng (lý tưởng là 1), nhưng cung cấp rất nhiều "đòn bẩy" trong đó chúng cung cấp cho bạn rất nhiều chức năng: bạn chỉ cần triển khai 1 phương thức trong lớp của bạn nhưng nhận được rất nhiều phương thức khác "miễn phí". Hãy nghĩ về một giao diện thu thập, ví dụ, với một trừu tượng đơn foreachphương thức và defaultphương pháp thích map, fold, reduce, filter, partition, groupBy, sort, sortBy,, vv

Dưới đây là một vài ví dụ. Hãy bắt đầu với java.util.function.Function<T, R>. Nó có một phương thức trừu tượng duy nhất R apply<T>. Và nó có hai phương thức mặc định cho phép bạn kết hợp hàm với hàm khác theo hai cách khác nhau, trước hoặc sau. Cả hai phương thức sáng tác này đều được thực hiện bằng cách sử dụngapply :

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

Bạn cũng có thể tạo một giao diện cho các đối tượng có thể so sánh, đại loại như thế này:

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

Hoặc một khung bộ sưu tập cực kỳ đơn giản, trong đó tất cả các hoạt động của bộ sưu tập trả về Collection, bất kể loại ban đầu là gì:

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

Điều này trở nên rất thú vị khi kết hợp với lambdas, bởi vì giao diện "đòn bẩy" như vậy có thể được thực hiện bởi lambda (đó là giao diện SAM).

Đây là trường hợp sử dụng tương tự như Phương thức mở rộng đã được thêm vào trong C♯, nhưng các phương thức mặc định có một ưu điểm khác biệt: chúng là các phương thức cá thể "đúng", có nghĩa là chúng có quyền truy cập vào chi tiết triển khai riêng tư của giao diện ( privatesắp có phương thức giao diện trong Java 9), trong khi Phương thức mở rộng chỉ là đường cú pháp cho phương thức tĩnh.

Nếu Java có được Giao diện tiêm, nó cũng sẽ cho phép vá khỉ kiểu mô-đun, an toàn. Điều này sẽ rất thú vị đối với những người triển khai ngôn ngữ trên JVM: hiện tại, ví dụ, JRuby hoặc thừa hưởng hoặc bọc các lớp Java để cung cấp cho họ các ngữ nghĩa bổ sung của Ruby, nhưng lý tưởng nhất là họ muốn sử dụng cùng các lớp. Với giao diện tiêm và phương pháp mặc định, họ có thể tiêm ví dụ như một RubyObjectgiao diện vào java.lang.Object, do đó một Java Objectvà Ruby Objectlà những điều tương tự chính xác .


1
Tôi không hoàn toàn làm theo điều này. Phương thức mặc định trên giao diện phải được xác định theo các phương thức khác trên giao diện hoặc các phương thức được xác định trong Object. Bạn có thể đưa ra một ví dụ về cách bạn tạo giao diện phương thức đơn có ý nghĩa với một phương thức mặc định không? Nếu bạn cần cú pháp Java 9 để chứng minh, điều đó tốt.
JimmyJames

Ví dụ: một Comparablegiao diện với một bản tóm tắt compareTophương pháp, và mặc định lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, isBetween, và clampphương pháp, tất cả được thực hiện về compareTo. Hoặc, chỉ cần nhìn vào java.util.function.Function: nó có một applyphương thức trừu tượng và hai phương thức sáng tác mặc định, cả hai đều được thực hiện theo nghĩa apply. Tôi đã thử đưa ra một ví dụ về Collectiongiao diện, nhưng làm cho tất cả trở nên an toàn về kiểu là khó khăn và quá dài cho câu trả lời này - Tôi sẽ thử đưa ra một phiên bản không bảo mật, không bảo quản kiểu. Giữ nguyên.
Jörg W Mittag

3
Các ví dụ giúp. Cảm ơn. Tôi đã hiểu nhầm ý của bạn bởi giao diện phương thức đơn.
JimmyJames

Các phương thức mặc định có nghĩa là một giao diện phương thức trừu tượng duy nhất không còn phải là một giao diện phương thức duy nhất ;-)
Jörg W Mittag

Tôi đã suy nghĩ về điều này và tôi nhận ra rằng AbstractCollection và AbstractList về cơ bản là những gì bạn đang nói ở đây (2 phương thức thay vì 1 nhưng tôi không nghĩ đó là điều cốt yếu.) Nếu chúng được làm lại như các giao diện với các phương thức defualt, thì nó sẽ cực kỳ đơn giản để biến một iterable thành một bộ sưu tập bằng cách thêm kích thước và tạo một danh sách từ bất cứ thứ gì cũng chỉ trong tích tắc nếu bạn có thể lập chỉ mục và biết kích thước.
JimmyJames
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.