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


почему этот код выполняется без каких-либо исключений?

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));
}
4 60

4 ответа:

Дженерики-это время компиляции. Во время выполнения, обычный 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);

из - за стирания типа-нет проверок времени выполнения для дженериков, во время компиляции удаляются параметры типа: Java generics-стирание типа-когда и что происходит.

вы можете быть удивлены, но вы не должны использовать отражение, чтобы добавить Double до List<Integer>:

List<Integer> a = new ArrayList<Integer>();
((List)a).add(new Double(0.555));

причина этого стирания типа: дело в том, что это список Integers известен компилятору, а не JVM.

после компиляции кода,List<Integer> становится List<Object>, что позволяет код на основе отражения в комплекте без ошибок.

обратите внимание, что ваш код имеет сильный намек на причину, почему это работает:

a.getClass()
    .getMethod("add", Object.class) // <<== Here: Object.class, not Integer.class
    .invoke(a, new Double(0.55555));

Также обратите внимание, что вы можете достичь того же злого результата с помощью некоторого творческого использования кастинг, без рефлексии. Все это является следствием проектного решения по реализации дженериков Java с стиранием типов.

Generics-это только средство времени компиляции, которое предоставляет java. До дженериков не было никакого способа убедиться во время компиляции, что экземпляр "объекта", который вы получаете из коллекции, на самом деле имеет тип, который вы ожидаете. Мы должны были бы привести объект к правильному типу, чтобы сделать его пригодным для использования в коде, и это может быть рискованно, так как только во время выполнения JVM будет жаловаться с ClassCastException. Во время компиляции не было ничего, что могло бы защитить нас от этого.

дженерики решили эту проблема путем принудительной проверки типов в коллекциях во время компиляции. Но еще одна важная вещь, о дженериков заключается в том, что они не существуют во время выполнения. Если вы декомпилируете класс, содержащий коллекцию типов, такую как List или Map, и видите источник java, созданный из него, вы не найдете там своего общего объявления коллекции. Поскольку код reflections работает во время выполнения и не имеет времени компиляции, поэтому вы не получаете исключения. Попробуйте сделать то же самое во время компиляции с обычным put или add операция и вы получите ошибку времени компиляции.