Как использовать дженерики с массивом классов? - PullRequest
47 голосов
/ 15 апреля 2009

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

Class<? extends SuperClass>[] availableTypes = { SubClass1.class, SubClass2.class };

Это дает мне ошибку:

Cannot create a generic array of Class<? extends SuperClass>.

Я получаю то же сообщение, если пытаюсь квалифицировать создание массива справа от инициализации:

Class<? extends SuperClass>[] availableTypes = Class<? extends SuperClass>[] { SubClass1.class, SubClass2.class };

Я могу получить код для компиляции, если исключу квалификации дженериков:

Class[] availableTypes = { SubClass1.class, SubClass2.class };

Но тогда я получаю предупреждение об общем:

Класс является необработанным типом. Ссылки на универсальный тип Class должны быть параметризованы.

Я пытаюсь; Я пытаюсь! :) Кроме того, на этом этапе, даже если это не вызвало предупреждение, я теряю часть интерфейса, который я пытался определить. Я не хочу просто возвращать массив произвольных классов; Я хочу вернуть массив классов, которые являются подклассами определенного суперкласса!

В Eclipse есть несколько довольно мощных инструментов для определения того, какие параметры использовать для исправления объявлений обобщенных типов, но в этом случае он падает, как это обычно бывает при работе с Class. Предложенная процедура «Infer Generic Type Arguments» вообще не меняет код, оставляя предупреждение.

Я смог обойти это, используя вместо этого коллекцию:

List<Class<? extends SuperClass>> availableTypes = new List<Class<? extends SuperClass>>();

Но как правильно сделать это с массивами?

Ответы [ 6 ]

23 голосов
/ 15 апреля 2009

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

Два обходных пути:

  • Придерживайтесь использования коллекции (например, ArrayList<Class<? extends SuperClass>>), которая работает так же хорошо, как массив, а также позволяет расширение.
  • Поместите аннотацию @SuppressWarnings("unchecked") в код, создающий массив, вместе с комментарием, оправдывающим его использование.
14 голосов
/ 15 апреля 2009

Используйте этот синтаксис:

Class<? extends SuperClass>[] avail = new Class[] { SubClass1.class, ... };

Это выдаст вам предупреждение «unchecked», и это правильно, поскольку вы можете включить объект Class для типа, который не расширяется SuperClass в массиве.

12 голосов
/ 15 апреля 2009

Правильный способ сделать это с массивами - это сделать с коллекцией. Сожалею! По сложному ряду причин массивы не очень хорошо работают с генериками. Массивы имеют другую модель ковариации, чем обычные объекты, что в конечном итоге вызывает проблемы, с которыми вы сталкиваетесь. Например, с массивами, но не (обычно) с общими объектами, вы можете легально сделать это:

Object[] myArray = new String[5];

пока вы не можете сделать это:

LinkedList<Object> myCollection = new LinkedList<String>();

Если вам нужна более подробная информация, вы можете увидеть страницу Arrays In Java Generics из превосходного Generics FAQ *

Как сказал Симонн, вы также можете просто использовать свои массивы такими, какие они есть, и использовать @SuppressWarnings("unchecked"), чтобы заставить замолчать предупреждения. Это будет работать, но без безопасности типов, которую могут предоставить вам дженерики. Если вас беспокоит производительность, просто используйте ArrayList, так что вы просто используете тонкую оболочку вокруг массива, но со всеми гарантиями безопасности типов, предоставляемыми обобщениями.

5 голосов
/ 15 апреля 2009

Но как правильно сделать это с массивами?

Нет безопасного типа для этого; использование коллекции - правильный подход. Чтобы понять почему, представьте, было ли это разрешено. У вас может быть такая ситуация:

// Illegal!
Object[] baskets = new FruitBasket<? extends Citrus>[10];

// This is okay.
baskets[0] = new FruitBasket<Lemon>();

// Danger! This should fail, but the type system will let it through.
baskets[0] = new FruitBasket<Potato>();

Система типов должна определить, имеет ли добавляемая в массив корзина тип FruitBasket<? extends Citrus> или подтип. FruitBasket не соответствует и должен быть отклонен с ArrayStoreException. Но ничего не происходит!

Из-за стирания типа JVM может видеть только тип времени выполнения массива. Во время выполнения нам нужно сравнить тип массива с типом элемента, чтобы убедиться, что они совпадают. Тип компонента среды выполнения массива - FruitBasket[] после стирания типа; аналогично, тип времени выполнения элемента - FruitBasket. Никаких проблем не будет обнаружено - и поэтому это опасно.

2 голосов
/ 15 апреля 2009

Проблема в том, что создание массива универсального типа недопустимо. Единственный способ обойти это - приведение к универсальному типу при создании массива, но это не очень хорошее решение. (Обратите внимание, что можно использовать универсальный массив, но не создавать его: см. этот вопрос .)

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

0 голосов
/ 01 мая 2014

Не тип безопасен и с предупреждением о снятии флажка:

Class<? extends SuperClass>[] availableTypes =
    (Class<? extends SuperClass>[])
    (new Class[]{ SubClass1.class, SubClass2.class });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...