Почему двойной может быть добавлен в список целых чисел с использованием отражения

Почему этот код работает без каких-либо исключений?

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, и вы получите ошибку времени компиляции.