private Random rand = new Random(47), // Список создаваемых типов, производных от Pet public abstract List return typesO get(n) newInstanceO. } catchdnstantiationException e) { throw new RuntimeException(e), } catchdllegalAccessException e) { throw new RuntimeException(e). } } public Pet[] createArray(int size) { Pet[] result = new Pet[size], for(int i = 0. i < size, i++) result[i] = randomPetO. return result; } public ArrayList ArrayList } } /// ~ Абстрактный метод getTypes() поручает производному классу получение списка объектов Class. В качестве типа класса указан «любой производный от Pet», поэтому newlnstance() создает Pet без необходимости преобразования типа. Метод randomPet() осуществляет случайную выборку из List и использует полученные объекты Class для создания нового экземпляра данного класса вызовом Class. newlnstance(). Метод createArray() использует randomPet() для заполнения массива, a arrayList(), в свою очередь, использует createArray(). При вызове newlnstance() возможны два вида исключений, обрабатываемые в секциях catch за блоком try. Имена исключений достаточно хорошо объясняют суть проблемы (IllegalAccessException — нарушение механизма безопасности Java, в данном случае если конструктор по умолчанию объявлен private). Определяя субкласс PetCreator, достаточно предоставить список типов Pet, которые должны создаваться с использованием randomPet() и других методов. Метод getTypes() возвращает ссылку на статический объект List. Реализация с использованием forName() выглядит так: //• typei nfo/pets/ForNameCreator.java package typeinfo pets; import java util *; public class ForNameCreator extends PetCreator { private static List new ArrayList II Типы, создаваемые случайным образом, private static StringE] typeNames = { "typeinfo pets.Mutt", "typeinfo pets Pug", "typeinfo pets EgyptianMau", "typeinfo pets.Manx", "typeinfo.pets.Cymric", "typeinfo.pets.Rat", "typeinfo.pets.Mouse", "typeinfo pets.Hamster" }: @SuppressWarni ngs("unchecked") private static void loaderO { try { for(String name : typeNames) types.add( (Class extends Pet>)Class forName(name)). } catch(ClassNotFoundException e) { throw new RuntimeException(e); } } static { loaderO; } public List Метод loader() создает список List объектов Class с использованием метода Class.forName(). При этом может произойти исключение ClassNotFoundException, что вполне понятно — ведь ему передается строка, содержимое которой невозможно проверить на стадии компиляции. При ссылке на эти классы необходимо указывать имя пакета, которому они принадлежат (typeinfo). Для получения типизованного списка объектов Class требуется преобразование типа, что приводит к выдаче предупреждения на стадии компиляции. Метод loader() определяется отдельно и размещается в секции статической инициализации, потому что директива @SuppressWarnings не может располагаться прямо в секции статической инициализации. Для подсчета объектов Pet нам понадобится механизм подсчета их разных видов. Для этой цели идеально подойдет карта (Map), в которой ключами являются имена типов Pet, а значениями — переменные Integer с количеством Pet. Например, это позволит получать ответы на вопросы типа «сколько существует объектов Hamster?». При подсчете Pet будет использоваться ключевое слово instanceof: II: typeinfo/PetCount.java II Использование instanceof. import typeinfo.pets.*; import java util.*; import static net.mindview.util.Print.*, public class PetCount { static class PetCounter extends HashMap Integer quantity = get(type); if(quantity == null) put(type, 1); else put(type, quantity +1), } } public static void countPets(PetCreator creator) { PetCounter counter= new PetCounter(), for(Pet pet : creator.createArray(20)) { // Подсчет всех объектов Pet: printnb(pet.getClass().getSimpleNameO + " "), if(pet instanceof Pet) counter.count("Pet"); if(pet instanceof Dog) counter.count("Dog"); if(pet instanceof Mutt) counter count("Mutt"), if(pet instanceof Pug) counter countC'Pug"); if(pet instanceof Cat) counter.count("Cat"); if(pet instanceof Manx) counter count("EgyptianMau"); if(pet instanceof Manx) counter.count("Manx"); if(pet instanceof Manx) counter.count("Cymric"). if(pet instanceof Rodent) counter.count("Rodent"), if(pet instanceof Rat) counter count("Rat"); if(pet instanceof Mouse) counter.count("Mouse"); if(pet instanceof Hamster) counter.count("Hamster"), } // Вывод результатов подсчета. printO; print(counter); } public static void main(String[] args) { countPets(new ForNameCreator()); } } /* Output- Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric {Pug=3. Cat=9, Hamster=l, Cymric=7, Mouse=2, Mutt=3, Rodent=5, Pet=20, Manx=7, EgyptianMau=7, Dog=6, Rat=2} *///:- В countPets массив случайным образом заполняется объектами Pet с использованием PetCreator. Затем каждый объект Pet в массиве тестируется и подсчи-тывается при помощи instanceof. У ключевого слова instanceof имеется одно серьезное ограничение: объект можно сравнивать только с именованным типом, но не с объектом Class. Возможно, вам показалось, что в предыдущем примере перебор всех выражений instanceof выглядит неудобно и громоздко, и вы правы. Тем не менее автоматизировать этот процесс невозможно — создать из объектов Class список ArrayList и сравнивать объекты по очереди с каждым его элементом не получится (к счастью, существует альтернативное решение, но это попозже). Впрочем, особенно горевать по этому поводу не стоит — если вам приходится записывать множество проверок instanceof, скорее всего, изъян кроется в архитектуре программы. Использование литералов class Если записать пример PetCreator.java с использованием литералов class, программа во многих отношениях становится более понятной: //• typeinfo/pets/LiteralPetCreator.java // Using class literals package typeinfo pets, import java util.*; public class LiteralPetCreator extends PetCreator { // Блок try не нужен @SuppressWarnings("unchecked") public static final List Pet.class, Dog class. Cat.class, Rodent.class, Mutt class. Pug.class. EgyptianMau.class. Manx.class. Cymric class. Rat.class. Mous'e.class.Hamster.class)); // Типы для случайного создания: private static final List } public static void main(String[] args) { System.out.printin(types); } } /* Output: [class typeinfo pets.Mutt, class typeinfo.pets.Pug. class typeinfo.pets.EgyptianMau. class typeinfo pets Manx, class typeinfo.pets.Cymric, class typeinfo.pets.Rat, class typeinfo.pets.Mouse, class typeinfo.pets.Hamster] *///.- В будущем примере PetCount3.java контейнер Map заполняется всеми типами Pet (не только генерируемыми случайным образом), поэтому нам понадобился список allTypes. Список types представляет собой часть allTypes (создается вызовом List.subList()) со всеми типами Pet, поэтому он используется для случайного генерирования Pet. На этот раз при создании types блок try не нужен, так как необходимые проверки типов проводятся еще во время компиляции и исключения не возбуждаются, в отличие от метода Class.forName(). Теперь библиотека typeinfo.pets содержит две реализации PetCreator. Чтобы вторая реализация использовалась по умолчанию, мы можем создать //• typeinfo/pets/Pets java // Фасад для получения PetCreator по умолчанию package typeinfo.pets, import java util *, public class Pets { public static final PetCreator creator = new LiteralPetCreator(); public static Pet randomPetO { return creator.randomPetO: } public static Pet[] createArray(int size) { return creator.createArray(size). } public static ArrayList } } ///:- При этом также обеспечиваются косвенные вызовы randomPet(), createArray() и arrayList(). Поскольку PetCount.countPets() получает аргумент PetCreator, мы можем легко проверить работу LiteralPetCreator (через представленный фасад): II. typeinfo/PetCount2.java import typeinfo pets *. public class PetCount2 { public static void main(String[] args) { PetCount.countPets(Pets creator), } } /* (Выполните, чтобы увидеть результат) *///:-Результат будет таким же, как у PetCount.java. Динамический вызов instanceof Метод Class.islnstance() позволяет выполнить динамическую проверку типа объекта. Благодаря ему в примере PetCount.java наконец-то можно будет избавиться от нагромождения instanceof: II: typeinfо/PetCount3.java // Using isInstanceO import typeinfo.pets.*: import java.util.*, import net.mindview.util *; import static net.mindview util Print *; public class PetCount3 { static class PetCounter extends LinkedHashMap super(MapData map(LiteralPetCreator.allTypes, 0)). } public void count(Pet pet) { // Class.isInstanceO избавляет от множественных instanceof: for(Map Entry put(pair.getKey(). pair.getValueO + 1): } public String toStringO { StringBuilder result = new StringBuilder("{"); for(Map.Entry result.append(pai r.getKey().getSi mpleName()); result.append("="); result.append(pai r.getValue()); result.appendC, "); } result.delete(result.1ength0 -2, result.1ength()); result.append("J"); return result.toStringO; } } public static void main(String[] args) { PetCounter petCount = new PetCounterO; for(Pet pet : Pets.createArray(20)) { printnbCpet.getClassO.getSimpleNameO + " "); petCount.count(pet); } printO; print(petCount); } } /* Output: Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric {Pet=20, Dog=6. Cat-9. Rodent=5, Mutt-3. Pug=3. EgyptianMau=2, Manx=7, Cymric=5, Rat=2, Mouse=2, Hamster=l} *///:- Для подсчета всех разновидностей Pet контейнер PetCounter Map заполняется типами из LiteralPetCreator.allTypes. При этом используется класс net.mindview. util.MapData, который получает Iterable (allTypesList) и константу (0 в данном случае) и заполняет Map ключами из allTypes со значениями 0. Без предварительного заполнения Map будут подсчитаны только случайно сгенерированные типы, но не базовые типы (такие, как Pet и Cat). Как видите, метод islnstance() избавил нас от необходимости нагромождать конструкции с instanceof. Вдобавок теперь в программу можно легко добавить новые типы Pet — для этого следует просто изменить массив LiteralPet Creator, types; остальная часть программы не потребует правки (которая была бы неизбежна с операторами instanceof). Метод toStringO был перегружен для получения удобочитаемого вывода. Рекурсивный подсчет Контейнер Map в PetCount3.PetCounter был заполнен всеми классами Pet. Вместо предварительного заполнения карты мы также можем воспользоваться методом Class.isAssignableFrom() и создать обобщенный инструмент подсчета, не ограниченный подсчетом Pet: //: net/mi ndvi ew/uti1/TypeCounter.java // Подсчет экземпляров в семействе типов package net.mindview.util; import java.util.*; public class TypeCounter extends HashMap } public void count(Object obj) { Class > type = obj .getClassO; i f(!baseType.i sAssi gnableFrom(type)) throw new RuntimeException(obj + " incorrect type: " + type + should be type or subtype of " + baseType); countClass(type); } private void countClass(Class > type) { Integer quantity = get(type); put(type, quantity == null ? 1 : quantity +1); Class > superclass = type.getSuperclassO; if(superClass != null && baseType.i sAssi gnableFrom(superClass)) countClass(superClass); } public String toStringO { StringBuilder result = new StringBuilder("{"); for(Map.Entry } result.delete(result.1ength О -2, result. 1 ength О); result.append("}"); return result.toStringO; } } ///:- Метод count() получает Class для своего аргумента, а затем использует.isAs-signableFrom() для проверки принадлежности объекта к интересующей вас иерархии. Метод countClas^O сначала производит подсчет для точного типа класса, а затем, если baseType допускает присваивание из суперкласса, рекурсивно вызывает countClass() для суперкласса. II: typeinfo/PetCount4.java import typeinfo.pets.*, import net.mindview.util.*; import static net.mindview.util.Print.*; public class PetCount4 { public static void main(String[] args) { TypeCounter counter = new TypeCounter(Pet.class); for(Pet pet : Pets.createArray(20)) { printnb(pet.getClass().getSimpleNameO + " "); counter.count(pet); } printO: print(counter); _ Л } } /* Output: (Пример) Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric {Mouse=2, Dog=6, Manx=7, EgyptianMau=2, Rodent=5, Pug=3, Mutt=3. Cymric=5, Cat=9. Hamster=l, Pet=20, Rat=2} *///:- Как видно из результатов, подсчитываются как базовые, так и конкретные типы. Регистрация фабрик У построения объектов иерархии Pet есть один недостаток: каждый раз, когда в иерархию включается новый тип Pet, вы должны добавить его в LiteralPet-Creator.java. В системах с регулярным добавлением новых классов это может создать проблемы. Первое, что приходит в голову, — добавить в каждый класс статический инициализатор, который добавлял бы свой класс в некий список. К сожалению, статические инициализаторы вызываются только при первой загрузке класса, поэтому возникает «порочный круг»: класс отсутствует в списке генератора, поэтому генератор не может создать объект этого класса, соответственно, класс не загрузится и не будет помещен в список. По сути, вы вынуждены создать список вручную (разве что вы напишете утилиту, которая будет анализировать исходный код, а затем создавать и компилировать список). Вероятно, лучшее, что можно сделать, — это разместить список в одном централизованном, очевидном месте. Вероятно, лучшим местом для него будет базовый класс иерархии. В этом разделе мы также внесем другое изменение: создание объекта будет передано самому классу с использованием паттерна //: typeinfo/factory/Factory.java package typeinfo.factory: public interface Factory Обобщенный параметр T позволяет create() возвращать разные типы для разных реализаций Factory. Также при этом используется ковариантность возвращаемых типов. В следующем примере базовый класс Part содержит список объектов-фабрик. Фабрики типов, которые должны создаваться методом createRandom(), «регистрируются» в базовом классе включением в список partFactories: //: typeinfo/RegisteredFactories.java II Регистрация фабрик в базовом классе import typeinfo.factory.*: import java util.*: class Part { public String toStringO { return getClass().getSimpleName(); } static List static { // При вызове Collections addAllO выдается предупреждение // "unchecked generic array creation for varargs parameter" partFactories.add(new Fuel Filter FactoryO). partFactories add(new AirFilter FactoryO), partFactories add(new CabinAirFilter.FactoryO), partFactories add(new Oil Filter FactoryO); partFactories add(new FanBelt FactoryO); partFactories.add(new PowerSteeringBelt.Factory()), partFactories add(new GeneratorBelt FactoryO), } private static Random rand = new Random(47); public static Part createRandomO { int n = rand nextInt(partFactories sizeO), return partFactories get(n) createO; class Filter extends Part {} class Fuel Filter extends Filter { // Создание фабрики для каждого конкретного типа public static class Factory implements typeinfo factory.Factory public Fuel Filter createO { return new Fuel Filter О. } } } class AirFilter extends Filter { public static class Factory implements typeinfo factory Factory public AirFilter createO { return new AirFilterO. } } } class CabinAirFiIter extends Filter { public static class Factory implements typeinfo factory Factory return new CabinAirFilter(); } class Oil Filter extends Filter { public static class Factory implements typeinfo factory Factory public Oil Filter createO { return new OilFilterO; } } } class Belt extends Part {} class FanBelt extends Belt { public static class Factory implements typeinfo.factory.Factory public FanBelt createO { return new FanBeltO; } } } class GeneratorBelt extends Belt { public static class Factory implements typeinfo.factory.Factory return new GeneratorBeltO: } class PowerSteeringBelt extends Belt { public static class Factory implements typei nfо.factory.Factory return new PowerSteeringBeltO; } } } public class RegisteredFactories { public static void main(String[] args) { for(int i = 0; i < 10; i++) System.out.pri ntin(Part.createRandom()); } } /* Output: GeneratorBelt CabinAirFiIter GeneratorBelt AirFiIter PowerSteeringBelt CabinAirFiIter Fuel Filter PowerSteeringBelt PowerSteeringBelt Fuel Filter *///:- He все классы иерархии рассчитаны на создание экземпляров; в нашем примере классы Filter и Belt существуют исключительно в целях классификации. Экземпляры этих классов не создаются — только одного из их субклассов. Если класс Хотя для включения всех фабрик в список можно воспользоваться вызовом Collections.addAll(), компилятор выдает предупреждение, поэтому я вернулся к вызовам add(). Метод createRandom() случайным образом выбирает объект фабрики из partFactories и вызывает его метод create() для получения нового объекта Part. instanceof и сравнение Class При получении информации о типе объекта важно различать действие любой формы оператора instanceof (будь это сам оператор instanceof или метод isInstanceO ~~ они дают одинаковые результаты) и прямого сравнения объектов Class. Вот пример, который показывает, в чем их различия: //. typeinfo/FamilyVsExactType java // Различия между instanceof и class package typeinfo, import static net mindview util.Print.*, class Base {} class Derived extends Base {} public class FamilyVsExactType { static void test(Object x) { print ("Тестируем x типа " + x.getClassO); printC'x instanceof Base " + (x instanceof Base)), printC'x instanceof Derived "+ (x instanceof Derived)); print("Base.isInstance(x) "+ Base.class.islnstance(x)); print("Derived islnstance(x) " + Deri ved.class.i slnstance(x)); printC'x getClassO == Base.class " + (x.getClassO == Base.class)); printC'x.getClassO == Derived.class " + (x.getClassO == Deri ved. cl ass)). print("x.getClassO.equals(Base.class)) "+ (x getClassO .equals(Base.class))); printC'x getClassO equals (Deri ved. class)) " + (x.getClassO. equals (Deri ved. class))); } public static void main(String[] args) { test(new BaseO); test (new DerivedO), } } /* Output: Тестируем x типа class typeinfo.Base x instanceof Base true x instanceof Derived false Base islnstance(x) true Derived islnstance(x) false x getClassO == Base.class true x getClassO == Derived class false x getClassO equals(Base.class)) true x.getClassO equals(Derived.class)) false Тестируем x типа class typeinfo.Derived x instanceof Base true x instanceof Derived true Base.islnstance(x) true Derived.islnstance(x) true x.getClassO == Base.class false x.getClassO == Derived class true x.getClassO equals(Base.class)) false x.getClassO.equals(Derived.class)) true *///:- Метод test() осуществляет проверку типов полученного объекта, используя для этого обе формы оператора instanceof. Затем он получает ссылку на объект Class и использует операцию сравнения ссылок == и метод equals(), чтобы проверить объекты Class на эквивалентность. Пример доказывает справедливость утверждения о том, что действие оператора instanceof и метода islnstance() одинаково. Совпадают и результаты работы операции сравнения == и метода equals(). Но сами тесты приводят к разным заключениям. В соответствии с концепцией типа instanceof дает ответ на вопрос: «Объект принадлежит этому классу или производному от него?» С другой стороны, сравнение объектов Class оператором == не затрагивает наследования — либо тип точно совпадает, либо нет. Рефлексия: динамическая информация о классе Если вы не знаете точный тип объекта, RTTI сообщит вам его. Однако в этом случае существуют ограничения: тип должен быть известен еще во время компиляции программы, иначе определить его с помощью RTTI и сделать с этой информацией что-то полезное будет невозможно. Другими словами, компилятор должен располагать информацией обо всех классах, к которым вы затем хотели бы применить динамическое определение типов (RTTI). Сначала кажется, что это ограничение не столь существенно, но предположим, что у вас появилась ссылка на объект, который не находится в пространстве вашей программы. Более того, класс этого объекта недоступен во время ее компиляции. Например, вы получили последовательность байтов с диска или из сетевого соединения, и вам сказали, что эта последовательность представляет некоторый класс. Но компилятор ничего не знал об этом классе, когда обрабатывал вашу программу, как же его можно использовать? В традиционных средах программирования такая задача показалась бы далекой от реальности. Однако границы мира программирования все больше расширяются и мы все чаще встречаемся с такими ситуациями. Во-первых, такие возможности требуются для компонентного программирования, которое служит основой для систем Другая важная предпосылка поддержки динамической информации о классе — предоставление возможности создавать и использовать объекты на удаленных платформах. Этот механизм, называемый Класс Class (уже описанный в этой главе) поддерживает концепцию Важно понимать, что в механизме рефлексии нет ничего сверхъестественного. Когда вы используете рефлексию для работы с объектом неизвестного типа, виртуальная машина JVM рассматривает его и видит, что он принадлежит определенному классу (это делает и обычное RTTI), но, перед тем как проводить с ним некоторые действия, необходимо загрузить соответствующий объект Class. Таким образом, файл .class для класса этого объекта должен быть доступен JVM либо в сети, либо в локальной системе. Таким образом, истинное различие между традиционным RTTI и рефлексией состоит в том, что при использовании RTTI файл .class открывается и анализируется компилятором. Другими словами, вы можете вызывать методы объекта «нормальным» способом. При использовании рефлексии файл .class во время компиляции недоступен; он открывается и обрабатывается системой выполнения. Извлечение информации о методах класса Рефлексия редко используется напрямую; она существует в языке в основном для поддержки других возможностей, таких как сериализация объектов и компоненты JavaBeans. Однако существуют ситуации, в которых динамическая информация о классе просто незаменима. Для примера возьмем программу, выводящую на экран список методов некоторого класса. При просмотре исходного кода класса или его документации будут видны только те методы, которые были определены или переопределены именно //: typeinfo/ShowMethods.java // Использование рефлексии для вывода полного списка методов // класс, в том числе и определенных в базовом классе. // {Args: ShowMethods} import java.lang.reflect.*; import java.util.regex.*; import static net.mindview.util.Print.*; public class ShowMethods { private static String usage = "usage:\n" + "ShowMethods qualified.class.name\n" + "To show all methods in class or:\n" + "ShowMethods qualified.class.name word\n" + "To search for methods involving 'word'"; private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if(args.length < 1) { print(usage); System exit(O); } int lines = 0; try { Class > с = Class.forName(args[0]); Method[] methods = c.getMethods(); Constructor[] ctors = c.getConstructorsO; if(args.length == 1) { for(Method method . methods) print( p.matcher(method.toStri ng()).replaceAl 1С")); for(Constructor ctor • ctors) pri nt(p.matcher(ctor.toStri ng()).replaceAl1("") ); lines = methods.length + ctors.length, } else { for(Method method : methods) if (method. toStringO. indexOf(args[l]) != -1) { print( p.matcher(method.toStri ng О) replaceAll ("")); lines++; } for(Constructor ctor : ctors) if(ctor.toStringO.indexOf(args[l]) != -1) { print(p.matcher( ctor.toStri ng О).replaceAl 1 (" lines++; } } } catch(ClassNotFoundException e) { print("No such class: " + e): } } } /* Output: public static void main(String[]) public native int hashCodeO public final native Class getClassO public final void wait(long.int) throws InterruptedException public final void waitO throws InterruptedException public final native void wait(long) throws InterruptedException public boolean equals(Object) public String toStringO public final native void notifyО public final native void notifyAllO public ShowMethodsO *///:- Методы класса Class getMethods() и getConstructors() возвращают массивы объектов Method и Constructor, которые представляют методы и конструкторы класса. В каждом из этих классов есть методы для получения и анализа имен, аргументов и возвращаемых значений представляемых методов и конструкторов. Впрочем, также можно использовать простой метод toString(), как и сделано здесь, чтобы получить строку с полным именем метода. Остальная часть кода разбирает командную строку и определяет, подходит ли определенное выражение образцу для поиска (с использованием indexOf()), а после выделяет описатели имен классов. Результат, полученный от Class.forName(), не может быть известен во время компиляции, поэтому вся информация о сигнатуре методов становится доступной во время выполнения. Если вы тщательно изучите документацию по рефлексии из JDK, то увидите, что рефлексия позволяет установить необходимые аргументы и вызвать метод объекта, «абсолютно неизвестного» во время компиляции программы (чуть позже будут приведены соответствующие примеры). Скорее всего, вам эти возможности никогда не понадобятся, но сам факт их существования интересен. Приведенный выше результат был получен из командной строки java ShowMethods ShowMethods На экран выводится открытый (public) конструктор по умолчанию, хотя в тексте программы такой конструктор не определяется. Тот конструктор, что имеется теперь в классе, автоматически сгенерирован компилятором. Если вы после этого сделаете класс ShowMethods не открытым (удалите из его определения спецификатор доступа public, то есть предоставите ему доступ в пределах пакета), сгенерированный компилятором конструктор исчезнет из списка методов. Сгенерированный конструктор имеет тот же уровень доступа, что и его класс. Также интересно запустить программу в виде java ShowMethods java lang String с передачей дополнительного параметра char, int, String и т. п. Эта программа сэкономит вам немало времени при программировании, когда вы будете мучительно вспоминать, есть ли у этого класса определенный метод, если вам потребуется узнать, имеются ли у некоторого класса методы, возвращающие объекты Color, и т. д. Динамические посредники //. typeinfo/SimpleProxyDemo java import static net mindview.util Print *, interface Interface { void doSomethingO; void somethingElse(String arg). } class Real Object implements Interface { public void doSomethingO { printC'doSomething"); } public void somethingElse(String arg) { printC'somethingElse " + arg), } } class SimpleProxy implements Interface { private Interface proxied, public SimpleProxy(Interface proxied) { this.proxied = proxied, } public void doSomethingO { print("SimpleProxy doSomething"), proxied doSomethingO, } public void somethingElse(String arg) { print("SimpleProxy somethingElse " + arg); proxied.somethingElse(arg); } class SimpleProxyDemo { public static void consumer^Interface iface) { iface doSomethingO; iface somethingElseC'bonobo"); } public static void main(String[] args) { consumer(new RealObjectO), consumer(new SimpleProxy(new RealObjectO)), } } /* Output doSomething somethingElse bonobo SimpleProxy doSomething doSomething SimpleProxy somethingElse bonobo somethingElse bonobo */// ~ Поскольку consumer() получает Interface, он не знает, что ему передается — «настоящий» объект (RealObject) или посредник (Proxy), потому что оба типа реализуют Interface. Объект Proxy, находящийся между клиентом и «настоящим» объектом, выполняет операции, а затем вызывает идентичные методы RealObject. Посредник пригодится в любой ситуации, когда требуется отделить дополнительные операции от «настоящего» объекта, и особенно когда нужно легко переключаться из режима использования дополнительных операций в режим отказа от них (и наоборот — главной целью паттернов является инкапсуляция изменений, поэтому для оправдания их применения что-то должно изменяться). Допустим, вы хотите отслеживать вызовы методов RealObject, измерять затраты на эти вызовы, и т. д. Такой код не должен встраиваться в приложение, а посредник позволит легко добавить или убрать его по мере необходимости. // typeinfo/SimpleDynamicProxy java import java lang.reflect *. class DynamicProxyHandler implements InvocationHandler { private Object proxied; public DynamicProxyHandler(Object proxied) { this proxied = proxied, } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System out.printlnC'**** proxy. " + proxy.getClass() + method- " + method + ", args " + args); if(args != nul 1) for(Object arg : args) System.out.println(" " + arg); return method.invoke(proxied, args); } } class SimpleDynamicProxy { public static void consumer(Interface iface) { iface.doSomething(); i face.somethi ngElse("bonobo"); } public static void main(String[] args) { Real Object real = new Real ObjectО; consumer(real); // Вставляем посредника и вызываем снова: Interface proxy = (Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[]{ Interface.class }. new DynamicProxyHandler(real)); consumer(proxy); } } /* Output. doSomething somethingElse bonobo **** proxy: class SProxyO. method: public abstract void Interface.doSomething(), args: null doSomething **** proxy: class SProxyO. method: public abstract void Interface.somethi ngElse(java.1ang.Stri ng), args: [Ljava.1ang.Object.@42e816 bonobo somethingElse bonobo *///:- Динамический посредник создается вызовом статического метода Proxy. newProxyInstance(), которому должен передаваться загрузчик класса, список интерфейсов, которые должны реализовываться посредником (а не классов или абстрактных классов!), а также реализация интерфейса Invocation Handler. Динамический посредник перенаправляет все вызовы обработчику, поэтому конструктор обработчика обычно получает ссылку на «настоящий» объект для перенаправления ему запросов. Метод invoke() получает объект посредника на случай, если ему понадобится определить, откуда поступил запрос — впрочем, обычно это несущественно. Будьте внимательны при вызове методов посредника из invoke(), потому что вызовы через интерфейс перенаправляются через посредника. В общем случае вы выполняете опосредованную операцию, а затем используете Method.invoke() для перенаправления запроса опосредованному объекту с передачей необходимых аргументов. При этом некоторые вызовы методов могут отфильтровываться, а другие — проходить: //: typeinfo/SelectingMethods.java // Looking for particular methods in a dynamic proxy. import java.lang.reflect.*; import static net.mindview.util.Print.*; class MethodSelector implements InvocationHandler { private Object proxied; public MethodSelector(Object proxied) { this proxied = proxied; } public Object invoke(Object proxy, Method method. Objectd args) throws Throwable { i f(method.getName().equals("i nteresti ng")) print("Посредник обнаружил интересный метод"); return method.invoke(proxied. args); } } interface SomeMethods { void boringlO; void boring2(); void interesting(String arg). void boring3(); } class Implementation implements SomeMethods { public void boringlO { printC'boringl"); } public void boring2() { print("boring2"); } public void interesting(String arg) { print("interesting " + arg); } public void boring3() { print("boring3"); } } class SelectingMethods { public static void main(String[] args) { SomeMethods proxy= (SomeMethods)Proxy.newProxyInstance( SomeMethods.class.getClassLoader(). new Class[]{ SomeMethods.class }. new MethodSelector(new Implementation))); proxy.boringlO; proxy.boring2(); proxy.i nteresti ng("bonobo"); proxy.boring3(); } } /* Output: boringl boring2 Посредник обнаружил интересный метод interesting bonobo boring3 *///:- В данном случае мы просто проверяем имена методов, но с таким же успехом можно анализировать другие аспекты сигнатуры и даже значения аргументов. Вряд ли вам придется каждый день пользоваться динамическими посредниками, но они хорошо подходят для решения многих разновидностей задач. Объекты с неопределенным состоянием Если использовать для обозначения неопределенного состояния (то есть отсутствия) объекта встроенное значение null, то при каждом использовании ссылки придется проверять, не равна ли она null. Это быстро утомляет, а код получается излишне громоздким. Проблема заключается в том, что null не имеет собственного поведения, кроме выдачи NullPointerException при попытке выполнения с ним какой-либо операции. Иногда бывает полезно ввести понятие Было бы интересно представить себе язык программирования, автоматически создающий объекты с неопределенным состоянием, но на практике они применяются не так уж часто — иногда проверки null оказывается достаточно, иногда можно уверенно считать, что значение null вам не попадется, а иногда даже обработка аномальных ситуаций через NullPointerException является допустимой. Наибольшую пользу объекты с неопределенным состоянием приносят «вблизи от данных», представляя сущности в пространстве задачи. Простой пример: во многих системах имеется класс Person, а в коде возникают ситуации, когда объект не представляет конкретную личность (или, по крайней мере, информация о ней недоступна); при традиционном подходе вам следовало бы проверить ссылку null. Также можно воспользоваться объектом с неопределенным состоянием, но, даже несмотря на то, что такой объект будет отвечать на все сообщения, на которые отвечает «настоящий» объект, все равно потребуется способ проверки его на «определенность». Проще всего определить для этого специальный интерфейс: //. net/mindview/uti1/Null java package net.mindview util; public interface Null {} ///•- Это позволяет instanceof обнаруживать объекты с неопределенным состоянием и, что еще важнее, не требует включения метода isNull() во все классы (в конце концов, это фактически будет другим способом выполнения RTTI — так почему бы сразу не воспользоваться встроенными средствами?): // typeinfo/Person java // Класс с неопределенным состоянием объекта import net mindview util.*; class Person { public final String first; public final String last; public final String address; // И т д. public Person(String first. String last, String address){ this.first = first; 1 Идея принадлежит Бобби Вульфу (Bobby Woolf) и Брюсу Андерсону (Bruce Anderson). this.last = last; this.address = address; } public String toStringO { return "Person: " + first + " " + last + " " + address; } public static class NullPerson extends Person implements Null { private NullPerson^) { super("None". "None". "None"); } public String toStringO { return "NullPerson"; } } public static final Person NULL = new NullPersonO; } ///:- В общем случае объект с неопределенным состоянием является синглетным, поэтому он создается как экземпляр static final. Это возможно благодаря тому, что объект Person Представьте, что вы собираетесь открыть новое предприятие, но, пока вакансии еще не заполнены, в каждой должности Position можно временно хранить «заполнитель» — объект Person с неопределенным состоянием: //• typeinfo/Position.java class Position { private String title; private Person person; public Position(String jobTitle. Person employee) { title = jobTitle; person = employee; if(person == null) person = Person.NULL; } public Position(String jobTitle) { title = jobTitle; person = Person.NULL; } public String getTitleO { return title; } public void setTitle(String newTitle) { title = newTitle; } public Person getPersonО { return person; } public void setPerson(Person newPerson) { person = newPerson. if(person == null) person = Person.NULL; } public String toStringO { return "Position: " + title + " " + person; } } ///:- Превращать Position в объект с неопределенным состоянием не обязательно, потому что существование Person.NULL подразумевает неопределенность Position (возможно, позднее выяснится, что явная поддержка неопределенного состояния для Position нужна, и вы добавите ее, но в соответствии с одним из канонов экстремального программирования в начальный проект следует включить «простейшее решение, которое будет работать», и включать новые функции лишь по мере возникновения реальной необходимости). Теперь класс Staff может проверять объекты с неопределенным состоянием при заполнении вакансий: //: typeinfo/Staff.java import java util.*; public class Staff extends ArrayList public void addCString title, Person person) { add(new PositionCtitle, person)), } public void add(String.. titles) { for(String title : titles) add(new PositionCtitle)); } public StaffCString. titles) { add(titles); } public boolean positionAvailable(String title) { for(Position position : this) if(position.getTitleO.equals(title) && position.getPersonO == Person.NULL) return true; return false, } public void fillPosition(String title, Person hire) { for(Position position ; this) if(position.getTitleO.equals(title) && position.getPersonO == Person.NULL) { position.setPerson(hire); return; } throw new RuntimeException( "Position " + title + " not available"); } public static void main(String[] args) { Staff staff = new Staff("President", "СТО", "Marketing Manager", "Product Manager". "Project Lead", "Software Engineer", "Software Engineer", "Software Engineer", "Software Engineer", "Test Engineer", "Technical Writer"); sta ff.fi11Pos i t i on("Pres i dent". new PersonCMe", "Last". "The Top, Lonely At")); staff.fi11Position("Project Lead". new PersonC"Janet". "Planner". "The Burbs")); if(staff.positionAvailableC"Software Engineer")) staff.fi11Position("Software Engi neer". new PersonO'Bob". "Coder". "Bright Light City")); System.out.printin(staff). } } /* Output: [Position: President Person: Me Last The Top. Lonely At. Position. СТО NullPerson. Position: Marketing Manager NullPerson. Position: Product Manager NullPerson. Position. Project Lead Person: Janet Planner The Burbs. Position: Software Engineer Person: Bob Coder Bright Light City. Position: Software Engineer NullPerson. Position: Software Engineer NullPerson. Position- Software Engineer NullPerson. Position. Test Engineer NullPerson. Position: Technical Writer NullPerson] *///.- Обратите внимание: в некоторых местах нам по-прежнему приходится проверять объекты на определенное состояние, что принципиально не отличается от проверки null, но в других местах, скажем, при преобразованиях toStringO), лишние проверки не нужны; мы просто считаем, что ссылка на объект действительна. Если вместо конкретных классов используются интерфейсы, для автоматического создания объектов с неопределенным состоянием можно воспользоваться динамическим посредником. Допустим, имеется интерфейс Robot, определяющий имя и модель робота, а также список List //: typeinfo/Operation java public interface Operation { String description): void commandO; } ///.- Чтобы воспользоваться услугами робота, следует вызвать метод operations(): //: typeinfo/Robot.java import java.util *; import net.mindview util.*: public interface Robot { String nameO, String model О; List public static void test(Robot r) { if(r instanceof Null) System.out.pri ntin("[Nul1 Robot]"). System.out.println("Ha3BaHMe- " + r.nameO); System, out. pri nti n( "Модель " + r.model О). for(Operation operation : r.operationsO) { System, out. pri nti n(operati on description)): operation.commandO; } } } } ///.- При этом используется вложенный класс, выполняющий проверку. Теперь мы можем создать робота для уборки снега: // typeinfo/SnowRemovalRobot java import java util.*. public class SnowRemovalRobot implements Robot { private String name. public SnowRemovalRobot(String name) {this name = name,} public String nameО { return name; } public String model О { return "SnowBot Series 11". } public List new OperationO { public String description) { return name + " может убирать снег", } public void commando { System out.println(name + " убирает снег"), } }. new OperationO { public String descriptionO { return name + " может колоть лед". } public void commando { System out println(name + " колет лед"); } ь new OperationO { public String descriptionO { return name + " может чистить крышу"; } public void commando { System out.println(name + " чистит крышу"), } } ); } public static void main(String[] args) { Robot Test.test(new SnowRemovalRobot("SIusher")); } } /* Output: Название: Slusher Модель: SnowBot Series 11 Slusher может убирать снег Slusher убирает снег Slusher может колоть лед-Si usher колет лед Slusher может чистить крышу Slusher чистит крышу *///:- Предполагается, что существуют разные типы роботов, и для каждого типа Robot объект с неопределенным состоянием должен делать что-то особенное — в нашем примере выдавать информацию о конкетном типе Robot, представленном объектом. Эта информация перехватывается динамическим посредником: //: typeinfo/NullRobot.java // Использование динамического посредника для создания // объекта с неопределенным состоянием import java.lang.reflect *, import java util *, import net.mindview util.*; class NullRobotProxyHandler implements InvocationHandler { private String null Name; private Robot proxied = new NRobotO. NullRobotProxyHandler(CIass extends Robot> type) { nullName = type getSimpleNameO + " NullRobot"; } private class NRobot implements Null, Robot { public String nameO { return nullName; } public String model О { return nullName; } public List return Col lections.emptyList(); } } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(proxied, args); public class NullRobot { public static Robot newNullRobot(CIass extends Robot> type) { return (Robot)Proxy.newProxyInsta nee( NullRobot class.getClassLoaderO, new Class[]{ Null.class, Robot.class }, new Nul1RobotProxyHandler(type)); } public static void main(String[] args) { Robot[] bots = { new SnowRemova1 Robot("SnowBee"), newNul1 Robot(SnowRemova1 Robot.class) }: for(Robot bot : bots) Robot.Test.test(bot), } } /* Output-Название: SnowBee Модель: SnowBot Series 11 SnowBee может убирать снег SnowBee убирает снег SnowBee может колоть лед SnowBee колет лед SnowBee может чистить крышу SnowBee чистит крышу [Null Robot] Название. SnowRemova1 Robot NullRobot Модель: SnowRemovalRobot NullRobot *///.- Каждый раз, когда вам требуется объект Robot с неопределенным состоянием, вы вызываете newNuURobotQ и передаете тип Robot, для которого создается посредник. Посредник выполняет требования о поддержке интерфейсов Robot и Null, а также предоставляет имя опосредованного типа. Интерфейсы и информация о типах Одной из важных целей ключевого слова interface является изоляция компонентов и сокращение привязок. Использование интерфейсов вроде бы позволяет добиться этой цели, однако RTTI позволяет обойти ограничения — интерфейсы не обеспечивают стопроцентной изоляции. Начнем следующий пример с интерфейса: //: typeinfo/interfacea/A.java package typeinfo.interfacea; public interface A { void f(); } ///:- Затем интерфейс реализуется, и выясняется, что можно «в обход» добраться до фактического типа реализации: //• typei nfo/1nterfaceVi olati on.java // Интерфейс можно обойти import typeinfo.interfacea.*; class В implements A { public void f() {} public void g() {} } public class InterfaceViolation { public static void main(String[] args) { A a = new BO; a.fO; // a.gO; // Ошибка компиляции System.out.pri ntln(a.getClass().getName()); if(a instanceof B) { В b = (B)a; b.gO; } } } /* Output: В *///:- Используя RTTI, мы выясняем, что объект а реализован в форме В. Преобразование к типу В позволяет вызвать метод, не входящий в интерфейс А. Все это абсолютно законно и допустимо, но, скорее всего, вы предпочли бы оградить клиентских программистов от подобных выходок. Казалось бы, ключевое слово interaface должно защищать вас, но на самом деле этого не происходит, а факт использования В для реализации А становится известен любому желающему. Одно из возможных решений: просто скажите программистам, что если они будут использовать фактический класс вместо интерфейса, то пускай сами разбираются со всеми возникающими проблемами. Вероятно, во многих случаях этого достаточно, но если «вероятно» вас не устраивает — можно применить более жесткие меры. Проще всего установить для реализации пакетный уровень доступа, чтобы она оставалась невидимой для клиентов за пределами пакета: //. typeinfo/packageaccess/HiddenC.java package typeinfo.packageaccess; import typeinfo interfacea.*; import static net mindview util.Print *. class С implements A { public void f() { print("public С f()"), } public void g() { printCpublic С g()"); } void u() { print ("package C.uO"); } protected void v() { print("protected С v()"). } private void wO { printC'private C.wO"), } } public class HiddenC { public static A makeAO { return new CO; } } ///:- Единственная открытая (public) часть пакета, HiddenC, выдает интерфейс А при вызове. Интересно отметить, что, даже если makeA() будет возвращать С, за пределами пакета все равно удастся использовать только А, потому что имя С недоступно. Попытка нисходящего преобразования к С тоже завершается неудачей: II: typeinfo/Hiddenlmplementation.java // Пакетный доступ тоже можно обойти import typeinfo.interfacea.*; import typeinfo.packageaccess *; import java.lang.reflect *; public class Hiddenlmplementation { public static void main(String[] args) throws Exception { . A a = Hi ddenC. makeAO; a.fO; System.out.pri ntin(a.getClass О.getName()); // Ошибка компиляции, символическое имя 'С' не найдено /* if(a instanceof С) { С с = (С)а; с.дО; } */ // Однако рефлексия позволяет вызвать д(): callHiddenMethod(a, "д"); // ... И даже еще менее доступные методы! callHiddenMethod(a, "и"); са11 Hi ddenMethod С а, "v"); callHiddenMethod(a, "w"); } static void call HiddenMethod(Object a, String methodName) throws Exception { Method g = a getClassO getDeclaredMethod(methodName), g.setAccessible(true). g.invoke(a), } } /* Output public C.fO typeinfo.packageaccess С public С gO package С uO protected C.vO private C.wO */// ~ Как видите, рефлексия позволяет вызвать Можно подумать, что проблема решается распространением только откомпилированного кода, но и это не так. Достаточно запустить javap — декомпилятор, входящий в JDK. Командная строка выглядит так: javap -private С Флаг -private означает, что при выводе должны отображаться все члены, даже приватные. Таким образом, любой желающий сможет получить имена и сигнатуры приватных методов и вызвать их. А если реализовать интерфейс в виде приватного внутреннего класса? Вот как это выглядит: //: typeinfo/Innerlmplementation java // Приватные внутренние классы не скрываются от рефлексии import typeinfo interfacea *; import static net mindview.util Print.*; class InnerA { private static class С implements A { public void f() { printC'public C.fO"); } public void gO { printC'public C.gO"); } void uO { print("package C.uO"); } protected void v() { print ("protected C.vO"), } private void w() { printC'private С w()"). } } public static A makeAO { return new CO; } } public class Innerlmplementation { public static void main(String[] args) throws Exception { A a = InnerA makeAO; a f(). System out. pri ntl n(a getClassO .getNameO); // Рефлексия все равно позволяет добраться до приватного класса: Hiddenlmplementation callHiddenMethod(a. "g"); HiddenImplementation.callHiddenMethod(a, "u"), HiddenImplementation.callHiddenMethod(a, "v"), HiddenImplementation.callHiddenMethod(a. "w"); } public С f() InnerASC public С g() package С u() protected С v() private С w() */// ~ He помогло. Как насчет анонимного класса? // typeinfo/AnonymousImplementation java // Анонимные внутренние классы тоже не скрыты от рефлексии import typeinfo.interfacea *. import static net.mindview util Print *, class AnonymousA { public static A makeAO { return new AO { public void fO { printCpublic С f()"), } public void gO { printCpublic С g()n); } void uO { print (package C.uO"), } protected void vO { print ("protected C.vO"). } private void wО { printOprivate С wO"). } public class AnonymousImplementation { public static void main(String[] args) throws Exception { A a = AnonymousA.makeAO; a.fO; System.out.pri ntin(a.getCl ass О.getName()). // Рефлексия все равно позволяет добраться до приватного класса. Hiddenlmplementation callHiddenMethod(a, "g"), Hiddenlmplementation.call HiddenMethod(a, "u"); Hi ddenlmpl ementati on callHiddenMethod(a, Y); HiddenImplementation.callHiddenMethod(a, "w"); } } /* Output: public C.fO AnonymousA!1 public C.gO package C.uO protected C.vO private C.wO *///•- Похоже, не существует никакого способа предотвратить обращение и вызов методов с уровнем доступа, отличным от public, посредством рефлексии. Сказанное относится и к полям данных, даже к приватным: //. typeinfo/ModifyingPrivateFields.java import java lang reflect *; class WithPrivateFinalField { private int i = 1. private final String s = "I'm totally safe"; private String s2 = "Am I safe?", public String toStringO { return Hi = " + i + ", " + s + ", " + s2, } } public class ModifyingPrivateFields { public static void main(String[] args) throws Exception { WithPrivateFinalField pf = new WithPrivateFinalField(), System out println(pf); Field f = pf getClassO getDeclaredFieldC'i"), f.setAccessible(true), System.out.printlnC'f.getlnt(pf). " + f getlnt(pf)). f setlnt(pf. 47); System out.println(pf); f = pf getClassO getDeclaredFieldC's"). f.setAccessible(true); System.out.printlnC'f.get(pf): " + f.get(pf)); f.set(pf. "No, you're not!"); System.out.println(pf); f = pf. getClassO. getDeclaredField("s2"); f.setAccessible(true); System.out.printlnC'f.get(pf). " + f.get(pf)), f.set(pf, "No, you're not!"); System.out.println(pf), } } /* Output: i = 1, I'm totally safe. Am I safe? f getlnt(pf): 1 i =47, I'm totally safe. Am I safe? f.get(pf): I'm totally safe i =47, I'm totally safe, Am I safe? f.get(pf): Am I safe? i =47, I'm totally safe. No, you're not! *///:- Впрочем, final-поля защищены от изменений. Система времени выполнения спокойно воспринимает любые попытки их изменения, но при этом ничего не происходит. В действительности все эти нарушения уровня доступа не так уж страшны. Если кто-то захочет вызывать методы, которым вы назначили приватный или пакетный доступ (тем самым ясно показывая, что вызывать их не следует), вряд ли он станет жаловаться на то, что вы изменили некоторые аспекты этих методов. С другой стороны, «черный ход» к внутреннему устройству класса позволяет решить некоторые проблемы, нерешаемые другими средствами, и в общем случае преимущества рефлексии неоспоримы. Резюме Динамическое определение типов (RTTI) позволяет вам получить информацию о точном типе объекта тогда, когда у вас для него имеется лишь ссылка базового типа. Таким образом, оно открывает широкие возможности для злоупотреблений со стороны новичков, которые еще не поняли и не успели оценить всю мощь полиморфизма. У многих людей, ранее работавших с процедурными языками, возникает сильное желание разбить свою программу на множество конструкций switch при помощи RTTI. Однако при этом они лишаются всех преимуществ полиморфизма, относящихя к разработке программы в целом и ее дальнейшей поддержке. В Java рекомендуется использовать именно полиморфные методы, а к услугам RTTI следует прибегать только в крайнем случае. Впрочем, при использовании полиморфных методов требуется полный контроль над базовым классом, поскольку в некоторой точке программы, после наследования очередного класса, вы можете обнаружить, что базовый класс не содержит нужного вам метода, и тогда RTTI вас выручит: при наследовании вы расширяете интерфейс класса, добавляя в него новые методы. Особенно верно это при использовании в качестве базовых классов библиотек, которые вы не можете изменить. Далее в своем коде в подходящий момент вы обнаруживаете новый тип и вызываете для него нужный метод. Такой подход не противоречит основам полиморфизма и расширяемости программы, так как добавление в программу нового типа не требует изменения бесчисленного множества конструкций switch. Но чтобы извлечь пользу из дополнительной функциональности нового класса, придется использовать RTTI. Включение некоторого метода в базовый класс будет выгодно только одному производному классу, который действительно реализует его, но все остальные производные классы будут вынуждены использовать для этого метода какую-либо бесполезную «заглушку». Интерфейс базового класса «размывается» и раздражает тех, кому приходится переопределять ненужные абстрактные методы при наследовании от базового класса. Например, рассмотрим иерархию классов, представляющих музыкальные инструменты. Предположим, что вы хотите прочистить мундштуки духовых инструментов своего оркестра. Конечно, можно поместить в базовый класс Instrument (общее представление музыкального инструмента) еще один метод с lea rS pi tVa Ive () (прочистка мундштуков), но тогда получится, что и у синтезатора, и у барабана есть мундштук! С помощью RTTI можно получить гораздо более верное решение данной задачи, поскольку этот метод уместно поместить в более конкретный класс (например, в класс Wind, базовый для всех духовых инструментов). Однако еще более разумным стало бы включение в класс Instrument метода preparelnstrument() (подготовить инструмент к игре), который подошел бы всем инструментам без исключения. На первый взгляд можно было бы ошибочно решить, что в данном случае без RTTI не обойтись. Наконец, иногда RTTI решает проблемы производительности. Если ваш код использует полиморфизм по всем правилам, но один из объектов чрезвычайно непродуктивно обрабатывается кодом, предназначенным для базового типа, то для этого объекта можно сделать исключение, определить его точный тип с помощью RTTI и работать с ним более производительно. Однако ни в коем случае не следует писать программы, ориентируясь только на их эффективность, как бы соблазнительно это ни было. Сначала надо получить Мы также видели, что рефлексия открывает перед программистом множество новых возможностей и делает возможным более динамичный стиль программирования. Пожалуй, динамическая природа рефлексии кому-то покажется пугающей. Для тех, кто привык к безопасной статической проверке типов, сама возможность выполнения действий, правильность которых проверяется только на стадии выполнения, а для выдачи информации используются исключения, выглядит шагом в неверном направлении. Некоторые доходят до утверждений, будто сама возможность исключения на стадии выполнения свидетельствует о том, что такого кода лучше избегать. На мой взгляд, чувство безопасности весьма иллюзорно — неожиданности и исключения возможны всегда, даже если программа не содержит блоков try и спецификации исключений. Предпочитаю думать, что существование логически целостной модели выдачи информации об ошибках Параметризация Одним из механизмов обеспечения универсальности кода в объектно-ориен-тированных языках является полиморфизм. Например, вы можете написать метод, который получает в аргументе объект базового класса, а затем использует этот метод с любым классом, производным от него. Метод становится чуть более универсальным, а область его применения расширяется. Это относится и к классам — использование базового класса вместо производного обеспечивает дополнительную гибкость. Конечно, наследование возможно только для классов, не являющихся final. Впрочем, иногда даже рамки одной иерархии оказываются слишком тесными. Если в аргументе метода передается интерфейс вместо класса, то ограничения ослабляются и в них включается все, что реализует данный интерфейс, — в том числе и классы, которые еще не были созданы. Это дает программисту-клиенту возможность реализовать интерфейс, чтобы соответствовать требованиям вашего класса или метода. Таким образом, интерфейсы позволяют выходить за рамки иерархий классов, если только у вас имеется возможность создать новый класс. Но в некоторых случаях даже интерфейсы оказываются недостаточно гибкими. Интерфейс требует, чтобы ваш код работал в этом конкретном интерфейсе. Если бы было можно указать, что ваш код работает «с некоторым не заданным типом», а не с конкретным интерфейсом или классом, программа приобрела бы еще более общий характер. В этом и состоит концепция