有哪些硬性 "rules" 用于使用子类型进行转换?
What are some hardfast "rules" for casting with subtypes?
我正在为即将到来的 Java 期末考试尝试一些模拟考试,我遇到了这个问题。
Consider the following class definitions and indicate whether 'Test.main()' would compile successfully. If it does compile, indicate whether it would run successfully or if not, indicate what Exception would be thrown.
public class A {
public int method(int[] a) {...}
}
public class B extends A {
@Override
public int method(int[] a) {...}
}
public class C extends B {
@Override
public int method(int[] a) {...}
public void otherMethod() {...}
}
public class Test {
public static void main(String[] args) {
A a = new C();
B b = new B();
b = (B) a;
}
}
我认为 Test.main() 会编译但会抛出运行时异常,因为 a 是实际类型 C,而我们正试图将其转换为类型 B。事实并非如此,因为答案说这很好。
我对涉及的层次结构比 2 层更深的转换规则感到很困惑。讲座幻灯片实际上没有这种信息!
那么,如果考试中出现此类问题,需要牢记哪些硬性问题 "rules"?
层次比较复杂的时候,尽量画出来更清晰:
A <- B <- C
I thought that Test.main() would compile but throw a runtime exception due to the fact that a is of actual type C and we are trying to cast it to type B.
a
的基础类型是C
。但是,C
可转换为 B
和 A
因为 C
继承自 B
和 B
继承自 A
.
基本上,引用类型转换是否成功的一般规则如下:
For any cast in the below format:
(X)Y
where X
is a reference type and Y
is a variable of a reference type, the cast will succeed at runtime if you can go from Y
's underlying type to X
in the inheritance hierarchy by only going along the directions of the arrows.
假设我们有这段代码:
A a = new A();
B b = (B)a;
这会失败,因为我们需要逆着箭头的方向从 A
到 B
那么你怎么知道一个转换在编译时是否会失败呢?
这很容易。只需检查 Y
的 变量类型 (不是基础类型!)是否与 X
.
无关
例如:
// these two types are unrelated
class Foo {}
class Bar {}
// ...
Foo f = new Foo();
Bar b = (Bar)f; // fails to compile
然而,如果Y
的变量类型与X
相关,它编译正常:
Object f = new Foo();
Bar b = (Bar)f; // Bar inherits from Object, so compiles fine.
// But since Foo (f's underlying type) is unrelated to Bar
// this crashes at runtime
要完全理解这个看似简单的问题所涉及的问题需要很长时间,需要理解the Java language specification,但一个像样的理解也许是触手可及的。 @JBNizet 的具体化想法也很有用。
一些术语和简化是为了:
- 当你说"converting from a type to another type"时,前者称为source类型,后者称为target类型。现在让我们将讨论限制在具体的引用类型上(即 class,如
A
、B
和 C
)。我们将源类型表示为 Source
,将目标类型表示为 Target
。
- 在涉及转换(隐式或显式)引用类型的赋值语句中,源类型变量出现在
=
符号的右侧,目标类型变量出现在其左侧。因此,您可以: Target t = (Target) s
,其中 s
是 Source
. 类型的变量
- 引用类型的变量有两种种:编译时类型和运行时类型。
现在,适用的规则according to the JLS(经过一些简化)是:
For this kind of assignment (Target t = (Target) s
) to compile, either Target
must be a subclass (or subtype) of Source
, or Source
must be a subclass of Target
.
(实际严格的规则是:If T is a class type, then either |S| <: |T|, or |T| <: |S|
。否则会出现编译时错误。|S|
意味着擦除 of S
和 <:
表示 是子 class-of 关系。)
现在,您的 class 和 A
、B
和 C
创建以下层次结构:C <: B <: A
:
main
方法所做的事情可以有效地表示为:
A a = new C(); //as-is (1)
// the intervening B b = new B() does not make any difference
B b = (B) a; //as-is (2)
现在,按照上面适用的规则,因为 b
(即 Target
)的类型 B
是 [=11] 的子 class =](即 Source
),(2) 中的赋值应该可以正常编译,因为您正在从 中转换 来源 class (A
) 到一个目标class(B
)处于是-subclass-of关系.
根据(1),变量a
的运行时类型实际上是C
。并且由于 C
是 B
的子 class,任何 C
类型的变量(源)总是可以被分配 到 类型 B
(目标)的变量,用于在运行时成功进行转换(即 而不是 抛出 ClassCastException
)。这叫做 Liskov Substitution Principle.
我正在为即将到来的 Java 期末考试尝试一些模拟考试,我遇到了这个问题。
Consider the following class definitions and indicate whether 'Test.main()' would compile successfully. If it does compile, indicate whether it would run successfully or if not, indicate what Exception would be thrown.
public class A {
public int method(int[] a) {...}
}
public class B extends A {
@Override
public int method(int[] a) {...}
}
public class C extends B {
@Override
public int method(int[] a) {...}
public void otherMethod() {...}
}
public class Test {
public static void main(String[] args) {
A a = new C();
B b = new B();
b = (B) a;
}
}
我认为 Test.main() 会编译但会抛出运行时异常,因为 a 是实际类型 C,而我们正试图将其转换为类型 B。事实并非如此,因为答案说这很好。
我对涉及的层次结构比 2 层更深的转换规则感到很困惑。讲座幻灯片实际上没有这种信息!
那么,如果考试中出现此类问题,需要牢记哪些硬性问题 "rules"?
层次比较复杂的时候,尽量画出来更清晰:
A <- B <- C
I thought that Test.main() would compile but throw a runtime exception due to the fact that a is of actual type C and we are trying to cast it to type B.
a
的基础类型是C
。但是,C
可转换为 B
和 A
因为 C
继承自 B
和 B
继承自 A
.
基本上,引用类型转换是否成功的一般规则如下:
For any cast in the below format:
(X)Y
where
X
is a reference type andY
is a variable of a reference type, the cast will succeed at runtime if you can go fromY
's underlying type toX
in the inheritance hierarchy by only going along the directions of the arrows.
假设我们有这段代码:
A a = new A();
B b = (B)a;
这会失败,因为我们需要逆着箭头的方向从 A
到 B
那么你怎么知道一个转换在编译时是否会失败呢?
这很容易。只需检查 Y
的 变量类型 (不是基础类型!)是否与 X
.
例如:
// these two types are unrelated
class Foo {}
class Bar {}
// ...
Foo f = new Foo();
Bar b = (Bar)f; // fails to compile
然而,如果Y
的变量类型与X
相关,它编译正常:
Object f = new Foo();
Bar b = (Bar)f; // Bar inherits from Object, so compiles fine.
// But since Foo (f's underlying type) is unrelated to Bar
// this crashes at runtime
要完全理解这个看似简单的问题所涉及的问题需要很长时间,需要理解the Java language specification,但一个像样的理解也许是触手可及的。 @JBNizet 的具体化想法也很有用。
一些术语和简化是为了:
- 当你说"converting from a type to another type"时,前者称为source类型,后者称为target类型。现在让我们将讨论限制在具体的引用类型上(即 class,如
A
、B
和C
)。我们将源类型表示为Source
,将目标类型表示为Target
。 - 在涉及转换(隐式或显式)引用类型的赋值语句中,源类型变量出现在
=
符号的右侧,目标类型变量出现在其左侧。因此,您可以:Target t = (Target) s
,其中s
是Source
. 类型的变量
- 引用类型的变量有两种种:编译时类型和运行时类型。
现在,适用的规则according to the JLS(经过一些简化)是:
For this kind of assignment (
Target t = (Target) s
) to compile, eitherTarget
must be a subclass (or subtype) ofSource
, orSource
must be a subclass ofTarget
.
(实际严格的规则是:If T is a class type, then either |S| <: |T|, or |T| <: |S|
。否则会出现编译时错误。|S|
意味着擦除 of S
和 <:
表示 是子 class-of 关系。)
现在,您的 class 和 A
、B
和 C
创建以下层次结构:C <: B <: A
:
main
方法所做的事情可以有效地表示为:
A a = new C(); //as-is (1)
// the intervening B b = new B() does not make any difference
B b = (B) a; //as-is (2)
现在,按照上面适用的规则,因为 b
(即 Target
)的类型 B
是 [=11] 的子 class =](即 Source
),(2) 中的赋值应该可以正常编译,因为您正在从 中转换 来源 class (A
) 到一个目标class(B
)处于是-subclass-of关系.
根据(1),变量a
的运行时类型实际上是C
。并且由于 C
是 B
的子 class,任何 C
类型的变量(源)总是可以被分配 到 类型 B
(目标)的变量,用于在运行时成功进行转换(即 而不是 抛出 ClassCastException
)。这叫做 Liskov Substitution Principle.