Java: (Отсутствие) ошибок в универсальном приведении типов

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

import java.util.*;  // For ArrayList
public class Hat<T>
{
  public ArrayList<T> convert(String s)
  {
    T t = (T) s;    // Cast happens here

    ArrayList<T> list = new ArrayList<T>();
    list.add(t);
    return list;
  }
}

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

Hat<Integer> h = new Hat<Integer>();
ArrayList<Integer> iList = h.convert("hello");

Это создает ArrayList из целых чисел, который каким-то образом имеет String в качестве элемента! Это не вызывает никаких ошибок во время выполнения, даже если вы распечатываете ArrayList (он печатает «[привет]»).

Я ожидал, что метод "convert" выдаст ошибку. Почему этого не происходит и возможно ли это сделать? Интересно, что это происходит, когда я пытаюсь вернуть элемент из ArrayList как целое число, но ошибка возникает не из-за метода "convert".

Напомним, что универсальные типы - это стертый во время выполнения.

Oliver Charlesworth 08.05.2018 01:04
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
1
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В Java универсальные шаблоны используются только во время компиляции; они «стираются» после проверки типов программы и не влияют на выполнение программы. В частности, во время выполнения нет разницы между ArrayList<Integer> и ArrayList<String> (или ArrayList или чем-либо еще, если на то пошло). После завершения проверки типов ваша программа будет удалена, и программа, которая будет выполняться, будет эквивалентна:

public class Hat
{
  public ArrayList convert(String s)
  {
    Object t = s;
    ArrayList list = new ArrayList();
    list.add(t);
    return list;
  }
}

Hat h = new Hat();
ArrayList iList = h.convert("hello");

который ведет себя так, как вы наблюдали.

Возникает вопрос, почему эта программа выполняет проверку типов, когда она явно выдает неверное значение, которое претендует на роль ArrayList<Integer>, но содержит строки? Разве система типов не должна отвергать такие программы?

Ну, есть, за исключением того, что есть большая лазейка: непроверенные приведения. Когда вы выполняете приведение к типу, который включает в себя общий - в вашем случае строку T t = (T) s; - Java не имеет во время выполнения ничего, что можно было бы использовать для проверки правильности преобразования из-за стирания. Разработчики Java мог только что запретили такое приведение, и в этом случае ваша программа не сможет скомпилироваться.

Однако они этого не сделали. Вместо этого они решили разрешить приведение типов, включающих обобщенные типы, и были уверены, что программист, написавший приведение, был умнее компилятора и знал, что приведение сработает. Однако, если вы используете одно из этих приведений, все ставки отключены, и система типов может закончиться, как вы обнаружили, с ArrayList<Integer>, которые на самом деле содержат строки. Чтобы предупредить вас о том, что вам нужно быть осторожным, у них был компилятор но выдавать предупреждение о "непроверенном приведении" всякий раз, когда вы пишете такое приведение, напоминая вам о подозрительном приведении, и вам нужно доказать его правильность. В кодовых базах, над которыми я работал, непроверенные приведения должны быть аннотированы @SuppressWarning и комментарием, описывающим, почему приведение всегда допустимо.

Так что, если вы используете хочу для работы с неконтролируемыми приведениями и предпочитаете выполнить проверку во время выполнения? В этом случае вам придется самостоятельно программировать проверку времени выполнения. Часто это можно сделать с объектами Class. В вашем случае вы можете добавить дополнительный параметр Class в свой конструктор Hat, который представляет класс, которым, как вы ожидаете, должен быть T, и использовать его для создания типизированного преобразования, которое проверяется во время выполнения:

public class Hat<T>
{
  private final Class<? extends T> expectedClass;

  public Hat(Class<? extends T> expectedClass)
  {
    this.expectedClass = expectedClass;
  }

  public ArrayList<T> convert(String s)
  {
    T t = expectedClass.cast(s);  // This cast will fail at runtime if T isn't String

    ArrayList<T> list = new ArrayList<T>();
    list.add(t);
    return list;
  }
}

Тогда ваш позывной должен быть изменен на:

Hat<Integer> h = new Hat<Integer>(Integer.class);
ArrayList<Integer> iList = h.convert("hello");   // throws

Другие вопросы по теме