2015-09-18 19 views
23

Hãy xem xét các lớp sau đây:sử dụng đệ quy của Stream.flatMap()

public class Order { 

    private String id; 

    private List<Order> orders = new ArrayList<>(); 

    @Override 
    public String toString() { 
     return this.id; 
    } 

    // getters & setters 
} 

LƯU Ý: Điều quan trọng cần lưu ý rằng tôi không thể sửa đổi lớp này, bởi vì tôi đang tiêu thụ nó từ một bên ngoài API.

Cũng xem xét hệ thống phân cấp sau đây của đơn đặt hàng:

Order o1 = new Order(); 
o1.setId("1"); 
Order o11 = new Order(); 
o11.setId("1.1"); 
Order o111 = new Order(); 
o111.setId("1.1.1"); 
List<Order> o11Children = new ArrayList<>(Arrays.asList(o111)); 
o11.setOrders(o11Children); 

Order o12 = new Order(); 
o12.setId("1.2"); 
List<Order> o1Children = new ArrayList<>(Arrays.asList(o11, o12)); 
o1.setOrders(o1Children); 

Order o2 = new Order(); 
o2.setId("2"); 
Order o21 = new Order(); 
o21.setId("2.1"); 
Order o22 = new Order(); 
o22.setId("2.2"); 
Order o23 = new Order(); 
o23.setId("2.3"); 
List<Order> o2Children = new ArrayList<>(Arrays.asList(o21, o22, o23)); 
o2.setOrders(o2Children); 

List<Order> orders = new ArrayList<>(Arrays.asList(o1, o2)); 

Mà có thể được biểu thị theo cách này:

1 
1.1 
1.1.1 
1.2 
2 
2.1 
2.2 
2.3 

Bây giờ, tôi muốn san bằng hệ thống cấp bậc này của đơn đặt hàng vào một List, do đó Tôi nhận được các thông tin sau:

[1, 1.1, 1.1.1, 1.2, 2, 2.1, 2.2, 2.3] 

Tôi đã quản lý làm điều đó bằng cách đệ quy sử dụng flatMap() (cùng với một lớp helper), như sau:

List<Order> flattened = orders.stream() 
    .flatMap(Helper::flatten) 
    .collect(Collectors.toList()); 

Đây là lớp helper:

public final class Helper { 

    private Helper() { 
    } 

    public static Stream<Order> flatten(Order order) { 
     return Stream.concat(
      Stream.of(order), 
      order.getOrders().stream().flatMap(Helper::flatten)); // recursion here 
    } 
} 

Các dòng sau:

System.out.println(flattened); 

Tạo sự sản lượng sau:

[1, 1.1, 1.1.1, 1.2, 2, 2.1, 2.2, 2.3] 

Cho đến nay rất tốt. Kết quả là hoàn toàn chính xác.

Tuy nhiên, after reading this question, tôi có một số lo ngại về việc sử dụng flatMap() trong một phương pháp đệ quy. Đặc biệt, tôi muốn biết cách luồng được mở rộng (nếu đó là thuật ngữ). Vì vậy, tôi biến đổi lớp Helper và sử dụng peek(System.out::println) để kiểm tra này:

public static final class Helper { 

    private Helper() { 
    } 

    public static Stream<Order> flatten(Order order) { 
     return Stream.concat(
      Stream.of(order), 
      order.getOrders().stream().flatMap(Helper::flatten)) 
     .peek(System.out::println); 
    } 
} 

Và kết quả là:

1 
1.1 
1.1 
1.1.1 
1.1.1 
1.1.1 
1.2 
1.2 
2 
2.1 
2.1 
2.2 
2.2 
2.3 
2.3 

Tôi không chắc chắn nếu điều này là sản phẩm đó sẽ được in.

Vì vậy, tôi tự hỏi liệu có thể cho phép luồng trung gian chứa các yếu tố lặp lại hay không. Hơn nữa, những ưu và nhược điểm của phương pháp này là gì? Có đúng không, sau khi tất cả, sử dụng flatMap() theo cách này? Có cách nào tốt hơn để đạt được điều tương tự không?

+0

Chỉ tò mò: tại sao bạn đặt id sau khi tạo thứ tự thay vì biến nó trở thành đối số cho hàm tạo? – sprinter

+0

@sprinter Tôi không thể sửa đổi lớp 'Order' vì nó là một phần của API mà tôi đang sử dụng. –

+1

Ah tôi hiểu rồi. Sau đó, hầu hết câu trả lời của tôi là khá vô ích. Nó có thể là giá trị thêm rằng phần thông tin cho câu hỏi của bạn. – sprinter

Trả lời

15

Vâng, tôi đã sử dụng cùng một mẫu với lớp chung Tree và không có cảm giác sai với nó. Điểm khác biệt duy nhất là bản thân lớp Tree được cung cấp một phương thức children()allDescendants(), cả hai đều trả về một số Stream và tòa nhà thứ hai ở trên. Điều này liên quan đến “Should I return a Collection or a Stream?”“Naming java methods that return streams”.

Từ góc nhìn Stream s, không có sự khác biệt giữa số flatMap đối với trẻ em thuộc loại khác (nghĩa là khi di chuyển thuộc tính) và flatMap cho trẻ em cùng loại. Cũng không có vấn đề gì nếu luồng trả lại chứa cùng một phần tử một lần nữa, vì không có mối quan hệ nào giữa các phần tử của các luồng. Về nguyên tắc, bạn có thể sử dụng flatMap làm hoạt động filter, sử dụng mẫu flatMap(x -> condition? Stream.of(x): Stream.empty()). Bạn cũng có thể sử dụng nó để sao chép các phần tử như trong this answer.

+0

Mối quan tâm của tôi đến từ đây: [http://stackoverflow.com/questions/29229373/why-filter-after-flatmap-is-not-completely-lazy-in-java-streams](http://stackoverflow.com/questions/29229373/why-filter-after-flatmap-là-không-hoàn toàn-lười-trong-java-suối) Nó đã cho tôi một thời gian để tìm câu hỏi –

+2

Đây là một điểm yếu trong việc thực hiện (rất nhiều người xem xét nó một lỗi), nhưng nó không làm mất hiệu lực giải pháp của bạn. Cách tiếp cận của bạn không sai. Và các vấn đề liên quan đến hiệu suất liên quan đến các hoạt động ngắn mạch nên được các nhà bảo trì JRE cố định. – Holger

+0

Cảm ơn, đó là tất cả những gì tôi muốn biết. –

10

Thực sự không có vấn đề gì với bạn khi sử dụng flatMap theo cách này. Mỗi bước trung gian trong một luồng khá độc lập (theo thiết kế) nên không có rủi ro trong đệ quy của bạn. Điều chính bạn cần xem ra là bất cứ điều gì có thể thay đổi danh sách cơ bản trong khi bạn đang phát trực tuyến nó. Trong trường hợp của bạn mà dường như không phải là một rủi ro.

Lý tưởng nhất là bạn sẽ làm cho này một phần đệ quy của lớp Order bản thân:

class Order { 
    private final List<Order> subOrders = new ArrayList<>(); 

    public Stream<Order> streamOrders() { 
     return Stream.concat(
      Stream.of(this), 
      subOrders.stream().flatMap(Order::streamOrders)); 
    } 
} 

Sau đó, bạn có thể sử dụng orders.stream().flatMap(Order::streamOrders) mà có vẻ tự nhiên hơn một chút để tôi vì sử dụng một lớp helper.

Như một vấn đề đáng quan tâm, tôi có xu hướng sử dụng các loại phương pháp stream này để cho phép sử dụng các trường thu thập chứ không phải là bộ nạp cho trường. Nếu người dùng của phương thức này không cần biết bất kỳ điều gì về bộ sưu tập cơ bản hoặc cần có khả năng thay đổi nó thì việc trả về luồng là thuận tiện và an toàn.

Tôi sẽ lưu ý rằng có một rủi ro trong cấu trúc dữ liệu của bạn mà bạn cần biết: một đơn đặt hàng có thể là một phần của một số đơn đặt hàng khác và thậm chí có thể là một phần của chính nó. Điều này có nghĩa rằng nó khá tầm thường để gây đệ quy vô hạn và một stack overflow:

Order o1 = new Order(); 
o1.setOrders(Arrays.asList(o1)); 
o1.streamOrders(); 

Có rất nhiều mô hình tốt có sẵn để tránh những loại vấn đề vì vậy xin hỏi bạn có muốn giúp đỡ một số trong khu vực đó.

Bạn chỉ ra rằng bạn không thể thay đổi lớp học Order. Trong trường hợp đó tôi đề nghị bạn mở rộng nó để tạo ra phiên bản an toàn hơn của riêng bạn:

class SafeOrder extends Order { 
    public SafeOrder(String id) { 
     setId(id); 
    } 

    public void addOrder(SafeOrder subOrder) { 
     getOrders().add(subOrder); 
    } 

    public Stream<SafeOrder> streamOrders() { 
     return Stream.concat(Stream.of(this), subOrders().flatMap(SafeOrder::streamOrders)); 
    } 

    private Stream<SafeOrder> subOrders() { 
     return getOrders().stream().map(o -> (SafeOrder)o); 
    } 
} 

Đây là một diễn viên khá an toàn vì bạn mong đợi người dùng sử dụng addOrder. Không rõ ràng vì họ vẫn có thể gọi số getOrders và thêm Order thay vì SafeOrder. Một lần nữa có các mẫu để ngăn chặn điều đó nếu bạn quan tâm.

+0

Mối quan tâm của tôi đến từ đây: [http://stackoverflow.com/questions/29229373/why-filter-after-flatmap-is-not-completely-lazy-in-java-streams](http://stackoverflow.com/question/29229373/why-filter-after-flatmap-là-không-hoàn toàn-lười-trong-java-suối) Tôi mất một thời gian để tìm câu hỏi –

+3

@FedericoPeraltaSchaffner rằng vấn đề không ảnh hưởng đến tính chính xác của giải pháp này ở tất cả. Đó là về các điều kiện mà theo đó một dòng có thể được đoản mạch ngắn - OP trong trường hợp đó cho thấy rằng trong một số điều kiện hoạt động dòng nên chấm dứt nhưng không.Câu trả lời vẫn đúng, đôi khi JRE xử lý nhiều hơn mức cần thiết. – sprinter

+0

Cảm ơn. Tôi muốn kiểm tra tính chính xác của cách tiếp cận của tôi liên quan đến câu hỏi được liên kết, nhưng câu trả lời của bạn về cách thiết kế chính xác trên kịch bản này cũng rất hữu ích. –

Các vấn đề liên quan