我在 http://www.javabeginner.com/learn-java/java-object-typecasting 上遇到过这个例子,在它谈论显式类型转换的部分有一个让我感到困惑的例子。
这个例子:
class Vehicle {
String name;
Vehicle() {
name = "Vehicle";
}
}
class HeavyVehicle extends Vehicle {
HeavyVehicle() {
name = "HeavyVehicle";
}
}
class Truck extends HeavyVehicle {
Truck() {
name = "Truck";
}
}
class LightVehicle extends Vehicle {
LightVehicle() {
name = "LightVehicle";
}
}
public class InstanceOfExample {
static boolean result;
static HeavyVehicle hV = new HeavyVehicle();
static Truck T = new Truck();
static HeavyVehicle hv2 = null;
public static void main(String[] args) {
result = hV instanceof HeavyVehicle;
System.out.print("hV is an HeavyVehicle: " + result + "\n");
result = T instanceof HeavyVehicle;
System.out.print("T is an HeavyVehicle: " + result + "\n");
result = hV instanceof Truck;
System.out.print("hV is a Truck: " + result + "\n");
result = hv2 instanceof HeavyVehicle;
System.out.print("hv2 is an HeavyVehicle: " + result + "\n");
hV = T; //Sucessful Cast form child to parent
T = (Truck) hV; //Sucessful Explicit Cast form parent to child
}
}
在最后一行中,T 被分配了引用 hV 并类型转换为 (Truck),为什么在注释中说这是从父到子的成功显式转换?据我了解,转换(隐式或显式)只会更改对象的声明类型,而不是实际类型(不应更改,除非您实际将新类实例分配给该对象的字段引用)。如果 hv 已经被分配了一个 HeavyVehicle 类的实例,它是 Truck 类的超类,那么如何将这个字段类型转换为一个更具体的子类,称为 Truck,它从 HeavyVehicle 类扩展而来?
我的理解是,强制转换的目的是限制对对象(类实例)的某些方法的访问。因此,您不能将对象转换为比对象实际分配的类具有更多方法的更具体的类。这意味着该对象只能被转换为超类或与其实际实例化的类相同的类。这是正确的还是我错了?我仍在学习,所以我不确定这是否是看待事物的正确方式。
我也明白这应该是一个向下转型的例子,但我不确定如果实际类型没有这个对象被向下转型的类的方法,这实际上是如何工作的。显式转换是否会以某种方式改变对象的实际类型(而不仅仅是声明的类型),以便该对象不再是 HeavyVehicle 类的实例,而是现在成为 Truck 类的实例?
原文由 SineLaboreNihil 发布,翻译遵循 CC BY-SA 4.0 许可协议
参考与对象与类型
对我来说,关键是理解对象与其引用之间的区别,或者换句话说,理解对象与其类型之间的区别。
当我们在 Java 中创建一个对象时,我们声明了它的真实性质,它永远不会改变(例如
new Truck()
)。但是 Java 中的任何给定对象都可能具有 多种类型。 其中一些类型显然是由类层次结构给定的,其他的则不那么明显(即泛型、数组)。特别是对于引用类型,类层次结构决定了子类型化规则。例如,在您的示例中, 所有卡车都是 heavy vehicles , 所有 heavy vehicles 都是 vehicle 。因此,这种 is-a 关系的层次结构决定了一辆卡车有 _多种兼容类型_。
当我们创建一个
Truck
时,我们定义了一个“引用”来访问它。此引用必须具有这些兼容类型之一。所以这里的关键点是认识 _到对对象的引用不是对象本身_。正在创建的对象的性质永远不会改变。但是我们可以使用不同种类的兼容引用来访问对象。这是多态性的特点之一。可以通过不同“兼容”类型的引用访问同一对象。
当我们进行任何类型的转换时,我们只是假设了不同类型引用之间这种兼容性的性质。
向上转型或扩大参考转换
现在,有了
Truck
类型的引用,我们可以很容易地得出结论,它始终与Vehicle
类型的引用兼容,因为 所有卡车都是 Vehicles 。因此,我们可以向上转换引用,而无需使用显式转换。它也被称为 扩大引用转换,主要是因为随着类型层次结构的上升,类型变得更通用。
如果需要,您可以在此处使用显式强制转换,但没有必要这样做。我们可以看到
t
和v
引用的实际对象是一样的。它是,而且永远是Truck
。向下转换或缩小引用转换
现在,有了
Vechicle
类型的引用,我们不能“安全地”断定它实际上引用了Truck
。毕竟它也可能引用其他形式的 Vehicle。例如如果您在代码中的某处发现
v
引用而不知道它引用的是哪个特定对象,则您不能“安全地”论证它是指向Truck
还是指向Sedan
或任何其他类型的车辆。编译器很清楚它不能对被引用对象的真实性质提供任何保证。但是程序员,通过阅读代码,可以确定他/她在做什么。与上述情况一样,您可以清楚地看到
Vehicle v
正在引用Sedan
。在这些情况下,我们可以做一个沮丧。我们这样称呼它是因为我们在类型层次结构中向下。我们也将此称为 缩小引用转换。我们可以说
这总是需要显式转换,因为编译器不能确定这是安全的,这就是为什么这就像问程序员,“你确定你在做什么吗?”。如果你对编译器撒谎,你会在运行时抛出一个
ClassCastException
,当这段代码被执行时。其他类型的子类型化规则
Java 中还有其他子类型化规则。例如,还有一个称为数字提升的概念,它会自动强制表达式中的数字。像
在这种情况下,由两种不同类型(整数和双精度)组成的表达式在计算表达式之前将整数向上转换/强制转换为双精度值,从而产生双精度值。
您也可以进行原始的向上转型和向下转型。如在
在这些情况下,当信息可能丢失时需要显式转换。
一些子类型化规则可能不那么明显,例如数组的情况。例如,所有引用数组都是
Object[]
的子类型,但原始数组不是。在泛型的情况下,特别是使用像
super
和extends
这样的通配符,事情变得更加复杂。像其中
a
b
的子类型。c
d
的子类型。Using covariance, wherever
List<? extends Number>
appears you can pass aList<Integer>
, thereforeList<Integer>
is a subtype ofList<? extends Number>
.Contravariance produce a similar effect and wherever the type
List<? super Number>
appears, you could pass aList<Object>
, which makes ofList<Object>
a subtype ofList<? super Number>
.装箱和拆箱也受制于一些铸造规则(在我看来这也是某种形式的强制)。