По какой причине я не могу создать универсальные типы массивов в Java? - PullRequest
254 голосов
/ 28 мая 2010

В чем причина, почему Java не позволяет нам делать

private T[] elements = new T[initialCapacity];

Я мог понять, что .NET не позволил нам сделать это, поскольку в .NET у вас есть типы значений, которые во время выполнения могут иметь разные размеры, но в Java все виды T будут ссылками на объекты, таким образом, имея того же размера (поправьте меня, если я ошибаюсь).

В чем причина?

Ответы [ 15 ]

186 голосов
/ 28 мая 2010

Это связано с тем, что массивы Java (в отличие от универсальных) содержат во время выполнения информацию о типе компонента. Таким образом, вы должны знать тип компонента при создании массива. Поскольку вы не знаете, что такое T во время выполнения, вы не можете создать массив.

131 голосов
/ 28 мая 2010

Цитата:

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

class Box<T> {
    final T x;
    Box(T x) {
        this.x = x;
    }
}

class Loophole {
    public static void main(String[] args) {
        Box<String>[] bsa = new Box<String>[3];
        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3); // error not caught by array store check
        String s = bsa[0].x; // BOOM!
    }
}

Мы предложили решить эту проблему с помощью статически безопасных массивов (он же Variance), который был отклонен для Tiger.

- gafter

(я полагаю, что это Neal Gafter , но я не уверен)

См. это в контексте здесь: http://forums.sun.com/thread.jspa?threadID=457033&forumID=316

44 голосов
/ 29 мая 2010

Не предоставив достойного решения, вы просто получите что-то худшее ИМХО.

Обходной путь выглядит следующим образом.

T[] ts = new T[n];

заменяется на (при условии, что T расширяет Object, а не другой класс)

T[] ts = (T[]) new Object[n];

Я предпочитаю первый пример, однако больше академических типов предпочитают второй или просто предпочитают не думать об этом.

Большинство примеров того, почему вы не можете просто использовать Object [], в равной степени применимы к List или Collection (которые поддерживаются), поэтому я считаю их очень плохими аргументами.

Примечание: это одна из причин, по которой библиотека Collections сама не компилируется без предупреждений. Если этот вариант использования не может быть поддержан без предупреждений, то что-то в корне сломано с моделью обобщенных типов IMHO.

29 голосов
/ 28 мая 2010

Причина, по которой это невозможно, заключается в том, что Java реализует свои универсальные параметры исключительно на уровне компилятора, и для каждого класса создается только один файл класса. Это называется Тип Erasure .

Во время выполнения скомпилированный класс должен обрабатывать все свои использования с одним и тем же байт-кодом. Таким образом, new T[capacity] абсолютно не знает, какой тип должен быть создан.

27 голосов
/ 12 октября 2015

Массивы ковариантны

Массивы называются ковариантными, что в основном означает, что, учитывая правила подтипирования Java, массив типа T[] может содержать элементы типа T или любой подтип T. Например

Number[] numbers = new Number[3];
numbers[0] = newInteger(10);
numbers[1] = newDouble(3.14);
numbers[2] = newByte(0);

Но не только это, правила подтипирования Java также утверждают, что массив S[] является подтипом массива T[], если S является подтипом T, поэтому что-то подобное также допустимо :

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

Поскольку согласно правилам подтипирования в Java массив Integer[] является подтипом массива Number[], поскольку Integer является подтипом числа.

Но это правило подтипов может привести к интересному вопросу: что произойдет, если мы попытаемся это сделать?

myNumber[0] = 3.14; //attempt of heap pollution

Эта последняя строка будет хорошо скомпилирована, но если мы запустим этот код, мы получим ArrayStoreException, потому что мы пытаемся поместить double в целочисленный массив. Тот факт, что мы обращаемся к массиву через ссылку Number, здесь не имеет значения, важно то, что массив является массивом целых чисел.

Это означает, что мы можем обмануть компилятор, но мы не можем обмануть систему типов во время выполнения. И это так, потому что массивы - это то, что мы называем типом reifiable. Это означает, что во время выполнения Java знает, что этот массив был фактически создан как массив целых чисел, к которым просто случается обращение через ссылку типа Number[].

Итак, как мы видим, одна вещь - это фактический тип объекта, другая вещь - это тип ссылки, которую мы используем для доступа к ней, верно?

Проблема с обобщением Java

Теперь проблема с универсальными типами в Java заключается в том, что информация о типе для параметров типа отбрасывается компилятором после завершения компиляции кода; поэтому эта информация типа не доступна во время выполнения. Этот процесс называется тип стирания . Существуют веские причины для реализации таких обобщений в Java, но это длинная история, и она связана с бинарной совместимостью с уже существующим кодом.

Важным моментом здесь является то, что, поскольку во время выполнения нет информации о типе, нет способа гарантировать, что мы не совершаем загрязнение кучи.

Давайте теперь рассмотрим следующий небезопасный код:

List<Integer> myInts = newArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution

Если компилятор Java не мешает нам делать это, система типов во время выполнения также не может остановить нас, потому что во время выполнения нет никакого способа определить, что этот список должен быть списком только целых чисел. , Среда выполнения Java позволила бы нам помещать все, что мы хотим в этот список, когда он должен содержать только целые числа, потому что, когда он был создан, он был объявлен как список целых чисел. Вот почему компилятор отклоняет строку № 4, потому что это небезопасно и, если разрешено, может нарушить предположения системы типов.

Поэтому разработчики Java позаботились о том, чтобы мы не могли обмануть компилятор. Если мы не можем обмануть компилятор (как мы можем сделать с массивами), то мы не можем обмануть и систему типов во время выполнения.

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

Я пропустил некоторые части этих ответов, вы можете прочитать полную статью здесь: https://dzone.com/articles/covariance-and-contravariance

18 голосов
/ 26 февраля 2013

Ответ уже дан, но если у вас уже есть экземпляр T, вы можете сделать это:

T t; //Assuming you already have this object instantiated or given by parameter.
int length;
T[] ts = (T[]) Array.newInstance(t.getClass(), length);

Надеюсь, я смогу помочь, Ferdi265

5 голосов
/ 28 мая 2010

Основная причина заключается в том, что массивы в Java ковариантны.

Хороший обзор здесь .

4 голосов
/ 28 мая 2010

мне нравится ответ, косвенно данный Гафтер . Однако я предлагаю это неправильно. Я немного изменил код Gafter. Он компилируется и работает какое-то время, затем бомбит, где Гафтер предсказал, что это будет

class Box<T> {

    final T x;

    Box(T x) {
        this.x = x;
    }
}

class Loophole {

    public static <T> T[] array(final T... values) {
        return (values);
    }

    public static void main(String[] args) {

        Box<String> a = new Box("Hello");
        Box<String> b = new Box("World");
        Box<String> c = new Box("!!!!!!!!!!!");
        Box<String>[] bsa = array(a, b, c);
        System.out.println("I created an array of generics.");

        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3);
        System.out.println("error not caught by array store check");

        try {
            String s = bsa[0].x;
        } catch (ClassCastException cause) {
            System.out.println("BOOM!");
            cause.printStackTrace();
        }
    }
}

Выход

I created an array of generics.
error not caught by array store check
BOOM!
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at Loophole.main(Box.java:26)

Так что мне кажется, что вы можете создавать универсальные типы массивов в Java. Я неправильно понял вопрос?

2 голосов
/ 29 сентября 2016

Из Учебник по Oracle :

Вы не можете создавать массивы параметризованных типов. Например, следующий код не компилируется:

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

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

Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.

Если вы попробуете то же самое с общим списком, возникнет проблема:

Object[] stringLists = new List<String>[];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.

Если бы разрешались массивы параметризованных списков, предыдущий код не смог бы выдать желаемое ArrayStoreException.

Для меня это звучит очень слабо. Я думаю, что любой, кто обладает достаточным пониманием дженериков, будет в порядке и даже ожидает, что ArrayStoredException не будет выброшено в таком случае.

2 голосов
/ 06 мая 2015

В моем случае я просто хотел массив стеков, что-то вроде этого:

Stack<SomeType>[] stacks = new Stack<SomeType>[2];

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

  1. Создан не универсальный класс-оболочка для стека (скажем, MyStack)
  2. MyStack [] стеки = новый MyStack [2] работал отлично

Ужасно, но Ява счастлива.

Примечание: как упомянуто BrainSlugs83 в комментарии к вопросу, вполне возможно иметь массивы обобщений в .NET

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...