简体   繁体   中英

How does this casting of an interface to a class work?

IY iy = new D();
C c1 = (C) iy; // What is going on here...
c1.doOther();  // and here?

public interface IX {
    void doIt();
}

public interface IY {
    void doOther();
}

public class A {
    public void doIt(double d) {
        System.out.println("Doit A " + d);
    }
}

public class B extends A implements IX {
    public void doIt() {
        System.out.println("Doit B");
    }

    public void doIt(int i) {
        System.out.println("Doit B " + i);
    }
}

public class C extends A implements IY {
    public void doIt() {
        System.out.println("Doit C");
    }

    public void doOther() {
        System.out.println("DoOther " + this.getClass().getSimpleName());
    }
}

public class D extends C {
    public void doIt() {
        System.out.println("Doit D");
    }
}

Prints out DoOther D


Why does this happen? I am learning about Javas type system and I am super confused.

What exactly is happening on each step? I will try and give my understanding of what is happening.

IY iy = new D() compiles and runs fine due to the fact that D inherits from C which inherits from IY . Due to the transitivity rule (D <: C <: IY => D <: IY) D is sub to IY and it works to store a D-object into a variable of type IY because D is a type of IY .

C c1 = (C) iy , here is where I get confused. To my understanding we are casting a D-object into a C-object . Or is it something else that happens? Am I maybe casting a variable of type IY (interface) to the type C ? The type changes, but the value of the variable (which is a reference to a D-object ) remains the same between iy and c1 ?

This works because C is super to D . Now this is a dangerous thing to do if I have understood this correctly, due to the fact that this will not always run because it is not guaranteed that C and D have a relationship, therefore it can fail during runtime and give the ClassCastException exception, is this correct?

But then the result of c1.doOther() confuses me. If c1 is a C-object and we do c1.doOther() , isn't the C-object c1 calling its own instance method doOther() ? If that is the case then the line this.getClass().getSimpleName() should return DoOther C but it returns DoOther D instead as if it was the D-object calling its super method, which seems to be the actual case?

What is really going on here?

casting a D-object into a C-object

This is not how I would describe it. The cast (C) checks the type of the object. If it passes, then the assignment is made. If not then an error is thrown.

No changes or other from-one-"into"-another is done. The cast only tests that the object is of the requested type. Once that object is found to be of the requested type, then operations (methods) relating to that type are allowed.

A corollary operation is the instanceOf keyword, which allows you the programmer to test a type without throwing an error.

if( iy instanceOf C ) {
   // do C stuff
}
else
   System.err.println( "I couldn't do it." );

If c1 is a C-object and we do c1.doOther(), isn't the C-object c1 calling its own instance method doOther()? If that is the case then the line this.getClass().getSimpleName() should return DoOther C

The type system is basically in two halves. There's the type you declare to the compiler (here IY and then you cast to C ), and then there's the actual real type of the object. Operations that are allowed (ie method calls) are based on the declared type.

However, the actual call is based on the type of the object itself, which in this case is always going to be a D regardless of the declared type. So you always get D 's interpretation of being an IY or a C type. It's always D that is being called here. Again, there is no changing based on casting or assignment. The type of D is "polymorphic" and can "look like" an IY or C , but it's always really a D .

TL;DR: Casting doesn't change the underlying instance's type, it just exposes it as a variable of a different type to all later code.


Analogy:

You have a box, and you put an accountant named Alice in it. You put a label on that box that says 'Accountant'.

You then put another label on it that says 'senior accountant'. This hasn't changed Alice in the box. Alice doesn't do accounting any different to any other senior accountant, so they don't override that behaviour.

When you ask Alice to doAccounting , since they haven't overridden that behaviour, the basic senior accountant workflow for doAccounting is run, but as part of that workflow there is a step that gets the accountants name to sign the document.

Would you be surprised that the resulting signature on the document said 'Alice'.


More detail:

IY iy = new D(); // (1)
C c1 = (C) iy; // (2)
c1.doOther();  // (3)

(1) You create an instance of type D , and store it as a variable iy . You give this variable the type IY , which means that anything using that variable will only see that is an instance of IY . This does not mean that it isn't a D anymore, it's just you're not exposing that to any later code. But the variable is just a 'pointer' to the newly created D instance.

(2) You cast the D instance you created in step (1) to type C , and store this in a new variable c1 . This is evaluated at runtime, and it succeeds as the iy variable being cast is of type D (as discussed above) and by your class definitions D extends C - ie all D s are C s (but not vice-versa). You declare the c1 variable as having type C , which is fine - but that does not change the fact that the underlying instance is still the D that you created above. Again, c1 is now a 'pointer' to the previous D instance. You've just exposed to later code that is at least of type C .

(3) You call an instance method doOther on your c1 variable. This is invoked on the underlying instance that c1 points to. As discussed above, this is still the same instance of type D that you created in step (1). So it looks at that instances doOther method. This then looks if type D has an override of doOther to call, but since it doesn't goes to the next one in the hierarchy which is from type C . As part of the method, getClass() is used. Much like the doOther call, this is invoked on your D instance, and that does have its own getClass() method (because all new classes will). So the getClass() from type D is called, and you see the result you see.

IY iy = new D();
C c1 = (C) iy;
c1.doOther();

As per the code you shared IY, C, A are parent of D. Whatever type you changing your D object, it is still D object only the reference are going to change, not the D object. When you call c1.doOther() it is calling with the D object, you are getting DoOther D.

The type only restrict what ever method you can call. If the reference is IY then you can only able to call doOther. If it point to C, then you can access everything C have. It will go like this...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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