stringtranslate.com

финал (Ява)

В языке программирования 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 расширяет Base { public void m1 () {...} // ОК, переопределяем Base#m1() public void m2 () {...} // запрещено               public static void m3 () {...} // ОК, скрываем Base#m3() public static void m4 () {...} // запрещено }           

Распространенным заблуждением является то, что объявление метода как finalповышает эффективность, позволяя компилятору напрямую вставлять метод везде, где он вызывается (см. встроенное расширение ). Поскольку метод загружается во время выполнения , компиляторы не могут этого сделать. Только среда выполнения и JIT- компилятор точно знают, какие классы были загружены, и поэтому только они могут принимать решения о том, когда встраивать, является ли метод окончательным или нет. [5]

Исключением являются компиляторы машинного кода, которые генерируют непосредственно исполняемый машинный код , специфичный для платформы . При использовании статического связывания компилятор может с уверенностью предположить, что методы и переменные, вычисляемые во время компиляции, могут быть встроены.

Итоговые переменные

Конечную переменную можно инициализировать только один раз либо с помощью инициализатора, либо с помощью оператора присваивания . Ее не нужно инициализировать в момент объявления: это называется «пустой финальной переменной». Пустая конечная переменная экземпляра класса должна быть обязательно назначена в каждом конструкторе класса, в котором она объявлена; аналогично, пустая финальная статическая переменная должна быть обязательно назначена в статическом инициализаторе класса, в котором она объявлена; в противном случае в обоих случаях возникает ошибка времени компиляции. [6] (Примечание: если переменная является ссылкой, это означает, что переменная не может быть повторно привязана для ссылки на другой объект. Но объект, на который она ссылается, по-прежнему является изменяемым , если он изначально был изменяемым.)

В отличие от значения константы , значение конечной переменной не обязательно известно во время компиляции. Хорошей практикой считается представлять конечные константы заглавными буквами, используя подчеркивание для разделения слов. [7]

Пример:

общественный класс Сфера {    // pi — универсальная константа, настолько постоянная, насколько это возможно. public static Final Double PI = 3.141592653589793 ;        публичный финальный двойной радиус ; публичный финал Double xPos ; публичный финальный двойной yPos ; публичный финальный двойной zPos ;                Сфера ( двойной x , двойной y , двойной z , двойной r ) { радиус = r ; хПос = х ; уПос = у ; zPos = z ; }                      [ ... ] }

Любая попытка переназначить radius, xPos, yPosили zPosприведет к ошибке компиляции. Фактически, даже если конструктор не устанавливает конечную переменную, попытка установить ее вне конструктора приведет к ошибке компиляции.

Чтобы проиллюстрировать, что окончательность не гарантирует неизменности: предположим, что мы заменяем три позиционные переменные одной:

 публичная финальная позиция pos ;   

где posобъект с тремя pos.xсвойствами pos.yи pos.z. Тогда posнельзя назначить, но можно присвоить три свойства, если только они сами не являются окончательными.

Как и полная неизменяемость , использование финальных переменных имеет большие преимущества, особенно при оптимизации. Например, Sphereвероятно, будет функция, возвращающая его объем; знание того, что его радиус постоянен, позволяет нам запомнить вычисленный объем. Если у нас относительно мало Sphereфайлов и их объемы нужны нам очень часто, прирост производительности может быть существенным. Создание радиуса a Sphere finalинформирует разработчиков и компиляторов о том, что такого рода оптимизация возможна во всем коде, использующем Spheres.

Хотя это и кажется нарушением finalпринципа, следующее утверждение является юридическим:

for ( final SomeObject obj : someList ) { // делаем что-нибудь с объектом }       

Поскольку переменная obj выходит за пределы области видимости на каждой итерации цикла, она фактически переобъявляется на каждой итерации, позволяя objиспользовать один и тот же токен (т. е.) для представления нескольких переменных. [8]

Конечные переменные во вложенных объектах

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

Если приведенная выше конструкция нарушается из-за наличия в дереве объекта, который не является неизменяемым, ожидание не означает, что что-либо, достижимое через конечную переменную, является постоянным. Например, следующий код определяет систему координат, начало которой всегда должно находиться в (0, 0). Источник реализован с использованием метода java.awt.Point, и этот класс определяет свои поля как общедоступные и изменяемые. Это означает, что даже при достижении originобъекта по пути доступа только с конечными переменными этот объект все равно можно изменить, как демонстрирует приведенный ниже пример кода.

импортировать java.awt.Point ; публичный класс FinalDemo {    статический класс Координатная система { частное начало конечной точки = новая точка ( 0 , 0 );            public Point getOrigin () { return origin ; } }        public static void main ( String [] args ) { CoordateSystem координаторная система = новая координатная система ();           система координат . getOrigin (). х = 15 ;   утвердить систему координат . getOrigin (). получитьX () == 0 ; } }    

Причина этого в том, что объявление переменной Final означает лишь то, что эта переменная будет указывать на один и тот же объект в любое время. Однако объект, на который указывает переменная, не находится под влиянием этой последней переменной. В приведенном выше примере координаты x и y начала координат можно свободно изменять.

Чтобы предотвратить эту нежелательную ситуацию, общим требованием является то, что все поля неизменяемого объекта должны быть окончательными, а типы этих полей сами по себе должны быть неизменяемыми. Это лишает права java.util.Dateиспользовать java.awt.Pointнекоторые другие классы в таких неизменяемых объектах.

Заключительные и внутренние занятия

Когда анонимный внутренний класс определен в теле метода, все переменные, объявленные finalв области этого метода, доступны из внутреннего класса. Для скалярных значений после присвоения значение переменной finalне может измениться. Для значений объекта ссылка не может измениться. Это позволяет компилятору Java «захватывать» значение переменной во время выполнения и сохранять копию как поле во внутреннем классе. Как только внешний метод завершается и его кадр стека удаляется, исходная переменная исчезает, но частная копия внутреннего класса сохраняется в собственной памяти класса.

импортировать javax.swing.* ; публичный класс FooGUI {    public static void main ( String [] args ) { // инициализируем компоненты графического интерфейса Final JFrame jf = new JFrame ( «Hello world!» ); //разрешает доступ к jf из тела внутреннего класса jf . add ( new JButton ( "Нажмите на меня" ));                // упаковываем и делаем видимыми в потоке Event-Dispatch SwingUtilities . вызываетLater ( new Runnable () { @Override public void run () { jf .pack (); // это была бы ошибка времени компиляции, если бы jf не был окончательным jf .setLocationRelativeTo ( null ) ; jf .setVisible ( true ) ; } }); } }               

Пустой финал

Пустая переменная Final , появившаяся в Java 1.1, представляет собой конечную переменную, в объявлении которой отсутствует инициализатор. [9] [10] До версии Java 1.1 конечная переменная должна была иметь инициализатор. Пустой финал по определению «финал» может быть назначен только один раз. т. е. он должен быть отменен при назначении. Чтобы сделать это, компилятор Java запускает анализ потока, чтобы гарантировать, что для каждого присвоения пустой конечной переменной переменная определенно не была назначена до присвоения; в противном случае возникает ошибка времени компиляции. [11]

окончательное логическое значение hasTwoDigits ; if ( число >= 10 && число < 100 ) { hasTwoDigits = true ; } if ( число > - 100 && число <= - 10 ) { hasTwoDigits = true ; // ошибка компиляции, поскольку последняя переменная уже может быть назначена. }                         

Кроме того, перед доступом к нему должен быть обязательно назначен пустой финал. [11]

последнее логическое значение isEven ;  если ( число % 2 == 0 ) { isEven = true ; }         Система . вне . println ( евен ); // ошибка компиляции, поскольку переменная не была назначена в else-кейсе. 

Однако обратите внимание, что нефинальная локальная переменная также должна быть точно назначена перед доступом. [11]

логическое значение isEven ; // *не* финал  если ( число % 2 == 0 ) { isEven = true ; }         Система . вне . println ( евен ); // Та же ошибка компиляции, поскольку нефинальная переменная не была назначена в else-кейсе. 

C/C++ аналог финальных переменных

В C и C++ аналогичной конструкцией является const ключевое слово . Это существенно отличается от finalJava, главным образом тем, что является квалификатором типа : 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# есть два связанных ключевых слова:

  1. Эквивалентное ключевое слово для методов и классов:sealed
  2. Эквивалентное ключевое слово для переменных: readonly [12]

Обратите внимание, что ключевое различие между производным ключевым словом C/C++ constи ключевым словом C# readonlyзаключается в том, что constоно оценивается во время компиляции, а readonlyвычисляется во время выполнения, и, следовательно, может иметь выражение, которое вычисляется и фиксируется только позже (во время выполнения).

Рекомендации

  1. ^ Кобленц, Майкл; Саншайн, Джошуа; Олдрич, Джонатан; Майерс, Брэд; Вебер, Сэм; Шулл, Форрест (14–22 мая 2016 г.). «Изучение языковой поддержки неизменяемости». 38-я Международная конференция по программной инженерии .
  2. ^ Спецификация языка Java № 4.12.4
  3. ^ JLS 8.4.3.3. окончательные методы
  4. ^ Написание финальных классов и методов.
  5. ^ Теория и практика Java: это ваш окончательный ответ?
  6. ^ Спецификация языка Java № 8.3.1.2.
  7. ^ http://geosoft.no/development/javastyle.html
  8. ^ Паттис, Ричард Э. «Больше Java». Продвинутое программирование/Практикум 15–200 . Школа компьютерных наук Университета Карнеги-Меллона . Проверено 23 июля 2010 г.
  9. ^ Фланаган, Дэвид (май 1997 г.). «Глава 5 Внутренние классы и другие новые возможности языка: 5.6 Другие новые возможности Java 1.1». Java в двух словах (2-е изд.). О'Рейли. ISBN 1-56592-262-Х.
  10. ^ «Глава 4. Типы, значения и переменные». Спецификация языка Java® (Java SE 8 Edition) . Oracle America, Inc., 2015 г. Проверено 23 февраля 2015 г.
  11. ^ abc «Определенное присвоение». Спецификация языка Java® (Java SE 8 Edition) . Oracle America, Inc., 2015 г. Проверено 29 октября 2016 г.
  12. ^ Что является эквивалентом финала Java в C#?