Trong Java, các lợi thế của các luồng trên các vòng lặp là gì? [đóng cửa]


133

Tôi đã được hỏi điều này tại một cuộc phỏng vấn và tôi không tin rằng tôi đã đưa ra câu trả lời tốt nhất mà tôi có thể có. Tôi đã đề cập rằng bạn có thể thực hiện tìm kiếm song song và các giá trị null được xử lý bằng một số phương tiện mà tôi không thể nhớ được. Bây giờ tôi nhận ra tôi đã nghĩ về Tùy chọn. Tôi đang thiếu gì ở đây? Họ cho rằng đó là mã tốt hơn hoặc ngắn gọn hơn nhưng tôi không chắc là mình đồng ý.


Xem xét cách trả lời ngắn gọn, có vẻ như đây không phải là một câu hỏi quá rộng.


Nếu họ đang hỏi câu hỏi này tại các cuộc phỏng vấn, và rõ ràng là họ, mục đích nào có thể phá vỡ nó phục vụ ngoài mục đích làm cho việc tìm câu trả lời khó hơn? Ý tôi là, bạn đang tìm gì vậy? Tôi có thể chia nhỏ câu hỏi và trả lời tất cả các câu hỏi phụ nhưng sau đó tạo một câu hỏi phụ huynh với các liên kết đến tất cả các câu hỏi con ... mặc dù có vẻ khá ngớ ngẩn. Trong khi chúng ta đang ở đó, xin vui lòng cho tôi một ví dụ về một câu hỏi ít rộng hơn. Tôi biết không có cách nào để chỉ hỏi một phần của câu hỏi này và vẫn nhận được một câu trả lời có ý nghĩa. Tôi có thể hỏi chính xác cùng một câu hỏi theo một cách khác. Ví dụ: tôi có thể hỏi "Luồng phục vụ mục đích gì?" hoặc "Khi nào tôi sẽ sử dụng một luồng thay vì vòng lặp for?" hoặc "Tại sao phải bận tâm với các luồng thay vì các vòng lặp?" Đây là tất cả chính xác cùng một câu hỏi mặc dù.

... Hoặc nó được coi là quá rộng vì ai đó đã đưa ra một câu trả lời đa điểm thực sự dài? Thành thật mà nói bất cứ ai biết có thể làm điều đó với hầu như bất kỳ câu hỏi. Ví dụ, nếu bạn là một trong những tác giả của JVM, có lẽ bạn có thể nói về các vòng lặp suốt cả ngày khi hầu hết chúng ta không thể.

"Vui lòng chỉnh sửa câu hỏi để giới hạn câu hỏi cho một vấn đề cụ thể với đủ chi tiết để xác định câu trả lời đầy đủ. Tránh hỏi nhiều câu hỏi khác nhau cùng một lúc. Xem trang Cách hỏi để được trợ giúp làm rõ câu hỏi này."

Như đã lưu ý dưới đây, một câu trả lời đầy đủ đã được đưa ra chứng minh rằng có một và nó đủ dễ để cung cấp.


7
Đây là ý kiến ​​dựa trên imho. Cá nhân, tôi thích các luồng vì nó làm cho mã dễ đọc hơn. Nó cho phép viết những gì bạn muốn thay vì làm thế nào . Hơn nữa, nó là hoàn toàn xấu khi làm những điều tuyệt vời với một lớp lót.
Arnaud Denoyelle

19
Ngay cả khi đó là một dòng 30, một lót? Tôi không thích chuỗi dài.
user447607

1
Bên cạnh đó, tất cả những gì tôi đang tìm kiếm ở đây là câu trả lời thích hợp cho một cuộc phỏng vấn. Đây là "ý kiến" duy nhất quan trọng.
user447607

1
Nói về mặt giáo dục, câu hỏi này cũng giúp tôi tiết kiệm được một cuộc phỏng vấn trong tương lai, @slim thực sự đóng đinh nó, nhưng nói một cách công nghiệp, nó cũng nói về cách các ngôn ngữ lập trình của Microsoft xây dựng sự nghiệp của họ bằng cách xé toạc ngôn ngữ java, và cuối cùng java đã trả thù bằng cách xé toạc tắt biểu thức Lambda và phát trực tiếp từ các đối thủ, hãy xem java sẽ làm gì về Structs và Unions trong tương lai :)
ShayHaned

5
Lưu ý rằng các luồng chỉ khai thác một phần sức mạnh trong lập trình chức năng: - /
Thorbjørn Ravn Andersen

Câu trả lời:


251

Điều thú vị là câu hỏi phỏng vấn hỏi về những lợi thế, mà không hỏi về những bất lợi, vì có cả hai.

Các luồng là một phong cách khai báo nhiều hơn . Hoặc một phong cách biểu cảm hơn . Có thể được coi là tốt hơn để tuyên bố ý định của bạn trong mã, hơn là mô tả cách nó được thực hiện:

 return people
     .filter( p -> p.age() < 19)
     .collect(toList());

... nói khá rõ ràng rằng bạn đang lọc các yếu tố phù hợp từ một danh sách, trong khi:

 List<Person> filtered = new ArrayList<>();
 for(Person p : people) {
     if(p.age() < 19) {
         filtered.add(p);
     }
 }
 return filtered;

Nói "Tôi đang làm một vòng lặp". Mục đích của vòng lặp được chôn sâu hơn trong logic.

Suối thường terser . Ví dụ tương tự cho thấy điều này. Terser không phải lúc nào cũng tốt hơn, nhưng nếu bạn có thể ngắn gọn và biểu cảm cùng một lúc, thì càng nhiều càng tốt.

Các luồng có ái lực mạnh mẽ với các chức năng . Java 8 giới thiệu lambdas và các giao diện chức năng, mở ra toàn bộ toybox các kỹ thuật mạnh mẽ. Các luồng cung cấp cách thuận tiện và tự nhiên nhất để áp dụng các hàm cho chuỗi các đối tượng.

Các luồng khuyến khích ít biến đổi . Đây là loại liên quan đến khía cạnh lập trình chức năng - loại chương trình bạn viết bằng luồng có xu hướng là loại chương trình mà bạn không sửa đổi đối tượng.

Các luồng khuyến khích khớp nối lỏng lẻo hơn . Mã xử lý luồng của bạn không cần biết nguồn của luồng hoặc phương thức kết thúc cuối cùng của luồng.

Luồng có thể thể hiện ngắn gọn hành vi khá tinh vi . Ví dụ:

 stream.filter(myfilter).findFirst();

Có thể nhìn vào cái nhìn đầu tiên như thể nó lọc toàn bộ luồng, sau đó trả về phần tử đầu tiên. Nhưng trên thực tế, điều findFirst()khiển toàn bộ hoạt động, vì vậy nó dừng hiệu quả sau khi tìm thấy một mục.

Các luồng cung cấp phạm vi cho lợi ích hiệu quả trong tương lai . Một số người đã đo điểm chuẩn và nhận thấy rằng các luồng đơn luồng từ Lists trong bộ nhớ hoặc mảng có thể chậm hơn vòng lặp tương đương. Điều này là hợp lý bởi vì có nhiều đối tượng và chi phí cao hơn trong chơi.

Nhưng quy mô dòng. Cũng như hỗ trợ tích hợp sẵn của Java cho các hoạt động luồng song song, có một vài thư viện để giảm bản đồ phân tán sử dụng Luồng làm API, vì mô hình phù hợp.

Nhược điểm?

Hiệu năng : Một forvòng lặp thông qua một mảng cực kỳ nhẹ cả về mặt sử dụng heap và CPU. Nếu tốc độ thô và tiết kiệm bộ nhớ là ưu tiên, sử dụng luồng sẽ tệ hơn.

Quen thuộc . Thế giới đầy những lập trình viên thủ tục có kinh nghiệm, từ nhiều nền tảng ngôn ngữ, với những vòng lặp quen thuộc và các luồng là tiểu thuyết. Trong một số môi trường, bạn muốn viết mã quen thuộc với loại người đó.

Chi phí nhận thức . Do tính chất khai báo của nó và tăng tính trừu tượng từ những gì xảy ra bên dưới, bạn có thể cần xây dựng một mô hình tinh thần mới về cách mã liên quan đến thực thi. Trên thực tế, bạn chỉ cần làm điều này khi có sự cố, hoặc nếu bạn cần phân tích sâu về hiệu suất hoặc các lỗi tinh vi. Khi nó "chỉ hoạt động", nó chỉ hoạt động.

Trình gỡ lỗi đang được cải thiện, nhưng ngay cả bây giờ, khi bạn bước qua mã luồng trong trình gỡ lỗi, nó có thể khó hơn vòng lặp tương đương, bởi vì một vòng lặp đơn giản rất gần với các biến và vị trí mã mà trình gỡ lỗi truyền thống làm việc.


4
Tôi nghĩ thật công bằng khi gọn gàng rằng những thứ giống như luồng đang trở nên phổ biến hơn nhiều và hiện xuất hiện trong rất nhiều ngôn ngữ thường được sử dụng mà không đặc biệt là định hướng FP.
Casey

5
Với những ưu và nhược điểm được liệt kê ở đây, tôi nghĩ rằng các luồng KHÔNG đáng giá cho bất cứ điều gì ngoài việc sử dụng rất đơn giản (logic nhỏ nếu / sau đó, không có nhiều cuộc gọi lồng nhau hoặc lambdas, v.v.), trong các phần hiệu suất không phê bình
Henrik Kjus Alstad

1
@HenrikKjusAlstad Đó hoàn toàn không phải là thứ tôi muốn giao tiếp. Các luồng là trưởng thành, mạnh mẽ, biểu cảm và hoàn toàn thích hợp cho mã cấp sản xuất.
mỏng

Ồ, tôi không có nghĩa là tôi sẽ không sử dụng nó trong sản xuất. Nhưng thay vào đó, tôi sẽ mặc định các vòng lặp / ifs lỗi thời, v.v., thay vì các luồng, đặc biệt là nếu luồng kết quả sẽ trông phức tạp. Tôi chắc chắn có những cách sử dụng trong đó một luồng sẽ đánh bại các vòng lặp và nếu rõ ràng, nhưng thường xuyên hơn là không, tôi nghĩ đó là "bị trói", hoặc đôi khi thậm chí ngược lại. Vì vậy, tôi đã đặt nặng vấn đề tranh luận về nhận thức để tuân theo những cách thức cũ.
Henrik Kjus Alstad

1
@lijepdam - nhưng bạn vẫn có mã có nội dung "Tôi đang lặp lại danh sách này (xem bên trong vòng lặp để tìm hiểu lý do tại sao)", khi "lặp lại danh sách" không phải là mục đích cốt lõi của mã.
mỏng

16

Về mặt cú pháp, Luồng được thiết kế để hoạt động với các tập dữ liệu có khả năng vô cùng lớn, trong khi các mảng, Bộ sưu tập và gần như mọi lớp Java SE thực hiện Iterable hoàn toàn nằm trong bộ nhớ.

Một nhược điểm của Luồng là các bộ lọc, ánh xạ, v.v., không thể đưa ra các ngoại lệ được kiểm tra. Điều này làm cho Stream trở thành một lựa chọn kém cho các hoạt động I / O trung gian.


7
Tất nhiên, bạn cũng có thể lặp qua các nguồn vô hạn.
mỏng

Nhưng nếu các yếu tố cần xử lý được duy trì trong một DB, làm thế nào để bạn sử dụng Luồng? Một nhà phát triển cơ sở có thể bị cám dỗ để đọc tất cả chúng trong Bộ sưu tập chỉ để sử dụng Luồng. Và đó sẽ là một thảm họa.
Lluis Martinez

2
@LluisMartinez một thư viện máy khách DB tốt sẽ trả về một cái gì đó giống như Stream<Row>- hoặc có thể viết các Streamthao tác con trỏ kết quả DB của riêng mình .
mỏng

Luồng đó âm thầm bỏ qua các ngoại lệ là một lỗi tôi mới bị cắn gần đây. Không trực quan.
xxfelixxx

@xxfelixxx Luồng không âm thầm bỏ qua các ngoại lệ. Hãy thử chạy nó:Arrays.asList("test", null).stream().forEach(s -> System.out.println(s.length()));
VGR

8
  1. Bạn nhận ra không chính xác: các hoạt động song song sử dụng Streams, không phải Optionals.

  2. Bạn có thể xác định các phương thức làm việc với các luồng: lấy chúng làm tham số, trả về chúng, v.v. Bạn không thể định nghĩa một phương thức lấy một vòng lặp làm tham số. Điều này cho phép một hoạt động luồng phức tạp một lần và sử dụng nó nhiều lần. Lưu ý rằng Java có một nhược điểm ở đây: các phương thức của bạn phải được gọi someMethod(stream)là trái ngược với luồng của chính nó stream.someMethod(), vì vậy việc trộn chúng làm phức tạp việc đọc: thử xem thứ tự các thao tác trong

    myMethod2(myMethod(stream.transform(...)).filter(...))

    Nhiều ngôn ngữ khác (C #, Kotlin, Scala, v.v.) cho phép một số dạng "phương thức mở rộng".

  3. Ngay cả khi bạn chỉ cần các thao tác tuần tự và không muốn sử dụng lại chúng, để bạn có thể sử dụng các luồng hoặc vòng lặp, các thao tác đơn giản trên các luồng có thể tương ứng với các thay đổi khá phức tạp trong các vòng lặp.


Giải thích 1. Không phải giao diện Tùy chọn là phương tiện mà null được xử lý trong chuỗi? Về 3, điều đó có ý nghĩa bởi vì với các bộ lọc ngắn mạch, phương thức sẽ chỉ được gọi cho các lần xuất hiện được chỉ định. Có hiệu quả. Điều hợp lý là tôi có thể nói rằng việc sử dụng chúng giúp giảm nhu cầu viết mã bổ sung cần kiểm tra, v.v. Khi xem xét, tôi không chắc ý của bạn về trường hợp tuần tự trong 2.
user447607

1. Optionallà một thay thế cho null, nhưng nó không có gì để làm với các hoạt động song song. Trừ khi "Bây giờ tôi nhận ra tôi đã nghĩ đến Tùy chọn" trong câu hỏi của bạn chỉ nói về việc nullxử lý?
Alexey Romanov

Tôi đã thay đổi thứ tự 2 và 3 và mở rộng cả hai một chút.
Alexey Romanov

6

Bạn lặp qua một chuỗi (mảng, bộ sưu tập, đầu vào, ...) vì bạn muốn áp dụng một số chức năng cho các thành phần của chuỗi.

Các luồng cung cấp cho bạn khả năng soạn thảo các hàm trên các phần tử chuỗi và cho phép thực hiện hầu hết các hàm phổ biến (ví dụ: ánh xạ, lọc, tìm, sắp xếp, thu thập, ...) độc lập với trường hợp cụ thể.

Do đó, với một số tác vụ lặp trong hầu hết các trường hợp, bạn có thể diễn đạt nó với ít mã hơn bằng cách sử dụng Luồng, tức là bạn có thể đọc được .


4
Vâng, nó không chỉ dễ đọc mặc dù. Mã bạn không phải viết là mã bạn không phải kiểm tra.
user447607

3
có vẻ như bạn cũng đang nghĩ ra câu trả lời hay cho cuộc phỏng vấn của mình
wero

6

Tôi muốn nói rằng sự song song của nó rất dễ sử dụng. Hãy thử lặp lại hàng triệu mục nhập song song với một vòng lặp for. Chúng tôi đi nhiều cpus, không nhanh hơn; Vì vậy, càng dễ chạy song song thì càng tốt, và với Streamđiều này thật dễ dàng.

Điều tôi thích rất nhiều là sự dài dòng mà họ cung cấp. Phải mất ít thời gian để hiểu những gì họ thực sự làm và sản xuất trái ngược với cách họ làm điều đó.

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.