2009-01-15 90 views
36

Tôi cố gắng để biên dịch đoạn mã sau:Làm cách nào để sử dụng vòng lặp foreach trong Java để lặp qua các giá trị trong HashMap?

private String dataToString(){ 
    Map data = (HashMap<MyClass.Key, String>) getData(); 
    String toString = ""; 
    for(MyClass.Key key: data.keySet()){ 
     toString += key.toString() + ": " + data.get(key); 
    return toString; 
} 

tôi nhận được một lỗi trong cho dòng nói rằng:

 
incompatible types 
found : java.lang.Object 
required: MyClass.Key 

Phương pháp getData() trả về một Object (nhưng trong trường hợp này Object trở có cấu trúc HashMap). MyClass.Key là một enum mà tôi đã tạo ra cho các mục đích của ứng dụng của tôi (trong một tập tin lớp học - MyClass).

Khi tôi tạo vòng lặp foreach có cùng cấu trúc trong MyClass.java, tôi không gặp sự cố này.

Tôi đang làm gì sai?

+0

Không cần phải truyền getData() vào HashMap khi bạn chỉ định gán nó cho Bản đồ. Thay vào đó, hãy tạo một Bản đồ. Điều gì sẽ xảy ra nếu getData() trả về một HashMap không (giống như một TreeMap)? –

+0

Tôi thực sự đã để lại một số thông tin ở đây ... getData() thực sự là getData (String key), trong đó khóa xác định đối tượng mong muốn mà tôi muốn nhận. Vì vậy, vì tôi biết Object tôi đang nhận được, tôi biết chính xác những gì tôi nên đúc nó. – troyal

Trả lời

42

Một cách nhẹ hiệu quả hơn để làm điều này:

Map<MyClass.Key, String> data = (HashMap<MyClass.Key, String>) getData(); 
    StringBuffer sb = new StringBuffer(); 
    for (Map.Entry<MyClass.Key,String> entry : data.entrySet()) { 
     sb.append(entry.getKey()); 
     sb.append(": "); 
     sb.append(entry.getValue()); 
    } 
    return sb.toString(); 

Nếu có thể, xác định "getData" vì vậy bạn không cần dàn diễn viên.

+0

Đã bỏ phiếu này vì .entrySet() là cách hiệu quả nhất để lặp qua Bản đồ và nó giữ các tham chiếu ban đầu cho Bản đồ, do đó nếu bạn thay đổi Mục nhập, bạn thay đổi bản đồ thực tế và cứ như vậy. Nó cũng rất tiện lợi. – Esko

+0

Tôi muốn, nhưng getData() được sử dụng trên tất cả các dự án này cũng như những dự án khác, vì vậy tốt nhất nên để nó như là (trả về một đối tượng). – troyal

+0

+1, bạn chắc chắn nên thay đổi getData() để trả về Bản đồ . Nó sẽ không phá vỡ mã cũ hơn. –

38

Thay đổi:

Map data = (HashMap<MyClass.Key, String>) getData(); 

để

Map<MyClass.Key, String> data = (HashMap<MyClass.Key, String>) getData(); 

Vấn đề là data.keySet() trả về một Collection<Object> nếu dữ liệu chỉ là một Map. Khi bạn làm cho nó chung chung, keySet() sẽ trả về một Collection<MyClass.Key>. Thậm chí tốt hơn ... lặp lại trên entrySet(), sẽ là Collection<MyClass.Key, String>. Nó tránh tìm kiếm băm bổ sung.

+0

Cảm ơn! Bạn có thể giải thích lý do tại sao sửa chữa nó? – troyal

+0

Bởi vì cách bạn khai báo nó, Dữ liệu bản đồ, khai báo dữ liệu dưới dạng Bản đồ của loại không xác định, do đó, keySet() trả về Đối tượng. Thực hiện thay đổi cho trình biên dịch biết rằng các khóa là MyClass.Key, không phải Object. –

+0

Khi bạn chỉ định getData cho dữ liệu Bản đồ, bạn thực sự gán nó cho Bản đồ bằng cách định nghĩa nó là Map dữ liệu bạn cho phép vòng lặp foreach giữ lại kiến ​​thức về loại khóa là gì . –

3

Câu trả lời của Motlin là chính xác.

Tôi có hai ghi chú ...

  1. Không sử dụng toString += ..., nhưng sử dụng StringBuilder thay vào đó và nối thêm dữ liệu vào nó.

  2. Diễn viên mà Martin đề xuất sẽ cung cấp cho bạn cảnh báo không được kiểm soát, mà bạn sẽ không thể thoát khỏi, vì nó thực sự không an toàn.

Một cách khác mà không cần cảnh báo (và với StringBuilder):

private String dataToString(){ 
    Map<?, ?> data = (Map<?, ?>) getData(); 
    StringBuilder toString = new StringBuilder(); 
    for (Object key: data.keySet()) { 
     toString.append(key.toString()); 
     toString.append(": "); 
     toString.append(data.get(key)); 
    } 
    return toString.toString(); 
} 

này hoạt động, bởi vì phương thức toString mà bạn gọi vào key được định nghĩa trong lớp Object, vì vậy bạn không cần phải casting tại tất cả các.

Sử dụng entrySet thậm chí còn tốt hơn, vì không cần phải thực hiện tìm kiếm khác trong bản đồ.

+0

Bạn có thể loại bỏ cảnh báo bằng @SuppressWarning ("không được chọn") –

+0

Đó chỉ là vấn đề ẩn, không thực sự khắc phục chúng. @SuppressWarning ("bỏ chọn") nên được sử dụng với thận trọng tuyệt vời. –

+0

Tôi tự hỏi nếu nó là tốt hơn để lưu trữ data.keSet() trong một biến địa phương hoặc là vòng foreach tối ưu hóa? – Alfred

4

Bạn có thể lấy các entrySet thay vào đó, để tránh cần lớp chính:

private String dataToString(){  
    Map data = (HashMap<MyClass.Key, String>) getData();  
    String toString = "";  
    for(Map.Entry entry: data.entrySet()) {   
     toString += entry.getKey() + ": " + entry.getValue(); 
    }  
    return toString; 
} 
+0

Lặp lại thông qua mục nhập Số hiệu quả hơn là thực hiện keySet, mặc dù tôi cũng cần xuất ra tên của các khóa? – troyal

+0

Mục nhập số hiệu quả hơn vì bạn không phải thực hiện tra cứu trên mỗi khóa. –

+0

@Blue - cách hiệu quả hơn BECAUSE bạn đang sử dụng cả khóa và giá trị. –

5

Tôi đã tìm thấy ví dụ đơn giản này tại java forum.Cú pháp của nó rất giống với cú pháp của Danh sách foreach, đó là những gì tôi đang tìm kiếm.

import java.util.Map.Entry; 
HashMap nameAndAges = new HashMap<String, Integer>(); 
for (Entry<String, Integer> entry : nameAndAges.entrySet()) { 
     System.out.println("Name : " + entry.getKey() + " age " + entry.getValue()); 
} 

[EDIT:] Tôi đã thử nghiệm và hoạt động hoàn hảo.

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