Quay lại từ lambda forEach () trong java


97

Tôi đang cố gắng thay đổi một số vòng lặp for-each thành lambda forEach()-methods để khám phá khả năng của biểu thức lambda. Những điều sau đây dường như là có thể:

ArrayList<Player> playersOfTeam = new ArrayList<Player>();      
for (Player player : players) {
    if (player.getTeam().equals(teamName)) {
        playersOfTeam.add(player);
    }
}

Với lambda forEach()

players.forEach(player->{if (player.getTeam().equals(teamName)) {playersOfTeam.add(player);}});

Nhưng cách tiếp theo không hoạt động:

for (Player player : players) {
    if (player.getName().contains(name)) {
        return player;
    }
}

với lambda

players.forEach(player->{if (player.getName().contains(name)) {return player;}});

Có điều gì đó sai trong cú pháp của dòng cuối cùng hoặc không thể trả về từ forEach()phương thức?


Tôi chưa quá quen thuộc với nội bộ của lambdas, nhưng khi tôi đặt câu hỏi cho bản thân: "Bạn sẽ quay trở lại từ đâu?", Tôi nghi ngờ ban đầu rằng đó không phải là phương pháp.
Gimby

1
@Gimby Có, returntrong một câu lệnh lambda trả về từ chính lambda, không phải từ bất cứ thứ gì được gọi là lambda. Việc kết thúc sớm luồng sử dụng ("chập mạch") findFirstnhư được thể hiện trong câu trả lời của Ian Roberts .
Stuart Marks

Câu trả lời:


121

returntrả về từ biểu thức lambda chứ không phải từ phương thức chứa. Thay vì forEachbạn cần filterphát trực tiếp:

players.stream().filter(player -> player.getName().contains(name))
       .findFirst().orElse(null);

Ở đây filtergiới hạn luồng đối với những mục phù hợp với vị từ, findFirstsau đó trả về một Optionalvới mục phù hợp đầu tiên.

Cách này trông kém hiệu quả hơn so với phương pháp vòng lặp vòng lặp, nhưng trên thực tế findFirst()có thể gây đoản mạch - nó không tạo ra toàn bộ luồng đã lọc và sau đó trích xuất một phần tử từ đó, thay vào đó nó chỉ lọc nhiều phần tử cần thiết để tìm cái phù hợp đầu tiên. Bạn cũng có thể sử dụng findAny()thay vì findFirst()nếu bạn không nhất thiết phải quan tâm đến việc nhận được trình phát phù hợp đầu tiên từ luồng (đã đặt hàng) mà chỉ đơn giản là bất kỳ vật phẩm phù hợp nào. Điều này cho phép hiệu quả tốt hơn khi có liên quan đến song song.


Cảm ơn, đó là những gì tôi đang tìm kiếm! Có vẻ như có rất nhiều điều mới trong Java8 để khám phá :)
samutamm

10
Hợp lý, nhưng tôi đề nghị bạn không sử dụng orElse(null)trên một Optional. Điểm chính của Optionalnó là cung cấp một cách để chỉ ra sự hiện diện hoặc vắng mặt của một giá trị thay vì quá tải giá trị null (dẫn đến NPE). Nếu bạn sử dụng optional.orElse(null)nó sẽ mua lại tất cả các vấn đề với null. Tôi chỉ sử dụng nó nếu bạn không thể sửa đổi trình gọi và nó thực sự mong đợi một giá trị rỗng.
Stuart Marks

1
@StuartMarks thực sự, việc sửa đổi kiểu trả về của phương thức thành Optional<Player>sẽ là một cách tự nhiên hơn để phù hợp với mô hình luồng. Tôi chỉ đang cố gắng chỉ ra cách sao chép hành vi hiện có bằng lambdas.
Ian Roberts

for (Part part: các phần) if (! part.isEmpty ()) return false; Tôi tự hỏi những gì thực sự là ngắn hơn. Và rõ ràng hơn. Api dòng java đã thực sự phá hủy ngôn ngữ java và môi trường java. Ác mộng phải làm việc trong bất kỳ dự án java nào vào năm 2020
mmm

17

Tôi đề nghị bạn trước tiên nên cố gắng hiểu toàn bộ Java 8, quan trọng nhất trong trường hợp của bạn, đó sẽ là các luồng, lambdas và tham chiếu phương thức.

Bạn không bao giờ nên chuyển đổi mã hiện có sang mã Java 8 trên cơ sở từng dòng một, bạn nên trích xuất các tính năng và chuyển đổi chúng.

Những gì tôi xác định được trong trường hợp đầu tiên của bạn là:

  • Bạn muốn thêm các phần tử của cấu trúc đầu vào vào danh sách đầu ra nếu chúng khớp với một số vị từ.

Hãy xem cách chúng tôi làm điều đó, chúng tôi có thể làm điều đó với những điều sau:

List<Player> playersOfTeam = players.stream()
    .filter(player -> player.getTeam().equals(teamName))
    .collect(Collectors.toList());

Những gì bạn làm ở đây là:

  1. Biến cấu trúc đầu vào của bạn thành một luồng (tôi giả sử ở đây rằng nó thuộc loại Collection<Player>, bây giờ bạn có một Stream<Player>.
  2. Lọc bỏ tất cả các phần tử không mong muốn bằng a Predicate<Player>, ánh xạ mọi trình phát tới boolean true nếu nó được giữ lại.
  3. Thu thập các phần tử kết quả trong một danh sách, thông qua a Collector, ở đây chúng ta có thể sử dụng một trong các trình thu thập thư viện tiêu chuẩn, đó là Collectors.toList().

Điều này cũng kết hợp hai điểm khác:

  1. Mã chống lại các giao diện, vì vậy mã chống lại List<E>hơn ArrayList<E>.
  2. Sử dụng suy luận kim cương cho tham số kiểu trong new ArrayList<>(), dù gì thì bạn cũng đang sử dụng Java 8.

Bây giờ vào điểm thứ hai của bạn:

Bạn lại muốn chuyển đổi một thứ gì đó của Java cũ sang Java 8 mà không cần nhìn vào bức tranh lớn hơn. Phần này đã được trả lời bởi @IanRoberts , mặc dù tôi nghĩ rằng bạn cần phải làm players.stream().filter(...)...theo những gì anh ấy đề xuất.


5

Nếu bạn muốn trả về một giá trị boolean, thì bạn có thể sử dụng một cái gì đó như thế này (nhanh hơn nhiều so với bộ lọc):

players.stream().anyMatch(player -> player.getName().contains(name));


1

Bạn cũng có thể đưa ra một ngoại lệ:

Ghi chú:

Để dễ đọc, mỗi bước của luồng phải được liệt kê trong dòng mới.

players.stream()
       .filter(player -> player.getName().contains(name))
       .findFirst()
       .orElseThrow(MyCustomRuntimeException::new);

nếu logic của bạn là "định hướng ngoại lệ" lỏng lẻo, chẳng hạn như có một vị trí trong mã của bạn bắt tất cả các ngoại lệ và quyết định phải làm gì tiếp theo. Chỉ sử dụng phát triển theo hướng ngoại lệ khi bạn có thể tránh xả rác cơ sở mã của mình với nhiều bội số try-catchvà việc ném các ngoại lệ này dành cho các trường hợp rất đặc biệt mà bạn mong đợi và có thể xử lý đúng cách.)

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.