在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。然后它就变成了类树层次结构的叶级,没有人可以进一步扩展它。避免庞大的类层次结构是一个很好的实践。

其他回答

值得一提的是一些简单的定义:

类/方法

您可以将类的部分或全部方法声明为final,以指示该方法不能被子类覆盖。

变量

一旦final变量初始化,它总是包含相同的值。

Final基本上避免被任何东西(子类,变量“重赋”)覆盖/上写,这取决于情况。

这是一个很常见的面试问题。通过这些问题,面试官试图了解你对对象行为的理解程度,包括构造函数、方法、类变量(静态变量)和实例变量。 如今,面试官们会问另一个最受欢迎的问题: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,但是对于面试,我们必须做好准备。

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

如果你让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将是相同的,只是它的内容被改变了)。

“最终变量只能赋值一次”

*反思* -“哇哦,等等,拿着我的啤酒”。


在两种情况下冻结final字段:

构造函数结束。 当反射设置字段的值时。(想要多少次就有多少次)


让我们犯法吧

public class HoldMyBeer 
{
    final int notSoFinal;
    
    public HoldMyBeer()
    {
       notSoFinal = 1;
    }

    static void holdIt(HoldMyBeer beer, int yetAnotherFinalValue) throws Exception
    {
       Class<HoldMyBeer> cl = HoldMyBeer.class;
       Field field = cl.getDeclaredField("notSoFinal");
       field.setAccessible(true);
       field.set(beer, yetAnotherFinalValue);
    }

    public static void main(String[] args) throws Exception 
    {
       HoldMyBeer beer = new HoldMyBeer();
       System.out.println(beer.notSoFinal);
       holdIt(beer, 50);
       System.out.println(beer.notSoFinal);
       holdIt(beer, 100);
       System.out.println(beer.notSoFinal);
       holdIt(beer, 666);
       System.out.println(beer.notSoFinal);
       holdIt(beer, 8888);
       System.out.println(beer.notSoFinal);
    }    
}

输出:

1
50
100
666
8888

“final”字段已经被分配了5个不同的“final”值(注意引号)。它可以一直被赋予不同的值……

为什么?因为反射就像chucknorris,如果它想改变一个初始化的final字段的值,它就会这样做。有人说他自己就是那个将新值推入堆栈的人:

Code:
   7: astore_1
  11: aload_1
  12: getfield                
  18: aload_1
  19: bipush        50        //wait what
  27: aload_1
  28: getfield                
  34: aload_1
  35: bipush        100       //come on...
  43: aload_1
  44: getfield                
  50: aload_1
  51: sipush        666      //...you were supposed to be final...
  60: aload_1
  61: getfield                
  67: aload_1
  68: sipush        8888     //ok i'm out whatever dude
  77: aload_1
  78: getfield