2014-09-12 20 views
13

Tôi có một số mã mà trông giống như thế này (một phần của một bài kiểm tra tiêu cực của phương pháp get):Không ClassCastException khi đúc để loại generic khác nhau để lớp thực tế

import java.util.*; 
public class Test { 
    Map<String, Object> map = new HashMap<>(); 
    public static void main (String ... args) { 
     Test test = new Test(); 
     test.put("test", "value"); // Store a String 
     System.out.println("Test: " + test.get("test", Double.class)); // Retrieve it as a Double 
    } 

    public <T> T get(String key, Class<T> clazz) { 
     return (T) map.get(key); 
    } 

    public void put(String key, Object value) { 
     map.put(key, value); 
    } 
} 

tôi đã mong đợi nó để ném một ClassCastException nhưng nó chạy qua quá trình in thành công:

Test: value 

Tại sao nó không bị ném?

+0

Lệnh gọi 'System.out.println' là gì? – mariosangiorgio

+1

@mariosangiorgio: Nó in 'Test: value'. – Dici

+0

Tôi có cảm giác nó liên quan đến cùng một lý do tại sao bạn không thể vượt qua một kiểu nguyên thủy cho một đối số kiểu chung. Bạn đã thử thay đổi các giá trị trong 'Bản đồ' từ' Đối tượng' thành một cái gì đó khác, chẳng hạn như 'Chuỗi'? Có lẽ nó được cho phép do đặc tính đa hình của 'Object' –

Trả lời

4

Đó là bài học để xem xét những gì các lớp trông giống như sau khi gỡ bỏ các tham số kiểu (loại chô bôi):

public class Test { 
    Map map = new HashMap(); 
    public static void main (String ... args) { 
     Test test = new Test(); 
     test.put("test", "value"); 
     System.out.println("Test: " + test.get("test", Double.class)); 
    } 

    public Object get(String key, Class clazz) { 
     return map.get(key); 
    } 

    public void put(String key, Object value) { 
     map.put(key, value); 
    } 
} 

này biên dịch và tạo ra kết quả tương tự mà bạn nhìn thấy.

Phần khôn lanh là dòng này:

System.out.println("Test: " + test.get("test", Double.class)); 

Nếu bạn đã làm điều này:

Double foo = test.get("test", Double.class); 

thì sau khi loại chô bôi trình biên dịch sẽ chèn một dàn diễn viên (vì sau khi loại chô bôi test.get() lợi nhuận Object):

Double foo = (Double)test.get("test", Double.class); 

Tương tự, compi ler có thể chèn một dàn diễn viên trong dòng trên cũng vậy, như thế này:

System.out.println("Test: " + (Double)test.get("test", Double.class)); 

Tuy nhiên, nó không chèn một dàn diễn viên, bởi vì các diễn viên là không cần thiết cho nó để biên dịch và cư xử một cách chính xác, vì nối chuỗi (+) hoạt động trên tất cả các đối tượng theo cùng một cách; nó chỉ cần biết loại là Object, không phải là một lớp con cụ thể.Vì vậy, trình biên dịch có thể bỏ qua một diễn viên không cần thiết và nó trong trường hợp này.

17

Đó là vì bạn đang truyền sang loại chung T, được xóa khi chạy, giống như tất cả các Generics Java. Vì vậy, những gì thực sự xảy ra trong thời gian chạy, là bạn đang truyền tới Object và không phải là Double.

Lưu ý rằng, ví dụ: nếu T được định nghĩa là <T extends Number>, bạn sẽ được truyền tới Number (nhưng vẫn chưa đến Double).

Nếu bạn muốn thực hiện kiểm tra kiểu thời gian chạy, bạn cần sử dụng tham số thực tế clazz (có sẵn khi chạy) và không phải loại chung T (đã bị xóa). Ví dụ, bạn có thể làm một cái gì đó như:

public <T> T get(String key, Class<T> clazz) { 
    return clazz.cast(map.get(key)); 
} 
+1

Đây có thể chỉ là một dòng 'return clazz.cast (map.get (key)); ' –

+0

Bạn nói đúng - tôi đoán tôi nên kiểm tra Javadoc, vì nó không phải là thứ Tôi thường phải giải quyết. Tôi sẽ cập nhật câu trả lời của tôi. –

+0

@LouisWasserman, Cyäegha: lưu ý rằng cả hai đều khác nhau một chút về cách chúng xử lý 'null'. 'class.cast (null)' làm việc không có vấn đề, trong khi 'null.getClass()' ném một NullPointerException. –

2

Dường như Java là không thể xử lý các diễn viên với một kiểu suy ra, tuy nhiên nếu bạn sử dụng phương pháp Class.cast, cuộc gọi để có được ném một ngoại lệ như mong đợi:

public <T> T get(String key, Class<T> clazz) { 
    return clazz.cast(map.get(key)) ; 
} 

Thật không may, tôi không thể giải thích kỹ hơn.

Chỉnh sửa: bạn có thể quan tâm đến điều này Oracle doc.

5

Bạn không nhận được ClassCastException vì ngữ cảnh mà bạn trả về giá trị từ bản đồ không yêu cầu trình biên dịch thực hiện kiểm tra tại thời điểm này vì nó tương đương với gán giá trị cho biến số Object (Xem rgettman's answer).

Cuộc gọi đến:

test.get("test", Double.class); 

là một phần của một hoạt động nối chuỗi sử dụng toán tử +. Đối tượng được trả lại từ bản đồ của bạn chỉ được coi như là Object. Để hiển thị 'đối tượng' được trả về dưới dạng một số String, yêu cầu phương thức toString() và vì đây là phương thức trên Object không yêu cầu truyền.

Nếu bạn thực hiện cuộc gọi đến test.get("test", Double.class); bên ngoài ngữ cảnh của chuỗi nối, bạn sẽ thấy rằng nó không hoạt động tức là

này không biên dịch:

// can't assign a Double to a variable of type String... 
String val = test.get("test", Double.class); 

Nhưng điều này không:

String val = test.get("test", Double.class).toString(); 

Nói một cách khác, mã của bạn:

System.out.println("Test: " + test.get("test", Double.class)); 

tương đương với:

Object obj = test.get("test", Double.class); 
System.out.println("Test: " + obj); 

hay:

Object obj = test.get("test", Double.class); 
String value = obj.toString();  
System.out.println("Test: " + value); 
7

Tôi tìm thấy một sự khác biệt trong các mã byte khi một phương pháp được gọi là trên trả lại "đúp" và khi không có phương pháp được gọi.

Ví dụ: nếu bạn gọi doubleValue() (hoặc thậm chí getClass()) trên "Double" trả về, thì ClassCastException xảy ra. Sử dụng javap -c Test, tôi nhận được bytecode sau:

34: ldc   #15     // class java/lang/Double 
36: invokevirtual #16 // Method get (Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; 
39: checkcast  #15     // class java/lang/Double 
42: invokevirtual #17     // Method java/lang/Double.doubleValue:()D 
45: invokevirtual #18 // Method java/lang/StringBuilder.append:(D)Ljava/lang/StringBuilder; 

Các hoạt động checkcast phải ném ClassCastException. Ngoài ra, trong trường hợp ngụ ý StringBuilder, append(double) sẽ được gọi.

Nếu không có một cuộc gọi đến doubleValue() (hoặc getClass()):

34: ldc   #15     // class java/lang/Double 
36: invokevirtual #16 // Method get:(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object; 
39: invokevirtual #17 // Method java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder; 

Không có hoạt động checkcast, và append(Object) được kêu gọi ngầm StringBuilder, bởi vì sau khi loại tẩy xoá, T chỉ là Object anyway.

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