final
Из-за различий в контексте использования (final
ключевого слова) его значение может немного отличаться. Однако основной идеей является объявление "этот элемент нельзя изменять". Причины запрета на изменение могут быть связаны как с архитектурой дизайна, так и с производительностью. Из-за этих различий возможно неправильное использование ключевого слова final
.
В следующих разделах мы рассмотрим три применения ключевого слова final
: данные, методы и классы.
final
данныеМногие языки программирования предоставляют способы сообщить компилятору, что некоторые данные являются "постоянными". Константы обычно используются для двух целей:
(1) Константы времени компиляции, которые никогда не меняются
(2) Значения, инициализируемые во время выполнения программы, которые не должны меняться
Для констант времени компиляции компилятор (или программа) может "включить" постоянное значение в необходимые вычисления. Это позволяет выполнять вычисления заранее во время компиляции, тем самым экономя время выполнения. В Java такие формы констант должны быть примитивными типами данных (Primitives) и должны быть объявлены с использованием ключевого слова final
. При объявлении такой константы обязательно должно быть указано её значение.Как static
, так и final
поля могут хранить только одно значение, которое нельзя изменить.
Если использовать final
вместе со ссылками на объекты вместо примитивных типов данных, это может вызвать некоторую путаницу. Для примитивных типов данных final
делает значение постоянным; для ссылок на объекты final
делает саму ссылку постоянной. При объявлении таких полей обязательна инициализация ссылки конкретным объектом. Ссылка никогда не должна указывать на другой объект. Тем не менее, сам объект можно модифицировать. Java не предоставляет средств для прямого преобразования объекта в постоянный (хотя можно создать свой класс, который будет имитировать поведение постоянного объекта). Это ограничение также распространяется на массивы, которые тоже являются объектами.
Ниже представлен пример использования final
полей:
//: FinalData.java
// Эффект ключевого слова final на полях
class Value {
int i = 1;
}
``````java
public class FinalData {
// May be a compile-time constant
final int i1 = 9;
static final int I2 = 99;
// A typical public constant:
public static final int I3 = 39;
// Cannot be a compile-time constant:
final int i4 = (int)(Math.random() * 20);
static final int i5 = (int)(Math.random() * 20);
}
Значение v1 = new Значение();
константное Значение v2 = new Значение();
статическое конstantное Значение v3 = new Значение();
// ! константное Значение v4; // Error before Java 1.1:
// no initial value provided
// Arrays:
константное целое[] a = { 1, 2, 3, 4, 5, 6 };
Перевод:
Значение v1 = new Значение();
константное Значение v2 = new Значение();
статическое константное Значение v3 = new Значение();
// ! константное Значение v4; // Ошибка до Java 1.1:
// нет начального значения
// Массивы:
константное целое[] a = { 1, 2, 3, 4, 5, 6 };
```публичный void вывод(Строковое id) {
Система.вывод.println(
id + ": " + "i4 = " + i4 +
", i5 = " + i5);
}
публичный статический void основной(Строковое[] аргументы) {
КонстантныеДанные fd1 = новое КонstantныеДанные();
// ! fd1.i1++; // Ошибка: нельзя изменять значение
fd1.v2.i++; // Объект не является постоянным!
fd1.v1 = новое Значение(); // В порядке — не константный
для(целого i = 0; i < fd1.a.длина; i++)
fd1.a[i]++; // Объект не является постоянным!
// ! fd1.v2 = новое Значение(); // Ошибка: Нельзя
// ! fd1.v3 = новое Значение(); // менять обработчик
// ! fd1.a = новое целое[3];
fd1.вывод("fd1");
Система.вывод.println("Создание нового объекта КонstantныеДанные");
КонstantныеДанные fd2 = новое КонstantныеДанные();
fd1.вывод("fd1");
fd2.вывод("fd2");
}
} ///:~
```Переменные от `v1` до `v4` демонстрируют значение ссылки `final`. Как видно в методе `main()`, нельзя считать, что поскольку `v2` является `final`, его значение больше не может меняться. Однако мы действительно не можем привязать `v2` к новому объекту, так как свойство `final` указывает на то, что ссылка не должна изменяться. Это точное значение слова `final` для ссылки. То же самое применимо к массивам, которые являются просто другим типом ссылок.
Преобразование ссылки в `final` кажется менее полезным по сравнению с преобразованием базовых данных в `final`.
(2) Пустой `final`Java 1.1 позволяет нам создавать «пустые `final`», которые относятся к некоторым специальным полям. Несмотря на то, что они объявлены как `final`, они не получают начального значения. В любом случае пустые `final` должны быть правильно инициализированы перед использованием. Компилятор активно гарантирует это правило. Однако среди всех применений ключевого слова `final`, пустые `final` предоставляют наибольшую гибкость. Например, поле `final` внутри класса теперь может отличаться для каждого объекта, при этом сохраняя свою «неизменность». Пример:```java
//: BlankFinal.java
// "Пустой" final член данных
class Poppet {}
class BlankFinal {
final int i = 0; // Инициализированное final
final int j; // Пустое final
final Poppet p; // Пустое final обработчик
// Пустые finals ДОЛЖНЫ быть инициализированы
// в конструкторе:
BlankFinal() {
j = 1; // Инициализация пустого final
p = new Poppet();
}
BlankFinal(int x) {
j = x; // Инициализация пустого final
p = new Poppet();
}
public static void main(String[] args) {
BlankFinal bf = new BlankFinal();
}
} ///:~
Таким образом, нас вынуждают инициализировать final
— либо используя выражение при объявлении поля, либо в каждом конструкторе. Это гарантирует правильную инициализацию final
полей перед их использованием.
(3) final
аргументы
Java 1.1 позволяет нам объявлять параметры как final
, путём соответствующего объявления в списке параметров. Это означает, что внутри метода мы не можем изменять то, на что ссылается параметр. Пример:
//: FinalArguments.java
// Использование "final" с аргументами метода
``````java
public class FinalArguments {
void c(final Gizmo g) {
//! g = new Gizmo(); // Незаконно -- g является final
g.spin();
}
void sbez(Gizmo g) {
g = new Gizmo(); // ОК -- g не является final
g.spin();
}
// void f(final int i) { i++; } // Нельзя изменять
// Вы можете только читать из final примитивного типа:
int g(final int i) { return i + 1; }
public static void main(String[] args) {
FinalArguments bf = new FinalArguments();
bf.sbez(new Gizmo());
bf.c(new Gizmo());
}
} ///:~
Обратите внимание, что в данный момент можно всё ещё присвоить параметру `final` значение `null`, при этом компилятор этого не зафиксирует. Это аналогично тому, как мы действуем с непараметрами `final`.
Методы f()
и g()
демонстрируют, что происходит с параметрами примитивных типов, объявленными как final
: мы можем только читать эти параметры, но не менять их.
final
методыИспользование final
методов может быть мотивировано двумя причинами. Первая заключается в том, чтобы "закрыть" метод от изменения его семантики любыми наследниками. При проектировании программы вы можете использовать этот подход, если хотите гарантировать, что поведение метода останется неизменным при наследовании и он не будет переопределен.Второй причиной использования final
методов является повышение производительности выполнения программы. Когда метод объявляется как final
, компилятор может заменить все вызовы этого метода на "встроенные" вызовы. То есть, вместо того чтобы генерировать обычный код для вызова метода (например, помещение аргументов на стек; переход к коду метода и его выполнение; возврат управления; очистка аргументов со стека; обработка возвращаемого значения), компилятор просто вставляет тело метода прямо там, где был вызов. Это позволяет избежать системных затрат на вызов метода. Однако, если метод слишком большой, программа может стать более объемной, и возможное увеличение производительности может быть сведено на нет временем, затраченным на выполнение метода внутри себя. Java-компилятор автоматически обнаруживает такие ситуации и "разумно" решает, следует ли встраивать final
метод. Тем не менее, лучше не полагаться полностью на то, что компилятор всегда принимает правильное решение. Обычно стоит делать метод final
, когда его код очень мал или когда вы явно хотите запретить переопределение метода.Все private
методы внутри класса автоматически являются final
. Поскольку нам недоступен private
метод, он никогда не будет переопределен другим методом (если попытаться это сделать, компилятор сообщит об ошибке). Вы можете указать модификатор final
для private
метода, но это не добавит ему никакого дополнительного смысла.
final
классыЕсли весь класс объявлен как final
(перед определением класса используется ключевое слово final
), это означает, что вы не хотите, чтобы от него происходили наследования, или не позволяете никому другому делать это. Другими словами, по тем или иным причинам наш класс точно не требует изменений; или из соображений безопасности мы не хотим допустить создание подклассов (подклассификацию).
Классы, объявленные как final
, не могут иметь наследников. Кроме того, мы можем учитывать вопросы производительности выполнения и стремиться обеспечить максимальную эффективность всех действий, связанных с объектами этого класса. В следующем примере:
//: Jurassic.java
// Делаем весь класс final
class SmallBrain {}
final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f() {}
}
//! class Further extends Dinosaur {}
// Ошибка: Нельзя расширять final класс 'Dinosaur'
public class Jurassic {
public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++;
}
} ///:~
```Обратите внимание, что данные члены могут быть как `final`, так и нет, в зависимости от нашего выбора. Правила применения `final` также применимы к данным членам, независимо от того, является ли класс `final`. После объявления класса как `final` запрещается его наследование — больше никаких ограничений. Однако, поскольку наследование запрещено, все методы внутри `final` класса автоматически становятся `final`. Поскольку теперь они уже не могут быть переопределены.Можно указать `final` для метода внутри `final` класса, но это не имеет смысла.
### 6.8.4 Учтите `final`
При проектировании класса часто возникает вопрос, следует ли сделать один из методов `final`. Может показаться важным повысить производительность при использовании своего класса, никто не хочет переопределять свои методы. Такое мнение может быть правильным в некоторых случаях.
Однако следует осторожно делать такие предположения. Обычно трудно предугадать, как будет повторно использоваться класс в будущем. Особенно это относится к общим классам. Если вы определите метод как `final`, вы можете лишить других программистов возможности наследовать ваш класс, поскольку вы просто не смогли предвидеть такого использования.
---Перевод выполнен согласно указанным правилам, сохранив исходное форматирование и структуру документа. Стандартная библиотека Java является лучшим примером для иллюстрации этой точки зрения. Один из наиболее часто используемых классов в ней — это `Vector`. Если мы рассмотрим производительность выполнения кода, то заметим, что только если ни один метод не будет объявлен как `final`, он сможет полностью раскрыть свой потенциал. Мы легко можем представить себе ситуацию, когда нам хотелось бы наследовать и переопределить такой полезный класс, но его создатели отвергли эту идею. Однако мы можем привести по крайней мере два аргумента против этого решения. Во-первых, класс `Stack` (стек) наследуется от `Vector`, то есть говорить, что `Stack` "является" `Vector`, было бы неточным. Во-вторых, многие важные методы класса `Vector`, такие как `addElement()` и `elementAt()`, стали синхронизированными. Как будет показано в главе 14, это может вызвать значительные затраты по времени, которые могут аннулировать любое улучшение производительности, предоставленное ключевым словом `final`. Поэтому программистам приходится гадать, где следует проводить оптимизацию. Увидеть такое неудачное решение в стандартной библиотеке просто невероятно, и трудно даже представить, какие эмоции это может вызвать среди программистов. Ещё одним важным стандартным классом является `Hashtable` (ассоциативный массив).Этот класс не использует никаких `final` методов. Как мы уже упоминали в других частях этой книги, очевидно, что некоторые дизайнеры классов имеют совершенно другой уровень навыков по сравнению с другими (внимание на короткие имена методов в `Hashtable` по сравнению с именами методов в `Vector`). Для пользователей библиотеки классов это должно быть невидимым. Несоответствие в дизайне продукта усложняет работу пользователям. Это также подчеркивает необходимость большой ответственности при проектировании и проверке кода.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )