Обобщения в Java – часть 2
ОГЛАВЛЕНИЕ
В следующей части будут рассмотрены проблемы со смешением обобщенного и необобщенного кода и проблемы с преобразованием необобщенного устаревшего кода в обобщения.
Непроверенное предупреждение
Компилятор Java предупреждает, если не может проверить безопасность типов. Это происходит при смешении обобщенного и необобщенного кода (это плохая идея). Разработка приложений без проверки таких предупреждений является риском. Лучше рассматривать предупреждения как ошибки.
Рассмотрите следующий пример:
public class Test
{
public static void foo1(Collection c)
{
}
public static void foo2(Collection<Integer> c)
{
}
public static void main(String[] args)
{
Collection<Integer> coll = new ArrayList<Integer>();
foo1(coll);
ArrayList lst = new ArrayList();
foo2(lst);
}
}
Имеется метод foo1, принимающий традиционный Collection(коллекция) в качестве параметра. Метод foo2, напротив, принимает обобщенную версию Collection. Объект традиционного ArrayList отправляется методу foo2. Поскольку ArrayList может содержать объекты разных типов, внутри метода foo2, компилятор не в состоянии гарантировать, что Collection<Integer> будет содержать только экземпляры Integer. В данном случае компилятор выдает предупреждение, показанное ниже:
Предупреждение: строка (22) [не проверено] обнаружено непроверенное преобразование:
java.util.ArrayList требуется:
java.util.Collection<java.lang.Integer>
Хотя получить это предупреждение лучше, чем не быть предупрежденным о потенциальной проблеме, было бы лучше, если бы это была ошибка вместо предупреждения. Используйте флаг компиляции – Xlint, чтобы точно не упустить это предупреждение.
Есть еще одна проблема. В методе main отправляется обобщенный Collection из Integer методу foo1. Хотя компилятор не жалуется на это, это опасно. Что если внутри метода foo1 в коллекцию добавляются объекты типов, отличных от Integer? Это нарушит безопасность типов.
Возникает вопрос, почему компилятор разрешил рассматривать обобщенный тип как традиционный тип. Причина в том, что на уровне байтового кода нет понятия обобщений. Подробно это описано в разделе “Реализация обобщений”.
Ограничения
В использовании обобщений есть ряд ограничений. Нельзя создать массив обобщенных коллекций. Любой массив коллекций шаблонов разрешен, но является опасным с позиции безопасности типов. Нельзя создать обобщение элементарного типа. Например, ArrayList<int> запрещен. Нельзя создать параметризованные статические поля внутри обобщенного класса или иметь статические методы с параметризованными типами в качестве параметров. Например, рассмотрите следующее:
class MyClass<T>
{
private Collection<T> myCol1; // нормально
private static Collection<T> myCol2; // ошибка
}
Внутри обобщенного класса нельзя создать экземпляр объекта или массива объектов параметризованного типа. Например, если имеется обобщенный класс MyClass<T> внутри метода этого класса, нельзя написать:
new T();
или
new T[10];
Можно сгенерировать исключение обобщенного типа; однако в блоке catch придется использовать конкретный тип, вместо обобщенного.
Можно унаследовать класс от другого обобщенного класса; но нельзя унаследовать от параметрического типа. Например, тогда как:
class MyClass2<T> extends MyClass<T>
{
}
нормально,
class MyClass2<T> extends T
{
}
нет.
Запрещено наследовать от двух экземпляров одного и того же обобщенного типа. Например, тогда как:
class MyList implements MyCollection<Integer>
{
//...
}
нормально,
class MyList implements MyCollection<Integer>, MyCollection<Double>
{
//...
}
нет.
Какова причина этих ограничений? Они обусловлены способом реализации обобщений. Понимание механизмов реализации обобщений в Java дает понимание, откуда эти ограничения берутся и почему они существуют.