基于此oracle doc example 我尝试创建自己的示例进行测试。当然,建议的参考示例工作。但是当我尝试基于Class.cast方法时,我遇到了一个问题:
我的代码是
CL3扩展CL2扩展CL1扩展CL0扩展Base。
public class CL3 extends CL2 {
int x = 3;
public int getX() { return x; }
}
public class CL2 extends CL1 {
int x = 2;
public int getX() { return x; }
}
public class CL1 extends CL0 {
int x = 1;
public int getX() { return x; }
}
public class CL0 extends Base {
int x = 0;
public int getX() { return x; }
public String getPath() {
System.out.println("before obj : " + getClass().cast(this));
System.out.println("before class : " + getClass());
System.out.println("before x : " + getClass().cast(this).x);
System.out.println("before getX() : " + getClass().cast(this).getX());
System.out.println();
return getClazzPath(getClass());
}
}
public abstract class Base {
int x = -1;
abstract int getX();
@SuppressWarnings("unchecked")
public <T extends CL0> String getClazzPath(Class<T> clazz) {
System.out.println("clazz : " + clazz);
System.out.println("cast : " + clazz.cast(this));
System.out.println("cast.x : " + clazz.cast(this).x);
System.out.println("cast.getX() : " + clazz.cast(this).getX());
System.out.println("#");
return clazz.cast(this).x + (clazz == CL0.class ? "" : "/" + getClazzPath((Class<T>) clazz.getSuperclass()));
}
}
,主要功能代码为:
public static void main(String[] args) {
CL3 cl3 = new CL3();
System.out.println("cl3.getX()=" + cl3.getX());
System.out.println("((CL2)cl3).getX()=" + ((CL2) cl3).getX());
System.out.println("((CL1)cl3).getX()=" + ((CL1) cl3).getX());
System.out.println("((CL0)cl3).getX()=" + ((CL0) cl3).getX());
System.out.println("((IdentyfiedScope)cl3).getX()=" + ((Base) cl3).getX());
System.out.println();
System.out.println("cl3.x=" + cl3.x);
System.out.println("((CL2)cl3).x=" + ((CL2) cl3).x);
System.out.println("((CL1)cl3).x=" + ((CL1) cl3).x);
System.out.println("((CL0)cl3).x=" + ((CL0) cl3).x);
System.out.println("((IdentyfiedScope)cl3).x=" + ((Base) cl3).x);
System.out.println();
System.out.println(cl3.getPath());
}
输出为:
cl3.getX()=3
((CL2)cl3).getX()=3
((CL1)cl3).getX()=3
((CL0)cl3).getX()=3
((IdentyfiedScope)cl3).getX()=3
cl3.x=3
((CL2)cl3).x=2
((CL1)cl3).x=1
((CL0)cl3).x=0
((IdentyfiedScope)cl3).x=-1
before obj : test.code.hierarchy.read.CL3@70dea4e
before class : class test.code.hierarchy.read.CL3
before x : 0
before getX() : 3
clazz : class test.code.hierarchy.read.CL3
cast : test.code.hierarchy.read.CL3@70dea4e
cast.x : 0
cast.getX() : 3
#
clazz : class test.code.hierarchy.read.CL2
cast : test.code.hierarchy.read.CL3@70dea4e
cast.x : 0
cast.getX() : 3
#
clazz : class test.code.hierarchy.read.CL1
cast : test.code.hierarchy.read.CL3@70dea4e
cast.x : 0
cast.getX() : 3
#
clazz : class test.code.hierarchy.read.CL0
cast : test.code.hierarchy.read.CL3@70dea4e
cast.x : 0
cast.getX() : 3
#
0/0/0/0
问题是-为什么当我们使用Class.cast(由getPath()。getClazzPath()方法调用)时,当直接访问x字段时,结果与强制转换运算符产生的结果不同吗?如我们所见,getClazzPath方法('clazz:')的输出返回正确的类型CL3-> CL2-> CL1-> CL0,但x始终引用为0。
我发现它与getClazzPath方法中的T类型有关-但我不知道如何正确解释它。
如果有任何专家可以解释为什么在我的情况下强制转换运算符和Class.cast之间的行为不同?
答案 0 :(得分:2)
类型转换不会更改对象的类型。它仅更改对其引用的编译时类型,并在需要时进行有关有效性的运行时检查。
由于强制转换不会更改对象的类型,因此它永远不会更改调用getX()
之类的可覆盖方法的结果,该方法将始终调用最特定的方法。同样,将对象附加到String
时,强制转换无效,因为结果将是可覆盖方法toString()
的调用。
涉及字段或private
方法时,引用的编译时类型可能会影响访问哪个字段或方法,但是,因为有关类型的知识仅限于类型参数通用方法的使用,您不能期望仅调用者知道的类型会受到影响。
所以当您更换
@SuppressWarnings("unchecked")
public <T extends CL0> String getClazzPath(Class<T> clazz) {
System.out.println("clazz : " + clazz);
System.out.println("cast : " + clazz.cast(this));
System.out.println("cast.x : " + clazz.cast(this).x);
System.out.println("cast.getX() : " + clazz.cast(this).getX());
System.out.println("#");
return clazz.cast(this).x + (clazz == CL0.class ? "" : "/" + getClazzPath((Class<T>) clazz.getSuperclass()));
}
使用
@SuppressWarnings("unchecked")
public <T extends CL0> String getClazzPath(Class<T> clazz) {
System.out.println("clazz : " + clazz);
System.out.println("cast : " + (T)this);
System.out.println("cast.x : " + ((T)this).x);
System.out.println("cast.getX() : " + ((T)this).getX());
System.out.println("#");
return clazz.cast(this).x + (clazz == CL0.class ? "" : "/" + getClazzPath((Class<T>) clazz.getSuperclass()));
}
结果没有改变。当(T)…
是clazz.cast(…)
时,普通类型强制转换clazz
和Class<T>
之间没有区别,两者都具有将引用的编译时类型更改为{ {1}} ¹。
但是T
是什么?该方法不知道,由于声明T
,它是否可分配给CL0
,因此,它允许访问<T extends CL0>
的成员,包括字段CL0
。
您可能会认为这是一种语言设计错误,尽管CL0.x
可能是声明其自身的子类,但允许通过类型CL0
的引用访问T
的字段之类的不可覆盖的成员具有相同名称的字段。实际上,对于T
成员,即使private
的{{1}}成员可以访问,编译器通过类型为T
的引用访问它们时,也会产生错误。>
为进一步证明普通类型转换和private
之间没有区别,可以更改
CL0
您的Clazz.cast
方法的
System.out.println("cl3.getX()=" + cl3.getX());
System.out.println("((CL2)cl3).getX()=" + ((CL2) cl3).getX());
System.out.println("((CL1)cl3).getX()=" + ((CL1) cl3).getX());
System.out.println("((CL0)cl3).getX()=" + ((CL0) cl3).getX());
System.out.println("((IdentyfiedScope)cl3).getX()=" + ((Base) cl3).getX());
System.out.println();
System.out.println("cl3.x=" + cl3.x);
System.out.println("((CL2)cl3).x=" + ((CL2) cl3).x);
System.out.println("((CL1)cl3).x=" + ((CL1) cl3).x);
System.out.println("((CL0)cl3).x=" + ((CL0) cl3).x);
System.out.println("((IdentyfiedScope)cl3).x=" + ((Base) cl3).x);
,结果也一样。这两种方法之间没有什么区别,重要的是,在一个地方,您要转换为具体类型main
,System.out.println("cl3.getX()=" + cl3.getX());
System.out.println("((CL2)cl3).getX()=" + CL2.class.cast(cl3).getX());
System.out.println("((CL1)cl3).getX()=" + CL1.class.cast(cl3).getX());
System.out.println("((CL0)cl3).getX()=" + CL0.class.cast(cl3).getX());
System.out.println("((IdentyfiedScope)cl3).getX()=" + Base.class.cast(cl3).getX());
System.out.println();
System.out.println("cl3.x=" + cl3.x);
System.out.println("((CL2)cl3).x=" + CL2.class.cast(cl3).x);
System.out.println("((CL1)cl3).x=" + CL1.class.cast(cl3).x);
System.out.println("((CL0)cl3).x=" + CL0.class.cast(cl3).x);
System.out.println("((IdentyfiedScope)cl3).x=" + Base.class.cast(cl3).x);
,CL3
,CL2
或CL1
,另一方面,您正在转换为类型参数CL0
。
¹的区别在于Base
将在运行时检查有效性,这与未检查的强制转换不同,但是由于它们在此示例中都是有效的,因此结果不会更改,并且我们可以专注于对成员选择的影响。
答案 1 :(得分:0)
谢谢Holger的解释。缩短历史记录。在我的示例中,我不能基于字段不是多态的事实,因为字段引用是在编译时解决的。。
在主要方法中,代码:
System.out.println("cl3.x=" + cl3.x);
System.out.println("((CL2)cl3).x=" + ((CL2) cl3).x);
System.out.println("((CL1)cl3).x=" + ((CL1) cl3).x);
System.out.println("((CL0)cl3).x=" + ((CL0) cl3).x);
System.out.println("((IdentyfiedScope)cl3).x=" + ((Base) cl3).x);
生成输出:
cl3.x=3
((CL2)cl3).x=2
((CL1)cl3).x=1
((CL0)cl3).x=0
((IdentyfiedScope)cl3).x=-1
因为在编译时,例如'(((CL1)cl3).x)'更改为'CL1.x'。
方法的控制台输出:
public <T extends CL0> String getClazzPath(Class<T> clazz) {
System.out.println("clazz : " + clazz);
System.out.println("cast.x : " + clazz.cast(this).x);
}
将始终为'cast.x:0'打印,因为在编译时,编译器始终将其解析为'CL0.x'。在那个地方,无论在运行时报告什么混乱,因为在编译时将始终使用CL0.x。这是合乎逻辑的,因为我们不能确保CL0的子级具有x字段。
clazz : class test.code.hierarchy.read.CL3
cast.x : 0
clazz : class test.code.hierarchy.read.CL2
cast.x : 0