简体   繁体   English

Java 中的显式类型转换示例

[英]Explicit type casting example in Java

I have come across this example on http://www.javabeginner.com/learn-java/java-object-typecasting and in the part where it talks about explicit type casting there is one example which confuses me.我在http://www.javabeginner.com/learn-java/java-object-typecasting上遇到过这个例子,在它谈到显式类型转换的部分有一个例子让我感到困惑。

The example:例子:

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
    }
}

In the last line where T is assigned the reference hV and typecast as (Truck), why does it say in the comment that this is a Successful Explicit Cast from parent to child?在 T 被分配引用 hV 并且类型转换为 (Truck) 的最后一行中,为什么在评论中说这是从父级到子级的成功显式转换? As I understand casting (implicit or explicit) will only change the declared type of object, not the actual type (which shouldn't ever change, unless you actually assign a new class instance to that object's field reference).据我所知,转换(隐式或显式)只会更改对象的声明类型,而不是实际类型(它不应该更改,除非您实际为该对象的字段引用分配了一个新类实例)。 If hv was already assigned an instance of a HeavyVehicle class which is a super class of the Truck class, how can then this field be type cast into a more specific subclass called Truck which extends from the HeavyVehicle class?如果已经为 hv 分配了一个 HeavyVehicle 类的实例,该类是 Truck 类的超类,那么如何将这个字段类型转换为一个更具体的子类,称为 Truck,它从 HeavyVehicle 类扩展而来?

The way I understand it is that casting serves the purpose of limiting access to certain methods of an object (class instance).我的理解是,强制转换的目的是限制对对象(类实例)的某些方法的访问。 Therefore you can't cast an object as a more specific class which has more methods then the object's actual assigned class.因此,您不能将对象转换为更具体的类,该类具有比对象实际分配的类更多的方法。 That means that the object can only be cast as a superclass or the same class as the class from which it was actually instantiated.这意味着该对象只能被转换为超类或与实际实例化它的类相同的类。 Is this correct or am I wrong here?这是正确的还是我错了? I am still learning so I am not sure if this is the correct way of looking at things.我仍在学习,所以我不确定这是否是正确的看待事物的方式。

I also understand that this should be an example of downcasting, but I am not sure how this actually works if the actual type doesn't have the methods of the class to which this object is being downcasted.我也明白这应该是向下转换的一个例子,但是如果实际类型没有这个对象被向下转换到的类的方法,我不确定这实际上是如何工作的。 Does explicit casting somehow change the actual type of object (not just the declared type), so that this object is no longer an instance of HeavyVehicle class but now becomes an instance of Truck class?显式转换是否以某种方式改变了对象的实际类型(不仅仅是声明的类型),以便该对象不再是 HeavyVehicle 类的实例,而是现在成为 Truck 类的实例?

Reference vs Object vs Types引用 vs 对象 vs 类型

The key, for me, is understanding the difference between an object and its references, or put in other words the difference between an object and its types.对我来说,关键是理解对象与其引用之间的区别,或者换句话说,就是理解对象与其类型之间的区别。

When we create an object in Java, we declare its true nature, which will never change (eg new Truck() ).当我们在 Java 中创建一个对象时,我们声明了它的真实性质,它永远不会改变(例如new Truck() )。 But any given object in Java is likely to have multiple types.但是 Java 中的任何给定对象都可能有多种类型。 Some of these types are obviously given by the class hierarchy, others are not so obvious (ie generics, arrays).其中一些类型显然是由类层次结构给出的,另一些则不那么明显(即泛型、数组)。

Specifically for reference types, the class hierarchy dictates the subtyping rules.特别是对于引用类型,类层次结构规定了子类型规则。 For instance in your example all trucks are heavy vehicles , and all heavy vehicles are vehicle s.例如,在您的示例中,所有卡车都是重型车辆所有重型车辆都是车辆 Therefore, this hierarchy of is-a relationships dictates that a truck has multiple compatible types .因此,这种 is-a 关系的层次结构决定了卡车具有多种兼容类型

When we create a Truck , we define a "reference" to get access to it.当我们创建Truck ,我们定义了一个“引用”来访问它。 This reference must have one of those compatible types.此引用必须具有这些兼容类型之一。

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

So the key point here is the realization that the reference to the object is not the object itself .所以这里的关键点是认识到对对象的引用不是对象本身 The nature of the object being created is never going to change.被创建对象的性质永远不会改变。 But we can use different kinds of compatible references to gain access to the object.但是我们可以使用不同类型的兼容引用来访问对象。 This is one of the features of polymorphism here.这是这里多态的特点之一。 The same object may be accessed through references of different "compatible" types.可以通过不同“兼容”类型的引用访问同一对象。

When we do any kind of casting, we are simply assuming the nature of this compatibility between different types of references.当我们进行任何类型的转换时,我们只是假设不同类型引用之间的这种兼容性的性质。

Upcasting or Widening Reference Conversion向上转换或扩大参考转换

Now, having a reference of type Truck , we can easily conclude that it's always compatible with a reference of type Vehicle , because all Trucks are Vehicles .现在,有了Truck类型的引用,我们可以很容易地得出结论,它始终与Vehicle类型的引用兼容,因为所有 Trucks 都是 Vehicles Therefore, we could upcast the reference, without using an explicit cast.因此,我们可以向上转换引用,而不使用显式转换。

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

It is also called a widening reference conversion , basically because as you go up in the type hierarchy, the type gets more general.它也称为扩展引用转换,基本上是因为随着您在类型层次结构中的上升,类型变得更加通用。

You could use an explicit cast here if you wanted, but it would be unnecessary.如果需要,您可以在此处使用显式强制转换,但这是不必要的。 We can see that the actual object being referenced by t and v is the same.我们可以看到tv引用的实际对象是相同的。 It is, and will always be a Truck .它是,并且永远是Truck

Downcasting or Narrowing Reference Conversion向下转换或缩小参考转换

Now, having a reference of type Vechicle we cannot "safely" conclude that it actually references a Truck .现在,有了Vechicle类型的引用,我们不能“安全地”得出它实际上引用了Truck结论。 After all it may also reference some other form of Vehicle.毕竟它也可能引用其他形式的 Vehicle。 For instance例如

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

If you find the v reference somewhere in your code without knowing to which specific object it is referencing, you cannot "safely" argument whether it points to a Truck or to a Sedan or any other kind of vehicle.如果您在代码中的某处找到v引用而不知道它引用的是哪个特定对象,则您无法“安全地”论证它是否指向TruckSedan或任何其他类型的车辆。

The compiler knows well that it cannot give any guarantees about the true nature of the object being referenced.编译器很清楚它不能保证所引用对象的真实性质。 But the programmer, by reading the code, may be sure of what s/he is doing.但是程序员通过阅读代码,可以确定他/她在做什么。 Like in the case above, you can clearly see that Vehicle v is referencing a Sedan .与上面的例子一样,您可以清楚地看到Vehicle v引用了Sedan

In those cases, we can do a downcast.在这些情况下,我们可以做一个低头。 We call it that way because we are going down the type hierarchy.我们这样称呼它是因为我们要沿着类型层次结构向下移动。 We also call this a narrowing reference conversion .我们也称其为收缩引用转换 We could say我们可以说

Sedan s = (Sedan) v;

This always requires an explicit cast, because the compiler cannot be sure this is safe and that's why this is like asking the programmer, "are you sure of what you are doing?".这总是需要显式转换,因为编译器无法确定这是安全的,这就是为什么这就像问程序员,“你确定你在做什么吗?”。 If you lie to the compiler you will throw you a ClassCastException at run time, when this code is executed.如果你对编译器撒谎,你会在运行时抛出一个ClassCastException ,当这段代码被执行时。

Other Kinds of Subtyping Rules其他类型的子类型规则

There are other rules of subtyping in Java. Java 中还有其他子类型规则。 For instance, there is also a concept called numeric promotion that automatically coerce numbers in expressions.例如,还有一个称为数字提升的概念,它可以自动强制表达式中的数字。 Like in喜欢在

double d = 5 + 6.0;

In this case an expression composed of two different types, integer and double, upcasts/coerces the integer to a double before evaluating the expression, resulting in a double value.在这种情况下,由两种不同类型(整数和双精度)组成的表达式在计算表达式之前将整数向上转换/强制转换为双精度,从而产生双精度值。

You may also do primitive upcasting and downcasting.你也可以做原始的向上转换和向下转换。 As in

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

In these cases, an explicit cast is required when information can be lost.在这些情况下,当信息可能丢失时,需要显式转换。

Some subtyping rules may not be so evident, like in the cases of arrays.一些子类型规则可能不那么明显,就像在数组的情况下一样。 For instance, all reference arrays are subtypes of Object[] , but primitive arrays are not.例如,所有引用数组都是Object[]子类型,但原始数组不是。

And in the case of generics, particularly with the use of wildcards like super and extends , things get even more complicated.而在泛型的情况下,特别是使用superextends等通配符时,事情变得更加复杂。 Like in喜欢在

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

Where the type of b is a subtype of the type of a .其中的类型b是的类型的子类型a And the type of d is a subtype of the type of c . d的类型是c类型的子类型。

And also boxing and unboxing are subject to some casting rules (yet again this is also some form of coercion in my opinion).而且装箱和拆箱也受制于一些铸造规则(在我看来,这也是某种形式的强制)。

You got it right.你做对了。 You can successfully cast an object only to its class, some of its parent classes or to some interface it or its parents implement.您只能成功地将对象强制转换为它的类、它的一些父类或它或它的父类实现的某些接口。 If you casted it to some of the parent classes or interfaces, you can cast it back to the original type.如果将其强制转换为某些父类或接口,则可以将其强制转换回原始类型。

Otherwise (while you can have it in source), it will result in a runtime ClassCastException.否则(虽然您可以在源代码中使用它),它将导致运行时 ClassCastException。

Casting is typically used to make it possible to store different things (of the same interface or parent class, eg. all your cars) in the same field or a collection of the same type (eg. Vehicle), so that you can work with them the same way.转换通常用于将不同的东西(相同接口或父类,例如所有汽车)存储在同一字段或相同类型的集合(例如车辆)中,以便您可以使用他们以同样的方式。

If you then want to get the full access, you can cast them back (eg. Vehicle to Truck)如果您想获得完全访问权限,您可以将它们退回(例如,车辆到卡车)


In the example, I am pretty sure that the last statement is invalid and the comment is simply wrong.在这个例子中,我很确定最后一条语句是无效的,注释是完全错误的。

When you make a cast from a Truck object to a HeavyVehicle like that:当您像这样从 Truck 对象转换为 HeavyVehicle 时:

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

The object is still a truck, but you only have access to heavyVehicle methods and fields using the HeavyVehicle reference.该对象仍然是一辆卡车,但您只能使用 HeavyVehicle 引用访问 HeavyVehicle 方法和字段。 If you downcast to a truck again, you can use again all the truck methods and fields.如果您再次沮丧到卡车,您可以再次使用所有卡车方法和字段。

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

If the actual object you are downcasting is not a truck, a ClassCastException will be throw like in the following example:如果您向下转换的实际对象不是卡车,则将抛出 ClassCastException,如下例所示:

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

The exception is thrown because the actual object is not of the correct class, its an object of a superclass (HeavyVehicle)抛出异常是因为实际对象不属于正确的类,它是超类 (HeavyVehicle) 的对象

The last line of code compiles and runs successfully with no exceptions.最后一行代码编译并成功运行,没有异常。 What it does is perfectly legal.它的所作所为是完全合法的。

  1. hV initially refers to an object of type HeavyVehicle (let's call this object h1): hV 最初指的是一个 HeavyVehicle 类型的对象(我们称这个对象为 h1):

     static HeavyVehicle hV = new HeavyVehicle(); // hV now refers to h1.
  2. Later, we make hV refer to a different object, of type Truck (let's call this object t1):稍后,我们让 hV 指向一个不同的对象,类型为 Truck(我们称这个对象为 t1):

     hV = T; // hV now refers to t1.
  3. Lastly, we make T refer to t1.最后,我们让 T 指代 t1。

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

T already referred to t1, so this statement didn't change anything. T 已经引用了 t1,所以这个语句没有改变任何东西。

If hv was already assigned an instance of a HeavyVehicle class which is a super class of the Truck class, how can then this field be type cast into a more specific subclass called Truck which extends from the HeavyVehicle class? 如果已经为 hv 分配了一个 HeavyVehicle 类的实例,该类是 Truck 类的超类,那么如何将这个字段类型转换为一个更具体的子类,称为 Truck,它从 HeavyVehicle 类扩展而来?

By the time we reach the last line, hV no longer refers to an instance of HeavyVehicle.当我们到达最后一行时,hV 不再是指 HeavyVehicle 的实例。 It refers to an instance of Truck.它指的是 Truck 的一个实例。 Casting an instance of Truck to type Truck is no problem.将 Truck 的实例转换为 Truck 类型没有问题。

That means that the object can only be cast as a superclass or the same class as the class from which it was actually instantiated. 这意味着该对象只能被转换为超类或与实际实例化它的类相同的类。 Is this correct or am I wrong here? 这是正确的还是我错了?

Basically, yes, but don't confuse the object itself with a variable that refers to the object.基本上,是的,但不要将对象本身与引用该对象的变量混淆。 See below.见下文。

Does explicit casting somehow change the actual type of object (not just the declared type), so that this object is no longer an instance of HeavyVehicle class but now becomes an instance of Truck class? 显式转换是否以某种方式改变了对象的实际类型(不仅仅是声明的类型),以便该对象不再是 HeavyVehicle 类的实例,而是现在成为 Truck 类的实例?

No. An object, once created, can never change its type.不可以。对象一旦被创建,就永远不能改变它的类型。 It can't become an instance of another class.它不能成为另一个类的实例。

To reiterate, nothing changed on the last line.重申一下,最后一行没有任何变化。 T referred to t1 before that line and it refers to t1 afterward. T 在该行之前指代 t1,在之后指代 t1。

So why is the explicit cast (Truck) necessary on the last line?那么为什么最后一行需要显式转换 (Truck) 呢? We are basically helping just helping out the compiler.我们基本上只是帮助编译器。

We know that by that point, hV refers to an object of type Truck, so it's ok to assign that object of type Truck to the variable T. But the compiler isn't smart enough to know that.到那时,我们知道 hV 指的是 Truck 类型的对象,因此可以将 Truck 类型的对象分配给变量 T。但是编译器不够聪明,无法知道这一点。 The compiler wants our assurance that when it gets to that line and tries to make the assignment, it will find an instance of Truck waiting for it.编译器希望我们保证,当它到达该行并尝试进行分配时,它将找到一个 Truck 的实例等待它。

The above code will compile and run fine.上面的代码将编译并运行良好。 Now change above code and add following line System.out.println(T.name);现在更改上面的代码并添加以下行 System.out.println(T.name);

This will make sure that you are not using the object T after downcasting hV object as Truck.这将确保您在将 hV 对象向下转换为 Truck 后不使用对象 T。

Currently, in your code you are not using T after downcast so everything is fine and working.目前,在您的代码中,您在向下转型后没有使用 T,因此一切正常且工作正常。

This is because, by explicitly cast hV as Truck, complier does complain considering that programmer as casted the object and is aware of the what object is been casted to what.这是因为,通过将 hV 显式转换为 Truck,编译器确实会抱怨认为程序员已转换对象并且知道将什么对象转换为什么。

But at runtime JVM is not able to justify the casting and throws ClassCastException "HeavyVehicle cannot be cast to Truck".但是在运行时 JVM 无法证明转换的合理性并抛出 ClassCastException “HeavyVehicle cannot be cast to Truck”。

To help better illustrate some points made above, I modified the code in question and add more codes to it with inline comments (including actual outputs) as follows:为了更好地说明上面提出的一些观点,我修改了有问题的代码,并使用内联注释(包括实际输出)向其中添加了更多代码,如下所示:

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