русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Тайна стирания


Дата добавления: 2015-06-12; просмотров: 696; Нарушение авторских прав


Когда вы приступаете к более глубокому изучению контейнеров, некоторые обстоятельства на первых порах выглядят довольно странно. Например, запись ArrayList.class возможна, а запись ArrayList<Integer>.class — нет. Или возьмите следующий фрагмент:

//: generics/ErasedTypeEquivalence.java

import java.util.*;

 

public class ErasedTypeEquivalence {

public static void main(String[] args) {

Class c1 = new ArrayList<String>().getClass();

Class c2 = new ArrayList<Integer>().getClass();

System.out.println(c1 == c2);

}

}

<spoiler text="Output:">

true

</spoiler> Было бы логично считать, что ArrayList<String> и ArrayList<Integer> — разные типы, поэтому их поведение должно различаться, и при попытке поместить Integer в ArrayList<String> результат (неудача) должен отличаться от того, который будет получен при помещении Integer в ArrayList<Integer> (успех). Однако эта программа создает впечатление, что эти типы одинаковы. Следующий пример еще сильнее запутывает ситуацию:

//: generics/LostInformation.java

import java.util.*;

 

class Frob {}

class Fnorkle {}

class Quark<Q> {}

class Particle<POSITION,MOMENTUM> {}

 

public class LostInformation {

public static void main(String[] args) {

List<Frob> list = new ArrayList<Frob>();

Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>();

Quark<Fnorkle> quark = new Quark<Fnorkle>();

Particle<Long,Double> p = new Particle<Long,Double>();

System.out.println(Arrays.toString(

list.getClass().getTypeParameters()));

System.out.println(Arrays.toString(

map.getClass().getTypeParameters()));

System.out.println(Arrays.toString(

quark.getClass().getTypeParameters()));



System.out.println(Arrays.toString(

p.getClass().getTypeParameters()));

}

}

<spoiler text="Output:">

[E]

[K, V]

[Q]

[POSITION, MOMENTUM]

</spoiler> Согласно документации JDK, Class.getTypeParameters() «возвращает массив объектов TypeVariable, представляющих переменные типов, указанные в параметризованном объявлении...» Казалось бы, по ним можно определить параметры типов — но, как видно из результатов, вы всего лишь узнаете, какие идентификаторы использовались в качестве заполнителей, а эта информация не представляет особого интереса. Мы приходим к холодной, бездушной истине: Информация о параметрах типов недоступна внутри параметризованного кода. Таким образом, вы можете узнать идентификатор параметра типа и ограничение параметризованного типа, но фактические параметры типов, использованные для создания конкретного экземпляра, остаются неизвестными. Этот факт, особенно раздражающий программистов с опытом работы на C++, является основной проблемой, которую приходится решать при использовании параметризации в Java.

Параметризация в Java реализуется с применением стирания (erasure). Это означает, что при использовании параметризации вся конкретная информация о типе утрачивается. Внутри параметризованного кода вы знаете только то, что используется некий объект. Таким образом, List<String> и List<Integer> действительно являются одним типом во время выполнения; обе формы «стираются» до своего низкоуровневого типа List. Именно стирание и создаваемые им проблемы становятся главной преградой при изучении параметризации вJava; этой теме и будет посвящен настоящий раздел.

Подход C++

В следующем примере, написанном на C++, используются шаблоны. Синтаксис параметризованных типов выглядит знакомо, потому что многие идеи C++ были взяты за основу при разработке Java:

#include <iostream>

using namespace std;

 

template<class T> class Manipulator {

T obj;

public:

Manipulator(T x) { obj = x; }

void manipulate() { obj.f(); }

};

 

class HasF {

public:

void f() { cout << "HasF::f()" << endl; }

};

 

int main() {

HasF hf;

Manipulator<HasF> manipulator(hf);

manipulator.manipulate();

}

<spoiler text="Output:">

HasF-:f()

</spoiler> Класс Manipulator хранит объект типа Т. Нас здесь интересует метод manipulate(), который вызывает метод f() для obj. Как он узнает, что у параметра типа Т существует метод f()? Компилятор C++ выполняет проверку при создании экземпляра шаблона, поэтому в точке создания Manipulator<HasF> он узнает о том, что HasF содержит метод f(). В противном случае компилятор выдает ошибку, а безопасность типов сохраняется. Написать такой код на C++ несложно, потому что при создании экземпляра шаблона код шаблона знает тип своих параметров. С параметризацией Java дело обстоит иначе. Вот как выглядит версия HasF, переписанная на Java:

//: generics/HasF.java

public class HasF {

public void f() { System.out.println("HasF.f()"); }

}

Если мы возьмем остальной код примера и перепишем его на Java, он не будет компилироваться:

//: generics/Manipulation.java

// {CompileTimeError} (He компилируется)

 

class Manipulator<T> {

private T obj;

public Manipulator(T x) { obj = x; }

// Error: cannot find symbol: method f():

public void manipulate() { obj.f(); }

}

 

public class Manipulation {

public static void main(String[] args) {

HasF hf = new HasF();

Manipulator<HasF> manipulator =

new Manipulator<HasF>(hf);

manipulator.manipulate();

}

}

Из-за стирания компилятор Java не может сопоставить требование о возможности вызова f() для obj из manipulate() с тем фактом, что HasF содержит метод f(). Чтобы вызвать f(), мы должны «помочь» параметризованному классу, и передать ему ограничение; компилятор принимает только те типы, которые соответствуют указанному ограничению. Для задания ограничения используется ключевое слово extends. При заданном ограничении следующий фрагмент компилируется нормально:

//: generics/Manipulator2.java

class Manipulator2<T extends HasF> {

private T obj;

public Manipulator2(T x) { obj = x; }

public void manipulate() { obj.f(); }

}

Ограничение <T extends HasF> указывает на то, что параметр Т должен относиться к типу HasF или производному от него. Если это условие выполняется, то вызов f() для obj безопасен. Можно сказать, что параметр типа стирается до первого ограничения (как будет показано позже, ограничений может быть несколько). Мы также рассмотрим понятие стирания параметра типа. Компилятор фактически заменяет параметр типа его «стертой» версией, так что в предыдущем случае Т стирается до HasF, а результат получается таким, как при замене Т на HasF в теле класса. Справедливости ради нужно заметить, что в Manipulation2.java параметризация никакой реальной пользы не дает. С таким же успехом можно выполнить стирание самостоятельно, создав непараметризованный класс:

//: generics/Manipulator3.java

class Manipulator3 {

private HasF obj;

public Manipulator3(HasF x) { obj = x; }

public void manipulate() { obj.f(); }

}

Мы приходим к важному заключению: параметризация полезна только тогда, когда вы хотите использовать параметры типов, более «общие», нежели конкретный тип (и производные от него), то есть когда код должен работать для разных классов. В результате параметры типов и их применение в параметризованном коде сложнее простой замены классов. Впрочем, это не означает, что форма <Т extends HasF> чем-то ущербна. Например, если класс содержит метод, возвращающий Т, то параметризация будет полезной, потому что метод вернет точный тип:

//: generics/ReturnGenericType.java

class ReturnGenericType<T extends HasF> {

private T obj;

public ReturnGenericType(T x) { obj = x; }

public T get() { return obj; }

}

Просмотрите код и подумайте, достаточно ли он «сложен» для применения параметризации. Ограничения будут более подробно рассмотрены далее в этой главе.



<== предыдущая лекция | следующая лекция ==>
Построение сложных моделей | Миграционная совместимость


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.008 сек.