Главная » Java, Советы » Предпочитайте статические классы-члены нестатическим

0

Класс называется вложенным (nested), если он определен внутри другого класса.

Вложенный класс должен создаваться только для того, чтобы обслуживать окружающий его класс. Если вложенный класс оказывается полезен в каком-либо ином контексте, он должен стать классом верхнего уровня. Существуют четыре категории вложенных классов: статический класс-член (static member class), нестатический класс-член (nonstatic member class), анонимный класс (anonymoиs class) и локальный класс (local class). За исключением первого, остальные категории классов называются внутренними (inner class). В этой статье рассказывается о том, когда и какую категорию вложенного класса нужно использовать и почему.

Статический класс-член – это простейшая категория вложенного класса. Лучше всего рассматривать его как обычный класс, который декларирован внутри другого класса и имеет доступ ко всем членам окружающего его класса, даже к закрытым. Статический класс-член является статическим членом своего внешнего класса и подчиняется тем же правилам доступа, что и остальные статические члены. Если он декларирован как закрытый, доступ к нему имеет лишь окружающий его класс, и т. д.

В одном из распространенных вариантов статический класс-член используется как открытый вспомогательный класс, который пригоден для применения, только когда есть внешний класс. Например, рассмотрим перечисление, описывающее операции, которые может выполнять калькулятор (статья 21). Класс Operation должен быть открытым статическим классом-членом класса Calculator. Клиенты класса Calculator могут ссылаться на операции, выполняемые калькулятором, используя такие имена, как Calculator.Ореration.PLUS или Calculator.Ореration.MINUS. Этот вариант приводится ниже.

С точки зрения синтаксиса, единственное различие между статическими и нестатическими классами-членами заключается в том, что в декларации статических Классов-членов присутствует модификатор static. Несмотря на свою синтаксическую схожесть, эти две категории вложенных классов совершенно разные. Каждый экземпляр нестатического члена-класса неявным образом связан с содержащим его

экземпляром класса-контеинера (enclosing instance). Из метода в экземпляре нестатического класса-члена можно вызывать методы содержащего его экземпляра, либо, используя специальную конструкцию this [JLS 15.8.4], можно получить ссылку на включающий экземпляр. Если экземпляр вложенного класса может существовать в отрыве от экземпляра внешнего класса, то вложенный класс не может быть нестатическим классом-членом: нельзя создать экземпляр нестатического класса-члена, не создав включающего его экземпляра.

Связь между экземпляром нестатического класса-члена и включающим его эк-

земпляроМ устанавливается при создании первого, и после этого поменять ее нельзя. Обычно эта связь задается автоматически путем вызова конструктора нестатического класса-члена из экземпляра метода во внешнем классе. Иногда можно установить связь вручную, используя выражение enclosinglnstance.newMemberClass(args). Как можно предположить, эта связь занимает место в экземпляре нестатического класса-члена и увеличивает время его создания.

Нестатические классы-члены часто используются для определения адаптера (Adapter) [Сатта95, стр. 139], при содействии которого экземпляр внешнего класса воспринимается своим внутренним классом как экземпляр некоторого класса, не имеющего к нему отношения. Например, в реализациях интерфейса Мар нестатические классы-члены обычно применяются для создания представлении /(оллекции (collection view), возвращаемых методами keySet, entrySet и values интерфейса Мар. Аналогично, в реализациях интерфейсов коллекций, таких как Set и List, нестатические классы-члены обычно используются для создания итераторов:

// Типичный вариант использования нестатического класса-члена

public class MySet extends AbstractSet {

// Основная часть класса опущена

publiC Iterator iterator() {

return new Mylterator(); }

private class Mylterator implements Iterator {

}

}

Если вы объявили класс-член, которому не нужен доступ к экземпляру содержащего его класса, не забудьте поместить в соответствующую декларацию модификатор static с тем, чтобы сделать этот класс-член статическим. Если вы не установите модификатор static, каждый экземпляр класса будет содержать ненужную ссылку на внешний объект. Поддержание этой связи требует и времени, и места, но не приносит никакой пользы. Если же вам когда-нибудь потребуется разместить в памяти экземпляр этого класса без окружающего его экземпляра, вы не сможете это сделать, так как нестатические классы-члены обязаны иметь окружающий их экземпляр. 88

Закрытые статические классы-члены обычно должны представлять составные части объекта, доступ к которым осуществляется через внешний класс. Например, рассмотрим экземпляр класса Мар, который сопоставляет ключи и значения. Внутри экземпляра Мар для каждой пары ключ/значение обычно создается объект Entry. Хотя каждая такая запись ассоциируется со схемой, клиенту не надо обращаться к собственным методам этой записи (geyKey, getValue и setValue). Следовательно, использовать нестатические классы-члены для представления отдельных записей в схеме Мар было бы расточительностью, самое лучшее решение – закрытый статический класс-член. Если в декларации этой записи вы случайно пропустите модификатор statlc, схема будет работать, но каждая запись будет содержать ненужную ссылку на общую схему, напрасно занимая время и место в памяти.

Вдвойне важно правильно сделать выбор между статическим и нестатическим классом-членом, когда этот класс является открытым или защищенным членом класса, передаваемого клиентам. В этом случае класс-член является частью внешнего API, и в последующих версиях уже нельзя будет сделать нестатический класс-член статическим, не потеряв совместимости на уровне двоичных кодов.

Анонимные классы в языке программирования Java не похожи ни на какие другие.

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

Применение анонимных классов имеет несколько ограничений. Поскольку анонимный класс одновременно декларируется и порождает экземпляр, его можно использовать, только когда его экземпляр должен порождаться лишь в одном месте программы. Анонимный класс не имеет имени, поэтому может применяться только в том случае, если после порождения экземпляра не нужно на него ссылаться. Анонимный класс обычно реализует лишь методы своего интерфейса или суперкласса. Он не объявляет каких-либо новых методов, так как для доступа к ним нет поименованного типа. Поскольку анонимные классы стоят среди выражений, они должны быть очень короткими, возможно, строк двадцать или меньше. Использование более длинных анонимных Классов может усложнить про грамму с точки зрения ее чтения.

Анонимный класс обычно служит для создания объекта функции (function object), такого как экземпляр класса Comparator. Например, при вызове следующего метода строки в массиве, будут отсортированы по их длине:

// Типичный пример использования анонимного класса

Arrays.sort(args, new Comparator() {

public int compare(Object o1, Object o2) {

return ((String)o1).length() – ((String)o2).length();

} );

 

Другой распространенный случай использования анонимного класса – создание объекта процесса (process object), такого как экземпляры классов Thread, Runnable или ТiтerTask. Третий вариант: в статическом методе генерации (см. метод intArrayAsList в статье 16). Четвертый вариант: инициализация открытого статического поля final, которое соответствует сложному перечислению типов, когда для каждого экземпляра в перечислении требуется отдельный подкласс (см. класс Operation в статье 21). Если, как было рекомендовано ранее, класс Operation будет статическим членом класса Calculator, то отдельные константы класса Operation окажутся дважды вложенными классами:

 

// Типичный пример использования открытого

// статического класса-члена

public class Calculator {

public static abstract class Operation{

 private final String name;

Operation(String пате) { this.name = name; }

public String toString() { return this.name; }

// Выполняет арифметическую операцию, представленную

// данной константой

abstract double eval(double х, double у);

// Дважды вложенные анонимные классы

public static final Operation PLUS = new Operation("+"){

double eval(double х, double у) { return х + у; }

};

public static final Operation MINUS = new Operation("-") {

double eval(double х, double у)  {геturn х – у; }

};

public static final Operation TIMES = new Operation("*"){

double eval(double х, double у)  { , return х * у; }

 };

public static final Operation DIVIDE = new Operat1on("/"){

double eval(double х, double у) { return х / у; }

 

                        };

}

// Возвращает результат указанной операции

public double calculate(double х, Operation ор, double у) {

геturn op.eval(x, у);

                        }

            }

 

Локальные классы, вероятно, относятся к наиболее редко используемой из четырех категорий вложенных классов. Локальный класс можно декларировать везде, где разрешается декларировать локальную переменную, и он подчиняется тем же самым правилам видимости. Локальный класс имеет несколько признаков, объединяющих его с каждой из трех других категорий вложенных классов. Как и классы-члены, локальные классы имеют имена и могут использоваться многократно. Как и анонимные классы, они имеют окружающий их экземпляр тогда и только тогда, когда применяются в нестатическом контексте. Как и анонимные классы, они должны быть достаточно короткими, чтобы не мешать удобству чтения метода или инициализатора, в котором они содержатся.

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

 

 

Источник: Джошуа Блох, Java TM Эффективное программирование, Издательство «Лори»

По теме:

  • Комментарии