2013-03-13 3 views
17

이 스 니펫에 ClassCastException이 표시되지 않는 이유는 누군가가 나를 밝혀 주시겠습니까? 나는 내가 예상했던대로 왜 작동하지 않는지에 대해 엄격하게 관심이있다. 나는 이것이 나쁜 디자인인지 여부에 상관없이이 시점에서 신경 쓰지 않는다. Java에서 지연 클래스 캐스트?

public class Test { 
    static class Parent { 
    @Override 
    public String toString() { return "parent"; } 
    } 

    static class ChildA extends Parent { 
    @Override 
    public String toString() { return "child A"; } 
    } 

    static class ChildB extends Parent { 
    @Override 
    public String toString() { return "child B"; } 
    } 

    public <C extends Parent> C get() { 
    return (C) new ChildA(); 
    } 

    public static void main(String[] args) { 
    Test test = new Test(); 

    // should throw ClassCastException... 
    System.out.println(test.<ChildB>get()); 

    // throws ClassCastException... 
    System.out.println(test.<ChildB>get().toString()); 
    } 
} 

는 자바 버전, 컴파일하고, 출력을 실행합니다

$ java -version 
java version "1.7.0_17" 
Java(TM) SE Runtime Environment (build 1.7.0_17-b02) 
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode) 
$ javac -Xlint:unchecked Test.java 
Test.java:24: warning: [unchecked] unchecked cast 
    return (C) new ChildA(); 
      ^
    required: C 
    found: ChildA 
    where C is a type-variable: 
    C extends Parent declared in method <C>get() 
1 warning 
$ java Test 
child A 
Exception in thread "main" java.lang.ClassCastException: Test$ChildA cannot be cast to Test$ChildB 
    at Test.main(Test.java:30) 
+0

에 캐스팅이 필요하지 않습니다하지만이 이전 질문을 생각 나게하고있어, toString()는 슈퍼 클래스의 메소드 있음을 추론하는 문제가 없습니다/대답, 어쩌면 도움이 될 것입니다 http://stackoverflow.com/q/6538058/630384 – DHall

+1

거기에 검사되지 않은 캐스트 경고가있는 이유가 있습니다. 그것은 문자 그대로 캐스팅이 런타임 검사를 유발하지 않을 수 있다는 것을 의미합니다. – millimoose

답변

8

Type erasure : 제네릭은 컴파일러에서 (호환성 이유로 인해) 제거되어 캐스트 (필요한 경우)으로 바뀌는 구문 기능 일뿐입니다.

C get 메서드는 C 형식을 모릅니다 (즉, new C()을 인스턴스화 할 수 없습니다). test.<ChildB>get()의 호출은 실제로 test.get의 호출입니다. return (C) new ChildA()은 무제한 유형 C의 삭제가 Parent (가장 왼쪽 경계)이기 때문에 return (Object) new ChildA()으로 변환됩니다. 그런 다음 println은 인수로 Object을 필요로하기 때문에 캐스트가 필요하지 않습니다.

은 을 호출하기 전에 test.<ChildB>get()ChildB으로 캐스팅하기 때문에 실패합니다.

myPrint(test.<ChildB>get())과 같은 호출도 실패합니다. 에 의해 의 캐스트는 이 호출 될 때 ChildB으로 유형 변환됩니다.생성 된 바이트 코드에서

public static void myPrint(ChildB child) { 
    System.out.println(child); 
} 
+0

필자는 지우개를 알고있는 동안이 답변이 점들을 연결하고 캐스트가 실제로 컴파일 된 코드 (예 :'myPrint ')에 도입 될 때 알 수있게 도와주기 때문에이 대답을 올바르게 표시하고 있습니다. 감사! –

+0

'Object'에서 사용할 수있는'toString'을 호출하고 있지만 첫 번째 호출이 캐스트 체크를 도입하지 않는다는 점은 여전히 ​​흥미 롭습니다. 'ChildB' 특정 메소드가 없습니다. –

+0

컴파일 할 때 제네릭이 제거되면 왜 generic 타입을 사용하여 컴파일 할'.jar '를 줄 수 있습니까? 런타임 중에 사용되지 않더라도 약간의 추적이 있어야합니다. –

11

이 삭제를 입력 할 예정입니다. 컴파일 시간에

public <C extends Parent> C get() { 
    return (C) new ChildA(); 
} 

를 컴파일 할 때 단순히 ChildAParent의 하위 유형이며 따라서 캐스트가 확실히 실패하지 않을 것이라는 점을 확인합니다. ChildAC 유형에 할당되지 않을 수도 있으므로 불안정한 상황이라는 것을 알고 있습니다. 따라서 무언가가 잘못 될 수 있다는 것을 알려주는 체크되지 않은 경고를 발행합니다. (왜 코드를 그냥 거부하지 않고 컴파일 할 수 있습니까? 자바 프로그래머가 최소한의 재 작성으로 이전 제네릭 코드를 마이그레이션해야하는 필요성 때문에 동기를 부여하는 언어 디자인 선택) get()에 대한 자세한 내용은

실패하지 않습니다 : C 유형 매개 변수에 대한 런타임 구성 요소가 없습니다. 컴파일이 끝나면 형식 인수는 단순히 프로그램에서 지워지고 상한값 (Parent)으로 바뀝니다. 따라서 형식 인수가 ChildA과 호환되지 않는 경우에도 호출은 성공하지만 실제로 get()의 결과를 ChildB으로 사용하려고 시도하면 캐스트 (Parent에서 ChildB까지)가 발생하고 예외가 발생합니다.

이야기의 도덕 : 캐스트가 항상 성공한다는 것을 증명할 수 없다면 체크되지 않은 캐스트 예외를 오류로 처리하십시오.

6

봐는 :

12 invokevirtual Test.get() : Test$Parent [30] 
15 invokevirtual java.io.PrintStream.println(java.lang.Object) : void [32] 
18 getstatic java.lang.System.out : java.io.PrintStream [24] 
21 aload_1 [test] 
22 invokevirtual Test.get() : Test$Parent [30] 
25 checkcast Test$ChildB [38] 
28 invokevirtual Test$ChildB.toString() : java.lang.String [40] 
31 invokevirtual java.io.PrintStream.println(java.lang.String) : void [44] 

println에 대한 첫 번째 호출은 그래서 캐스트가 필요하지 않습니다 호출의 Object 버전을 사용합니다.

+0

고마워요! 컴파일러가 두 번째 경우에 필요한 캐스트를 고려한다는 것은 여전히 ​​흥미 롭습니다. –

4

컴파일 타임 형식 검사가 검사되지 않은 캐스트에 의해 회피되는 경우 JLS를 읽을 때 런타임 유형 검사가 언제 발생해야하는지 명확하지 않습니다. 나는 컴파일러가 그 타입이 건전하다고 가정하는 것이 가능하고 가능한 한 늦게 런타임 체크를 지연시킬 수 있다고 생각한다. 이는 각 컴파일러의 특성에 따라 다르므로 나쁜 소식입니다. 따라서 프로그램의 동작이 잘 정의되어 있지 않습니다.

분명히, 컴파일러는 우리가 그렇게하도록 컴파일러에 어떤 오류를 배치 할 수 없습니다 처음 println

Parent tmp = test.<ChildB>get(); // ok at runtime 
System.out.println(tmp); 

로, 그것은 완벽하게 합법적이다 변환합니다.

컴파일러는 그러므로 이러한 간단한 프로그램

ChildB tmp = test.<ChildB>get(); // fail at runtime 
System.out.println(tmp); 

코드로 변환 할 수 런타임 동작 JLS 의해 정의되지 않는다.


2 println의 동작은 정의되지 않습니다. 컴파일러는 따라서 내가 대답을하지 않는 서브 클래스

Parent tmp = test.<ChildB>get(); 
String str = tmp.toString(); 
System.out.println(str);