我在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类的父类(super class),那么如何将该字段类型转换为更具体的子类,该子类从HeavyVehicle类扩展而来?
据我了解,强制转换的目的是限制对对象(类实例)某些方法的访问。因此,您不能将对象转换为比该对象实际分配的类具有更多方法的更特定的类。这意味着该对象只能转换为父类(super class)或与实际实例化该类的类相同的类。这是正确的还是我错了?我仍在学习,因此不确定这是否是正确看待事物的方法。
我也理解这应该是向下转换的示例,但是如果实际类型没有向下转换此对象的类的方法,则我不确定这实际上如何工作。显式转换是否会以某种方式更改对象的实际类型(而不仅仅是声明的类型),从而使该对象不再是HeavyVehicle类的实例,而是现在成为Truck类的实例?
最佳答案
引用vs对象vs类型
对我而言,关键是理解对象及其引用之间的区别,换句话说,就是对象及其类型之间的区别。
当我们用Java创建对象时,我们声明了它的真实本质,它将永远不会改变(例如new Truck()
)。但是Java中的任何给定对象都可能具有多种类型。其中一些类型显然是由类层次结构给出的,而其他类型则不是那么明显(即泛型,数组)。
专门针对引用类型,类层次结构规定了子类型化规则。例如,在您的示例中,所有卡车均为重型车辆,所有重型车辆均为车辆。因此,此is-a关系层次结构指示卡车具有多种兼容类型。
当我们创建Truck
时,我们定义一个“引用”来访问它。该引用必须具有这些兼容类型之一。
Truck t = new Truck(); //or
HeavyVehicle hv = new Truck(); //or
Vehicle h = new Truck() //or
Object o = new Truck();
因此,这里的关键是要认识到对对象的引用不是对象本身。创建的对象的性质永远不会改变。但是我们可以使用各种兼容的引用来访问该对象。这是这里多态性的特征之一。可以通过引用不同“兼容”类型的对象来访问同一对象。当我们进行任何类型的转换时,我们只是假设不同类型的引用之间具有这种兼容性。
向上转换或扩展引用转换
现在,有了
Truck
类型的引用,我们可以轻松得出结论,它始终与Vehicle
类型的引用兼容,因为所有卡车都是车辆。因此,我们可以不使用显式强制转换就转换引用。Truck t = new Truck();
Vehicle v = t;
它也称为widening reference conversion,基本上是因为当您进入类型层次结构时,类型会变得更加通用。如果需要,可以在此处使用显式强制转换,但这是不必要的。我们可以看到
t
和v
引用的实际对象是相同的。是,并且将永远是Truck
。向下转换或缩小引用转换
现在,有了
Vechicle
类型的引用,我们不能“安全地”得出结论,它实际上引用了Truck
。毕竟,它也可以引用其他形式的车辆。例如Vehicle v = new Sedan(); //a light vehicle
如果您在代码中的某个位置找到v
引用,却不知道它引用的是哪个特定对象,则不能“安全”地论证它是指向Truck
还是指向Sedan
或任何其他类型的车辆。编译器很清楚,它不能对所引用对象的真实性质提供任何保证。但是程序员通过阅读代码可以确定他/她在做什么。像上面的情况一样,您可以清楚地看到
Vehicle v
引用了Sedan
。在这些情况下,我们可以进行下调。之所以这样称呼,是因为我们沿着类型层次结构前进。我们也将其称为narrowing reference conversion。我们可以说
Sedan s = (Sedan) v;
这总是需要显式的强制转换,因为编译器无法确保这样做是安全的,这就是为什么这就像问程序员“您确定自己在做什么吗?”。如果您对编译器撒谎,则会在执行该代码时在运行时向您抛出ClassCastException
。其他类型的分型规则
Java中还有其他子类型化规则。例如,还有一个称为数字提升的概念,它可以自动强制表达式中的数字。像
double d = 5 + 6.0;
在这种情况下,由两种不同类型(整数和 double 型)组成的表达式会在评估表达式之前将整数向上转换/强制为 double 型,从而产生 double 值。您也可以进行原始的向上转换和向下转换。如
int a = 10;
double b = a; //upcasting
int c = (int) b; //downcasting
在这些情况下,当信息可能丢失时,需要显式强制转换。某些子类型化规则可能不那么明显,例如在数组的情况下。例如,所有引用数组都是
Object[]
的子类型,但原始数组不是。在泛型的情况下,尤其是使用诸如
super
和extends
这样的通配符时,事情变得更加复杂。像List<Integer> a = new ArrayList<>();
List<? extends Number> b = a;
List<Object> c = new ArrayList<>();
List<? super Number> d = c;
其中b
的类型是a
的子类型。 d
的类型是c
的子类型。装箱和拆箱也受制于某些强制转换规则(不过,在我看来,这也是一种强制形式)。
关于java - Java中的显式类型转换示例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20096297/