2014-12-22 3 views
14

다른 질문에 대한 정보 (테스트)를 보면서 나는 무엇인가를 발견하고 그것이 일어나는 이유를 전혀 몰랐습니다. 지금, 나는이 절대적으로 끔찍한 코드가 있음이이 작업을 수행하는 실질적인 이유가없고, 알고 있지만, 왜 작동하는지 그 것이다 :Generics는 정확히 어떻게 작동합니까?

그래서
ArrayList<Quod> test=new ArrayList<Quod>(); 
ArrayList obj=new ArrayList(); 
test=obj; 
obj.add(new Object()); 

System.out.println(test.get(0)); 

는, 기본적으로 내가의 ArrayList를에 객체를 추가하고 Quods. 지금, 나는 자바가 효율적으로 이것을 검사 할 방법이 없다는 것을 알 수있다. 아마 모든 참조를 살펴 봐야 할 것이고 아마 아무 곳에도 저장되지 않을 것이다. 그런데 왜 그것이 작동하는지() 작동합니다. get() 이클립스에서 마우스를 가져갈 때 말한 것처럼 Quod의 인스턴스를 반환한다고 가정하지 않습니까? Quod 유형의 객체를 반환하겠다고 약속했을 때 객체 만 반환 할 수 있다면 int를 반환 할 때 String을 반환 할 수없는 이유는 무엇입니까?

상황이 더 이상합니다. 이 런타임 오류 (java.lang.ClassCastException가 오류)와로 생각 될 때 이것은 충돌 (!?!?) :

ArrayList<Quod> test=new ArrayList<Quod>(); 
ArrayList obj=new ArrayList(); 
test=obj; 
obj.add(new Object()); 

System.out.println(test.get(0).toString()); 

은 왜 있으며, toString이 객체에 호출 할 수 없습니다

? println() 메서드가 toString을 호출하는 것이 왜 괜찮습니까?하지만 직접 호출 할 수는 없습니까?


편집 : 난 내가 만들 ArrayList에의 첫 번째 인스턴스로 아무것도 안하고 있어요 것을 알고, 그래서 그것은 본질적으로 처리 단지 시간 낭비입니다.


편집 : Java 1.6에서 Eclipse를 사용하고 있습니다. 다른 이들은 Java 1.8을 실행하는 Eclipse에서 동일한 결과를 얻는다 고 말했습니다. 그러나 다른 컴파일러에서는 두 경우 모두 CCE 오류가 발생합니다.

+2

, 당신은 폐기된다 'test'를 재 할당 할 때 그 참조. – azurefrog

+0

java.lang.ClassCastException 오류 – WiErD0

+0

그래, 내가 ArrayList에 추가하고 있지 않다는 것을 알고 . 하지만 출력물이 작동하지 않는 이유는 무엇입니까? – WiErD0

답변

12

자바 제네릭은 형식 지우기를 통해 구현됩니다. 즉, 형식 인수는 컴파일 및 연결에만 사용되지만 실행을 위해 지워집니다. 즉, 컴파일 시간 유형과 런타임 유형간에 1 : 1 대응은 없습니다. 특히, 제네릭 형식 주의 모든 인스턴스와 동일한 런타임 클래스 : 컴파일 시간 형 시스템에서

new ArrayList<Quod>().getClass() == new ArrayList<String>().getClass(); 

는 형식 인수가 존재하고, 유형 검사에 사용됩니다. 런타임 형식 시스템에서 형식 인수가 없으므로 확인되지 않습니다.

이것은 캐스트 및 원시 형식에 대해서는 문제가되지 않지만 형변환은 유형 정확성에 대한 주장이며 유형 검사를 컴파일 타임에서 런타임으로 연기합니다. 그러나 우리가 보았 듯이, 컴파일 타임과 런타임 타입 사이에는 1 : 1 대응이 없다. 컴파일 중 형식 인수가 지워집니다. 따라서 런타임은 유형 매개 변수를 포함하는 형변환의 정확성을 완전히 검사 할 수 없으며 부정확 한 형변환이 성공하여 컴파일 시간 유형 시스템을 위반하게됩니다. Java 언어 스펙에서는 힙 오염을 호출합니다.

결과적으로 런타임은 형식 인수의 정확성에 의존 할 수 없습니다. 그럼에도 불구하고 메모리 손상을 방지하기 위해 런타임 유형 시스템의 무결성을 강화해야합니다. 이는 일반적인 참조가 실제로 사용될 때까지 형식 검사를 지연함으로써 수행합니다.이 시점에서 런타임은 지원해야하는 메서드 나 필드를 알고 있으며 실제로 해당 필드 나 메서드를 선언하는 클래스 나 인터페이스의 인스턴스인지 확인할 수 있습니다 .(이 동작을 변경하지 않습니다) 다시 나는 약간 단순화하여 코드 예제에 그와

:

ArrayList<Quod> test = new ArrayList<Quod>(); 
ArrayList obj = test; 
obj.add(new Object()); 
System.out.println(test.get(0)); 

obj의 선언 된 유형은 원시 타입 ArrayList입니다. 원시 형식은 컴파일 타임에 형식 인수 검사를 비활성화합니다. 결과적으로 ArrayList이 컴파일 시간 유형 시스템에 Quod 인스턴스 만 보유 할 수 있더라도 Object을 add 메소드에 전달할 수 있습니다. 즉, 우리는 성공적으로 컴파일러에 거짓말을하고 힙 오염을 달성했습니다.

그러면 런타임 유형 시스템이 해제됩니다. 런타임 유형 시스템에서 ArrayList는 Object 유형의 참조로 작동하므로 Objectadd 메소드로 전달하는 것은 완벽합니다. 따라서 get()을 호출하고 Object을 반환합니다. 첫 번째 코드 예제에서는, 당신은 :

System.out.println(test.get(0)); 

test.get(0)의 컴파일 타임 유형 Quod, 유일한 일치에 println 방법은 println(Object)이다, 따라서이 내장되어 메소드의 서명입니다 그리고 여기에 사물이 분기 있었다 클래스 파일에. 따라서 런타임에 println(Object) 메소드에 Object을 전달합니다. 그것은 완벽하게 괜찮아서 아무런 예외도 던지지 않습니다.

System.out.println(test.get(0).toString()); 

다시 test.get(0)의 컴파일 시간 유형이 Quod이지만, 지금 우리는 toString() 메소드를 호출됩니다 두 번째 코드 예제에서

, 당신은있다. 따라서 컴파일러는 Quod 유형에 선언 된 (또는 상속 된) toString 메소드를 호출하도록 지정합니다. 당연히이 메서드는 Quod의 인스턴스를 가리키는 데 this이 필요합니다. 따라서 컴파일러는 메서드를 호출하기 전에 바이트 코드에 Quod에 추가 캐스트를 삽입합니다.이 캐스트는 ClassCastException을 throw합니다.

즉, 참조는 Quod과 같은 방식으로 사용되지 않기 때문에 첫 번째 코드 예제를 허용하지만 참조가 형식 Quod의 메서드에 액세스하는 데 사용되므로 두 번째 코드는 거부됩니다.

컴파일러가이 합성 캐스트를 정확하게 삽입 할 때 의존해서는 안되지만 올바른 코드를 작성하여 처음부터 힙 공해가 발생하지 않도록해야합니다. Java 컴파일러는 코드가 힙 (heap) 오염을 일으킬 수있을 때마다 확인되지 않은 원시 유형 경고를 표시하여이를 지원해야합니다. 경고를 제거하십시오, 당신은 그 세부 사항들을 이해할 필요가 없을 것입니다 ;-).

+0

아주 좋은 설명. System.out.println (((Object) test.get (0)). toString());'을 시도해 성공했다는 것을 알게되었습니다. – 5gon12eder

+2

나는 이것을 정말로 이해하지 못한다. 'toString()'은 Quod에만 국한되지 않습니다. 'toString()'은'Object' 메소드입니다. 런타임시,'toString()'메소드는'Quod' 인스턴스의 런타임 유형에 따라 동적으로 선택됩니다. 예를 들어,'Rod' 클래스가'Quod'를 확장하면, 컴파일러는'toString()'메소드가 호출 될 방법을 알지 못합니다 ('Quod' 또는'Rod'의 메소드) 호출 할 toString() 메소드를 지정할 수 없습니다. 그렇다면 먼저 'Quod'에 주조해야 할 점은 무엇입니까? –

+0

컴파일러가'Quod'에 캐스트를 삽입한다고 생각합니다.'test '의 컴파일 타임 타입이 완료되어야한다고 말했기 때문입니다 ('ArrayList '). 이 * 특정 * 경우에, 어쨌든'Object'를 받아들이는 메소드로 그것을 전달하기 때문에 캐스트를 제거 할 수 있기를 바랄지도 모르지만 그렇지 않습니다. 'ArrayList '에서'get' 할 때 항상 * Foo에 캐스트를 삽입합니다. –

2

질문의 요점은 다음과 같습니다

왜 직접에 그것의 toString을 호출 println 메소드() 메서드 괜찮지 만, 하지 나를 위해?

ClassCastException 예외로 인해 toString()를 호출하기 만 인해 컴파일러에 의해 추가 명시 적 캐스트에 발생되지 않습니다.

그림은 천개의 단어로 표시되므로 일부 디 컴파일 된 코드를 살펴 보겠습니다.다음 코드

을 고려해보십시오

public static void main(String[] args) { 
    List<String> s = new ArrayList<String>(); 
    s.add("kshitiz"); 
    List<Integer> i = new ArrayList(s); 

    System.out.println(i.get(0)); //This works 
    System.out.println(i.get(0).toString()); // This blows up!!! 
} 

이제 디 컴파일 코드를 보면 :

public static void main(String[] args) { 
    ArrayList s = new ArrayList(); 
    s.add("kshitiz"); 
    ArrayList i = new ArrayList(s); 
    System.out.println(i.get(0)); 
    System.out.println(((Integer)i.get(0)).toString()); 
} 

Integer에 명시 적 캐스트를 참조하십시오? 이제 컴파일러가 이전 줄에 캐스트를 추가하지 않은 이유는 무엇입니까? 방법 println()의 서명은 다음과 같습니다 println 이후

public void println(Object x) 

Objecti.get(0)의 결과가 어떤 캐스트가 추가되지 않습니다 Object입니다 기대하고있다.

당신이 toString()를 호출하는 것은 또한 괜찮습니다, 아니 캐스트가 발생하지 않도록이 같은 그것을 할 것을 부여 :

당신은`ArrayList를 `에 아무것도 추가하지 않는
public static void main(String[] args) { 
    List<String> s = new ArrayList<String>(); 
    s.add("kshitiz"); 
    List<Integer> i = new ArrayList(s); 

    myprint(i.get(0)); 
} 

public static void myprint(Object arg) { 
    System.out.println(arg.toString()); //Invoked toString but no exception 
} 
관련 문제