В языке программирования Java , то final
ключевое слово используется в нескольких контекстах , чтобы определить объект , который может быть назначен только один раз.
После того, как final
переменная была назначена, она всегда содержит одно и то же значение. Если final
переменная содержит ссылку на объект, то состояние объекта может быть изменено операциями над объектом, но переменная всегда будет ссылаться на один и тот же объект (это свойство final
называется нетранзитивностью [1] ). Это применимо также к массивам, потому что массивы являются объектами; если final
переменная содержит ссылку на массив, то компоненты массива могут быть изменены операциями с массивом, но переменная всегда будет ссылаться на один и тот же массив. [2]
Заключительные занятия
Окончательный класс не может иметь подклассов. Поскольку это может повысить безопасность и эффективность, многие классы стандартной библиотеки Java являются окончательными, например, java.lang.System
и java.lang.String
.
Пример:
общедоступный конечный класс MyFinalClass {...}открытый класс ThisIsWrong расширяет MyFinalClass {...} // запрещено
Окончательные методы
Последний метод не может быть переопределен или скрыт подклассами. [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 () {...} // ОК, отменяем Base # m1 () public void m2 () {...} // запрещено public static void m3 () {...} // ОК, скрываем Base # m3 () public static void m4 () {...} // запрещено }
Распространенное заблуждение состоит в том, что объявление метода как метода final
повышает эффективность, позволяя компилятору напрямую вставлять метод, где бы он ни был вызван (см. Встроенное расширение ). Поскольку метод загружается во время выполнения , компиляторы не могут этого сделать. Только среда выполнения и JIT- компилятор точно знают, какие классы были загружены, и поэтому только они могут принимать решения о том, когда встраивать, является ли метод окончательным. [5]
Компиляторы машинного кода, которые генерируют непосредственно исполняемый машинный код , зависящий от платформы , являются исключением. При использовании статической компоновки компилятор может с уверенностью предположить, что методы и переменные, вычисляемые во время компиляции, могут быть встроены.
Конечные переменные
Окончательная переменная может быть инициализирован только один раз, либо с помощью инициализатора или оператора присваивания. Ее не нужно инициализировать в момент объявления: это называется «пустой конечной» переменной. Пустая конечная переменная экземпляра класса должна быть определенно назначена в каждом конструкторе класса, в котором она объявлена; аналогично, пустая конечная статическая переменная должна быть определенно назначена в статическом инициализаторе класса, в котором она объявлена; в противном случае в обоих случаях возникает ошибка времени компиляции. [6] (Примечание: если переменная является ссылкой, это означает, что переменная не может быть повторно привязана для ссылки на другой объект. Но объект, на который она ссылается, остается изменяемым , если он был изменен изначально.)
В отличие от значения константы , значение конечной переменной не обязательно известно во время компиляции. Считается хорошей практикой представлять конечные константы в верхнем регистре, используя подчеркивание для разделения слов. [7]
Пример:
public class Sphere { // pi - универсальная константа, почти такая же постоянная, как и все. публичный статический финальный двойной PI = 3.141592653589793 ; публичный финальный двойной радиус ; публичный финал double xPos ; публичный финальный двойной yPos ; публичный финал двойной zPos ; Сфера ( двойной x , двойной y , двойной z , двойной r ) { радиус = r ; xPos = x ; yPos = y ; zPos = z ; } [ ... ] }
Любая попытка передать radius
, xPos
, yPos
или zPos
приведет к ошибке компиляции. Фактически, даже если конструктор не устанавливает конечную переменную, попытка установить ее вне конструктора приведет к ошибке компиляции.
Чтобы проиллюстрировать, что окончательность не гарантирует неизменяемости: предположим, мы заменяем три позиционные переменные одной:
публичная итоговая позиция pos ;
где pos
- объект с тремя свойствами pos.x
, pos.y
и pos.z
. Тогда pos
нельзя присвоить, но можно назначить три свойства, если только они сами не являются окончательными.
Как и полная неизменяемость , использование конечных переменных имеет большие преимущества, особенно при оптимизации. Например, Sphere
вероятно, будет функция, возвращающая его объем; знание того, что его радиус постоянен, позволяет нам запоминать вычисленный объем. Если у нас относительно мало Sphere
s и их тома нам нужны очень часто, выигрыш в производительности может быть значительным. Создание радиуса a Sphere
final
сообщает разработчикам и компиляторам, что такого рода оптимизация возможна во всем коде, который использует Sphere
s.
Хотя это кажется нарушением final
принципа, следующее заявление является юридическим:
for ( final SomeObject obj : someList ) { // что-то делаем с obj }
Поскольку переменная obj выходит за пределы области видимости с каждой итерацией цикла, она фактически повторно объявляется на каждой итерации, позволяя использовать один и тот же токен (т.е. obj
) для представления нескольких переменных. [8]
Конечные переменные во вложенных объектах
Конечные переменные можно использовать для построения деревьев неизменяемых объектов. После создания эти объекты гарантированно больше не изменятся. Для этого неизменный класс должен иметь только поля final, а эти поля final могут иметь только сами неизменяемые типы. Примитивные типы Java неизменны, как и строки и некоторые другие классы.
Если приведенная выше конструкция нарушается из-за наличия в дереве объекта, который не является неизменяемым, ожидание не выполняется, что что-либо, достижимое через конечную переменную, является постоянным. Например, следующий код определяет систему координат, начало которой всегда должно быть в (0, 0). Источник реализуется с помощью java.awt.Point
хотя, и этот класс определяет свои поля как общедоступные и изменяемые. Это означает, что даже при достижении origin
объекта по пути доступа только с конечными переменными, этот объект все еще может быть изменен, как показано в приведенном ниже примере кода.
import java.awt.Point ;public class FinalDemo { статический класс CoordinateSystem { частная конечная точка начала координат = новая точка ( 0 , 0 ); public Point getOrigin () { возвращение происхождения ; } } общественных статический недействительный основной ( Строка [] арг ) { CoordinateSystem coordinateSystem = новый CoordinateSystem (); Координатная система . getOrigin (). х = 15 ; утверждать систему координат . 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 ) { // инициализируем компоненты графического интерфейса final JFrame jf = new JFrame ( "Hello world!" ); // разрешает доступ к jf из тела внутреннего класса jf . добавить ( новый JButton ( "Щелкните меня" )); // упаковать и сделать видимыми в потоке событий-диспетчеризации SwingUtilities . invokeLater ( new Runnable () { @Override public void run () { jf . pack (); // это было бы ошибкой времени компиляции, если бы jf не было окончательным jf . setLocationRelativeTo ( null ); jf . setVisible ( true ); } }); } }
Бланк финал
Пустая окончательное , который был введен в Java 1.1, является окончательным переменным, декларация не имеет инициализатор. [9] [10] До версии Java 1.1 конечная переменная должна была иметь инициализатор. Бланк финала, по определению «финала», может быть назначен только один раз. т.е. он должен быть не назначен, когда происходит присвоение. Для этого компилятор Java выполняет анализ потока, чтобы гарантировать, что для каждого присвоения пустой конечной переменной переменная определенно не назначается перед назначением; в противном случае возникает ошибка времени компиляции. [11]
последнее логическое значение hasTwoDigits ; если ( число > = 10 && число < 100 ) { hasTwoDigits = true ; } если ( число > - 100 && число <= - 10 ) { hasTwoDigits = true ; // ошибка компиляции, потому что последняя переменная уже могла быть назначена. }
Кроме того, перед доступом должен быть определенно назначен пустой финал. [11]
final boolean isEven ;если ( число % 2 == 0 ) { isEven = true ; }Система . из . println ( isEven ); // ошибка компиляции, потому что переменная не была назначена в другом случае.
Обратите внимание, что не конечная локальная переменная также должна быть определенно назначена перед доступом. [11]
логическое isEven ; // * не * окончательныйесли ( число % 2 == 0 ) { isEven = true ; }Система . из . println ( isEven ); // Та же ошибка компиляции, потому что не конечная переменная не была назначена в другом случае.
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
- ^ JLS 8.4.3.3. финальные методы
- ^ Написание заключительных классов и методов
- ^ Теория и практика Java: это ваш окончательный ответ?
- ^ Спецификация языка Java № 8.3.1.2.
- ^ http://geosoft.no/development/javastyle.html
- ^ Паттис, Ричард Э. «Больше Явы» . Продвинутое программирование / Практикум 15–200 . Школа компьютерных наук Университета Карнеги-Меллона . Проверено 23 июля 2010 года .
- ^ Фланаган, Дэвид (май 1997 г.). «Глава 5 Внутренние классы и другие новые возможности языка: 5.6 Другие новые возможности Java 1.1». Java в двух словах (2-е изд.). О'Рейли. ISBN 1-56592-262-X.
- ^ «Глава 4. Типы, значения и переменные» . Спецификация языка Java® (Java SE 8 Edition) . Oracle America, Inc. 2015 . Проверено 23 фев 2015 .
- ^ а б в «Определенное присвоение» . Спецификация языка Java® (Java SE 8 Edition) . Oracle America, Inc. 2015 . Проверено 29 окт 2016 .
- ^ Что эквивалентно финалу Java в C #?