2016-01-08 29 views
19

Các Java 8 API nói:Tại sao luồng Java này hoạt động hai lần?

Traversal của nguồn đường ống không bắt đầu cho đến khi vận hành thiết bị đầu cuối của đường ống được thực thi.

Vậy tại sao đoạn mã sau ném:

java.lang.IllegalStateException: suối đã được hoạt động khi hoặc đóng cửa

Stream<Integer> stream = Stream.of(1,2,3); 
stream.filter(x-> x>1); 
stream.filter(x-> x>2).forEach(System.out::print); 

Các hoạt động lọc đầu tiên theo API không được phép hoạt động trên Luồng.

Trả lời

26

Điều này xảy ra vì bạn đang bỏ qua giá trị trả lại là filter.

Stream<Integer> stream = Stream.of(1,2,3); 
stream.filter(x-> x>1); // <-- ignoring the return value here 
stream.filter(x-> x>2).forEach(System.out::print); 

Stream.filter trả về một mớiStream bao gồm các yếu tố của dòng này phù hợp với vị ngữ nhất định. Nhưng điều quan trọng cần lưu ý là đó là Luồng mới. Cái cũ đã được vận hành khi bộ lọc được thêm vào nó. Nhưng cái mới thì không.

Trích dẫn từ Stream Javadoc:

Một dòng nên được phẫu thuật (cách gọi một hoạt động dòng trung gian hoặc thiết bị đầu cuối) chỉ một lần.

Trong trường hợp này, filter là thao tác trung gian hoạt động trên phiên bản Luồng cũ.

Vì vậy, mã này sẽ làm việc tốt:

Stream<Integer> stream = Stream.of(1,2,3); 
stream = stream.filter(x-> x>1); // <-- NOT ignoring the return value here 
stream.filter(x-> x>2).forEach(System.out::print); 

Theo ghi nhận của Brian Goetz, bạn sẽ thường chuỗi những cuộc gọi với nhau:

Stream.of(1,2,3).filter(x-> x>1) 
       .filter(x-> x>2) 
       .forEach(System.out::print); 
+0

Vì vậy, hoạt động trên một Stream là một cái gì đó khác với truyền tải luồng. Tôi cho rằng hoạt động trên một Stream thay đổi trạng thái bên trong của nó. –

+0

@ilias yes, nó thay đổi trạng thái nội bộ của Luồng. – mks

0

Đây là lạm dụng số stream được phát hiện khi bạn đính kèm nhiều hơn một .fliter() vào đó.

Nó không nói nó đã được đi qua nhiều hơn một lần, duy nhất mà nó đã "đã được vận hành theo"

+0

Tôi không thể nói xuống ở đây, tôi thấy điều này là * ít nhất * một thông báo lỗi lạ – Eugene

+0

@Eugene hoạt động trên luồng xảy ra khi phương thức filter() được gọi, chứ không phải khi nó bị chấm dứt. –

3

filter() phương pháp sử dụng dòng và trả về một Stream dụ khác mà bạn bỏ qua trong bạn chẳng hạn.

filter là một hoạt động trung gian nhưng bạn không thể gọi bộ lọc hai lần trên cùng một dòng dụ

Mã của bạn nên được viết như sau:

Stream<Integer> stream = Stream.of(1,2,3); 
           .filter(x-> x>1) 
           .filter(x-> x>2); 
stream.forEach(System.out::print); 

Như lọc là một hoạt động trung gian, "không có gì" được thực hiện khi gọi phương thức luận đề. Tất cả công việc thực sự được xử lý khi gọi phương thức forEach()

+0

Tôi nghĩ rằng "không có gì được thực hiện" là không chính xác. Một cái gì đó "đã được thực hiện" khi bạn gọi bộ lọc đầu tiên, và đó là lý do tại sao khi bạn gọi bộ lọc thứ hai một Stream cùng, nó không. – mks

+0

@ MrinalK.Samanta nó là lý do tại sao tôi đặt theo dấu ngoặc kép;) Tôi có nghĩa là nó chỉ là khai báo và không lọc thực sự được thực hiện tại thời điểm này, nhưng chỉ khi một hoạt động thiết bị đầu cuối như forEach được gọi là – Prim

1

Các tài liệu trên Streams nói:

"Luồng phải được vận hành trên (gọi một đường trung gian hoặc đầu cuối eam hoạt động) chỉ một lần. "

Bạn thực sự có thể thấy điều này trong mã nguồn. Khi bạn gọi lọc nó trả về một hoạt động không quốc tịch mới, đi qua các ví dụ đường ống hiện nay ở các nhà xây dựng (this):

@Override 
public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) { 
    Objects.requireNonNull(predicate); 
    return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE, 
             StreamOpFlag.NOT_SIZED) { 
     .... 
} 

Cuộc gọi constructor kết thúc lên gọi AbstractPipeline constructor, được thiết lập như thế này:

AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) { 
    if (previousStage.linkedOrConsumed) 
     throw new IllegalStateException(MSG_STREAM_LINKED); 
    previousStage.linkedOrConsumed = true; 
    ... 
} 

Lần đầu tiên bạn gọi bộ lọc trên nguồn (dòng 2), nó đặt giá trị boolean thành true. Vì bạn không sử dụng lại giá trị trả về được đưa ra bởi bộ lọc, cuộc gọi thứ hai để lọc (dòng 3) sẽ phát hiện nguồn gốc ban đầu (dòng 1) đã được liên kết (do cuộc gọi bộ lọc đầu tiên) và do đó bạn ngoại lệ được.

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