Как использовать 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>>();
Но какой правильный способ сделать это с помощью массивов?
Ответы
Ответ 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 });