Win a copy of The Little Book of Impediments (e-book only) this week in the Agile and Other Processes forum!
  • Post Reply
  • Bookmark Topic Watch Topic
  • New Topic

ClassCastException - Mala Gupta, A twist in the tail 7.4

 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator


I understand that the cast is a promise to the compiler - and I'm usually good at spotting ClassCastExceptions, but this threw me for some reason. Probably because it looks like the compiler should be able to spot that b is a reference to a BlackInk object.

On second glance I suppose the compiler can't catch it because it's still possible for a subclass of BlackInk to implement the interface? (with the compiler not knowing what b actually is at compile time). Right?

I was just so confident in my wrong answer (that it wouldn't compile). I've got my exam in two weeks exactly.......


Nick
 
Roel De Nijs
Sheriff
Posts: 10662
144
AngularJS Chrome Eclipse IDE Hibernate Java jQuery MySQL Database Spring Tomcat Server
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
nick woodward wrote:On second glance I suppose the compiler can't catch it because it's still possible for a subclass of BlackInk to implement the interface? (with the compiler not knowing what b actually is at compile time). Right?

You are absolutely spot-on! Mark the BlackInk class final and see what happens...
 
Roel De Nijs
Sheriff
Posts: 10662
144
AngularJS Chrome Eclipse IDE Hibernate Java jQuery MySQL Database Spring Tomcat Server
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
nick woodward wrote:I was just so confident in my wrong answer (that it wouldn't compile).

So definitely time to refresh your knowledge on this topic. I recommend having a look at this topic and this one (about exactly the same code snippet), this thread (also similar code snippet and an additional one) and this thread (about casting or using instanceof operator on a class vs an interface).

Hope it helps!
Kind regards,
Roel
 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
ah yes, the compiler does then kick up a stink!

but I'm surprised that marking the actual reference itself (final BlackInk b) as final doesn't do the same.....

cheers for the quick reply!


*edit - thanks for the links!

Yeah, this week is all about 'refreshes' - each day has a topic, today being exceptions.

then next week I'll do an exam every day. then (hopefully) sit the real thing in two weeks.....

I'll try not to be too much of a pain Roel! Any help is really appreciated. Really don't want to reschedule this thing!

 
Roel De Nijs
Sheriff
Posts: 10662
144
AngularJS Chrome Eclipse IDE Hibernate Java jQuery MySQL Database Spring Tomcat Server
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
nick woodward wrote:but I'm surprised that marking the actual reference itself (final BlackInk b) as final doesn't do the same.....

If you would take a bit of time and think about it for 10 seconds or maybe 30 (or even 5 minutes), you'll know yourself why this doesn't do the same. So I'm not gonna provide the solution for you. It's up to you to solve your own question. And if you carefully have re-read all topics I mentioned, I'm pretty sure you'll succeed in this task. And to raise the stakes (and the pressure*) a bit: I'll award you a cow if you can correctly explain your own question




(*) Raising the pressure is no issue as pressure makes diamonds
 
nick woodward
Ranch Hand
Posts: 370
11
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Sorry for the late response.....

I read those threads, and while they did help me, they completely ruined my understanding of casting!



This was clear to me. I'm making a promise to the compiler that there is a type B object to be assigned at runtime. The compiler has to take my word for it because it doesn't execute code. I assumed that it doesn't look at the RHS of the assignment at all (because for example it could be b = (B) getUnknownObject()).

Those three threads seemed to outline the following:

That if there are two classes that are unrelated casting results in a compile time error:



The same seems to be true with the instanceof operator. if there is no relationship between the two the compiler will step in.

However, if one is a class, and the other an interface, this allows for a subclass to implement the interface, so the compiler has to allow the casting (like the initial example/question about printable). This then results in a ClassCastException. With the exception being if the class is final

What has confused me though is why the compiler can see that this:



is not acceptable. but this:



is ok. The compiler must be looking at the RHS in the first example to know that 'new A();' has no relationship to the cast B, but it somehow can't see that in the second example that no form of polymorphism is possible. If it was B b = (B) variableOfTypeA; I'd understand. The variable of type A could refer to an object of type B. But the argument that the compiler 'doesn't execute code' doesn't make sense to me here, because it is checking the RHS in former, but not in the latter.

I actually threw my toys out of the pram last night I got myself so confused!!! Having said that, I'm definitely pushing my exam back by a week! (annoying, I have to go to the neighbouring city, ours doesn't have another slot until the 1st of march!!!)

________________________

As for the final BlackInk b = new BlackInk(); not doing the same as making the class final - I think this is an extention of my (same) confusion! I assume your answer would be that the compiler doesn't look at the RHS... so doesn't know if b is a subclass (and final in this context doesn't prevent subclassing, just reassignment an object to this variable) - but with it being final and initialised on that line, why can't the compiler see? it seems similar to a compile time constant.

Clearly I need to up my work level....... Depressing!!

Thanks Roel,

Nick
 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I'm going to go back over the threads again - but I think I'm simply missing a small detail on what the compiler does and doesn't check.
 
Steffe Wilson
Ranch Hand
Posts: 165
12
  • Likes 4
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I have struggled with this stuff too Nick so commiserations! In case my understanding helps shed any light, this is how I see it:

At compile time the compiler cannot know an actual object type (because it hasn't been instantiated at compile time) so the best that the compiler can do to flag up any potential casting issues is to reject casts to unrelated classes. The compiler's logic is that if they are not related class types then there is no feasible way for a class cast to work at runtime, no matter what the actual object type turns out to be.

If the two classes are related however then the compiler has to let it through because at compile time it doesn't know what the actual object type is going to be. So its left to the runtime system to flag any mismatch (via ClassCastException).

Recall that classes A and B are related if A ISA B or if B ISA A. Remember also ISA relationships can be indirect, eg A ISA B if A ISA IntermediateClass and IntermediateClass ISA B. So any two classes in a vertical class hierarchy line are considered related.

Specifically in your second example

it does seem absurd that the compiler doesn't appear to recognise that new A() yields an object of class A, which cannot be cast to B, but I would suspect that the compiler writers wanted to be consistent and follow the same rules and have the same runtime error rather than introduce a special case. So its treating the last line as it would if it was this:

That's my take anyway, lets wait for the meastro.

 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks Steffe - I like that way of looking at it!

I suppose if you look at the code 'new A();' as the constructor that it is - or rather code that returns rather than guarantees an object of A, then the consistancy is there.

 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
line 1 - Compiler error. Dog and Table are two unrelated classes.
line 2 - Compiler error. Strokable IS-NOT-A Dog (Dog IS-A Strokable)
line 3 - Compiler error. Table and Dog are two unrelated classes
line 5 - ClassCastException. new Animal() *could* be a Cat. But it isn't at run time.
line 6 - as with line 5.
line 10 - ClassCastException. Cats aren't strokable - but the compiler could be a cat person and believe that maybe a subclass of cat was.
line 11 - ClassCastException. Animals aren't either - but again the return type 'Animal' could be a strokable subclass. It isn't.
line 12 - Demonstrating that making the class final tells the compiler that this cannot be a subclass, and therefore cannot be strokable.


so basically I'm trying to generalise the rules in my head of what is and isn't allowed:

the reference type: does the cast type fulfil the IS-A requirement?
yes: does the type to be assigned have a relationship (sub or super) with the cast type? = COMPILER OK

if the type to be assigned DOESN'T have a relationship with the cast type, is the cast type an interface and the class not final? = COMPILER OK

does the type to be assigned ACTUALLY implement the interface? JVM happy. if not, ClassCastException.

That's as close as I can get. It's still not totally straight in my head, but a LOT better! Hopefully some of my ramblings might help someone else - at least if they aren't completely littered with mistakes!

I'm going to go read those 3 threads again with my new understanding...!

Nick
 
Steffe Wilson
Ranch Hand
Posts: 165
12
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Its minefield. lol. I agree with most of your embedded comments, but here are a few clarifications:

Line 6 - note that the cast was permitted before the compiler got as far as failing the assignment, and for the reason you stated. (see below re casting to Interfaces)

Line 8 - I think your comment is correct but the comment is slightly ambiguous so for clarity: the problem raised by the compiler here is with the cast not the assignment. On RHS Dog and Table aren't related so the cast is flagged as an error by the compiler.

Line 13 - the runtime exception here is because you cannot cast an Animal to a Cat because an Animal is not a Cat (however a Cat ISA Animal so the reverse cast would be ok at runtime, that is if you had a getCat() method)

Line 19 - the compiler rule for casting to Interfaces is different to casting to classes (oh joy... ). The compiler allows any object to be cast to any non-final Interface. Strokeable is non-final so compiler allows this cast. But it fails later because at runtime an object cast to an Interface has to be an instanceof that Interface and here Cat is not an instanceof Strokeable.

Line 22 - same rationale as Line 13 above.

Edit: fixed my line nos to match code section in your previous post.
 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
yep, I'm still doing examples. i'm fairly sure i've got it now. you're right, some of those explanations weren't great! they were at the time... i promise!
Steffe Wilson wrote:
Line 6 - I think your comment is correct but the comment is slightly ambiguous so for clarity: the problem raised by the compiler here is with the cast not the assignment. On RHS Dog and Table aren't related so the cast is flagged as an error by the compiler.

are you sure?

I thought that the compiler has a problem because a Strokable object cannot be assigned to a Dog object.

** ah, it seems we have different numbers appearing on our screens. strange. thanks for answering that - it makes much more sense that the problem is with the (Dog)new Table();


**edit: a few more I've been doing. minus long rambling:


 
Steffe Wilson
Ranch Hand
Posts: 165
12
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yeah sorry about line number confusion, I copy pasted your code to my editor and it concat'd some lines and threw the line nos off.

re your last batch, yep agreed plus:

line 7 - compiler error is on the assignment rather than the cast (C ISA A so cast is ok)
line 11 - class cast exception because A is not instanceof I

 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
RE the PM about the inline post comments: I cannot edit that post anymore, but typed the explanations with line numbers if someone wants to merge them:


** comments edited - it's actually really difficult to add 'lineX' style comments from the small editing window (they also won't match Steffe's response as I'm redoing them):

line 1 - Compiler error. Dog and Table are two unrelated classes.
line 2 - Compiler error. Strokable IS-NOT-A Dog (Dog IS-A Strokable)
line 3 - Compiler error. Table and Dog are two unrelated classes
line 5 - ClassCastException. new Animal() *could* be a Cat. But it isn't at run time.
line 6 - as with line 5.
line 10 - ClassCastException. Cats aren't strokable - but the compiler could be a cat person and believe that maybe a subclass of cat was.
line 11 - ClassCastException. Animals aren't either - but again the return type 'Animal' could be a strokable subclass. It isn't.
line12 - Demonstrating that making the class final tells the compiler that this cannot be a subclass, and therefore cannot be strokable.
 
Roel De Nijs
Sheriff
Posts: 10662
144
AngularJS Chrome Eclipse IDE Hibernate Java jQuery MySQL Database Spring Tomcat Server
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Steffe Wilson wrote:That's my take anyway, lets wait for the meastro.

I assume that's me But your explanation is absolutely spot-on! So have a cow for such an excellent post.

I am sometimes really wondered why people have such confusion about such a simple rule: the compiler does never execute any code. And it's a very easy rule to apply consistently so it will definitely be a very helpful one (on the mock/actual exams).

So let's have a look at the first code snippetThe compiler is not interested in which object is created here, the compiler only wants to know the type of the expression new A(). That's really the only thing the compiler cares about. Why? Because that's the only thing the compiler needs to determine if this cast is a valid one. So from the compiler's point of view, the line B b = (B)new A(); is equivalent withSo the compiler knows that new A() will evaluate to class A, and because classes A and B are incompatible types, the compiler gives a compiler error.

And with the second code snippet, the compiler does exactly the sameThe compiler knows new A() will evaluate to class A, and because classes A and B belong to the same class hierarchy, the compiler is happy with the cast and won't give a compiler error. (But you'll get a ClassCastException at runtime)

And I agree with you that it's easy to spot that new A() will never be a B object and thus the cast will fail (at runtime). So to answer your question: why does the compiler does not flag this one as a compiler error? You'll probably know the answer: because the compiler does never execute any code Let's say for a few seconds, the compiler would execute code. Let's examine the consequences of this change using the following code statementsSo to have consistent behavior, all 3 lines in the main method should be flagged as a compiler error. Because they will always create an A instance. I think we can agree that the analysis of this code will take much longer to evaluate and verify, both for the compiler and developers, than the current (static) code analysis. So instead of compiling your classes (application) in a few (milli)seconds, it would take several minutes. And the compiler has to flag all 3 lines as a compiler error; otherwise compilation would not be consistent and it would be even harder to tell/explain when a statement will be flagged as a compiler error (or not).
That's why I like this simple and easy rule so much! The compiler does never execute any code! I don't think it can get any easier than this


Now let's see if you have understood all my ramblings. And I noticed you have already practiced a lot, so this should be a walk in the park for you What's the result of this code snippet?

Hope it helps!
Kind regards,
Roel
 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Steffe Wilson wrote:Yeah sorry about line number confusion, I copy pasted your code to my editor and it concat'd some lines and threw the line nos off.

re your last batch, yep agreed plus:

line 7 - compiler error is on the assignment rather than the cast (C ISA A so cast is ok)
line 11 - class cast exception because A is not instanceof I





line 7 - isn't that what I wrote? yeah the problem is that A isn't a C so can't be assigned.
line 11 - agreed!

thanks Steffe. Much appreciated!
 
Roel De Nijs
Sheriff
Posts: 10662
144
AngularJS Chrome Eclipse IDE Hibernate Java jQuery MySQL Database Spring Tomcat Server
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
nick woodward wrote:RE the PM about the inline post comments: I cannot edit that post anymore, but typed the explanations with line numbers if someone wants to merge them:

I have added them for you
 
Roel De Nijs
Sheriff
Posts: 10662
144
AngularJS Chrome Eclipse IDE Hibernate Java jQuery MySQL Database Spring Tomcat Server
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
nick woodward wrote:As for the final BlackInk b = new BlackInk(); not doing the same as making the class final - I think this is an extention of my (same) confusion! I assume your answer would be that the compiler doesn't look at the RHS... so doesn't know if b is a subclass (and final in this context doesn't prevent subclassing, just reassignment an object to this variable) - but with it being final and initialised on that line, why can't the compiler see? it seems similar to a compile time constant.

You are spot-on! So you definitely have earned your cow

Applying final to a reference variable only prevents reassigning another object to this reference variable. But you can still subclass the BlackInk class and create another class which does implement the Printable interface. And such an instance can be assigned to a reference variable of type BlackInk. And this object can be cast to the Printable interface without any issues (no compiler error and no runtime exception).

One of my previous posts already explains in great detail that the compiler does never execute any code. And also explains why that's definitely a very good idea. But I can add another very important rule: a reference variable can never be a compile time constant! Meaning that even reference variables to primitive wrapper classes are not compile-time constants. There is one exception to this rule: String reference variables (if they refer to a String literal). If you want an excellent explanation about compile-time constants, check out this topic.
So although the syntax seems similar to a compile-time constant, an object (a reference variable) will never be considered to be a compile-time constant. And the reason is again the same: the new operator has to be invoked and therefore code needs to run and a compile-time constant is known at compile time. That's why in the following code snippet s1 is considered a compile-time constant and s2 is not.

Hope it helps!
Kind regards,
Roel
 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Roel De Nijs wrote:
Steffe Wilson wrote:That's my take anyway, lets wait for the meastro.

I assume that's me But your explanation is absolutely spot-on! So have a cow for such an excellent post.

i won't tell him if you don't Steffe! :P


So let's have a look at the first code snippetThe compiler is not interested in which object is created here, the compiler only wants to know the type of the expression new A(). That's really the only thing the compiler cares about. Why? Because that's the only thing the compiler needs to determine if this cast is a valid one. So from the compiler's point of view, the line B b = (B)new A(); is equivalent withSo the compiler knows that new A() will evaluate to class A, and because classes A and B are incompatible types, the compiler gives a compiler error.

And with the second code snippet, the compiler does exactly the sameThe compiler knows new A() will evaluate to class A, and because classes A and B belong to the same class hierarchy, the compiler is happy with the cast and won't give a compiler error. (But you'll get a ClassCastException at runtime)

And I agree with you that it's easy to spot that new A() will never be a B object and thus the cast will fail (at runtime). So to answer your question: why does the compiler does not flag this one as a compiler error? You'll probably know the answer: because the compiler does never execute any code Let's say for a few seconds, the compiler would execute code. Let's examine the consequences of this change using the following code statementsSo to have consistent behavior, all 3 lines in the main method should be flagged as a compiler error. Because they will always create an A instance. I think we can agree that the analysis of this code will take much longer to evaluate and verify, both for the compiler and developers, than the current (static) code analysis. So instead of compiling your classes (application) in a few (milli)seconds, it would take several minutes. And the compiler has to flag all 3 lines as a compiler error; otherwise compilation would not be consistent and it would be even harder to tell/explain when a statement will be flagged as a compiler error (or not).
That's why I like this simple and easy rule so much! The compiler does never execute any code! I don't think it can get any easier than this

agreed!


Now let's see if you have understood all my ramblings. And I noticed you have already practiced a lot, so this should be a walk in the park for you What's the result of this code snippet?

hmmmm....

right. the assignment B b1 = (B) is fine.
I want to say that (B)(Object) create(); is correct because I'm promising the compiler that:
create is of type A and A IS-AN Object. The question is, am I'm promising the compiler that Object is of type B, or does it fail the IS-A test.

I'm going with it compiles fine because Object and B are related, so the compiler should take my word for it. But it's a ClassCastException because we are lying to the compiler. A is an object, but isn't a B.


Hope it helps!
Kind regards,
Roel


depends if I've got that question right!

No honestly, thanks a lot. And you too Steffe. Even if I forget half this thread, I won't forget the idea that new A() should be viewed in terms of its type not a guaranteed object.
 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Roel De Nijs wrote:
final String s1 = "nick";
final String s2 = new String("roel");

switch (args[0]) {
case s1:
case s2: // compiler error
}
}[/code]



nice! I would've fallen for that hook line and sinker in the exam.

that's going on an index card!

*edit: would you mind another example like that previous one please? i found it more difficult than i would like - probably because I didn't choose it myself!

......that and i've had a beer. danish i'm afraid!
 
Roel De Nijs
Sheriff
Posts: 10662
144
AngularJS Chrome Eclipse IDE Hibernate Java jQuery MySQL Database Spring Tomcat Server
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
nick woodward wrote:right. the assignment B b1 = (B) is fine.
I want to say that (B)(Object) create(); is correct because I'm promising the compiler that:
create is of type A and A IS-AN Object. The question is, am I'm promising the compiler that Object is of type B, or does it fail the IS-A test.

I'm going with it compiles fine because Object and B are related, so the compiler should take my word for it. But it's a ClassCastException because we are lying to the compiler. A is an object, but isn't a B.

Absolutely spot-on!

The compiler sees something likeSo from the compiler's point of view, all casts are valid, because all casts happen between compatible types: A IS-A Object and B IS-A Object. But at runtime, a ClassCastException will be thrown, because the object returned by the create method IS-NOT-A B.
 
Roel De Nijs
Sheriff
Posts: 10662
144
AngularJS Chrome Eclipse IDE Hibernate Java jQuery MySQL Database Spring Tomcat Server
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
nick woodward wrote:nice! I would've fallen for that hook line and sinker in the exam.

that's going on an index card!

Maybe I should start writing a book and ask big bucks for it, instead of giving all this awesome hints and tips for free Or maybe I could ask Oracle if I could write some of the exam questions. That sounds even more fun and less work
 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
am definitely going to learn how to write it out that way, just in case i get muddled in the exam!
 
nick woodward
Ranch Hand
Posts: 370
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Roel De Nijs wrote:
nick woodward wrote:nice! I would've fallen for that hook line and sinker in the exam.

that's going on an index card!

Maybe I should start writing a book and ask big bucks for it, instead of giving all this awesome hints and tips for free Or maybe I could ask Oracle if I could write some of the exam questions. That sounds even more fun and less work


you laugh, but i mentioned to you before (didnt i?) that you should start keeping a list of your 'best of' answers. at worst you'd have a great reference to help on here (saving you time) and at best - honestly you'd have enough for a book or some sort of mini enthuware. i'd buy it.

but you should probably do all this after I've passed my oracle exams.... all of them.
 
Roel De Nijs
Sheriff
Posts: 10662
144
AngularJS Chrome Eclipse IDE Hibernate Java jQuery MySQL Database Spring Tomcat Server
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
nick woodward wrote:you laugh, but i mentioned to you before (didnt i?) that you should start keeping a list of your 'best of' answers. at worst you'd have a great reference to help on here (saving you time) and at best - honestly you'd have enough for a book or some sort of mini enthuware. i'd buy it.

Yeah, you definitely mentioned it! And that's exactly the reason why I mentioned it
 
Steffe Wilson
Ranch Hand
Posts: 165
12
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
nick woodward wrote:
Steffe Wilson wrote:line 7 - compiler error is on the assignment rather than the cast (C ISA A so cast is ok)

line 7 - isn't that what I wrote? yeah the problem is that A isn't a C so can't be assigned.

Nick, yes indeed, I was just clarifying that the compiler error was flagged against the assignment rather than the cast, because the original code comment:
could have been interpreted either way; that the cast was flagged or the assignment was flagged.


And Roel, thanks for the helpful clarifications above (and for the cow!).


 
  • Post Reply
  • Bookmark Topic Watch Topic
  • New Topic