2016-09-17 51 views
9

Tôi đã sử dụng các tham chiếu lambdas và phương thức trong Java 8 một thời gian và có một điều mà tôi không hiểu. Dưới đây là mã ví dụ:Tham chiếu phương thức Java 8 và các phương thức ghi đè

Set<Integer> first = Collections.singleton(1); 
    Set<Integer> second = Collections.singleton(2); 
    Set<Integer> third = Collections.singleton(3); 

    Stream.of(first, second, third) 
      .flatMap(Collection::stream) 
      .map(String::valueOf) 
      .forEach(System.out::println); 

    Stream.of(first, second, third) 
      .flatMap(Set::stream) 
      .map(String::valueOf) 
      .forEach(System.out::println); 

Hai đường ống này làm tương tự, chúng in ra ba số, một số trên mỗi dòng. Sự khác biệt nằm trong dòng thứ hai của họ, có vẻ như bạn chỉ có thể thay thế tên lớp trong phân cấp thừa kế miễn là nó có phương thức (giao diện Collection có phương thức mặc định là "stream", không được định nghĩa lại trong giao diện Set). tôi đã cố gắng ra những gì sẽ xảy ra nếu phương pháp này là xác định lại một lần nữa và một lần nữa, sử dụng các lớp:

private static class CustomHashSet<E> extends HashSet<E> { 
    @Override 
    public Stream<E> stream() { 
     System.out.println("Changed method!"); 
     return StreamSupport.stream(spliterator(), false); 
    } 
} 

private static class CustomCustomHashSet<E> extends CustomHashSet<E> { 
    @Override 
    public Stream<E> stream() { 
     System.out.println("Changed method again!"); 
     return StreamSupport.stream(spliterator(), false); 
    } 
} 

Sau khi thay đổi nhiệm vụ đầu tiên, thứ hai và thứ ba sử dụng các lớp này tôi có thể thay thế các tài liệu tham khảo phương pháp (CustomCustomHashSet :: suối) và không ngạc nhiên là họ đã in ra các thông báo gỡ lỗi trong mọi trường hợp, ngay cả khi tôi đã sử dụng Collection :: stream. Có vẻ như bạn không thể gọi phương thức super, overriden với các tham chiếu phương thức.

Có sự khác biệt thời gian chạy nào không? Thực hành tốt hơn là gì, hãy tham khảo giao diện/lớp cấp cao nhất hoặc sử dụng loại bê tông, loại đã biết (Set)? Cảm ơn!

Chỉnh sửa: Chỉ cần rõ ràng, tôi biết về thừa kế và LSP, sự nhầm lẫn của tôi liên quan đến thiết kế tham chiếu phương thức trong Java 8. Ý tưởng đầu tiên của tôi là thay đổi lớp trong tham chiếu phương pháp sẽ thay đổi hành vi , rằng nó sẽ gọi phương pháp siêu từ lớp được chọn, nhưng như các bài kiểm tra cho thấy, nó không có sự khác biệt. Thay đổi các kiểu cá thể đã tạo sẽ thay đổi hành vi.

+3

Tôi luôn tưởng tượng các loại tham chiếu phương thức này (ví dụ tham chiếu tĩnh của phương thức mẫu) hoạt động giống như trong C++. Ví dụ được thêm vào như một tham số, làm một cái gì đó như 'A :: someMethod' =' (A a) -> a.someMethod() '. Bạn cũng có thể vượt qua một lớp con của A (LSP), tại đó công văn động điểm sẽ khởi động và gọi thực thi ghi đè. –

+1

Bạn có thể đăng bài kiểm tra mà bạn đã thực hiện với 'CustomHashSet' và' CustomCustomHashSet' không? Bởi vì ngay cả khi sử dụng tham chiếu phương thức 'Collection :: stream', nếu thể hiện của đối tượng là' CustomHashSet', nó là 'CustomHashSet.stream()' sẽ được gọi ra. Bạn có chắc là bạn đã xây dựng các đối tượng 'CustomHashSet' mới? – Tunaki

+0

@Tunaki xin lỗi nếu tôi không đủ rõ ràng. Những gì bạn mô tả là chính xác những gì đã xảy ra, nếu tôi thay đổi các trường hợp để CustomHashSet sau đó CustomHashSet.stream() đã được gọi, in "Thay đổi phương pháp!" ngay trước khi in số hiện tại. Sự nhầm lẫn của tôi là về thiết kế ngôn ngữ, suy nghĩ đầu tiên của tôi là việc thay đổi lớp trong một tham chiếu phương thức sẽ thay đổi hành vi, giống như cách bạn có thể gọi phương thức cơ sở bằng "siêu". Nhưng các bài kiểm tra cho thấy không có gì thay đổi, tất cả phụ thuộc vào loại cá thể thực tế. – Elopteryx

Trả lời

2

Tham chiếu phương pháp thậm chí phải tôn trọng nguyên tắc OOP của phương pháp ghi đè. Nếu không, mã như

public static List<String> stringify(List<?> o) { 
    return o.stream().map(Object::toString).collect(Collectors.toList()); 
} 

Vì tên lớp nào để sử dụng cho tham chiếu phương pháp: Tôi thích sử dụng lớp hoặc giao diện chung nhất khai báo phương pháp.

Lý do là: bạn viết phương thức của mình để xử lý tập hợp Set. Sau đó bạn thấy rằng phương pháp của bạn cũng có thể hữu ích cho một bộ sưu tập của Collection, vì vậy bạn thay đổi chữ ký phương thức của bạn cho phù hợp. Bây giờ nếu mã của bạn trong phương thức luôn tham chiếu phương thức Set, bạn sẽ phải điều chỉnh các tham chiếu phương thức này.

Từ

public static <T> void test(Collection<Set<T>> data) { 
    data.stream().flatMap(Set::stream).forEach(e -> System.out.println(e)); 
} 

để

public static <T> void test(Collection<Collection<T>> data) { 
    data.stream().flatMap(Collection::stream).forEach(e -> System.out.println(e)); 
} 

bạn cần thay đổi cơ thể phương pháp cũng vậy, trong khi đó nếu bạn đã viết phương pháp của bạn như

public static <T> void test(Collection<Set<T>> data) { 
    data.stream().flatMap(Collection::stream).forEach(e -> System.out.println(e)); 
} 

bạn sẽ không phải thay đổi thân phương pháp.

+0

Tôi đoán không có lý do nào để tham chiếu phương thức hoạt động khác với các lời gọi phương thức cổ điển khi nói đến thừa kế. Bạn đã trả lời câu hỏi của tôi với các ví dụ chi tiết, tôi đánh dấu câu hỏi này là câu trả lời được chấp nhận. Cảm ơn! – Elopteryx

3

A SetCollection. Collection có phương thức stream(), vì vậy Set cũng có cùng phương pháp đó, cũng như tất cả các triển khai Set (ví dụ: HashSet, TreeSet, v.v ...).

Xác định phương thức thuộc về bất kỳ loại siêu đặc biệt nào không có sự khác biệt vì nó luôn luôn giải quyết theo phương pháp thực tế được khai báo bằng cách thực hiện thực hiện của đối tượng trong thời gian chạy.


Xem Liskov Substitution Principle:

nếu S là một subtype của T, sau đó đối tượng của loại T có thể được thay thế bằng các đối tượng của loại S mà không thay đổi bất kỳ thuộc tính mong muốn của chương trình đó

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