2009-05-24 42 views
22

Tôi có một cái gì đó như thế này:Java foreach hiệu quả

Map<String, String> myMap = ...; 

for(String key : myMap.keySet()) { 
    System.out.println(key); 
    System.out.println(myMap.get(key)); 
} 

Vậy là myMap.keySet() gọi là một lần trong vòng lặp foreach ? Tôi nghĩ đó là, nhưng muốn ý kiến ​​của bạn.

Tôi muốn biết nếu sử dụng foreach theo cách này (myMap.keySet()) có tác động hiệu suất hoặc nó tương đương với điều này:

Set<String> keySet = myMap.keySet(); 
for (String key : keySet) { 
    ... 
} 
+0

(Cú pháp của vòng lặp nâng cao là một chút quay lại phía trước.) –

+2

Tôi không biết liệu tôi có đồng ý với việc gọi tối ưu hóa sớm này hay không. Nó là hợp lý để muốn hiểu những gì trình biên dịch đang làm với mã của bạn. Chúng tôi cũng không biết tại thời điểm nào trong dự án của anh ấy (nếu anh ta thậm chí còn làm việc trên một dự án và không hỏi về mặt học thuật) anh ta đang hỏi điều này. Nó có thể là vào cuối cùng. –

+0

+1 cho câu hỏi. – user12458

Trả lời

65

Nếu bạn muốn hoàn toàn chắc chắn, sau đó biên dịch cả hai cách và dịch ngược nó và so sánh. Tôi đã làm điều này với các nguồn sau đây:

public void test() { 
    Map<String, String> myMap = new HashMap<String, String>(); 

    for (String key : myMap.keySet()) { 
    System.out.println(key); 
    System.out.println(myMap.get(key)); 
    } 

    Set<String> keySet = myMap.keySet(); 
    for (String key : keySet) { 
    System.out.println(key); 
    System.out.println(myMap.get(key)); 
    } 
} 

và khi tôi dịch ngược file class với Jad, tôi nhận được:

public void test() 
{ 
    Map myMap = new HashMap(); 
    String key; 
    for(Iterator iterator = myMap.keySet().iterator(); iterator.hasNext(); System.out.println((String)myMap.get(key))) 
    { 
     key = (String)iterator.next(); 
     System.out.println(key); 
    } 

    Set keySet = myMap.keySet(); 
    String key; 
    for(Iterator iterator1 = keySet.iterator(); iterator1.hasNext(); System.out.println((String)myMap.get(key))) 
    { 
     key = (String)iterator1.next(); 
     System.out.println(key); 
    } 
} 

Vì vậy, có câu trả lời của bạn. Nó được gọi một lần với dạng vòng lặp for.

+10

+1 cho bằng chứng thực tế về trình biên dịch –

+0

Woah! thats thú vị ... Nice. +1. –

+0

Xin lỗi vì nhận xét của necro, nhưng có điều gì đó lạ ở đây. Tại sao một câu lệnh println trong phần cuối của vòng lặp for-loop, nhưng cuộc gọi đến tiếp theo là trong cơ thể? Có vẻ như một thiết lập rất kỳ quặc. – Carcigenicate

5

Vâng, nó được gọi là một lần duy nhất một trong hai cách

-2

Tôi tin rằng trình biên dịch của nó được tối ưu hóa chỉ chạy một lần cho mỗi lần nhập vòng lặp.

9

keySet() chỉ được gọi một lần. "Tăng cường vòng lặp" dựa trên giao diện Iterable, mà nó sử dụng để có được một Iterator, sau đó được sử dụng cho vòng lặp. Nó thậm chí không thể lặp qua một Set theo bất kỳ cách nào khác, vì không có chỉ mục hoặc bất kỳ thứ gì mà bạn có thể lấy các phần tử riêng lẻ. Tuy nhiên, những gì bạn thực sự nên làm là từ bỏ loại lo lắng tối ưu hóa vi mô này hoàn toàn - nếu bạn có vấn đề về hiệu suất thực, thì cơ hội là khoảng 99% mà bạn không bao giờ nghĩ đến.

+2

"những gì bạn thực sự nên làm là từ bỏ loại lo lắng tối ưu hóa vi mô hoàn toàn" Mối quan tâm ông đã có được hầu như không vi tối ưu hóa nói chung ... – hhafez

+0

Bạn có thể xây dựng một trường hợp đặc biệt độc hại dẫn đến một vấn đề hiệu suất lớn cho khá nhiều thứ, nhưng điều đó không làm thay đổi thực tế là hầu như chắc chắn sẽ không có vấn đề gì - cho một điều, các phím được lưu trữ trong bất kỳ việc thực hiện Bản đồ nào mà tôi từng thấy. –

+0

Micro optimization - Tôi quan tâm đến nó! Họ tạo sự khác biệt trong các chương trình phức tạp chắc chắn! – user12458

35

Chỉ được gọi một lần. Trong thực tế nó sử dụng một iterator để làm các trick.

Hơn nữa, trong trường hợp của bạn, tôi nghĩ bạn nên sử dụng

for (Map.Entry<String, String> entry : myMap.entrySet()) 
{ 
    System.out.println(entry.getKey()); 
    System.out.println(entry.getValue()); 
} 

để tránh tìm kiếm trên bản đồ mỗi lần.

+2

Cảm ơn tất cả các bạn, vì đã chia sẻ thông minh của bạn! Tôi muốn các đồng nghiệp của tôi giống nhau! –

+0

Điều gì sẽ xảy ra nếu myMap.entrySet() không trả lại giá trị không đổi, (myMap được cập nhật trong vòng lặp như thêm cặp khóa-giá trị)? Nó được gọi chỉ một lần? Nó sẽ không tạo ra kết quả lạ? – user12458

+0

@JavaTechnical: vấn đề là, bạn không được phép thay đổi nội dung của bản đồ trong khi cập nhật, nếu không bạn sẽ nhận được ConcurrentModificationException. Nếu bạn cần thay đổi bản đồ trong khi lặp lại, cách an toàn duy nhất để làm điều đó là thông qua một Iterator. –

7

Câu trả lời là trong ngôn ngữ Java Specification, không cần phải biên soạn lại :) Đây là những gì chúng ta có thể đọc về the enhanced for statement:

Các tăng cường cho tuyên bố có dạng:

EnhancedForStatement: 
     for (VariableModifiersopt Type Identifier: Expression) Statement 

Biểu thức phải có loại Iterable hoặc loại khác phải thuộc loại mảng (§10.1) hoặc xảy ra lỗi biên dịch .

Phạm vi của biến cục bộ được khai báo trong phần FormalParameter của tuyên bố nâng cao for (§14.14) là Tuyên bố chứa

Ý nghĩa của tăng cường for tuyên bố được đưa ra bởi dịch sang một for tuyên bố cơ bản.

Nếu loại Expression là một subtype của Iterable, sau đó để I được kiểu của biểu thức Expression.iterator(). Việc tăng cường for tuyên bố là tương đương đến một for tuyên bố cơ bản của hình thức :

for (I #i = Expression.iterator(); #i.hasNext();) { 

     VariableModifiersopt Type Identifier = #i.next(); 
    Statement 
} 

đâu #i là một trình biên dịch tạo nhận dạng đó là khác biệt với bất kỳ định danh khác (trình biên dịch tạo hoặc) nằm trong phạm vi (§6.3) tại thời điểm được tăng cường cho tuyên bố .

Nếu không, Biểu thức nhất thiết phải có loại mảng, T[]. Hãy để L1 ... Lm là chuỗi (có thể trống) nhãn ngay trước câu lệnh nâng cao for. Sau đó, ý nghĩa của tăng cường cho tuyên bố được đưa ra bởi cơ for tuyên bố sau:

T[] a = Expression; 
L1: L2: ... Lm: 
for (int i = 0; i < a.length; i++) { 
     VariableModifiersopt Type Identifier = a[i]; 
     Statement 
} 

đâu mộti là trình biên dịch tạo định danh được phân biệt với bất kỳ định danh khác (biên dịch được tạo ra hoặc cách khác) nằm trong phạm vi tại các điểm nơi tăng cường cho tuyên bố xảy ra.

Trong trường hợp của bạn, myMap.keySet() trả về một subtype của Iterable để tăng cường for tuyên bố của bạn là tương đương với for tuyên bố cơ bản sau đây:

for (Iterator<String> iterator = myMap.keySet().iterator(); iterator.hasNext();) { 
    String key = iterator.next(); 

    System.out.println(key); 
    System.out.println(myMap.get(key)); 
} 

myMap.keySet() do đó được gọi là một lần duy nhất.

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