Domanda

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.

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

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?

È stato utile?

Soluzione

Reference vs Object vs Types

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 (e.g. new Truck()). But any given object in Java is likely to have multiple types. Some of these types are obviously given by the class hierarchy, others are not so obvious (i.e. 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 vehicles. Therefore, this hierarchy of is-a relationships dictates that a truck has multiple compatible types.

When we create a Truck, we define a "reference" to get access to it. 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. 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. It is, and will always be a Truck.

Downcasting or Narrowing Reference Conversion

Now, having a reference of type Vechicle we cannot "safely" conclude that it actually references a Truck. After all it may also reference some other form of 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.

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.

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.

Other Kinds of Subtyping Rules

There are other rules of subtyping in 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.

And in the case of generics, particularly with the use of wildcards like super and extends, things get even more complicated. 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 a is a subtype of the type of b. And the type of c is a subtype of the type of d.

Using covariance, wherever List<? extends Number> appears you can pass a List<Integer>, therefore List<Integer> is a subtype of List<? extends Number>.

Contravariance produce a similar effect and wherever the type List<? super Number> appears, you could pass a List<Object>, which makes of List<Object> a subtype of List<? super Number>.

And also boxing and unboxing are subject to some casting rules (yet again this is also some form of coercion in my opinion).

Altri suggerimenti

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.

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 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. 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:

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)

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):

    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 = T; // hV now refers to t1.
    
  3. Lastly, we make T refer to t1.

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

T already referred to t1, so this statement didn't change anything.

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?

By the time we reach the last line, hV no longer refers to an instance of HeavyVehicle. It refers to an instance of Truck. Casting an instance of Truck to type Truck is no problem.

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?

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.

So why is the explicit cast (Truck) necessary on the last line? 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. 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.

The above code will compile and run fine. Now change above code and add following line System.out.println(T.name);

This will make sure that you are not using the object T after downcasting hV object as Truck.

Currently, in your code you are not using T after downcast so everything is fine and working.

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.

But at runtime JVM is not able to justify the casting and throws 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

        }

}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top