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.

Overriding Methods

If a subclass defines a method that has exactly the same method header as a method inherited from its superclass, then the subclass method overrides the superclass method. The method as implemented in the subclass will be the one that counts for the subclass. The original superclass method is still available to the subclass, if necessary, using the super keyword together with the dot operator.

Example:

In our above classes, the Rectangle and Circle classes both use the display method inherited from their superclass Shape. However, that method does not print out information particular to these new classes. Therefore, both classes may choose to override their inherited display methods. Redefine the display method of the Rectangle class to also print out the values of the width and length fields.

 Most methods will remain the same; the only one that needs to be changed is the display method:

public class Rectangle extends Shape 
{  // fields 
   double length, width; 
   // constructors
   public Rectangle()
   { // as before }
   public Rectangle(double _length, double _width) 
   { // as before } 
   // methods 
   public void computeArea() 
   { // as before } 
   public void computePerimeter() 
   { // as before } 
   public void display() 
   { 
      System.out.println("Name: " + name); 
      System.out.println("Area: " + area); 
      System.out.println("Perimeter: " + perimeter); 
      System.out.println("Length: " + length); 
      System.out.println("Width: " + width); 
   } 
}

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.

Example:

Redesign the Rectangle’s display method again, this time using the superclass display method to shorten and simplify the code.

The new code for the Rectangle’s display method could like this:

public void display() 
{ 
   super.display(); 
   System.out.println("Length: " + length); 
   System.out.println("Width: " + width); 
}

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 Rectangle’s display method without ever touching the code of the Rectangle class.
 
Example:

Create an improved version of the display method in the Shape class. Note that this improved version will automatically be used in the Rectangle and Circle classes as well, without having to recompile their code. Make sure to verify this.

Here is the new definition of our Shape class. The Rectangle and Circle classes remain unchanged, but they will still utilize the new code.

public class Shape 
{  // fields 
   String name; 
   double area, perimeter; 
   // constructor 
   public Shape() 
   { 
      name = "undetermined"; 
      area = perimeter = 0; 
   } 
   public void display() 
   { 
      System.out.println("******** GEOMETRIC SHAPE ******************"); 
      System.out.println(name+", area "+area+", perimeter "+perimeter); 
   } 
}

Abstract Methods and Classes

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:

Abstract Method

An abstract method is a method that has only a method header but no implementation body. A method must be designated as abstract by using the keyword abstract. A class containing an abstract method will automatically become an abstract class and must be declared as such (see below).
An abstract method can not have an implementation body.

Syntax

modifier abstract returnType methodName(parameterList);

Example:

In our above classes, the Shape class has fields area and perimeter, but does not have any methods to compute these values. In fact, in can not really compute them without knowing more information about the geometry of the particular shape. Define two abstract methods to compute the area and perimeter for the Shape class.

Defining abstract classes is easy, since they do not have any implementation body:

public abstract void computeArea();
public abstract void computePerimeter();

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:

Abstract Class

An abstract class is a class that contains at least one abstract method, i.e. a method that has only a function header but no implementation body. Abstract classes must be defined using the keyword abstract.
An abstract class can not be instantiated.

Syntax

modifier abstract class ClassName [extends Name] [implements Interfaces]

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.

Example:

List the new definition of our abstract Shape class, including the two abstract methods defined above.

Most of our class remains the same, but it is listed here anyway for completeness.

public abstract class Shape 
{  // fields 
   String name; 
   double area, perimeter; 
   // constructor 
   public Shape() 
   { 
      name = "undetermined"; 
      area = perimeter = 0; 
   } 
   public abstract void computeArea(); 
   public abstract void computePerimeter(); 
   public void display() 
   { 
      System.out.println("******** GEOMETRIC SHAPE ******************"); 
      System.out.println(name+", area "+area+", perimeter "+perimeter); 
   } 
}

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.

Multiple Inheritance

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 a concept by which one class simultaneously extends more than one superclass at the same level of a hierarchy. Multiple inheritance is not supported in Java.

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.

The Final and Static Modifiers

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.

Final Fields

A final field is a data field whose value can not change. To declare a field to be final, the final keyword is used. Final fields can be thought of as constants.
Final fields must be initialized at the time they are declared. By convention, final fields should have names in capital letters.

Syntax:

modifier final type fieldName = initialValue;

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.

Example:

Create a test class containing a final field Pi with value 3.1415. Then attempt to modify that field in a method to see what error message the Java compiler will produce.

class Test 
{  // fields 
   final double Pi = 3.1415; 
   // methods 
   void changePi() 
   { 
      Pi = 3.14; 
   } 
}

When attempting to compile this class, the resulting error message will look similar to:

C:\PROGS\JAVA\OOP\INTRO\>javac Test.java 
Test.java:6: Can't assign a value to a final variable: Pi

Here is another, somewhat more involved example that uses inheritance, abstract classes and methods, and final fields and methods.
Example:

Suppose we want to create a billing system for students at Java State University. The rates that the university charges per credit hour are different for in-state and out-of-state students: $75 per credit hour for in-state students, $200 for out-of-state students. Create the following three classes:

An abstract Student class containing fields for the two different rates, and a field for the student’s name. The class should contain a display method to display the student’s 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 university’s name, the student’s name, and the total amount for the credit hours of the student.

Use the modifiers final and protected to ensure that a subclass can not change the rate the university charges or the name of the university. Also, use abstract classes and methods when necessary and add the usual main method to test your program.

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 university’s 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.

public abstract class Student 
{ // fields 
   protected final double INSTATE_RATE = 75; 
   protected final double OUTSTATE_RATE = 200; 
   protected String studentName; 
   // methods 
   abstract void display(); 
   void showSchoolName() 
   { 
      System.out.println("Java State University"); 
      System.out.println("*********************");
   } 
   public static void main(String args[]) 
   {  // NOTE: The classes InStateStudent and OutStateStudents must be 
      // created before this class will compile correctly.
      InStateStudent resident = new InStateStudent("John Doe", 24); 
      OutStateStudent alien = new OutStateStudent("Joan Smith", 26); 
      resident.display(); 
      alien.display(); 
   } 
} 
class InStateStudent extends Student 
{  // fields 
   private int creditHours; 
   // constructor 
   InStateStudent(String _studentName, int _creditHours) 
   { 
      studentName = _studentName; 
      creditHours = _creditHours; 
   } 
   void display() 
   { 
      showSchoolName(); 
      System.out.println(studentName+" takes "+creditHours+" credits."); 
      System.out.println("In-state bill: " + creditHours * INSTATE_RATE); 
   }
} 
class OutStateStudent extends Student 
{  // fields 
   private int creditHours; 
   // constructor 
   OutStateStudent(String _studentName, int _creditHours) 
   { 
      studentName = _studentName; 
      creditHours = _creditHours; 
   } 
   void display() 
   { 
      showSchoolName(); 
      System.out.println(studentName+" takes "+creditHours+" credits."); 
      System.out.println("Out--state bill: "+creditHours*OUTSTATE_RATE); 
   } 
}

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

Static methods are methods that can be used without instantiating the appropriate object from a class. To declare a method as static, the static keyword is used.

Static methods can not refer to non-static data, but input parameters to methods are considered static data and can therefore be manipulated.

Static methods can be accessed using className.methodName(parameterList) in addition to objectName.methodName(parameterList).

Syntax:

modifier static returnType methodName(parameterList) 
{ // implementation }

Static methods are useful, for example, if you need to perform certain calculations only, without having to manipulate non-static data.

Example:

Define a static method distance that takes as input two double numbers representing a point in two-dimensional space. The method should return the distance of that point to the origin. Recall that if (x,y) is a point in 2-dimensional space, its distance to the origin is .
 

We can use a static method here since the only information the method needs is the input variables:

public static double distance(double x, double y)
{ 
   return Math.sqrt(x*x + y*y); 
}

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:

public static main(String args[])
// implementation

Example:

The java.lang.Integer class contains a method defined as

public static int parseInt(String s)

 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.

class Test 
{  // fields 
   String s = "123"; 
   // methods 
   int convert() 
   { 
      return Integer.parseInt(s); 
   } 
   public static void main(String args[]) 
   { 
      Test t = new Test(); 
      System.out.println("Converted number plus 10: " + (t.convert() + 10)); 
   } 
}

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

(bgw)