Tại sao tôi nên sử dụng các hoạt động chức năng của Cameron, thay vì một vòng lặp for?


39
for (Canvas canvas : list) {
}

NetBeans đề nghị tôi sử dụng "các hoạt động chức năng":

list.stream().forEach((canvas) -> {
});

Nhưng tại sao điều này được ưa thích ? Nếu bất cứ điều gì, nó khó đọc và hiểu hơn. Bạn đang gọi stream(), sau đó forEach()sử dụng biểu thức lambda với tham số canvas. Tôi không thấy cái nào đẹp hơn forvòng lặp trong đoạn đầu tiên.

Rõ ràng tôi chỉ nói về thẩm mỹ mà thôi. Có lẽ có một lợi thế kỹ thuật ở đây mà tôi đang thiếu. Nó là gì? Tại sao tôi nên sử dụng phương pháp thứ hai thay thế?



15
Trong ví dụ cụ thể của bạn, nó sẽ không được ưa thích.
Robert Harvey

1
Miễn là thao tác duy nhất là một forEach duy nhất, tôi có xu hướng đồng ý với bạn. Ngay khi bạn thêm các hoạt động khác vào đường ống hoặc bạn tạo ra một chuỗi đầu ra, thì cách tiếp cận luồng trở nên thích hợp hơn.
JacquesB

@RobertHarvey phải không? tại sao không?
sara

@RobertHarvey Tôi đồng ý câu trả lời được chấp nhận thực sự cho thấy phiên bản dành cho phiên bản bị thổi ra khỏi nước đối với các trường hợp phức tạp hơn, nhưng tôi không hiểu tại sao lại "thắng" trong trường hợp tầm thường. bạn nói nó giống như nó hiển nhiên nhưng tôi không thấy nó, vì vậy tôi đã hỏi.
sara

Câu trả lời:


45

Luồng cung cấp sự trừu tượng hóa tốt hơn nhiều cho thành phần của các hoạt động khác nhau mà bạn muốn thực hiện trên đầu các bộ sưu tập hoặc luồng dữ liệu đến. Đặc biệt là khi bạn cần ánh xạ các thành phần, lọc và chuyển đổi chúng.

Ví dụ của bạn không thực tế lắm. Hãy xem xét các mã sau đây từ trang web của Oracle .

List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
  if(t.getType() == Transaction.GROCERY){
    groceryTransactions.add(t);
  }
}
Collections.sort(groceryTransactions, new Comparator(){
  public int compare(Transaction t1, Transaction t2){
    return t2.getValue().compareTo(t1.getValue());
  }
});
List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
  transactionsIds.add(t.getId());
}

có thể được viết bằng luồng:

List<Integer> transactionsIds = 
    transactions.stream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

Tùy chọn thứ hai dễ đọc hơn nhiều. Vì vậy, khi bạn có các vòng lặp lồng nhau hoặc các vòng lặp khác nhau để xử lý một phần, đó là ứng cử viên rất tốt cho việc sử dụng API Streams / Lambda.


5
Có các kỹ thuật tối ưu hóa như hợp nhất bản đồ ( stream.map(f).map(g)stream.map(f.andThen(g))) xây dựng / giảm nhiệt hạch (khi xây dựng một luồng trong một phương thức và sau đó chuyển nó sang một phương thức khác tiêu thụ nó, trình biên dịch có thể loại bỏ luồng) và hợp nhất luồng (có thể hợp nhất nhiều luồng op cùng nhau thành một vòng lặp mệnh lệnh duy nhất), có thể làm cho hoạt động truyền phát hiệu quả hơn nhiều. Chúng được triển khai trong trình biên dịch GHC Haskell, và trong một số trình biên dịch ngôn ngữ chức năng Haskell khác và khác, và có các triển khai nghiên cứu thử nghiệm cho Scala.
Jörg W Mittag

Hiệu suất có thể không phải là một yếu tố khi xem xét chức năng so với vòng lặp vì chắc chắn trình biên dịch có thể / nên thực hiện chuyển đổi từ vòng lặp for sang hoạt động chức năng nếu Netbeans có thể và nó được xác định là đường dẫn tối ưu.
Ryan

Tôi không đồng ý rằng thứ hai dễ đọc hơn. Phải mất một thời gian để tìm hiểu những gì đang xảy ra. Có một lợi thế về hiệu suất để thực hiện nó theo phương pháp thứ hai bởi vì nếu không thì tôi không thấy nó?
Bok McDonagh

@BokMcDonagh, tôi có kinh nghiệm là các nhà phát triển không thể đọc được các bản tóm tắt mới. Tôi sẽ đề nghị sử dụng nhiều API như vậy, để làm quen hơn, vì đó là tương lai. Không chỉ trong thế giới Java.
Luboskrnac

16

Một ưu điểm khác của việc sử dụng API phát trực tuyến chức năng là nó ẩn các chi tiết triển khai. Nó chỉ mô tả những gì nên được thực hiện, không phải làm thế nào. Ưu điểm này trở nên rõ ràng khi nhìn vào sự thay đổi cần phải thực hiện, để thay đổi từ thực hiện luồng đơn sang xử lý mã song song. Chỉ cần thay đổi .stream()thành .parallelStream().


13

Nếu bất cứ điều gì, nó khó đọc và hiểu hơn.

Đó là chủ quan cao. Tôi thấy phiên bản thứ hai dễ đọc và dễ hiểu hơn nhiều. Nó phù hợp với cách các ngôn ngữ khác (ví dụ: Ruby, Smalltalk, Clojure, Io, Ioke, Seph) làm điều đó, nó đòi hỏi ít khái niệm hơn để hiểu (đó chỉ là một cách gọi phương thức bình thường như mọi ngôn ngữ khác, trong khi ví dụ đầu tiên là cú pháp chuyên biệt).

Nếu bất cứ điều gì, đó là một vấn đề quen thuộc.


6
Vâng, đó là sự thật. Nhưng điều này trông giống như một bình luận hơn là một câu trả lời cho tôi.
Omega

Hoạt động tự do cũng sử dụng cú pháp chuyên dụng: "->" là mới
Ryan
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.