posted 19 years ago
Your example shows a case of variable shadowing, which is allowed by the language but never a good idea. The int r is defined as a class level variable in both class A and B, and there's never a good reason to do this if B extends A. If those two variables represent the same data, then B should just use the variable it inherits from A. A should provide an accessor and mutator (getter/setter) that B should use instead of keeping another copy around.
If r represents different data altogether in B, then B should give it a different name. The name of any piece of data in a system should specifically describe that data well enough to disambiguate it from other data in the same class. When B extends A, it inherits all of A's data members, meaning that A's data members are now also part of B, just like A's methods are inherited by B. So B has an int r that it defines, and another int r that it inherits from A. This is of course not ambiguous to the compiler, but it will create doubt in the minds of programmers everywhere and is therefore to be avoided.
As far as polymorphism goes, here's a key concept every Java programmer should understand: the difference between type and class.
These terms are often used interchangably (most of the time this doesn't change the meaning of correctness of the statement, but sometimes it does). The "class" of an object is not quite the same as the "type" of an object. The distinction is: when an object is born, it is born of a specific, instantiable class. That object is of that class from the moment it's newed up to the time it is garbage collected. Objects cannot change their class.
The "type" of a class, however, is conferred upon that object by the reference that is used to access it. See the example code below for clarity:
In the code above, what is happening on line 1? I create a new LinkedList object on the heap, and assign the reference to that object to a variable called link. The new operator does more than one thing here: (1) declares memory on the heap for a LinkedList object, (2) creates the object and places it in that memory, (3) initializes the object by running its chain of constructors, and (4) returns a reference to that object. The = operator assigns the returned value of the new operator to link.
Now, as I was saying, because the new operator created a LinkedList, the reference stored in link refers to an object of class LinkedList. LinkedList is now and forever the class to which this object belongs.
On line 1, I am referring to that object with a variable of *type* LinkedList. That means, whenever I call a method on the object referred to by that particular reference, the JVM will treat the object as type LinkedList.
On line 2, I assign the reference to a new variable, list, of type List. Now, the object that list refers to is still of class LinkedList, but if I use the list reference to access that object, then it is of type List. Similarly, on line 3, I create a new reference called obj that allows me to treat the object of class LinkedList as type Object.
So now I have 3 different references to this object of class LinkedList, allowing me to treat it as type Object, List, or LinkedList. So while class is a long-lived concept, type only lives for the duration of a method call. The type is conferred upon the object for the duration of that method call depending on which reference I use:
In this code, it is important to realize that the hashCode() method that runs in all cases is the implementation attached to the object on the heap. The method implementation that runs will never change--the implementations are determined by the class of an object, not by its type. Consider the following:
In the above code, it is impossible for AbstractShape to calculate the perimeter of a shape because it's not a particular kind of shape, and each kind of shape uses a different formula for calculating its perimeter. (Normally, of course, AbstractShape would be declared abstract and just leave the method in the Shape interface abstract and not define it at all, but for the purposes of this example I've given that method a default implementation and made the class concrete to illustrate the point.)
So, consider the following:
In this code, there are only two objects created, an object of class Circle and another of class Square. There are three different ways of referencing each of those objects:
All three of these calls to getPerimeter() is calling the same exact implementation of that method--the one attached to Circle. Why? Because the referent object in each case is of *class* Circle, regardless of the fact that different types are being conferred upon that object in each call. So this code will never print out -1. Indeed, if you look at that last call, you'll see that method implementations must be determined by the class of an object and not its type; in that call we're referencing the object as a Shape, which is an interface and therefore can not have implementations defined.
Now let's see why it's unnecessary to use variable shadowing. Consider this rewrite of the above shape example:
I could have implemented Circle and Square so that they declared a class variable numSides and set it. But why? They extend AbstractShape, which already declares this variable, and that means they inherit it as well. It's a private variable, so they cannot access it, but it's important to realize that in the above code, Circle *already has* a numSides variable. Declaring another one with the same name is exactly equivalent to declaring another int with a different name, like nSides, and using it instead of numSides. This would leave numSides lying dormant, certainly a waste of memory and a bad design (because now there's ambiguity as to where this data *should* be stored--there are two places, numSides and nSides, equally valid according to this design).
When a class extends another class, it literally gets that data as though it declared it--newing up a Circle object will now declare space inside that object's memory to hold this int. And it can access it using the getter and setter provided by the AbstractShape class, which are also inherited by the Circle object and therefore attached to it.
Just for the sake of completeness, I will show the shape code the way I would actually implement it. You can look at the differences between it and the other versions and compare them based on what you have (hopefully) learned.
sev