“final"关键字在Java工作?(我仍然可以修改对象。)

在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是如何工作的?

551588 次浏览

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

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

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

  1. 由于final变量是非静态的,所以可以在构造函数中初始化它。但是如果你让它是静态的,它就不能被构造函数初始化(因为构造函数不是静态的)。
  2. 对列表的添加不期望通过使列表最终停止。final只是将引用绑定到特定对象。你可以自由地改变对象的“状态”,但不能改变对象本身。

如果您将foo设置为静态的,则必须在类构造函数中初始化它(或在定义它的地方内联初始化它),如以下示例所示。

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

private static final List foo;


static
{
foo = new ArrayList();
}

内联:

private static final List foo = new ArrayList();

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

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

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

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

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

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

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

注:

final修饰符仅确保基本类型和引用具有const性。

当您声明final对象时,您得到的是该对象的final 参考,但对象本身不是常量。

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

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

    private static final List foo;


static {
foo = new ArrayList();
}


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

关键字final表示变量只能初始化一次。在你的代码中,你只执行了一次final的初始化,所以条件是满足的。该语句执行foo的单独初始化。注意,final != immutable,它只意味着引用不能改变。

foo = new ArrayList();

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

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

最后关键字有多种使用方式:

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

其他用途:

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

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

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

对于ints, doubles等,它将确保该值不能改变,

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

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

这是最受欢迎的面试问题。通过这些问题,面试官试图了解你对对象行为的理解程度,包括构造函数、方法、类变量(静态变量)和实例变量 现在,面试官会问另一个最受欢迎的问题:java 1.8的EYZ1。我将在最后解释这个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'方法。< br >

通过使用new关键字,每次创建对象只能调用一个关于构造函数:构造函数。不能多次调用构造函数,因为构造函数不是这样设计的。< br >

一个方法可以被你想调用多少次就调用多少次(甚至永远不会调用),而且编译器知道它。< br >

# EYZ0 < br >

private final List foo;  // 1

foo实例变量。当我们创建Test类对象时,实例变量foo将被复制到Test类的对象中。如果在构造函数内部赋值foo,那么编译器知道该构造函数只会被调用一次,因此在构造函数内部赋值它是没有问题的。 < br > < br > 如果在方法中赋值foo,编译器知道一个方法可以被多次调用,这意味着该值必须被多次更改,这对于final变量是不允许的。所以编译器决定构造函数是一个好选择!# EYZ2 < / p >

场景2

private static final List foo = new ArrayList();

foo现在是一个Test0变量。当我们创建Test类的实例时,foo将不会被复制到对象中,因为foo是静态的。现在foo不是每个对象的独立属性。这是Test类的属性。但是foo可以被多个对象看到,如果使用new关键字创建的每个对象最终都会调用Test构造函数,在多个对象创建时改变值(记住static foo不是复制到每个对象中,而是在多个对象之间共享。

# EYZ0 < br >

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

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

最后类不能子类化
最后方法不能被覆盖。(这个方法在超类中)
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,但是对于面试,我们必须做好准备。

以上都是正确的。此外,如果您不希望其他人从您的类中创建子类,则将您的类声明为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的方法不能被子类重写。

# EYZ0 # EYZ1。

首先,在你的代码中初始化(即第一次赋值)foo的地方在这里:

foo = new ArrayList();

foo是一个对象(类型为List),所以它是参考类型,而不是价值类型(像int)。因此,它持有对存储List元素的内存位置(例如0xA7D2A834)的引用。像这样的线

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

不要更改foo的值(同样,它只是一个对内存位置的引用)。相反,它们只是在引用的内存位置中添加元素。要违反最后关键字,你必须尝试重新分配foo,如下所示:

foo = new ArrayList();

会给你一个编译错误。


现在,考虑一下当您添加静态关键字时会发生什么。

当你没有static关键字时,实例化类的每个对象都有自己的foo副本。因此,构造函数将一个值赋给foo变量的一个空白的新副本,这是完全没问题的。

然而,当你有static关键字时,内存中只有一个foo与类相关联。如果要创建两个或多个对象,构造函数每次都会尝试重新分配那个foo,这违反了最后关键字。

这是一个非常好的面试问题。有时他们甚至会问你final对象和immutable对象的区别是什么。

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

一个不可变对象的状态可以改变,但是它的引用可以改变。 例:< / p >
    String x = new String("abc");
x = "BCG";

可以修改Ref变量x指向另一个字符串,但是不能修改abc的值。

3)实例变量(非静态字段)在调用构造函数时初始化。所以你可以在构造函数中初始化变量的值。

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

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

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

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

类/方法

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

变量

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

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

以下是使用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类不能被扩展(继承)

final class Base { }
class Derived extends Base { } //Compiler Error:cannot inherit from final Base


public class Main {
public static void main(String args[]) {
}
}

final方法不能被子类覆盖。

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

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

  1. 变量
  2. 方法

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

Java final变量:

如果你让任何变量为final,你final变量的不能更改值(它将是常数)。

变量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类示例

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

如果你把任何方法作为最终的,你不能覆盖它。

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();
}
}
< p >共享: # EYZ0 < / p >

阅读所有答案。

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

public void showCaseFinalArgumentVariable(final int someFinalInt){


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


}

可用于不应更改的变量。

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

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

一个final class意味着没有< em > < / em >其他类可以< em > < / em >扩展最后一个类。当Java运行时(JRE < em > < / em >)知道一个对象引用的类型是final类(比如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.

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

  1. 方法

任何类的final method意味着任何子类扩展了该类< em >不能覆盖< / em >的最终方法。因此,此场景中的运行时行为也与前面提到的类的行为完全相同。

  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 < em > < / em >知道值不能更改,它将所有这些最终确定的值(最终引用的值)加载到< em > < / em > L1缓存中。因为它< em >不需要< / em >< em >加载回< / em >一次又一次地从主存< em > < / em >。否则它会加载到L2缓存,并从主存中进行时间到时间的加载。所以这也是一种性能提升。

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

<强> # EYZ0 < / >强

# eyz1 - # eyz2。


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

  • 构造函数结束。
  • 当反射设置字段的值时。(# EYZ0)

让我们打破法律

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

我只能回答你的问题,在这种情况下,你不能改变喷火参考值。你只是简单地把价值放入相同的参考,这就是为什么你可以添加价值到foo 参考。这个问题发生在你不能很好地理解的参考价值原始值之间的区别。参考 价值也是一个在堆内存中存储对象喷火0(这是value)的值。

public static void main(String[] args)
{
Test t = new Test();
t.foo.add("bar"); // Modification-2
System.out.println("print - " + t.foo);
}

但是在这种情况下,您可以看到,如果尝试编写以下代码,将会出现编译时错误。

public static void main(String[] args)
{
Main main = new Main();
main.foo=new ArrayList<>();//Cannot assign a value to final variable 'foo'
System.out.println("print - " + main.foo);
}