Java中的"final"关键字如何工作?(我仍然可以修改一个对象.)

G.S*_*G.S 457 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码工作正常,没有错误.

现在将变量更改为static:

private static final List foo;
Run Code Online (Sandbox Code Playgroud)

现在是编译错误.这final真的有用吗?

Ami*_*itG 562

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

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.
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,我们为'Test'定义了一个构造函数,并给它一个'setFoo'方法.

关于构造函数:通过使用关键字,每个对象创建 只能调用一次构造函数new.您不能多次调用构造函数,因为构造函数不是为此而设计的.

关于方法:可以根据需要多次调用方法(即使从不),编译器也知道它.

场景1

private final List foo;  // 1
Run Code Online (Sandbox Code Playgroud)

foo是一个实例变量.当我们创建Test类对象然后实例变量时foo,将被复制到Test类的对象中.如果我们foo在构造函数内部分配,那么编译器知道构造函数只会被调用一次,所以在构造函数中分配它没有问题.

如果我们foo在方法内部分配,编译器知道可以多次调用方法,这意味着必须多次更改该值,这对于final变量是不允许的.所以编译器决定构造函数是个不错的选择!您只能为最终变量赋值一次.

情景2

private static final List foo = new ArrayList();
Run Code Online (Sandbox Code Playgroud)

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

场景3

t.foo.add("bar"); // Modification-2
Run Code Online (Sandbox Code Playgroud)

以上Modification-2是你的问题.在上面的例子中,您没有更改第一个引用的对象,但是您要添加foo允许的内容.如果您尝试将a分配new ArrayList()foo引用变量,则编译器会抱怨.
规则如果已初始化final变量,则无法更改它以引用其他对象.(在这种情况下ArrayList)

最终类不能被子类化,
最终方法不能被重写.(此方法是超类)
最终方法可以覆盖.(以语法的方式阅读.此方法在子类中)

  • 只是要清楚。在场景 2 中,如果在 Test 类中设置了 `foo` 并且创建了多个 Test 实例,那么尽管最终指定了 `foo` 会被多次设置吗? (2认同)
  • 我认为考虑场景 3 的一个有用的方法是您将 `final` 分配给 `foo` 引用的内存地址,该内存地址是一个 ArrayList。您没有将 `final` 分配给 `foo` 的第一个元素(或任何与此相关的元素)引用的内存地址。因此你不能改变`foo`,但你可以改变`foo[0]`。 (2认同)

Mar*_*nik 497

你总是允许初始化一个final变量.编译器确保您只能执行一次.

请注意,对存储在final变量中的对象的调用方法与其语义无关final.换句话说:final只是关于引用本身,而不是引用对象的内容.

Java没有对象不变性的概念; 这是通过精心设计物体来实现的,并且是一项非常重要的工作.

  • 嗯.它所有关于参考而不是价值.谢谢! (42认同)
  • 尝试做t.foo = new ArrayList(); 在main方法中你将得到编译错误...引用foo绑定到ArrayList的一个最终对象...它不能指向任何其他ArrayList (11认同)
  • `final`存在于类文件中,对优化运行时具有显着的语义后果.它也可能产生*成本*因为JLS对对象的"final"字段的一致性有很强的保证.例如,ARM处理器必须在具有"final"字段的类的每个构造函数的末尾使用显式内存屏障指令.但是,在其他处理器上不需要这样做. (5认同)
  • @androiddeveloper Java中的任何内容都无法显式控制堆栈/堆放置.更具体地说,由HotSpot JIT编译器决定的堆栈放置受*转义分析*的影响,这比检查变量是否为"final"要多得多.可变对象也可以进行堆栈分配.尽管如此,`final` fields*可能*有助于逃避分析,但这是一个非常间接的路线.另请注意,*有效的final*变量与源代码中标记为`final`的变量具有相同的处理. (4认同)
  • 我有个问题.我认识的人称"最终"也使变量存储在堆栈中.它是否正确?我到处搜索过,找不到任何可以批准或反对这种说法的参考文献.我搜索过Java和Android文档.还搜索了"Java内存模型".也许它在C/C++上以这种方式工作,但我不认为它在Java上以这种方式工作.我对么? (2认同)

czu*_*upe 205

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

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

其他用途:

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

静态类变量将从JVM的开头存在,并且应该在类中初始化.如果执行此操作,则不会显示错误消息.

  • 这是迄今为止我最喜欢的答案.简单直接,这是我期望在关于java的在线文档中阅读的内容. (22认同)
  • @jorgesaraiva 您可以根据需要多次_assign_(不是_initialize_)`static` 字段(只要它们不是`final`)。请参阅 [此 wiki](/sf/ask/182985071/) 以了解 _assignment_ 和 _initialization_ 之间的区别。 (2认同)

Sma*_*ker 52

final关键字可以在这取决于它的使用两种不同的解释:

值类型:对于ints,doubles等,它将确保值不会改变,

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

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

  • **最后**,我一直在寻找的答案。 (4认同)

luc*_*mon 23

如果您创建foo静态,则必须在类构造函数(或您定义它的内联)中初始化它,如下面的示例所示.

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

private static final List foo;

static
{
   foo = new ArrayList();
}
Run Code Online (Sandbox Code Playgroud)

排队:

private static final List foo = new ArrayList();
Run Code Online (Sandbox Code Playgroud)

这里的问题不是final修饰符的工作原理,而是static修饰符的工作原理.

final修正通过调用构造函数完成时间(即必须在构造函数初始化)强制参考的初始化.

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

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

如果不在线final初始化属性,则修饰符会强制您初始化它,并且必须在构造函数中执行此操作.如果你还有一个static修饰符,你必须初始化属性的构造函数是类的初始化块:static{}.

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

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

边注:

final修改确保常量性只对基本类型和引用.

声明final对象时,获得的是对该对象的final 引用,但对象本身不是常量.

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


use*_*871 7

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

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

2)不可变对象是一个状态不能改变的对象,但它的引用可以改变.例如:

    String x = new String("abc"); 
    x = "BCG";
Run Code Online (Sandbox Code Playgroud)

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

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

4)"但我发现你可以改变类的构造函数/方法中的值". - 您无法在方法中更改它.

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


iva*_*ncz 6

值得一提的是一些直截了当的定义:

类/方法

您可以将一些或所有类方法声明为final,以指示该方法不能被子类覆盖.

变量

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

final 基本上避免覆盖/替换任何东西(子类,变量"重新分配"),具体取决于具体情况.

  • 我认为关于变量的最终定义有点短;“在Java中,当final关键字与原始数据类型(int、float等)变量一起使用时,该变量的值不能更改,但当final与非原始变量一起使用时(请注意,非原始变量始终是对 Java 中对象的引用),所引用对象的成员可以更改。对于非原始变量而言,final 只是意味着它们不能更改为引用任何其他对象”。https://www.geeksforgeeks.org/g-fact-48/ (3认同)

Ali*_*aee 5

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

  1. 变量
  2. 方法

final关键字可与变量被应用,一个final不具有任何值的变量,被称为空白final变量或未初始化final变量。它只能在构造函数中初始化。空白final变量也可以仅staticstatic块中初始化。

Java最终变量:

如果你做任何变量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  
Run Code Online (Sandbox Code Playgroud)

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();  
      }  
  }  
Run Code Online (Sandbox Code Playgroud)

Java最终方法:

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

final方法示例(本田中的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();  
   }  
}  
Run Code Online (Sandbox Code Playgroud)

共享自:http : //www.javatpoint.com/final-keyword


ara*_*ran 5

"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);
    }    
}
Run Code Online (Sandbox Code Playgroud)

输出:

1
50
100
666
8888
Run Code Online (Sandbox Code Playgroud)

“最后”现场已经被分配5个不同的 “最终”值(注意引号)。它可以一遍又一遍地被分配不同的值......

为什么?因为反射就像 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                
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

449558 次

最近记录:

7 年,5 月 前