为了账号安全,请及时绑定邮箱和手机立即绑定

Java中的显式类型转换示例

Java中的显式类型转换示例

杨__羊羊 2019-12-25 14:11:09
我在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类的超类,那么如何将该字段类型转换为一个更具体的子类,该子类从HeavyVehicle类扩展而来?据我了解,强制转换的目的是限制对对象(类实例)某些方法的访问。因此,您不能将对象转换为比该对象实际分配的类具有更多方法的更具体的类。这意味着该对象只能转换为超类或与实际实例化该类的类相同的类。这是正确的还是我错了?我仍在学习,所以不确定这是否是正确看待事物的方法。我也知道这应该是向下转换的示例,但是如果实际类型没有向下转换该对象的类的方法,则我不确定这实际上如何工作。显式转换是否会以某种方式更改对象的实际类型(而不仅仅是声明的类型),从而使该对象不再是HeavyVehicle类的实例,而是现在成为Truck类的实例?
查看完整描述

3 回答

?
绝地无双

TA贡献1946条经验 获得超4个赞

引用vs对象vs类型


对我而言,关键是理解对象及其引用之间的区别,换句话说,就是对象及其类型之间的区别。


当我们用Java创建对象时,我们声明了它的真实本质,它将永远不会改变。但是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,因为所有卡车都是Vehicles。因此,我们可以不使用显式强制转换就向上引用该参考。


Truck t = new Truck();

Vehicle v = t;

这也称为扩展引用转换,基本上是因为当您进入类型层次结构时,类型会变得更加通用。


如果需要,可以在此处使用显式转换,但这不是必需的。我们可以看到t和所引用的实际对象v是相同的。是,并且将永远是Truck。


向下转换或缩小参考转换


现在,有了类型的引用,Vechicle我们不能“安全地”得出结论,它实际上引用了Truck。毕竟,它也可以引用其他形式的车辆。例如


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

如果您v在代码中的某处找到引用,却不知道引用的是哪个特定对象,则不能“安全地”论证它是指向a Truck还是指向a Sedan或任何其他种类的车辆。


编译器很清楚,它不能对所引用对象的真实性质提供任何保证。但是程序员通过阅读代码可以确定他/她正在做什么。像上述情况一样,您可以清楚地看到它Vehicle v引用了Sedan。


在这些情况下,我们可以进行下调。之所以这样称呼,是因为我们要沿着类型层次结构前进。我们也称此为缩窄参考转换。我们可以说


Sedan s = (Sedan) v;

这总是需要显式的强制转换,因为编译器不能确定这样做是否安全,这就是为什么这就像问程序员“您确定自己在做什么吗?”。如果您对编译器撒谎,则ClassCastException在执行此代码时会在运行时得到。


其他种类的分型规则


Java中还有其他子类型化规则。例如,还有一个称为数字提升的概念,它可以自动强制表达式中的数字。像


double d = 5 + 6.0;

在这种情况下,由两种不同类型(整数和双精度型)组成的表达式在评估该表达式之前将整数强制转换/强制为双精度型,从而产生双精度值。


您也可以进行原始的向上转换和向下转换。如


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。


装箱和拆箱也受制于某些强制转换规则(不过,在我看来,这也是一种强制形式)。


查看完整回答
反对 回复 2019-12-25
?
不负相思意

TA贡献1777条经验 获得超10个赞

你答对了。您只能将对象成功地强制转换为其类,其某些父类或该对象或其父级实现的某些接口。如果将其强制转换为某些父类或接口,则可以将其强制转换为原始类型。


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


投射通常用于使在同一字段中存储不同的东西(具有相同的接口或父类,例如您的所有汽车)或具有相同类型的集合(例如Vehicle)成为可能,以便您可以使用他们以同样的方式。


如果您随后希望获得完全访问权限,则可以将其撤回(例如,车辆到卡车)


在示例中,我非常确定最后一条语句无效,并且注释完全错误。


查看完整回答
反对 回复 2019-12-25
?
开心每一天1111

TA贡献1836条经验 获得超13个赞

最后一行代码可以毫无例外地编译并成功运行。它所做的是完全合法的。


hV最初是指类型为HeavyVehicle的对象(我们将此对象称为h1):


static HeavyVehicle hV = new HeavyVehicle(); // hV now refers to h1.

稍后,我们将hV引用为卡车类型的另一个对象(我们将此对象称为t1):


hV = T; // hV now refers to t1.

最后,我们使T参考t1。


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

T已经提到了t1,因此该语句没有任何改变。


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

当我们到达最后一行时,hV不再引用HeavyVehicle的实例。它指的是Truck的一个实例。将Truck的实例强制转换为Truck类型是没有问题的。


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

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


显式转换是否会以某种方式更改对象的实际类型(而不仅仅是声明的类型),从而使该对象不再是HeavyVehicle类的实例,而是现在成为Truck类的实例?

否。对象一旦创建,就无法更改其类型。它不能成为另一个类的实例。


重申一下,最后一行没有任何变化。T在该行之前指的是t1,在其后指的是t1。


那么,为什么在最后一行必须进行显式强制转换(卡车)?我们基本上只是在帮助编译器。


我们知道,到那时,hV引用了类型为Truck的对象,因此可以将类型为Truck的对象分配给变量T。但是编译器还不足够聪明。编译器希望我们保证,当到达该行并尝试进行分配时,它将找到一个正在等待它的Truck实例。


查看完整回答
反对 回复 2019-12-25
  • 3 回答
  • 0 关注
  • 507 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信