1 / 116

Java Unit 14 The Methods toString (), equals(), and clone()

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

junius
Télécharger la présentation

Java Unit 14 The Methods toString (), equals(), and clone()

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Java Unit 14The Methods toString(), equals(), and clone()

  2. 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

  3. 14.1 toString()

  4. 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. At each stage, if the example is based on the food hierarchy, the class reflecting the current changes will be named version 6. • At the end of the unit, the V6 naming convention will apply to the classes of the food hierarchy containing the last, most desirable implementation decisions on the topics covered in the unit.

  5. 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 superclass and a subclass where objects can be constructed of both so that the results of calls on both can be shown. • If the FoodV6 class remained abstract, it would still be possible to write a toString(), equals(), or clone() method for it. • They 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.

  6. 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. • 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); • For a class where the method has been overridden 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]

  7. 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. • Here is some code using the toString() method on an object of a class defined in the last chapter, where the method has not been overridden: • FoodV6 myfood = new FoodV6("Ace", "Peas", "grams"); • String myfoodString = myfood.toString(); • myterminal.println(myfoodString); • This is the output: • FoodV6@720eeb

  8. For the programmer supplied class where the method implementation inherited from the Object class has not been overridden, the name of the class is given, followed by an @ sign, followed by digits and letters. • The digits and letters form a hexadecimal integer which is referred to as a hash code. • In effect, this is a system supplied, unique identifier for a given object during any program run. • The hash code value may differ from run to run. • Although in Java there is no direct memory access, the system may derive such a unique hash code from the address of the object, since the location of an object will not change during the course of a single program run.

  9. 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(). It calls this method when printing an object, whether the method has been overridden or not.

  10. 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 mystring and whatever is returned by a call to toString() on the reference myfood.

  11. 14.1.7 It is Desirable to Override toString() • There are several things to be gleaned from these examples. • It is generally 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 have no need to call toString() in your code, 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.

  12. 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; • }

  13. 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. • Here is such a method for the PackagedFoodV6 class. • public String toString() • { • String returnString = • "PackagedFoodV6[brand=" + getBrand() • + ", name=" + getProductName() • + ", units=" + getUnits() • + ", size=" + size • + ", itemCost=" + itemCost + "]"; • return returnString; • }

  14. 14.1.10 The getClass() Method and the getName() Method • Objects of any class inherit the getClass() method from the Object class. • 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. • The getClass() method can be used when writing a toString() method. • It will also be used again later on in this unit. • Here is the API documentation for the getClass() method:

  15. 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. • This can be used with the getClass() method when writing a toString() method.

  16. 14.1.11 Using getClass(), getName(), and super in toString() • The toString() method in the FoodV6 class could be rewritten as shown below. • 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; • }

  17. 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; • }

  18. The call to toString() in the subclass would give output with two pairs of square brackets, one containing the instance variables inherited from the superclass, the other containing the instance variables defined locally in the subclass. • In other words, it would take the following form. • PackagedFoodV6[superclass instance variables][subclass instance variables] • The thing to note is that the implicit parameter in the call to super would be a PackagedFoodV6 object, and dynamic binding means that the call to getClass().getName() in the superclasstoString() method at runtime would produce a result of PackagedFoodV6, not FoodV6. This is what you see in the output.

  19. 14.1.12 How Not to Use super in toString() • In a subclass implementation of toString() it would be syntactically possible to call super, followed by either the hardcoded name of the subclass or a call to getClass().getName(), followed by the concatenation of the instance variable names and values for the subclass. • In other words, it would show full inheritance information. The output would take the form shown below. • SuperClass[superclass instance variables]SubClass[subclass instance variables] • This approach is not recommended because the output of the toString() method is not in the customary or expected form. • From a practical point of view, if the inheritance hierarchy is deep, the output would be excessively long.

  20. 14.2 equals()

  21. 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.

  22. 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)

  23. 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)

  24. 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.

  25. 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.

  26. 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.

  27. 14.2.4 Example Code for the equals() Method • A simple, complete implementation of the equals() method for the FoodV6 class might take the following form. • 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; • }

  28. Just like with the toString() method, the equals() method is made more general by not hardcoding the class of interest. • This is accomplished by using the getClass() method instead of the instanceof operator. • By itself, this equals() method would work if you used this line of code: • if(anotherFoodinstanceof FoodV6) • However, as will be shown in a later example, in order to successfully call the equals() method from a subclass, it's necessary to use this line of code instead: • if(this.getClass() == anotherFood.getClass())

  29. 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:

  30. 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)); • }

  31. 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.

  32. 14.2.6 The equals() Method in a Subclass • Here 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.

  33. public boolean equals(Object anotherPackagedFood) • { • if(this.getClass() == anotherPackagedFood.getClass()) • { • 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; • }

  34. 14.2.7 Making Use of super in the equals() Method of the Subclass • By using the getClass() method in the equals() method implementations, it becomes possible to use the superclass equals() method when writing the code for the subclass equals() method. • Here is the PackagedFoodV6 example rewritten using this approach. • Notice that the method is shorter than the previous method and that it avoids having to use the inherited get methods in order to access inherited instance variables.

  35. public boolean equals(Object anotherPackagedFood) • { • if(super.equals(anotherPackagedFood)) • { • PackagedFoodV6 that = (PackagedFoodV6) anotherPackagedFood; • if(this.size == that.size • && this.itemCost == that.itemCost) • return true; • else • return false; • } • else • return false; • }

  36. The call to super.equals() checks for two things: whether anotherPackagedFood is of the right class, and whether the contents of the instance variables defined in the FoodV6 class are the same. • The superclass equals() method is able to check that anotherPackagedFood is in fact an instance of the PackagedFoodV6 class, which would agree with the implicit parameter, this, because the superclass equals() method was coded with the getClass() method. • When the superclass equals() method is run on the implicit parameter of the subclass method, dynamic binding means that getClass() will return "PackagedFoodV6" (assuming that the subclass method was called on such an object). • Likewise, when the superclass equals() method is run on the explicit parameter, anotherPackagedFood, dynamic binding means that getClass() will return "PackagedFoodV6" (assuming that the subclass method explicit parameter was called on such an object).

  37. 14.3 What is Cloning?

  38. 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.

  39. 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.

  40. Example Code • public class ShippingBox • { • private PackagedFoodV6 aFood; • private int numberOfPackages; • public ShippingBox(PackagedFoodV6 aFoodIn, • int numberOfPackagesIn) • { • aFood = aFoodIn; • numberOfPackages = numberOfPackagesIn; • } • }

  41. 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.

  42. mybox ShippingBox object PackagedFoodV6 object (myfood) theirbox Diagram of situation:

  43. 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);

  44. 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:

  45. 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.

  46. 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);

  47. 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.

  48. 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.

  49. 14.4 Handling and Throwing Exceptions • The topic of handling and throwing exceptions is raised here because it will be relevant to the implementation of the clone() method. • At some point earlier in learning Java you have probably encountered the idea of handling exceptions. • If you make a call to certain kinds of system supplied methods, they throw checked exceptions. • The term "checked" can be interpreted to mean that the compiler checks to see whether your code handles the exception. • In other words, your code will be correct if the call that throws the exception is made in a try block, and there is a corresponding catch block. • It is in the catch block that you presumably do something appropriate to the case where the exception is thrown.

  50. There is an alternative to handling exceptions as or where they are thrown. • The method you're writing, which contains the call that throws an exception, can itself be declared to throw that kind of exception. • By making the declaration and not handling the exception in a try/catch block in your code, what you are specifying is the following: If your method is called, and the call within the method throws an exception, your method will pass the exception along. • As a result, your method has to be called in a try/catch block. • The code that calls your method then has the choice of handling the exception or passing it along. • Passing the buck in this way can go on until there is nowhere else to pass it to—in other words, until you reach a user program with a main() method. • Passing the buck can also end earlier—at that point in a set of methods which call each other where it seems desirable to handle the exception.

More Related