繁体   English   中英

Java 中的显式类型转换示例

[英]Explicit type casting example in Java

我在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 类的实例?

引用 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类型的引用兼容,因为所有 Trucks 都是 Vehicles 因此,我们可以向上转换引用,而不使用显式转换。

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

它也称为扩展引用转换,基本上是因为随着您在类型层次结构中的上升,类型变得更加通用。

如果需要,您可以在此处使用显式强制转换,但这是不必要的。 我们可以看到tv引用的实际对象是相同的。 它是,并且永远是Truck

向下转换或缩小参考转换

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

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

如果您在代码中的某处找到v引用而不知道它引用的是哪个特定对象,则您无法“安全地”论证它是否指向TruckSedan或任何其他类型的车辆。

编译器很清楚它不能保证所引用对象的真实性质。 但是程序员通过阅读代码,可以确定他/她在做什么。 与上面的例子一样,您可以清楚地看到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[]子类型,但原始数组不是。

而在泛型的情况下,特别是使用superextends等通配符时,事情变得更加复杂。 喜欢在

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

其中的类型b是的类型的子类型a d的类型是c类型的子类型。

而且装箱和拆箱也受制于一些铸造规则(在我看来,这也是某种形式的强制)。

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

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

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

如果您想获得完全访问权限,您可以将它们退回(例如,车辆到卡车)


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

当您像这样从 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

抛出异常是因为实际对象不属于正确的类,它是超类 (HeavyVehicle) 的对象

最后一行代码编译并成功运行,没有异常。 它的所作所为是完全合法的。

  1. hV 最初指的是一个 HeavyVehicle 类型的对象(我们称这个对象为 h1):

     static HeavyVehicle hV = new HeavyVehicle(); // hV now refers to h1.
  2. 稍后,我们让 hV 指向一个不同的对象,类型为 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 类的超类,那么如何将这个字段类型转换为一个更具体的子类,称为 Truck,它从 HeavyVehicle 类扩展而来?

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

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

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

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

不可以。对象一旦被创建,就永远不能改变它的类型。 它不能成为另一个类的实例。

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

那么为什么最后一行需要显式转换 (Truck) 呢? 我们基本上只是帮助编译器。

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

上面的代码将编译并运行良好。 现在更改上面的代码并添加以下行 System.out.println(T.name);

这将确保您在将 hV 对象向下转换为 Truck 后不使用对象 T。

目前,在您的代码中,您在向下转型后没有使用 T,因此一切正常且工作正常。

这是因为,通过将 hV 显式转换为 Truck,编译器确实会抱怨认为程序员已转换对象并且知道将什么对象转换为什么。

但是在运行时 JVM 无法证明转换的合理性并抛出 ClassCastException “HeavyVehicle cannot be cast to Truck”。

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

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

        }

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM