Какая причина, по которой я не могу создавать общие типы массивов в Java?

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

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

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

Какова причина?

Ответ 1

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

Ответ 2

Цитата:

Массивы родовых типов не потому что они не звучат. проблема связана с взаимодействием Массивы 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!
    }
}

Мы предложили разрешить это проблема с использованием статически безопасных массивов (aka Variance) bute, который был отклонен для Тигра.

- gafter

(Я считаю, что это Neal Gafter, но я не уверен)

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

Ответ 3

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

Обычная работа заключается в следующем.

T[] ts = new T[n];

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

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

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

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

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

Ответ 4

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

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

Ответ 5

Ковариантные массивы

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

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

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

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

Это означает, что мы можем обмануть компилятор, но мы не можем обмануть систему типов во время выполнения. И это так, потому что массивы - это то, что мы называем типом 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

Ответ 6

Ответ уже был дан, но если у вас уже есть экземпляр 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

Ответ 7

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

Там хороший обзор здесь.

Ответ 8

Мне нравится ответ, косвенно Gafter. Однако я предлагаю, что это неправильно. Я немного изменил код Гафтера. Он компилируется, и он работает некоторое время, а затем он бомбит, где 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. Не понял ли я этот вопрос?

Ответ 9

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

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

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

  • Создан не-общий класс оболочки вокруг Stack (скажем, MyStack)
  • MyStack [] stacks = new MyStack [2] отлично работает

Уродливый, но Java счастлив.

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

Ответ 10

Из Учебник 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 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 не будет выбрано в этом случае.

Ответ 11

Я знаю, что я немного опаздываю на вечеринку здесь, но я решил, что смогу помочь любым будущим гуглерам, поскольку ни один из этих ответов не устранил мою проблему. Ответ Ferdi265 очень помог.

Я пытаюсь создать свой собственный Связанный список, поэтому для меня работал следующий код:

package myList;
import java.lang.reflect.Array;

public class MyList<TYPE>  {

    private Node<TYPE> header = null;

    public void clear() {   header = null;  }

    public void add(TYPE t) {   header = new Node<TYPE>(t,header);    }

    public TYPE get(int position) {  return getNode(position).getObject();  }

    @SuppressWarnings("unchecked")
    public TYPE[] toArray() {       
        TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());        
        for(int i=0 ; i<size() ; i++)   result[i] = get(i); 
        return result;
    }


    public int size(){
         int i = 0;   
         Node<TYPE> current = header;
         while(current != null) {   
           current = current.getNext();
           i++;
        }
        return i;
    }  

В методе toArray() лежит способ создания для меня массива общего типа:

TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());    

Ответ 12

Конечно, должен быть хороший способ обойти (возможно, используя отражение), потому что мне кажется, что именно это делает ArrayList.toArray(T[] a). Я цитирую:

public <T> T[] toArray(T[] a)

Возвращает массив, содержащий все элементы в этом списке в правильном порядке; тип времени выполнения Возвращаемый массив - массив указанного массива. Если список соответствует указанного массива, возвращается в нем. В противном случае новый массив выделенного типом среды выполнения указанного массива и размером этот список.

Таким образом, одним из способов использования этой функции является создание ArrayList объектов, которые вы хотите в массиве, а затем используйте toArray(T[] a) для создания реального массива. Это было бы не скоро, но вы не указали свои требования.

Знает ли кто-нибудь, как toArray(T[] a) реализован?

Ответ 13

Это связано с тем, что после того, как они были добавлены в java, дженерики были добавлены в java, поэтому их рода неуклюжие, потому что оригинальные разработчики Java думали, что при создании массива тип будет указан при его создании. Так что это не работает с дженериками, поэтому вам нужно сделать E [] array = (E []) новый объект [15]; Это компилируется, но оно дает предупреждение.

Ответ 14

Если мы не можем создавать типичные массивы, почему язык имеет общие типы массивов? Какой смысл иметь тип без объектов?

Единственная причина, по которой я могу думать, - varargs - foo(T...). В противном случае они могли бы полностью очистить общие типы массивов. (Ну, на самом деле им не нужно было использовать массив для varargs, так как varargs не существовало до 1.5. Возможно, это еще одна ошибка.)

Итак, это ложь, вы можете создавать типичные массивы через varargs!

Конечно, проблемы с общими массивами по-прежнему реальны, например.

static <T> T[] foo(T... args){
    return args;
}
static <T> T[] foo2(T a1, T a2){
    return foo(a1, a2);
}

public static void main(String[] args){
    String[] x2 = foo2("a", "b"); // heap pollution!
}

Мы можем использовать этот пример, чтобы фактически продемонстрировать опасность общего массива.

С другой стороны, мы используем общие varargs в течение десятилетия, и небо еще не падает. Поэтому мы можем утверждать, что проблемы преувеличены; Это не важно. Если разрешено явное создание общего массива, у нас появятся ошибки здесь и там; но мы привыкли к проблемам стирания, и мы можем жить с ним.

И мы можем указать на foo2, чтобы опровергнуть утверждение о том, что спецификация удерживает нас от проблем, которые, как они утверждают, удерживают нас. Если у Sun было больше времени и ресурсов на 1,5, я считаю, что они могли бы достичь более удовлетворительной резолюции.

Ответ 15

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

Но это не рекомендуется.

Поскольку стирание типа и, что более важно, covariance в массиве, который только позволяет массиву подтипа, может быть назначен массиву супертипа, что вынуждает вас использовать явное приведение типа при попытке вернуть значение обратно, вызывая запуск -time ClassCastException, что является одной из основных целей, которые непатентованные средства пытаются устранить: Более строгие проверки типов во время компиляции.

Object[] stringArray = { "hi", "me" };
stringArray[1] = 1;
String aString = (String) stringArray[1]; // boom! the TypeCastException

Более прямой пример можно найти в Эффективной Java: элемент 25.


ковариация: массив типа S [] является подтипом T [], если S является подтипом T

Ответ 16

Попробуйте это:

List<?>[] arrayOfLists = new List<?>[4];