Подтвердить что ты не робот

Как использовать generics с массивом классов?

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

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

Но затем я получаю предупреждение generics:

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

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

В Eclipse есть довольно мощные инструменты для определения того, какие параметры следует использовать для исправления объявлений дженериков, но в этом случае он падает, как это имеет место, когда вы имеете дело с классом. Предлагаемая им процедура "Внутренний тип аргументов" не изменяет код вообще, оставляя предупреждение.

Мне удалось обойти это, используя вместо этого Collection:

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

Но какой правильный способ сделать это с помощью массивов?

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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

Ответ 3

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

Object[] myArray = new String[5];

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

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

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

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

Ответ 4

Но какой правильный способ сделать это с помощью массивов?

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

// 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. Никаких проблем не обнаружено - и почему это опасно.

Ответ 5

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

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

Ответ 6

Небезопасный тип и с немедленным предупреждением о бросании:

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