Java 8. Обобщения. Использование необработанного типа. Неожиданное преобразование типов

Допустим, у меня есть класс с объявлением необработанного типа как List (list1). Это просто простой пример:

public class Wildcards {
    public boolean contains(List list1, List<?> list2){

      /*for(Object element: list1) {
            if (list2.contains(element)) {
                return true;
            }
        }*/

        list1.add("12sdf34"); //insert String 

        return false;
    }
}

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

Теперь давайте использовать этот метод следующим образом:

List<Integer> list1 = new ArrayList<Integer>();
List<Double> list2 = new ArrayList<Double>();

System.out.println("Contains? " + (new Wildcards()).contains(list1, list2));
System.out.println("List1 element: " + list1.get(0));

Я не получу никаких ошибок и получу следующий результат:

Содержит? ЛОЖЬ

Элемент List1: 12sdf34

Может ли кто-нибудь объяснить, как это могло быть, когда я инициализировал list1 как список целых чисел?


person Kirill Ch    schedule 25.04.2018    source источник


Ответы (2)


Ответ двоякий. Во-первых, универсальные шаблоны удаляются во время компиляции. Вторая часть — это фактическая реализация ArrayList. Начнем с стирания типа.

Во время компиляции все универсальные типы заменяются их верхней границей. Например, общий параметр <T extends Comparable<T>> сворачивается в Comparable<T>. Если верхняя граница не указана, она заменяется на Object. Это делает дженерики эффективным инструментом для проверки типов во время компиляции, но мы теряем всю информацию о типах во время выполнения. Project Valhalla может исправить это в будущем, а может и нет. Поскольку ваш метод имеет дело с необработанными и неограниченными типами, компилятор предполагает Object как универсальный тип и, таким образом, list1.add("12sdf34"); пропускает проверку типа.

Так почему бы вам не получить какое-нибудь исключение во время выполнения? Почему ArrayList не «распознает», что значение, которое вы ему даете, имеет неправильный тип? Поскольку ArrayList использует Object[] в качестве вспомогательного буфера. Следующий логический вопрос: почему ArrayList использует Object[] вместо T[]? Из-за стирания типов: мы не можем создавать экземпляры T или T[] во время выполнения. Кроме того, тот факт, что массивы ковариантны и сохраняются, в то время как дженерики инвариантны и стираются, приводит к взрывоопасному продукту, если их смешать.

Для вашей программы это означает, что ни ошибка компиляции, ни исключение времени выполнения не будут выброшены, и поэтому ваш ArrayList<Integer> может содержать String. Вы, однако, попадете в неприятности, написав

...
System.out.println("Contains? " + (new Wildcards()).contains(list1, list2));
System.out.println("List1 element: " + list1.get(0))
int i = list1.get(0);

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

person Turing85    schedule 25.04.2018
comment
Спасибо. Этот ответ немного более понятен для меня. - person Kirill Ch; 25.04.2018

Во время выполнения параметры универсального типа удаляются< /a>, что можно считать означающим, что типы параметров заменены на Object. Таким образом, если компиляция прошла успешно, вы можете добавить любой объект любого типа в любой список.

Все общие проверки типов выполняются во время компиляции, и компилятор не может вызвать ошибку для необработанных типов, в противном случае код, написанный для Java 1.4 (до универсальных) или более ранних версий, не будет компилироваться. Следовательно, вместо этого выдается предупреждение «rawtypes».

person user31601    schedule 25.04.2018
comment
Обратите внимание, однако, что возврат объектов из списка может завершиться ошибкой. Поскольку список объявлен List‹Integer›, при получении элементов из него компилятор обычно пытается получить целые числа. Другими словами, он обычно приводит к Integer объект, полученный в результате вызова get(). В примере, приведенном в вопросе, этого не происходит, потому что list1.get(0) используется где-то, где целое число не требуется, а объект работает нормально. Преобразование в Integer было бы ненужным накладным расходом. Однако Integer i = list1.get(0) вызовет исключение ClassCastException. Что, после того как я перечитал себя, очевидно. - person kumesana; 25.04.2018