2010-05-04 14 views
8

Tôi đã gặp một hành vi kỳ lạ của Java có vẻ như là một lỗi. Là nó? Đúc một đối tượng cho một loại chung (ví dụ: K) không ném ClassCastException ngay cả khi đối tượng không phải là một phiên bản của K. Dưới đây là một ví dụ:Việc đưa vào kiểu generic trong Java không làm tăng ClassCastException?

import java.util.*; 
public final class Test { 
    private static<K,V> void addToMap(Map<K,V> map, Object ... vals) { 
    for(int i = 0; i < vals.length; i += 2) 
     map.put((K)vals[i], (V)vals[i+1]); //Never throws ClassCastException! 
    } 
    public static void main(String[] args) { 
    Map<String,Integer> m = new HashMap<String,Integer>(); 
    addToMap(m, "hello", "world"); //No exception 
    System.out.println(m.get("hello")); //Prints "world", which is NOT an Integer!! 
    } 
} 

Cập nhật: Nhờ Cletus và Andrzej Doyle cho câu trả lời hữu ích của bạn. Vì tôi chỉ có thể chấp nhận một, tôi chấp nhận Andrzej Doyle's answer vì nó đã dẫn tôi đến một giải pháp mà tôi nghĩ không phải là quá xấu. Tôi nghĩ rằng đó là một cách tốt hơn để khởi tạo một Bản đồ nhỏ trong một lớp lót.

/** 
    * Creates a map with given keys/values. 
    * 
    * @param keysVals Must be a list of alternating key, value, key, value, etc. 
    * @throws ClassCastException if provided keys/values are not the proper class. 
    * @throws IllegalArgumentException if keysVals has odd length (more keys than values). 
    */ 
    public static<K,V> Map<K,V> build(Class<K> keyClass, Class<V> valClass, Object ... keysVals) 
    { 
    if(keysVals.length % 2 != 0) 
     throw new IllegalArgumentException("Number of keys is greater than number of values."); 

    Map<K,V> map = new HashMap<K,V>(); 
    for(int i = 0; i < keysVals.length; i += 2) 
     map.put(keyClass.cast(keysVals[i]), valClass.cast(keysVals[i+1])); 

    return map; 
    } 

Và sau đó bạn gọi nó là như thế này:

Map<String,Number> m = MapBuilder.build(String.class, Number.class, "L", 11, "W", 17, "H", 0.001); 

Trả lời

1

Như cletus nói, tẩy xoá có nghĩa là bạn không thể kiểm tra điều này khi chạy (và nhờ quá trình truyền, bạn không thể kiểm tra điều này tại thời gian biên dịch).

Hãy nhớ rằng generics là một tính năng chỉ có thời gian biên dịch.Bộ sưu tập đối tượng không có bất kỳ thông số chung nào, chỉ có tài liệu tham khảo bạn tạo cho đối tượng đó. Đây là lý do tại sao bạn nhận được rất nhiều cảnh báo về "unchecked cast" nếu bạn cần downcast một bộ sưu tập từ một kiểu thô hoặc thậm chí Object - bởi vì không có cách nào cho trình biên dịch xác minh rằng đối tượng là kiểu generic chính xác (như bản thân đối tượng không có loại chung).

Ngoài ra, hãy nhớ ý nghĩa của việc truyền - đó là cách nói trình biên dịch "Tôi biết rằng bạn không nhất thiết phải kiểm tra các loại khớp, nhưng tin tưởng tôi, tôi biết họ làm". Khi bạn ghi đè lên kiểm tra kiểu (không chính xác) và sau đó kết thúc với một loại không phù hợp, những người ya sẽ đổ lỗi? ;-)

Dường như vấn đề của bạn nằm xung quanh việc thiếu các cấu trúc dữ liệu chung không đồng nhất. Tôi sẽ đề nghị rằng chữ ký kiểu của phương pháp của bạn nên giống như private static<K,V> void addToMap(Map<K,V> map, List<Pair<K, V>> vals), nhưng tôi không tin rằng bạn sẽ làm được điều gì đó thực sự. Một danh sách các cặp về cơ bản là một bản đồ, do đó, việc tuân theo các thông số vals an toàn để gọi phương thức này sẽ hoạt động nhiều như chỉ cần điền trực tiếp bản đồ.

Nếu bạn thực sự, thực sự muốn giữ lại lớp học của bạn gần như nó nhưng được thêm thời gian chạy loại an toàn, có lẽ sau đây sẽ cung cấp cho bạn một số ý tưởng:

private static<K,V> void addToMap(Map<K,V> map, Object ... vals, Class<K> keyClass, Class<V> valueClass) { 
    for(int i = 0; i < vals.length; i += 2) { 
    if (!keyClass.isAssignableFrom(vals[i])) { 
     throw new ClassCastException("wrong key type: " + vals[i].getClass()); 
    } 
    if (!valueClass.isAssignableFrom(vals[i+1])) { 
     throw new ClassCastException("wrong value type: " + vals[i+1].getClass()); 
    } 

    map.put((K)vals[i], (V)vals[i+1]); //Never throws ClassCastException! 
    } 
} 
+0

Tôi chỉ hy vọng một cách an toàn để khởi tạo một bản đồ trong một lớp lót, giống như trong một số ngôn ngữ khác (ví dụ: Perl/PHP/Javascript), nơi bạn có thể làm một cái gì đó như 'map = (key1 => val1, key2 => val2, vv), ' – Kip

+0

Đúng - bạn không thể làm điều đó, vì mảng không có thông tin kiểu thời gian biên dịch và thông số chung không có thông tin kiểu thời gian chạy. Việc truyền các tham số 'Class ' là cách duy nhất để bạn có thể kết hợp thời gian biên dịch và kiểm tra thời gian chạy với nhau. –

7

Java Generics sử dụng loại tẩy xoá, có nghĩa là những loại tham số không được giữ lại khi chạy vì vậy đây là hoàn toàn hợp pháp:

List<String> list = new ArrayList<String>(); 
list.put("abcd"); 
List<Integer> list2 = (List<Integer>)list; 
list2.add(3); 

vì bytecode được biên dịch trông giống như sau:

List list = new ArrayList(); 
list.put("abcd"); 
List list2 = list; 
list2.add(3); // auto-boxed to new Integer(3) 

Generics Java chỉ đơn giản là cú pháp đường khi đúc Object s.

+0

Vì vậy, tôi đoán không có cách nào để chức năng của tôi hoạt động như dự định? Tôi đã thử sử dụng 'vals [i] instanceof K' và' K.class.getName() 'sau khi đăng bài này, nhưng điều đó cho phép lỗi cú pháp. (Tôi đoán vì không thực sự là 'K' khi chạy ...) – Kip

0

Generics Java chỉ áp dụng trong thời gian biên dịch và không chạy thời gian. Vấn đề là cách bạn đã thực hiện, trình biên dịch java không có cơ hội đảm bảo an toàn kiểu tại thời gian biên dịch.

Vì ur K, V không bao giờ nói chúng mở rộng bất kỳ lớp cụ thể nào, trong thời gian biên dịch java không có cách nào để biết nó được cho là số nguyên.

Nếu bạn thay đổi mã của bạn như sau

private static void addToMap (Bản đồ bản đồ, đối tượng ... Vals)

nó sẽ cung cấp cho bạn một lỗi thời gian biên dịch

1

Generics Java được thực hiện bằng "loại xóa", có nghĩa là khi chạy, mã không biết bạn có một Chuỗi Bản đồ <, Số nguyên > - nó chỉ thấy Bản đồ. Và vì bạn đang chuyển đổi công cụ thành Đối tượng (bằng cách sử dụng danh sách tham số của hàm addToMap), tại thời gian biên dịch, mã "trông đúng". Nó không cố gắng chạy các công cụ khi biên dịch nó.

Nếu bạn quan tâm đến các loại tại thời gian biên dịch, đừng gọi chúng là Đối tượng. :) Hãy chức năng addToMap bạn trông giống như

private static<K,V> void addToMap(Map<K, V> map, K key, V value) { 

Nếu bạn muốn chèn nhiều mục trong bản đồ, bạn sẽ muốn thực hiện một lớp kinda như Map.Entry java.util, và quấn chìa khóa/cặp giá trị của bạn trong trường hợp của lớp đó.

+0

Ví dụ về addToMap của bạn về cơ bản giống với phương thức Map.put(). Tôi đã hy vọng tôi có thể khởi tạo một Bản đồ một cách gọn gàng nhất có thể. Oh well. – Kip

+0

Bạn có thể, nhưng bạn sẽ từ bỏ an toàn loại. Không đáng, IMO. Và một trong những lý do tôi thích .net cho Java những ngày này. – cHao

1

Đây là một lời giải thích khá tốt những gì Generics làm và không làm trong Java: http://www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html

Nó rất khác với C#!

Tôi nghĩ bạn đang cố gắng làm một việc như thế này? Nơi có biên dịch an toàn thời gian cho các cặp mà bạn đang thêm vào bản đồ:

addToMap(new HashMap<String, Integer>(), new Entry<String,Integer>("FOO", 2), new Entry<String, Integer>("BAR", 8)); 

    public static<K,V> void addToMap(Map<K,V> map, Entry<K,V>... entries) { 
     for (Entry<K,V> entry: entries) { 
      map.put(entry.getKey(), entry.getValue()); 
     } 
    } 

    public static class Entry<K,V> { 

     private K key; 
     private V value; 

     public Entry(K key,V value) { 
      this.key = key; 
      this.value = value; 
     } 

     public K getKey() { 
      return key; 
     } 

     public V getValue() { 
      return value; 
     } 
    } 

Chỉnh sửa sau khi bình luận:

Ahh, thì có lẽ tất cả các bạn đang thực sự tìm kiếm là cú pháp phức tạp này được sử dụng để sách nhiễu những người mới thuê mới chuyển sang Java.

Map<String, Integer> map = new HashMap<String,Integer>() {{ 
    put("Foo", 1); 
    put("Bar", 2); 
}}; 
+0

tôi đã cố gắng khởi tạo bản đồ trong một lớp lót nhỏ gọn, an toàn. hy vọng tiếp cận sự đơn giản của 'map = (key1 => val1, key2 => val2, v.v.) của PHP;' – Kip

+0

Có những cách tối nghĩa để sắp xếp làm điều đó trong java, xem câu trả lời đã chỉnh sửa. Đừng nhận ra rằng làm điều đó nói chung là nhiều hơn về khía cạnh 'thông minh' của sự vật hơn là 'thực hành kỹ thuật phần mềm âm thanh' trong ý kiến ​​của nhiều người :) – Affe

+0

cảm ơn, bây giờ bạn đề cập đến nó, tôi nghĩ rằng tôi đã thấy cú pháp đó trước đây. – Kip

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