- •Содержание
- •1. Введение
- •2. Создание и уничтожение объектов
- •3. Методы, общие для всех объектов
- •4. Классы и интерфейсы
- •5. Замена конструкций на языке c
- •6. Методы
- •7. Общие вопросы программирования
- •8. Исключения
- •9. Потоки
- •10. Сериализация
- •11. Литература
- •Глава 1
- •Глава 2 Создание и уничтожение объектов
- •Рассмотрите возможность замены конструкторов статическими методами генерации.
- •Свойство синглтона обеспечивайте закрытым конструктором
- •Отсутствие экземпляров обеспечивает закрытый конструктор
- •Не создавайте дублирующих объектов
- •Уничтожайте устаревшие ссыпки (на объекты)
- •Остерегайтесь методов flnalize
- •Глава 3 Методы, общие для всех объектов
- •Переопределяя метод equals, соблюдайте общие соглашения
- •Переопределяя метод equals, всегда переопределяйте hashCode
- •Всегда переопределяйте метод toStrlng
- •Подумайте над реализацией интерфейса ComparabIe
- •Глава 4 Классы и интерфейсы
- •Сводите к минимуму доступность классов и членов
- •Предпочитайте постоянство
- •Предпочитайте компоновку наследованию
- •Предпочитайте интерфейсы абстрактным классам.
- •Используйте интерфейсы только для определения типов
- •Предпочитайте статические классы-члены нестатическим
- •Глава 5
- •3Амена констрvкций на языке с
- •3Аменяйте структуру классом
- •3Амеияйте объедииеиие иерархией классов
- •/* Помещает данные в одно из полей объединения ... */
- •3Аменяйте конструкцию enum классом
- •Указатель на функцию заменяйте кпассом и интерфейсом
- •Глава 6 Методы
- •Проверяйте достоверность параметров
- •При необходимости создавайте резервные копии
- •Тщательно проектируйте сигнатуру метода
- •Перегружая методы, соблюдайте осторожность
- •Возвращайте массив нулевой длины, а не null
- •Для всех открытых элементов арi пишите dос - комментарии
- •Глава 7 Общие вопросы программирования
- •Сводите к минимуму область видимости локальных переменных
- •Изучите библиотеки и пользуйтесь ими
- •Не используйте строку там, где более уместен иной тип
- •При конкатенации строк опасайтесь потери производительности
- •Для ссыпки на объект используйте его интерфейс
- •Соблюдайте осторожность при использовании машинно-зависимых методов
- •Соблюдайте осторожность при оптимизации
- •Глава 8 Исключения
- •Используйте исключения лишь в исключительных ситуациях
- •Применяйте обрабатываемые исключения для восстановления, для программных ошибок используйте исключения времени выполнения
- •Избегайте ненужных обрабатываемых исключений
- •Предпочитайте стандартные исключения
- •Инициируйте исключения, соответствующие абстракции
- •Для каждого метода документируйте все инициируемые исключения
- •В описание исключения добавляйте информацию о сбое
- •Добивайтесь атомарности методов по отношению к сбоям
- •Не игнорируйте исключений
- •Глава 9 Потоки
- •Синхронизируйте доступ потоков к совместно используемым изменяемым данным
- •Избегайте избыточной синхронизации
- •Никогда не вызывайте метод wait вне цикла
- •Не попадайте в зависимость от планировщика потоков
- •При работе с потоками документируйте уровень безопасности
- •Избегайте группировки потоков
- •Глава 10 Сериализация
- •Соблюдайте осторожность при реализации интерфейса SerializabIe
- •Рассмотрите возможность использования специализированной сериализованной формы
- •Метод readObject должен создаваться с защитой
- •При необходимости создавайте метод readResolue
Для ссыпки на объект используйте его интерфейс
В статье 25 дается совет: в качестве типа параметра указывать интерфейс, а не класс. В более общей формулировке: ссылаясь на объект, вы должны отдавать предпочтение не классу, а интерфейсу. Если есть подходящие типы интерфейсов, то параметры, возвращаемые значения, переменные и поля следует декларировать, указывая интерфейс. Единственный случай, когда вам нужно ссылаться на класс объекта,- при его создании. Для пояснения рассмотрим случай с классом Vector, который является реализацией интерфейса List. Возьмите за правило писать так
// Хорошо: указывается тип интерфейса.
List subscribers = new Vector();
а не так:
// Плохо: в качестве типа указан класс!
Vector subscribers = new Vector();
Если вы выработаете привычку указывать в качестве типа интерфейс, ваша про грамма будет более гибкой. Когда вы решите поменять реализацию, все, что для этого потребуется,- изменить название класса в конструкторе (или использовать другой статической метод генерации). Например, первую из представленных деклараций можно переписать следующим образом:
List subscribers = new ArrayList();
И весь окружающий код сможет продолжить работу. Код, окружающий эту декларацию, ничего не знал о прежнем типе, который реализовывал интерфейс. Поэтому он не должен заметить подмену декларации.
Однако нельзя упускать из виду следующее: если первоначальная реализация интерфейса выполняла некие особые функции, не предусмотренные общими соглашениями для этого интерфейса, и программный код зависел от этих функций, крайне важно, чтобы новая реализация интерфейса обеспечивала те же функции. Например, если программный код, окружавший первую декларацию, зависел от того обстоятельства, что Vector синхронизирован по отношению к потокам, то замена класса Vector на ArrayList будет некорректной.
145
Есть лишь несколько сложных приложений, которым необходим механизм отражения. В их число входят визуализаторы классов, инспекторы объектов, анализаторы программного кода и интерпретирующие встроенные системы. Отражение можно также использовать в системах вызова удаленных процедур (RPC system) с целью снижения потребности в компиляторах запросов. Если у вас есть сомнения, подпадает ли ваше приложение в одну из этих категорий, вероятнее всего, оно к ним не относится.
Вы можете без больших затрат использовать многие преимущества механизма отражения, если будете применять его в усеченном виде. Во многих программах, которым нужен класс, отсутствовавший на момент компиляции, для ссылки на него можно использовать соответствующий интерфейс или суперкласс (статья 34). Если это то, что вам нужно, вы можете сначала опосредованно создать экземпляры, а затем обращаться к ним обычным образом. используя их интерфейс или суперкласс. Если соответствующий конструктор, как часто бывает, не имеет параметров, вам даже не нужно обращаться к пакету java.lang.ref1ect - требуемые функции предоставит метод C1ass. newlnstance.
В качестве примера приведем программу, которая создает экземпляр интерфейса Set, чей класс задан первым аргументом командной строки. Остальные аргументы командной строки программа вставляет в полученный набор и затем распечатывает его. При выводе аргументов программа уничтожает дубликаты (первый аргумент игнорируется). Порядок печати аргументов зависит от того, какой класс указан в первом аргументе. Если вы указываете "java.util.HashSet", аргументы выводятся в произвольном порядке, если - "java.uti1.TreeSet", они печатаются в алфавитном порядке, поскольку элементы в наборе TreeSet сортируются.
// Опосредованное создание экземпляра с доступом
public static void main(String[] args) {
// Преобразует имя класса в экземпляр класса
Class сl = null;
try {
сl =Class.forName(args[0]); }
catch(ClassNotFoundException е) {
System.err.println("Class not found.”); // Класс не найден
System. exi t (1) ; }
// Создает экземпляр класса
Sets = null;
try {
s = (Set) cl.newlnstance();
catch(IllegalAccessExceptlon е) {
System.err.println("Class not accessible. "); // Доступ к классу невозможен System.exit(1);
148
} catch(InstantiationException е) {
System.err.println("Class not instantiable."); // Класс не позволяет создать экземпляр System.exit(1); }
// Проверяет набор
s.addAll(Arrays.asList(args).subList(1, args.length-1));
System.out.println(s); }
Хотя эта программа является всего лишь игрушкой, она показывает мощный прием. Этот код легко превратить в универсальную программу про верки наборов, которая проверяет правильность указанной реализации Set, активно воздействуя на один или несколько экземпляров и выясняя, выполняют ли они соглашения для интерфейса Set. Точно так же его можно превратить в универсальный инструмент для анализа производительности. Методика, которую демонстрирует эта программа, в действительности достаточна для создания полноценной инфраструктуры с предоставлением услуг (service provider framework) (статья 1). В большинстве случаев описанный прием это все, что нужно знать об отражении классов.
Этот пример иллюстрирует два недостатка системы отражения. Во-первых, во время его выполнения могут возникнуть три ошибки, которые, если бы не использовался механизм отражения, были бы обнаружены еще на стадии компиляции. Во-вторых, Для генерации экземпляра класса по его имени потребовалось Двадцать строк кода, тогда как вызов конструктора уложился бы ровно в одну строку. Эти недостатки, однако, касаются лишь той части программы, которая создает объект как экземпляр класса. Как только экземпляр создан, он неотличим от любого другого экземпляра Set. Благодаря этому значительная часть кода в реальной программе не поменяется от локального применения механизма отражения.
Приемлемым, хотя и редким вариантом использования отражения является разрушение зависимости класса от других классов, методов и полей, которые в момент выполнения могут отсутствовать, Это может пригодиться при написании пакета, который должен работать с различными версиями какого-либо другого пакета. Прием заключается в том, чтобы компилировать наш пакет для работы в минимальном окружении (обычно это поддержка самой старой версии), а доступ ко всем новым классам и методам осуществлять через механизм отражения. Чтобы это работало, необходимо Предпринимать правильные действия, когда в ходе выполнения программы обнаружится, что тот или иной новый класс или метод, к которому вы пытаетесь получить доступ, в данный момент отсутствует. Эти действия могут заключаться в применении каких-либо альтернативных средств, позволяющих достичь той же цели, или же в использовании усеченного функционала.
Подведем итоги. Отражение - это мощный инструмент, который необходим для решения определенных сложных задач системного программирования. Однако у него
149
есть много недостатков. Если вы пишите программу, которая должна работать с классами, неизвестными на момент компиляции, то вы должны по возможности использовать механизм отражения только для создания экземпляров отсутствовавших классов, а для доступа к полученным объектам следует применять некий интерфейс или суперкласс, который известен уже на стадии компиляции.