529

在 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这真的如何运作?

4

19 回答 19

618

这是一个最喜欢的面试问题。通过这个问题,面试官试图找出你对构造函数、方法、类变量(静态变量)和实例变量的对象行为的理解程度。

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构造函数,该构造函数会在多个对象创建时更改值(记住static foo不是在每个对象中复制,而是在多个对象之间共享.)

方案 3

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

以上Modification-2来自你的问题。在上述情况下,您不会更改第一个引用的对象,而是在foo其中添加允许的内容。如果您尝试将 a 分配new ArrayList()foo引用变量,编译器会抱怨。
规则如果你已经初始化了一个final变量,那么你不能改变它来引用一个不同的对象。(在这种情况下ArrayList

final类不能被子类化
final方法不能被覆盖。(此方法在超类中)
最终方法可以覆盖。(以语法方式阅读。此方法在子类中)

于 2013-03-27T10:08:12.330 回答
573

您始终可以初始化变量final。编译器确保您只能执行一次。

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

Java 没有对象不变性的概念;这是通过仔细设计对象来实现的,并且是一项非常艰巨的工作。

于 2013-03-27T09:03:50.473 回答
233

Final关键字有多种使用方式:

  • 最终不能被子类化。
  • 最终方法不能被子类覆盖
  • final变量只能初始化一次

其他用法:

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

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

于 2013-03-27T09:09:57.367 回答
66

关键字可以用final两种不同的方式解释,具体取决于它的用途:

值类型:对于ints、doubles等,会保证值不能改变,

引用类型:对于对象final的引用,确保引用永远不会改变,这意味着它将始终引用同一个对象。它不保证被引用的对象内部的值保持不变。

因此,final List<Whatever> foo;确保foo始终引用同一个列表,但所述列表的内容可能会随着时间而改变。

于 2013-03-27T09:12:38.107 回答
24

如果您制作foo静态,则必须在类构造函数(或定义它的内联)中对其进行初始化,如下例所示。

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

private static final List foo;

static
{
   foo = new ArrayList();
}

排队:

private static final List foo = new ArrayList();

这里的问题不是final修饰符如何工作,而是static修饰符如何工作。

修饰符在final对构造函数的调用完成时强制对引用进行初始化(即,您必须在构造函数中对其进行初始化)。

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

  • if foois static,foo = new ArrayList()将在static{}您为类定义的构造函数执行之前执行
  • 如果foo不是staticfoo = new ArrayList()将在您的构造函数运行之前执行

当您不内联初始化属性时,final修饰符会强制您对其进行初始化,并且您必须在构造函数中这样做。如果您还有一个static修饰符,则必须在其中初始化属性的构造函数是类的初始化块:static{}

您在代码中遇到的错误来自于在static{}加载类时运行的事实,在您实例化该类的对象之前。因此,在创建类时您还没有初始化foo

static{}块视为类型对象的构造函数Class。这是您必须初始化static final类属性的地方(如果没有内联完成)。

边注:

final修饰符仅确保原始类型和引用的 const-ness 。

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

声明属性时真正实现的final是,一旦您为特定目的声明了一个对象(如final List您已声明的那个),那么只有该对象将用于该目的:您将无法更改List foo为另一个List,但您仍然可以List通过添加/删除项目来更改您的项目(List您使用的项目将是相同的,只是其内容发生了变化)。

于 2013-03-27T09:06:28.713 回答
12

这是一个非常好的面试问题。有时他们甚至可能会问你最终对象和不可变对象有什么区别。

1)当有人提到最终对象时,意味着引用不能改变,但它的状态(实例变量)可以改变。

2) 不可变对象是其状态不能改变,但其引用可以改变的对象。前任:

    String x = new String("abc"); 
    x = "BCG";

ref 变量 x 可以更改为指向不同的字符串,但“abc”的值不能更改。

3) 实例变量(非静态字段)在构造函数被调用时被初始化。因此,您可以在构造函数中为变量初始化值。

4)“但我看到你可以更改类的构造函数/方法中的值”。-- 你不能在方法内部改变它。

5) 在类加载期间初始化一个静态变量。所以你不能在构造函数中初始化,它必须在它之前完成。因此,您需要在声明本身期间为静态变量赋值。

于 2014-09-24T20:34:16.993 回答
11

java中的final关键字用于限制用户。javafinal关键字可以在许多上下文中使用。最终可以是:

  1. 多变的
  2. 方法
  3. 班级

final关键字可以和变量一起使用,没有值的变量final称为空白final变量或未初始化final变量。它只能在构造函数中初始化。空白final变量也可以是仅在块static中初始化的变量。static

Java最终变量:

如果将任何变量设为final则无法更改变量的值final它将是常量)。

final变量示例

有一个 final 变量 speedlimit,我们要改变这个变量的值,但是它不能改变,因为 final 变量一旦赋值就永远不能改变。

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则无法扩展它。

最后一课的例子

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最终方法:

如果您将任何方法设为最终方法,则无法覆盖它。

方法示例final(Honda 中的 run() 无法覆盖 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

于 2017-01-03T04:45:18.923 回答
10

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

类/方法

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

变量

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

final根据具体情况,基本上避免被任何东西(子类,变量“重新分配”)覆盖/覆盖。

于 2016-06-29T16:05:11.723 回答
7

"A final variable can only be assigned once"

*Reflection*- “哇哇等等,拿着我的啤酒”


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”值(注意引号)。而且它可能会一遍又一遍地被分配不同的值......

为什么?因为反射就像 Chuck Norris,如果它想改变一个已初始化的 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                
于 2021-01-30T20:04:02.960 回答
4

final是Java中用于限制用户的保留关键字,可以应用于成员变量、方法、类和局部变量。在 Java 中, final 变量通常用static关键字声明,并被视为常量。例如:

public static final String hello = "Hello";

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

例如:

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

注意:声明为 final 的类不能扩展或继承(即,不能有超类的子类)。还需要注意的是,声明为 final 的方法不能被子类覆盖。

使用 final 关键字的好处在 这个线程中得到了解决。

于 2014-02-17T02:05:35.797 回答
2

阅读所有答案。

还有另一个用户案例final可以使用关键字,即在方法参数中:

public void showCaseFinalArgumentVariable(final int someFinalInt){

   someFinalInt = 9; // won't compile as the argument is final

}

可用于不应更改的变量。

于 2017-06-27T23:48:06.357 回答
2

假设你有两个钱盒,红色和白色。您只为这些钱盒分配了两个孩子,并且不允许他们互换它们的钱盒。所以你有红色或白色的钱盒(最终)你不能修改盒子但你可以把钱放在你的盒子上。没人在乎(修改-2)。

于 2016-01-27T20:04:40.823 回答
1

首先,代码中初始化(即第一次分配) foo 的位置在这里:

foo = new ArrayList();

foo 是一个对象(类型为 List),因此它是引用类型,而不是类型(如 int)。因此,它包含对存储 List 元素的内存位置(例如 0xA7D2A834)的引用。像这样的线条

foo.add("foo"); // Modification-1

不要更改 foo 的值(同样,它只是对内存位置的引用)。相反,他们只是将元素添加到引用的内存位置。要违反final关键字,您必须再次尝试重新分配 foo,如下所示:

foo = new ArrayList();

会给你一个编译错误。


现在,考虑一下添加static关键字时会发生什么。

当您没有 static 关键字时,实例化该类的每个对象都有自己的 foo 副本。因此,构造函数将值分配给 foo 变量的空白、新副本,这非常好。

但是,当您确实有 static 关键字时,与该类关联的内存中只存在一个 foo 。如果您要创建两个或更多对象,则构造函数每次都会尝试重新分配一个 foo,这违反了final关键字。

于 2014-07-22T19:02:21.247 回答
1

当您将其设为静态最终时,应在静态初始化块中对其进行初始化

    private static final List foo;

    static {
        foo = new ArrayList();
    }

    public Test()
    {
//      foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }
于 2013-03-27T09:08:24.223 回答
1
  1. 由于最终变量是非静态的,因此可以在构造函数中对其进行初始化。但是如果你把它设为静态,它就不能被构造函数初始化(因为构造函数不是静态的)。
  2. 添加到列表预计不会通过使列表最终化而停止。final只是将引用绑定到特定对象。您可以自由更改该对象的“状态”,但不能更改对象本身。
于 2013-03-27T09:03:55.613 回答
1

关键字表示一个final变量只能被初始化一次。在您的代码中,您只执行最终的一次初始化,因此满足条款。此语句执行foo. 注意final!= 不可变,它只意味着引用不能改变。

foo = new ArrayList();

当您声明foostatic final变量时,必须在加载类时进行初始化,并且不能依赖实例化(也就是对构造函数的调用)来初始化foo,因为静态字段必须在没有类实例的情况下可用。不能保证在使用静态字段之前已经调用了构造函数。

static final当你在场景下执行你的方法时,Test类在实例化之前加载t,此时没有实例化foo意味着它还没有被初始化,所以foo设置为所有对象的默认值,即null. 在这一点上,我假设您的代码NullPointerException在您尝试将项目添加到列表时抛出 a 。

于 2013-03-27T09:09:30.237 回答
1

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

final关键字可以在多个地方使用。

  1. 班级

Afinal class表示没有其他类可以扩展该最终类。当 Java 运行时 ( JRE ) 知道对象引用的类型是最终类(比如 F)时,它知道该引用的值只能是 F 类型。

前任:

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

因此,当它执行该对象的任何方法时,该方法不需要在运行时使用virtual table来解析。即不能应用运行时多态性。所以运行时间并不关心这一点。这意味着它可以节省处理时间,从而提高性能。

  1. 方法

任何类的 Afinal method意味着扩展该类的任何子类都不能覆盖该最终方法。因此,这种情况下的运行时行为也与我之前提到的类行为完全相同。

  1. 字段、局部变量、方法参数

如果将上述任何一种指定为final,则表示该值已经确定,因此无法更改该值

前任:

对于字段,本地参数

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
}

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

所以在以上3种场景中,当我们没有final在可以使用的地方指定关键字时,不用担心,编译器优化会为我们做这件事。编译器优化还为我们做了很多其他的事情。:)

于 2018-01-14T03:49:16.070 回答
1

以下是使用 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 变量可以稍后赋值(声明时不强制赋值),但只能赋值一次。

最终类最终类不能扩展(继承)

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();
    }
}
于 2016-10-07T06:26:44.330 回答
0

以上都是正确的。此外,如果您不希望其他人从您的类创建子类,则将您的类声明为 final。然后它成为你的类树层次结构的叶级,没有人可以进一步扩展它。避免巨大的类层次结构是一个很好的做法。

于 2013-12-23T02:10:04.280 回答