在java中显式types转换的例子

我在http://www.javabeginner.com/learn-java/java-object-typecasting中遇到过这个例子,在它谈到显式types转换的部分,有一个例子让我困惑。

例子:

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和types转换为(Truck)的最后一行中,为什么它在评论中说这是一个从父母到孩子的成功显式演员? 据我所知铸造(隐式或显式)只会改变声明的对象的types,而不是实际的types(除非你实际上分配一个新的类实例到该对象的字段引用,它不应该改变)。 如果hv已经被分配了一个HeavyVehicle类的实例,它是Truck类的超类,那么如何将这个字段types转换为一个更具体的子类,称为Truck,它是从HeavyVehicle类扩展而来的?

我理解的方式是,投射服务的目的是限制访问对象(类实例)的某些方法。 因此,你不能把一个对象作为一个更具体的类来转换,这个类有更多的方法,然后是对象的实际分配的类。 这意味着该对象只能被转换为一个超类或与其实际实例化的类相同的类。 这是正确的还是我错了? 我还在学习,所以我不确定这是否是正确的看事情的方式。

我也明白,这应该是一个向下转换的例子,但我不知道如果实际的types没有这个对象的类被贬低的方法如何实际工作。 显式转换以某种方式改变了对象的实际types(不仅仅是声明的types),所以这个对象不再是HeavyVehicle类的一个实例,而是现在成为Truck类的一个实例?

参考vs对象与types

对我来说,关键是理解一个对象和它的引用之间的区别,或者换句话说,一个对象和它的types之间的区别。

当我们用Java创build一个对象时,我们声明它的真实性质,它永远不会改变。 但是Java中的任何给定的对象都可能有多种types。 这些types中的一些显然是由于类层次结构而给出的,其他types并不是那么明显(即generics,数组)。

特别是对于引用types,类层次结构决定了子types规则。 例如在你的例子中, 所有的卡车都是重型车辆所有的重型车辆都是车辆 。 因此,这种关系的层次结构表明卡车具有多种兼容的types

当我们创build一个Truck ,我们定义一个“参考”来访问它。 该引用必须具有其中一种兼容types。

 Truck t = new Truck(); //or HeavyVehicle hv = new Truck(); //or Vehicle h = new Truck() //or Object o = new Truck(); 

所以这里的关键是认识到对象的引用不是对象本身 。 被创build的对象的性质永远不会改变。 但是我们可以使用不同种类的兼容引用来访问对象。 这是多态的特征之一。 同一个对象可以通过引用不同的“兼容”types来访问。

当我们做任何types的转换时,我们只是简单地假设不同types的转换之间的兼容性。

上传或扩展参考转换

现在,参照Trucktypes,我们可以很容易地得出结论,它总是与Vehicle的参考兼容,因为所有卡车都是车辆 。 因此,我们可以上传引用,而不使用明确的转换。

 Truck t = new Truck(); Vehicle v = t; 

它也被称为扩展的引用转换 ,基本上是因为当你在types层次结构中,types变得更一般。

如果你愿意的话,你可以在这里使用一个明确的演员,但这是没有必要的。 我们可以看到tv引用的实际对象是相同的。 这是,而且将永远是一辆Truck

向下转换或缩小参考转换

现在,参考Vechicletypes,我们不能“安全”地推断它实际上是引用了Truck 。 毕竟它也可能引用一些其他forms的车辆。 例如

 Vehicle v = new Sedan(); //a light vehicle 

如果在代码中的某个地方findv引用而不知道它引用的是哪个特定的对象,那么无论它是指向一辆Truck还是一辆Sedan或任何其他types的车辆,都不能“安全”地进行争论。

编译器很清楚,它不能保证被引用对象的真实性。 但程序员通过阅读代码可以确定他/她在做什么。 就像在上面的情况一样,你可以清楚地看到Vehicle v正在引用一辆Sedan

在这种情况下,我们可以沮丧。 我们这样称呼它,因为我们正沿着types层次结构。 我们也称这是一个缩小的参考转换 。 我们可以说

 Sedan s = (Sedan) v; 

这总是需要明确的转换,因为编译器不能确定这是安全的,这就是为什么这就像问程序员“你确定你在做什么?”。 如果你对编译器说谎,你将在运行时得到一个ClassCastException ,当这个代码被执行时。

其他types的子types规则

在Java中还有其他子types的规则。 例如,还有一个称为数字促销的概念,它自动强制expression式中的数字。 像

 double d = 5 + 6.0; 

在这种情况下,由两个不同types(整数和双精度)组成的expression式,在计算expression式之前将整数转换为double,导致double值。

你也可以做原始的向上转换和向下转换。 如在

 int a = 10; double b = a; //upcasting int c = (int) b; //downcasting 

在这些情况下,当信息可能丢失时,需要明确的转换。

一些分类规则可能不是那么明显,就像数组的情况一样。 例如,所有引用数组都是Object[]子types,但是原始数组不是。

在generics的情况下,特别是使用superextends等通配符的时候,情况会变得更加复杂。 像

 List<Integer> a = new ArrayList<>(); List<? extends Number> b = a; List<Object> c = new ArrayList<>(); List<? super Number> d = c; 

如果b的types是a的types的子types。 而d的types是ctypes的子types。

而且拳击和拆箱也受到一些施法规则的限制(在我看来,这也是某种forms的强制)。

你说得对。 你可以成功地将一个对象转换成它的类,它的一些父类或它或它的父母实现的某个接口。 如果将其转换为某些父类或接口,则可以将其转换回原始types。

否则(虽然你可以在源代码中),它将导致运行时ClassCastException。

铸造通常用于使相同的领域或相同types的集合(例如车辆)存储不同的东西(相同的接口或父类,例如所有的汽车),以便您可以使用他们以同样的方式。

如果你想获得完整的访问权限,你可以将它们转换回来(例如Vehicle to Truck)


在这个例子中,我很确定最后一条语句是无效的,而注释完全是错误的。

当你从一个卡车对象到一个HeavyVehicle这样的:

 Truck truck = new Truck() HeavyVehicle hv = truck; 

该对象仍然是卡车,但您只能使用HeavyVehicle参考访问heavyVehicle方法和字段。 如果您再次下车到卡车,您可以再次使用所有的卡车方法和领域。

 Truck truck = new Truck() HeavyVehicle hv = truck; Truck anotherTruckReference = (Truck) hv; // Explicit Cast is needed here 

如果实际的向下转换对象不是卡车,那么ClassCastException将被抛出,如下例所示:

 HeavyVehicle hv = new HeavyVehicle(); Truck tr = (Truck) hv; // This code compiles but will throw a ClasscastException 

抛出exception是因为实际对象不是正确的类,它是超类的一个对象(HeavyVehicle)

最后一行代码编译并运行成功,没有例外。 它做的是完全合法的。

  1. hV最初是指HeavyVehicletypes的对象(我们称之为h1):

     static HeavyVehicle hV = new HeavyVehicle(); // hV now refers to h1. 
  2. 后来,我们让hV引用一个不同的对象,types为Truck(让我们调用这个对象t1):

     hV = T; // hV now refers to t1. 
  3. 最后,我们让T指t1。

     T = (Truck) hV; // T now refers to t1. 

T已经提到了t1,所以这个声明没有改变任何东西。

如果hv已经被分配了一个HeavyVehicle类的实例,该类是Truck类的超类,那么如何将这个字段types转换为一个更具体的子类,称为Truck,它是从HeavyVehicle类扩展而来的?

当我们到达最后一行时,hV不再指重型车的一个实例。 它是指卡车的一个实例。 将卡车的一个实例铸造成卡车是没有问题的。

这意味着该对象只能被转换为一个超类或与其实际实例化的类相同的类。 这是正确的还是我错了?

基本上,是的,但不要将对象本身与引用对象的variables混淆。 见下文。

显式转换以某种方式改变对象的实际types(不仅仅是声明的types),这样这个对象不再是HeavyVehicle类的一个实例,而是现在成为Truck类的一个实例?

不。一旦创build了一个对象,就永远不能改变它的types。 它不能成为另一个阶级的一个实例。

重申,最后一行没有任何改变。 T表示该行之前的t1,之后表示t1。

那么为什么在最后一行需要显式演员(卡车)呢? 我们基本上只是帮助编译器。

我们知道那个时候,hV指的是一个Trucktypes的对象,所以把这个Truck对象赋值给variablesT是可以的。但是编译器不够聪明。 编译器需要我们的保证,当它到达该线路并尝试进行分配时,它会find卡车正在等待的一个实例。

上面的代码会编译并运行正常。 现在改变上面的代码并添加以下行System.out.println(T.name);

这将确保您在将HTC对象下载为Truck之后不使用对象T.

目前,在你的代码中你没有使用T后downcast所以一切都很好,工作。

这是因为,通过明确地将hV转换为Truck,编译器确实抱怨程序员认为该对象是铸造的对象,并且知道什么对象被铸造到什么地方。

但是在运行时JVM不能certificate这个强制转换,并抛出ClassCastException“HeavyVehicle不能转换为卡车”。

为了更好地说明上面提到的几点,我修改了有问题的代码,并使用内联注释(包括实际输出)添加更多代码,如下所示:

 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 a HeavyVehicle: " + result + "\n"); // true result = T instanceof HeavyVehicle; System.out.print("T is a HeavyVehicle: " + result + "\n"); // true // But the following is in error. // T = hV; // error - HeavyVehicle cannot be converted to Truck because all hV's are not trucks. result = hV instanceof Truck; System.out.print("hV is a Truck: " + result + "\n"); // false hV = T; // Sucessful Cast form child to parent. result = hV instanceof Truck; // This only means that hV now points to a Truck object. System.out.print("hV is a Truck: " + result + "\n"); // true T = (Truck) hV; // Sucessful Explicit Cast form parent to child. Now T points to both HeavyVehicle and Truck. // And also hV points to both Truck and HeavyVehicle. Check the following codes and results. result = hV instanceof Truck; System.out.print("hV is a Truck: " + result + "\n"); // true result = hV instanceof HeavyVehicle; System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true result = hV instanceof HeavyVehicle; System.out.print("hV is a HeavyVehicle: " + result + "\n"); // true result = hv2 instanceof HeavyVehicle; System.out.print("hv2 is a HeavyVehicle: " + result + "\n"); // false } }