最终关键字的工作原理
在Java中,我们使用final
关键字和变量来指定它的值不被改变。 但是我发现你可以改变类的构造函数/方法中的值。 同样,如果变量是static
那么这是一个编译错误。
这里是代码:
import java.util.ArrayList; import java.util.List; class Test { private final List foo; public Test() { foo = new ArrayList(); foo.add("foo"); // Modification-1 } public static void main(String[] args) { Test t = new Test(); t.foo.add("bar"); // Modification-2 System.out.println("print - " + t.foo); } }
上面的代码工作正常,没有错误。
现在将该变量更改为static
:
private static final List foo;
现在这是一个编译错误。 这个final
如何真正起作用?
你总是被允许初始化一个final
变量。 编译器确保你只能做一次。
请注意,调用存储在final
变量中的对象的方法与final
的语义无关。 换句话说, final
只是关于引用本身,而不是关于引用对象的内容。
Java没有对象不可变性的概念; 这是通过精心设计的对象来实现的,是一个远非平凡的努力。
这是一个最喜欢的面试问题 。 有了这个问题,面试官就会试图找出你对构造函数,方法,类变量(静态变量)和实例变量的理解程度。
import java.util.ArrayList; import java.util.List; class Test { private final List foo; public Test() { foo = new ArrayList(); foo.add("foo"); // Modification-1 } public void setFoo(List foo) { //this.foo = foo; Results in compile time error. } }
在上面的例子中,我们为“Test”定义了一个构造函数,并给了它一个“setFoo”方法。
关于构造函数:通过使用new
关键字,每个对象创建只能调用一次 构造函数。 你不能多次调用构造函数,因为构造函数不是这样设计的。
关于方法:可以根据需要多次调用一个方法(甚至从不),编译器知道它。
情况1
private final List foo; // 1
foo
是一个实例变量。 当我们创建Test
类对象时,实例变量foo
将被复制到Test
类的对象中。 如果我们在构造函数中分配foo
,那么编译器就知道构造函数只会被调用一次,所以在构造函数中分配它没有任何问题。
如果我们在一个方法中分配foo
,编译器知道一个方法可以被多次调用,这意味着这个值将不得不多次改变,这是final
变量所不允许的。 所以编译器决定构造函数是不错的选择! 您只能将值分配给最终变量一次。
情景2
private static final List foo = new ArrayList();
foo
现在是一个静态变量。 当我们创建一个Test
类的实例时, foo
不会被复制到对象,因为foo
是静态的。 现在foo
不是每个对象的独立属性。 这是Test
类的一个属性。 但是foo
可以被多个对象看到,如果每个对象都是使用new
关键字创建的,这个关键字最终会调用Test
多重对象创建时改变值的构造函数(Remember static foo
不会被复制到每个对象中,但是在多个对象之间共享。)
情景3
t.foo.add("bar"); // Modification-2
以上Modification-2
是从你的问题。 在上面的例子中,你并没有改变第一个被引用的对象,但是你在foo
中添加了允许的内容。 如果您尝试将new ArrayList()
分配给foo
引用变量,编译器会抱怨。
规则如果你已经初始化一个final
变量,那么你不能改变它来引用一个不同的对象。 (在这种情况下ArrayList
)
最后的课程不能被分类
最终的方法不能被覆盖。 (这个方法是超类)
最终的方法可以覆盖。 (以语法方式阅读这个方法,这个方法在一个子类中)
Final关键字有多种使用方式:
- 最后一堂课不能分类。
- 最后的方法不能被子类覆盖
- 最终的变量只能被初始化一次
其他用法:
- 当一个匿名内部类被定义在一个方法体内时,所有在该方法范围内声明为final的变量都可以从内部类中访问
一个静态的类变量将从JVM的开始存在,并且应该在类中初始化。 如果你这样做,错误信息将不会出现。
final
关键字可以用两种不同的方式解释,具体取决于它的用途:
值类型:对于int
s, double
s等,它将确保值不能改变,
引用类型:为了引用对象, final
确保引用永远不会改变,这意味着它将始终引用同一个对象。 它不保证被引用的对象内部的值保持不变。
因此, final List<Whatever> foo;
确保foo
总是指向相同的列表,但是所述列表的内容可能随时间而改变。
如果你使foo
静态的,你必须在类的构造函数中初始化它,或者像下面的例子那样在内联的地方定义它。
类构造函数(不是实例):
private static final List foo; static { foo = new ArrayList(); }
一致:
private static final List foo = new ArrayList();
这里的问题不是final
修饰符如何工作,而是static
修饰符是如何工作的。
final
修饰符在调用构造函数完成时(即,必须在构造函数中初始化它)时强制引用的初始化。
当你在线初始化一个属性时,它会在你为构造函数定义的代码运行之前被初始化,所以你会得到以下结果:
- 如果
foo
是static
,那么foo = new ArrayList()
将在您为类定义的static{}
构造函数执行之前执行 - 如果
foo
不是static
,foo = new ArrayList()
将在您的构造函数运行之前执行
如果不初始化内联属性,则final
修饰符将强制初始化它,并且必须在构造函数中进行初始化。 如果你还有一个static
修饰符,你将不得不初始化属性的构造函数是类的初始化块: static{}
。
在代码中出现的错误是由于在类实例化之前,在加载类时运行static{}
。 因此,当创建类时,您将不会初始化foo
。
将static{}
块想象为一个Class
类型的对象的构造函数。 这是你必须做你的static final
类属性的初始化(如果没有内联)。
边注:
final
修饰符只保证原始类型和引用的一致性。
当你声明final
对象时,你得到的是对象的final
引用 ,但是对象本身并不是常量。
当你声明一个final
属性时你真正实现的是,一旦你为了你的特定目的而声明了一个对象(比如你声明的final List
),那么那个对象将被用于这个目的:你将不能将List foo
更改为另一个List
,但是您仍然可以通过添加/删除项目来更改List foo
(您正在使用的List
将是相同的,只是其内容已更改)。
这是一个很好的面试问题。 有时他们甚至会问你最后的对象和不可变的对象之间有什么区别。
1)当有人提到最后一个对象时,这意味着引用不能被改变,但是它的状态(实例变量)是可以改变的。
2)不可变的对象是一个状态不能改变的对象,但是它的引用可以被改变。 例如:
String x = new String("abc"); x = "BCG";
ref变量x可以改变为指向一个不同的字符串,但是“abc”的值不能被改变。
3)实例变量(非静态字段)在调用构造函数时被初始化。 所以你可以在构造函数中初始化你的变量值。
4)“但是我看到你可以改变类的构造函数/方法的值”。 – 你不能在一个方法中改变它。
5)在类加载期间初始化一个静态变量。 所以你不能在构造函数里初始化它,甚至在它之前也要完成它。 所以你需要在声明过程中给静态变量赋值。
假设你有两个存钱罐,红色和白色。 您只分配这些钱箱只有两个孩子,他们不允许交换他们的箱子。 所以你有红色或白色的钱盒(最后),你不能修改盒子,但你可以把钱放在你的box.Nobody cares(修改-2)。
值得一提的是一些直截了当的定义:
类/方法
你可以声明一些或所有类的方法是最终的。 您在方法声明中使用final关键字来指示该方法不能被子类覆盖。
变量
一旦分配了最终变量,它总是包含相同的值。
FINAL基本上避免被任何东西(子类,变量“reasign”)覆盖/替代,取决于具体情况。
final
是Java中的一个保留关键字,用于限制用户,它可以应用于成员变量,方法,类和局部变量。 最终变量通常在Java中用static
关键字声明,并被视为常量。 例如:
public static final String hello = "Hello";
当我们在变量声明中使用final
关键字时,存储在该变量中的值不能被后者改变。
例如:
public class ClassDemo { private final int var1 = 3; public ClassDemo() { ... } }
注意 :声明为final的类不能被扩展或继承(即不能有超类的子类)。 注意,声明为final的方法不能被子类覆盖。
在 这个线程 中解决使用final关键字的好处 。
当你使它静态最终它应该被初始化在一个静态的初始化块
private static final List foo; static { foo = new ArrayList(); } public Test() { // foo = new ArrayList(); foo.add("foo"); // Modification-1 }
final
关键字表示一个变量只能被初始化一次。 在你的代码中,你只执行一个final的初始化,所以条件满足。 这个语句执行foo
的独立初始化。 注意final
!=不可变的,它只意味着参考不能改变。
foo = new ArrayList();
当你将foo
声明为static final
,变量必须在类被加载时被初始化,并且不能依赖于实例化(aka调用构造函数)来初始化foo
因为静态字段必须可用而没有类的实例。 不能保证在使用静态字段之前调用构造函数。
当你在static final
场景下执行你的方法时,在这个时候在实例化t
之前加载了Test
类,没有实例化foo
这意味着它没有被初始化,所以foo
被设置为所有null
对象的默认null
。 在这一点上,我假设你的代码尝试添加一个项目到列表时,会抛出一个NullPointerException
异常。
- 由于最终变量是非静态的,因此可以在构造函数中进行初始化。 但是如果你使它成为静态的,它不能被构造函数初始化(因为构造函数不是静态的)。
- 添加到列表不会停止列表最后。
final
只是绑定到特定对象的引用。 你可以自由地改变那个对象的'状态',而不是对象本身。
最重要的是正确的。 此外,如果您不希望其他人从您的班级创建子班级,请将班级声明为最终班级。 然后它成为您的类树层次的叶级别,没有人可以进一步扩展它。 避免巨大的层次结构是一个很好的做法。
首先,你正在初始化(即第一次分配)foo的代码在这里:
foo = new ArrayList();
foo是一个对象(具有List类型),所以它是一个引用类型,而不是一个值类型(如int)。 因此,它保存了一个存储位置(例如0xA7D2A834)的引用,这些位置是存储List元素的地方。 这样的线
foo.add("foo"); // Modification-1
不要改变foo的值(这也是对内存位置的引用)。 相反,他们只是添加元素到引用的内存位置。 违反最后的关键字,你将不得不尝试重新分配foo如下:
foo = new ArrayList();
这会给你一个编译错误。
现在,以这种方式,考虑添加static关键字时会发生什么。
当你没有static关键字时,实例化这个类的每个对象都有它自己的foo副本。 因此,构造函数将一个值赋给foo变量的空白新副本,这非常好。
但是,当您拥有static关键字时,只有一个foo存在于与该类关联的内存中。 如果要创建两个或更多对象,构造函数将尝试每次重新分配一个foo,这违反了final关键字。
以下是使用final的不同上下文。
最终变量最终变量只能分配一次。 如果变量是一个引用,这意味着该变量不能被重新绑定来引用另一个对象。
class Main { public static void main(String args[]){ final int i = 20; i = 30; //Compiler Error:cannot assign a value to final variable i twice } }
最后的变量可以稍后赋值(不是强制赋值时声明),而是只赋值一次。
最终课最后一课不能扩展(继承)
final class Base { } class Derived extends Base { } //Compiler Error:cannot inherit from final Base public class Main { public static void main(String args[]) { } }
最终方法最后的方法不能被子类覆盖。
//Error in following program as we are trying to override a final method. class Base { public final void show() { System.out.println("Base::show() called"); } } class Derived extends Base { public void show() { //Compiler Error: show() in Derived cannot override System.out.println("Derived::show() called"); } } public class Main { public static void main(String[] args) { Base b = new Derived();; b.show(); } }
java中的final关键字用于限制用户。 java final关键字可以在很多情况下使用。 最后可以是:
变量,方法,类,。 final关键字可以和变量一起应用,最后一个没有值的变量被称为空白变量或者未初始化的最终变量。 它只能在构造函数中初始化。 空白的final变量也可以是静态的,只能在静态块中初始化。
Java最终变量
如果你把任何变量作为final,你不能改变final变量的值(它将是常量)。
最终变量的例子
有一个最终变量speedlimit,我们要改变这个变量的值,但不能改变,因为最后一个赋值的变量永远不能改变。
class Bike9{ final int speedlimit=90;//final variable void run(){ speedlimit=400; // this will make error } public static void main(String args[]){ Bike9 obj=new Bike9(); obj.run(); } }//end of class
Java最终课堂:
如果你把任何课程作为最终的,你不能扩展它。
最后一堂课的例子
final class Bike{} class Honda1 extends Bike{ //cannot inherit from final Bike,this will make error void run(){System.out.println("running safely with 100kmph");} public static void main(String args[]){ Honda1 honda= new Honda(); honda.run(); } }
Java最后的方法:
如果你使任何方法作为最终的,你不能覆盖它。
本田的run()函数中的final方法的例子不能覆盖Bike中的run()
class Bike{ final void run(){System.out.println("running");} } class Honda extends Bike{ void run(){System.out.println("running safely with 100kmph");} public static void main(String args[]){ Honda honda= new Honda(); honda.run(); } }
分享: http : //www.javatpoint.com/final-keyword
阅读所有的答案。
还有另一个用户案例, final
关键字可以用在方法参数:
public void showCaseFinalArgumentVariable(final int someFinalInt){ someFinalInt = 9; // won't compile as the argument is final }
可以用于不应该被改变的变量。