Странное поведение во время компиляции при попытке использовать примитивный тип в дженериках.

StackOverflow https://stackoverflow.com/questions/2453359

Вопрос

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, или вам придется создать отдельные методы для ссылочных типов и для примитивных типов.

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

Несколько моментов:

  1. примитивы автобокс к своим объектам-аналогам (оберткам), когда это необходимо.
  2. примитивные массивы являются объектами, поэтому они не упаковываются автоматически.
  3. дженерики не могут использовать примитивы в качестве параметров типа

Для ваших примеров вот мои предположения:

в 3 автобокс происходит с тип параметра, но этого не происходит с возвращаемым массивом
в 4 автобокс происходит с тип параметра, но этого не происходит с аргумент метода, так что на самом деле int[] генерируется, но Integer[] ожидается

Автобоксинг в случае параметра типа может быть не совсем автобокс, но это что-то с той же идеей.

Обновлять: ваш второй пример не имеет ничего плохого. int.class это Class, поэтому у компилятора нет причин отклонять его.

Согласен с оригинальным постером.Это безумие.Почему я не могу использовать примитив с универсальным?Возможно, это не проблема компилятора, а проблема языка.Пропускать примитивные типы из дженериков просто неправильно.

Для этого:

intArray = (int[]) Array.newInstance(int.class, 0);

int.class — это просто объект класса.Так что можно пройти мимо.«int» — это тип, поэтому это не нормально, поскольку он явно примитивен.Не сказать, что это «лучший» способ создания языка, просто придерживаться языка.

Это настолько безумно, что я не могу создать оболочку для выделения памяти (массива) примитивов, используя generic.Если вы используете объекты, это настолько раздуто для огромных коллекций, что это расточительно.Люди, создавшие язык/машину Java, явно имеют ограниченный мозг.Они могут сделать что-то неправильно с первого раза, но чтобы исправить это, потребуется десятилетие, и если это будет сделано неправильно.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top