通过对象访问时,静态最终字段和在声明时初始化的实例最终字段之间的差异[重复]
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】:
MyClass
和MyOtherClass
中的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 提到的,我也不懂字节码。但是,它可能对其他人有用。以上是关于通过对象访问时,静态最终字段和在声明时初始化的实例最终字段之间的差异[重复]的主要内容,如果未能解决你的问题,请参考以下文章