Rất bối rối trước suy luận kiểu Bộ so sánh Java 8


84

Tôi đã xem xét sự khác biệt giữa Collections.sortlist.sort, đặc biệt liên quan đến việc sử dụng các Comparatorphương thức tĩnh và liệu các kiểu tham số có được yêu cầu trong các biểu thức lambda hay không. Trước khi chúng ta bắt đầu, tôi biết tôi có thể sử dụng tham chiếu phương thức, ví dụ: Song::getTitleđể khắc phục sự cố của mình, nhưng truy vấn của tôi ở đây không phải là thứ tôi muốn sửa mà là thứ tôi muốn có câu trả lời, tức là tại sao trình biên dịch Java lại xử lý nó theo cách này .

Đây là phát hiện của tôi. Giả sử chúng ta có một ArrayListloại Song, với một số bài hát được thêm vào, có 3 phương thức lấy tiêu chuẩn:

    ArrayList<Song> playlist1 = new ArrayList<Song>();

    //add some new Song objects
    playlist.addSong( new Song("Only Girl (In The World)", 235, "Rhianna") );
    playlist.addSong( new Song("Thinking of Me", 206, "Olly Murs") );
    playlist.addSong( new Song("Raise Your Glass", 202,"P!nk") );

Đây là lời gọi đến cả hai loại phương thức sắp xếp hoạt động, không vấn đề gì:

Collections.sort(playlist1, 
            Comparator.comparing(p1 -> p1.getTitle()));

playlist1.sort(
            Comparator.comparing(p1 -> p1.getTitle()));

Ngay khi tôi bắt đầu xâu chuỗi thenComparing, những điều sau sẽ xảy ra:

Collections.sort(playlist1,
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

playlist1.sort(
        Comparator.comparing(p1 -> p1.getTitle())
        .thenComparing(p1 -> p1.getDuration())
        .thenComparing(p1 -> p1.getArtist())
        );

tức là lỗi cú pháp vì nó không biết kiểu p1nữa. Vì vậy, để khắc phục điều này, tôi thêm kiểu Songvào tham số đầu tiên (so sánh):

Collections.sort(playlist1,
            Comparator.comparing((Song p1) -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

playlist1.sort(
        Comparator.comparing((Song p1) -> p1.getTitle())
        .thenComparing(p1 -> p1.getDuration())
        .thenComparing(p1 -> p1.getArtist())
        );

Bây giờ đến phần KẾT NỐI. Đối với p laylist1.sort, tức là Danh sách, điều này giải quyết tất cả các lỗi biên dịch, cho cả hai lần thenComparinggọi sau . Tuy nhiên, đối với Collections.sort, nó giải quyết nó cho cái đầu tiên, nhưng không phải cái cuối cùng. Tôi đã thử nghiệm thêm một số cuộc gọi bổ sung vào thenComparingvà nó luôn hiển thị lỗi cho cuộc gọi cuối cùng, trừ khi tôi đặt (Song p1)tham số.

Bây giờ tôi đã tiếp tục kiểm tra điều này hơn nữa với việc tạo TreeSetvà sử dụng Objects.compare:

int x = Objects.compare(t1, t2, 
                Comparator.comparing((Song p1) -> p1.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );


    Set<Song> set = new TreeSet<Song>(
            Comparator.comparing((Song p1) -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

Điều tương tự cũng xảy ra như trong, đối với TreeSet, không có lỗi biên dịch nhưng đối Objects.comparevới cuộc gọi cuối cùng thenComparinghiển thị lỗi.

Bất cứ ai có thể vui lòng giải thích tại sao điều này đang xảy ra và cũng là lý do tại sao không cần phải sử dụng (Song p1)chút nào khi chỉ cần gọi phương thức so sánh (mà không cần thenComparinggọi thêm ).

Một truy vấn khác về cùng chủ đề là khi tôi thực hiện điều này với TreeSet:

Set<Song> set = new TreeSet<Song>(
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

tức là loại bỏ kiểu Songkhỏi tham số lambda đầu tiên cho cuộc gọi phương thức so sánh, nó hiển thị lỗi cú pháp trong lệnh gọi so sánh và lệnh gọi đầu tiên thenComparingnhưng không đến lệnh gọi cuối cùng thenComparing- gần như ngược lại với những gì đang xảy ra ở trên! Trong khi đó, đối với tất cả 3 ví dụ khác tức là với Objects.compare, List.sortCollections.sortkhi tôi loại bỏ đầu tiên Songloại param nó show cú pháp lỗi cho tất cả các cuộc gọi.

Rất cám ơn trước.

Đã chỉnh sửa để bao gồm ảnh chụp màn hình các lỗi tôi đã nhận được trong Eclipse Kepler SR2, mà tôi đã tìm thấy kể từ đó là Eclipse cụ thể vì khi biên dịch bằng trình biên dịch java JDK8 trên dòng lệnh, nó biên dịch OK.

Sắp xếp lỗi trong Eclipse


Sẽ rất hữu ích nếu bạn đưa vào câu hỏi của mình tất cả các thông báo lỗi biên dịch mà bạn nhận được trong tất cả các bài kiểm tra của mình.
Eran

1
thành thật mà nói, tôi nghĩ sẽ dễ dàng nhất cho một người nào đó biết được vấn đề là gì bằng cách tự chạy mã nguồn.
Yên tĩnh

Các loại của t1t2trong Objects.compareví dụ là gì? Tôi đang cố gắng suy luận chúng, nhưng việc phân lớp suy luận kiểu của tôi trên suy luận kiểu của trình biên dịch là không thể chữa được. :-)
Stuart Marks

1
Ngoài ra bạn đang sử dụng trình biên dịch nào?
Stuart Marks

1
Bạn có hai vấn đề riêng biệt ở đây. Một trong những người trả lời đã chỉ ra rằng bạn có thể sử dụng tham chiếu phương pháp mà bạn đã loại bỏ. Cũng giống như lambdas có cả hương vị "được nhập rõ ràng" và "được nhập ngầm", các tham chiếu phương thức có các hương vị "chính xác" (một quá tải) và "không chính xác" (nhiều quá tải). Có thể sử dụng tham chiếu phương thức chính xác hoặc lambda rõ ràng để cung cấp thông tin nhập bổ sung nếu không có. (Loại Explicit nhân chứng và các cặn lắng cũng có thể được sử dụng, nhưng là búa thường lớn hơn.)
Brian Goetz

Câu trả lời:


105

Đầu tiên, tất cả các ví dụ bạn nói gây ra lỗi đều biên dịch tốt với việc triển khai tham chiếu (javac từ JDK 8.) Chúng cũng hoạt động tốt trong IntelliJ, vì vậy rất có thể các lỗi bạn đang thấy là dành riêng cho Eclipse.

Câu hỏi cơ bản của bạn dường như là: "tại sao nó ngừng hoạt động khi tôi bắt đầu chuỗi." Lý do là, trong khi các biểu thức lambda và các lệnh gọi phương thức chung là biểu thức đa (kiểu của chúng là phân biệt ngữ cảnh) khi chúng xuất hiện dưới dạng tham số phương thức, khi chúng xuất hiện thay thế dưới dạng biểu thức nhận phương thức, thì không.

Khi bạn nói

Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));

có đủ thông tin kiểu để giải quyết cho cả đối số kiểu của comparing()và kiểu đối số p1. Cuộc comparing()gọi nhận kiểu đích của nó từ chữ ký của Collections.sort, vì vậy nó được biết là comparing()phải trả về a Comparator<Song>, và do đó p1phải là Song.

Nhưng khi bạn bắt đầu chuỗi:

Collections.sort(playlist1,
                 comparing(p1 -> p1.getTitle())
                     .thenComparing(p1 -> p1.getDuration())
                     .thenComparing(p1 -> p1.getArtist()));

bây giờ chúng tôi có một vấn đề. Chúng ta biết rằng biểu thức ghép comparing(...).thenComparing(...)có kiểu đích là Comparator<Song>, nhưng vì biểu thức bộ thu cho chuỗi comparing(p -> p.getTitle()), là một lệnh gọi phương thức chung và chúng ta không thể suy ra các tham số kiểu của nó từ các đối số khác của nó, chúng ta đã gặp may . Vì chúng ta không biết loại biểu thức này, chúng ta không biết rằng nó có một thenComparingphương thức, v.v.

Có một số cách để khắc phục điều này, tất cả đều liên quan đến việc đưa thêm thông tin kiểu để đối tượng ban đầu trong chuỗi có thể được nhập đúng cách. Đây là thứ tự thô sơ của việc giảm ham muốn và tăng tính xâm nhập:

  • Sử dụng tham chiếu phương thức chính xác (một phương thức không có quá tải), như Song::getTitle. Sau đó, điều này cung cấp đủ thông tin kiểu để suy ra các biến kiểu cho lệnh comparing()gọi, và do đó cung cấp cho nó một kiểu, và do đó tiếp tục đi xuống chuỗi.
  • Sử dụng lambda rõ ràng (như bạn đã làm trong ví dụ của mình).
  • Cung cấp một loại chứng cho comparing()cuộc gọi: Comparator.<Song, String>comparing(...).
  • Cung cấp kiểu đích rõ ràng với kiểu truyền, bằng cách truyền biểu thức người nhận tới Comparator<Song>.

13
+1 để thực sự trả lời OP "tại sao trình biên dịch không thể suy ra điều này" thay vì chỉ đưa ra các cách giải quyết / giải pháp.
Joffrey

Cảm ơn bạn đã trả lời Brian. Tuy nhiên, tôi vẫn tìm thấy điều gì đó chưa được giải đáp, tại sao List.sort lại hoạt động khác với Collections.sort, ở chỗ cái trước chỉ yêu cầu lambda đầu tiên chứa kiểu tham số, nhưng cái sau cũng yêu cầu cái cuối cùng đến, ví dụ: nếu tôi có so sánh tiếp theo là 5 lệnh gọi thenComparing Tôi sẽ phải đặt (Bài hát p1) trong so sánh và cuối cùng thenComparing. Cũng trong bài đăng ban đầu của tôi, bạn sẽ thấy ví dụ dưới cùng của TreeSet nơi tôi loại bỏ tất cả các loại tham số và lệnh gọi cuối cùng tới thenComparing là OK nhưng những người khác thì không - vì vậy điều này hoạt động khác.
Yên tĩnh

3
@ user3780370 Bạn vẫn đang sử dụng trình biên dịch Eclipse? Tôi chưa thấy hành vi này, nếu tôi hiểu đúng câu hỏi của bạn. Bạn có thể (a) thử nó với javac từ JDK 8 và (b) nếu nó vẫn không thành công, hãy đăng mã?
Brian Goetz

@BrianGoetz Cảm ơn vì đề xuất này. Tôi vừa biên dịch nó trong Command Window bằng javac và nó biên dịch như bạn đã nói. Nó dường như là một vấn đề Eclipse. Tôi vẫn chưa cập nhật lên Eclipse Luna, được xây dựng có mục đích cho JDK8, vì vậy hy vọng rằng nó có thể được khắc phục. Tôi thực sự có một ảnh chụp màn hình để cho bạn thấy những gì đang xảy ra trong Eclipse nhưng không biết làm thế nào để đăng trên đây.
Yên tĩnh

2
Tôi nghĩ bạn có nghĩa là Comparator.<Song, String>comparing(...).
shmosel,

23

Vấn đề là kiểu truyền thông. Nếu không thêm a (Song s)vào so sánh đầu tiên, comparator.comparingkhông biết loại đầu vào nên nó mặc định là Đối tượng.

Bạn có thể khắc phục sự cố này bằng 1 trong 3 cách:

  1. Sử dụng cú pháp tham chiếu phương thức Java 8 mới

     Collections.sort(playlist,
                Comparator.comparing(Song::getTitle)
                .thenComparing(Song::getDuration)
                .thenComparing(Song::getArtist)
                );
    
  2. Rút từng bước so sánh thành tham chiếu cục bộ

      Comparator<Song> byName = (s1, s2) -> s1.getArtist().compareTo(s2.getArtist());
    
      Comparator<Song> byDuration = (s1, s2) -> Integer.compare(s1.getDuration(), s2.getDuration());
    
        Collections.sort(playlist,
                byName
                .thenComparing(byDuration)
                );
    

    BIÊN TẬP

  3. Buộc loại do Bộ so sánh trả về (lưu ý bạn cần cả loại nhập và loại khóa so sánh)

    sort(
      Comparator.<Song, String>comparing((s) -> s.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );
    

Tôi nghĩ rằng thenComparinglỗi cú pháp "cuối cùng" đang gây hiểu lầm cho bạn. Nó thực sự là một vấn đề về kiểu với toàn bộ chuỗi, nó chỉ là trình biên dịch chỉ đánh dấu phần cuối của chuỗi là lỗi cú pháp vì đó là khi kiểu trả về cuối cùng không khớp với tôi đoán.

Tôi không chắc tại sao lại Listthực hiện một công việc truyền thông tốt hơn Collectionvì nó phải thực hiện cùng một kiểu chụp nhưng có vẻ như không.


Tại sao nó biết nó cho ArrayListnhưng không cho Collectionsgiải pháp (cho lời gọi đầu tiên trong chuỗi có một Songtham số)?
Sotirios Delimanolis

4
Cảm ơn bạn đã trả lời, tuy nhiên, nếu bạn đọc bài đăng của tôi, bạn sẽ thấy tôi nói: "Trước khi chúng ta bắt đầu, tôi biết tôi có thể sử dụng tham chiếu phương thức, ví dụ: Song :: getTitle để khắc phục sự cố của tôi, nhưng truy vấn của tôi ở đây không quá nhiều một cái gì đó tôi muốn sửa nhưng một cái gì đó tôi muốn có câu trả lời, tức là tại sao trình biên dịch Java lại xử lý nó theo cách này. "
Yên tĩnh

Tôi muốn có câu trả lời tại sao trình biên dịch lại hoạt động theo cách đó khi tôi sử dụng biểu thức lambda. Nó chấp nhận so sánh (s -> s.getArtist ()) nhưng sau đó khi tôi chuỗi .thenComparing (s -> s.getDuration ()) chẳng hạn, nó cho tôi lỗi cú pháp cho cả hai cuộc gọi, nếu sau đó tôi thêm một kiểu rõ ràng vào lệnh gọi so sánh, ví dụ: so sánh ((Bài hát) -> s.getArtist ()) thì điều này sẽ khắc phục sự cố đó và đối với List.sort và TreeSet, nó cũng giải quyết tất cả các lỗi biên dịch khác mà không cần phải thêm các loại tham số bổ sung, tuy nhiên, đối với các Collections.sort & Objects.compare ví dụ các thenComparing cuối cùng vẫn thất bại
Tranquility

1

Một cách khác để xử lý lỗi thời gian biên dịch này:

Truyền biến hàm so sánh đầu tiên của bạn một cách rõ ràng và sau đó tốt để bắt đầu. Tôi đã sắp xếp danh sách đối tượng org.bson.Documents. Vui lòng xem mã mẫu

Comparator<Document> comparator = Comparator.comparing((Document hist) -> (String) hist.get("orderLineStatus"), reverseOrder())
                       .thenComparing(hist -> (Date) hist.get("promisedShipDate"))
                       .thenComparing(hist -> (Date) hist.get("lastShipDate"));
list = list.stream().sorted(comparator).collect(Collectors.toList());

0

playlist1.sort(...) tạo một giới hạn Bài hát cho biến kiểu E, từ khai báo danh sách phát1, "gợn sóng" đến bộ so sánh.

Trong Collections.sort(...), không có ràng buộc như vậy và suy luận từ kiểu của bộ so sánh đầu tiên là không đủ để trình biên dịch suy ra phần còn lại.

Tôi nghĩ rằng bạn sẽ nhận được hành vi "đúng" từ Collections.<Song>sort(...), nhưng không có cài đặt java 8 để kiểm tra nó cho bạn.


hi, có bạn là chính xác ở chỗ thêm bộ sưu tập <Sông> không thoát khỏi lỗi cho cuộc gọi thenComparing ngoái.
Tranquillity
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.