2012-06-26 48 views
53

thể trùng lặp:
Why is Java's Iterator not an Iterable?Tại sao Java không cho phép foreach trên các trình vòng lặp (chỉ trên các vòng lặp)?

Idiomatic way to use for-each loop given an iterator?

Can we use for-each loop for iterating the objects of Iterator type?

Các foreach vòng lặp là như xa như tôi biết đường cú pháp bổ sung trong Java 5. Vì vậy,

Iterable<O> iterable; 
for(O o : iterable) { 
    // Do something 
} 

về cơ bản sẽ tạo ra bytecode giống như

Iterable<O> iterable; 
for(Iterator<O> iter = iterable.iterator(); iter.hasNext(); /* NOOP */) { 
    O o = iter.next(); 
    // Do something 
} 

Tuy nhiên, nếu tôi không có một iterable ở nơi đầu tiên, nhưng chỉ một iterator (nói, bởi vì một lớp học cung cấp hai vòng lặp khác nhau), tôi có thể không sử dụng vòng lặp foreach đường cú pháp. Rõ ràng tôi vẫn có thể thực hiện lặp lại kiểu cũ đơn giản. Tuy nhiên, tôi thực sự muốn làm:

Iterator<O> iter; 
for(O o : iter /* Iterator<O>, not Iterable<O>! */) { 
    // Do something 
} 

Và dĩ nhiên tôi có thể làm một giả Iterable:

class Adapter<O> implements Iterable<O> { 
    Iterator<O> iter; 

    public Adapter(Iterator<O> iter) { 
     this.iter = iter; 
    } 

    @Override 
    public Iterator<O> iterator() { 
     return iter; 
    } 
} 

(Mà trên thực tế là một sự lạm dụng xấu xí của API Iterable, vì nó có thể ! chỉ được lặp một lần)

Nếu nó được thiết kế xung quanh Iterator thay vì iterable, người ta có thể làm một số điều thú vị:

for(O o : iterable.iterator()) {} // Iterate over Iterable and Collections 

for(O o : list.backwardsIterator()) {} // Or backwards 

Iterator<O> iter; 
for(O o : iter) { 
    if (o.something()) { iter.remove(); } 
    if (o.something()) { break; } 
} 
for(O : iter) { } // Do something with the remaining elements only. 

Có ai biết lý do tại sao ngôn ngữ được thiết kế theo cách này? Để tránh sự mơ hồ nếu một lớp học sẽ triển khai cả hai IteratorIterable? Để tránh các lỗi lập trình giả định rằng "for (O o: iter)" sẽ xử lý tất cả các phần tử hai lần (và quên để có được một iterator mới)? Hoặc là có một số lý do khác cho việc này?

Hoặc có một số mẹo ngôn ngữ tôi chỉ không biết?

+1

Hoặc [this one] (http://stackoverflow.com/questions/2162917/can-we-use-for-each-loop-for-iterating-the-objects-of-iterator-type). –

+1

Cả hai bản sao gần giống nhau, nhưng không hoàn toàn giống nhau. Iterator không nên lặp lại, bởi vì bạn chỉ có thể lặp lại một lần (không "khởi động lại"). Và tôi biết rằng bạn không thể sử dụng foreach trên các trình vòng lặp (trừ khi bạn sử dụng bộ điều hợp mà tôi phác thảo), nhưng tôi đang cố gắng hiểu tại sao Java được thiết kế theo cách này. –

+0

Meh; Tôi có xu hướng không đồng ý. Câu trả lời được đưa ra trong 1.5 tài liệu nói rằng các nhà thiết kế đã chọn cách này để xử lý các trường hợp sử dụng phổ biến nhất. Đôi khi câu trả lời * là * "chỉ vì". –

Trả lời

17

Vì vậy, tôi có một lời giải thích nào hợp lý bây giờ:

phiên bản ngắn: Bởi vì cú pháp cũng áp dụng cho mảng, mà không cần phải lặp.

Nếu cú ​​pháp được thiết kế xung quanh Iterator như tôi đã đề xuất, nó sẽ không phù hợp với mảng. Hãy để tôi cung cấp cho ba phiên bản:

A) như được lựa chọn bởi các nhà phát triển Java:

Object[] array; 
for(Object o : array) { } 
Iterable<Object> list; 
for(Object o : list) { } 
Iterator<Object> iter; 
while(iter.hasNext()) { Object o = iter.next(); } 

Các cư xử theo cùng một cách và là cao phù hợp qua mảng và các bộ sưu tập. Tuy nhiên, Iterator phải sử dụng kiểu lặp lại cổ điển (ít nhất là không có khả năng gây ra lỗi).

B) phép mảng và Iterators:

Object[] array; 
for(Object o : array) { } 
Iterable<Object> list; 
for(Object o : list.iterator()) { } 
Iterator<Object> iter; 
for(Object o : iter) { } 

Bây giờ mảng và các bộ sưu tập không phù hợp; nhưng mảng và ArrayList có liên quan rất chặt chẽ và nên hành xử theo cùng một cách. Bây giờ nếu tại bất kỳ thời điểm nào, ngôn ngữ là mở rộng để thực hiện ví dụ: mảng thực hiện Iterable, nó trở nên không nhất quán.

C) cho phép tất cả ba:

Object[] array; 
for(Object o : array) { } 
Iterable<Object> list; 
for(Object o : list) { } 
Iterator<Object> iter; 
for(Object o : iter) { } 

Bây giờ nếu chúng ta kết thúc trong tình huống không rõ ràng khi một trong hai người nào thực hiện cảIterableIterator (là vòng lặp for trù được một iterator mới hoặc lặp quá dòng - xảy ra dễ dàng trong cấu trúc giống cây!?!). Một đơn giản tie-braker ala "Iterable nhịp đập Iterator" tiếc là sẽ không làm: nó đột nhiên giới thiệu thời gian chạy so với sự khác biệt thời gian biên dịch và các vấn đề generics. Bây giờ đột nhiên, chúng ta cần phải chú ý đến việc liệu chúng ta muốn lặp qua các bộ sưu tập/vòng lặp hay mảng hay không, tại thời điểm này chúng ta đã thu được rất ít lợi ích với chi phí của một sự nhầm lẫn lớn.

Cách "cho mỗi" là trong Java (A) là rất nhất quán, nó gây ra rất ít lỗi lập trình và cho phép thay đổi trong tương lai có thể biến mảng thành đối tượng thông thường.

Có một biến thể D) cũng có thể hoạt động tốt: cho mỗi chỉ dành cho Iterator.Ưu tiên sử dụng bằng cách thêm một phương pháp .iterator() để mảng nguyên thủy:

Object[] array; 
for(Object o : array.iterator()) { } 
Iterable<Object> list; 
for(Object o : list.iterator()) { } 
Iterator<Object> iter; 
for(Object o : iter) { } 

Nhưng điều này đòi hỏi phải có những thay đổi đối với môi trường thời gian chạy, không chỉ là trình biên dịch, và phá vỡ tính tương thích ngược. Ngoài ra, sự nhầm lẫn được đề cập vẫn còn hiện diện là

Iterator<Object> iter; 
for(Object o : iter) { } 
for(Object o : iter) { } 

Chỉ lặp lại dữ liệu một lần.

+0

* "Với cú pháp Iterator, người ta cần viết ..." * Tại sao? –

+0

Bởi vì nếu không tôi có thể có một lớp thực hiện cả hai 'Iterable' và' Iterator' và gây ra sự nhầm lẫn thời gian lớn. –

+0

Và điều đó có liên quan gì đến mảng?Tôi có thể làm điều đó anyway, mà không tham chiếu đến mảng - và nó * chính xác * loại phức tạp tôi gọi ra trong câu trả lời của tôi (cắt giảm "đoán" :-)). –

31

Có ai biết lý do tại sao ngôn ngữ được thiết kế theo cách này?

Vì for-each chỉ có ý nghĩa hơn những điều iter thể, và không có ý nghĩa hơn iter ators. Nếu bạn đã có một trình lặp, bạn đã có những gì bạn cần làm với một vòng lặp đơn giản.

Hãy so sánh: Tôi bắt đầu với một iter thể:

// Old way 
Iterator<Thingy> it = iterable.iterator(); 
while (it.hasNext()) { 
    Thingy t = it.next(); 
    // Use `t` 
} 

// New way 
for (Thingy t : iterable) { 
    // Use `t` 
} 

Versus Tôi bắt đầu với một iterator:

// Old/current way 
while (iterator.hasNext()) { 
    Thing t = iterator.next(); 
    // Use `t` 
} 

// Imagined way 
for (Thingy t : iterator) { 
    // Use `t` 
} 

Không có nhiều trong nó trong ví dụ thứ hai, và nó làm phức tạp ngữ nghĩa của từng cái bằng cách tạo ra một trường hợp đặc biệt.

Câu hỏi "Tại sao" luôn khó khăn khi không được hướng dẫn tại những người tham gia chính tham gia vào quyết định, nhưng tôi đoán là sự phức tạp thêm không đáng giá so với tiện ích cận biên.

+0

bên cạnh đó, bạn chỉ có thể đi một lần qua tất cả các mục trong một trình lặp vòng – Qnan

+1

Tại sao nó chỉ có ý nghĩa đối với những thứ "có thể lặp lại", như đã nói ngược với * trình lặp *? Tôi rõ ràng có thể muốn làm điều gì đó "cho mọi phần tử được trả về bởi trình lặp này". –

+10

Không thấy đối số cho điều đó. Nếu bạn viết "những cách cũ" bằng cách sử dụng một vòng lặp for thay vì trong khi, bạn nhận được khá nhiều cùng một mã. Và hãy giơ tay nếu phiên bản "phiên bản cũ" của trình duyệt trông trực quan hơn với bạn. Một lập luận tốt hơn sẽ là vòng lặp for được ngầm phá hoại mà có thể gây ngạc nhiên - tôi vẫn không thích nó mặc dù. – Voo

8

Giao diện Iterable được tạo chính xác cho mục đích đó (được tăng cường cho vòng lặp) như được mô tả trong original JSR, mặc dù giao diện Iterator đã được sử dụng.

+0

Cảm ơn con trỏ đó. Bây giờ tôi có một ý tưởng thô sơ về những gì có thể là lý do. Tôi sẽ phải suy nghĩ thêm một chút về những dòng đó, sau đó tôi sẽ cập nhật. –

+2

Rõ ràng, lý do để giới thiệu 'java.lang.Iterable' thay vì chỉ sử dụng' java.util.Iterator' là để tránh sự phụ thuộc ngôn ngữ cốt lõi vào 'java.util'. ;) –

6

Vì vòng lặp "for" sẽ hủy hoại đối với trình lặp. Iterator không thể được thiết lập lại (tức là di chuyển trở lại phần đầu) trừ khi nó thực hiện subInterator ListIterator.

Khi bạn đặt Iterator qua vòng lặp "for", nó sẽ không còn sử dụng được nữa. Tôi đoán là các nhà thiết kế ngôn ngữ đã quyết định rằng điều này kết hợp với các trường hợp đặc biệt bổ sung (trong đó đã có hai cho Iterable và mảng) trong trình biên dịch để dịch thành bytecode (bạn không thể tái sử dụng cùng một phép biến đổi như iterable) là đủ một kẻ phản đối để không thực hiện nó.

Khi bạn tự làm điều này trong mã thông qua giao diện trình lặp, nó ít nhất sẽ được rõ ràng rõ ràng những gì đang xảy ra.

Với lambdas sắp tới họ có thể làm đẹp này và dễ dàng:

Iterator<String> iterator = ...; 
Collections.each (iterator, (String s) => { System.out.println(s); }); 

List<String> list = ...; 
Collections.each (list, (String s) => { System.out.println(s); }); 

mà không vi phạm tương thích ngược, và vẫn có một cú pháp tương đối đơn giản. Tôi nghi ngờ họ sẽ xây dựng các phương pháp như "mỗi", "thu thập" và "bản đồ" vào các giao diện khác nhau vì điều đó sẽ phá vỡ sự tương thích ngược, cộng với bạn vẫn còn mảng để xử lý.

+0

Vâng, nếu "đặt lại" được thực hiện một cách rõ ràng bằng cách gọi '.iterator()' trên 'Iterable' s, thì việc đặt lại cũng phải rõ ràng. Vâng, người ta có thể vẫn bỏ qua không đặt lại ... –

+0

Iterable mang lại cho bạn một cơ chế lặp có thể dự đoán trong vòng lặp lặp lại nhiều lần cho kết quả tương tự mỗi lần (giả sử bạn chưa ẩn dữ liệu). Điều tương tự không thể nói nếu bạn có thể lặp qua một Iterator (kể từ khi tôi nói, nó không thể được thiết lập lại). – Matt

+0

Đối với một số kiểu dữ liệu như hàng đợi, khái niệm tiêu thụ "cho mỗi" có ý nghĩa. Cho phép các loại là "for-each-possible" mà không có lời hứa là * liên tục * đếm được, có vẻ như là một khả năng hữu ích và các trình vòng lặp vốn có khả năng được đọc chính xác một lần. – supercat

0

Tôi nghĩ rằng một phần của câu trả lời có thể được ẩn trong thực tế là vòng lặp cho mỗi là cú pháp đường. Vấn đề là bạn muốn làm một cái gì đó mà mọi người làm rất nhiều, dễ dàng hơn rất nhiều. Và (ít nhất theo kinh nghiệm của tôi) thành ngữ

Iterator iterator = iterable.iterator(); 
while(iterator.hasNext()) { 
    Element e = (Element)iterator.next() ; 
} 

xảy ra mọi lúc trong mã kiểu cũ. Và làm những điều kỳ lạ với nhiều người lặp lại thì không.

+0

về mặt kỹ thuật, tôi nghĩ bytecode trông giống như vòng lặp for: for (Iterator iterator = iterable.iterator(); iterator.hasNext() ;) – Matt

+0

Trong thực tế trong JSR, nơi họ đề xuất cho mỗi cú pháp, họ đã sử dụng loại * for * loop. Lý do là biến phạm vi, Iterator sau đó chỉ được xác định bên trong vòng lặp. Cú pháp trong khi là sách giáo khoa Java cổ điển, nhưng bạn cũng cần phải thêm một '{}' nữa để có được sự thay đổi phạm vi biến. –

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