финал (Ява)
В языке программирования Java final
Ключевое слово используется в нескольких контекстах для определения объекта, который может быть назначен только один раз.
Раз в final
присвоена переменная, она всегда содержит одно и то же значение. Если final
переменная содержит ссылку на объект, то состояние объекта может быть изменено операциями над объектом, но переменная всегда будет ссылаться на один и тот же объект (это свойство final
называется нетранзитивностью [1] ). Это относится и к массивам, поскольку массивы — это объекты; если final
переменная содержит ссылку на массив, то компоненты массива могут быть изменены операциями с массивом, но переменная всегда будет ссылаться на один и тот же массив. [2]
Заключительные занятия
[ редактировать ] класс Последний не может быть подклассом. Поскольку это может обеспечить преимущества в безопасности и эффективности, многие классы стандартной библиотеки Java являются окончательными, например: java.lang.System
и java.lang.String
.
Пример:
public final class MyFinalClass {...}
public class ThisIsWrong extends MyFinalClass {...} // forbidden
Заключительные методы
[ редактировать ]Последний метод не может быть переопределен или скрыт подклассами. [3] Это используется для предотвращения неожиданного поведения подкласса, изменяющего метод, который может иметь решающее значение для функции или согласованности класса. [4]
Пример:
public class Base
{
public void m1() {...}
public final void m2() {...}
public static void m3() {...}
public static final void m4() {...}
}
public class Derived extends Base
{
public void m1() {...} // OK, overriding Base#m1()
public void m2() {...} // forbidden
public static void m3() {...} // OK, hiding Base#m3()
public static void m4() {...} // forbidden
}
Распространенным заблуждением является то, что объявление метода как final
повышает эффективность, позволяя компилятору напрямую вставлять метод везде, где он вызывается (см. встроенное расширение ). Поскольку метод загружается во время выполнения , компиляторы не могут этого сделать. Только среда выполнения и JIT- компилятор точно знают, какие классы были загружены, и поэтому только они могут принимать решения о том, когда встраивать, является ли метод окончательным или нет. [5]
, специфичный для платформы компиляторы машинного кода, которые генерируют непосредственно исполняемый машинный код Исключением являются . При использовании статического связывания компилятор может с уверенностью предположить, что методы и переменные, вычисляемые во время компиляции, могут быть встроены.
Итоговые переменные
[ редактировать ]переменную Конечную . можно инициализировать только один раз либо с помощью инициализатора, либо с помощью оператора присваивания Ее не нужно инициализировать в момент объявления: это называется «пустой финальной переменной». Пустая конечная переменная экземпляра класса должна быть обязательно назначена в каждом конструкторе класса, в котором она объявлена; аналогично, пустая финальная статическая переменная должна быть обязательно назначена в статическом инициализаторе класса, в котором она объявлена; в противном случае в обоих случаях возникает ошибка времени компиляции. [6] (Примечание. Если переменная является ссылкой, это означает, что переменную нельзя повторно привязать для ссылки на другой объект. Но объект, на который она ссылается, по-прежнему является изменяемым , если он изначально был изменяемым.)
В отличие от значения константы , значение конечной переменной не обязательно известно во время компиляции. Хорошей практикой считается представлять конечные константы заглавными буквами, используя подчеркивание для разделения слов. [7]
Пример:
public class Sphere {
// pi is a universal constant, about as constant as anything can be.
public static final double PI = 3.141592653589793;
public final double radius;
public final double xPos;
public final double yPos;
public final double zPos;
Sphere(double x, double y, double z, double r) {
radius = r;
xPos = x;
yPos = y;
zPos = z;
}
[...]
}
Любая попытка переназначить radius
, xPos
, yPos
, или zPos
приведет к ошибке компиляции. Фактически, даже если конструктор не устанавливает конечную переменную, попытка установить ее вне конструктора приведет к ошибке компиляции.
Чтобы проиллюстрировать, что окончательность не гарантирует неизменность: предположим, что мы заменяем три позиционные переменные одной:
public final Position pos;
где pos
это объект с тремя свойствами pos.x
, pos.y
и pos.z
. Затем pos
не могут быть присвоены, но могут быть присвоены трем свойствам, если только они сами не являются окончательными.
Как и полная неизменяемость , использование финальных переменных имеет большие преимущества, особенно при оптимизации. Например, Sphere
вероятно, будет функция, возвращающая его объем; знание того, что его радиус постоянен, позволяет нам запомнить вычисленный объем. Если у нас относительно мало Sphere
Поскольку их объемы нужны нам очень часто, прирост производительности может быть существенным. Делаем радиус a Sphere
final
сообщает разработчикам и компиляторам, что такого рода оптимизация возможна во всем коде, использующем Sphere
с.
Хотя кажется, что это нарушает final
В принципе, следующее является юридическим заявлением:
for (final SomeObject obj : someList) {
// do something with obj
}
Поскольку переменная obj выходит за пределы области видимости на каждой итерации цикла, она фактически переобъявляется на каждой итерации, позволяя использовать один и тот же токен (т. е. obj
), который будет использоваться для представления нескольких переменных. [8]
Конечные переменные во вложенных объектах
[ редактировать ]Конечные переменные можно использовать для построения деревьев неизменяемых объектов. После создания эти объекты гарантированно больше не изменятся. Чтобы добиться этого, неизменяемый класс должен иметь только финальные поля, а сами эти финальные поля могут иметь только неизменяемые типы. Примитивные типы Java неизменяемы, как и строки и некоторые другие классы.
Если приведенная выше конструкция нарушается из-за наличия в дереве объекта, который не является неизменяемым, ожидание не означает, что что-либо, достижимое через конечную переменную, является постоянным. Например, следующий код определяет систему координат, начало координат которой всегда должно находиться в (0, 0). Происхождение реализовано с использованием java.awt.Point
однако этот класс определяет свои поля как общедоступные и изменяемые. Это означает, что даже при достижении origin
объект по пути доступа только с конечными переменными, этот объект все равно можно изменить, как показывает приведенный ниже пример кода.
import java.awt.Point;
public class FinalDemo {
static class CoordinateSystem {
private final Point origin = new Point(0, 0);
public Point getOrigin() { return origin; }
}
public static void main(String[] args) {
CoordinateSystem coordinateSystem = new CoordinateSystem();
coordinateSystem.getOrigin().x = 15;
assert coordinateSystem.getOrigin().getX() == 0;
}
}
Причина этого в том, что объявление переменной Final означает лишь то, что эта переменная будет указывать на один и тот же объект в любое время. Однако объект, на который указывает переменная, не находится под влиянием этой последней переменной. В приведенном выше примере координаты x и y начала координат можно свободно изменять.
Чтобы предотвратить эту нежелательную ситуацию, общим требованием является то, что все поля неизменяемого объекта должны быть окончательными, а типы этих полей сами должны быть неизменяемыми. Это дисквалифицирует java.util.Date
и java.awt.Point
и несколько других классов от использования в таких неизменяемых объектах.
Заключительные и внутренние занятия
[ редактировать ]Когда анонимный внутренний класс определен в теле метода, все объявленные переменные final
в области этого метода доступны изнутри внутреннего класса. Для скалярных значений после присвоения значение final
переменная не может измениться. Для значений объекта ссылка не может измениться. Это позволяет компилятору Java «захватывать» значение переменной во время выполнения и сохранять копию как поле во внутреннем классе. Как только внешний метод завершается и его кадр стека удаляется, исходная переменная исчезает, но частная копия внутреннего класса сохраняется в собственной памяти класса.
import javax.swing.*;
public class FooGUI {
public static void main(String[] args) {
//initialize GUI components
final JFrame jf = new JFrame("Hello world!"); //allows jf to be accessed from inner class body
jf.add(new JButton("Click me"));
// pack and make visible on the Event-Dispatch Thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
jf.pack(); //this would be a compile-time error if jf were not final
jf.setLocationRelativeTo(null);
jf.setVisible(true);
}
});
}
}
Пустой финал
[ редактировать ]Пустая переменная Final , появившаяся в Java 1.1, представляет собой конечную переменную, в объявлении которой отсутствует инициализатор. [9] [10] До Java 1.1 конечная переменная должна была иметь инициализатор. Пустой финал по определению «финал» может быть назначен только один раз. т. е. он должен быть отменен при назначении. Чтобы сделать это, компилятор Java запускает анализ потока, чтобы гарантировать, что для каждого присвоения пустой конечной переменной переменная определенно не была назначена до присвоения; в противном случае возникает ошибка времени компиляции. [11]
final boolean hasTwoDigits;
if (number >= 10 && number < 100) {
hasTwoDigits = true;
}
if (number > -100 && number <= -10) {
hasTwoDigits = true; // compile-error because the final variable might already be assigned.
}
Кроме того, перед доступом к нему должен быть обязательно назначен пустой финал. [11]
final boolean isEven;
if (number % 2 == 0) {
isEven = true;
}
System.out.println(isEven); // compile-error because the variable was not assigned in the else-case.
Однако обратите внимание, что нефинальная локальная переменная также должна быть точно назначена перед доступом. [11]
boolean isEven; // *not* final
if (number % 2 == 0) {
isEven = true;
}
System.out.println(isEven); // Same compile-error because the non-final variable was not assigned in the else-case.
C/C++ аналог финальных переменных
[ редактировать ]В C и C++ аналогичная конструкция — это const
ключевое слово . Это существенно отличается от final
в Java, в основном, это квалификатор типа : const
является частью типа , а не только частью идентификатора (переменной). Это также означает, что константность значения может быть изменена путем приведения (явного преобразования типов), в данном случае известного как «константное приведение». Тем не менее, отказ от константности и последующее изменение объекта приводит к неопределенному поведению, если объект был изначально объявлен. const
. Java final
— это строгое правило, согласно которому невозможно скомпилировать код, который напрямую нарушает или обходит окончательные ограничения. Однако с помощью отражения часто можно изменить конечные переменные. Эта функция чаще всего используется при десериализации объектов с конечными членами.
Кроме того, поскольку C и C++ напрямую предоставляют указатели и ссылки, существует различие между тем, является ли сам указатель константой, и являются ли константами данные, на которые указывает указатель. Применение const
на сам указатель, как в SomeClass * const ptr
, означает, что содержимое, на которое имеется ссылка, может быть изменено, но сама ссылка не может быть изменена (без приведения). Такое использование приводит к поведению, имитирующему поведение final
ссылка на переменную в Java. Напротив, при применении const только к ссылочным данным, как в const SomeClass * ptr
, содержимое не может быть изменено (без приведения), но сама ссылка может. И ссылка, и содержимое, на которое ссылаются, могут быть объявлены как const
.
Аналоги C# для ключевого слова Final
[ редактировать ]C# можно считать похожим на Java с точки зрения особенностей языка и базового синтаксиса: в Java есть JVM, в C# — .Net Framework; В Java есть байт-код, в C# — MSIL; В Java нет поддержки указателей (реальная память), в C# то же самое.
Что касается последнего ключевого слова, в C# есть два связанных ключевых слова:
- Эквивалентное ключевое слово для методов и классов:
sealed
- Эквивалентное ключевое слово для переменных:
readonly
[12]
Обратите внимание, что ключевое различие между производным ключевым словом C/C++ const
и ключевое слово C# readonly
это что const
оценивается во время компиляции, а readonly
оценивается во время выполнения и, следовательно, может иметь выражение, которое вычисляется и фиксируется только позже (во время выполнения).
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ Кобленц, Майкл; Саншайн, Джошуа; Олдрич, Джонатан; Майерс, Брэд; Вебер, Сэм; Шулл, Форрест (14–22 мая 2016 г.). «Изучение языковой поддержки неизменяемости». 38-я Международная конференция по программной инженерии .
- ^ Спецификация языка Java № 4.12.4
- ^ «Глава 8. Занятия» . docs.oracle.com . Проверено 25 апреля 2024 г.
- ^ «Написание финальных классов и методов» . docs.oracle.com . Проверено 25 апреля 2024 г.
- ^ «Теория и практика Java: это ваш окончательный ответ?» . разработчик.ibm.com . Архивировано из оригинала 8 февраля 2009 г. Проверено 25 апреля 2024 г.
- ^ Спецификация языка Java № 8.3.1.2.
- ^ «Руководство по стилю программирования на Java» . petroware.no . Проверено 25 апреля 2024 г.
- ^ Паттис, Ричард Э. «Больше Java» . Продвинутое программирование/Практикум 15–200 . Школа компьютерных наук Университета Карнеги-Меллон . Проверено 23 июля 2010 г.
- ^ Фланаган, Дэвид (май 1997 г.). «Глава 5 Внутренние классы и другие новые возможности языка: 5.6 Другие новые возможности Java 1.1». Java в двух словах (2-е изд.). О'Рейли. ISBN 1-56592-262-Х .
- ^ «Глава 4. Типы, значения и переменные» . Спецификация языка Java® (Java SE 8 Edition) . Oracle America, Inc., 2015 г. Проверено 23 февраля 2015 г.
- ^ Jump up to: а б с «Определенное назначение» . Спецификация языка Java® (Java SE 8 Edition) . Oracle America, Inc., 2015 г. Проверено 29 октября 2016 г.
- ^ Что является эквивалентом финала Java в C#?