• Post Reply
  • Bookmark Topic Watch Topic
  • New Topic

Problem with output( Casting)

 
Rohan Deshmkh
Ranch Hand
Posts: 127
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
This is a question from enthuware's.



And the following declarations:
A a = new A();
B b = new B();
Identify options that will compile and run without error.


1. a = (B)(I)b;
2. b = (B)(I) a;
3. a = (I) b;
4. I i = (C) a;

I am not able to understand it's output.The correct answer is option 1.How? I assumed it is correct because b is an instance of B.No matter what's on the left side of = and not taking into consideration about(I) in the expression (B)(I)b

Option 2 is not correct beacuse a is not an instance of B.

Why is option 3 not correct.b is an instance of I(Indirectly).Look's like now my assumption of not looking on the left side of = is false and now it does matter to whom you are assigning the variable.
Option 4 is wrong because a is a A but A is not C.So we cannot cast this way.


Am i correct?
 
Jesper de Jong
Java Cowboy
Saloon Keeper
Posts: 15644
48
Android IntelliJ IDE Java Scala Spring
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Option 3 is wrong because you cannot assign any arbitrary I to a variable of type A.

Class A implements interface I, but there could be other classes, unrelated to A, that also implement I. If option 3 were possible, you could do:
 
harshvardhan ojha
Ranch Hand
Posts: 157
1
Android Java MySQL Database
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi Rohan, why you are assuming that left side of = doesn't matter? Although your other assumptions seems to be correct only look at option C, you need to make your understanding about how a subclass casted to interface cant be assigned to superclass. Again superclass is not subclass.
 
Tony Docherty
Bartender
Posts: 3054
59
  • Likes 3
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
1. a = (B)(I)b;

Type B is upcast to I - Legal as B extends A which implements I
Type I is downcast to B - Legal as the object in this case is of type B
Type B is assigned to variable of type A - Legal, you can always assigned an object to a variable which is a super type. ie it is always legal to upcast.

2. b = (B)(I) a;

Type A is upcast to I - Legal as A implements I
Type I is downcast to B - Illegal as the object in this case is of type A and can't be downcast to type B

3. a = (I) b;

Type B is upcast to I - Legal as B extends A which implements I
Type I is assigned to variable of type A - Illegal, you can't downcast unless you specify the type you are downcasting to.

4. I i = (C) a;

Type A is downcast to C - Illegal as the object in this case is of type a and can not be downcast to type C.

.Look's like now my assumption of not looking on the left side of = is false

Yes you are right in realising your assumption is wrong. See the answer in your other thread.
 
Rohan Deshmkh
Ranch Hand
Posts: 127
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Ok , i finally got it.
Thanks to everyone , especially Tony Docherty for nice and step by step explanation.
 
Chris Heinz
Greenhorn
Posts: 29
1
Eclipse IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi folks,
Sorry for bumping such an old thread, but I got this question wrong(and the topic of casting is still giving me some troubles) on an enthuware mock test and just had a couple follow-up questions. Any help is appreciated.

Tony Docherty wrote:
1. a = (B)(I)b;

1. Type B is upcast to I - Legal as B extends A which implements I
2. Type I is downcast to B - Legal as the object in this case is of type B
Type B is assigned to variable of type A - Legal, you can always assigned an object to a variable which is a super type. ie it is always legal to upcast.

^^^1. So, here when you say, 'Type B' you are referring to the fact that 'b' is of type B?
^^^2. Again, when you say 'the object in this case is of type B', you mean that the object, 'b', is of Type B?


Tony Docherty wrote:
2. b = (B)(I) a;

Type A is upcast to I - Legal as A implements I
3. Type I is downcast to B - Illegal as the object in this case is of type A and can't be downcast to type B

^^^3. This is a bit confusing. Illegal as the object is of type A. I understand that 'a' can't be cast to B because A is a base class of B, but, the object, 'a' of type A was casted to 'I' just before this, so I would think that if 'I' can be cast to 'B', then all is well. Obviously, I'm wrong, but I can't get this to 'click'.

When comparing (B)(I)b with (B)(I)a, my take away is that regardless of the interface casting, 'b' can be cast to B(duh), and 'a' can not be cast to B. So, even after casting a or b to I, you still must look at the relationship between b and B or a and B to determine if casting is valid?


Tony Docherty wrote:
3. a = (I) b;

Type B is upcast to I - Legal as B extends A which implements I
4. Type I is assigned to variable of type A - Illegal, you can't downcast unless you specify the type you are downcasting to.

^^^4. It is obvious to me why this is not a valid statement, but Tony's explanation is a bit confusing. The way I think of it is that using a class A reference variable to refer to an Interface object is invalid. To me, that is why this is not valid. The verbage Tony uses, "can't downcast unless you specify the type you are downcasting to" does not click with me. Is it possible somebody could elaborate on this point?

Thanks for any help. Sorry for some of the newb questions. I just started studying recently. Trying to move out of embedded software and into the sw application world. Thanks again!
Chris
 
Tony Docherty
Bartender
Posts: 3054
59
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Chris Heinz wrote:Hi folks,
Sorry for bumping such an old thread, but I got this question wrong(and the topic of casting is still giving me some troubles) on an enthuware mock test and just had a couple follow-up questions. Any help is appreciated.

Welcome to the Ranch.
Bumping the thread in this case is fine as your questions are about the content in the thread.

Chris Heinz wrote:
^^^1. So, here when you say, 'Type B' you are referring to the fact that 'b' is of type B?
^^^2. Again, when you say 'the object in this case is of type B', you mean that the object, 'b', is of Type B?

Yes. The object referenced by variable 'b' is of type 'B'

Chris Heinz wrote:
Tony Docherty wrote:
2. b = (B)(I) a;

Type A is upcast to I - Legal as A implements I
3. Type I is downcast to B - Illegal as the object in this case is of type A and can't be downcast to type B

^^^3. This is a bit confusing. Illegal as the object is of type A. I understand that 'a' can't be cast to B because A is a base class of B, but, the object, 'a' of type A was casted to 'I' just before this, so I would think that if 'I' can be cast to 'B', then all is well. Obviously, I'm wrong, but I can't get this to 'click'.

It doesn't matter what you legally cast an object to it is still the same object with the same type it's just how you are referring to it that changes. A reference of type 'I' can only be cast to type 'B' if the object is of type 'B' or a sub class of type 'B'

Chris Heinz wrote:
When comparing (B)(I)b with (B)(I)a, my take away is that regardless of the interface casting, 'b' can be cast to B(duh), and 'a' can not be cast to B. So, even after casting a or b to I, you still must look at the relationship between b and B or a and B to determine if casting is valid?

Yes

Chris Heinz wrote:
Tony Docherty wrote:
3. a = (I) b;

Type B is upcast to I - Legal as B extends A which implements I
4. Type I is assigned to variable of type A - Illegal, you can't downcast unless you specify the type you are downcasting to.

^^^4. It is obvious to me why this is not a valid statement, but Tony's explanation is a bit confusing. The way I think of it is that using a class A reference variable to refer to an Interface object is invalid. To me, that is why this is not valid. The verbage Tony uses, "can't downcast unless you specify the type you are downcasting to" does not click with me. Is it possible somebody could elaborate on this point?

The variable 'b' can be assigned to 'a' but once you have upcast it to type I you are trying to assign a reference of type I to a variable of type A which as you say is illegal. Therefore you need to explicitly downcast it to a type that can be assigned to 'a'. Does that make more sense?
 
Chris Heinz
Greenhorn
Posts: 29
1
Eclipse IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Tony Docherty wrote:
It doesn't matter what you legally cast an object to it is still the same object with the same type it's just how you are referring to it that changes. A reference of type 'I' can only be cast to type 'B' if the object is of type 'B' or a sub class of type 'B'

Ah, of course. That makes sense.

Tony Docherty wrote:
The variable 'b' can be assigned to 'a' but once you have upcast it to type I you are trying to assign a reference of type I to a variable of type A which as you say is illegal. Therefore you need to explicitly downcast it to a type that can be assigned to 'a'. Does that make more sense?

That cleared things up. Thanks for your help, Tony.
 
Aristoteles Philosophus
Greenhorn
Posts: 1
Eclipse IDE Java Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I'd like to summarize my understanding of this thread as it seems important to me (esp. Tony Docherty's explanations). It covers the Enthuware mock exam question enthuware.ocajp.i.v7.2.1328 in detail.
Moreover, I'd appreciate any comment if my view is correct or where I have some misunderstanding. Many thanks!

Premise: I want to differentiate between compiler error and runtime exception when casting
Reason: A cast and assignment has to pass a 2-steps validation process involving the Java compiler first and the JVM second. Either can signal that the cast is invalid by raising an error (compiler) or an exception (JVM).

1. Compiler check - right side of assignment:
On the right side of the assignment sign the compiler verifies that the reference data type of the object to be cast (i.e. source type) can theoretically be cast to the desired reference data type (target type). More concretely, the compiler checks if the data type of the reference variable to be cast (source type) has a direct hierarchical class relationship with the reference data type to be cast to (target type). This can be an upward relationship (potential upcast) as well as a downward relationship (potential downcast). The compiler will not raise an error if a ref. var. of a superclass type was cast to a ref. var. of its subclass type, because the superclass type ref. var. could theoretically point to an object of its subclass type. The compiler needs to consider this possibility because it validates the casts solely on the base of reference data types not on the actual object type! The compiler does NOT check whether this possibility holds true by inspecting the actual object - this is the JVM's task as we will see later.
Nevertheless, if the reference type check fails (e.g. because the casting involves totally unrelated classes), the compiler raises a compiler error. Take:

The compiler checks one casting step after the other:
Cast 1: Can a ref. var. 'b' of type B theoretically be addressed by a ref. var. of type I? Yes it can, as class B is an (indirect) implementer of interface I through class A, there is a direct hierarchical relationship between class B and interface I; We have a valid upcast in front of us - at least in terms of reference types. For simplicity, I omit the notion of non final classes that do not implement the interface used in a cast.
As a result of this cast the reference data type of 'b' has been set from B to I. I is used as source reference type for the second cast.

Cast 2: Can a ref. var. 'b' of type I theoretically be addressed by a ref. var. of type B? Yes it can, same explanation as above except that we have a downcast here. Behind a ref. var. of type I there can easily be an object of type B.

Result: The right side casts seem OK for the compiler as it only checks the reference types according to their class hierarchy. The compiler does NOT check if the actual object type referred to by 'b' would actually pass each casting step.

2. Compiler check - assignment of right side to left side:
For the assignment check the compiler performs a strict IS-A test based on the (last) reference types for each side: Is the reference type of the last cast on right side of the equal sign a (sub)type or implementer of the left side reference type? Take again:

As the right side expression passes the compiler test and its last cast results in a ref. var. 'b' of type B (having been cast from B to I and back to B) the compiler needs to verify, if ref. var. 'b' of type B can be assigned to a ref. var 'a' of type A. Again, the compiler performs a strict IS-A test based on the reference types to validate this assignment.

In our case the compiler checks this:
IS ref. var. 'b' of type B itself A class or a subclass of the target class referenced by var. 'a'; the interface check is irrelevant here as 'a' does not refer to an interface type which the compiler knows. The answer is 'yes'; since B is a subclass of A ref., var. 'b' can be assigned to ref. var. 'a'. Again, this check is solely performed on base of the reference types (not the object types behind the references).

Result: The assignment seems OK for the compiler because the right side expression passes the IS-A test of the left side expression in terms of reference variable types. Remember, the compiler does NOT check if the actual object type referred to by 'b' would pass this assignment step; e.g. the compiler does NOT check if the object referred to by 'b' is really an instance of class B. The actual object type of ref. var. 'a' is irrelevant as we neither cast this object nor otherwise use it.

Now that the compiler did not detect any errors, our assignment compiles without an error. Next, the JVM can start validating the casts and assignment at runtime. If the JVM succeeds as well, the code runs without compiler errors and without runtime exceptions. Otherwise the JVM raises a class cast exception.

3. JVM check - right side casts at runtime:

The JVM performs practical IS-A checks on the actual object type to be cast and the reference types used during the casts. In contrast, the compiler has performed rather theoretical tests solely on reference types. This means, the JVM verifies if the actual object type behind the reference variable can be cast to the target reference types (all right side operations) and be assigned to the type of the reference variable (assignment to the ref. var. on the left side). Take again:

Step 1: The JVM inspects the object type behind ref. var. 'b' which is B; it is the same as its reference type, cf. B b = new B(). B b determines the reference type; new B() determines the object type.
Step 2: Now the JVM practically checks if an object of type B can be cast to an interface type I performing an IS-A test; this is a valid upcast as class B implements interface I through inheritance from class A.
Step 3: Finally, the JVM checks if an object of type B can be addressed by a reference type of B (our second cast). Of course, this is possible (identical classes).

At step 3 the JVM differs significantly from the compiler!
Not only that the JVM looks at the object type during casting it also goes back to the original object type behind ref. var. 'b' to validate each cast instead of taking always the last cast reference type to validate the next cast. Otherwise the cast of an superclass type object (I replace the interface type for a superclass type here for simplicity) to a subclass type would have to fail as an object of a superclass type cannot be cast to a subclass. It simply would fail the IS-A test. How can we verify, if the JVM looks at the original object type instead of the previous cast type (like the compiler does)? Take:

I have only replaced the first cast to (I) by a cast to (A); the compiler says 'fine' as we have a simple upcast first followed by a simple downcast; the assignment passes the IS-A test.
What does the JVM say? Nothing, the code runs successfully, because the JVM validates that it is the object type B which is referred to by the ref. var. 'b' that gets cast to (B) - NOT the previously cast type (A)! Otherwise a class cast exception would be thrown by the JVM.
So the JVM performs a check on the cast of the object B referred to by ref. var. 'b' to (A) first, and performs another check on the cast of object type B referred to by ref. var. 'b' to (B). Finally, the JVM sees that an object of type B is assigned to a reference variable 'a' of type A which is fine. Now take:

In this example I changed the reference type of 'b' from B to A and the object type referred to by 'b' to A. Additionally, I changed the first cast from (A) to (I) just to separate an A object type from a cast to (B). We expect the compiler to complete without error as an A reference type is a valid upcast to (I) which A is an implementer of, then it is correctly downcast to its subclass' reference type (B); finally, a reference type of B is assigned to its superclass type A. Fine!
What does the JVM see? An object of type A (referred to by 'b' of ref. type A) is upcast to (I); fine so far. Then we have a cast to (B): We know that in Java once created an object cannot change its type; only the reference variable can change its reference type. Thus, the object type referred to by ref. var. 'b' will NOT change unless 'b' points to a different object. In our case it does not - 'b' continues pointing to an object of type A. Since the JVM performs cast checks based on the object type, the JVM now neglects the first cast when it comes to the second cast. Instead, it verifies if an object of type A can be cast to a reference type of (B). This will fail as an object of a superclass type cannot be cast to an object of a subclass type! The JVM will throw a class cast exception at runtime. This is exactly what happens, if the code is compiled and run. We get the message 'Exception in thread "main" java.lang.ClassCastException: A cannot be cast to B'. Since we have inserted the cast to (I) we know that the JVM went back to the original object type as we have expected. Otherwise, the message would have mentioned a cast from I to B (very unlikely: there are no interface objects type in Java) or something else.
This excursion proves that the JVM validates each casting step on base of the original object type.

In contrast, the compiler checks if an object referred to by a reference variable of a superclass type theoretically could be cast to a reference variable of a subclass type; this is theoretically possible, as a reference variable of a superclass type can theoretically point to an object of a subclass type. This makes polymorphism possible. Therefore, a ref. var. of a superclass type can easily be downcast to its own subclass type from a compiler's point of view - the compiler simply does not check. If the ref. var. of superclass type points to an object of superclass type instead, the compiler would NOT take notice. The compiler is a theoretical actor, whereas the JVM is a practical one!

In our example this third JVM step - in our original example not the excursion - succeeds as an object of type B can be cast to itself.

Result: The right side of the assignment will run without any exception.

4. JVM check - assignment at runtime:

Finally, the JVM validates that the object type referred to by ref. var. 'b' (on the right side of the assignment) is the same as the reference type of ref. var. 'a' (on the left side of the assignment) or a subclass of it. Basically, it performs a IS-A test between object type of 'b' and reference type of 'a'.

Result: As class B (which is referred to by 'b') is a subclass of A (which is referred to by 'a') this check succeeds. The assignment of an object of type B to a reference variable of (super)type A is valid. The JVM executes the code without exception.

5. Some examples:

Compiler: upcast from ref. type A to I is OK as an A object could be pointed to by an I ref. type; downcast from I to B is OK as B is an (indirect) implementer of I which could have been upcast before (as we did); assignment of a B reference type to a B reference type is OK; Result: No compiler error.
JVM: upcast of object type A to I is OK as A is-a I; downcast of object type A to B will fail as no superclass object type can be referred to by an subclass reference type; the assignment of an A object type to a b reference type would fail as well for the same reason; Result: Class Cast Exception at runtime.


Compiler: upcast from a ref. type A to I is OK as a (indirect) interface implementer can be upcast to its interface ref. type; assignment of ref. type I to ref. type A will fail as assignments must pass the IS-A test (I is not a subclass or implementer of A); Result: Compiler error.
JVM: regardless of the compiler error the JVM would let the assignment pass as it looks at the object type of 'b' which is B; since B is an A the assignment would be valid - but this is a rather hypothetical and therefore irrelevant question.


Compiler: downcast from ref. type A to subclass ref. type C is OK since ref. var. 'a' of type A could point to an object of subclass type C; assignment of C ref. type to I which C implements indirectly through B and A is OK; Result: No compilation error.
JVM: downcast of a A object type to a subtype ref. type fails (A is NOT a C); the assignment of an interface implementer type C to its interface would be OK - again a rather hypothetical question; Result: Class Cast Exception at runtime.


Compiler: upcast of ref. type B to implemented interface type I is OK but assignment of interface ref. type I to its implementer B fails (I is NOT a B): Result: Compiler error.
JVM: upcast would be OK; assignment would fail as an object of superclass type A cannot be assigned to a ref. var. of subclass type B (fails is-a test); rather hypothetical question.

Let's slightly modify the last example by inserting a final cast to B:

Compiler: upcast of ref. type B to implemented interface type I is OK, downcast from I to its implementer ref. type B is OK because an interface ref. type could point to an object of an implementer class; assignment of a subclass ref. type B to a superclass ref. type A is OK (B is a A); Result: No compiler error.
JVM: upcast of a A type object to the implemented interface type I would be OK; downcast of a superclass object type A to a subclass ref. type B would fail the IS-A test; assignment would be OK (B obj. type to B ref. type) but is hypothetical; Result: Class Cast Exception at runtime.


Compiler: upcast of ref. var. 'a' of ref. type B (!!!) to implemented interface type I is OK; downcast from implemented interface type I to implementer ref. type C (indirect implementer through inheritance from A via B) is OK as I ref. type could point to an implementer object of type C; upcast from subclass ref. type C to superclass ref. type B is OK, assignment of B ref. type to C ref. type fails as B is not a C (fails IS-A test); Result: Compiler error.
JVM: upcast of an B object type referred to by ref. var. 'a' to an implemented interface of type I is OK; downcast from an B object type to a ref. type fails IS-A test (B is NOT a C); the last cast of a B object to an B ref. type would be OK again (but hypothetical); assignment of superclass object type B to a subclass ref. type C would fail the IS-A test - again a rather hypothetical question; Result: Class Cast Exception at runtime if there was not a compiler error before.


Cowgirls and cowboys, this is my understanding and reasoning which I put up for discussion! Many thanks for your critics, guidance and feedback!
 
  • Post Reply
  • Bookmark Topic Watch Topic
  • New Topic