在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是Java中限制用户的保留关键字,它可以应用于成员变量、方法、类和局部变量。Final变量通常在Java中使用static关键字声明,并被视为常量。例如:

public static final String hello = "Hello";

当我们在变量声明中使用final关键字时,存储在该变量中的值不能在后面更改。

例如:

public class ClassDemo {
  private final int var1 = 3;
  public ClassDemo() {
    ...
  }
}

注意:声明为final的类不能扩展或继承(也就是说,不能有超类的子类)。同样值得注意的是,声明为final的方法不能被子类重写。

使用final关键字的好处将在本文中讨论。

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

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

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

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

你总是可以初始化一个final变量。编译器确保你只能做一次。

注意,调用存储在final变量中的对象的方法与final的语义无关。换句话说:final只与引用本身有关,而与被引用对象的内容无关。

Java没有对象不变性的概念;这是通过仔细设计对象来实现的,这是一个远不是微不足道的努力。

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

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


在两种情况下冻结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                

我想在这里写一个更新的和深入的回答。

Final关键字可以在几个地方使用。

final类意味着没有其他类可以扩展这个final类。当Java运行时(JRE)知道一个对象引用的类型是final类(比如F)时,它就知道该引用的值只能是F类型。

Ex:

F myF;
myF = new F();    //ok
myF = someOther;  //someOther cannot be in type of a child class of F.
                  //because F cannot be extended.

因此,当它执行该对象的任何方法时,该方法不需要在运行时使用虚拟表进行解析。即不能应用运行时多态性。所以运行时不会担心这个。这意味着它节省了处理时间,从而提高了性能。

方法

任何类的final方法意味着任何扩展该类的子类都不能重写该final方法。因此,此场景中的运行时行为也与前面提到的类的行为完全相同。

字段,局部变量,方法参数

如果将上述任何类型指定为final,则意味着该值已经最终确定,因此不能更改该值。

Ex:

对于字段,为本地参数

final FinalClass fc = someFC; //need to assign straight away. otherwise compile error.
final FinalClass fc; //compile error, need assignment (initialization inside a constructor Ok, constructor can be called only once)
final FinalClass fc = new FinalClass(); //ok
fc = someOtherFC; //compile error
fc.someMethod(); //no problem
someOtherFC.someMethod(); //no problem

对于方法参数

void someMethod(final String s){
    s = someOtherString; //compile error
}

这仅仅意味着不能更改最终参考值的值。也就是说,只允许一次初始化。在这个场景中,在运行时,由于JRE知道值不能更改,它将所有这些最终确定的值(最终引用的值)加载到L1缓存中。因为它不需要一次又一次地从主存中加载。否则它会加载到L2缓存,并从主存中进行时间到时间的加载。所以这也是一种性能提升。

因此,在上述三种情况下,当我们没有在可以使用的地方指定最后的关键字时,我们不需要担心,编译器优化会为我们做这件事。编译器优化还为我们做了很多其他的事情。:)