2016-07-08 2 views
9

Я изучаю дженерики в этот период, и сегодня я нашел эту тайну для меня.Как стирание Java влияет на общие массивы?

Давайте рассмотрим следующий фиктивный класс:

public class Main{ 

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

     c.getArray();      //No Exception 
     //c.getArray().getClass();   //Exception 
     //int a = c.getArray().length;  //Exception 

    } 

} 


class Container<T> { 

    T[] array; 

    @SuppressWarnings("unchecked") 
    Container() { 
     array = (T[])new Object[1]; 
    } 

    void put(T item) { 
     array[0] = item; 
    } 

    T get() { return array[0]; } 

    T[] getArray() { return array; } 
} 

Из-за стирания во время выполнения, то T [] тип возвращаемого значения метода GetArray() превращается в Object [], что вполне разумно меня.

Если мы обращаемся к этому методу как к (c.getArray()), то исключаются исключения, но если мы попытаемся вызвать некоторые методы в возвращаемом массиве, например c.Array(). GetClass() или . если мы попытаемся получить доступ к полю, к примеру c.getArray() длины, то следующее исключение:

исключение в потоке «основного» java.lang.ClassCastException: [Ljava.lang. Объект; не может быть применено к [Ljava.lang.Integer;

Почему это исключение выбрасывается? Почему он не выбрасывается также для простого вызова c.getArray()? Почему он пытается использовать Integer [], если мы просто вызываем getClass() или получаем длину? Получают ли getClass() и длину не для Object []?

Заранее благодарен за ваши многочисленные (надеюсь) и объяснительные (я надеюсь, что это тоже) ответы.

+0

Чтобы разыменовать значение 'c.getArray()', ссылка на него должна быть временно сохранена в стеке. Я могу себе представить, что JLS говорит - где-то, все еще глядя - что эту временную переменную нужно проверить, чтобы убедиться, что она является неизученным типом (так как вы знаете, что это не поддерживается). –

+1

Действительно, он работает, если вы выполняете разыменование в методе, например 'static void foo (Container c) {c.getArray(). GetClass(); } ' –

+0

Есть интересная разница в байт-коде, если вы меняете' 'на' ': с' Integer', есть команда 'checkcast' (причина для ClassCastException'); с 'Object', никакая команда' checkcast' не добавляется. Понятно, поскольку все 'T []' могут быть переданы в 'Object []'; просто слегка удивляет, что это приводит к разному байт-коду. –

ответ

1

Если вы выполняете небезопасный непроверенный бросок, это может или не может вызвать какое-либо исключение. Вы не можете получить какое-либо исключение.

В этом случае, получилось ли исключение, зависит от того, вставил ли компилятор актерский состав в стираемом коде, чтобы передать результат вызова Integer[]. В этом случае кажется, что бросок был вставлен во второй и третий случай, но не в первый случай.

В каждом из трех случаев компилятору разрешено вставлять литье (поскольку разрешено считать, что результат Integer[] или не вставить литой (потому что выражение используется таким образом, что требуется только Object[] во всех трех). Вставить ли бросок или нет до конкретной реализации компилятора для принятия решения.

Почему этот компилятор не вставлял литье в первом случае и вставлял литье во втором и третьем случаях? очевидным объяснением было бы то, что в первом случае результат, очевидно, не используется, поэтому очень просто определить, что бросок не нужен. Во втором и третьем случаях для определения того, что приведение не требуется, потребуется посмотреть, как выражение есть u sed, чтобы увидеть, что он также будет работать с Object[]; и это довольно сложный анализ. Авторы компилятора, вероятно, предпочли простой подход, когда они пропускают трансляцию только тогда, когда результат не используется.

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

2

Причина исключения заключается в том, что компилятор ожидает Integer[], но получает Object[]. Он добавил время выполнения - на сайтах вызовов getArray. Эти заклинания обнаружили ложный, фиктивный, безрезультатный бросок в вашем конструкторе.

Для того, чтобы быть точным, нужен фактический класс T, чтобы создавать экземпляры.

@SuppressWarnings("unchecked") 
Container(Class<T> type) { 
    array = (T[]) Array.newInstance(type, 10); 
} 


    Container<Integer> c = new Container<Integer>(Integer.class); 

    c.getArray(); 
    Class<?> t = c.getArray().getClass(); 
    System.out.println(t.getName()); 
    int a = c.getArray().length; 

Также здесь остается «опасным» приведение к T[], но это неизбежно, как Array.newInstance является низкоуровневый метод для п-мерных массивов, как в:

(double[][][][][][]) Array.newInstance(double.class, 3, 3, 3, 3, 3, 6); 
+0

Но я не могу понять, почему он не жалуется на c.getArray(), но он делает это для c.getArray(). getClass(). Что сбивает меня с ума, так это то, что c.getArray() отлично работает, а c.getArray(). GetClass() - нет.Почему компилятор жалуется только на второй? В случае c.getArray() должен произойти нерабочий актер. – acejazz

+0

Вы не назначаете g.getArray(). –

1

Я не был в состоянии найти точное место в JLS, который говорит, что это поведение, но я думаю, что причина в том, что-то вроде этого:

выражение:

c.getArray().getClass(); 

примерно эквивалентно:

Integer[] arr = (Integer[]) c.getArray(); 
arr.getClass(); 

, где бросок должен быть добавлен из-за типа стирания. Этот неявный листинг добавляет команду checkcast в байт-код, который не работает с ClassCastException, так как c.getArray() имеет тип Object[].

Глядя на байткод для:

static void implicit() { 
    Container<Integer> c = new Container<Integer>(); 
    c.getArray().getClass(); //Exception 
} 

static void explicit() { 
    Container<Integer> c = new Container<Integer>(); 
    Integer[] arr = (Integer[]) c.getArray(); 
    arr.getClass(); //Exception 
} 

мы получаем:

static void implicit(); 
    Code: 
     0: new   #2     // class Container 
     3: dup 
     4: invokespecial #3     // Method Container."<init>":()V 
     7: astore_0 
     8: aload_0 
     9: invokevirtual #4     // Method Container.getArray:()[Ljava/lang/Object; 
     12: checkcast  #5     // class "[Ljava/lang/Integer;" 
     15: invokevirtual #6     // Method java/lang/Object.getClass:()Ljava/lang/Class; 
     18: pop 
     19: return 

    static void explicit(); 
    Code: 
     0: new   #2     // class Container 
     3: dup 
     4: invokespecial #3     // Method Container."<init>":()V 
     7: astore_0 
     8: aload_0 
     9: invokevirtual #4     // Method Container.getArray:()[Ljava/lang/Object; 
     12: checkcast  #5     // class "[Ljava/lang/Integer;" 
     15: checkcast  #5     // class "[Ljava/lang/Integer;" 
     18: astore_1 
     19: aload_1 
     20: invokevirtual #6     // Method java/lang/Object.getClass:()Ljava/lang/Class; 
     23: pop 
     24: return 

Таким образом, единственное различие в версии explicit являются три инструкции:

 15: checkcast  #5     // class "[Ljava/lang/Integer;" 
     18: astore_1 
     19: aload_1 

которые находятся там только из-за явного хранения этого в переменной, насколько я понимаю.

+0

Неправильный перевод происходит в 'Integer [] arr = (Integer []) c.getArray();', правильно? Таким образом, если 'c.getArray(). GetClass()' fail, 'cArray()' должен завершиться аналогичным образом, поскольку приведение выполняется перед вызовом 'getClass()'. – acejazz

+1

Нет, я так не думаю - это разыменование 'c.getArray()', которое, похоже, вызывает проблему. Например, 'System.out.println (c.getArray())' отлично работает; но это вызывает перегрузку 'System.out.println (Object)', которая не требует приведения. –

+0

Это интересно, спасибо. Если 'System.out.println (c.getArray())' отлично работает, потому что использует Object, не будет одинаковым для 'c.getArray(). GetClass()'? Я имею в виду, 'getClass()' - метод Object, поэтому он должен работать в любом случае. Где я ошибаюсь? – acejazz

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