Even in spite of being final
a field can be modified outside of static initializer and (at least JVM HotSpot) will execute the bytecode perfectly fine.
The problem is that Java compiler does not allow this, but this can be easily bypassed using objectweb.asm
. Here is p̶e̶r̶f̶e̶c̶t̶l̶y̶ ̶v̶a̶l̶i̶d̶ ̶c̶l̶a̶s̶s̶f̶i̶l̶e̶ an invalid classfile from the JVMS specification standpoint, but it passes bytecode verification and then is successfully loaded and initialized under JVM HotSpot OpenJDK12:
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Cl", null, "java/lang/Object", null);
{
FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "fld", "I", null, null);
fv.visitEnd();
}
{
// public void setFinalField1() { //... }
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField1", "()V", null, null);
mv.visitMaxs(2, 1);
mv.visitInsn(Opcodes.ICONST_5);
mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
{
// public void setFinalField2() { //... }
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField2", "()V", null, null);
mv.visitMaxs(2, 1);
mv.visitInsn(Opcodes.ICONST_2);
mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
cw.visitEnd();
In Java, the class looks roughly speaking as follows:
public class Cl{
private static final int fld;
public static void setFinalField1(){
fld = 5;
}
public static void setFinalField2(){
fld = 2;
}
}
which cannot be compiled with javac
, but can be loaded and executed by JVM.
JVM HotSpot has special treatment of such classes in the sense that it prevents such «constants» from participating in constant folding. This check is done on the bytecode rewriting phase of class initialization:
// Check if any final field of the class given as parameter is modified
// outside of initializer methods of the class. Fields that are modified
// are marked with a flag. For marked fields, the compilers do not perform
// constant folding (as the field can be changed after initialization).
//
// The check is performed after verification and only if verification has
// succeeded. Therefore, the class is guaranteed to be well-formed.
InstanceKlass* klass = method->method_holder();
u2 bc_index = Bytes::get_Java_u2(bcp + prefix_length + 1);
constantPoolHandle cp(method->constants());
Symbol* ref_class_name = cp->klass_name_at(cp->klass_ref_index_at(bc_index));
if (klass->name() == ref_class_name) {
Symbol* field_name = cp->name_ref_at(bc_index);
Symbol* field_sig = cp->signature_ref_at(bc_index);
fieldDescriptor fd;
if (klass->find_field(field_name, field_sig, &fd) != NULL) {
if (fd.access_flags().is_final()) {
if (fd.access_flags().is_static()) {
if (!method->is_static_initializer()) {
fd.set_has_initialized_final_update(true);
}
} else {
if (!method->is_object_initializer()) {
fd.set_has_initialized_final_update(true);
}
}
}
}
}
}
The only restriction that JVM HotSpot checks is that the final
field should not be modified outside of the class that the final
field is declared at.
Even in spite of being final
a field can be modified outside of static initializer and (at least JVM HotSpot) will execute the bytecode perfectly fine.
The problem is that Java compiler does not allow this, but this can be easily bypassed using objectweb.asm
. Here is p̶e̶r̶f̶e̶c̶t̶l̶y̶ ̶v̶a̶l̶i̶d̶ ̶c̶l̶a̶s̶s̶f̶i̶l̶e̶ an invalid classfile from the JVMS specification standpoint, but it passes bytecode verification and then is successfully loaded and initialized under JVM HotSpot OpenJDK12:
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Cl", null, "java/lang/Object", null);
{
FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "fld", "I", null, null);
fv.visitEnd();
}
{
// public void setFinalField1() { //... }
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField1", "()V", null, null);
mv.visitMaxs(2, 1);
mv.visitInsn(Opcodes.ICONST_5);
mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
{
// public void setFinalField2() { //... }
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField2", "()V", null, null);
mv.visitMaxs(2, 1);
mv.visitInsn(Opcodes.ICONST_2);
mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
cw.visitEnd();
In Java, the class looks roughly speaking as follows:
public class Cl{
private static final int fld;
public static void setFinalField1(){
fld = 5;
}
public static void setFinalField2(){
fld = 2;
}
}
which cannot be compiled with javac
, but can be loaded and executed by JVM.
JVM HotSpot has special treatment of such classes in the sense that it prevents such «constants» from participating in constant folding. This check is done on the bytecode rewriting phase of class initialization:
// Check if any final field of the class given as parameter is modified
// outside of initializer methods of the class. Fields that are modified
// are marked with a flag. For marked fields, the compilers do not perform
// constant folding (as the field can be changed after initialization).
//
// The check is performed after verification and only if verification has
// succeeded. Therefore, the class is guaranteed to be well-formed.
InstanceKlass* klass = method->method_holder();
u2 bc_index = Bytes::get_Java_u2(bcp + prefix_length + 1);
constantPoolHandle cp(method->constants());
Symbol* ref_class_name = cp->klass_name_at(cp->klass_ref_index_at(bc_index));
if (klass->name() == ref_class_name) {
Symbol* field_name = cp->name_ref_at(bc_index);
Symbol* field_sig = cp->signature_ref_at(bc_index);
fieldDescriptor fd;
if (klass->find_field(field_name, field_sig, &fd) != NULL) {
if (fd.access_flags().is_final()) {
if (fd.access_flags().is_static()) {
if (!method->is_static_initializer()) {
fd.set_has_initialized_final_update(true);
}
} else {
if (!method->is_object_initializer()) {
fd.set_has_initialized_final_update(true);
}
}
}
}
}
}
The only restriction that JVM HotSpot checks is that the final
field should not be modified outside of the class that the final
field is declared at.
Even in spite of being final
a field can be modified outside of static initializer and (at least JVM HotSpot) will execute the bytecode perfectly fine.
The problem is that Java compiler does not allow this, but this can be easily bypassed using objectweb.asm
. Here is p̶e̶r̶f̶e̶c̶t̶l̶y̶ ̶v̶a̶l̶i̶d̶ ̶c̶l̶a̶s̶s̶f̶i̶l̶e̶ an invalid classfile from the JVMS specification standpoint, but it passes bytecode verification and then is successfully loaded and initialized under JVM HotSpot OpenJDK12:
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Cl", null, "java/lang/Object", null);
{
FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "fld", "I", null, null);
fv.visitEnd();
}
{
// public void setFinalField1() { //... }
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField1", "()V", null, null);
mv.visitMaxs(2, 1);
mv.visitInsn(Opcodes.ICONST_5);
mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
{
// public void setFinalField2() { //... }
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "setFinalField2", "()V", null, null);
mv.visitMaxs(2, 1);
mv.visitInsn(Opcodes.ICONST_2);
mv.visitFieldInsn(Opcodes.PUTSTATIC, "Cl", "fld", "I");
mv.visitInsn(Opcodes.RETURN);
mv.visitEnd();
}
cw.visitEnd();
In Java, the class looks roughly speaking as follows:
public class Cl{
private static final int fld;
public static void setFinalField1(){
fld = 5;
}
public static void setFinalField2(){
fld = 2;
}
}
which cannot be compiled with javac
, but can be loaded and executed by JVM.
JVM HotSpot has special treatment of such classes in the sense that it prevents such «constants» from participating in constant folding. This check is done on the bytecode rewriting phase of class initialization:
// Check if any final field of the class given as parameter is modified
// outside of initializer methods of the class. Fields that are modified
// are marked with a flag. For marked fields, the compilers do not perform
// constant folding (as the field can be changed after initialization).
//
// The check is performed after verification and only if verification has
// succeeded. Therefore, the class is guaranteed to be well-formed.
InstanceKlass* klass = method->method_holder();
u2 bc_index = Bytes::get_Java_u2(bcp + prefix_length + 1);
constantPoolHandle cp(method->constants());
Symbol* ref_class_name = cp->klass_name_at(cp->klass_ref_index_at(bc_index));
if (klass->name() == ref_class_name) {
Symbol* field_name = cp->name_ref_at(bc_index);
Symbol* field_sig = cp->signature_ref_at(bc_index);
fieldDescriptor fd;
if (klass->find_field(field_name, field_sig, &fd) != NULL) {
if (fd.access_flags().is_final()) {
if (fd.access_flags().is_static()) {
if (!method->is_static_initializer()) {
fd.set_has_initialized_final_update(true);
}
} else {
if (!method->is_object_initializer()) {
fd.set_has_initialized_final_update(true);
}
}
}
}
}
}
The only restriction that JVM HotSpot checks is that the final
field should not be modified outside of the class that the final
field is declared at.
Введение
Возникают ситуации, когда необходимо запретить вносить изменения состояния или поведения, показать завершенность.
В чем это проявляется в Java
? Бывают ситуации, когда необходимо запретить наследование от класса, переопределение некоторых методов в классе или изменение значения переменной.
Например, объявление константы.
Ключевое слово final
означает завершенность и применимо к классам, методам и переменным.
Применение
Завершенность класса
Применение final
по отношению к классу объявляет класс завершенным, т.е запрещает дальнейшее наследование от такого класса.
public final class FileUtils { // some code } // compilation error class FileUtilsExt extends FileUtils { }
Это может быть полезным для написания классов, содержащих только статические методы, так называемых классов-утилит.
Пример из JDK
:
public final class Math { // code }
Объявление класса завершенным неявно делает завершенными и все его методы.
Вопрос:
Возможно ли одновременное объявление класса как abstract
и final
?
Ответ:
Нет, такое объявление недопустимо. Это логично, так как ключевое слово abstract
говорит о том, что класс не является завершенным, в то время как final
индификатор того, что класс полностью завершен.
Завершенность метода
Применение final
по отношению к методу объявляет метод завершенным, т.е запрещает дальнейшее переопределние такого метода.
public class Person { final void hello() { System.out.println("Hello!"); } // some code } // compilation error class Employee extends Person { @Override void hello() { System.out.println("Hello Employee!"); } }
Это полезно, когда вы допускаете использование класса в наследовании, но конкретное поведение переопределять хотите запретить.
Не имеет смысла объявлять метод private final
так как private
метод не виден в наследниках, соответственно не может быть переопределен.
Также конструктор не может быть объявлен как final
, что в принципе логично.
Завершенность переменной
Переменная может быть объявлена как final
, что позволяет предотвратить ее изменение.
Для переменных примитивного типа это означает, что однажды присвоенное значение не может быть изменено.
Для ссылочных переменных это означает, что после присвоения объекта, нельзя изменить ссылку на данный объект. Но сам объект, его состояние, изменить можно!
Свойство класса
Переменная класса, объявленная как final
, но не являющаяся static
, должна инициализироваться при объявлении или в теле конструктора, или блоке инициализации, иначе произойдет ошибка компиляции.
public class ArrayList { private final int size; private final String type = "ArrayList"; private final String[] data; // compilation error public ArrayList(int size) { this.size = size; } }
Применение final
к полям класса позволяет создавать неизменяемые(immutable) объекты. Т.е объекты, внутреннее состояние которых не изменяется.
Грубо говоря, это объекты с правами только на чтение. Это очень удобно, так как позвоялет использовать объекты в разных потоках исполнения, ведь они потокобезопасны.
Вопрос:
Пусть есть класс, где все поля объявлены как final
. Как вы напишите набор getter
-ов и setter
-ов дял такого класса?
Ответ:
Если все поля объявлены как final
, то никаких setter
-ов в таком классе быть не может, так как все поля будут проинициализированы один раз при объявлении или в теле конструктора, или блоке инициализации.
Переменные, являющиеся полями класса и объявленные как static
обязаны быть проинициализированы сразу или в статическом блоке инициализации:
public class ArrayList { static final String TYPE; static { type = "ArrayList"; } }
Обратите внимание на наименование переменной — это константа. Константами в Java
принято называть public static final
переменные класса.
Про константы и их оформление можно прочесть тут.
Константы часто используются для борьбы c магическими (или волшебными) числами, то есть непонятно что означающими числами или строками. Например, следующий код содержит магическое число 9.8
(фу, как грубо):
public final class PhysicUtil { public static double getVelocity(double time) { return time * 9.8; } }
Понятно, что это константа, обозначающая ускорение свободного падения на поверхности Земли. Но понятно становится только тем, кто понимает контекст задачи и удобнее было, если бы мы константное выражение использовали с именем:
public final class PhysicUtil { public final static double EARTH_ACCELERATION = 9.8; public static double getVelocity(double time) { return time * EARTH_ACCELERATION; } }
Подобный подход также облегчил жизнь разработчику и пользователю, если бы в классе выше было несколько методов, использующих это число.
Локальная переменная
Локальная переменная, объявленная как final
, должна быть проинициализирована до момента ее использования, иначе возникнет ошибка компиляции:
void hello() { final int salary; final String greeting = "Hello"; System.out.println(salary); // compilation error System.out.println(greeting); }
Модификатор final
делает переменную константой в локальной области видимости объявления.
void hello() { final int salary = 80; System.out.println(salary); salary = 90; // compilation error }
Также, final
может быть применен и к аргументам методов:
void hello(final salary) { System.out.println(salary); salary = 90; // compilation error }
Такой подход гарантирует, что ссылка не будет изменена в теле метода.
Рекомендации
Рекомендация по использованию final
схожа с советом по использованию private
: все, что можно, делайте final
.
Если объект планируется использовать в разных потоках исполнения, то сделайте его неизменяемым, объявив все поля final
.
Если класс не должен участвовать в наследовании, то лишите его этой возможности.
Контролируйте то, что можно переопределять, а что нельзя.
Мой совет: старайтесь и ссылки в методе, и переменные в теле метода также объявлять как final
. Это может спасти вас от случайного присваивания ссылке не того значения.
Хотя некоторые считают такое повсеместное использование неправильным, так как такое обилие final
делает код более многословным и трудночитаемым, но мне кажется, что плюсов от использования больше, чем минусов от многословности.
В таких языках как Scala
рекомендуемый способ объявления переменной выглядит как val
, который как раз таки работает как объявление переменной с помощью final
. Со временм я уверен подобный способ объявления будет и в Java
.
Подводные камни
Главное, что надо помнить — это то, что, когда вы работаете с ссылочными переменными и объявляете такую переменную как final
, то после присвоения объекта, нельзя изменить ссылку на данный объект. Но сам объект, его состояние, изменить можно!
public class StringArrayList { final String[] arr; public StringArrayList() { this.arr = new String[10]; arr[0] = "World"; } } public class Main { public static void main() { StringArrayList list = new StringArrayList(); System.out.println(StringArrayList.arr[0]); // print "World" // modify array list.arr[0] = "Hello" System.out.println(StringArrayList.arr[0]); // print "Hello" list.arr = new String[15]; // compilation error } }
Если вы разберете пример выше, то увидите, что если вы объявили ссылку на изменяемый объект как final
, то изменить значение ссылки нельзя — компилятор защищает эту ссылку, ведь вы объявили ее финальной. Но сам объект(если до него есть доступ) изменить можно!
Это как если вы прикрутили намертво полку к стене. Саму полку уже не изменить и не снять, но вещи оттуда взять или положить можно. Разумеется, если прав доступа до такой полки хватит!
Модификатор final — это способ, с помощью которого вы можете контролировать работу своей программы и ее составных частей. Это один из Ваших инструментов:
Суть модификатора final — сделать дальнейшее изменение объекта невозможным. С английского «final» можно перевести как «последний, окончательный»:
Вы можете применять этот модификатор тремя способами: для класса, для поля (переменной) и для метода.
Final для полей
Если вы хотите, чтобы после инициализации никто не мог бы изменить вашу переменную, напишите слово «final»:
public class Test { public static void main(String[] args) { final int I = 1; System.out.println(I); } } |
Или так:
public class Test { final int I = 1; public static void main(String[] args) { System.out.println(I); } } |
Теперь, изменить переменную нельзя. Если вы попробуете поменять значение, то получите ошибку:
Тем не менее, вы не должны сразу задавать значение переменной. Суть в том, что первое заданное значение меняться не будет. Например, такой код будет работать:
public class Test { public static void main(String[] args) { final int I; I = 10; System.out.println(I); } } |
Но этот не будет:
И этот тоже 🙂
*Обратите внимание: переменные с final — это константы. При этом их принято писать заглавными буквами — тут CamelStyle не работает :
Final для методов
К методам тоже можно применить модификатор final:
Это будет значить, что при наследовании данный метод нельзя переопределить:
Final для классов
Модификатор final может применяться к классам тоже. Это будет означать, что нельзя создать наследников этого класса:
Из-за того, что класс объявляется final, можно сказать, что все его методы тоже становятся final — их нельзя переопределить, как и в прошлом примере:
Вот, теперь Вы и сами можете применять модификатор final в своем коде!
Надеемся, что наша статья была Вам полезна. Также есть возможность записаться на наши курсы по Java в Киеве. Обучаем с нуля. Детальную информацию Вы можете найти у нас на сайте.
Final — это ключевое слово в Java, которое можно применять к переменным, методам и классам для ограничения их поведения. В этом посте представлен обзор поведения переменных, методов и классов в Java при применении ключевого слова final. Давайте подробно обсудим каждый из них:
1. Конечная переменная
Мы не можем изменить значение конечной переменной после ее инициализации. Конечная переменная отличается от константы, поскольку значение конечной переменной не обязательно известно во время компиляции.
Конечная переменная может быть инициализирована только один раз либо с помощью инициализатора, либо с помощью оператора присваивания. Если переменная final не инициализируется во время объявления, она должна быть инициализирована внутри конструктора класса, в котором она объявлена. Такая переменная также называется пустой конечной переменной. Любая попытка установить пустую конечную переменную вне конструктора приведет к ошибке компиляции.
Точно так же, если статический final переменная не инициализируется при объявлении, то она должна быть инициализирована внутри блока статического инициализатора класса, в котором она объявлена. Такая переменная называется пустой статической конечной переменной. Любая попытка установить пустую статическую конечную переменную вне статического инициализатора приведет к ошибке компиляции.
Рассмотрим следующий код. Здесь любая попытка переназначить x
, y
, или же z
приведет к ошибке компиляции.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class Point { public final int x = 1; // конечная переменная public final int y; // пустая конечная переменная public static final int z; // пустая статическая конечная переменная // Конструктор public Point() { y = 2; } // Статический блок инициализатора static { z = 3; } } class Main { public static void main(String[] args) { Point pt = new Point(); System.out.println(«(« + pt.x + «,» + pt.y + «,» + pt.z + «)»); } } |
Скачать Выполнить код
результат:
(1,2,3)
Если конечная переменная содержит ссылку на объект, то компоненты объекта могут быть изменены операциями над объектом. Тем не менее, переменная всегда будет ссылаться на один и тот же объект.
Чтобы проиллюстрировать, что окончательность не гарантирует неизменности, рассмотрим следующий код, где мы заменили x
, y
, z
переменные с одним Position
объект с тремя свойствами x
, y
, а также z
. В настоящее время Position
объект не может быть назначен, но три свойства могут быть назначены, если они сами не являются окончательными.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
class Position { // неконечные поля public int x, y, z; // Конструктор public Position(int x, int y, int z) { this.x = x; this.y = y; this.z = z; } @Override public String toString() { return «(« + x + «,» + y + «,» + z + «)»; } } class Point { // позиция окончательная public final Position pos; // Конструктор public Point(int x, int y, int z) { pos = new Position(x, y, z); } public void modify() { // раскомментирование следующей строки приводит к ошибке компиляции // pos = new Position(10, 20, 30); // однако это разрешено pos.x = 10; pos.y = 20; pos.z = 30; } } class Main { public static void main(String[] args) { Point pt = new Point(1, 2, 3); System.out.println(pt.pos); pt.modify(); System.out.println(pt.pos); } } |
Скачать Выполнить код
результат:
(1,2,3)
(10,20,30)
2. Заключительный урок
Если класс объявлен в Java как final, то он не может быть расширен. Последний класс используется для безопасного и эффективного кода. Несколько классов в стандартной библиотеке Java являются окончательными. Например, System
класс в java.lang
пакет окончательный. String
класс также является окончательным в Java.
Чтобы проиллюстрировать поведение конечного класса, рассмотрим следующий код, который выдает ошибку компиляции, поскольку класс B
пытается расширить окончательный класс A
.
final class A {} class B extends A {} // запрещенный class Main { public static void main(String[] args) {} } |
Скачать Выполнить код
Вывод (ошибка компиляции):
Main.java:3: error: cannot inherit from final A
class B extends A { // error!
^
3. Окончательный метод
Если метод объявлен как final в Java, то он не может быть переопределен ни одним подклассом класса, в котором он объявлен. Это используется для предотвращения неожиданного поведения подкласса, изменяющего метод, который может иметь решающее значение для метода или согласованности класса.
Чтобы проиллюстрировать поведение метода final, рассмотрим следующий код, который выдает ошибку компиляции:
class A { public final void fun() {} } class B extends A { public void fun() {} // запрещенный } class Main { public static void main(String[] args) {} } |
Скачать Выполнить код
Вывод (ошибка компиляции):
Main.java:8: error: fun() in B cannot override fun()
in A
public void fun() {
^
overridden method is final
Это все, что касается ключевого слова final в Java.
Ссылка: Финал (Java) — Википедия
Спасибо за чтение.
Пожалуйста, используйте наш онлайн-компилятор размещать код в комментариях, используя C, C++, Java, Python, JavaScript, C#, PHP и многие другие популярные языки программирования.
Как мы? Порекомендуйте нас своим друзьям и помогите нам расти. Удачного кодирования 🙂
Содержание
- 1. Какое назначение использования спецификатора final в программах на Java?
- 2. В каких элементах языка программирования Java может применяться спецификатор final?
- 3. Какими способами можно инициализировать неизменные данные в классе, которые объявлены с спецификатором final?
- 4. Какое отличие между неизменными и изменяемыми данными? Какие особенности неизменных данных в классе?
- 5. Пример, который демонстрирует применение спецификатора final с одиночными данными (переменными)
- 6. Каким образом осуществляется начальная инициализация final-данных с помощью конструктора? Пример
- 7. Что произойдет, если данные, которые объявляются со спецификатором final, не инициализировать значениями в классе?
- 8. Какие особенности неизменных данных, которые объявлены как статические (static)?
- 9. Что такое пустые константы?
- 10. Могут ли статические (static) поля данных класса быть пустой константой?
- 11. Что такое неизменные аргументы? Пример
- 12. Для чего используются неизменные методы?
- 13. Что такое inline-методы?
- 14. Пример объявления и использования неизменных методов
- 15. Какие особенности использования спецификаторов final и private для методов?
- 16. Может ли неизменный метод быть объявлен как статический (static)?
- 17. Что такое неизменные классы? В каких случаях целесообразно применять неизменные классы? Пример
- Связанные темы
Поиск на других ресурсах:
1. Какое назначение использования спецификатора final в программах на Java?
Спецификатор final предназначен для объявления неизменных данных, методов и классов. Неизменные данные, методы или классы не могут изменять свое значение или программный код на протяжении выполнения всей программы.
Если при объявлении некоторой переменной применяется спецификатор final, то эта переменная автоматически становится константой. Попытка изменить значение final-константы приведет к ошибке.
⇑
2. В каких элементах языка программирования Java может применяться спецификатор final?
Спецификатор final может применяться к таким элементам языка как:
- данные (константы, переменные);
- методы;
- классы.
⇑
3. Какими способами можно инициализировать неизменные данные в классе, которые объявлены со спецификатором final?
Существуют два способа установить начальное значение неизменных данных:
- с помощью непосредственного присваивания (=) значения при объявлении final-поля данных класса;
- с помощью конструктора класса. Этот способ подходит только для нестатических неизменных данных. Сначала в классе объявляется поле класса со спецификатором final. Это поле называется пустой константой. Затем эта пустая константа обязательно инициализируется значением в конструкторе класса. Количество перегруженных конструкторов, которые инициализируют пустые константы, может быть любым.
⇑
4. Какое отличие между неизменными и изменяемыми данными? Какие особенности неизменных данных в классе?
Между неизменными данными и изменяемыми данными есть два следующих отличия:
- неизменные данные объявляются со спецификатором final;
- неизменные данные получают значение только один раз при их объявлении или с использованием конструктора класса. Данные, которые изменяются, могут получать разные значения сколько угодно. То есть, неизменные данные нельзя использовать в левой части оператора присваивания = (в отличие от данных, которые изменяются).
⇑
5. Пример, который демонстрирует применение спецификатора final с одиночными данными (переменными)
Объявляется класс ConstData, содержащий неизменные данные в виде переменных a, c. При попытке изменить значения a, c компилятор выдает ошибку. Действие спецификатора final распространяется и на статические переменные (переменная c). Ниже приведен программный код класса ConstData
// класс, который содержит неизменные данные class ConstData { public final int a=32; // переменная a имеет неизменное значение public int b=50; // переменная, значение которой можно изменять public final static double c=3.85; // значение статического члена c есть неизменным // метод, который изменяет значение b public void Change() { // a = 45; - ошибка, нельзя изменять значение final-переменной b = 120; // это работает, так как b объявленная без final //ConstData.c = 320.53; - ошибка, переменная c объявлена как final } }
Следующий программный код демонстрирует применение класса ConstData.
// использование объекта класса ConstData ConstData fd = new ConstData(); fd.Change(); int t = fd.b; // t = 120 t = fd.a; // t = 32, final-данные можно только читать // нельзя изменять значение статического члена ConstData.c // ConstData.c = 230.55; - ошибка! double d = ConstData.c; // d = 3.85
⇑
6. Каким образом осуществляется начальная инициализация final-данных с помощью конструктора? Пример
Неизменные нестатические данные, которые были объявлены со спецификатором final, можно инициализировать с помощью конструктора при условии, что при объявлении в классе, этим данным не было присвоено ни одно значение.
Например. Объявляется класс SetConstData, в котором объявляются разные виды данных:
- обычные данные (переменные), значения которых можно изменять;
- неизменные нестатические данные, которые объявлены с использованием спецификатора final;
- неизменные статические данные, которые объявлены с объединением спецификаторов static и final.
Класс также содержит объявление трех конструкторов, которые инициализируют начальными значениями те неизменные (final) данные, которые при объявлении не получили никаких значений.
Реализация класса имеет следующий вид:
// класс, который содержит различные виды данных class SetConstData { public final int a; // константу a обязательно нужно инициализировать в конструкторе public int b=50; // переменная, значение которой можно изменять много раз public final static double c=0; // значение статического члена c инициализируется при объявлении public final char d = 'Z'; // константа d уже инициализирована, в конструкторе ее нельзя инициализировать public final int e = 25; // присваивается значение при объявлении public final boolean f; // обязательно должно присваиваться значение в конструкторе // конструктор, который изменяет значения a, b, f SetConstData() { a = 100; // можно установить только один раз значение final-переменной a b = 200; // SetConstData.c = 300.0; // запрещено инициализировать final-переменную d, так как она уже инициализирована // d = 'F'; - ошибка // обязательно нужно 1 раз инициализировать final-константу f = true; } // второй конструктор, который инициализирует неизменные данные SetConstData(int _a, int _b, boolean _f) { // можно инициализировать только неизменные данные a, f a = _a; b = _b; f = _f; } // третий конструктор, который инициализирует неизменные данные SetConstData(int _a, boolean _f) { a = _a; f = _f; } // метод, который изменяет значение b public void Change() { // a = 45; - ошибка, нельзя изменять значение final-переменной b = 120; // это работает, так как b объявлена без final //FinalData.c = 320.53; - ошибка, переменная c объявлена как final } }
В вышеприведенном коде, неизменные данные (поля) a и f есть пустыми константами, которые обязательно должны быть инициализированы в конструкторе класса.
Использование класса SetConstData может быть следующим:
// Использование разных видов конструкторов для инициализации неизменных данных //SetConstData.c = 32309; - нельзя присваивать final-статическим данным значения double d = SetConstData.c; // d = 0.0, читать неизменные статические данные можно // создать экземпляр класса SetConstData с использованием конструктора без параметров SetConstData sd = new SetConstData(); boolean f = sd.f; // f = true int t = sd.a; // t = 100 t = sd.e; // t = 25 // создать экземпляр SetConstData с использованием конструктора с 3 параметрами SetConstData sd2 = new SetConstData(15,35,false); // инициализируются неизменные данные t = sd2.a; // t = 15 t = sd2.b; // t = 35 f = sd2.f; // f = false // создать экземпляр SetConstData с использованием конструктора с 2 параметрами SetConstData sd3 = new SetConstData(9, true); t = sd3.a; // t = 9 f = sd3.f; // f = true
⇑
7. Что произойдет, если данные, которые объявляются со спецификатором final, не инициализировать значениями в классе?
Если в классе объявляются final-данные, то они должны быть один раз инициализированы некоторым значением. Если не инициализировать final-данные ни одним из известных способов, то компилятор выдаст предупреждение о возможной ошибке.
Например. В нижеследующем классе объявляется final-поле класса
// инициализация class InitFinalData { final double pi = 3.1415; final int d; // значение d неинициализировано - может быть ошибка }
Компилятор Java такое описание пропускает с предупреждением
The blank final field d may not have been initialized
что означает, что данные могли быть неинициализированы.
⇑
8. Какие особенности неизменных данных, которые объявлены как статические (static)?
В сравнении с нестатическими, для статических неизменных данных можно выделить следующие особенности:
- статические неизменные данные объявляются с объединением спецификаторов final static;
- значение статического неизменного члена данных инициализируется при объявлении. Нельзя инициализировать значение статического неизменного члена данных в конструкторе класса;
- статическому неизменному члену данных класса значение присваивается только 1 раз при объявлении.
⇑
9. Что такое пустые константы?
Пустые константы – это поля класса, которые объявляются со спецификатором final но которые не инициализированы начальным значением (которым не было присвоенное начальное значение).
Значение пустой константы обязательно должны инициализироваться в конструкторе класса.
⇑
10. Могут ли статические (static) поля данных класса быть пустой константой?
Нет, не могут. Статические поля данных класса объявляются со спецификатором static. Во время объявления им сразу должно присваиваться некоторое значение, например
final static int si = 25; final static double pi = 3.1415; final static char c = '0'; final static int t; // это может быть ошибка, значение t неинициализировано
⇑
11. Что такое неизменные аргументы? Пример
Неизменные аргументы – это аргументы, которые, при передаче в метод, объявляются со спецификатором final. Неизменные аргументы объявляются при реализации метода. Значение неизменного аргумента в методе изменять нельзя. Если попробовать изменить значение неизменного аргумента, то выйдет ошибка компиляции.
Например. Объявляется класс FinalArguments, содержащий два метода которые получают final-аргументы. Первый метод Power() получает два неизменных (final) параметры x, y. Метод возводит x в степень y. Второй метод Sqr() возвращает квадрат числа, которое есть входным параметром x, который объявляется как неизменный (final).
Программный код класса FinalArguments следующий
class FinalArguments { // возводит x в степень y public double Power(final int x, final int y) { double res = 1.0; for (int i=0; i<y; i++) res = res * x; return res; } // возвращает квадрат числа public double Sqr(final double x) { return x*x; } }
Ниже приведено использование методов класса FinalArguments
// использование методов класса FinalArguments FinalArguments fa = new FinalArguments(); double d; d = fa.Power(8, 5); // d = 32768.0 d = fa.Sqr(5); // d = 25.0
Если метод Sqr() в классе FinalArguments переписать, например, следующим образом
public double Sqr(final double x) { x = x*x; // это есть ошибка, поскольку изменяется значение x return x; }
то компилятор выдаст ошибку
The final local variable x cannot be assigned
Это происходит по той причине, что в методе Sqr() осуществляется попытка изменить значение неизменного аргумента x.
⇑
12. Для чего используются неизменные методы?
В языке программирования Java спецификатор final можно ставить перед объявлением метода. Такой метод называется неизменным методом.
Неизменный метод объявляется в следующих случаях:
- если нужно заблокировать метод в унаследованных классах. То есть, унаследованные классы не могут изменить содержание метода, объявленного со спецификатором final;
- если нужно рекомендовать компилятору, чтобы вызов метода был встроенным (inline). Однако, это есть только рекомендация. Компилятор сам определяет, использовать ли стандартный механизм вставки метода в код (занести аргументы в стек, обработать выполнение метода, вытянуть аргументы из стека, обработать полученный результат) или непосредственно вставить код метода в тело вызывающей программы.
⇑
13. Что такое inline-методы?
В большинстве языков программирования обычный вызов метода из некоторого кода требует выполнения следующей последовательносты обязательных операций:
- занести аргументы в стек (если метод имеет параметры);
- перейти к телу метода;
- выполнить код (тело) метода;
- возвратить управление из метода;
- удалить аргументы из стека;
- обработать возвращенное из метода значение.
Inline-методы – это методы, тело которых непосредственно вставляется в программный код вызывающей программы (метода). В этом случае отсутствуют лишние шаги при вызове метода: занесение аргументов в стек, переход и возврат из метода. Отсутствие лишних шагов приводит к повышению скорости выполнения вызванного метода. То есть, увеличивается общее быстродействие программы, которая вызвала метод.
Однако, если тело inline-метода есть довольно большим (содержит много кода), то это приводит к чрезмерному увеличению программы, которая его вызвала.
⇑
14. Пример объявления и использования неизменных методов
Задан класс A, который есть базовым для класса B. В классе A объявляются
- одна внутренняя переменная a;
- два неизменных метода Set() и Get(). Эти методы запрещено переопределять в унаследованном классе B;
- два обычных метода SetA() и GetA(), которые можно переопределять в унаследованном классе.
Объявление классов A и B имеет вид:
// класс A - базовый класс class A { public int a; // методы, которые запрещено переопределять в унаследованных классах final void Set(int _a) { a = _a; } final int Get() { return a; } // методы, которые разрешено переопределять в унаследованных классах void SetA(int _a) { a = _a; } int GetA() { return a; } } // класс B наследует (расширяет) возможности класса A class B extends A { public int b; // методы класса B, переопределяют методы класса A void SetA(int _a) { a = _a; } int GetA() { return a; } // запрещено объявлять методы Set() и Get() в классе B // void Set(int _a) { a = _a; } // int Get() { return a; } }
Использование экземпляров классов A и B может быть, например, следующим
// использование объектов классов A и B
A objA = new A(); objA.Set(5); // objA.a = 5 int t = objA.Get(); // t = 5 t = objA.GetA(); // t = 5 B objB = new B(); objB.Set(12); t = objB.Get(); // t = 12 objB.SetA(54); t = objB.Get(); // t = 54 t = objB.GetA(); // t = 54
Если в классе B попробовать объявить методы Set() и Get() следующим образом
class B extends A { ... // запрещено объявлять методы Set() и Get() в классе B void Set(int _a) { a = _a; } int Get() { return a; } }
то компилятор выдаст ошибку
Cannot override the final method from A
⇑
15. Какие особенности использования спецификаторов final и private для методов?
Спецификатор private делает метод класса невидимым. То есть, унаследованные классы не могут его переопределить или изменить точно также как при объявлении final-метода. Это означает, что совместное объединение этих спецификаторов создает избыточность в объявлении. Если метод объявлен как private, то нецелесообразно к его объявлению добавлять спецификатор final, так как это ничего не изменит.
Если в классе объявлен private-метод с именем, например ABC(), то в унаследованном классе можно создавать метод с таким самым именем ABC(). Однако, метод ABC() унаследованного класса не имеет ничего общего с методом ABC() базового класса. Метод ABC() унаследованного класса не переопределяет метод ABC() базового класса – это методы разных классов, в которых просто совпадает имя.
⇑
16. Может ли неизменный метод быть объявлен как статический (static)?
Да. В этом случае, вызов статического неизменного метода из класса такой же как и обычного статического метода (без спецификатора final).
⇑
17. Что такое неизменные классы? В каких случаях целесообразно применять неизменные классы? Пример
Неизменный класс – это класс, который не может использоваться как базовый при наследственности. Неизменный класс не может быть унаследован другим классом. Перед объявлением неизменного класса используется спецификатор final, как показано ниже
// неизменный класс final class ClassName { // тело класса // ... }
Неизменные классы используются в случаях, когда нужно запретить любое наследование этого класса по соображениям безопасности. Структура неизменного класса остается постоянной. Все методы неизменного класса также есть неизменными независимо от наличия (отсутствия) спецификатора final.
Пример. Пусть задано объявление неизменного класса A:
// неизменный класс A final class A { int a; }
После такого объявления, объявить класс, который наследует (расширяет) класс A нельзя. Если попробовать это сделать, то компилятор выдаст ошибку.
При следующей попытке объявить класс B, который наследует (расширяет) возможности класса A
// нельзя наследовать класс A class B extends A { int b; }
компилятор выдает ошибку:
The type B cannot subclass the final class A
что значит
Тип B не может наследовать конечный класс A
Использование final-класса A в другом программном коде может быть следующим:
// объявить объект класса A A objA = new A(); objA.a = 32; int t = objA.a; // t = 32
⇑
Связанные темы
- Применение классов в программах на Java. Определение класса и объекта класса. Примеры
- Передача параметров в методах класса. Передача переменных примитивных типов и объектов в метод в качестве параметра
- Статические члены данных класса. Статические методы. Ключевое слово static
⇑