Java Programming, Lecture Notes # 44, Revised 10/03/99.
Preface
Introduction
Creating Subclasses
Final Classes and Methods
Abstract Classes and Methods
The Object Class
Overriding Methods
Object Class Methods
Review
Students in Prof. Baldwin's Introductory Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson.
As you already know, you can create new classes by inheriting from existing classes. The new class is often called the subclass while the inherited class is often called the superclass.
Also as you already know, an object of the new subclass contains all the variables and all the methods of the superclass and all its ancestors, all the way up the family tree, to and including the class named Object.
Some of the methods may have been overridden somewhere in the family tree.
All classes in Java are inherited from some class.
The class at the top of the inheritance hierarchy is the Object class. This class is defined in the package named java.lang. Thus, the Object class is the root of the Java family tree.
You also already know that an object has state and behavior.
Objects of classes which you define have their state and behavior influenced by the variables and methods which they inherit as well as any new variables and methods which you add.
New subclasses that you define can use the inherited methods as is, or their behavior can be modified relative to objects of the new class; a process commonly known as overriding a method.
Through the use of access control, subclasses can choose to allow variables inherited from the superclass to continue to be exposed to further inheritance, or to hide them.
Generally as you move from the root through the branches to the leaves in an object-oriented family tree, the classes become more specialized.
The following definition is from The Java Tutorial by Campione and Walrath:
Definition: A subclass is a class that derives from another class. A subclass inherits state and behavior from all of its ancestors. The term superclass refers to a class's direct ancestor as well as all of its ascendant classes. |
As you are aware, in order to create a subclass of an existing class, we use the extends keyword in the definition of the new class.
The class definition describes all there is to know about the class such as its super class, its variables and their type, its methods, its access control specification, etc.
Sometimes you may not want to allow your class to be subclassed, or you may not want to allow your methods to be overridden.
A class which is marked with the final keyword cannot be subclassed.
A method which is marked with the final keyword cannot be overridden.
The flip side of final is abstract. A class which is marked as abstract is designed to be subclassed. It is not possible to instantiate an object from an abstract class.
Likewise, a method which is marked abstract is designed to be overridden. An abstract method defines the interface only, and cannot be invoked until it if fully defined. (You may have noticed that there are some similarities between abstract methods and method declarations in an interface.)
As mentioned earlier, all classes in Java inherit directly or indirectly from the Object class. Thus, all new classes inherit some methods which are defined in Object. As you might expect, those methods are of a very general nature since they apply to all objects.
Your new class can either use these inherited methods as is, override them, or ignore them altogether.
Replacing the Method Implementation
Enhancing a Superclass Constructor
Methods a Subclass Must and Cannot Override
A subclass can either completely override the implementation of an inherited method, or the subclass can enhance the method by adding functionality to it.
If your method overrides a method in its superclass, you can use the keyword super to bypass the overridden version in the subclass and invoke the version in the superclass.
This was illustrated by a program named super2 in an earlier lesson on classes. This program contained an overridden version of a superclass method named myMethod() that uses the value of an incoming parameter to decide whether to invoke the superclass version or to execute its own code.
The program is repeated below for convenience.
/*File super2.java Copyright 1997, R.G.Baldwin Illustrates bypassing a local overridden method in favor of the method in the superclass. The output from this program is: In main Incoming parameter is false Overridden method invoked Incoming parameter is true Base-class method invoked **********************************************************/ class base{ void myMethod(boolean superFlag){//method to override System.out.println( "Incoming parameter is " + superFlag); System.out.println("Base-class method invoked"); }//end myMethod }//end base class definition //=======================================================// class super2 extends base{ void myMethod(boolean superFlag){//override myMethod() //Make decision to invoke this version or the version // in the superclass. if(superFlag) super.myMethod(superFlag);//super version else{ //this version System.out.println( "Incoming parameter is " + superFlag); System.out.println("Overridden method invoked"); }//end else }//end overridden myMethod() //-----------------------------------------------------// public static void main(String[] args){ //main method //instantiate an object of this type super2 obj = new super2(); System.out.println("In main"); //Invoke overridden version of myMethod() obj.myMethod(false); //Invoke superclass version of myMethod() obj.myMethod(true); }//end main method }//End super2 class definition. |
Obviously it would be possible to do both as well. In other words, there is no reason that an overridden method could not invoke the superclass version using the super keyword, and also execute additional code to provide additional behavior.
Some methods are defined with an empty implementation in the expectation that subclasses will define a completely new implementation. Whether or not the implementation of a method that is inherited is empty, your subclass can completely replace that implementation.
Although we haven't discussed multithreading yet, there is a very important method in the Thread class named run(). The implementation of the run() method in the Thread class is completely empty, meaning that it does nothing but define the method interface.
It doesn't make sense for the run() method to define a default behavior because it will ultimately be used to do whatever your thread needs to have done. The Java library programmers couldn't possibly anticipate your needs in this regard so they left it empty to allow you to define the behavior.
On the other hand, it cannot be marked abstract. That would prevent the instantiation of objects of the Thread class because classes containing abstract methods cannot be instantiated until a definition is provided for the abstract method. Instantiation of Thread objects is a critical aspect of multithreaded programming.
The desired result was accomplished by the Java library programmers by defining run() as an empty method.
You can completely replace the implementation of an inherited method by giving your new method the same signature as the method that you want to override and then providing whatever body makes sense for the task at hand.
In the case of the run() method, you might have something like the following:
class MyThread extends Thread { void run() { //Method body . . . } }//end class MyThread |
In this code fragment, the run() method in the class named MyClass overrides the run() method from the Thread class and provides its own implementation.
You must be careful to make certain that you understand the difference between overriding and overloading a method. To overload a method, you must duplicate the method name, but use a different argument list.
To override a method, you must match the entire method signature.
If you aren't careful when writing your new method signature, you will be overloading methods when you think that you are overriding them. This is a situation where you don't get any warnings from the JDK 1.1 compiler. It apparently assumes that you know what you are doing if you inadvertently overload instead of override a method.
Sometimes when we create the constructor for a subclass, we would like to invoke a parameterized constructor for the superclass also.
This can be accomplished by causing the first line of our constructor to contain the keyword super followed by a parameter list as though calling a method named super(). The parameter list must match the parameters in the argument list of the superclass constructor.
This has the effect of causing the constructor for the superclass to be invoked using parameters passed from the subclass before the constructor for the subclass is executed.
According to Java in a Nutshell by David Flanagan, "If super doesn't appear as the first statement of a constructor body, the Java compiler inserts an implicit call -- super() -- to the immediate superclass constructor."
That is, the noarg constructor for the superclass is automatically invoked whenever we execute the constructor for a new subclass unless we specifically invoke a parameterized constructor for the superclass.
The super keyword can also be used at other places in a method to access variables in the superclass or invoke methods in the superclass using syntax such as:
super.myMethod(14); x = super.myVariable; |
This is sometimes required when variable names or method names of the subclass duplicate and hide (or shadow) variable or method names of the superclass.
If the subclass is not declared to be abstract, it must override any methods which are declared abstract in the superclass.
In other words, an abstract method must be overridden to create a concrete method in the first non-abstract class in the inheritance hierarchy.
A subclass cannot override methods that are declared final in the superclass because, by definition, final methods cannot be overridden.
The equals() Method
The Class Class
The getClass() Method
The toString() Method
Other Methods Provided by the Object Class
The Object class which is at the top of the Java class hierarchy provides a number of useful methods for all objects to use. A complete list of the available methods can be obtained from the API documentation available from JavaSoft.
For example, Object provides
a method by which one object may compare itself to another object, a method to convert an object to a string, a method to wait for a specified condition to occur, a method to notify other objects that a condition has changed, and a method to return a Class object that describes the class of the object.
Classes that you define will all inherit the equals() method from the Object class. You can use this method to compare two objects for equality.
Note that is is not the same as comparing two object references using the == operator. That test simply determines if two object references point to the same object.
The equals() method is intended to be used to determine if two separate objects are of the same type and contain the same data. The method returns true if the objects are equal and false otherwise.
The system already knows how to apply the equals() method to all of the standard classes and objects of which the compiler has knowledge. For example, you can already use the method to test two String objects for equality.
Your classes can override the equals() method to make an appropriate comparison between two objects of a type that you define. The following Java application overrides the equals() method to compare two objects of the new equals1 class. (Obviously there is nothing to ascertain that your overridden equals() actually does the comparison properly, so you could make your overridden equals() method do just about anything that you want it to do.)
Note that the signature of the equals() method requires an argument of the generic type Object.
If you define the method with an argument of a different type, you will be overloading, not overriding the method.
Once the method begins execution, it is necessary to downcast the argument to type equals1 before attempting to perform the comparison. We will have more to say about upcasting and downcasting in a subsequent lesson. In any event, pay particular attention to the parentheses in the downcast operation.
Also note the use of the instanceof operator to confirm that the incoming object is of the correct type.
In this example program one of the objects provided for equivalence testing is of the wrong type (String) and the overridden equals() method simply reports that it is not an equivalent object.
/*File equals1.java Copyright 1997, R.G.Baldwin Illustrates overriding the equals() method of the Object class to determine if two objects are equivalent. This is not the same as testing to see if two references point to the same object by using the == relational operator. The program was tested using JDK 1.1.3 under Win95 The output from this application is: obj1 equals obj1: true obj1 equals obj2: true obj1 equals obj3: false obj1 equals obj4: false **********************************************************/ class equals1{//controlling class int myData; equals1(int data){//constructor myData = data; }//end constructor //-----------------------------------------------------// //Overridden equals method public boolean equals(Object arg){ //Test for proper type and not null. If not null and // proper type, test for equivalent data values. Note // the use of the instanceof operator. if( (arg != null) && (arg instanceof equals1)){ //If proper type, downcast object type to equals1 // type. Then compare and return result return (this.myData == ((equals1)arg).myData); }else return false;//not proper type, return false }//end method equals() //-----------------------------------------------------// public static void main(String[] args){ //instantiate objects for testing equals1 obj1 = new equals1(2); equals1 obj2 = new equals1(2); equals1 obj3 = new equals1(3); String obj4 = "A string object"; //Perform tests and report results System.out.println("obj1 equals obj1: " + obj1.equals(obj1)); System.out.println("obj1 equals obj2: " + obj1.equals(obj2)); System.out.println("obj1 equals obj3: " + obj1.equals(obj3)); System.out.println("obj1 equals obj4: " + obj1.equals(obj4)); }//end main //-----------------------------------------------------// }//end class definition for equals1 |
There is a class named Class which is defined as follows (See JavaSoft API Documentation):
public final class java.lang.Class extends java.lang.Object{ // Methods public static Class forName(String className); public ClassLoader getClassLoader(); public Class[] getInterfaces(); public String getName(); public Class getSuperclass(); public boolean isInterface(); public Object newInstance(); public String toString(); } Instances of the class Class represent classes and interfaces in a running Java application. There is no public constructor for the class Class. Class objects are constructed automatically by the Java Virtual Machine as classes are loaded and or by calls to the defineClass method in the class loader. |
I mention the class named Class at this point to indicate that once you have a Class object that describes another object, you can make various inquiries about the object, such as getting its name and the name of its superclass. You can also instantiate new instances of the class without use of the new operator. We will use some of this capability in the next section.
The getClass() method of the Object class can be used to obtain a Class object that describes another object. That is, it returns an object of type Class (see the Class class above) which contains important information about the target object.
Having obtained an object of type Class that describes the class of an object, methods from the Class class shown above can then be used to obtain information about the object.
This is a fairly common technique in Java; to create an object that describes another object. Then methods can be invoked on the descriptor object to obtain information about the target object. In Java, we have Bean descriptor objects, File descriptor objects, Class descriptor objects, etc.
Also, having obtained an object of type Class that describes the class of an object, the newInstance() method of the Class class can be used to instantiate another object of the same type. The result is as though the new operator were used with a Noarg constructor of a known class.
If a reference to the new object is needed, it is necessary to declare the reference variable as the generic Object type, but the actual object will take on all the attributes of the actual class for which it is being instantiated.
The following program illustrates some of these features. First it simply instantiates an object, gets the class of that object, and applies some methods from the Class class to obtain and display other information about the object.
Next, it asks the user to enter a 0 or a 1 and instantiates a new object on the basis of the user input.
If the user enters a 0, a String object is instantiated.
Otherwise, a new object is instantiated by applying the getClass() method to an existing object and using the newInstance() method to instantiate a new object of that type.
The getClass() method is then used to obtain and display information about the new object.
/*File GetClass1.java Copyright 1997, R.G.Baldwin Illustrates use of the getClass() method from the Object class and several methods from the Class class. Also illustrates instantiating new objects of types which the compiler cannot possibly know about at compile time. Tested using JDK 1.1.3 under Win95. For a user input of 0, the output is: Name of class for obj1: GetClass1 Name of superclass for obj1: class java.lang.Object Enter a 0 or a 1 0 Name of class for obj2: java.lang.String Name of superclass for obj2: class java.lang.Object For a user input of 1, the output is: Name of class for obj1: GetClass1 Name of superclass for obj1: class java.lang.Object Enter a 0 or a 1 1 Name of class for obj2: GetClass1 Name of superclass for obj2: class java.lang.Object **********************************************************/ import java.io.*; class GetClass1{ public static void main(String[] args){ //instantiate an object of this type GetClass1 obj1 = new GetClass1(); //Use the getClass() method from the Object class and // two methods from the Class class to obtain and // display information about the object. System.out.println("Name of class for obj1: " + obj1.getClass().getName()); System.out.println("Name of superclass for obj1: " + obj1.getClass().getSuperclass()); //Now instantiate another object based on user input // such that the compiler cannot possibly know the // type of the object at compile time. Object obj2 = null;//reference to generic object System.out.println("Enter a 0 or a 1");//prompt try{ int inData = System.in.read();//get user input if( (char)inData == '0')//if user entered '0' obj2 = "String object";//create a string object else //create new object of the type of another object obj2 = obj1.getClass().newInstance(); }catch(Exception e){ System.out.println("Exception " + e); }//end catch block //Now display the class and superclass of new object System.out.println("Name of class for obj2: " + obj2.getClass().getName()); System.out.println("Name of superclass for obj2: " + obj2.getClass().getSuperclass()); }//end main }//end class GetClass1 |
Note that the getClass() method is a final method and cannot be overridden. It returns a Class object (an object of type Class) which allows you to apply the methods of the Class class to the object.
We have only illustrated a couple of the methods available in the Class class, but since our primary emphasis here is the use of methods inherited from the Object class, we'll let it go at that.
It would be a good exercise for the student to design simple programs that illustrate the use of the remaining methods of class Class.
The Object class provides a toString() method that can be used to convert all objects to some appropriate string representation. The actual string representation depends on the type of object, and whether or not the toString() method of the Object class has been overridden..
For example, the overridden toString() method extracts the integer contained in an Integer object.
Similarly, if you apply the overridden toString() method to a Thread object, certain information about the object important to threads will be extracted and returned as a string.
You can simply accept the default conversion of objects of your new classes, or you can override the method to cause it to convert objects of your design to strings according to your definition of conversion.
The following program illustrates overriding the toString() method for a newly-defined class so that it can be used to convert objects of that class into strings.
It also illustrates the default string representation for a new class when the toString() method is not overridden.
/*File myToString.java Copyright 1997, R.G.Baldwin Illustrates overriding the toString() method of the Object class to display the data in an object. Also illustrates default conversion of an object to a string when the class of the object has no overridden toString() method. This program was tested using JDK 1.1.3 under Win95. The output from the program for one run was: Obj w/overridden toString() method: Richard G. Baldwin Obj w/o overridden toString() method: myOtherString@1cc74d **********************************************************/ class myOtherString{//class without overridden toString() }//end class myOtherString //=======================================================// class myToString{//controlling class String first; //define instance variables for the class String middle; String last; //constructor myToString(String first, String middle, String last){ this.first = first; this.middle = middle; this.last = last; }//end constructor //-----------------------------------------------------// //Override toString() method from Object class public String toString(){ //convert object to a string and return it return (first + " " + middle + " " + last); }//end overridden toString method //-----------------------------------------------------// public static void main(String[] args){//main method myToString obj = new myToString(//instantiate obj "Richard","G.","Baldwin"); //display it using overridden toString() System.out.println( "Obj w/overridden toString() method: " + obj.toString()); //display object with no overridden toString() method System.out.println( "Obj w/o overridden toString() method: " + new myOtherString().toString()); }//end main method //-----------------------------------------------------// }//end myToString class definition |
Several other methods of the Object class will be discussed in other lessons. For example, the finalize() method will be discussed in lessons dealing with finalization. You will learn how to override the finalize() method for purposes associated with new types which you define.
There are also several methods which are used in multithreaded programming to cause the various threads to be synchronized when needed:
These methods will be discussed in those lessons which deal with multithreaded programming.
Q - The class at the top of the inheritance hierarchy is the Object class and this class is defined in the package named java.Object: True or False? If false, explain why.
A - False. The Object class is defined in the package named java.lang.
Q - We say that an object has state and behavior: True or False? If false, explain why.
A - True.
Q - In Java, a method can be defined as an empty method, normally indicating that it is intended to be overridden: True or False? If false, explain why.
A - True.
Q - Including an empty method in a class definition will make it impossible to instantiate an object of that class: True or False.
A - False.
Q - A subclass can invoke the constructor for the immediate superclass by causing the last line of of the subclass constructor to contain the keyword super followed by a parameter list as though calling a function named super() and the parameter list must match the method signature of the superclass constructor: True or False? If false, explain why.
A - False. This can only be accomplished by causing the first line of the constructor to contain the keyword super followed by a parameter list as though calling a function named super(). The parameter list must match the method signature of the superclass constructor.
Q = The equals() method is used to determine if two reference variables point to the same object: True or False? If false, explain why.
A- False. You can use the equals() method to compare two objects for equality. You can use the equality operator (==) to determine if two reference variables point to the same object.
Q - The equals() method is used to determine if two separate objects are of the same type and contain the same data. The method returns false if the objects are equal and true otherwise. True or False? If false, explain why.
A - False. The method returns true if the objects are equal and false otherwise.
Q - The equals() method is defined in the Object class: True or False? If false, explain why.
A - True.
Q - Your classes can override the equals() method to make an appropriate comparison between two objects of a type that you define: True or False? If false, explain why.
A - True.
Q - You must override the equals() method to determine if two string objects contain the same data: True or False? If false, explain why.
A - False. The system already knows how to apply the equals() method to all of the standard classes and objects of which the compiler has knowledge. For example, you can already use the method to test two String objects or two array objects for equality.
Q - Given an object named obj1, provide a code fragment that shows how to obtain the name of the class from which obj1 was instantiated and the name of the superclass of that class.
A - See code fragment below:
System.out.println("Name of class for obj1: " + obj1.getClass().getName()); System.out.println("Name of superclass for obj1: " + obj1.getClass().getSuperclass()); |
Q - Given an object named obj2, provide a code fragment that shows how to use the newInstance() method to create a new object of the same type
A - See code fragment below:
obj2 = obj1.getClass().newInstance(); |
Q - By overriding the getClass() method, you can use that method to determine the name of the class from which an object was instantiated: True or False? If false, explain why.
A - False. The getClass() method is a final method and cannot be overridden.
Q - You must use the new operator to instantiate an object of type Class: True or False? If false, explain why.
A - False. There is no public constructor for the class Class. Class objects are constructed automatically by the Java Virtual Machine as classes are loaded and or by calls to the defineClass method in the class loader.
Q - The Class class provides a toString() method which can be used to convert all objects known to the compiler to some appropriate string representation. The actual string representation depends on the type of object: True or False? If false, explain why.
A - False. The Object class (not the Class class) provides a toString() method which can be used to convert all objects known to the compiler to some appropriate string representation. The actual string representation depends on the type of object.
Q - You can override the toString() method of the Class class to cause it to convert objects of your design to strings: True or False? If false, explain why.
A - False. The toString() method is a method of the Object class, not the Class class.
Q - By default, all classes in Java are either direct or indirect descendants of the Class class which is at the top of the inheritance hierarchy: True or False? If false, explain why.
A - False. By default, all classes in Java are either direct or indirect descendants of the Object class (not the Class class) which is at the top of the inheritance hierarchy.
Q - Write a program that meets the following specification.
/*File SampProg20.java From lesson 44 Copyright 1997, R.G.Baldwin Without viewing the solution which follows, write a Java application that illustrates overriding the equals() method of the Object class to determine if two objects are equivalent. This is not the same as testing to see if two references point to the same object by using the == relational operator on the reference variables. The output from this application should be: obj1 equals obj1: true obj1 equals obj2: true obj1 equals obj3: false obj1 equals obj4: false Terminating, Baldwin =========================================================== */ class SampProg20{//controlling class int myData; SampProg20(int data){//constructor myData = data; }//end constructor public static void main(String[] args){ //instantiate objects for testing SampProg20 obj1 = new SampProg20(2); SampProg20 obj2 = new SampProg20(2); SampProg20 obj3 = new SampProg20(3); String obj4 = "A string object"; //Perform tests and report results System.out.println("obj1 equals obj1: " + obj1.equals(obj1)); System.out.println("obj1 equals obj2: " + obj1.equals(obj2)); System.out.println("obj1 equals obj3: " + obj1.equals(obj3)); System.out.println("obj1 equals obj4: " + obj1.equals(obj4)); System.out.println("Terminating, Baldwin"); }//end main //Overridden equals method public boolean equals(Object arg){ //Test for proper type and not null. If not null and // proper type, test for equivalent instance values. // Note the use of the instanceof operator. if( (arg != null) && (arg instanceof SampProg20)){ //if proper type and not null //downcast Object type to SampProg20 type SampProg20 temp = (SampProg20)arg; //compare and return result return (this.myData == temp.myData); }else return false;//not proper type, return false }//end method equals() }//end class definition for SampProg20 |
Q - Write a program that meets the following specification.
/*File SampProg21.java from lesson 44 Copyright 1997, R.G.Baldwin Without viewing the solution that follows, write a Java application that illustrates the use of the getClass() method from the Object class to determine the class of an object. Also illustrate the use of the getName() and getSuperClass() methods from the Class class. Also illustrate the use of the newInstance() method to instantiate a new object. Provide a termination method with your name. For a user input of 0, the output should be: Name of class for obj1: SampProg21 Name of superclass for obj1: class java.lang.Object Enter a 0 or a 1 0 Name of class for obj2: java.lang.String Name of superclass for obj2: class java.lang.Object Terminating, Baldwin For a user input of 1, the output should be: Name of class for obj1: SampProg21 Name of superclass for obj1: class java.lang.Object Enter a 0 or a 1 1 Name of class for obj2: SampProg21 Name of superclass for obj2: class java.lang.Object Terminating, Baldwin **********************************************************/ import java.io.*; class SampProg21{ public static void main(String[] args){ //instantiate an object of this type SampProg21 obj1 = new SampProg21(); //Use the getClass() method from the Object class and // two methods from the Class class to obtain and // display information about the object. System.out.println("Name of class for obj1: " + obj1.getClass().getName()); System.out.println("Name of superclass for obj1: " + obj1.getClass().getSuperclass()); //Now instantiate another object based on user input // such that the compiler cannot possibly know the // type of the object at compile time. Object obj2 = null;//reference to a generic object System.out.println( "Enter a 0 or a 1");//prompt for input try{ int inData = System.in.read();//get user input if( (char)inData == '0')//if user entered '0' //create a string object obj2 = "This is a String object"; else //create a new object of the type of another // object obj2 = obj1.getClass().newInstance(); }catch(Exception e){System.out.println( "Exception " + e);} //Now display the class and superclass of the // new object System.out.println("Name of class for obj2: " + obj2.getClass().getName()); System.out.println("Name of superclass for obj2: " + obj2.getClass().getSuperclass()); System.out.println("Terminating, Baldwin"); }//end main }//end class SampProg21 |
.Q - Write a program that meets the following specification.
/*File SampProg22.java from lesson 44 Copyright 1997, R.G.Baldwin Without viewing the solution that follows, write a Java application that illustrates overriding the toString() method of the Object class. The output from the program should be: Object contains: Richard G. Baldwin Terminating, Baldwin **********************************************************/ class SampProg22{ String first; //define instance variables for the class String middle; String last; SampProg22(String firstName, String middleName, String lastName) {//constructor first = firstName; middle = middleName; last = lastName; }//end constructor public static void main(String[] args){//main method SampProg22 obj = new SampProg22( "Richard","G.","Baldwin");//instantiate object //Display it using overridden toString() System.out.println(obj.toString()); System.out.println("Terminating, Baldwin"); }//end main method public String toString(){//Override toString() method //Convert object to a string and return it return ("Object contains: " + first + " " + middle + " " + last); }//end overridden toString method }//end SampProg22 class definition |
-end-