Выбрать главу

Пустые константы

В Java разрешается создавать пустые константы — поля, объявленные как final, которым, однако, не было присвоено начальное значение. Во всех случаях пустую константу обязательно нужно инициализировать перед использовани­ем, и компилятор следит за этим. Впрочем, пустые константы расширяют сво­боду действий при использовании ключевого слова final, так как, например, поле final в классе может быть разным для каждого объекта, и при этом оно со­храняет свою неизменность. Пример:

//: c06:BlankFinal .java // "Пустые" неизменные поля.

class Poppet {

private int i:

Poppet(int ii) { i = ii; }

}

public class BlankFinal {

private final int i = 0; // Инициализированная константа private final int j; // Пустая константа private final Poppet p; // Пустая константа-ссылка II Пустые константы НЕОБХОДИМО инициализировать // в конструкторе: public BlankFinalО {

j = 1; // Инициализация пустой константы р = new Poppet(l); // Инициализация пустой неизменной ссылки

}

public BlankFinal(int х) {

j = х; // Инициализация пустой константы р = new Poppet(x), // Инициализация пустой неизменной ссылки

}

public static void main(String[] args) { new BlankFinal О; new BlankFinal(47),

}

} ///:-

Значения неизменных (final) переменных обязательно должны присваивать­ся или в выражении, записываемом в точке определения переменной, или в ка­ждом из конструкторов класса. Тем самым гарантируется инициализация по­лей, объявленных как final, перед их использованием.

Неизменные аргументы

Java позволяет вам объявлять неизменными аргументы метода, объявляя их с ключевым словом final в списке аргументов. Это значит, что метод не может изменить значение, на которое указывает передаваемая ссылка:

II: reusing/FinalArguments.java II Использование final с аргументами метода

class Gizmo {

public void spinO {}

}

public class Final Arguments { void with(final Gizmo g) {

III g = new GizmoO; II запрещено -- g объявлено final

}

void without(Gizmo g) {

g = new GizmoO: II Разрешено -- g не является final g.spinO;

}

II void f(final int i) { i++, } II Нельзя изменять. II неизменные примитивы доступны только для чтения: int g(final int i) { return i + 1; } public static void main(String[] args) {

Final Arguments bf = new FinalArgumentsO;

bf .without(null):                                                                              продолжение & bf with(niil 1),

}

} ///.-

Методы f() и g() показывают, что происходит при передаче методу примити­вов с пометкой finaclass="underline" их значение можно прочитать, но изменить его не удастся.

Неизменные методы

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

Второй причиной в прошлом считалась эффективность. В более ранних реа­лизациях Java объявление метода с ключевым словом final позволяло компиля­тору превращать все вызовы такого метода во встроенные (inline). Когда ком­пилятор видит метод, объявленный как final, он может (на свое усмотрение) пропустить стандартный механизм вставки кода для проведения вызова метода (занести аргументы в стек, перейти к телу метода, исполнить находящийся там код, вернуть управление, удалить аргументы из стека и распорядиться возвра­щенным значением) и вместо этого подставить на место вызова копию реально­го кода, находящегося в теле метода. Таким образом устраняются издержки обычного вызова метода. Конечно, для больших методов подстановка приведет к «разбуханию» программы, и, скорее всего, никаких преимуществ от использо­вания прямого встраивания не будет.

В последних версиях Java виртуальная машина выявляет подобные ситуа­ции и устраняет лишние передачи управления при оптимизации, поэтому ис­пользовать final для методов уже не обязательно — и более того, нежелательно.

Спецификаторы final и private

Любой закрытый (private) метод в классе косвенно является неизменным (final) методом. Так как вы не в силах получить доступ к закрытому методу, то не смо­жете и переопределить его. Ключевое слово final можно добавить к закрытому методу, но его присутствие ни на что не повлияет.

Это может вызвать недоразумения, так как при попытке переопределения закрытого (private) метода, также неявно являющегося final, все вроде бы рабо­тает и компилятор не выдает сообщений об ошибках:

//• reusi ng/Fi nalOverri di ngll1usi on.java

// Все выглядет так, будто закрытый (и неизменный) метод

// можно переопределить, но это заблуждение.

import static net mindview.util Print.*,

class WithFinals {

// To же, что и просто private:

private final void f() { printC'WithFinals f()M), }

// Также автоматически является final

private void g() { printC'WithFinals.g()"), }

class OverridingPrivate extends WithFinals {

private final void f() {

printC'OverridingPrivate fO").

}

private void g() {

printC'OverridingPrivate g()").

}

}

class 0verridingPrivate2 extends OverridingPrivate {

public final void f() {

print("0verridingPrivate2 f()").

}

public void g() {

print("0verridingPrivate2 g()").

}

public class FinalOverridingll1usion {

public static void main(String[] args) {

0verridingPrivate2 op2 = new 0verridingPrivate2();

op2 f();

op2.g();

// Можно провести восходящее преобразование-

OverridingPrivate op = op2;

// Но методы при этом вызвать невозможно.

//! op f().

//! op.g().

// И то же самое здесь- WithFinals wf = ор2. //! wf.fO, //! wf g();

}

} /* Output: 0verridingPrivate2.f()

0verridingPrivate2.g() */// ~

«Переопределение» применимо только к компонентам интерфейса базового класса. Иначе говоря, вы должны иметь возможность выполнить восходящее преобразование объекта к его базовому типу и вызвать тот же самый метод (это утверждение подробнее обсуждается в следующей главе). Если метод объявлен как private, он не является частью интерфейса базового класса; это просто неко­торый код, скрытый внутри класса, у которого оказалось то же имя. Если вы создаете в производном классе одноименный метод со спецификатором public, protected или с доступом в пределах пакета, то он никак не связан с закрытым методом базового класса. Так как privat-метод недоступен и фактически неви­дим для окружающего мира, он не влияет ни на что, кроме внутренней органи­зации кода в классе, где он был описан.

Неизменные классы

Объявляя класс неизменным (записывая в его определении ключевое слово final), вы показываете, что не собираетесь использовать этот класс в качестве базового при наследовании и запрещаете это делать другим. Другими словами, по какой-то причине структура вашего класса должна оставаться постоянной — или же появление субклассов нежелательно по соображениям безопасности.

// reusing/Jurassic java