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

Я прочитал о типа стирания и реализовать проверку типов производится во время компиляции а затем E просто заменяется на Object, так что все, что у нас есть, это public Object[] elements, но почему метод get преуспевает, если нет регулярного литья по типу? Не удаляется ли тип возвращаемого метода метода get?

Спасибо.

+0

Я не получаю исключение во время выполнения, но мне нужно было добавить дополнительный код, чтобы компилировать вещи и создавать «элементы», и это, вероятно, не то же самое, что и ваш код. Я думаю, нам нужно увидеть более полный пример. – ajb

+1

Дженерики и массивы не работают вместе на Java, к сожалению – hoaz

+1

@hoaz. Некоторые комбинации дженериков и массивов являются незаконными в Java, и компилятор будет отмечать ошибку. Но если программа компилируется, я думаю, что программа должна работать так, как ожидалось, но вопросник говорит, что есть исключение во время выполнения. Однако я не смог его получить. – ajb

ответ

2

ПРИМЕЧАНИЕ: Этот ответ относится к коду на вашей ссылке pastebin. Я рекомендую вам отредактировать свой вопрос и включить весь код. Это не так долго.

Проблема в этом конкретном коде заключается в том, что вы указали параметр doSomethingarr типа Array<Integer>. Так что, когда вы говорите

Integer bad1 = arr.elements[0]; 

Поскольку arr является Array<Integer>, т.е. параметра типа E является Integer, компилятор предполагает тип elements, который был объявлен как E[], является Integer[].

Однако, когда elements создается с new, либо в конструкторе или в append, где вы создаете его как temp, вы создали его как 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[], A ClassCastException происходит во время выполнения:

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

([L означает массив.)

Причина этого не происходит, когда вы используете get(), так это то, что код, который обращается к массиву, не находится там, где компилятор «знает», что E должен быть Integer. Поэтому он не предполагает, что elements является Integer[], и проверка не выполняется.

(я не совсем уверен, о точных механизмах, связанных с дженериков, но я думаю, что это правильно.)

2

Во-первых, у вас есть 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 работ, как ожидалось. Но теперь мы получаем новое исключение:

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

    Это означает, что внутри мы все еще работаем с Object[] но пытается заставить его работать, как Integer[], или более формально, как E[] (который не так, как указано в 1 .).

  3. Давайте добавим append метода (адаптирован из кода разместил OP):

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

    Здесь мы получим сообщение об ошибке выполнения здесь:

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

    Из-за тех же причин, изложенные выше , Обратите внимание, что это будет иметь место для ваших трех примеров в вашем сообщении.

Короче:

Вы не должны использовать E[] array напрямую. Вместо этого используйте Object[] array, как описано в исходном коде ArrayList.

Подробнее:

3

Даже если arr имеет тип Array<Integer>arr.elements имеет тип Integer[]), на самом деле имеет arr.elementsвыполнения типаObject[], потому что фактический массив является экземпляром типа Object[].

(Обратите внимание, что массивы, в отличие от дженериков, являются ковариантны, и сделать не имеют стиранию. Object[] foo = new String[5]; является законным — как String[] bar = (String[]) foo;. В то время как Integer[] baz = (Integer[]) foo; поднимет ClassCastException во время выполнения.)

Так что причина что любой ссылка на arr.elements запускает исключение во время выполнения - это то, что компилятор автоматически вставляет downcast в 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, когда это необходимо, а не некорректных и-непроверенных слепков из Object[] в E[].

Вы видите, что я имею в виду?

2

Тип erasure удаляет общие типы из вашего универсального класса, но при необходимости будет вставлять типы приведения, когда вы используете общий тип. В вашем примере есть типы бросков, добавленные компилятором, когда вы используете общий класс Array. Однако внутри общих массивов Array происходит разложение E на Object. (см. комментарии и вывод javap). Ошибка, которую вы видите, - это просто компилятор, жалующийся на приведение из Object [] в Integer [], что является незаконным (generics или нет).

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. -с Массив

> 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, необходим листинг.

Этот бросок не обязательно всегда, если arr.elements немедленно передается или назначены в то, что ожидает типа Object[] или Object, то бросок не сделан, потому что это не нужно. Но в других случаях бросок сделан.

Можно утверждать, что это не было бы неправильно тип стирания кода:

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

И компилятор предположительно мог скомпилирован так, если был действительно умный, и он действительно хотел. Но для этого компилятору необходимо добавить дополнительное сложное правило о том, следует ли отливать вещи, выходящие из дженериков, чтобы выяснить, может ли он отложить его до получения доступа к массиву. Плюс это не будет иметь никакой пользы от точки зрения компилятора, потому что есть еще один актер в любом случае. Таким образом, компилятор не делает этого таким образом.

Смежные вопросы