在Java中,我们使用带有变量的final关键字来指定其不被更改的值。 但我看到你可以改变类的构造函数/方法的值。同样,如果变量是静态的,那么这是一个编译错误。

代码如下:

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);
  }
}

以上代码工作正常,没有错误。

现在将变量更改为静态:

private static final List foo;

现在它是一个编译错误。期末考试是怎么进行的呢?


当前回答

当你让它成为静态final时,它应该在静态初始化块中初始化

    private static final List foo;

    static {
        foo = new ArrayList();
    }

    public Test()
    {
//      foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }

其他回答

Final关键字有很多种用法:

final类不能被子类化。 final方法不能被子类重写 final变量只能初始化一次

其他用途:

在方法体中定义匿名内部类时, 在该方法范围内声明为final的所有变量为 从内部类内部访问

静态类变量将从JVM开始时就存在,并且应该在类中初始化。如果您这样做,错误消息将不会出现。

final关键字可以有两种不同的解释方式,这取决于它的使用对象:

值类型:对于int型,double型等,它将确保值不能改变,

引用类型:对于对象的引用,final确保引用永远不会改变,这意味着它总是引用同一个对象。它不能保证对象中被引用的值保持不变。

因此,final List<Whatever> foo;确保foo总是指向相同的列表,但该列表的内容可能会随着时间的推移而改变。

如果你让foo是静态的,你必须在类构造函数中初始化它(或者你定义它的内联),就像下面的例子。

类构造函数(不是实例):

private static final List foo;

static
{
   foo = new ArrayList();
}

内联:

private static final List foo = new ArrayList();

这里的问题不是最终修饰符如何工作,而是静态修饰符如何工作。

final修饰符强制在调用构造函数完成之前对引用进行初始化(即必须在构造函数中初始化它)。

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

如果foo是静态的,foo = new ArrayList()将在你为你的类定义的静态{}构造函数执行之前执行 如果foo不是静态的,foo = new ArrayList()将在运行构造函数之前执行

如果不内联初始化属性,final修饰符强制初始化它,并且必须在构造函数中初始化。如果你也有一个静态修饰符,你必须初始化属性的构造函数是类的初始化块:static{}。

您在代码中得到的错误来自于这样一个事实,即静态{}在加载类时运行,在您实例化该类的对象之前。因此,在创建类时,你还没有初始化foo。

可以将静态{}块视为Class类型对象的构造函数。在这里必须初始化静态final类属性(如果不是内联完成的话)。

注:

final修饰符仅对基本类型和引用保证const性。

当你声明一个final对象时,你得到的是该对象的最终引用,但对象本身不是常量。

当你声明一个final属性时,你真正实现的是,一旦你声明了一个特定目的的对象(就像你已经声明的final List),这个对象将被用于该目的:你不能将List foo更改为另一个List,但你仍然可以通过添加/删除项来改变你的List(你正在使用的List将是相同的,只是它的内容被改变了)。

假设你有两个储蓄罐,红的和白的。你把这些钱箱只分配给两个孩子,他们不允许交换他们的钱箱。所以你有红色或白色的钱箱(最终),你不能修改盒子,但你可以把钱放在你的盒子上。没人在乎(修改-2)。

这是一个很常见的面试问题。通过这些问题,面试官试图了解你对对象行为的理解程度,包括构造函数、方法、类变量(静态变量)和实例变量。 如今,面试官们会问另一个最受欢迎的问题:java 1.8的最终版本是什么?我将在最后在java 1.8中解释这个有效的final。

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 is an instance variable. When we create Test class object then the instance variable foo, will be copied inside the object of Test class. If we assign foo inside the constructor, then the compiler knows that the constructor will be invoked only once, so there is no problem assigning it inside the constructor. If we assign foo inside a method, the compiler knows that a method can be called multiple times, which means the value will have to be changed multiple times, which is not allowed for a final variable. So the compiler decides constructor is good choice! You can assign a value to a final variable only one time.

场景2

private static final List foo = new ArrayList();

foo is now a static variable. When we create an instance of Test class, foo will not be copied to the object because foo is static. Now foo is not an independent property of each object. This is a property of Test class. But foo can be seen by multiple objects and if every object which is created by using the new keyword which will ultimately invoke the Test constructor which changes the value at the time of multiple object creation (Remember static foo is not copied in every object, but is shared between multiple objects.)

场景3

t.foo.add("bar"); // Modification-2

以上修改-2来自你的问题。在上面的例子中,你没有改变第一个被引用的对象,但是你在foo中添加了允许的内容。如果你试图给foo引用变量赋值一个新的ArrayList(),编译器会报错。 如果你已经初始化了一个final变量,那么你不能改变它来引用一个不同的对象。(在本例中是ArrayList)

Final类不能被子类化 Final方法不能被覆盖。(此方法在超类中) Final方法可以被重写。(请按语法方式阅读。这个方法在一个子类中)

现在让我们看看在java 1.8中什么是有效的最终版本?

public class EffectivelyFinalDemo { //compile code with java 1.8
    public void process() {
        int thisValueIsFinalWithoutFinalKeyword = 10; //variable is effectively final
        
        //to work without final keyword you should not reassign value to above variable like given below 
        thisValueIsFinalWithoutFinalKeyword = getNewValue(); // delete this line when I tell you.
        
        class MethodLocalClass {
            public void innerMethod() {
                //below line is now showing compiler error like give below
                //Local variable thisValueIsFinalWithoutFinalKeyword defined in an enclosing scope must be final or effectively final
                System.out.println(thisValueIsFinalWithoutFinalKeyword); //on this line only final variables are allowed because this is method local class
                // if you want to test effectively final is working without final keyword then delete line which I told you to delete in above program.  
            }
        }
    }

    private int getNewValue() {
        return 0;
    }
}

如果你没有使用final关键字,上面的程序将在java 1.7或<1.8中抛出错误。effective final是方法局部内部类的一部分。我知道你很少在本地类中使用这样有效的final,但是对于面试,我们必须做好准备。