问题
最近在研读公司项目的时候发现一段很有意思的代码:
if (a instanceof b) {
B ent = (B) a;
if (!useGivenAuditAttributes) {
ent.createdAt = MyDateUtils.now();
ent.createdBy = AppContext.instance.loginUser.userId;
ent.lastUpdatedAt = ent.createdAt;
ent.lastUpdatedBy = ent.createdBy;
}
}
后续并没有对ent对象进行操作,在 if 块结束后,它的作用域就结束了。因此,从表面上看,这个变量会被销毁,后续代码无法直接访问这个局部变量。
那么这段代码有什么作用呢?
查阅资料之后我发现了一个被遗忘的知识点。
解析
先说结论
ent 虽然是局部变量,但它本质上引用的是 a对象,这意味着对 ent 的修改实际上是在修改 a对象本身。
ent 在 if 块内被销毁只是引用被销毁,它指向的 a对象本身并不会被销毁。ent 对 a属性的修改是直接作用在 a对象上的,因此,后续代码依然能访问这些修改过的属性。
是不是很疑惑?如果不疑惑说明你很强!
首先总所周知的是,在 Java 中,对象引用是指一个变量存储了对象在内存中的地址,而不是对象本身。
在 Java 中,类的实例(对象)存在于堆内存中,而引用类型的变量(如 B ent
和 T a,这里的T代表泛型
)则存在于栈内存中,它们存储的是指向堆中实际对象的引用(即对象的内存地址)。
其次我们要知道,java当中强制类型转换的原理
当执行 B ent = (B) a;
时:
- 编译时检查: 编译器会验证 a的静态类型是否可以被转换为 B类型。因为 a的实际类型是A,即 B 的子类,所以这个转换在编译时是合法的。
- 运行时转换: 在运行时,JVM 会检查 a的实际类型,并将 a的引用(指向堆中对象的地址)赋值给 ent。注意,这里的转换只是更改了引用的静态类型(ent 是 B类型),而堆中对象本身并没有发生改变。
因此在类型转换后,变量 ent
和 a都存储了相同的内存地址,因此它们指向同一个对象,即堆中相同的 A实例。
内存中即为这样:
因为 ent 和 a都指向同一个对象,所以通过 ent 修改该对象的属性会影响到 a对象。
实例
下面上代码
public class A {
private String name;
public A(String name) {
this.name = name;
}
public A() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class B extends A{
private int age;
public B(String name, int age) {
super(name);
this.age = age;
}
public B(int age) {
this.age = age;
}
public B(String name) {
super(name);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public static void main(String[] args) {
B b = new B("jack", 10);
A a = (A) b;
System.out.println(a.getName());
a.setName("vivian");
System.out.println(b.getName());
}
运行截图如下: