Неожиданное добавление строки в список <Integers> - PullRequest
0 голосов
/ 06 октября 2018

Я не понимаю, как компилятор обрабатывает следующий код при выводе Тест , пока я ожидал ошибки.

List<Integer> b = new ArrayList<Integer>();
List a = b;
a.add("test");
System.out.println(b.get(0));

Я надеялся, что кто-то может сказать мне точные шагов, которые проходит компилятор при выполнении кода, чтобы я мог понять вывод.В настоящее время я понимаю, что:

  1. Во время компиляции компилятор проверяет, существует ли метод add, поддерживающий тип аргумента, в List классе, который является add (Object e) как тип.
  2. Однако во время выполнения он пытается вызвать add (Object e) из фактического объекта List , который не содержит этот метод, поскольку фактический объект не является необработанным ивместо этого содержит метод add (Integer e) .

Если в фактическом объекте нет метода add (Object e) List Как все же каким-то образом добавить строку в список целых чисел?

Ответы [ 2 ]

0 голосов
/ 06 октября 2018

Сюрпризом здесь является то, что b.get(0) не имеет проверки во время выполнения.Мы ожидаем, что код будет интерпретирован компилятором так:

System.out.println((Integer)b.get(0)); // throws CCE

Действительно, если бы мы попытались:

Integer str = b.get(0); // throws CCE

, мы получили бы время выполнения ClassCastException.

Действительно, мы даже получили бы ту же ошибку, переключая printf вместо println:

System.out.printf(b.get(0)); // throws CCE

Как это имеет смысл?

Это ошибка, которую нельзя исправить из-за обратной совместимости.Если целевой контекст может разрешить удаление контрольного приведения, то он исключается, несмотря на изменение семантики.И в этом случае перегрузка меняется с println(Integer) на println(Object).Хуже того, существует перегрузка println(char[]) с другим поведением!

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

0 голосов
/ 06 октября 2018

Вы совсем близко.Время компиляции проверяет все панорамирование:

a имеет тип List, поэтому вызов

a.add("test");

завершается.b относится к типу (время компиляции) ArrayList<Integer>, поэтому

b.get(0)

также проверяется.Обратите внимание, что проверки выполняются только для типов переменных во время компиляции.Когда компилятор видит a.add("test"), он не знает значение времени выполнения объекта, на который ссылается переменная a.В общем, это действительно невозможно (в теоретической информатике есть результат), хотя анализ типа потока управления может уловить многих таких вещей.Такие языки, как TypeScript, могут делать удивительные вещи во время компиляции.

Теперь вы можете предположить, что во время выполнения такие вещи могут быть проверены.Увы, на Яве они не могут.Java стирает универсальные типы.Найдите статью о стирании типов Java для более подробной информации.TL; DR - то, что List<Integer> во время компиляции становится необработанным List во время выполнения.У JVM не было способа «переопределить» дженерики (хотя это делают другие языки!), Поэтому, когда дженерики были представлены, было принято решение, что Java просто удалит дженерики.Поэтому во время выполнения в вашем коде нет проблем с типами.

Давайте посмотрим на скомпилированный код:

   0: new           #2                  // class java/util/ArrayList
   3: dup
   4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
   7: astore_1
   8: aload_1
   9: astore_2
  10: aload_2
  11: ldc           #4                  // String test
  13: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
  18: pop
  19: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
  22: aload_1
  23: iconst_0
  24: invokeinterface #7,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
  29: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  32: return

Здесь вы можете непосредственно увидеть, что нет никаких проверок типов во время выполнения.Итак, полный (но, казалось бы, легкомысленный) ответ на ваш вопрос заключается в том, что Java проверяет только типы во время компиляции на основе типов переменных (известных во время компиляции), но параметры универсальных типов стираются икод запускается без них.

...