Странное поведение во время компиляции при попытке использовать примитивный тип в дженериках.
-
20-09-2019 - |
Вопрос
import java.lang.reflect.Array;
public class PrimitiveArrayGeneric {
static <T> T[] genericArrayNewInstance(Class<T> componentType) {
return (T[]) Array.newInstance(componentType, 0);
}
public static void main(String args[]) {
int[] intArray;
Integer[] integerArray;
intArray = (int[]) Array.newInstance(int.class, 0);
// Okay!
integerArray = genericArrayNewInstance(Integer.class);
// Okay!
intArray = genericArrayNewInstance(int.class);
// Compile time error:
// cannot convert from Integer[] to int[]
integerArray = genericArrayNewInstance(int.class);
// Run time error:
// ClassCastException: [I cannot be cast to [Ljava.lang.Object;
}
}
Я пытаюсь полностью понять, как работают дженерики в Java.В третьем задании в приведенном выше фрагменте все становится немного странно:компилятор жалуется, что Integer[]
невозможно преобразовать в int[]
.Утверждение, конечно, на 100% верно, но мне интересно ПОЧЕМУ компилятор подает эту жалобу.
Если вы прокомментируете эту строку и последуете «предложению» компилятора, как в 4-м задании, компилятор на самом деле доволен!!! СЕЙЧАС код компилируется отлично!Это, конечно, безумие, поскольку, как предполагает поведение во время выполнения, int[]
невозможно преобразовать в Object[]
(который является то, что T[]
стирается во время выполнения).
Итак, мой вопрос:почему компилятор «предлагает», что я назначаю Integer[]
вместо третьего задания?Как компилятор пришел к такому (ошибочному!) выводу?
Пока что в этих двух ответах много путаницы, поэтому я создал еще один сбивающий с толку пример, чтобы проиллюстрировать основную проблему:
public class PrimitiveClassGeneric {
static <T extends Number> T test(Class<T> c) {
System.out.println(c.getName() + " extends " + c.getSuperclass());
return (T) null;
}
public static void main(String args[]) {
test(Integer.class);
// "java.lang.Integer extends class java.lang.Number"
test(int.class);
// "int extends null"
}
}
Я единственный, кто думает, что это абсолютно безумие, что компилятор позволяет компилировать приведенный выше код?
Было бы неразумно распечатать c.getSuperclass().getName()
например, в приведенном выше коде, поскольку я указал, что T extends Number
.Конечно сейчас getName()
бросит NullPointerException
когда c == int.class
, с c.getSuperclass() == null
.
И для меня это очень веская причина вообще отказаться от компиляции кода.
Возможно, последнее безумие:
int.class.cast(null);
Этот код компилируется И работает нормально.
Решение
Тип int.class
является Class<Integer>
, так genericArrayNewInstance()
предполагалось бы вернуть Integer[]
.Но на самом деле функция создает int[]
, поэтому при его возврате будет возникать исключение приведения класса.По сути, актерский состав T[]
внутри функции в данном случае недопустимо, поскольку int[]
это не T[]
(примитивы нельзя использовать в переменных типа).Вы не можете обрабатывать примитивные типы массивов в целом;поэтому вам либо нужно, чтобы ваш метод просто возвращал тип Object
, или вам придется создать отдельные методы для ссылочных типов и для примитивных типов.
Другие советы
Несколько моментов:
- примитивы автобокс к своим объектам-аналогам (оберткам), когда это необходимо.
- примитивные массивы являются объектами, поэтому они не упаковываются автоматически.
- дженерики не могут использовать примитивы в качестве параметров типа
Для ваших примеров вот мои предположения:
в 3 автобокс происходит с тип параметра, но этого не происходит с возвращаемым массивом
в 4 автобокс происходит с тип параметра, но этого не происходит с аргумент метода, так что на самом деле int[]
генерируется, но Integer[]
ожидается
Автобоксинг в случае параметра типа может быть не совсем автобокс, но это что-то с той же идеей.
Обновлять: ваш второй пример не имеет ничего плохого. int.class
это Class
, поэтому у компилятора нет причин отклонять его.
Согласен с оригинальным постером.Это безумие.Почему я не могу использовать примитив с универсальным?Возможно, это не проблема компилятора, а проблема языка.Пропускать примитивные типы из дженериков просто неправильно.
Для этого:
intArray = (int[]) Array.newInstance(int.class, 0);
int.class — это просто объект класса.Так что можно пройти мимо.«int» — это тип, поэтому это не нормально, поскольку он явно примитивен.Не сказать, что это «лучший» способ создания языка, просто придерживаться языка.
Это настолько безумно, что я не могу создать оболочку для выделения памяти (массива) примитивов, используя generic.Если вы используете объекты, это настолько раздуто для огромных коллекций, что это расточительно.Люди, создавшие язык/машину Java, явно имеют ограниченный мозг.Они могут сделать что-то неправильно с первого раза, но чтобы исправить это, потребуется десятилетие, и если это будет сделано неправильно.