1.72k likes | 1.89k Views
Java Unit 14 The Methods toString (), equals(), and clone(). 14.1 toString () 14.2 equals() 14.3 What is Cloning? 14.4 Handling and Throwing Exceptions 14.5 The clone() Method in the Object Class and the Cloneable Interface
E N D
14.1 toString() • 14.2 equals() • 14.3 What is Cloning? • 14.4 Handling and Throwing Exceptions • 14.5 The clone() Method in the Object Class and the Cloneable Interface • 14.6 Overriding the clone() Method for Objects without References • 14.7 Overriding the clone() Method for Objects with a Reference • 14.8 The Importance of Cloning
14.1.1 Introduction • Programmer written classes inherit three important methods from the Object class, toString(), equals(), and clone(). • Whether to override them and how to do so correctly are the topics of this unit. • A progression of things will be shown and the food hiearchy class designs will change piece by piece.
The version number for the food hierarchy will remain V6 for the whole unit. • At the end of the unit, V6 will refer to the classes containing the preferred implementations of the topics covered in the unit.
14.1.2 For the Purposes of the Discussions in this Unit, Assume that FoodV6 is Not Abstract • For the purposes of illustrating the topics of this unit it's useful to have a concrete superclassand subclass. • That way instances of each can be constructed and methods can be called on them. • At the end of the last unit, the final version of the Food class was abstract. • In this unit the Food class will be concrete.
If the FoodV6 class remained abstract, it would still be possible to write a toString(), equals(), or clone() method for it. • The methods would still be useful because they could be called from a subclass by means of the keyword super. • However, it would not be possible to construct a FoodV6 object and call these methods on it.
14.1.3 toString() is Overridden in System Supplied Classes • The toString() method returns a string containing information about an object. • The method is overridden in system supplied classes and it is usually desirable to do the same in a programmer written class. • On the following overhead code is shown where the toString() method is called on a system supplied object:
Point myPoint = new Point(10, 20); • String myPointString = myPoint.toString(); • myterminal.println(myPointString);
For a class where the method has been overridden this is what you get: • The name of the class followed by square brackets containing the names and values of the instance variables of the object. • java.awt.Point[x=10,y=20]
14.1.4 The Inherited Version of toString() is not very Useful • The version of the method inherited from the Object class does not produce very useful output. • On the following overhead code is shown where the toString() method is called on an object where the method has not been overridden:
FoodV6 myFood = new FoodV6("Ace", "Peas", "grams"); • String myFoodString = myFood.toString(); • myterminal.println(myFoodString);
For a class where the method has been overridden this is what you get: • The name of the class followed by an @ sign, followed by digits and letters. • FoodV6@720eeb
The digits and letters form a hexadecimal integer which is referred to as a hash code. • The hash code is a system supplied, unique identifier for a given object during any program run. • The hash code value may differ from run to run.
It is worth recalling that a Java program does not have direct access to memory. • It only has references to objects, and the system takes care of their location in memory. • The location of an object in memory will not change during the course of a program run. • This means that the system may derive a hash code from an object’s address
14.1.5 The Method println() Depends on toString() • It is possible to get the same output without expressly calling toString(). Consider the following code: • Point myPoint= new Point(10, 20); • myterminal.println(myPoint); • FoodV6 myFood= new FoodV6("Ace", "Peas", "grams"); • myterminal.println(myFood);
Here are the results: • java.awt.Point[x=10,y=20] • FoodV6@720eeb • This happens because the implementation of the method println() depends on toString(). • println() internally calls toString() when printing an object, whether toString() has been overridden or not.
14.1.6 String Concatenation Depends on toString() • The toString() method is also used by the system to support the concatenation of strings and objects. • The following is valid code: • String myString; • myString= “xyz”; • myString= myString+ myFood; • The result of this code is the concatenation of the contents of myStringand whatever is returned by a call to toString() on the reference myFood.
14.1.7 It is Desirable to Override toString() • There are several things to be gleaned from these examples. • It is more useful for toString() to return the name of the class followed by instance variables and their values rather than a hash code. • Even if you don’t call toString()directly, its implementation is important because of its use by println() and in concatenation. • This means that every programmer written class should override the toString()method.
Several more observations can be made about toString() that relate to how it should be implemented. • Notice that even though Point is a subclass in a hierarchy, its parent classes are not shown as part of the return value from toString(). • For a system supplied class its package is shown, but this will not be an issue for us since we are not storing our classes in packages.
14.1.8 Example Code for Implementing toString() • Here is a simple, complete implementation of the toString() method for the FoodV6 class. • public String toString() • { • String returnString = • "FoodV6[brand=" + brand • + ", name=" + name • + ", units=" units + "]"; • return returnString; • }
14.1.9 The Implementation of toString() in Subclasses • You can write the same simple kind of toString() method for a subclass. • Notice that if you do this you will have to call get methods in order to retrieve the values of inherited instance variables. • On the following overhead a simple implementation of toString() us shown for the PackagedFoodV6 class.
public String toString() • { • String returnString = • "PackagedFoodV6[brand=" + getBrand() • + ", name=" + getProductName() • + ", units=" + getUnits() • + ", size=" + size • + ", itemCost=" + itemCost + "]"; • return returnString; • }
14.1.10 The getClass() Method and the getName() Method • Objects of any class inherit the getClass() method from the Object class. • The instanceof operator allowed you to test whether an object reference was of a given class. • getClass() allows you to find out at run time what class an object reference belongs to. • The getClass() method can be used when writing a toString() method. • It will also be used again later on in this unit. • The API documentation for the getClass() method is shown on the next overhead:
getClass • public final ClassgetClass() Returns the runtime class of an object. • As you can see from the documentation, a call to getClass() returns a reference to an instance of the Class class. • The Class class contains a method getName() which returns a String consisting of the name of the class of the Class object it's called on.
14.1.11 Using getClass(), getName(), and super in toString() • getClass() and getName() can be used when writing a toString() method. • The toString() method in the FoodV6 class could be rewritten as shown on the next overhead. • It may seem a little elaborate to rely on the system to give the class name rather than hardcoding, but the benefit becomes apparent when considering how to write a toString() method for a subclass.
public String toString() • { • String returnString = • getClass().getName() • + [brand=" + brand • + ", name=" + name • + ", units=" units + "]"; • return returnString; • }
If the toString() method for FoodV6 is written as shown above, then it is possible to write the toString() method for its subclass, PackagedFoodV6 as shown below. • public String toString() • { • String returnString = • super.toString() • + ", size=" + size • + ", itemCost=" + itemCost + "]"; • return returnString; • }
The call to toString() in the subclass would give output with two pairs of square brackets. • The first pair would contain the instance variables inherited from the superclass. • The other pair would contain the instance variables defined locally in the subclass. • In other words, this version of toString() would return something of this form. • PackagedFoodV6[superclass instance variables][subclass instance variables]
Recall that this is an implementation in a subclass • The implicit parameter in the call to super would be a PackagedFoodV6 object • Dynamic binding means that the call to getClass().getName() in the superclasstoString() method at runtime would produce a result of PackagedFoodV6, not FoodV6. • Thus, the output would be correct would still be correct for the subclass, showing the subclass name, not the superclass name.
14.1.12 How Not to Use super in toString() • Suppose in the superclass you hardcoded the superclass name in the toString() method • You didn’t use use getName().getClass() • Suppose in the subclass toString() method you first called super.toString() • Then you hardcoded the subclass name in the subclass toString() method • Then you took care of the subclass instance variables and their values
The return value for the subclass toString() method would show full inheritance information. • The output would take the form shown below. • SuperClass[superclass instance variables]SubClass[subclass instance variables] • There is nothing wrong with this, but it’s not recommended because this not the expected output of the toString() method. • From a practical point of view, if the inheritance hierarchy is deep, the output would be excessively long.
14.2.1 The Inherited equals() Method Tests for Equality of Reference • You have encountered the equals() method when doing comparisons for ifs and loops. • The equals() method is provided in the Object class. • It implements a test of equality of reference and is inherited by programmer written classes. • It is overridden in system supplied classes by an implementation that tests equality of contents.
The equals() method in the Object class works by making use of the hash codes mentioned in the previous section. • The hash code is an object’s unique, system supplied identifier, which does not change during the course of a program run. • The system tests for equality of reference by testing for equality of hash codes. • If the hash codes are the same, then the references must be to the same object.
14.2.2 Overriding the equals() Method to Test for Equality of Contents • The goal now is to override the equals() method so that it tests equality of contents, not equality of reference, for programmer written classes. • With a certain amount of knowledge about inheritance, this is not hard to do. • In general when overriding a method, the type of the method, the method name, and the parameter list of the new implementation have to be the same as those in the original.
In the API documentation you will find the following information for the equals() method: • public boolean equals(Object obj)
This means that with the exception of the name of the formal parameter, obj, which can be changed, the declaration in the class where this is being overridden has to be the same. • In the method declaration shown below, the name of the explicit parameter has no effect, but it indicates the case we want to deal with: • public boolean equals(Object anotherFood)
The implicit parameter of a call to this method will be a FoodV6 object. • In order for equality to be possible, the explicit parameter should also be a reference to a FoodV6. • However, the explicit parameter has to be typed as an Object reference to agree with the method we’re overriding.
In spite of its name, the formal parameter anotherFood is an Object reference. • If the actual parameter passed during a call is a FoodV6 reference, then the formal parameter anotherFood is a superclass reference. • As pointed out in the previous unit, superclass references to subclass objects are OK. • It is possible to recover subclass references from superclass references.
14.2.3 First Check that Objects are of the Same Class; Then Check the Contents • Equality only makes sense if the objects being compared are of the same class. • If they are, then their contents can be safely compared. • Because the explicit parameter is typed Object, and the Object class is at the top of the inheritance hierarchy, there is nothing syntactically preventing a user from making a call with an explicit parameter of any class.
A subclass reference can be recovered by casting, but a run time error results if a cast is done on a reference that is not of that class. • Therefore, a correct implementation of the equals() method will start by testing whether or not the explicit parameter is of the right class.
Comparing the contents of objects requires getting the values of instance variables. • Since a superclass reference does not have direct access to instance variables or methods in its subclasses, it is necessary to recover the subclass reference of the explicit parameter. • Once the subclass reference has been successfully recovered, the test of equality of contents can be implemented.
In summary, an implementation of equals() involves two things. • Checking to make sure that the explicit parameter is of the right type and recovering the subclass reference. • Then testing for equality of the instance variables. • On the next overhead a simple, complete implementation of the equals() method for the FoodV6 class is shown.
14.2.4 Example Code for the equals() Method • public boolean equals(Object anotherFood) • { • if(this.getClass() == anotherFood.getClass()) • { • FoodV6 that = (FoodV6) anotherFood; • if(this.brand.equals(that.brand) • && this.productName.equals(that.productName) • && this.units.equals(that.units)) • return true; • else • return false; • } • else • return false; • }
Just like with the toString() method, the equals() method is made more general by not hardcoding the class of interest. • By itself, the previous equals() method would work if the following if statement were substituted for the if statement shown • if(anotherFoodinstanceof FoodV6) • However, using the getClass() method in the implementation of this superclass equals() method will make for more flexible coding of the equals method in subclasses
14.2.5 An Alternative Approach to Writing the equals() Method • If you look in the book by Horstmann and Cornell, they suggest an alternative version of the code that accomplishes the same thing. • Written following their example, the equals() method for the FoodV6 class might look something like this:
public boolean equals(Object anotherFood) • { • if(this == anotherFood) • return true; • if(anotherFood == null) • return false; • if(this.getClass() != anotherFood.getClass()) • return false; • FoodV6 that = (FoodV6) anotherFood; • return (brand.equals(that.brand) • && productName.equals(that.brand) • && units.equals(that.units)); • }
This approach may be advantageous in the sense that it identifies specific cases which would test true or false for equality. • One might also argue that in the case where the objects are the same, the work of comparing the contents is saved. • On the other hand, standalone if statements with their own returns is not necessarily the most desirable coding style, and the savings are minimal.
14.2.6 The equals() Method in a Subclass • Shown on the next overhead is a simple, complete implementation of the equals() method for the subclass PackagedFoodV6. • It does not make use of the equals() method in the FoodV6 class. • In this example, an instance of the PackagedFoodV6 class will have inherited instance variables. • As usual, in order to get access to them so that the test for equality of contents can be performed, it is necessary to use the inherited get methods for those instance variables.