通过对象访问时,静态最终字段和在声明时初始化的实例最终字段之间的差异[重复]

Posted

技术标签:

【中文标题】通过对象访问时,静态最终字段和在声明时初始化的实例最终字段之间的差异[重复]【英文标题】:DIfference between a static final field and an instance final field initialized at declaration when accessed through an object [duplicate] 【发布时间】:2015-10-08 17:32:26 【问题描述】:

第一个例子

public class MyClass 
    final int x = 2;

x

    final,这意味着它在初始化后永远无法设置。 在声明时初始化,这意味着以后不能为其分配不同的值(即使在构造函数中也是如此)。 固定(无论实例如何),因为它的值不能在构造函数中更改(或其他任何地方)。

第二个例子

public class MyOtherClass 
    static final int x = 3;

x

    final,这意味着它在初始化后永远无法设置。 在声明时初始化,这意味着以后不能为其分配不同的值。 一个静态字段,无论实例如何,值都将始终保持不变。 常量,因为它既是static又是final

我的问题是

两者有什么区别? (不包括创建时间)

我错过了什么吗?

【问题讨论】:

这是我的第一个问题,请随时评论风格,如果我可以改进它。 如果你在声明时初始化一个最终变量,那么它可能应该是静态的... 一个潜在的区别是在运行时不使用static final 原语。编译器会在编译期间用该值替换它的所有出现,使其更加优化。 @Codebender:这也适用于非static 变量,甚至适用于局部变量。 所有 final用编译时常量初始化的变量是编译时常量,普通Java代码永远不会访问。 【参考方案1】:

虽然final,但JVM并没有做任何优化或假设,所以类的每个实例都会有一个x的实例。如果您声明成员 static,则该类的每个实例将共享同一 x 实例,因为它是静态分配的。此外,正如所写(x 具有包可见性),其他类可能会静态访问 x,即,没有可用的类实例。

【讨论】:

【参考方案2】:

如果你创建了这两种类的多个实例,那么,

在第一种情况下,MyClass 的所有对象都将拥有自己的 final x 字段。

在第二种情况下,MyOtherClass 的所有对象都将指向一个 final x 类字段,因为它本质上是静态的。

【讨论】:

我认为在第二种情况下,MyOtherClass 的对象根本不指向static final x。它属于类而不是对象。 是的,没错,我试图描绘同样的。【参考方案3】:

MyClassMyOtherClass 中的x 的区别是:

第一个只能通过MyClass 实例访问,并且该常量可以有多个副本。

第二个可以在没有MyOtherClass实例的情况下访问,并且只能存在一个副本。

在您的示例中,拥有一个或多个常量实例之间没有实际区别1。但是考虑一下:

public class YetAnotherClass 
    final int x;

    public YetAnotherClass(int x) 
        this.x = x;
    

...它显示了一个实例常量如何在不同的实例中具有不同的值。


1 - 这是夸大其词。首先,static final int x = 3; 声明了一个编译时常量,编译时常量可以在 switch case 表达式中使用,而非编译时常量则不能。其次,常量的非静态版本将占用MyClass 的每个实例中的空间。最后,如果您足够愚蠢地尝试使用反射来更改常量,那么行为会有所不同。 (只是不要这样做......)

【讨论】:

当只使用final int x 时,是否可以逃脱分析/JIT 优化缓存值? 可能。但是,如果使用反射访问常量,代码仍然需要工作,因此无论如何都可能需要分配该字段。 有道理。然后这也适用于static final int.. static final 的情况下,如果该常量符合编译时常量的条件,则优化(部分)由字节码编译器执行。 这似乎是Java编程语言中经常被忽视的东西,可能是因为有些教程也弄错了,但是static变量和非static变量在它涉及编译时常量行为。 所有 final 变量,用编译时常量初始化,也是编译时常量,普通Java 代码永远不会读取。这也适用于局部变量,yes,这意味着您可以在 switch 语句的 case 子句中使用局部变量,如果它们是编译时常量。【参考方案4】:

区别: 静态属于类,因此您可以在没有该类的任何实例的情况下访问它,因此只有一个副本。

在访问第二个时,您需要一个类的实例来访问它,这样您就可以拥有与 Object 一样多的非静态 final Object 副本。

您可以使用字节码进行验证: 对于静态

Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return

对于非静态决赛

Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_2
         6: putfield      #2                  // Field x:I
         9: return
      LineNumberTable:

如您所见,它有 putfield 设置在 objectref 中标识的字段的值(对对象的引用)

【讨论】:

令我困惑的是,人们认为通过向初学者展示字节码序列来向初学者解释 Java 语言是个好主意。 @StephenC - 捍卫这个答案.. 这个答案可能对其他人有用(中级?)以及:).. 不仅仅是 OP.. @StephenC 它只适合所有人:) 不,它不适合所有人。它适用于了解字节码的人。这排除了初学者,比如 OP(我期望)。此外,OP 正在询问差异不包括创建时间,这肯定是关于创建时间... "当访问第二个时" 第二个是静态的。但是,我明白你的意思。正如@StephanC 提到的,我也不懂字节码。但是,它可能对其他人有用。

以上是关于通过对象访问时,静态最终字段和在声明时初始化的实例最终字段之间的差异[重复]的主要内容,如果未能解决你的问题,请参考以下文章

何时选择变量来声明为最终静态

静态字段静态函数成员常量

Java-13,static关键字

Java类和对象

C#图解教程 第六章 深入理解类

C++ 中static 和final的区别是啥