2015-01-03 30 views
13

Tôi tự hỏi làm thế nào để tất cả các công cụ này có tham chiếu phương thức và giao diện chức năng hoạt động ở mức thấp hơn. Ví dụ đơn giản nhất là nơi chúng tôi có một số Danh sáchTham chiếu đến các phương thức có tham số khác nhau trong Java8

List<String> list = new ArrayList<>(); 
list.add("b"); 
list.add("a"); 
list.add("c"): 

Bây giờ chúng ta muốn sắp xếp nó với lớp Collections, vì vậy chúng tôi có thể gọi:

Collections.sort(list, String::compareToIgnoreCase); 

Nhưng nếu chúng ta định nghĩa so sánh tùy chỉnh nó có thể là một cái gì đó như :

Comparator<String> customComp = new MyCustomOrderComparator<>(); 
Collections.sort(list, customComp::compare); 

Vấn đề là Collections.sort nhận hai tham số: Danh sách và so sánh. Vì Comparator là giao diện chức năng, nó có thể được thay thế bằng biểu thức lambda hoặc tham chiếu phương thức có cùng chữ ký (tham số và kiểu trả về). Vì vậy, làm thế nào nó hoạt động mà chúng tôi có thể vượt qua cũng tham chiếu đến compareTo mà chỉ mất một tham số và chữ ký của các phương pháp này không phù hợp? Tham chiếu phương thức được dịch như thế nào trong Java8?

+1

, thậm chí có thể ví dụ rõ ràng hơn: 'Comparator cmp = String :: compareToIgnoreCase; 'Thành thực mà nói, tôi không biết nó hoạt động như thế nào. – MightyPork

+0

Tôi tin rằng điều này có thể bị đóng dưới dạng bản sao của http://stackoverflow.com/questions/20001427/double-colon-operator-in-java-8 – MightyPork

Trả lời

16

Từ Oracle method references tutorial: Reference

đến một phương pháp dụ của một đối tượng tùy ý của một loại đặc biệt

Sau đây là một ví dụ về một tham chiếu đến một phương pháp thể hiện của một đối tượng tùy ý một loại đặc biệt:

String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

Biểu thức lambda tương đương cho tham chiếu phương thức String::compareToIgnoreCase sẽ có danh sách tham số chính thức (String a, String b), trong đó a và b là các tên tùy ý được sử dụng để mô tả tốt hơn ví dụ này. Tham chiếu phương thức sẽ gọi phương thức a.compareToIgnoreCase(b).

Nhưng, những gì các nhà điều hành :: thực sự nghĩa là gì? Vâng, các nhà điều hành :: thể được mô tả như thế này (từ this SO question):

Phương pháp tham khảo có thể thu được trong phong cách khác nhau, nhưng tất cả đều có nghĩa như nhau:

  1. Một phương pháp tĩnh (ClassName::methodName)
  2. một phương pháp thể hiện của một đối tượng cụ thể (instanceRef::methodName)
  3. một phương pháp siêu của một đối tượng cụ thể (super::methodName)
  4. Một phương pháp thể hiện của một đối tượng tùy tiện của một loại đặc biệt (ClassName::methodName)
  5. Tham chiếu constructor lớp (ClassName::new)
  6. Một tài liệu tham khảo constructor mảng (TypeName[]::new)

Vì vậy, đó có nghĩa là phương pháp tham chiếu String::compareToIgnoreCase thuộc danh mục thứ hai (instanceRef::methodName) có nghĩa là nó có thể được dịch sang (a, b) -> a.compareToIgnoreCase(b).

Tôi tin rằng các ví dụ sau minh họa điều này hơn nữa. A Comparator<String> chứa một phương thức hoạt động trên hai toán hạng String và trả về một int. Điều đó có thể được mô tả giả là (a, b) ==> return int (trong đó các toán hạng là ab). Nếu bạn xem theo cách đó, tất cả các mục sau thuộc danh mục đó:

// Trad anonymous inner class 
// Operands: o1 and o2. Return value: int 
Comparator<String> cTrad = new Comparator<String>() { 
    @Override 
    public int compare(final String o1, final String o2) { 
     return o1.compareToIgnoreCase(o2); 
    } 
}; 

// Lambda-style 
// Operands: o1 and o2. Return value: int 
Comparator<String> cLambda = (o1, o2) -> o1.compareToIgnoreCase(o2); 

// Method-reference à la bullet #2 above. 
// The invokation can be translated to the two operands and the return value of type int. 
// The first operand is the string instance, the second operand is the method-parameter to 
// to the method compareToIgnoreCase and the return value is obviously an int. This means that it 
// can be translated to "instanceRef::methodName". 
Comparator<String> cMethodRef = String::compareToIgnoreCase; 

This great SO-answer to explains how lambda functions are compiled. Trong câu trả lời đó, Jarandinor đề cập đến đoạn sau đây từ tài liệu tuyệt vời của Brian Goetz mô tả more about lambda translations.

Thay vì tạo bytecode để tạo đối tượng triển khai biểu thức lambda (như gọi hàm tạo cho lớp bên trong), chúng tôi mô tả công thức xây dựng lambda và ủy quyền xây dựng thực tế cho thời gian chạy ngôn ngữ. Công thức đó được mã hóa trong các danh sách đối số tĩnh và động của một lệnh gọi động.

Về cơ bản, điều này có nghĩa là thời gian chạy gốc quyết định cách dịch lambda.

Brian tiếp tục:

Phương pháp tài liệu tham khảo được đối xử giống như biểu thức lambda, ngoại trừ rằng hầu hết tài liệu tham khảo phương pháp không cần phải được khử đường vào một phương pháp mới; chúng ta có thể chỉ cần tải một phương thức xử lý hằng số cho phương thức được tham chiếu và chuyển nó tới metafactory.

Vì vậy, lambdas là khử đường thành một phương pháp mới. Ví dụ.

class A { 
    public void foo() { 
     List<String> list = ... 
     list.forEach(s -> { System.out.println(s); }); 
    } 
} 

Đoạn mã trên sẽ khử đường một cái gì đó như thế này:

class A { 
    public void foo() { 
     List<String> list = ... 
     list.forEach([lambda for lambda$1 as Consumer]); 
    } 

    static void lambda$1(String s) { 
     System.out.println(s); 
    } 
} 

Nhưng, Brian cũng giải thích điều này trong tài liệu:

nếu phương pháp khử đường là một ví dụ phương thức, người nhận được coi là đối số đầu tiên

Brian tiếp tục giải thích rằng đối số còn lại của lambda được chuyển làm đối số cho phương thức được giới thiệu.

Như vậy, với sự giúp đỡ của this entry by Moandji Ezana, các desugaring của compareToIgnoreCase như một Comparator<String> có thể được chia nhỏ các bước sau:

  • Collections#sort cho một List<String> hy vọng một Comparator<String>
  • Comparator<String> là một giao diện chức năng với phương pháp int sort(String, String), tương đương với BiFunction<String, String, Integer>
  • Trường hợp so sánh do đó có thể được cung cấp bởi BiFunction lambda -tương thích: (String a, String b) -> a.compareToIgnoreCase(b)
  • String::compareToIgnoreCase đề cập đến một phương pháp dụ rằng có một cuộc tranh luận String, vì vậy nó là tương thích với lambda trên: String a trở thành người nhận và String b trở thành lý luận phương pháp

Edit: sau khi đầu vào từ OP tôi đã thêm một mức thấp dụ giải thích Thú vị câu hỏi desugaring

+0

Cảm ơn, tất cả những gì bạn đã mô tả là đúng, nhưng nó vẫn rất tư duy cấp cao và ví dụ từ hướng dẫn của Oracle rất dễ hiểu. Vấn đề vẫn còn với phương thức compareTo không khớp với chữ ký phương thức 'compare()' của Comparator. Nếu chúng ta có '(a, b) -> a.compareTo (b)' và '(a, b) -> comparator.compare (a, b)' thì có sự khác biệt khá lớn. Điều đó có nghĩa là tham chiếu phương thức phải khớp với chữ ký biểu thức lambda '(a, b) -> int' và không phải là chữ ký phương thức thực tế? – swch

+1

@slawekwch. Tôi đã cố gắng "hạ thấp cấp độ" một chút với một lời giải thích về cách 'compareToIgnoreCase' là * desugared * để' Comparator '. – wassgren

+0

oh đó là một lời giải thích tuyệt vời, cảm ơn – swch

Các vấn đề liên quan