Странное поведение при компиляции при попытке использовать примитивный тип в обобщениях - PullRequest
2 голосов
/ 16 марта 2010
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[] вместо 3-го назначения? Как компилятор может прийти к такому (ошибочному!) Заключению?


Пока что в этих двух ответах много путаницы, поэтому я создал еще один сбивающий с толку пример, чтобы проиллюстрировать основную проблему здесь:

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);

Этот код компилируется И работает нормально.

Ответы [ 3 ]

4 голосов
/ 16 марта 2010

Тип int.class равен Class<Integer>, поэтому genericArrayNewInstance() будет выводить Integer[]. Но функция на самом деле создает int[], поэтому при возвращении она будет иметь исключение приведения класса. По сути, приведение к T[] внутри функции в этом случае недопустимо, поскольку int[] не является T[] (примитивы нельзя использовать в переменных типа). Вы не можете обрабатывать типы примитивных массивов в общем; поэтому вам нужно либо чтобы ваш метод просто возвращал тип Object, либо вы должны создавать отдельные методы для ссылочных типов и для примитивных типов.

3 голосов
/ 16 марта 2010

Несколько баллов:

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

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

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

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

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

0 голосов
/ 10 декабря 2010

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

Для этого:

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

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

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

...