• Post Reply Bookmark Topic Watch Topic
  • New Topic

Why Does Downcasting New Object Compile If It Won't Run?  RSS feed

 
Stevens Miller
Bartender
Posts: 1445
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
As I understand it, downcasting is allowed by the compiler when there is a possibility that it can succeed. Thus, I understand why the first two downcasts are allowed in this code:
The downcast at Line 7 is allowed because it is possible that the Beast returned by manufactureBeast might be a Lion (which it is, which is why it succeeds at run-time and why Line 9 also succeeds at run-time).

The downcast at Line 11 is allowed for the same reason: the Beast it returns might be a Lion. But it's not so, at run-time, the attempt to downcast a Kitten to a Lion causes a ClassCastException at Line 11 (so Line 13 is never even reached).

But I don't see any way that Line 15 can be assigning a Lion to lion, yet the compiler allows it (notwithstanding that it also throws a ClassCastException if you comment out Line 11).

So, why does downcasting a new Object compile if it won't run?

 
Liutauras Vilda
Sheriff
Posts: 4928
334
BSD
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:So, why does downcasting a new Object compile if it won't run?

Because compiler never runs the code, so never creates the objects, it checks if two reference types are compatible, that is it and in fact they are, as Lion extends the Beast.

Following example would not fail as you only shuffling with reference types while actual object behind the hood is right one:

[edit] changed line 4 from lion -> lion1 as I had already lion on line 1
 
Stevens Miller
Bartender
Posts: 1445
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Liutauras Vilda wrote:
Stevens Miller wrote:So, why does downcasting a new Object compile if it won't run?

Because compiler never runs the code, so never creates the objects, it checks if two reference types are compatible, that is it and in fact they are, as Lion extends the Beast.

I guess that must be it. The fact that new Beast() can't ever be of type Lion must not be what the compiler is testing. Rather, it is looking at the type of new Beast(), which is Beast, and correctly (if, perhaps, naively) concluding that anything of type Beast might also (even if not necessarily) be of type Lion, and thus allows the assignment.

Is that it?
 
Junilu Lacar
Sheriff
Posts: 11494
180
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
If I remember correctly, there's an isAssignableFrom() method somewhere in the Class class. That might have something to do with this behavior.
 
Liutauras Vilda
Sheriff
Posts: 4928
334
BSD
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Just found a thread, and there you'll find Roel's posts. I'm sure he explained there well more in details, but the idea after a quick look I see is the same what I wrote.
https://coderanch.com/t/662619/certification/Inheritance-Practice-exams-Objective-Inheritance
 
Liutauras Vilda
Sheriff
Posts: 4928
334
BSD
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Roel De Nijs wrote:Secondly here is one very, very, very important rule you should remember: the compiler doesn't execute any code at all! So the compiler doesn't know (and doesn't care) the type of the object to which the reference variable car is referring to. The only thing the compiler knows is the type of the reference variable car (which is Car).

That is it I think.
 
Stevens Miller
Bartender
Posts: 1445
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Liutauras Vilda wrote:
Roel De Nijs wrote:The only thing the compiler knows is the type of the reference variable car...

That is it I think.

That makes perfect sense with there is a reference variable. Any variable might hold a reference to an object of the variable's own type, or to an object of any descendant of its type. That's how the "possibility" of success is detected. A superclass variable might hold a reference to an object of the type to which it is being cast, if that type is a subtype of the variable's type.

Here, there is no variable. There is only the reference to the new Beast object, with no possibility that it refers to any subtype of Beast. But, of course, that's only because Beast is a constructor, not a method of type Beast. I am guessing that the compiler, for "possibility" testing, makes no such distinction. That is, just as any method of type Beast might return a reference to a subtype of Beast (and, therefore, passes the possibility test), the compiler seems to be treating a call to a constructor as also a call to a routine of type Beast. The fact that constructors can never return references to subclasses of the class that they construct isn't being considered. I'm just surprised by this, as the compiler wouldn't have to run any code to make that determination. Rather, all it has to do is determine that the reference being cast was returned from a constructor. Downcasting a reference from a constructor will never succeed, as the constructor can't return a reference to any of its subclasses, regardless of what code is in the constructor (meaning the compiler doesn't need to run anything to make this determination).

Or, can anyone think of a situation where downcasting the reference returned from a constructor to a class that is a subclass of the constructor's class would not cause a ClassCastException? I can't.
 
Liutauras Vilda
Sheriff
Posts: 4928
334
BSD
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:There is only the reference to the new Beast object
There is no new and there is no object at a given time. Objects being alocated to memory location by the JVM at run-time.

Your previuos code basically is equal to:
And again we come back to where we been before:
Compiler doesn't execute code, it checks only if references (not objects) are related and indeed they are, Lion extends Beast (deja vu), so they are from the same hierarchy. During the run-time it becomes clear that Beast isn't a Lion, so it throws an exception.
 
Liutauras Vilda
Sheriff
Posts: 4928
334
BSD
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I was trying to read the JLS related part about it, but that was too difficult for me, but wasn't a surprise, as Campbell keeps telling that it could be a very difficult to read it
 
Anton Golovin
Ranch Hand
Posts: 531
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:As I understand it, downcasting is allowed by the compiler when there is a possibility that it can succeed. Thus, I understand why the first two downcasts are allowed in this code:
The downcast at Line 7 is allowed because it is possible that the Beast returned by manufactureBeast might be a Lion (which it is, which is why it succeeds at run-time and why Line 9 also succeeds at run-time).

The downcast at Line 11 is allowed for the same reason: the Beast it returns might be a Lion. But it's not so, at run-time, the attempt to downcast a Kitten to a Lion causes a ClassCastException at Line 11 (so Line 13 is never even reached).

But I don't see any way that Line 15 can be assigning a Lion to lion, yet the compiler allows it (notwithstanding that it also throws a ClassCastException if you comment out Line 11).

So, why does downcasting a new Object compile if it won't run?



Hi, Stevens,

ClassCastException is almost a thing of the past if generics are used prudently. However, if not, you should always check by using the instanceof operator whether a particular instance is of a particular class, or surround the code in question with the try-catch construct and catch that exception.

With best regards,

Anton.
 
Stevens Miller
Bartender
Posts: 1445
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Liutauras Vilda wrote:Your previuos code basically is equal to:
It's almost equal, but not quite. Here's the bytecode for a direct assignment from a constructor, downcasting it to a subclass:
 0: new           #2                  // class viewbytecode/Main
 3: dup           
 4: invokespecial #3                  // Method "":()V
 7: checkcast     #4                  // class viewbytecode/Minor
10: astore_1
And here's the bytecode for assigment first to a variable, then assignment from the variable, downcasting it to a subclass:
 0: new           #2                  // class viewbytecode/Main
 3: dup           
 4: invokespecial #3                  // Method "":()V
 7: astore_1      
 8: aload_1       
 9: checkcast     #4                  // class viewbytecode/Minor
12: astore_2
Now I am not adept at reading JVM bytecode, but it seems clear that the instructions at 7 and 8 in the second listing are storing and loading the value in an intermediate variable, whereas in the first listing those steps are absent. I believe the JVM does its loading and storing from a stack, so after the load at 8 in the second listing, the stack probably looks the same after 3 in both listings. The subsequent checkcast at 7 (in the first listing) and 9 (in the second listing) is most likely the source of the ClassCastException in both versions and, as you point out, reacts the same way in each. But my points still seems valid: the compiler knows it is assigning directly from a new construction, with no intermediate variable between the result of the call to the constructor and the assignment. However, this seems consistent with what we've both been saying: the compiler inserts a checkcast before the assignment, but doesn't execute that instruction itself. That's left to run-time, since the value on the stack being checked can only be checked then. There is no "stack" at compile time to hold anything to check. Regardless, even if the compiler isn't going to detect this, I'm a little surprised that my NetBeans IDE doesn't flag it. As an example, this code: provokes a, "Thread.sleep called in loop" warning from NetBeans. That's hardly fatal and (at least where I am) a pretty common construct. On the other hand, though, this: is always fatal. I'm just surprised that neither javac nor NetBeans utters a peep of complaint about it.
 
Paul Clapham
Sheriff
Posts: 22841
43
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Really, it's not worth the time for the compiler writers to make a special case for the construct we're discussing here. They certainly could do that, you're correct in pointing that out, but why bother? Almost nobody would ever write such code and if they did it would fail reliably.

And then that opens the floodgates for other people to request compiler errors for other things which appear to be trivial to flag, if the compiler could only be persuaded to evaluate an expression a little bit. Isn't it easier to just say "The compiler doesn't attempt to evaluate any expressions in source code" rather than deal with deciding which expressions it should evaluate?
 
Anton Golovin
Ranch Hand
Posts: 531
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi, Stevens,

Should cause an error along the lines of "Downcasting a just instantiated superclass."

With best regards,

Anton.
 
Stevens Miller
Bartender
Posts: 1445
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Paul Clapham wrote:Really, it's not worth the time for the compiler writers to make a special case for the construct we're discussing here.
And then that opens the floodgates for other people to request compiler errors for other things which appear to be trivial to flag, if the compiler could only be persuaded to evaluate an expression a little bit.

Yeah, I suppose you're right. If this kind of hand-holding belongs anywhere, it belongs in the IDE, not the compiler.

Although, even that baffles me. I typed this code into the NetBeans editor:


At Line 8, it gives me an, "empty statement after if" warning, but no warning about dividing by zero at either Line 3 or Line 10. What's baffling is that the behavior doesn't change when I alter the "hints" settings for these warnings. Both are turned off by default in NetBeans. Yet I see one and not the other. If I turn them both on, I still see the warning at Line 8 but nothing at Line 3 or Line 10.

Odd.
 
Campbell Ritchie
Marshal
Posts: 56593
172
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Liutauras Vilda wrote:. . . the JLS . . . Campbell keeps telling that it could be a very difficult to read it
It is difficult. But I have my own controversial expanation for why that code compiles. Gosling thought programmers knew what they are doing The compiler is programmed to believe the “oldest ever joke”
Where are you going with all that manure?
I'm going to put it on my rhubarb.
Oh. We always have custard on ours.
The compiler might think your cast is like rhubarb and manure, but it is also programmed to believe “the costumer is always right.” If he asks for manure, he wants manure. If he asks for a cast to a Lion, he expects to assign it to a Lion instance. When you fling the manure at the waiter assign it to something different, the JVM will fling the manure at the waiter fling an Exception in your direction.
 
Winston Gutkowski
Bartender
Posts: 10575
66
Eclipse IDE Hibernate Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Liutauras Vilda wrote:Your previuos code basically is equal to:

Hmmm, not sure I agree. The effect is the same, but your first line could be separated from the second by a hundred lines of code, while Stevens' Line 15 is a direct assignment.
For that reason, it seems perfectly reasonable that a compiler might disallow line 15, while allowing your code above, since 'new Beast()' cannot possibly return anything but a Beast, and the compiler IS aware of type hierarchy.

It seems more likely to me that they decided not to for consistency; but it's only a guess.

Winston
 
Stevens Miller
Bartender
Posts: 1445
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Campbell Ritchie wrote:Gosling thought programmers knew what they are doing

Um, is this the same Gosling who thought programmers didn't really understand unsigned integers ?
 
Liutauras Vilda
Sheriff
Posts: 4928
334
BSD
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Winston Gutkowski wrote:'new Beast()' cannot possibly return anything but a Beast, and the compiler IS aware of type hierarchy.

Yeah, if 'new' were treated somehow similarly as "compile time constant" for the cases where the casting is on the same line as 'new', then possibly could be such check, especially that at compile time is known who is extending who (as 'Beast beast = new Lion(); does not require casting').
 
Campbell Ritchie
Marshal
Posts: 56593
172
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:. . . no warning about dividing by zero at either Line 3 or Line 10. . . .
Only NetBeans can explain why you are not getting consistent warnings, but division by 0.0 is permissible in the Java® Language Specification. Those two expressions will of course evaluate to (+)∞.
 
It is sorta covered in the JavaRanch Style Guide.
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!