760 likes | 896 Views
Object-Oriented Programming (Java), Unit 14. Kirk Scott. The Methods toString(), equals(), and clone(). 14.1 toString() 14.2 equals() 14.3 What is Cloning? 14.4 Using the System Supplied clone() Method 14.5 Overriding the clone() Method for Objects without References
E N D
Object-Oriented Programming (Java), Unit 14 Kirk Scott
The Methods toString(), equals(), and clone() • 14.1 toString() • 14.2 equals() • 14.3 What is Cloning? • 14.4 Using the System Supplied clone() Method • 14.5 Overriding the clone() Method for Objects without References • 14.6 Overriding the clone() Method for Objects with References • 14.7 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.
14.1.2 toString() is Overridden in System Supplied Classes • Here is some code using the toString() method on a system supplied object: • Point mypoint = new Point(10, 20); • String mypointString = mypoint.toString(); • myterminal.println(mypointString); • This is the result: • java.awt.Point[x=10,y=20]
14.1.3 The Inherited Version of toString() is not very Useful • Here is some code using the toString() method on a Food object: • PackagedFoodV6 myfood = new • PackagedFoodV6("Ace", "Peas", "grams", 250.0, 1.59); • String myfoodString = myfood.toString(); • myterminal.println(myfoodString); • This is the result: • PackagedFoodV6@720eeb • This shows the object's hash code, a unique identifier which may be based on its memory address.
14.1.4 The Method println() Depends on toString() • Here is code using the println() method: • Point mypoint = new Point(10, 20); • myterminal.println(mypoint); • PackagedFoodV6 myfood = new • PackagedFoodV6("Ace", "Peas", "grams", 250.0, 1.59); • myterminal.println(myfood); • Here are the results: • java.awt.Point[x=10,y=20] • PackagedFoodV6@720eeb
14.1.5 String Concatenation Depends on toString() • Here is code using String concatenation: • String mystring; • mystring = “xyz”; • mystring = mystring + myfood; • myterminal.println(mystring); • This is the result: • xyzPackagedFoodV6@720eeb
14.1.6 It is Desirable to Override toString() • toString() should be overridden because println() and concatenation depend on it. • It should show the name of the class of the object. • It should show all of its instance variables and their values.
14.1.7 Example Code for Implementing toString() • Here is a complete implementation of the toString() method for the PackagedFoodV6 class. • Notice that get methods have to be called in order to retrieve the values of inherited instance variables. • public String toString() • { • String returnString = • "PackagedFoodV6[brand=" + getBrand() • + ", name=" + getProductName() • + ", units=" + getUnits() • + ", size=" + size • + ", itemCost=" + itemCost + "]"; • return returnString; • }
14.1.8 The Implementation of toString() Does Not Use super • Using super would eliminate the need to call get methods for inherited instance variables. • The names of all superclasses would be nested in the results. • The values of instance variables would be nested in square brackets in the results. • Such results would not conform to the standard.
14.2.1 The Inherited equals() Method Tests for Equality of Reference • The equals() method inherited from the Object class implements a test of equality of reference. • The equals() method in the Object class works by comparing hash code values. • It is overridden in system supplied classes by an implementation that tests equality of contents.
14.2.2 Overriding the equals() Method to Test for Equality of Contents • This is the signature of the equals() method in the Object class: • public boolean equals(Object obj) • When overriding, only the name of the formal parameter can be changed. In the PackagedFoodV6 class for example: • public boolean equals(Object anotherPackagedFood)
14.2.3 First Check that Objects are of the Same Class; Then Check the Contents • The explicit parameter is typed Object. Use instanceof to see whether the desired subclass reference can be recovered from this superclass reference. • If so, recover the subclass reference and check for equality of contents of the instance variables. Use get methods to access the inherited instance variables.
14.2.4 Example Code • Here is an example of an implementation for the PackagedFoodV6 class: • public boolean equals(Object anotherPackagedFood) • { • if(anotherPackagedFood instanceof PackagedFoodV6) • { • PackagedFoodV6 that = • (PackagedFoodV6) • anotherPackagedFood;
if(this.getBrand().equals(that.getBrand()) • && • this.getProductName().equals(that.getProductName()) • && this.getUnits().equals(that.getUnits()) • && this.size == that.size • && this.itemCost == that.itemCost) • return true; • else • return false; • } • else • return false; • }
14.3.1 Cloning is Copying Objects (Not Copying References) • It is possible to use assignment to have two references to the same object. • Cloning refers to constructing a second, distinct object with the same contents as the first.
14.3.2 Cloning is Complicated When Objects Contain References • Cloning becomes complex when objects contain instance variables which are references to other objects. • String instance variables avoid the problem because they are immutable. • The ShippingBox class given below contains a reference to a (mutable) PackagedFoodV6 object. This class will be used to illustrate cloning.
Example Code • public class ShippingBox • { • private PackagedFoodV6 aFood; • private int numberOfPackages; • public ShippingBox(PackagedFoodV6 aFoodIn, • int numberOfPackagesIn) • { • aFood = aFoodIn; • numberOfPackages = numberOfPackagesIn; • } • }
14.3.3 Copying a Reference is not Cloning • This code illustrates the construction of an instance of the ShippingBox class: • PackagedFoodV6 myfood = new PackagedFoodV6 • ("Ace", "Peas", "grams", 250.0, 1.59); • ShippingBox mybox = new ShippingBox(myfood, 12); • The following line of code makes a copy of the reference mybox: • ShippingBox theirbox = mybox; • mybox and theirbox are simply two references to the same object. They are not clones. A diagram follows.
mybox ShippingBox object PackagedFoodV6 object (myfood) theirbox Diagram of situation:
14.3.4 Cloning Involves the Construction of a Separate Object • Cloning can be accomplished without a clone() method. • In the code below, yourbox is a clone of mybox. It is a separate object which is constructed to contain the same values. • ShippingBox yourbox = new ShippingBox(myfood, 12);
mybox ShippingBox object PackagedFoodV6 object (myfood) ShippingBox object yourbox 14.3.5 In a Shallow Copy, Two Objects Refer to the Same Object • The following diagram illustrates the results of the previous construction:
A shallow copy, continued • The two objects mybox and yourbox are distinct, but they share a reference to the same object, myfood. • In a literal sense, mybox and yourbox do have the same contents. However, the fact that they each have a reference to the same object, myfood, is undesirable. • The two instances of ShippingBox are not independent of each other because they share this reference. In a situation like this yourbox is called a shallow copy of mybox. • A shallow copy is not generally regarded as a complete clone.
14.3.6 In a Deep Copy, Object References are Cloned Recursively • Assume that myfood and mybox have been created as shown above. • Then create a copy of myfood, called yourfood. • Then create yourbox using yourfood as an input parameter. • PackagedFoodV6 yourfood = new • PackagedFoodV6("Ace", "Peas", "grams", 250.0, 1.59); • ShippingBox yourbox = new ShippingBox(yourfood, 12);
mybox ShippingBox object PackagedFoodV6 object (myfood) ShippingBox object PackagedFoodV6 object (yourfood) yourbox A diagram of a clone • The results of the previous code are two separate references to two different ShippingBox objects. • Each of these objects contains a reference to separate PackagedFoodV6 objects which have the same contents.
14.3.7 Successful Clones Should Test Equal for Contents, not for Reference • For successful cloning, mybox.equals(yourbox) should test equal for contents. • In order for this to happen, myfood.equals(yourfood) should also test equal for contents.
14.4.1 The clone() Method in the Object Class • There is a clone() method in the Object class. • This clone() method is declared protected. • This clone() method is not freely inherited by subclasses. • The method is only inherited if the subclass implements the Cloneable interface. • This interface does not specify methods that the implementing class has to have. It simply grants access to the clone() method in the Object class.
14.4.2 The getClass() Method • The getClass() method is introduced here. • Next, the API documentation for the clone() method in the Object class will be shown. • The documentation of the getClass() method is shown first because it appears in the clone() documentation.
Here is the API documentation for the getClass() method: • getClass • public final ClassgetClass() Returns the runtime class of an object. • Just as the instanceof operator allowed you to test whether an object reference was of a given class, this method allows you to find out at run time what class an object reference belongs to.
14.4.3 Documentation of the clone() Method • It is the clone() method in the Object class that is of particular interest. Here is its API documentation: • clone • protected Objectclone() • throws cloneNotSupportedException • Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression: • x.clone() != x will be true, and that the expression: • x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
While it is typically the case that: • x.clone().equals(x) will be true, this is not an absolute requirement. • Copying an object will typically entail creating a new instance of its class, but it also may require copying of internal data structures as well. • No constructors are called.
14.4.4 The Inherited clone() Method Makes Shallow Copies • There is a lot of waffling in the API documentation. • If you ignore the occurrences of “not absolute requirements” and “not an absolute requirement” you get some idea of what the cloning method is intended to do. • It is supposed to return an object that is distinct from the original, but which tests positive for equality of contents.
A superclass cannot “know” in advance what kinds of references subclass objects may contain, so at the Object class level the clone() method makes simple copies of the values of instance variables contained in objects. • If the instance variables happen to be object references it cannot make clones of them. • In other words, the clone() method inherited from the Object class makes shallow copies.
14.4.5 The Meaning of the Access Modifer “Protected” • The inherited clone() method is declared protected. • It is possible to call this method within any other method defined in the same class. • It is also possible to call this method within methods that belong to classes that inherit from this class.
14.4.6 To Inherit the clone() Method, the Cloneable Interface has to be Implemented • Classes below the Object class do not inherit the clone() method from it by default. • A class can only inherit the clone() method from the Object class if the given class implements the Cloneable interface. • This interface does not specify any methods itself. It does cause the inheritance of the clone() method from the Object class. • Programmer written classes can only inherit and use the clone() method defined in the Object class if they implement the Cloneable interface. • The method they inherit is protected. • An attempt to call a clone() method on an object whose class does not implement the Cloneable interface will cause an exception to be thrown.
14.4.7 Summary of the Previous Points • The inherited clone() method only makes shallow copies. • If shallow copying is sufficient, the clone() method can be inherited by implementing the Cloneable interface. • The inherited clone() method is declared protected.
14.4.8 An Example of Implementing the Interface • For the purposes of illustration, the discussion so far has been in terms of the ShippingBox class, which contains a mutable object reference. • That is the case where the inherited clone() method is not ideal.
The syntax for inheriting the clone() method will be illustrated using the PackagedFoodV6 class, which contains an immutable reference, to a String. • This is an example of the case where it is sufficient to inherit a clone() method which only makes a shallow copy.
Let the declaration of the PackagedFoodV6 class be modified so that it implements the Cloneable interface: • public class PackagedFoodV6 extends FoodV6 implements Cloneable • This is all that’s necessary.
14.4.9 Testing the Inherited Method • Implementing the interface is all that needs to be done in order to inherit the clone() method. • The declaration asserts that it is OK to simply use the inherited clone() method to make copies of objects of this class. • Since the class does not contain mutable references, this is completely acceptable.
The inherited method is declared protected, so clone() can only be used within the PackagedFoodV6 class or its subclasses. • You could include a method such as the following in the PackagedFoodV6 class for the simple purpose of testing this approach:
public void justChecking() • { • PackagedFoodV6 myfood = new PackagedFoodV6("jkl", "uio", "hjk", 5, 4); • try • { • PackagedFoodV6 newFood = (PackagedFoodV6) myfood.clone(); • } • catch(CloneNotSupportedException exception) • { • System.out.println("CloneNotSupportedException caught."); • } • }
14.4.10 Details of Using the Inherited Method • The justChecking() method illustrates two aspects of the use of the clone() method that need further explanation. Take a look at this line of code in the body of the method: • PackagedFoodV6 newfood = (PackagedFoodV6) myfood.clone(); • The reference returned from the call to clone() is cast to the PackagedFoodV6 class. This is necessary because the clone() method in the Object class, which is being inherited, returns an Object class reference. • The declaration of the clone() method in the API documentation is shown again below as a reminder. • protected Object clone()
The other aspect of the example that should immediately stand out are the try and catch blocks: • try • { • … • } • catch(ClassName parameter) • { • … • } • Requiring a class to implement the Cloneable interface in order to inherit the method is a mechanism that forces the programmer to acknowledge that the clone() method makes shallow copies and is protected. • A call to the clone() method will throw an exception if the class doesn’t implement the Cloneable interface.