2014-10-30 37 views
50

Làm cách nào để kiểm tra xem Stream có trống không và ném một ngoại lệ nếu không, như một hoạt động không phải thiết bị đầu cuối?Làm cách nào để kiểm tra xem Java 8 Stream có trống không?

Về cơ bản, tôi đang tìm kiếm nội dung nào đó tương đương với mã bên dưới, nhưng không thực hiện luồng ở giữa. Đặc biệt, việc kiểm tra không nên xảy ra trước khi luồng thực sự được tiêu thụ bởi một hoạt động đầu cuối.

public Stream<Thing> getFilteredThings() { 
    Stream<Thing> stream = getThings().stream() 
       .filter(Thing::isFoo) 
       .filter(Thing::isBar); 
    return nonEmptyStream(stream,() -> { 
     throw new RuntimeException("No foo bar things available") 
    }); 
} 

private static <T> Stream<T> nonEmptyStream(Stream<T> stream, Supplier<T> defaultValue) { 
    List<T> list = stream.collect(Collectors.toList()); 
    if (list.isEmpty()) list.add(defaultValue.get()); 
    return list.stream(); 
} 
+14

Bạn không thể có bánh của bạn và ăn nó quá - và khá nghĩa đen như vậy, trong bối cảnh này. Bạn phải * tiêu thụ * luồng để tìm hiểu xem nó có trống không. Đó là điểm của ngữ nghĩa của luồng (lười biếng). –

+0

Nó sẽ được tiêu thụ cuối cùng, tại thời điểm này việc kiểm tra sẽ xảy ra – Cephalopod

+6

Để kiểm tra xem luồng có trống không, bạn phải cố gắng tiêu thụ ít nhất một phần tử. Tại thời điểm đó, dòng suối đã mất đi "sự trinh tiết" của nó và không thể bị tiêu thụ lại từ đầu. –

Trả lời

12

Nếu bạn có thể sống với capablilities song song hạn chế, giải pháp sau đây sẽ làm việc:

private static <T> Stream<T> nonEmptyStream(
    Stream<T> stream, Supplier<RuntimeException> e) { 

    Spliterator<T> it=stream.spliterator(); 
    return StreamSupport.stream(new Spliterator<T>() { 
     boolean seen; 
     public boolean tryAdvance(Consumer<? super T> action) { 
      boolean r=it.tryAdvance(action); 
      if(!seen && !r) throw e.get(); 
      seen=true; 
      return r; 
     } 
     public Spliterator<T> trySplit() { return null; } 
     public long estimateSize() { return it.estimateSize(); } 
     public int characteristics() { return it.characteristics(); } 
    }, false); 
} 

Dưới đây là một số mã ví dụ sử dụng nó:

List<String> l=Arrays.asList("hello", "world"); 
nonEmptyStream(l.stream(),()->new RuntimeException("No strings available")) 
    .forEach(System.out::println); 
nonEmptyStream(l.stream().filter(s->s.startsWith("x")), 
       ()->new RuntimeException("No strings available")) 
    .forEach(System.out::println); 

Vấn đề với (hiệu quả) thực hiện song song là hỗ trợ chia tách của Spliterator yêu cầu một cách an toàn chỉ để xem liệu một trong các đoạn có thấy bất kỳ giá trị nào theo cách an toàn không. Sau đó, cuối cùng của các mảnh thực hiện tryAdvance phải nhận ra rằng nó là cuối cùng (và nó cũng không thể tiến) để ném ngoại lệ thích hợp. Vì vậy, tôi không thêm hỗ trợ cho việc tách ở đây.

10

Bạn phải thực hiện thao tác đầu cuối trên Luồng để có thể áp dụng bất kỳ bộ lọc nào. Vì vậy, bạn không thể biết nếu nó sẽ được sản phẩm nào cho đến khi bạn tiêu thụ nó. Điều tốt nhất bạn có thể làm là chấm dứt Luồng với hoạt động đầu cuối findAny(), sẽ dừng khi tìm thấy bất kỳ phần tử nào, nhưng nếu không có phần tử nào, nó sẽ phải lặp qua tất cả danh sách đầu vào để tìm ra.

Điều này sẽ chỉ giúp bạn nếu danh sách đầu vào có nhiều yếu tố và một trong số ít các bộ lọc đầu tiên, vì chỉ một nhóm nhỏ danh sách sẽ phải được tiêu thụ trước khi bạn biết Luồng không trống.

Tất nhiên bạn sẽ vẫn phải tạo Luồng mới để tạo danh sách đầu ra.

+2

Có 'anyMatch (alwaysTrue())', tôi nghĩ nó gần nhất với 'hasAny'. –

+1

@MarkoTopolnik Chỉ cần kiểm tra các tài liệu tham khảo - những gì tôi đã có trong tâm trí đã findAny(), mặc dù anyMatch() cũng sẽ làm việc. – Eran

+3

'anyMatch (alwaysTrue())' hoàn toàn phù hợp với ngữ nghĩa dự định của 'hasAny', cho bạn' boolean' thay vì 'Tùy chọn ' --- nhưng chúng ta đang tách lông ở đây :) –

23

Các câu trả lời và nhận xét khác là chính xác để kiểm tra nội dung của luồng, người ta phải thêm hoạt động đầu cuối, do đó "tiêu thụ" luồng. Tuy nhiên, người ta có thể làm điều này và biến kết quả trở lại thành một luồng, mà không đệm toàn bộ nội dung của luồng. Dưới đây là một vài ví dụ:

static <T> Stream<T> throwIfEmpty(Stream<T> stream) { 
    Iterator<T> iterator = stream.iterator(); 
    if (iterator.hasNext()) { 
     return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); 
    } else { 
     throw new NoSuchElementException("empty stream"); 
    } 
} 

static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Supplier<T> supplier) { 
    Iterator<T> iterator = stream.iterator(); 
    if (iterator.hasNext()) { 
     return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); 
    } else { 
     return Stream.of(supplier.get()); 
    } 
} 

Về cơ bản biến dòng thành một Iterator để gọi hasNext() vào nó, và nếu đúng, xoay Iterator trở thành một Stream. Điều này không hiệu quả trong tất cả các hoạt động tiếp theo trên luồng sẽ đi qua các phương pháp hasNext()next() của Iterator, điều này cũng ngụ ý rằng luồng được xử lý tuần tự một cách hiệu quả (ngay cả khi nó được chuyển song song). Tuy nhiên, điều này cho phép bạn kiểm tra luồng mà không phải đệm tất cả các phần tử của nó.

Có thể có cách để thực hiện việc này bằng cách sử dụng Spliterator thay vì Iterator. Điều này có khả năng cho phép luồng trả lại có các đặc điểm giống như luồng đầu vào, bao gồm cả chạy song song.

+1

Tôi không nghĩ rằng có một giải pháp duy trì có thể hỗ trợ xử lý song song hiệu quả vì khó có thể hỗ trợ chia tách, tuy nhiên có 'EstimatedSize' và' characteristics' thậm chí có thể cải thiện hiệu suất luồng đơn. Tôi đã viết giải pháp 'Spliterator' khi bạn đang đăng giải pháp' Iterator'… – Holger

+1

Bạn có thể hỏi luồng cho một Spliterator, gọi tryAdvance (lambda), nơi lambda của bạn chụp bất cứ thứ gì được truyền tới nó, và sau đó trả về một Spliterator mà đại biểu gần như tất cả mọi thứ để Spliterator cơ bản, ngoại trừ việc nó glues phần tử đầu tiên trở lại vào đoạn đầu tiên (và sửa chữa kết quả của estimizeize). –

+1

@BrianGoetz Vâng, đó là suy nghĩ của tôi, tôi chỉ chưa bận tâm để đi qua công việc chân của xử lý tất cả những chi tiết. –

1

Tiếp theo ý tưởng Stuart, điều này có thể được thực hiện với một Spliterator như thế này:

static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Stream<T> defaultStream) { 
    final Spliterator<T> spliterator = stream.spliterator(); 
    final AtomicReference<T> reference = new AtomicReference<>(); 
    if (spliterator.tryAdvance(reference::set)) { 
     return Stream.concat(Stream.of(reference.get()), StreamSupport.stream(spliterator, stream.isParallel())); 
    } else { 
     return defaultStream; 
    } 
} 

Tôi nghĩ làm việc này với Luồng song song như các hoạt động stream.spliterator() sẽ chấm dứt dòng, và sau đó xây dựng lại nó theo yêu cầu

Trong trường hợp sử dụng của tôi, tôi cần mặc định Stream thay vì giá trị mặc định. đó là khá dễ dàng để thay đổi nếu đây không phải là những gì bạn cần

+0

Tôi không thể tìm hiểu xem điều này có ảnh hưởng đáng kể đến hiệu suất với các luồng song song hay không. Có lẽ nên kiểm tra nó nếu đây là một yêu cầu – phoenix7360

+0

Xin lỗi đã không nhận ra rằng @ Holger cũng đã có một giải pháp với 'Spliterator' Tôi tự hỏi làm thế nào hai so sánh. – phoenix7360

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