I think there is an errata in an explanation in the section "Casting Objects", page 282.
There is the following code
And there is this explanation: "[...] In the second example, though, we explicitly cast the object to a subclass of the object Primate and we gain access to all the methods available to the Lemur class."
I believe this explanation is wrong and confusing, since we are not able to change an object's type. When casting, we just change the type of the reference.
Maybe the sentence above should be "[...] In the second example, though, we explicitly cast the reference variable to a subclass of the class Primate and we gain access to all the methods available to the Lemur class."
Am I right?
Taking the opportunity, I would like to say what I understand about casting references, and if you guys don't mind, I would like to know if I understand it correctly.
Let's take the following code as an example (it's an example from the book, that I made some changes)
line1: Here I create a new instance of Cat, and assign it to a reference variable of Cat
line2: I have access to the getName() method, because it is defined in the Cat class
line3: Here I'm passing the reference to a reference variable of type Walk (which is an interface). I can do this directly without an explict cast, because Cat implements Walk, so I'm changing a more specific reference of a Cat object to a broader reference (more generic). The class Cat and the interface Walk are related, so the compiler will allow it. At runtime, it will be ok, since the variable c holds a Cat object, and a variable of type Walk can hold an object of Cat as well.
line4: I don't have a direct access to methods defined in the Cat class using a reference variable of type Walk, although those methods still exist in the object in the memory. To access those methods again, I need to do an explicit cast, so I'm narrowing the reference variable w to a more specific type (Cat), and now I have access to the getName() method again. It is ok, because the narrower type can hold the object
line5: Now, I can't assign w directly (without explicit cast), because the Walk interface doesn't extend the Run interface. I need to do an explicit cast. But wait, Run and Walk aren't directly related, so how this works? This works because Walk and Run are considered siblings, because Cat implements both of them, so it is ok for the compiler, and it is ok at runtime, since a Run variable can hold a Cat object too
line6: Same idea of the ((Cat) w).getName() above
line7: Same idea of the ((Cat) w).getName() and ((Cat) r).getName(), but now I'm assiging the cast to a Cat reference variable.
line8: Using the available method in the Cat class
line9: Using the available variable in the Cat class
When explicitly casting a reference, we are telling that we want a narrower reference of some broader type (super class or interface) that is able to point to an object that has the same type or a "smaller" type as the narrower reference type.
Concluding, when casting a more specific reference type to a broader reference type (from subclasses to superclasses, for example), we aren't required to do an explicit cast, but casting from broader reference types to narrower reference types requires us to do an explicit cast. To do an explicit cast without problems, the current reference type must be related to the more specific type inside the parentheses, and the new reference with the more specific type must be able to hold the object that the broader reference type is holding (to avoid a ClassCastException).
The type of the reference variable determines which variables and methods we can access, but the type of the object determines the version of the method that is going to be used at runtime (for example, when we have an overridden method).
Did I understand it correctly?
João Victor Gomes wrote:Did I understand it correctly?
Yes, you did! But in your explanations (which I extracted from the code snippet) there are some things which are not 100% correct.
First of all, it's important to remember that the compiler does not execute any code at all! So the compiler only knows reference variable c is of type Cat. The compiler doesn't care to which object this reference variable is actually referring. It could be Cat, SmallCat, BigCat or some other type which IS-A Cat.
At compile time the compiler will verify if both types of a cast are compatible with each other. If it is not, you'll get a compiler error. If it is, you can get a ClassCastException at runtime when the type of the object is not compatible with the type of the cast.
João Victor Gomes wrote:This works because Walk and Run are considered siblings, because Cat implements both of them, so it is ok for the compiler
This is incorrect! As mentioned earlier, the compiler only knows the type of reference variable w (which is Walk). The compiler does not know at all (and he doesn't care) that w is referring to a Cat. Why does this work? When using an explicit cast, you can always cast between two unrelated interfaces. To which object reference variable w is referring only matters at runtime. If the type of the object is not compatible with the type of the cast, you'll get a ClassCastException.
João Victor Gomes wrote:Taking the opportunity, I would like to say what I understand about casting references, and if you guys don't mind, I would like to know if I understand it correctly.
you should definitely add another class to the code snippet which doesn't implement these interfaces (e.g. Car) and see if if you'll get compiler errors if you replace Cat with Car. And it might be worthwile to mark both Cat and Car classes as final and see if that makes any difference...
Casting and instanceof is probably one of the more popular topics in this forum. So using the search function you'll probably find a bunch of (very) good topics on this exam objective. These topics contain all valuable information (with code snippets to illustrate rules and possible pitfalls) about casting:
Hope it helps!
Roel De Nijs wrote:
When using an explicit cast, you can always cast between two unrelated interfaces.
Precious information, I had no idea about it.
Thanks Roel, I'll read each of the topics that you mentioned, and do more tests about this subject. I guess this is the last thing that I want to fully understand before taking Enthuware mock exams.
Roel De Nijs wrote:
João Victor Gomes wrote:Precious information, I had no idea about it.
And do you have any idea about the reason why?
According to the JLS, because a subclass of the class Cat might implement those interfaces. In fact, if I mark the Cat class as final, the code would not compile, because there wouldn't be a subclass of Cat.
Line 1 - compiles but throws a ClassCastException at runtime. If I mark the class as final, it won't compile
Is that right?
João Victor Gomes wrote:Line 1 - compiles but throws a ClassCastException at runtime. If I mark the class as final, it won't compile
Is that right?
The compiler is a smart cookie: if a class is not marked as final, it can be subclassed and any of these subclasses can implement one (or more) interfaces; so a cast should definitely be possible (at runtime the type of the object the reference variable is refferring to must be compatible with the type of the cast, otherwise a ClassCastException will be thrown). But if the class is marked final, it knows the class can't be subclassed anymore and a reference variable of that class can never implement the interface; so it makes no sense to cast (hence a compiler error is given).