最终关键字的工作原理

在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修饰符在调用构造函数完成时(即,必须在构造函数中初始化它)时强制引用的初始化。

当你在线初始化一个属性时,它会在你为构造函数定义的代码运行之前被初始化,所以你会得到以下结果:

  • 如果foostatic ,那么foo = new ArrayList()将在您为类定义的static{}构造函数执行之前执行
  • 如果foo不是staticfoo = 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异常。

  1. 由于最终变量是非静态的,因此可以在构造函数中进行初始化。 但是如果你使它成为静态的,它不能被构造函数初始化(因为构造函数不是静态的)。
  2. 添加到列表不会停止列表最后。 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 } 

可以用于不应该被改变的变量。