2010-05-04 3 views
8

나는 버그처럼 보이는 자바의 이상한 행동을 보았습니다. 그렇지? 객체가 K의 인스턴스가 아니더라도 객체를 일반 유형 (예 : K)으로 캐스팅하면 ClassCastException이 발생하지 않습니다. 다음은 그 예이다 :Java에서 일반 유형으로 변환하면 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!! 
    } 
} 

업데이트 : 감사합니다 당신의 도움이 답변의 Andrzej 도일 클리 터스하고. 그 중 하나만 받아 들일 수 있기 때문에 Andrzej Doyle's answer을 수락 할 것입니다. 왜냐하면 그것이 제가 생각하는 해결책으로 이끌었 기 때문입니다. 입니다. 한 줄짜리 작은지도를 초기화하는 좀 더 나은 방법이라고 생각합니다.

/** 
    * 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; 
    } 

그리고는 당신은 다음과 같이 호출 :

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

답변

1

cletus가 말하듯이 지우기는 런타임에이를 확인할 수 없다는 것을 의미합니다 (캐스팅 덕분에 컴파일시이를 확인할 수 없음).

제네릭은 컴파일 타임 전용 기능입니다.컬렉션 개체에는 일반 매개 변수가 없으며 참조 만 해당 개체에 대해을 만듭니다. 따라서 원시 형식 또는 Object에서 컬렉션을 다운 캐스팅해야하는 경우 "확인되지 않은 캐스트"에 대한 경고가 많이 발생합니다. 왜냐하면 컴파일러가 개체가 올바른 일반 형식인지 확인하기위한 방법이 없기 때문입니다. 객체 자체에는 제네릭 형식이 없습니다).

캐스팅의 의미는 컴파일러에게 "형식이 일치하는지 반드시 확인할 수는 없지만 나를 신뢰하면, 나는 알고 있습니다."라는 것을 의미합니다. 형식 검사를 무시하고 (잘못) 형식 불일치로 끝나면 누가 책임질 것입니까? ;-)

당신의 문제는 이질적인 일반적인 데이터 구조가 부족한 것 같습니다. 당신의 메소드의 타입 시그니처는 private static<K,V> void addToMap(Map<K,V> map, List<Pair<K, V>> vals)과 같아야한다고 제안 할 것입니다. 쌍의 목록은 기본적으로 맵이므로 메서드를 호출하기 위해 typesafe vals 매개 변수를 변경하는 것은지도를 직접 채우는 것만 큼 많은 작업이 될 것입니다.

당신이 정말로, 정말로 그것은하지만 실행시의 형태 안전에 추가되는 약으로 클래스를 유지하려면 경우, 아마도 다음과 같은 몇 가지 아이디어를 당신에게 줄 것이다 : 이것은 상당히 좋은 설명입니다

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

필자는'map = (key1 => key1 => val1, key2 =>)와 같은 일을 할 수있는 다른 언어 (예 : Perl/PHP/Javascript)와 같이 일직선으로 맵을 초기화 할 수있는 형식적인 방법을 원했습니다. val2, etc.); – Kip

+0

네, 그럴 수 없습니다. 배열에는 컴파일 타임 타입 정보가없고 일반 매개 변수에는 런타임 타입 정보가 없습니다. 'Class '매개 변수를 전달하는 것은 컴파일 타임과 런타임 검사를 함께 묶을 수있는 유일한 방법입니다. –

7

자바 제네릭이 완벽하게 합법적 있도록 런타임에 유지되지 않습니다 그 매개 변수 유형을 의미 유형 삭제를 사용

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

이므로 컴파일 된 바이트 코드는 다음과 같이 보입니다.

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

자바 제네릭은 단순히 주조시 문법적인 설탕입니다. Object s.

+0

내 기능을 의도 한대로 작동시키는 방법이 없다고 생각합니까? 나는 이것을 게시 한 후에'vals [i] instanceofK'와'K.class.getName()'을 사용하려했지만 구문 오류가 있습니다. (실제로는'K'가 런타임에 없기 때문에 ...) – Kip

0

Java generics는 컴파일 타임에만 적용되며 런타임은 지원되지 않습니다. 문제는 구현 한 방식이므로 Java 컴파일러는 컴파일 타임에 유형 안전성을 보장 할 기회를 얻지 못합니다.

ur K, V는 컴파일 타임 중에 특정 클래스를 확장한다고 말하지 않으므로 java는 정수가되어야한다는 것을 알 수있는 방법이 없습니다.

를 다음과 같이 당신이 당신의 코드를 변경하는 경우

개인 정적 무효 addToMap (지도지도 개체 ... 애송이들이다)

가 당신에게 컴파일 시간 오류를 줄 것이다

1

자바의 제네릭이 완료되어 사용

"type erasure"는 런타임에 코드가 문자열()을 가지고 있음을 알지 못한다는 것을 의미합니다. 그리고 당신이 물건을 Object로 변환하기 때문에 (addToMap 함수의 param리스트를 통해), 컴파일시에 코드는 "옳은 것처럼 보입니다". 컴파일 할 때 물건을 실행하려고하지 않습니다.

컴파일 할 때 유형을 신경 쓰면 Object라고 부르지 마십시오.

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

당신은지도에 여러 항목을 삽입 할 경우

, 당신은 좀 java.util의의의 Map.Entry 같은 클래스를 만들고 싶어 것처럼 addToMap 기능이 보이게하고 키/값 쌍을 래핑 :) 그 클래스의 인스턴스에서.

+0

addToMap 예제는 기본적으로 Map.put() 메소드와 같습니다. 가능한 한 간결한 방식으로 Map을 초기화 할 수 있기를 희망했습니다. 오 잘. – Kip

+0

할 수는 있지만 형식 안전은 포기합니다. 그만한 가치는 없습니다. 그리고 내가 자바를 선호하는 이유 중 하나는 요즘 있습니다. – cHao

1

무엇이 Generics가 Java로 할 수 있고하지 않는 것 : http://www.angelikalanger.com/GenericsFAQ/FAQSections/ParameterizedTypes.html

이것은 C#과 매우 다릅니다!

나는 이런 식으로하려고 한 것 같아요?

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; 
     } 
    } 

편집 코멘트 후 :

아, 그럼 아마 당신이 정말로 찾고있는 모두가 괴롭 히하는 데 사용이 비밀의 구문은 어디지도에 추가 할 쌍 시간 안전을 컴파일있다 Java로 전환 한 신규 채용.

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

저는 기본적으로 조밀하고 타입 안전 한 원 - 라이너로 맵을 초기화하려고했습니다. PHP의'map = (key1 => val1, key2 => val2 등);의 단순성에 접근하고 싶습니다. – Kip

+0

자바에서 그렇게하는 것은 모호한 방법이 있습니다 (편집 된 답변 참조). 그 일을하는 것이 일반적으로 많은 사람들의 의견으로는 '건전한 소프트웨어 엔지니어링 실무'보다 '영리한'면에 있다는 것을 깨닫게됩니다. – Affe

+0

덕분에, 지금 당신이 언급 했으니까요. 전에 그 구문을 본 것 같아요. – Kip

관련 문제