java中的显式类型转换示例

时间:2013-11-20 12:42:08

标签: java casting downcast

我在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),为什么在评论中说这是从父到子的成功显式演员?据我所知,cast(隐式或显式)只会更改声明的对象类型,而不是实际类型(除非您实际为该对象的字段引用分配新的类实例,否则不应更改)。如果已经为hv分配了一个HeavyVehicle类的实例,它是Truck类的超类,那么如何将此字段类型转换为一个名为Truck的更具体的子类,该子类从HeavyVehicle类扩展?

我理解的方式是,转换的目的是限制对对象(类实例)的某些方法的访问。因此,您不能将对象转换为更具体的类,该类具有更多方法,然后是对象的实际分配类。这意味着该对象只能作为超类或与实际实例化的类相同的类进行转换。这是对的还是我错了?我还在学习,所以我不确定这是否是正确看待事物的方式。

我也明白这应该是一个向下转换的例子,但我不确定如果实际类型没有这个对象被降级的类的方法,这实际上是如何工作的。显式转换是否以某种方式改变了对象的实际类型(而不仅仅是声明的类型),因此该对象不再是HeavyVehicle类的实例,而是现在成为Truck类的实例?

6 个答案:

答案 0 :(得分:60)

参考与对象与类型

对我而言,关键在于理解对象与其引用之间的区别,或者换句话说,对象与其类型之间的区别。

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

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

当我们创建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,主要是因为当你进入类型层次结构时,类型变得更加通用。

如果你愿意,可以在这里使用显式转换,但这是不必要的。我们可以看到tv引用的实际对象是相同的。它始终是Truck

向下转换或缩小参考转换

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

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

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

编译器很清楚它无法保证被引用对象的真实性质。但程序员通过阅读代码可能会确定他/她在做什么。与上述情况一样,您可以清楚地看到Sedan引用Vehicle v

在这些情况下,我们可以做一个沮丧。我们这样称呼它是因为我们正在进行类型层次结构。我们也称之为narrowing reference conversion。我们可以说

Sedan

这总是需要一个显式的强制转换,因为编译器不能确定这是安全的,这就是为什么这就像问程序员,“你确定你在做什么吗?”。如果您对编译器撒谎,则在执行此代码时,将在运行时获得Sedan s = (Sedan) v;

其他种类的子类型规则

Java中还有其他子类型规则。例如,还有一个名为数字促销的概念会自动强制表达式中的数字。喜欢

ClassCastException

在这种情况下,一个表达式由两个不同的类型组成,整数和双,在估计表达式之前将整数转换/强制转换为double,从而得到一个double值。

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

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类型的子类型。

而拳击和拆箱也受制于一些施法规则(在我看来,这也是某种形式的强制行为)。

答案 1 :(得分:5)

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

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

Casting通常用于在同一个字段或相同类型的集合(例如Vehicle)中存储不同的东西(相同界面或父类,例如所有汽车),以便您可以用同样的方式与他们合作。

如果您希望获得完全访问权限,则可以将其强制转换(例如,“车辆到卡车”)


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

答案 2 :(得分:2)

当您从Truck对象进行强制转换时,如下所示:

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)

答案 3 :(得分:2)

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

  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.
    
  4. T已经提到了t1,所以这个陈述没有改变任何东西。

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

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

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

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

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

    没有。创建对象后,永远不会更改其类型。它不能成为另一个类的实例。

    重申一下,最后一行没有任何改变。 T在该行之前引用t1,之后引用t1。

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

    我们知道到那时,hV引用了Truck类型的对象,所以可以将Truck类型的对象分配给变量T.但是编译器不够聪明,不知道。编译器希望我们保证当它到达那一行并尝试进行赋值时,它会找到一个等待它的卡车实例。

答案 4 :(得分:1)

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

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

目前,在您的代码中,您在转发后不使用T,所以一切都很好并正常工作。

这是因为,通过显式地将hV转换为Truck,编译器确实抱怨考虑到程序员已经转换了对象并且知道什么对象被转换为什么。

但是在运行时JVM无法证明强制转换并抛出ClassCastException“HeavyVehicle无法转换为Truck”。

答案 5 :(得分:0)

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

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

        }

}