Spec-Zone .ru
спецификации, руководства, описания, API
След: Премия
Урок: Обобщения
Взаимодействие с Кодом Наследства
Домашняя страница > Премия > Обобщения

Взаимодействие с Кодом Наследства

До сих пор все наши примеры приняли идеализированный мир, где все используют последнюю версию языка программирования Java, который поддерживает обобщения.

Увы, в действительности это не имеет место. Миллионы строк кода были записаны в более ранних версиях языка, и они не будут все преобразованы быстро.

Позже, в Коде Наследства Преобразования, чтобы Использовать раздел Обобщений, мы займемся проблемой преобразования Вашего старого кода, чтобы использовать обобщения. В этом разделе мы сосредоточимся на более простой проблеме: как наследство может кодировать, и универсальный код взаимодействуют? У этого вопроса есть две части: использование наследства кодирует изнутри универсального кода и использования универсального кода в пределах кода наследства.

Используя Код Наследства в Универсальном Коде

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

Как пример, предположите, что Вы хотите использовать пакет com.Example.widgets. Люди в Example.com продают систему для контроля за состоянием запасов, выделения которого показывают ниже:

package com.Example.widgets;

public interface Part {...}

public class Inventory {
    /**
     * Adds a new Assembly to the inventory database.
     * The assembly is given the name name, and 
     * consists of a set parts specified by parts. 
     * All elements of the collection parts
     * must support the Part interface.
     **/ 
    public static void addAssembly(String name, Collection parts) {...}
    public static Assembly getAssembly(String name) {...}
}

public interface Assembly {
    // Returns a collection of Parts
    Collection getParts();
}

Теперь, требуется добавить новый код, который использует API выше. Было бы хорошо гарантировать, что Вы всегда вызывали addAssembly() с надлежащими параметрами - то есть, то, что набор Вы передаете в, действительно a Collection из Part. Конечно, обобщения, настраивают сделанный для этого:

package com.mycompany.inventory;

import com.Example.widgets.*;

public class Blade implements Part {
    ...
}

public class Guillotine implements Part {
}

public class Main {
    public static void main(String[] args) {
        Collection<Part> c = new ArrayList<Part>();
        c.add(new Guillotine()) ;
        c.add(new Blade());
        Inventory.addAssembly("thingee", c);
        Collection<Part> k = Inventory.getAssembly("thingee").getParts();
    }
}

Когда мы вызываем addAssembly, это ожидает, что второй параметр будет иметь тип Collection. Фактический параметр имеет тип Collection<Part>. Это работает, но почему? В конце концов большинство наборов не содержит Part объекты, и так вообще, у компилятора нет никакого способа знать какой набор тип Collection обращается к.

В надлежащем универсальном коде, Collection всегда сопровождался бы параметром типа. Когда универсальный тип как Collection используется без параметра типа, он вызвал необработанный тип.

Первый инстинкт большинства людей - это Collection действительно означает Collection<Object>. Однако, как мы видели ранее, не безопасно передать a Collection<Part> в месте, где a Collection<Object> требуется. Это больше с точностью до, говорят что тип Collection обозначает набор некоторого неизвестного типа, точно так же как Collection<?>.

Но ожидайте, который не может быть правильным также! Рассмотрите звонок getParts(), который возвращает a Collection. Это тогда присваивается k, который является a Collection<Part>. Если результатом вызова является a Collection<?>, присвоение было бы ошибкой.

В действительности присвоение является законным, но оно генерирует предупреждение непроверенное. Предупреждение необходимо, потому что факт - то, что компилятор не может гарантировать свою правильность. У нас нет никакого способа регистрировать код наследства getAssembly() гарантировать, что действительно возвращаемый набор является набором Parts. Тип, используемый в коде, Collection, и можно было по закону вставить все виды объектов в такой набор.

Так, разве это не должно быть ошибкой? Теоретически говоря, да; но фактически разговор, если универсальный код собирается вызвать код наследства, это должно быть позволено. Вам решать, программист, чтобы убедиться, что в этом случае, присвоение безопасно потому что контракт getAssembly() говорит, что это возвращает набор Parts, даже при том, что подпись типа не показывает это.

Таким образом, необработанные типы очень походят на подстановочные типы, но они не typechecked как строго. Это - преднамеренное проектное решение, чтобы позволить обобщениям взаимодействовать с существующим ранее кодом наследства.

Вызов кода наследства от универсального кода по сути опасен; как только Вы смешиваете универсальный код с неуниверсальным кодом наследства, вся безопасность гарантирует, что универсальная система типов обычно обеспечивает, являются пустыми. Однако, Вы все еще более обеспечены, чем Вы были, не используя обобщения вообще. По крайней мере, Вы знаете, что код Вашего конца является непротиворечивым.

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

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

Что происходит, если Вы все еще сделали ошибку, и код, который вызвал предупреждение, действительно не безопасен с точки зрения типов? Давайте смотреть на такую ситуацию. В процессе, мы получим некоторое понимание работ компилятора.

Стирание и Преобразование

public String loophole(Integer x) {
    List<String> ys = new LinkedList<String>();
    List xs = ys;
    xs.add(x); // Compile-time unchecked warning
    return ys.iterator().next();
}

Здесь, мы исказили список строк и простой список. Мы вставляем Integer в список, и попытку извлечь a String. Это является ясно неправильным. Если мы проигнорируем предупреждение и попытаемся выполнить этот код, то оно перестанет работать точно в точке, где мы пытаемся использовать неправильный тип. Во время выполнения этот код ведет себя как:

public String loophole(Integer x) {
    List ys = new LinkedList;
    List xs = ys;
    xs.add(x); 
    return(String) ys.iterator().next(); // run time error
}

Когда мы извлекаем элемент из списка, и пытаемся обработать его как строку, бросая его к String, мы получим a ClassCastException. Та же самая вещь происходит с универсальной версией loophole().

Причина этого, что обобщения реализуются компилятором Java как преобразование фронтэнда, названное стиранием. Можно (почти) думать об этом как о преобразовании от источника к источнику, посредством чего универсальная версия loophole() преобразовывается в неуниверсальную версию.

В результате безопасность типов и целостность виртуальной машины Java никогда не находятся в опасности, даже в присутствии предупреждений непроверенных.

В основном стирание избавляется от (или стирается), вся универсальная информация о типе. Вся информация о типе betweeen угловые скобки выводится, таким образом, например, параметризованный тип как List<String> преобразовывается в List. Все остающееся использование переменных типа заменяется верхней границей переменной типа (обычно Object). И, всякий раз, когда получающийся код не корректен типом, бросок к соответствующему типу вставляется, как в последней строке loophole.

Полное изложение стирания выходит за рамки этого учебного руководства, но простое описание, которое мы только дали, не далеко от истины. Хорошо знать немного об этом, особенно если Вы хотите сделать более сложные вещи как преобразование существующих API, чтобы использовать обобщения (см. Код Наследства Преобразования, чтобы Использовать раздел Обобщений), или только хотят понять, почему вещами является способ, которым они.

Используя Универсальный Код в Коде Наследства

Теперь давайте рассматривать обратный случай. Предположите, что Example.com хотел преобразовывать их API, чтобы использовать обобщения, но что некоторые из их клиентов еще не имеют. Таким образом, теперь код похож:

package com.Example.widgets;

public interface Part { 
    ...
}

public class Inventory {
    /**
     * Adds a new Assembly to the inventory database.
     * The assembly is given the name name, and 
     * consists of a set parts specified by parts. 
     * All elements of the collection parts
     * must support the Part interface.
     **/ 
    public static void addAssembly(String name, Collection<Part> parts) {...}
    public static Assembly getAssembly(String name) {...}
}

public interface Assembly {
    // Returns a collection of Parts
    Collection<Part> getParts();
}

и клиентский код похож:

package com.mycompany.inventory;

import com.Example.widgets.*;

public class Blade implements Part {
...
}

public class Guillotine implements Part {
}

public class Main {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        c.add(new Guillotine()) ;
        c.add(new Blade());

        // 1: unchecked warning
        Inventory.addAssembly("thingee", c);

        Collection k = Inventory.getAssembly("thingee").getParts();
    }
}

Клиентский код был записан прежде, чем обобщения были представлены, но он использует пакет com.Example.widgets и библиотека набора, оба из которых используют универсальные типы. Все использование универсальных описаний типа в клиентском коде является необработанными типами.

Строка 1 генерирует предупреждение непроверенное, потому что сырые данные Collection передается в где a Collection из Parts ожидается, и компилятор не может гарантировать что сырые данные Collection действительно a Collection из Parts.

Как альтернатива, можно скомпилировать клиентский код, используя источник 1.4 флага, гарантируя, что никакие предупреждения не сгенерированы. Однако, в этом случае Вы не будете в состоянии использовать любую из новых функций языка, представленных в JDK 5.0.



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

Предыдущая страница: Универсальные Методы
Следующая страница: Мелкий шрифт