Почему двойной может быть добавлен в список целых чисел с использованием отражения
Почему этот код работает без каких-либо исключений?
public static void main(String args[]) {
List<Integer> a = new ArrayList<Integer>();
try {
a.getClass()
.getMethod("add", Object.class)
.invoke(a, new Double(0.55555));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(a.get(0));
}
Ответы
Ответ 1
Дженерики - это вещь времени компиляции. Во время выполнения используется обычный ArrayList
, без какой-либо дополнительной проверки. Поскольку вы избегаете проверок безопасности, используя отражение для добавления элементов в свой список, ничто не может помешать Double
быть сохраненным внутри вашего List<Integer>
. Так же, как если бы вы делали
List<Integer> list = new ArrayList<Integer>();
List rawList = list;
rawList.add(new Double(2.5));
Если вы хотите, чтобы ваш список выполнял проверки типов во время выполнения, используйте
List<Integer> checkedList = Collections.checkedList(list, Integer.class);
Ответ 2
Из-за стирания типа - не существует проверок времени выполнения для генериков, во время компиляции параметры типа удаляются: Java generics - стирать стили - когда и что происходит.
Вы можете быть удивлены, но вам не нужно использовать отражение, чтобы добавить Double
в List<Integer>
:
List<Integer> a = new ArrayList<Integer>();
((List)a).add(new Double(0.555));
Ответ 3
Причиной этого является тип erasure: тот факт, что это список Integer
, известен компилятору, а не JVM.
После компиляции кода List<Integer>
становится List<Object>
, позволяя код на основе отражения без ошибок.
Обратите внимание, что ваш собственный код имеет сильный намек на то, почему это работает:
a.getClass()
.getMethod("add", Object.class) // <<== Here: Object.class, not Integer.class
.invoke(a, new Double(0.55555));
Также обратите внимание, что вы можете достичь того же злого результата через какое-то творческое использование кастинга, без отражения. Все это является следствием дизайнерского решения для реализации Java-дженериков с стиранием типа.
Ответ 4
Generics - это только время компиляции, предоставляемое java. До генериков не было возможности убедиться, что во время компиляции экземпляр "Объект", который вы получаете из коллекции, фактически относится к типу, который вы ожидаете. Мы должны были бы применить объект к соответствующему типу, чтобы сделать его пригодным для использования в коде, и это может быть рискованным, поскольку только во время выполнения JVM жалуется на ClassCastException
. Во время компиляции ничего не было, чтобы защитить нас от этого.
Generics решила эту проблему, проверив проверки типов в коллекциях во время компиляции. Но еще одна важная вещь о дженериках заключается в том, что они не существуют во время выполнения. Если вы декомпилируете класс, содержащий коллекцию типов, например "Список" или "Карта", и посмотрите источник, полученный из него, вы не найдете там свою коллективную декларацию коллекции. Поскольку код отражений работает во время выполнения и не имеет времени на компиляцию, вы не получаете там исключения. Попробуйте сделать то же самое во время компиляции с обычной операцией put или add, и вы получите ошибку времени компиляции.