Java .min () và .max (): tại sao điều này biên dịch?


215

Lưu ý: câu hỏi này bắt nguồn từ một liên kết chết là câu hỏi SO trước đó, nhưng ở đây ...

Xem mã này ( lưu ý: Tôi biết rằng mã này sẽ không "hoạt động" và Integer::comparenên được sử dụng - Tôi chỉ trích xuất nó từ câu hỏi được liên kết ):

final ArrayList <Integer> list 
    = IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList());

System.out.println(list.stream().max(Integer::max).get());
System.out.println(list.stream().min(Integer::min).get());

Theo javadoc của .min().max(), đối số của cả hai nên là a Comparator. Tuy nhiên, ở đây các tham chiếu phương thức là các phương thức tĩnh của Integerlớp.

Vì vậy, tại sao điều này biên dịch ở tất cả?


6
Lưu ý rằng nó không hoạt động đúng, nó nên được sử dụng Integer::comparethay vì Integer::maxInteger::min.
Christoffer Hammarström

@ Christoffer Hammarström Tôi biết rằng; lưu ý cách tôi nói trước khi trích xuất mã "Tôi biết, điều đó thật vô lý"
fge

3
Tôi đã không cố gắng sửa lỗi cho bạn, tôi đang nói với mọi người nói chung. Bạn đã làm cho nó nghe như thể bạn nghĩ rằng phần vô lý là phương pháp Integerkhông phải là phương pháp Comparator.
Christoffer Hammarström

Câu trả lời:


242

Hãy để tôi giải thích những gì đang xảy ra ở đây, bởi vì nó không rõ ràng!

Đầu tiên, Stream.max()chấp nhận một thể hiện Comparatorđể các mục trong luồng có thể được so sánh với nhau để tìm mức tối thiểu hoặc tối đa, theo một thứ tự tối ưu mà bạn không cần phải lo lắng quá nhiều.

Vì vậy, câu hỏi là, tất nhiên, tại sao được Integer::maxchấp nhận? Sau tất cả, nó không phải là một so sánh!

Câu trả lời là theo cách mà chức năng lambda mới hoạt động trong Java 8. Nó dựa trên một khái niệm được gọi một cách không chính thức là các giao diện "phương thức trừu tượng đơn" hoặc giao diện "SAM". Ý tưởng là mọi giao diện với một phương thức trừu tượng đều có thể được thực hiện tự động bởi bất kỳ lambda nào - hoặc tham chiếu phương thức - có chữ ký phương thức phù hợp với một phương thức trên giao diện. Vì vậy, kiểm tra Comparatorgiao diện (phiên bản đơn giản):

public Comparator<T> {
    T compare(T o1, T o2);
}

Nếu một phương thức đang tìm kiếm một Comparator<Integer>, thì về cơ bản nó đang tìm kiếm chữ ký này:

int xxx(Integer o1, Integer o2);

Tôi sử dụng "xxx" vì tên phương thức không được sử dụng cho mục đích phù hợp .

Do đó, cả hai Integer.min(int a, int b)Integer.max(int a, int b)đủ gần để autoboxing sẽ cho phép điều này xuất hiện dưới dạng Comparator<Integer>trong ngữ cảnh phương thức.


28
hoặc cách khác : list.stream().mapToInt(i -> i).max().get().
assylias

13
@assylias Bạn muốn sử dụng .getAsInt()thay vì get()mặc dù, như bạn đang đối phó với một OptionalInt.
skiwi

... Khi điều chúng ta chỉ cố gắng là cung cấp một bộ so sánh tùy chỉnh cho một max()chức năng!
Manu343726

Điều đáng chú ý là "Giao diện SAM" này thực sự được gọi là "Giao diện chức năng" và nhìn vào Comparatortài liệu chúng ta có thể thấy rằng nó được trang trí với chú thích @FunctionalInterface. Trang trí này là ma thuật cho phép Integer::maxInteger::minđược chuyển đổi thành một Comparator.
Chris Kerekes

2
@ChrisKerekes trình trang trí @FunctionalInterfacechủ yếu chỉ dành cho mục đích tài liệu, vì trình biên dịch có thể vui vẻ làm điều này với bất kỳ giao diện nào với một phương thức trừu tượng duy nhất.
errantlinguist

117

Comparatorlà một giao diện chức năngInteger::maxtuân thủ giao diện đó (sau khi xem xét tự động / hủy hộp thư). Nó nhận hai intgiá trị và trả về một int- giống như bạn mong đợiComparator<Integer> (một lần nữa, nheo mắt để bỏ qua sự khác biệt Integer / int).

Tuy nhiên, tôi sẽ không mong đợi nó làm điều đúng đắn, vì điều Integer.maxđó không tuân thủ ngữ nghĩa của Comparator.compare. Và thực sự nó không thực sự hoạt động nói chung. Ví dụ: thực hiện một thay đổi nhỏ:

for (int i = 1; i <= 20; i++)
    list.add(-i);

... và bây giờ maxgiá trị là -20 và mingiá trị là -1.

Thay vào đó, cả hai cuộc gọi nên sử dụng Integer::compare:

System.out.println(list.stream().max(Integer::compare).get());
System.out.println(list.stream().min(Integer::compare).get());

1
Tôi biết về các giao diện chức năng; và tôi biết tại sao nó cho kết quả sai. Tôi chỉ tự hỏi làm thế nào trên trái đất trình biên dịch đã không hét vào mặt tôi
fge 21/03 '

6
@fge: Vâng, đó là việc unboxing mà bạn không rõ ràng? (Tôi đã không nhìn vào phần chính xác điều đó.) Một Comparator<Integer>sẽ phải int compare(Integer, Integer)... nó không quan tâm-boggling rằng Java cho phép một tài liệu tham khảo phương pháp int max(int, int)để chuyển đổi để mà ...
Jon Skeet

7
@fge: Trình biên dịch có nghĩa vụ phải biết ngữ nghĩa của Integer::maxkhông? Từ quan điểm của nó, bạn đã thông qua một chức năng đáp ứng đặc điểm kỹ thuật của nó, đó là tất cả những gì nó thực sự có thể tiếp tục.
Mark Peters

6
@fge: Đặc biệt, nếu bạn hiểu một phần của những gì đang diễn ra, nhưng bị thu hút về một khía cạnh cụ thể của nó, thì đáng để làm rõ điều đó trong câu hỏi để tránh mọi người lãng phí thời gian giải thích các bit bạn đã biết.
Jon Skeet

20
Tôi nghĩ rằng vấn đề cơ bản là chữ ký loại Comparator.compare. Nó sẽ trả về một enumsố {LessThan, GreaterThan, Equal}, không phải là một int. Theo cách đó, giao diện chức năng sẽ không thực sự khớp và bạn sẽ gặp lỗi biên dịch. IOW: chữ ký loại Comparator.comparekhông nắm bắt đầy đủ ngữ nghĩa của việc so sánh hai đối tượng và do đó các giao diện khác hoàn toàn không liên quan gì đến việc so sánh các đối tượng vô tình có chữ ký cùng loại.
Jörg W Mittag

19

Điều này hoạt động vì Integer::mingiải quyết việc thực hiện Comparator<Integer>giao diện.

Tham chiếu phương thức Integer::mingiải quyết Integer.min(int a, int b), giải quyết IntBinaryOperatorvà có lẽ là hộp thư tự động xảy ra ở đâu đó làm cho nó trở thànhBinaryOperator<Integer> .

Và các phương thức min()resp max()của giao diện Stream<Integer>yêu cầu Comparator<Integer>được thực hiện.
Bây giờ điều này giải quyết theo phương pháp duy nhất Integer compareTo(Integer o1, Integer o2). Cái nào thuộc loạiBinaryOperator<Integer> .

Và do đó, phép thuật đã xảy ra khi cả hai phương pháp là a BinaryOperator<Integer>.


Nó không hoàn toàn chính xác để nói rằng Integer::minthực hiện Comparable. Nó không phải là một loại có thể thực hiện bất cứ điều gì. Nhưng nó được đánh giá thành một đối tượng thực hiện Comparable.
Lii

1
@Lii Cảm ơn, tôi đã sửa nó ngay bây giờ.
skiwi

Comparator<Integer>là một giao diện đơn phương thức trừu tượng (còn gọi là "chức năng") và Integer::minhoàn thành hợp đồng của nó, vì vậy lambda có thể được hiểu như thế này. Tôi không biết làm thế nào bạn thấy BinaryOperator xuất hiện ở đây (hoặc IntBinaryOperator) - không có mối quan hệ phụ giữa điều đó và Trình so sánh.
Paŭlo Ebermann

2

Ngoài thông tin được cung cấp bởi David M. Lloyd, người ta có thể thêm rằng cơ chế cho phép điều này được gọi là gõ mục tiêu .

Ý tưởng là kiểu trình biên dịch gán cho biểu thức lambda hoặc tham chiếu phương thức không chỉ phụ thuộc vào chính biểu thức mà còn phụ thuộc vào nơi nó được sử dụng.

Mục tiêu của một biểu thức là biến mà kết quả của nó được gán hoặc tham số mà kết quả của nó được truyền vào.

Các biểu thức và tham chiếu phương thức Lambda được gán một loại khớp với loại mục tiêu của chúng, nếu loại đó có thể được tìm thấy.

Xem phần Kiểu suy luận trong Hướng dẫn Java để biết thêm thông tin.


1

Tôi đã gặp lỗi với một mảng lấy max và min vì vậy giải pháp của tôi là:

int max = Arrays.stream(arrayWithInts).max().getAsInt();
int min = Arrays.stream(arrayWithInts).min().getAsInt();
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.