Обобщения в 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 дает понимание, откуда эти ограничения берутся и почему они существуют.