Java Generics: приведение необработанного типа к любому повторяемому типу не генерирует предупреждение о непроверенном приведении

#java #generics

#java #generics

Вопрос:

У меня следующий вопрос относительно приведенного ниже кода:

 public class GenericBridgeMethods <T> {

    public static void main(String[] args) {
        List obj = new ArrayList<Integer>();
        List <?> l1 = (List<?>) obj; // clause 1
        GenericBridgeMethods <?> g1 = (GenericBridgeMethods<?>) obj; // clause 2
   }

}
  

a. Пункт 1, конечно, не выдаст предупреждение о непроверенном приведении
b. Пункт 2 также не выдал предупреждение о непроверенном приведении

Я заметил, что литой из сырьевого типа (объект) в любой reifiable типа (как GenericBridgeMethods или GenericBridgeMethods <?>) не дают непроверенное приведение предупреждение. Если вы запустите этот код, в пункте 2 возникнет ошибка времени выполнения.

Разве компилятор не должен выдавать предупреждение в пункте 2

РЕДАКТИРОВАТЬ 1:

     ArrayList a1 = new ArrayList<Integer>(); // clause 3
    Number n1 = (Number)a1; // clause 4 ERROR
    Comparable c1 = (Comparable)a1; // clause 5

    List l1 = new ArrayList<Integer>(); // clause 6
    Number n2 = (Number)l1; // clause 7
    Comparable c2 = (Comparable)l1; // clause 8
  

Кто-нибудь может объяснить, почему только в пункте 4 есть ошибка?

Ответ №1:

Ну, во-первых, GenericBridgeMethods как вы его определили, T это не повторяемый тип. Повторяемый означает, что тип будет закодирован в классе и будет доступен во время выполнения. Это не относится T к .

Пункт 2 не выдает предупреждение во время выполнения, потому что оно проверено: будет проверка во время выполнения, которая obj присваивается GenericBridgeMethods типу. Поскольку вы выбрали подстановочный знак в качестве параметра типа, проверять ничего не T нужно.

Если, с другой стороны, вы сделали что-то подобное:

 GenericBridgeMethods<String> g1 = (GenericBridgeMethods<String>) obj;
  

это выдаст вам предупреждение о непроверенном присвоении, потому что факт, который obj является a GenericBridgeMethods из String s, не может быть проверен во время выполнения. Однако то же самое предупреждение появилось бы, если бы вы сделали это:

 List<String l1 = (List<String>) obj;
  

Редактировать

Если вы не понимаете, почему компилятор позволяет вам пытаться преобразовать a List в a GenericBridgeMethods , ответ заключается в том, что компилятор не может знать всю иерархию GenericBridgeMethods и ее подклассы. Может существовать подкласс GenericBridgeMethods этого implements List , и в этом случае приведение может быть законным.

Однако вы получите ошибку компиляции, если вы создали GenericBridgeMethods окончательный класс (и, таким образом, предотвратили его наличие подклассов). В этом случае вы получите ошибку необратимых типов.

Просто чтобы показать вам, что ваш вопрос имеет мало общего с повторяемыми типами и обобщениями, взгляните на это:

 public static void main(String[] args) {
   List obj = new ArrayList<Integer>();

   //this is allowed (no warning), even though it will fail at runtime
   CharSequence sequence = (CharSequence) obj; 
}
  

Вы можете явно привести obj к a CharSequence , даже если вы знаете, что это приведет к сбою во время выполнения. Причина в том, что все, что знает компилятор, это то, что obj это тип List . Поскольку List это интерфейс, может быть реализация CharSequence , которая также является a List , и поэтому приведение должно быть разрешено.

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

Редактировать — относительно вашего «редактировать # 1»

 ArrayList a1 = new ArrayList<Integer>(); // clause 3
Number n1 = (Number)a1; // clause 4 ERROR
Comparable c1 = (Comparable)a1; // clause 5

List l1 = new ArrayList<Integer>(); // clause 6
Number n2 = (Number)l1; // clause 7
Comparable c2 = (Comparable)l1; // clause 8
  

Вам интересно, почему не компилируется только «пункт 4». Я думаю, что я уже объяснял это выше и в комментариях, но я пройдусь по этому конкретному примеру для вас шаг за шагом.

 ArrayList a1 = new ArrayList<Integer>(); // clause 3
Number n1 = (Number)a1; // clause 4 ERROR
  

Приведение a1 к Number не работает, потому Number что и ArrayList оба являются классами, а не интерфейсами. Поскольку Java не допускает наследования от нескольких классов, для того, чтобы объект был экземпляром обоих Number и ArrayList , Number должен быть подклассом ArrayList или наоборот. Известно, что это неверно во время компиляции.

 ArrayList a1 = new ArrayList<Integer>(); // clause 3
Comparable c1 = (Comparable)a1; // clause 5
  

Поскольку Comparable это интерфейс, подклассом ArrayList может быть a Comparable .

 List l1 = new ArrayList<Integer>(); // clause 6
Number n2 = (Number)l1; // clause 7
  

Since List — это интерфейс Number , который может реализовать подкласс List . Компилятор не знает при проверке приведения, которое l1 содержит ArrayList .

Комментарии:

1. Приведение в пунктах 1 и 2 — это приведение к повторяемому типу. Это то, что я имел в виду. Пункт 2 ДЕЙСТВИТЕЛЬНО выдает ошибку времени выполнения! Это ошибка- java.lang. ClassCastException: java.util. ArrayList не может быть приведен к GenericBridgeMethod .

2. @yapkm01: В библиотеках Java нет такого понятия, как предупреждение во время выполнения. Это, конечно, сбой , потому что ArrayList не является GenericBridgeMethod . Но компилятор не может этого знать, поскольку может существовать подкласс GenericBridgeMethod этого implements List .

3. Извините, я изменил его на ошибку. ДА. Это выдает ошибку времени выполнения. Если это так, компилятор должен был выдать предупреждение о непроверенном приведении

4. @yap: непроверенные приведения имеют отношение к дженерикам. Ваш вопрос действительно не имеет ничего общего с дженериками; они затуманивают проблему. Речь идет вовсе не о повторяемости. Непроверенное предупреждение означает, что приведение фактически не будет проверять тип во время выполнения; вы не получите этого здесь, потому что, очевидно, тип проверяется во время выполнения (иначе вы бы не получили ClassCastException !!)

5. Я сказал это в своем ответе, но я повторю: непроверенный не означает «непроверенный во время компиляции», это означает «непроверенный во время выполнения «.