Java 中的“final”关键字是如何工作的? (我仍然可以修改对象。)

新手上路,请多包涵

在 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 真的有效吗?

原文由 G.S 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 735
2 个回答

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

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

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

原文由 Marko Topolnik 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

现在面试官问另一个最喜欢的问题 是什么是 java 1.8 的有效最终 版本。

我将在最后解释这个 effectively final 在 Java 1.8 中。

 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 中添加内容,这是允许的。如果您尝试将 new ArrayList() 分配给 foo 引用变量,编译器会报错。

规则 如果您已经初始化了一个 final 变量,那么您不能更改它以引用不同的对象。 (在这种情况下 ArrayList

final 类不能被子类化

final 方法不能被覆盖。 (此方法在超类中)

final 方法可以覆盖。 (按语法方式阅读。此方法在子类中)

现在让我们看看 在 Java 1.8 中什么是有效的 final?

 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 中抛出错误。 Effectively final 是 Method Local Inner 类的一部分。我知道你很少会在方法本地类中使用如此有效的 final,但是对于面试我们必须做好准备。

原文由 AmitG 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题