2014-10-15 3 views
2

이런 사람이 왜 나에게 설명 할 수 :제네릭 및 유형의 삭제

여기

전체 예를

public class Array<E> { 

    public E[] elements = (E[]) new Object[10]; 

    public E get(int idx) { 
     return elements[idx]; // Ignore bound-checking for brevity. 
    } 

    public static void doSomething(Array<Integer> arr) { 
     Integer good = arr.get(0); 
     Integer bad1 = arr.elements[0]; 
     Integer bad2 = ((Integer[]) arr.elements)[0]; 
     Integer bad3 = (Integer) arr.elements[0]; 
     // `bad1', `bad2', and `bad3' all produce a 
     // runtime exception. 
    } 

    public static void main(String[] args) { 
     Array<Integer> test = new Array<>(); 

     Array.doSomething(test); 
    } 
} 
: http://pastebin.com/et7sGLGW

I 유형의 삭제에 대한 읽기 및 유형 검사를 실현 한이 컴파일시 수행 그리고 E은 단순히 Object으로 바뀌기 때문에 우리는 모두 public Object[] elements이지만, 일반적인 캐스팅이없는 곳에서 get 메서드가 성공하는 이유는 무엇입니까? get의 메소드 리턴 유형도 지워지지 않습니까?

감사합니다.

+0

런타임 예외가 발생하지 않지만 컴파일하고 요소를 작성하기 위해 추가 코드를 추가해야했지만 코드와 다를 수 있습니다. 나는 좀 더 완전한 예를보아야 할 필요가 있다고 생각한다. – ajb

+1

Java에서 제네릭과 배열이 함께 작동하지 않습니다. – hoaz

+1

@hoaz 제네릭과 배열의 특정 조합은 Java에서 불법이며 컴파일러에서 오류를 표시합니다. 그러나 프로그램이 컴파일되면 프로그램이 예상대로 실행되어야한다고 생각하지만 질문자는 런타임 예외가 있음을 말합니다. 그러나, 나는 그것을 얻을 수 없었다. – ajb

답변

2

참고 :이 답변은 귀하의 붙여 넣기 링크에있는 코드를 나타냅니다. 질문을 수정하고 전체 코드를 포함하는 것이 좋습니다. 그렇게 오래 걸리지 않습니다.

이 코드의 문제점은 doSomething 매개 변수 arrArray<Integer>으로 선언했다는 것입니다. 당신이 arr 때문에

Integer bad1 = arr.elements[0]; 

을 말할 때 그래서 즉 EInteger있는 형식 매개 변수, Array<Integer>이며, 컴파일러는 E[]로 선언 된 elements의 유형을, 가정, Integer[]입니다. 그러나

, 당신이 temp로 만들 경우 elements가 생성자 또는 append에 하나, new으로 작성됩니다, 당신은 Object[]로 만든 :

elements = (E[]) new Object[(capacity = 2)]; 

또는

E[] temp = (E[]) new Object[(capacity *= 2)]; 
... 
elements = temp; 

이 수단을 런타임에 배열 객체가 만들어지면 런타임에 해당 유형이 Object[]으로 기록됩니다. E[]으로 캐스트하면 오브젝트의 런타임 유형이 변경되지 않으므로이 설정에 영향을주지 않습니다. 형 변환은 컴파일러가 표현식을 보는 방법에만 영향을 미칩니다.

따라서이 성명에서 : 여기

Integer bad1 = arr.elements[0]; 

는, 컴파일러는 위에서 설명한대로 elements가 입력 Integer[]의 것으로 추정된다 "알고있다". elements 실제 런타임 유형 Object[]이므로 및 Object[] 내재적, Integer[]로 전송할 수없는 ClassCastException 런타임시 발생

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; 

([L 수단 어레이.)

이 이유는 발생하지 않는다 get()을 사용하면 배열에 액세스하는 코드가 EInteger 인 컴파일러가 "알고있는"위치에 있지 않습니다. 따라서 elementsInteger[]이고 확인은 수행되지 않습니다.

2

먼저

(I 제네릭과 관련된 정확한 메커니즘에 대해 완전히 확실하지 않다,하지만이 옳다고 생각한다.), 당신은 Object[] 가지고 있고

를 사용하여이 E[]로 전달할 만들고있어
E[] elements = (E[]) new Object[10]; 

이것은 모든 문제의 원인입니다.

왜 이것이 문제입니까?

  1. 그런 배열을 만들조차 수 없습니다. 그것은 문제없이 컴파일되지만, 이론적으로 (그리고 런타임에) 이것은 무효합니다. 여기 Integer[]으로 예입니다 :

    Integer[] stringArray = (Integer[])new Object[10]; 
    

    이 줄은 실행시 오류가 발생합니다 :

    java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; 
    

    그러나 제네릭 가죽이 문제는 직접 데이터를 사용할 수있을 때까지.

  2. 는 이제이 코드 조각을 해보자 :

    public class Array<E> { 
        public E[] elements = (E[]) new Object[10]; 
    } 
    
    public class Client { 
        public static void main(String[] args) { 
         Array<Integer> array = new Array<>(); 
         array.elements[0] = 5; 
        } 
    } 
    

    전체 코드는 아무런 문제없이 컴파일하고 예상대로 array 작품의 초기화. 하지만 지금 우리는 새로운 예외를 얻을 : 내부적으로 우리는 여전히 1에 명시된 바와 같이 잘못 인 (E[]으로, 공식적으로 Object[] 작업이지만 Integer[]로 작동하도록 노력하고, 이상하고 있음을 의미

    array.elements[0] = 5; 
    //java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; 
    

    ). 여기

    public class Array<E> { 
        public E[] elements = (E[]) new Object[10]; 
        private int size = 0; 
        public void append(E element) { 
         elements[size++] = element; 
        } 
    } 
    
    public class Client { 
        public static void main(String[] args) { 
         Array<Integer> array = new Array<>(); 
         array.append(5); 
         System.out.println(array.elements[0]); 
        } 
    } 
    

    , 우리는 여기에 런타임 오류 얻을 것이다 :

  3. 의이 append 방법을 추가하자

    이 (OP에 의해 게시 된 코드에서 적응)

    System.out.println(array.elements[0]); 
    //java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; 
    

    를 인해 같은 이유로 위의 설명에 . 이 내용은 게시물의 세 가지 예에 해당합니다. 한마디로

:

직접 E[] array를 사용할 수 없습니다. 대신 ArrayList 소스 코드에 설명 된대로 Object[] array을 사용하십시오.

상세 정보 :

  • How to create a generic array in Java?. 허용 된 답변은 리플렉션을 사용하는 다른 방법을 제공합니다.실제 어레이 타입의 인스턴스 Object[] 때문에 arr.elements 실제로 런타임 타입Object[]을 가지고
3

arr 비록 Array<Integer> 입력을 갖는다 (그리고 arr.elementsInteger[] 입력 갖는다).

는 (배열, 제네릭과 달리, 공변, 그리고 하지가 삭제를 할 수 있습니다. String[] bar = (String[]) foo; 같이 Object[] foo = new String[5];이 — 법적이다. Integer[] baz = (Integer[]) foo; 실행시에 ClassCastException를 올릴 것입니다 반면.) 그래서

이유 그 어떤arr.elements에 대한 참조 런타임 예외를 컴파일러는 Integer[]에 다운 캐스트를 자동으로 삽입하여 형식과 런타임 형식을 다시 일치시킵니다. doSomething의 본체 안에는 arr.elements이 실제로는 암시 적 캐스트와 함께 (Integer[]) arr.elements을 의미합니다. get() 안으로 대조적으로

this 유형은 Array<E>이므로 elements의 타입을 체크 할 수없는 단지 E[]이다. 따라서 컴파일러는 암시 적 캐스트를 삽입하지 않습니다.


주요 테이크 홈 점은 (E[]) new Object[10]; 실제로 잘못된 것입니다. new Object[10]이 아니며E[]의 인스턴스를 만듭니다. 컴파일러에서 올바르지 않다는 것을 알 수는 없지만 이 될 캐스트가 많이 삽입됩니다. 올바르지 않습니다.

더 좋은 방법은 Object[] elements = new Object[]를 사용하고, 필요한 경우 Object[]에서 E[]잘못된 -와 - 체크되지 않은 캐스트보다는 EObject에서 선택하지 않은 올바른-하지만, 캐스트를 수행하는 것입니다.

내가 무슨 뜻인지 아십니까?

2

지우기 형식은 제네릭 형식을 제네릭 클래스에서 제거하지만 필요할 경우 제네릭 형식을 사용하는 형식 캐스트를 삽입합니다. 귀하의 예제에서는 일반 배열 클래스를 사용할 때 컴파일러에 의해 추가 된 형식 캐스트가 있습니다. 그러나 일반적인 Array occurences of E는 Object로 감쇠합니다. (javap의 주석과 출력 참조). 다음 오류는 컴파일러가 Object []에서 Integer [] 로의 형 변환에 대해 불평하는 것입니다. 이는 불법입니다 (제네릭이든 아니든).

public class Array<E> { 
    public E[] elements; 
    @SuppressWarnings("unchecked") 
    public Array() { 
     this.elements = (E[])new Object[]{1,2,3}; 
    } 
    public E get(int idx) { 
     return elements[idx]; // Ignore bound-checking for brevity.               
    } 

    public static void doSomething(Array<Integer> arr) { 
     Integer good = arr.get(0);        // produces (Integer) arr.get(0)        
     Integer good1 = (Integer) ((Object[])arr.elements)[0]; // no implicit cast           
     Integer bad1 = arr.elements[0];      // produces ((Integer[])arr.elements)[0]      
     Integer bad2 = ((Integer[]) arr.elements)[0];   // produces ((Integer[])((Integer[])arr.elements))[0]  
     Integer bad3 = (Integer) arr.elements[0];    // produces ((Integer[])arr.elements)[0]      
     // `bad1', `bad2', and `bad3' all produce a                   
     // runtime exception.                        
    } 

    public static void main(String[] args) throws Exception{ 
     doSomething(new Array<Integer>()); 
    } 
} 

javap -cp의 출력. -c 배열

> public static void 
> doSomething(Array<java.lang.Integer>); 
>  Code: 
>  0: aload_0  
>  1: iconst_0  
>  2: invokevirtual #6     // Method get:(I)Ljava/lang/Object; 
>  5: checkcast  #7     // class java/lang/Integer 
>  8: astore_1  
>  9: aload_0  
>  10: getfield  #5     // Field elements:[Ljava/lang/Object; 
>  13: checkcast  #4     // class "[Ljava/lang/Object;" 
>  16: iconst_0  
>  17: aaload   
>  18: checkcast  #7     // class java/lang/Integer 
>  21: astore_2  
>  22: aload_0  
>  23: getfield  #5     // Field elements:[Ljava/lang/Object; 
>  26: checkcast  #8     // class "[Ljava/lang/Integer;" 
>  29: iconst_0  
>  30: aaload   
>  31: astore_3  
>  32: aload_0  
>  33: getfield  #5     // Field elements:[Ljava/lang/Object; 
>  36: checkcast  #8     // class "[Ljava/lang/Integer;" 
>  39: checkcast  #8     // class "[Ljava/lang/Integer;" 
>  42: iconst_0  
>  43: aaload   
>  44: astore  4 
>  46: aload_0  
>  47: getfield  #5     // Field elements:[Ljava/lang/Object; 
>  50: checkcast  #8     // class "[Ljava/lang/Integer;" 
>  53: iconst_0  
>  54: aaload   
>  55: astore  5 
>  57: return 
1

이 모든 명시 적 캐스트 보여주고 있기 때문에, 당신의 코드를 입력 삭제 후 모양을 고려하는 것이 유익하다 :이

public class Array { 

    public Object[] elements = new Object[10]; 

    public Object get(int idx) { 
     return elements[idx]; // Ignore bound-checking for brevity. 
    } 

    public static void doSomething(Array arr) { 
     Integer good = (Integer)arr.get(0); 
     Integer bad1 = ((Integer[])arr.elements)[0]; 
     Integer bad2 = ((Integer[]) arr.elements)[0]; 
     Integer bad3 = (Integer) ((Integer[])arr.elements)[0]; 
     // `bad1', `bad2', and `bad3' all produce a 
     // runtime exception. 
    } 

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

     Array.doSomething(test); 
    } 
} 

캐스트 예외 때 발생하는 이유는 명백하다 그들이하다.

캐스팅이 발생할 때 캐스팅이 발생하는 이유는 무엇입니까?arr.elements은 (는) Integer[]으로 전송되는 이유는 무엇입니까? 유형 지우기 후에 arr.elements 유형은 Object[]입니다. 그러나이 메서드에서는 Integer[]이 될 것으로 예상하므로 일반 범위를 벗어나서 T으로 대체 된 특정 형식을 가진 메서드를 입력 할 때 캐스트가 필요합니다.

이 캐스트는 반드시 수행해야하는 것은 아닙니다. 이 유형 Object[] 또는 Object을 필요로하는 것으로 즉시 전달되거나 할당되는 경우 불필요하므로 캐스트가 작성되지 않습니다. 그러나 다른 경우에는 캐스트가 만들어집니다.

Integer bad1 = (Integer)arr.elements[0]; 

그리고 정말 똑똑하고 정말 원한다면 컴파일러는 아마 이런 식으로 컴파일 할 수 :

거의 틀림없이,이 코드의 잘못된 유형의 삭제되지 않을 것이다. 그러나이를 위해 컴파일러는 배열 액세스 이후까지 지연시킬 수 있는지 여부를 파악하기 위해 제네릭에서 오는 항목을 캐스팅할지 여부에 대한 추가 복잡한 규칙을 추가해야합니다. 게다가 컴파일러의 관점에서는 아무런 이점도 얻지 못할 것입니다. 왜냐하면 여전히 한 가지 방법으로 캐스트가 있기 때문입니다. 그래서 컴파일러는 이런 식으로하지 않습니다.

관련 문제