Подтвердить что ты не робот

ArrayList <Integer> принимает строку

public class Main {
    public static void main(String[] args) {

        ArrayList<Integer> ar = new ArrayList<Integer>();
        List l = new ArrayList();
        l.add("a");
        l.add("b");
        ar.addAll(l);
        System.out.println(ar);
    }
}

Выход: [a,b]

Вы не можете напрямую добавить String в ArrayList<Integer> ar, но с помощью addAll() это возможно.

Как добавить String в ArrayList, тип которого уже был указан как Integer? Может ли кто-нибудь выделить четкие детали реализации и причину этого?

4b9b3361

Ответ 1

Но как мы можем добавить строки к arraylist, тип которых уже был указан как Integer?

Из-за того, что Java-дженерики были разработаны для обратной совместимости с стиранием типов и типами raw, в основном.

Во время выполнения нет таких вещей, как ArrayList<Integer> - там просто ArrayList. Вы используете необработанный тип List, поэтому компилятор не выполняет никаких обычных проверок во время компиляции или добавления времени выполнения.

Компилятор предупреждает вас, что вы делаете небезопасные вещи:

Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

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

ar.addAll(l);

Это меня несколько удивляет с точки зрения компиляции - я считаю, что он действительно доверяет тому, что List является Collection<? extends Integer> действительно, когда мы этого не знаем.

Если вы избегаете использования сырых типов, этот беспорядок исчезает.

Ответ 2

Это больше касается смешения исходных и общих типов в системе типа Java, чем с стиранием стилей. Позвольте мне расширить фрагмент кода из вопроса:

    ArrayList<Integer> ar = new ArrayList<Integer>();
    List l = new ArrayList();      // (1)
    l.add("a");
    l.add("b");
    ar.addAll(l);                  // (2)
    System.out.println(ar);
    Integer i = ar.get(0);         // (3)

С сегодняшним стиранием дженериков линия (3) бросает ClassCastException. Если Java-генераторы были подтверждены, легко предположить, что проверка типа времени выполнения вызовет исключение, которое будет выбрано в строке (2). Это будет один из возможных вариантов создания обобщенных дженериков, но другие проекты могут не выполнять эту проверку. Почему нет? В основном по той же причине мы сегодня уничтожили дженерики: совместимость с миграцией.

Neal Gafter наблюдал в своей статье Reified Generics для Java, что существует много небезопасных применений дженериков с неправильными отбрасываниями и т.д. вперед. Сегодня, даже более чем через десять лет после введения дженериков, я все еще вижу много использования сырых типов. (В том числе, к сожалению, здесь, в разделе "Переполнение стека" ). Безусловно, выполнение проверки подлинности общего типа проверяет разрыв кода огромный, что, конечно же, будет большим ударом по совместимости.

Любое реалистичное общее предложение по оверенению должно было бы предоставить подтверждение на основе выбора, например, путем подтипирования (как в предложении Гафтера) или аннотациями (Геракиос, Бибудис, Смарагдакис. Параметры Reified Type с использованием аннотаций Java. [PDF] GPSE 2013.), и ему нужно будет решить, как обращаться с необработанными типами. Кажется совершенно нецелесообразным полностью исключать сырые типы. В свою очередь, эффективное использование сырых типов означает, что существует способ обхода общей системы типов.

(Такое решение не выполняется легкомысленно. Я стал свидетелем кричащих матчей между теоретиками типа, один из которых жаловался на то, что система типа Java unsound. Для теоретика типа это самые печальные из оскорблений.)

По сути, это то, что делает этот код: он обходит целостность проверки типа, используя необработанные типы. Даже если Java-генераторы были подтверждены, проверка не может выполняться по строке (2). В соответствии с некоторыми проектами генерируемых дженериков код может вести себя точно так же, как сегодня: выброс исключения в строке (3).

В Jon Skeet answer, он признается, что несколько удивлен, что в строке (2) компилятор верит, что список l содержит элементы нужного типа. Это не правда о доверии. В конце концов, компилятор здесь выдает предупреждение. Это скорее компилятор, говорящий: "Хорошо, вы используете сырые типы, вы сами по себе. Если вы позже получите ClassCastException, это не моя ошибка". Опять же, речь идет о разрешении типов raw для совместимости, а не стирании.

Ответ 3

Используется необработанный тип. Если вы используете List<String> l = new ArrayList<>(), вы обнаружите, что ваш код больше не будет компилироваться. Необработанные типы существуют только для обратной совместимости и не должны использоваться в новом коде.

Ответ 4

Когда он родился, у Java не было дженериков (то есть классов, которые параметризуются другим классом). Когда были добавлены дженерики, чтобы поддерживать совместимость, было решено не изменять формат байт-кода Java и файл класса. Таким образом, общие классы преобразуются компилятором в не-генерические. Это означает, что ArrayList фактически хранит экземпляры класса Object, и поэтому он также может принимать экземпляры String (это подкласс Object). Компилятор не всегда может обнаружить нарушения.

Ответ 5

Вы оставили свой второй список. Оставив тип первого списка, вы также можете сделать это:

ArrayList ar = new ArrayList();
ar.add(Integer.valueOf(42));
ar.add("Hello");

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

Ответ 6

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