Map (Ключевое слово extends и вопросительные знаки будут описаны позднее в этой главе.) Казалось бы, эта конструкция избыточна, а компилятор мог бы вычислить один из списков аргументов по-другому. В действительности сделать это он не может, но вычисление аргументов типов все же позволяет немного упростить код. Например, мы можем создать вспомогательную библиотеку с различными статическими методами, содержащими самые распространенные реализации различных контейнеров: //: net/mindview/uti1/New.java // Utilities to simplify generic container creation // by using type argument inference. package net mindview util; import java util *. public class New { public static } public static } public static } public static } public static } // Примеры: public static void main(String[] args) { Map } } ///- Примеры использования представлены в main() — вычисление аргументов типов устраняет необходимость в повторении списков параметров. Этот прием можно использовать в holding/MapOfList.java: public class SimplerPets { public static void main(String[] args) { Map } } ///.- Пример интересный, однако трудно сказать, насколько он эффективен в действительности. Человеку, читающему код, придется просмотреть дополнительную библиотеку и разобраться в ее коде. Возможно, вместо этого стоит оставить исходное (пусть и избыточное) определение — как ни парадоксально, этот вариант проще. Хотя, если в стандартную библиотеку Java будет добавлено некое подобие New.java, им можно будет пользоваться. Вычисление типов не работает ни в каких других ситуациях, кроме присваивания. Если передать результат вызова метода (скажем, New.mapO) в аргументе другого метода, компилятор //: generics/LimitsOfInference.java import typeinfo.pets.*; import java.util.*; public class LimitsOflnference { static void f(Map // f(New.mapO); II He компилируется } } ///:- Явное указание типа При вызове параметризованного метода также можно явно задать тип, хотя на практике этот синтаксис используется редко. Тип указывается в угловых скобках за точкой, непосредственно перед именем метода. При вызове метода в пределах класса необходимо ставить this перед точкой, а при вызове статических методов перед точкой указывается имя класса. Проблема, продемонстрированная в LimitsOflnference.java, решается при помощи следующего синтаксиса: //: generics/ExplicitTypeSpecification.java import typeinfo.pets.*: import java.util.*; i mport net.mi ndvi ew.uti1.*: public class ExplicitTypeSpecification { static void f(Map } } ///:- Конечно, при этом теряются преимущества от использования класса New для уменьшения объема кода, но дополнительный синтаксис необходим только за пределами команд присваивания. Параметризованные методы и переменные списки аргументов Параметризованные методы нормально сосуществуют с переменными списками аргументов: II: generics/GenericVarargs.java import java.util.*; public class GenericVarargs { public static result.add(item);-return result; public static void main(String[] args) { List Is = makeListC"ABCDEFFHIJKLMNOPQRSTUVWXYZ".split("")); System.out.printi n( Is); } } /* Output: [A] [А, В. C] [, А, В. C. D, E. F, F, H, I, J. K. L, M, N, 0. P. Q, R. S. T. U. V. W. X, Y, Z] *///•- Метод makeList() предоставляет ту же функциональность, что и метод java. util.Arrays.asList() из стандартной библиотеки. Использование параметризованных методов с Generator Генераторы хорошо подходят для заполнения Collection, и для выполнения этой операции было бы удобно создать параметризованный метод: //: generics/Genetcitors.java // Обобщенный метод заполнения коллекции import generics.coffee.*; import java.util.*; import net.mindview.util.*; public class Generators { public static fill(Collection coll .add(gen.nextO); return coll; } public static void main(String[] args) { Collection new ArrayList System.out.printin(c); Collection new ArrayList System.out.printO + \ "); } } /* Output: Americano 0 Latte 1 Americano 2 Mocha 3 1. 1. 2. 3. 5. 8. 13. 21. 34. 55. 89. 144. *///:- Обратите внимание на то, как параметризованный метод fill() применяется к контейнерам и генераторам как для типа Coffee, так и для Integer. Обобщенный генератор Следующий класс создает генератор для любого класса, обладающего конструктором по умолчанию. Для уменьшения объема кода в него также включен параметризованный метод для получения BasicGenerator: //: net/mindview/uti1/BasicGenerator java // Автоматическое создание Generator для класса // с конструктором по умолчанию (без аргументов) package net.mindview util; public class BasicGenerator public BasicGenerator(Class // Предполагается, что type является public-классом, return type.newlnstance(); } catch(Exception e) { throw new RuntimeException(e); } } // Получение генератора по умолчанию для заданного type: public static } } ///:- Класс предоставляет базовую реализацию, создающую объекты класса, который (1) является открытым (так как BasicGenerator определяется в отдельном пакете, соответствующий класс должен иметь уровень доступа public, не ограничиваясь пакетным доступом), и (2) обладает конструктором по умолчанию (то есть конструктором без аргументов). Чтобы создать один из таких объектов BasicGenerator, следует вызвать метод create() и передать ему обозначение генерируемого типа, параметризованный метод create() позволяет использовать запись BasicGenerator.create(MyType.class) вместо более громоздкой конструкции new BasicGenerator Для примера рассмотрим простой класс с конструктором по умолчанию: //: generics/CountedObject.java public class CountedObject { private static long counter = 0; private final long id = counter++; public long id() { return id; } public String toStringO { return "CountedObject " + id,} } ///:- Класс CountedObject отслеживает количество созданных экземпляров и включает его в выходные данные toString(). При помощи BasicGenerator можно легко создать Generator для CountedObject: //: generics/BasicGeneratorDemo java i mport net.mi ndvi ew.uti1.*. public class BasicGeneratorDemo { public static void main(String[] args) { Generator BasicGenerator create(CountedObject.class), for(int i = 0; i < 5; i++) System, out pri ntl n(gen. nextO); } } /* Output CountedObject 0 CountedObject 1 CountedObject 2 CountedObject 3 CountedObject 4 */// ~ Как видите, применение параметризованного метода снижает объем кода, необходимого для получения объекта Generator. Раз уж механизм параметризации Java все равно заставляет вас передавать объект Class, его можно заодно использовать для вычисления типа в методе create(). Упрощение работы с кортежами Используя вычисление аргументов типов в сочетании со static-импортом, можно оформить приведенную ранее реализацию кортежей в более универсальную библиотеку. В следующем примере кортежи создаются перегруженным статическим методом: //• net/mi ndvi ew/uti1/Tuple.java // Библиотека для работы с кортежами // с использованием вычисления аргументов типов package net mindview.util, public class Tuple { public static TwoTuple tuple(A а. В b) { return new TwoTuple(a, b). } public static ThreeTuple tuple(A а. В b. С с) { return new ThreeTuple(a, b, c): } public static FourTuple tuple(A а. В b. С с. D d) { return new FourTuple(a. b. c. d). } FiveTuple tuple(A а. В b, С с, D d. E e) { return new FiveTuple(a, b. c. d. e); } } ///:- А вот как выглядит обновленная версия TupleTest.java для тестирования Tuple.java: //: generics/TupleTest2.java import net.mindview.util.*; import static net.mindview.util.Tuple.*; public class TupleTest2 { static TwoTuple } static TwoTuple f2() { return tupleC'hi", 47); } static ThreeTuple } static FourTuple return tuple(new VehicleO, new AmphibianO, "hi", 47); } static FiveTuple } public static void main(String[] args) { TwoTuple } } /* Output (hi, 47) (hi, 47) (Amphibian@7d772e, hi, 47) (Vehicle@757aef, Amphibian@d9f9c3, hi. 47) (Vehicle@la46e30, Amphibian@3e25a5. hi. 47, 11.1) *///:- Обратите внимание: f() возвращает параметризованный объект TwoTuple, a f2() — ^параметризованный объект TwoTuple. Компилятор в данном случае не выдает предупреждения о f2(), потому что возвращаемое значение не используется в «параметризованном» стиле: в каком-то смысле проводится «восходящее преобразование» его до непараметризованного TwoTuple. Но, если попытаться сохранить результат f2() в параметризованном объекте TwoTuple, компилятор выдаст предупреждение. Вспомогательный класс Set Рассмотрим еще один пример использования параметризованных методов: математические операции между множествами. Эти операции удобно определить в виде параметризованных методов, используемых с различными типами: //: net/mi ndvi ew/uti1/Sets.java package net.mindview.util; import java.util.*; public class Sets { public static result.addAl1(b). return result; } public static Set } // Вычитание подмножества из надмножества-public static Set } // Дополнение -- все. что не входит в пересечение public static return difference(union(a. b). intersection^, b)); } } ///:- Первые три метода дублируют первый аргумент, копируя его ссылки в новый объект HashSet, поэтому аргументы Set не изменяются напрямую. Таким образом, возвращаемое значение представляет собой новый объект Set. Четыре метода представляют математические операции с множествами: union() возвращает объект Set, полученный объединением множеств-аргументов, intersection() возвращает объект Set с общими элементами аргументов, difference() вычисляет разность множеств, a complement) — объект Set со всеми элементами, не входящими в пересечение. Чтобы создать простой пример использования этих методов, мы воспользуемся перечислением, содержащим разные названия акварельных красок: //: generics/watercolors/Watercolors java package generics.watercolors; public enum Watercolors { ZINC. LEM0N_YELL0W. MEDIUM_YELLOW, DEEP_YELLOW, ORANGE. BRILLIANT_RED. CRIMSON. MAGENTA. ROSE_MADDER. VIOLET. CERULEAN_BLUE_HUE. PHTHALO_BLUE, ULTRAMARINE. COBALT_BLUE_HUE, PERMANENT_GREEN. VIRIDIAN_HUE. SAP_GREEN. YELL0W_0CHRE. BURNT_SIENNA. RAWJJMBER, BURNTJJMBER. PAYNES_GRAY. IVORY_BLACK } ///:- Для удобства (чтобы избежать уточнения всех имен) в следующем примере это перечисление импортируется статически. Мы используем EnumSet — новый инструмент Java SE5 для простого создания Set на базе перечисления. Статическому методу EnumSet.range() передаются первый и последний элементы диапазона, по которому строится множество: II: generics/WatercolorSets.java import generics.watercolors.*; import java.util.*; import static net.mindview.util.Print.*; import static net.mindview util.Sets *; import static generics.watercolors.Watercolors.*; public class WatercolorSets { public static void main(String[] args) { Set EnumSet.range(BRILLIANT_RED, VIRIDIAN_HUE); Set EnumSet range(CERULEAN_BLUE_HUE, BURNT_UMBER), printCsetl. " + setl); print("set2- " + set2); print("union(setl. set2)- " + union(setl. set2)); Set difference(setl. subset)). print("difference(set2. subset). " + difference(set2. subset)); print("complement(setl. set2) " + complement(setl. set2)); } } /* Output. setl. [BRILLIANT_RED. CRIMSON. MAGENTA. ROSE_MADDER, VIOLET. CERULEAN_BLUE_HUE. PHTHALO_BLUE. ULTRAMARINE. COBALT_BLUE_HUE. PERMANENT_GREEN. VIRIDIAN_HUE] set2- [CERULEAN_BLUE_HUE, PHTHALO_BLUE. ULTRAMARINE. COBALT_BLUE_HUE. PERMANENT_GREEN. VIRIDIAN_HUE. SAP_GREEN. YELL0W_0CHRE. BURNT_SIENNA. RAWJJMBER. BURNT_UMBER] union(setl. set2) [SAP_GREEN. ROSE_MADDER, YELL0W_0CHRE. PERMANENT_GREEN. BURNTJJMBER, COBALT_BLUE_HUE, VIOLET. BRILLIANT_RED. RAWJJMBER. ULTRAMARINE. BURNT_SIENNA. CRIMSON. CERULEAN_BLUE_HUE. PHTHALO_BLUE. MAGENTA. VIRIDIAN_HUE] intersection(setl. set2): [ULTRAMARINE. PERMANENT_GREEN. COBALT_BLUE_HUE. PHTHALO_BLUE. CERULEAN_BLUE_HUE. VIRIDIAN_HUE] difference(setl, subset): [ROSE_MADDER, CRIMSON. VIOLET. MAGENTA. BRILLIANT_RED] difference(set2. subset): [RAWJJMBER, SAP_GREEN. YELLOW J3CHRE, BURNT_SIENNA, BURNTJJMBER] complement(setl, set2): [SAP_GREEN. ROSE_MADDER. YELL0W_0CHRE. BURNTJJMBER, VIOLET. BRILLIANT_RED, RAW_UMBER. BURNT_SIENNA. CRIMSON. MAGENTA] *///:- В выходных данных показаны результаты выполнения каждой операции. В следующем примере представлены варианты вызова Sets.difference() для разных классов Collection и Map из java.util: //: net/mi ndvi ew/uti1/Contai nerMethodDi fferences.java package net.mindview.util; import java lang reflect *; import java.util.*, public class ContainerMethodDifferences { static Set Set } static void interfaces(Class > type) { System.out.print("Interfaces in " + type getSimpleNameO + ": "); List for(Class > с : type.getlnterfacesO) result.add(c.getSimpleName()); System.out.println(result); } static Set differencesass > superset, Class > subset) { System.out.pri nt(superset.getSimpleName() + " extends " + subset.getSimpleNameO + ", adds: "); Set methodSet(superset). methodSet(subset)); сотр.removeAll(object); // He показывать методы 'Object' System.out.println(comp); interfaces(superset), } public static void main(String[] args) { System.out.printlnC'Collection: " + methodSet(Collection.class)), interfaces(Collection.class); difference(Set.class. Collection.class); difference(HashSet.class. Set.class); difference(LinkedHashSet.class, HashSet.class); difference(TreeSet.class. Set.class); differences st. class. Col 1 ecti on.cl ass); difference(ArrayList.class, List.class); differences nkedLi st. class. List.class); difference(Queue.class. Collection.class); di fference(Pri ori tyQueue.class. Queue.class); System.out.println("Map: " + methodSet(Map.class)); difference(HashMap.class. Map.class); difference(LinkedHashMap.class. HashMap.class); difference(SortedMap.class. Map.class); difference(TreeMap.class, Map class); } } ///:- Анонимные внутренние классы Параметризация также может применяться к внутренним классам и анонимным внутренним классам. Пример реализации интерфейса Generator с использованием анонимных внутренних классов: //: generics/BankTeller.java II Очень простая имитация банковского обслуживания. import java.util.*; import net.mindview.util.*; class Customer { private static long counter = 1; private final long id = counter++; private Customer() {} public String toStringO { return "Customer " + id; } // Метод для получения объектов Generator: public static Generator public Customer nextO { return new CustomerO; } class Teller { private static long counter = 1; private final long id = counter++; private TellerO {} public String toStringO { return "Teller " + id; } // Синглетный объект Generator: public static Generator public Teller next О { return new TellerO; } }: } public class BankTeller { public static void serve(Teller t, Customer c) { System.out.printin(t + " обслуживает " + с); } public static void main(String[] args) { Random rand = new Random(47); Queue serve(tellers.get(rand.nextlnt(tellers.size())), c); } } /* Output: Teller 3 обслуживает Customer 1 Teller 2 обслуживает Customer 2 Teller 3 обслуживает Customer 3 Teller 1 обслуживает Customer 4 Teller 1 обслуживает Customer 5 Teller 3 обслуживает Customer 6 Teller 1 обслуживает Customer 7 Teller 2 обслуживает Customer 8 Teller 3 обслуживает Customer 9 Teller 3 обслуживает Customer 10 Teller 2 обслуживает Customer 11 Teller 4 обслуживает Customer 12 Teller 2 обслуживает Customer 13 Teller 1 обслуживает Customer 14 Teller 1 обслуживает Customer 15 *///•- И Customer, и Teller содержат приватные конструкторы, поэтому для создания их объектов пользователь вынужден использовать объекты Generator. Customer содержит метод generator(), который при каждом вызове создает новый объект Generator Поскольку метод generator() в Customer и объект Generator в Teller являются статическими, они не могут быть частью интерфейса, поэтому «обобщить» эту" конкретную идиому не удастся. Несмотря на это обстоятельство, она достаточно хорошо работает в методе fill(). Построение сложных моделей К числу важных преимуществ параметризации относится простота и надежность создания сложных моделей. Например, можно легко создать список (List) с элементами-кортежами: //: generics/TupleList.java // Построение сложных параметризованных типов путем объединения import java.util.*; import net.mindview util.*;