Вход/Регистрация
Философия Java3
вернуться

Эккель Брюс

Шрифт:

public class ErasureAndlnheritance { @SuppressWarni ngs("unchecked") public static void main(String[] args) { Derived2 d2 = new Derived2; Object obj = d2.get; d2.set(obj); // Предупреждение!

}

} ///

Derived2 наследует от GenericBase без параметризации, и компилятор не выдает при этом никаких предупреждений. Предупреждение выводится позже, при вызове set.

Для подавления этого предупреждения в Java существует директива, приведенная в листинге (до выхода Java SE5 она не поддерживалась):

@SuppressWarnings("unchecked")

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

Ошибка, выдаваемая в Derived3, означает, что компилятор рассчитывает увидеть «обычный» базовый класс.

Добавьте к этому дополнительные усилия на управление ограничениями, если вы не желаете интерпретировать параметр типа как простой Object, — и что мы получаем в остатке? Гораздо больше хлопот при гораздо меньше, пользе по сравнению с параметризованными типами в языках вроде С++, Ada или Eiffel. Конечно, это вовсе не означает, что эти языки в целом эффективнее Java в большинстве задач программирования, а говорит лишь о том, что их механизмы параметризации типов отличаются большей гибкостью и мощью, чем в Java.

Проблемы на границах

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

//: generics/ArrayMaker.java

import java.lang.reflect.*;

import java.util.*;

public class ArrayMaker<T> { private Class<T> kind;

public ArrayMaker(Class<T> kind) { this.kind = kind; } @SuppressWarni ngs("unchecked") T[] create(int size) {

return (T[])Array.newInstance(kind, size);

}

public static void main(String[] args) { ArrayMaker<String> stringMaker =

new ArrayMaker<String>(String.class); String[] stringArray = stringMaker.create(9); System.out.pri ntin(Arrays.toStri ng(stri ngArray));

}

} /* Output;

[null, null, null. null. null. null. null. null, null]

*///:-

Несмотря на то что объект kind хранится в виде Class<T>, стирание означает, что фактически он хранится в виде Class без параметра. Следовательно, при выполнении с ним каких-либо операций (например, при создании массива) Array. newlnstance не обладает информацией о типе, подразумеваемой kind. Метод не сможет выдать нужный результат, не требующий преобразования типа, а это приводит к выдаче предупреждения, с которым вам не удастся справиться.

Обратите внимание: для создания массивов в параметризованном коде рекомендуется использовать Array.newlnstance.

Если вместо массива создается другой контейнер, ситуация меняется:

//: generics/ListMaker.java

import java util.*;

public class ListMaker<T> {

List<T> create О { return new ArrayList<T>; } public static void main(String[] args) {

ListMaker<String> stringMaker= new ListMaker<String>; List<String> stringList = stringMaker.createO;

}

} ///:-

Компилятор не выдает предупреждений, хотя мы знаем, что <Т> в new ArrayList<T> внутри create удаляется — во время выполнения <Т> внутри класса нет, поэтому здесь его присутствие выглядит бессмысленным. Однако если вы попробуете применить эту идею на практике и преобразуете выражение в new ArrayList, компилятор выдаст предупреждение.

Но действительно ли этот элемент не имеет смысла? Что произойдет, если мы поместим в список несколько объектов, прежде чем возватим его?

II: generics/Fi1ledListMaker java

import java.util.*;

public class FilledListMaker<T> { List<T> create(T t, int n) {

List<T> result = new ArrayList<T>; for(int i = 0: i < n; i++)

result.add(t); return result:

}

public static void main(String[] args) {

FilledListMaker<String> stringMaker =

new Fi11edListMaker<String>; List<String> list = stringMaker.createC'Hello", 4): System.out.pri ntln(1i st);

}

} /* Output:

[Hello, Hello. Hello. Hello]

*lll:~

Хотя компилятор ничего не может знать о Т в create, он все равно способен проверить — на стадии компиляции — что заносимые в result объекты имеют тип Т и согласуются с ArrayList<T>. Таким образом, несмотря на то что стирание удаляет информацию о фактическом типе внутри метода или класса, компилятор все равно может проверить корректность использования типа в методе или классе.

Так как стирание удаляет информацию о типе внутри тела метода, на стадии выполнения особую роль приобретают границы — точки, в которых объект входит и выходит из метода. Именно в этих точках компилятор выполняет проверку типов и вставляет код преобразования. Рассмотрим следующий ^параметризованный пример:

  • Читать дальше
  • 1
  • ...
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • ...

Ебукер (ebooker) – онлайн-библиотека на русском языке. Книги доступны онлайн, без утомительной регистрации. Огромный выбор и удобный дизайн, позволяющий читать без проблем. Добавляйте сайт в закладки! Все произведения загружаются пользователями: если считаете, что ваши авторские права нарушены – используйте форму обратной связи.

Полезные ссылки

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

Подпишитесь на рассылку: