Overriding Methods and Fields
When a class extends another class, the subclass inherits all non-private methods from the superclass. While that usually results in shorter code for the subclass without loosing the functionality that the superclass had, it frequently may happen that some of the superclass methods or fields are not appropriate for a subclass. In those cases a subclass can override the inherited fields and method of the superclass.
While you can override Fields, it's generally not done. Methods, on the other hand, are frequently overridden, and we need to discuss that in detail.
Most methods will remain the same; the only one that needs to be changed is the display method:
Actually, the first part of the new display method is the same as the code for the original method that has been overridden. Therefore, we really should make use of the superclass display method, yet still override it to provide a more appropriate display method.
The new code for the Rectangles display method could like this:
This way, our new method becomes shorter, and still provides a better display method
for rectangles. In addition, if we improve the display method of the superclass Shape,
those improvements will automatically carry over to the Rectangles display method
without ever touching the code of the Rectangle class.
Here is the new definition of our Shape class. The Rectangle and Circle classes remain unchanged, but they will still utilize the new code.
While inheritance is certainly useful, what about situations where there clearly is a superclass, but it is not possible to specify what a particular superclass method is supposed to accomplish. The superclass is simply too abstract to allow a concrete implementation of a method.
A class can define a method without actually providing an implementation for it. Such methods are called abstract:
Defining abstract classes is easy, since they do not have any implementation body:
Note that when these methods are added to the Shape class, it must also be declared as an abstract class.
Of course, a natural question is why one would want to have abstract methods. After all, they do not actually do anything in the Shape class, and they are currently overridden in the Rectangle and Circle classes. So, why have them in the first place? To answer this question, we first need to know a few more facts about how abstract methods impact on the class they are defined in:
Note that any class that extends an abstract class inhebrits its abstract methods. If
it does not implement the abstract method (by overriding it with an implemented version),
it itself contains an abstract method and therefore also becomes an abstract class; as
such it can not be instantiated. Therefore, if a class extends an abstract class, it
should override the abstract methods with a concrete implementation, unless it too should
become an abstract class in turn.
The above paragraph could be rephrased as follows: if a superclass wants to ensure that each of its subclasses contains a particular method, a superclass can simply define an appropriate abstract method. Then each subclass, or more precisely, each subclass that needs to instantiate objects, must implement that method. b
That hopefully answers the above question, but now we could ask why it is so important to ensure that each subclass should contain a particular method. We will answer that question in the chapter on polymorphism; for now, let us finish our new abstract Shape class.
Most of our class remains the same, but it is listed here anyway for completeness.
Neither the Rectangle nor the Circle class need any changes since they already include a computeArea and computePerimeter method that override the abstract Shape methods. Recall, however, that our ShapeTester program used to instantiate a Shape, Rectangle, and Circle object. Since we have turned Shape into an abstract class, it can no longer be instantiated and we need to remove the corresponding lines in the ShapeTester program. That, in fact, makes sense: why should we create a real-existing Shape object when it is too abstract to actually do anything. On the other hand, if we design another geometric object that extends Shape, we have now insured that this new class must include the computeArea and computerPerimeter methods.
Class hierarchies can be many levels deep. Classes that extend a superclass can in turn
be extended again. There is no limit to these extensions, and all previous topics apply.
However, Java does impose a certain limit on inheritance: a class can not extend two
classes simultaneously at the same level of a hierarchy.
Multiple inheritance is not allowed in Java, so finally there's a concpet that's easy to understand. There is another mechanism called an Interface that can frequently substitute for multiple inheritance. We will define interfaces in the chapter on multi-threaded classes.
Sometimes you might not want to allow a class to serve as a superclass, or you might want to prevent a particular method from being overridden. In those instances, the final keyword can be used. The details are not important right now, and we will most like use the keyword 'final' with fields only.
In other words, to declare a value to be constant in Java, the final keyword should be used. Once a variable is declared final, its value can not change during the course of the program.
When attempting to compile this class, the resulting error message will look similar to:
Here is another, somewhat more involved example that uses inheritance, abstract classes
and methods, and final fields and methods.
|An abstract Student class containing fields for the two different rates, and a field for the students name. The class should contain a display method to display the students name and total billing amount and a method to print out the name of the university.|
|A class for an in-state student and one for an out-of-state student. Each class should contain a field for the number of credit hours the student is taking, and a method to print out the universitys name, the students name, and the total amount for the credit hours of the student.|
We could choose the following approach:
|To ensure that a subclass can not change the rates, we use the final modifier to declare the rate fields as constant in the Student class.|
|To ensure that the universitys name does not change, we use a final method in the Student class.|
|Finally, since the student class can not actually print out any billing information for a student without knowing the type of students and the number of credit hours, we declare the Student display method, and hence the entire class, as abstract.|
In addition, we can add the main method to this class instead of creating a separate class for testing everything, which means, of course, that we will be able to directly run the class Student.
The last modifier we need to discuss is perhaps the most interesting one. It allows you to create rather tricky classes, if desired, and also lets you easily define certain calculation methods, i.e. methods that only do some calculations without having to manipulate any data other than their input parameters. Again, we will skip the details, and focus only on static methods (leaving static fields alone).
Static methods are useful, for example, if you need to perform certain calculations only, without having to manipulate non-static data.
We can use a static method here since the only information the method needs is the input variables:
This method can now be included in some class and can be used without instantiating the
appropriate object first.
As a matter of fact, this method in turn calls upon the static method sqrt of the java.lang.Math package. All methods in the java.lang.Math package are static methods. Hence, they can be accessed by using the class name, a dot, and the method name without having to instantiate a Math object first. For example to compute the cosine of a number, one can use Math.cos(2.0). Other static methods include the conversion methods in the String, Double, and Integer classes (String.valueOf, Double.toString, Integer.parseInt, and more), and methods in the java.lang.System package such as System.out.println and System.gc. There is, of course, also the standard main method, which has as method header:
that returns an integer representation of the input string s, if possible. Use the static parseInt method from the Integer class to convert the string "123" to an integer number.
So, in the above example we have used a method Math.parseInt without first instantiating an object of type Math. That only works with static methods. This - finally - concludes the introduction to programming. From now on we will have a lot more fun ! We left out a few technical details, but other than that, this is the basics of object-oriented programming