Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Джош Блох

.pdf
Скачиваний:
57
Добавлен:
08.03.2016
Размер:
27.13 Mб
Скачать

Глава 5 Средства обобщенного программирования (Generics)

метод для «уменьшения» списка с помощью функции. Если список содержит целые числа и функция складывает значения двух чисел, то метод reduce вернет сумму всех значений в списке. Если функция умножает два целых числа, метод вернет произведение значений спи­ ска. Если список содержит строковые значения и функция объединя­ ет две строки, то метод вернет строку, содержащую обе строки сое­ диненные последовательно. Помимо списков и функций метод reduce принимает начальное значение уменьшения, которое возвращается, если список пуст. (Начальное значение обычно идентифицирует эле­ мент для функции, где 0 обозначает сложение, 1 — умножение, и объединение строк.) Вот как может выглядеть код без использова­ ния средств обобщенного программирования:

//Применение метода reduce без использования средств обобщенного

//программирования и с недостатками параллелизма.

static Object reduce(List list, Function f, Object initVal) { synchronized(list) {

Object result = initVal; for (Object о : list)

result = f.apply(result, o); return result:

}

}

interface Function {

Object apply(Object arg1, Object arg2);

}

Предположим, вы прочитали статью 67, в которой сказано, что нельзя вызывать «внешний метод» из синхронизированной зоны. Так что вам нужно модифицировать метод reduce для копирования комментариев списка, удерживая замок, который позволит вам вы­ полнять уменьшение на копии. До релиза 1.5 мы бы использовали метод List’s toArray (который запирает список изнутри):

//Применение метода reduce без средств обобщенного

//программирования и недостатков параллелизма.

static Object reduce(List list, Function f, Object initVal) {

ПО

С татья 25

Object[] snapshot = list.toArrayO; // Locks list internally Object result = initVal;

for (Object о : snapshot)

result = f.apply(result, o); return result;

}

Если вы попытаетесь сделать это, используя средства обоб­ щенного программирования, то вы столкнетесь с проблемой, кото­ рую мы обсудили выше. Вот как будет выглядеть версия интерфейса Function со средствами обобщенного программирования:

interface Function<T> {

Т apply(T arg1, Т arg2);

}

А вот наивная попытка использовать средства обобщенного программирования в пересмотренной версии метода reduce. Э то обобщенный м етод (статья 27). Не обращайте внимания, если вам не понятна декларация. Для В данном случае надо изучить содержи­ мое метода:

//Наивная версия с использованием обобщенного программирования

//для метода reduce - не компилировать!

static <Е> Е reduce(List<E> list,

Function<E> f, E initVal) {

E[] snapshot = list.toArrayO;

// Locks list

E result = initVal;

 

for (E e : snapshot)

 

result = f.apply(result,

e);

return result;

 

}

 

Е с л и в ы п о п ы т а е т е с ь о т к о м п и л и р о в а т ь э т о т м е т о д , т о п о л у ч и т с я с л е д у ю щ а я о ш и б к а :

Reduce.java:12: incompatible types found : Object[], required; E[]

E[] snapshot = list.toArrayO; // Locks list

171

Глава 5 Средства обобщенного программирования (Generics)

«Не беда, — подумаете вы, — я передам массив Obj ect в массив Е»:

Е[] snapshot = (Е[]) list.toArray();

Это избавляет от ошибки, но теперь вы получите предупреждение:

Reduce.java:12: warning: [unchecked] unchecked cast found : Obj ect[], required: E[]

E[] snapshot - (E[]) list.toArray(); // Запирает список

л

Компилятор говорит, что он не может проверить безопасность передачи при запуске, так как не представляет, чем будет Е при за­ пуске. Помните, что информация о типах элементов стирается при запуске. Будет ли работать программа? Да, окажется, что она будет работать, но она не безопасна. Небольшое изменение — и будет вы­ ходить сообщение ClassCastException в строке, которая не содержит исключительной ситуации. Во время компиляции Е[] соответствует типу, который может быть St ring [ ], Integer[] или любым другим типом массива. Исполняемым типом является Obj ect [ j, и именно это опасно. Передача массивов нематериальных типов должна использо­ ваться только при определенных обстоятельствах (статья 26).

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

//Метод reduce с использованием списка и средств обобщенного

//программирования.

static <Е> Е reduce(List<E> list, Function<E> f, E initVal) { List<E> snapshot;

synchronized(list) {

snapshot = new ArrayList<E>(list);

}

E result = initVal; for (E e : snapshot)

result = f.apply(result, e); return result;

}

172

С татья 26

Эта версия гораздо более «многословна», нежели версия с ис­ пользованием массивов, но оно того стоит для успокоения. Теперь мы точно знаем, что у нас не будет ошибки ClassCastException при выполнении.

Подводя итоги, можно сказать, что у массивов и средств обоб­ щенного программирования различные правила для типов. Массивы ковариантны и материальны, обобщенные средства — инвариантны и используют механизм «стирания». Следствием этого становится то, что массивы при запуске обеспечивают безопасность, но не без­ опасность типов при компиляции. С обобщенными средствами все совсем наоборот, т.е. массивы и обобщенные средства не стоит ис­ пользовать вместе. Если вам все же придется сделать это и получить при этом ошибки или предупреждения при компиляции, то в первую очередь постарайтесь заменить массивы списками.

Поддерживайте обобщенные типы

Обычно не так сложно задать параметры в декларировании кол­ лекции и использовать обобщенные типы и методы, поставляемые JD K . Написание же своего собственного обобщенного типа несколь­ ко сложнее. Однако это стоит того, и вам следует этому научиться.

Рассмотрим простую реализацию стека из статьи 6:

// Коллекция на основе объекта - типичный кандидат для обобщения public class Stack {

private Object[] elements; private int size = 0;

private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack() {

elements = new Object[DEFAULT_INITIAL_CAPACITY];

}

public void push(0bject e) { ensureCapacity();

173

Глава 5 Средства обобщенного программирования (Generics)

elements[size++] = е;

}

public Object рор() { if (size == 0)

throw new EmptyStackException(); Object result = elements[-size];

elements[size] = null; // Eliminate obsolete reference return result;

}

public boolean isEmptyO { return size == 0;

}

private void ensureCapacity() { if (elements.length == size)

elements = Arrays.copyOf(elements, 2 * size + 1);

}

}

Данный класс — основной претендент для обобщения, другими словами, идеально подходит для применения преимуществ средств обобщенного программирования. В том виде, в каком оно сейчас есть, вы должны передавать объекты, которые выпадают из стека, и эти передачи будут выдавать ошибку при выполнении. Первый шаг — обобщения класса связан с тем, что можно было добавить один или несколько параметров при его декларировании. В нашем случае есть только один параметр, представляющий тип элемента стека и наиме­ нование этого параметра Е (статья 56).

Следующий шаг — надо заменить все использования типа Object соответствующим параметром типа и выполнить компиляцию полу­ чившейся программы:

// Начальная попытка обобщить стек - не компилировать! public class Stack<E> {

private E[] elements; private int size = 0;

private static final int DEFAULT_INITIAL_CAPACITY = 16;

174

С татья 26

public Stack() {

elements = new E[DEFAULT_INITIAL_CAPACITY];

}

public void push(E e) { ensureCapacity(); elements[size++] = e;

}

public E pop() { if (size==0)

throw new EmptyStackException(); E result = elements[-size];

elements[size] = null; // Eliminate obsolete reference return result;

}

// no changes in isEmpty or ensureCapacity

}

Вы получите по крайней мере одну ошибку или предупреждение в этом классе, и этот класс не исключение. К счастью, этот класс выдаст только одну ошибку:

Stack.java:8: generic array creation elements = new E[DEFAULT_INITIAL_CAPACITY];

Как объяснено в статье 25 , вы не можете создавать массив из не­ материальных типов, таких как Е. Эта проблема появляется каждый раз, когда вы пишете обобщенный тип, поддерживаемый массивом. Есть два варианта решения проблемы. Первое решение обходит за­ прет на создание обобщенных массивов: создайте массив для Object и передайте его обобщенному типу массива. Теперь вместо ошибки компилятор выдаст предупреждение. Такое использование разреше­ но, но в целом не является безопасным для типов:

Stack.java:8: warning: [unchecked] unchecked cast found : Object[], required: E[]

elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];

175

Глава 5 Средства обобщенного программирования (Generics)

Возможно, компилятор не сможет доказать, что ваша программа безопасна, но это можете сделать вы. Вам необходимо убедить себя, что непроверенная передача не нарушит безопасность типов програм­ мы. Массив в запросе (elements) хранится в закрытом поле и никогда не возвращается клиенту и не передается методу push (которые имеет тип Е), так что непроверенная передача не может причинить вреда.

После того как вы убедились, что непроверенная передача без­ опасна, скройте все предупреждения в насколько возможно узком диапазоне (статья 24). В этом случае конструктор содержит только непроверенное создание массива, так что вполне нормально скрыть предупреждения во всем конструкторе. После добавления аннотации Stack отлично компилируется и вы можете использовать его, не бо­ ясь явных передач и исключений ClassCastException:

//Элементы массива будут содержать только экземпляры Е из push(E).

//Достаточно удостовериться в безопасности типов, однако

//исполняемый тип массива будет не Е[]; a Object[]!

@SuppressWarnings(“unchecked”) public Stack() {

elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];

}

Второй способ избежать ошибок при создании обобщенных массивов — это изменить тип поля elements с Е[] на Object[]. Если вы так сделаете, то получите другую ошибку:

Stack.java:19: incompatible types

found : Object, required: E E result = elements[-size];

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

Stack.j ava:19: warning: [unchecked] unchecked cast

found : Object, required: E E result = (E) elements[-size];

176

С татья 26

Поскольку Е является нематериальным типом, то компилятор не может проверить его передачу при выполнении. Снова вы мо­ жете легко доказать себе, что непроверенная передача безопасна, поэтому нормально будет скрыть предупреждения. Следуя совету из 24-й статьи, мы скрываем предупреждения только для той части, которая содержит непроверенную передачу, а не на всем методе pop:

// Допустимое сокрытие предупреждений о непроверенном коде public Е рор() {

if (size == 0)

throw new EmptyStackException();

//push требует от элементов принадлежности к типу Е,

//поэтому передача верна @SuppressWarnings(“unchecked”) Е result =

(Е)elements[-size];

elements[size] = null; // Eliminate obsolete reference return result;

}

Какую из двух технологий выбрать для создания обобщенных массивов — это вопрос вашего вкуса. В основном все одинаково. Бо­ лее рискованно скрывать предупреждения о непроверенных переда­ чах при использовании типов массивов, нежели при использовании скалярных типов, что предполагает использование второго решения. Но в более реалистичном классе, чем Stack, вы, вероятнее всего, бу­ дете производить чтение из массива во многих местах кода, так что выбор второго решения потребует несколько передач Е вместо одно­ кратной передачи Е[ ], поэтому первое решение является более обще­ принятым [Naftalin07, 6.7].

Следующая программа демонстрирует использование обобщен­ ного класса Stack. Программа печатает свои аргументы командной строки в обратном порядке и переведет их в верхний регистр. Не тре­ буется явных передач для запуска метода String’s tolIpperCase на элементах, выпадающих из стека, и автоматически генерирует пе­ редачу для гарантии успешного выполнения:

177

Глава 5 Средства обобщенного программирования (Generics)

// Маленькая программа, использующая на практике обобщенный класс Stack public static void main(String[] args) {

Stack<String> stack = new Stack<String>(); for (String arg : args)

stack.push(arg); while (!stack.isEmpty())

System, out. println(stack. pop().tol)pperCase());

}

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

Подавляющее большинство обобщенных типов похожи на наш пример со Stack в том, что его параметры не имеют ограничений:

вы можете создать Stack<Object>, Stack<int[ ]>, Stack<List<St ring»

или Stack от любого другого типа, ссылающегося на объект. Обрати­ те внимание, что вы не можете создавать Stack примитивного типа: попытка создать Stack<int> или Stack<double> приведет к ошиб­ ке компиляции. Это фундаментальное ограничение, накладываемое системой обобщенного программирования в языке Java. Вы можете обойти ограничения, используя упаковываемые примитивные типы (статья 49).

Есть несколько обобщенных типов, которые ограничивают раз­ решенные значения своих параметров. Например, рассмотрим java. util.concurrent.DelayQueue, декларация которой выглядит следую­ щим образом:

class DelayQueue<E extends Delayed> implements BlockingQueue<E>;

Список параметров типа (<E extends Delayed>) требует, чтобы фактический параметр типа Е был подтипом java. util, concurrent.

178

С татья 27

Delayed. Это позволит реализации DelayQueue и ее клиентам исполь­ зовать преимущества методов Delayed на элементе DelayQueue без не­ обходимости явной передачи или риска ошибки ClassCastException. Параметр типа Е называется связанный параметр. Обратите внима­ ние, что отношение подтипа определено таким образом, что каждый тип является подтипом самого себя [JLS, 4.10], так что разрешается

создавать DelayQueue<Delayed>.

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

Поддерживайте обобщенные методы

Методы, подобно классам, получают свои преимущества в виде обобщения. Статические служебные методы — хорошие претенденты для этого. Все алгоритмические методы в Collection (такие, как bi-

narySearch и Sort) обобщены.

Написание обобщенных методов схоже с написанием обобщен­ ных типов. Рассмотрим метод, который возвращает объединение двух множеств:

// Использует необработанные типы - недопустимо! (статья 23) public static Set union(Set si, Set s2) {

Set result = new HashSet(sl); result.addAll(s2);

return result;

}

179

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]