Sự khác biệt là như sau:
Một biểu thức lambda trông giống như
parameters -> expression
hoặc
parameters -> { block }
nơi một trong hai block
trả về một giá trị - hoặc nó không cho hành vi giống như void
.
Nói cách khác, một lambda parameters -> expression
tương đương với parameters -> { return expression; }
nếu expression
có một loại phi khoảng trống hoặc để parameters -> { expression; }
nếu expression
có kiểu void (như System.out.printf()
).
phiên bản đầu tiên của bạn về cơ bản sử dụng một biểu thức với một chút chi phí:
i -> i = i * 2
có thể được giảm xuống còn i -> i * 2
, như sự phân công i =
chỉ là không cần thiết, bởi vì i
biến mất ngay sau đó mà không được sử dụng bất kỳ hơn nữa.
Nó cũng giống như
Integer function(Integer i) {
i = i * 2;
return i;
}
hoặc
Integer function(Integer i) {
return (i = i * 2);
}
có thể được đơn giản hóa để
Integer function(Integer i) {
return i * 2;
}
Tất cả các ví dụ này sẽ phù hợp với giao diện UnaryOperator<Integer>
mà là một trường hợp đặc biệt cho Function<Integer, Integer>
.
Ngược lại, bạn ví dụ 2 là như
XY function(int i) {
i = i * 2;
}
mà không làm việc:
- Hoặc
XY
là void
(mà sẽ làm cho một Consumer<Integer>
mà không phù hợp với .map()
)
- Hoặc
XY
thực sự là Integer
(sau đó báo cáo trả về bị thiếu).
Trường hợp cần thiết cho giá trị đó (ngoại trừ việc biên dịch mã)?
Vâng, .forEach(System.out::println);
cần mà giá trị ...
Vì vậy, tất cả mọi thứ mà có thể được chuyển đổi sang một Function<T, R>
thể được trao cho một .map()
của một dòng T
, dẫn đến một R
dòng:
Stream.of(1, 2, 3).map(i -> i * 2)
Stream.of(1, 2, 3).map(i -> { return i * 2; })
chuyển số Integer
mà bạn đưa vào một số Integer
khác, cung cấp cho bạn Stream<Integer>
khác. Bạn nhận thấy rằng chúng được đóng hộp?
cách khác sẽ
// turn a Stream<Integer> to an IntStream with a
// int applyAsInt(Integer i) aka ToIntFunction<Integer>
Stream.of(1, 2, 3).mapToInt(i -> i * 2)
Stream.of(1, 2, 3).mapToInt(i -> { return i * 2; })
// turn an IntStream to a different IntStream with a
// int applyAsInt(int i) aka IntUnaryOperator
IntStream.of(1, 2, 3).map(i -> i * 2)
IntStream.of(1, 2, 3).map(i -> { return i * 2; })
// turn an IntStream to a Stream<Integer> with a
// Integer apply(int i) aka IntFunction<Integer>
IntStream.of(1, 2, 3).mapToObj(i -> i * 2)
IntStream.of(1, 2, 3).mapToObj(i -> { return i * 2; })
Tất cả các ví dụ này có điểm chung mà họ nhận được một giá trị và tạo ra một giá trị của một trong hai giống nhau hoặc một loại khác nhau. (Lưu ý cách các ví dụ sử dụng autoboxing và AutoUnboxing khi cần thiết.)
OTOH, tất cả mọi thứ mà có thể được chuyển đổi sang một Consumer<T>
thể được trao cho một .map()
của một dòng T
, mà có thể là bất kỳ hình thức lambda trong đó sản xuất một biểu void
:
.forEach(x -> System.out.println(x))
.forEach(x -> { System.out.println(x); }) // no return as you cannot return a void expression
.forEach(System.out::println) // shorter syntax for the same thing
.forEach(x -> { }) // just swallow the value
với ý nghĩ đó, nó rất dễ dàng để thấy rằng một lambda với void
kiểu biểu thức không thể được trao cho .map()
và lambda với một phi void
loại không thể được trao cho forEach()
.
Phiên bản "không cần thiết" yêu cầu phía bên tay phải của dấu mũi tên là biểu thức Java hợp lệ (lưu ý: biểu thức, không phải câu lệnh!); đây là lý do tại sao nó hoạt động. Nhưng cuối cùng cả hai làm điều tương tự. – fge