След: Премия
Урок: Обобщения
Преобразование Кода Наследства, чтобы Использовать Обобщения
Домашняя страница > Премия > Обобщения

Преобразование Кода Наследства, чтобы Использовать Обобщения

Ранее, мы показали, как новый и код наследства может взаимодействовать. Теперь, пора смотреть на более трудную проблему "generifying" старого кода.

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

Вы должны удостовериться, что универсальный API является весьма должным образом рестриктивным; это должно продолжать поддерживать исходный контракт API. Рассмотрите снова некоторые примеры от java.util.Collection. Предуниверсальный API похож:

interface Collection {
    public boolean containsAll(Collection c);
    public boolean addAll(Collection c);
}

Наивная попытка к generify это было бы следующее:

interface Collection<E> {

    public boolean containsAll(Collection<E> c);
    public boolean addAll(Collection<E> c);
}

В то время как это, конечно, безопасно с точки зрения типов, это не соответствует исходному контракту API. containsAll() метод работает с любым видом входящего набора. Это только успешно выполнится, если входящий набор действительно будет содержать только экземпляры E, но:

В случае addAll(), мы должны быть в состоянии добавить любой набор, который состоит из экземпляров подтипа E. Мы видели, как обработать эту ситуацию правильно в разделе Универсальные Методы.

Вы также должны гарантировать, что пересмотренный API сохраняет совместимость на уровне двоичных кодов со старыми клиентами. Это подразумевает, что стирание API должно быть тем же самым как оригиналом, ungenerified API. В большинстве случаев это выпадает естественно, но есть некоторые тонкие случаи. Мы исследуем один из самых тонких случаев, с которыми мы встретились, метод Collections.max(). Поскольку мы видели в разделе Больше Забавы с Подстановочными знаками, вероятной подписью для max() :

public static <T extends Comparable<? super T>> 
        T max(Collection<T> coll)

Это прекрасно, за исключением того, что стирание этой подписи:

public static Comparable max(Collection coll)

который отличается чем исходная подпись max():

public static Object max(Collection coll)

Возможно, конечно, определил эту подпись для max(), но это не было сделано, и все старые двоичные файлы class тот вызов Collections.max() зависьте от подписи, которая возвращается Object.

Мы можем вынудить стирание отличаться, явно определяя суперкласс в направляющемся в формальный параметр типа T.

public static <T extends Object & Comparable<? super T>> 
        T max(Collection<T> coll)

Это - пример предоставления многократных границ для параметра типа, используя синтаксис T1 & T2 ... & Tn. Переменная типа с многократными границами, как известно, является подтипом всех типов, перечисленных в связанном. Когда связанное кратное число используется, первый тип, упомянутый в связанном, используется в качестве стирания переменной типа.

Наконец, мы должны вспомнить это max только чтения от его входного набора, и так применимы к наборам любого подтипа T.

Это приносит нам к фактической подписи, используемой в JDK:

public static <T extends Object & Comparable<? super T>> 
        T max(Collection<? extends T> coll)

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

Другой проблемой, чтобы не упустить являются ковариантные возвраты, то есть, совершенствовав тип возврата метода в подклассе. Недопустимо использовать в своих интересах эту функцию в старом API. Чтобы видеть почему, давайте смотреть на пример.

Предположите, что Ваш исходный API имел форму:

public class Foo {
    // Factory. Should create an instance of 
    // whatever class it is declared in.
    public Foo create() {
        ...
    }
}

public class Bar extends Foo {
    // Actually creates a Bar.
    public Foo create() {
        ...
    }
}

Используя в своих интересах ковариантные возвраты, Вы изменяете это к:

public class Foo {
    // Factory. Should create an instance of 
    // whatever class it is declared in.
    public Foo create() {
        ...
    }
}

public class Bar extends Foo {
    // Actually creates a Bar.
    public Bar create() {
        ...
    }
}

Теперь, предположите, что сторонний клиент Вашего кода записал следующее:

public class Baz extends Bar {
    // Actually creates a Baz.
    public Foo create() {
        ...
    }
}

Виртуальная машина Java непосредственно не поддерживает переопределение методов с различными типами возврата. Эта функция поддерживается компилятором. Следовательно, если class Baz перекомпилирован, это не будет должным образом переопределять create() метод Bar. Кроме того, Baz должен будет быть изменен, так как код будет отклонен как записано - тип возврата create() в Baz не подтип типа возврата create() в Bar.


Проблемы с примерами? Попытайтесь Компилировать и Выполнить Примеры: FAQ.
Жалобы? Поздравление? Предложения? Дайте нам свою обратную связь.

Предыдущая страница: Больше Забавы с Подстановочными знаками
Следующая страница: Подтверждения



Spec-Zone.ru - all specs in one place