This series, The Object-Oriented Thought Process, is intended for someone just learning an object-oriented language and who wants to understand the basic concepts before jumping into the code, or someone who wants to understand the infrastructure behind an object-oriented language he or she is already using. These concepts are part of the foundation that any programmer will need to make the paradigm shift from procedural programming to object-oriented programming.
Figure 2. Cloning an Object
At this point, the graphical representation is in a logical state, the fido references and object with the name of fido and the spot references an object with the name of spot as seen in Diagram 2.
Diagram 2: An Object with two References and two Objects (different content)
Refining the Cloning Mechanism
In the previous example, you did the actual copy by brute force, meaning that, after you cloned the object, you called the setName() method directly to set the new name.
spot = (Dog) fido.clone(); spot.setName("spot");
Although this works, you can search for a more elegant way to accomplish this task. The obvious way to set the appropriate attributes is to send them over the clone() method’s parameter list.
spot = (Dog) fido.clone(String name, String breed);
Give this a try and see what actually happens. You will do this in a few steps because understanding the process by which the compiler operates here is quite educational. First, you will actually update the clone() method itself to include the parameters.
public Object clone(String n, String b) { try { return super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.toString()); } }
When you compile this code, you get the error message seen in Figure 3.
Figure 3: Error Message
Actually, this is entirely expected because the corresponding signature in the application does not match. You can take care of this by updating the application code.
spot = (Dog) fido.clone("spot", "mutt");
Although this compiles (as seen in Figure 4), note that the Object class does not have a clone() method with this signature. And, the name for the spot object somehow has reverted back to ‘fido’.
Figure 4: Application with Updated clone() Signature
After a brief inspection, it becomes obvious that there is nowhere in the clone() method that actually sets the attribute for the new object.
public Object clone(String n, String b) {
try {
// We just return the object reference . nothing else happens
return super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e.toString());
}
}
So, put in some code to set the attributes using the parameters provided.
public Object clone(String n, String b) {
try {
name = n;
breed = b;
return super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e.toString());
}
}
However, when you run the application, as in Figure 5, you now have both objects with name attributes ‘spot’.
Figure 5: Add Parameter Assignments to clone() Method
This might be confusing at first; however, take a look at the placement of the assignments. The assignments are, in fact, being made prior to the cloning itself. Thus, the change is being made to the original ‘fido’ object and then subsequently to the new ‘spot’ object. This is why both have the same name assignments.
Because the call to clone() is done within the actual return statement, you can’t update the new object at this point. One approach is to create a second reference and use that prior to returning the new object.
Dog ref;
...
public Object clone(String n, String b) {
try {
ref = (Dog) super.clone();
ref.setName(n);
ref.setBreed(b);
return ref;
} catch (CloneNotSupportedException e) {
throw new InternalError(e.toString());
}
}
When this is attempted, you get a compilation problem as seen in Figure 6. In fact, several of the potential solutions that you might come up with present various design problems. This realization leads you to the following conclusion: Even though there are many design variations that you could attempt in an effort to refine the clone() method to suit your purposes, they are all staring to get a bit complicated.
One of the programming mantras that I like to use is “keep things as simple as possible“. Thus, it is intuitively simpler to keep the clone() method as it is and then create another method to wrap the clone() method; this will provide the functionality that you want. Not only is this a simpler approach, but it also maintains the concept of reusability (and responsibly) because the clone() is only responsible for cloning the object as defined in the Object class documentation. Any refinement or added functionality is added in the more-specific wrapper class.
Figure 6: Problems with updating the clone() Method
The next step is to create the wrapper class itself.
Designing a Copy Mechanism
One design solution is to create a method in the Dog class called copy(). This makes more intuitive sense anyway, because you initially wanted to copy an object. Despite the fact that the term clone also makes sense, I will avoid using this term because the Object class actually has a clone() method. Create an entirely new method that is inherently part of your Dog class. In this way, there will be no confusion with the clone() method of the Object class.
In short, you will wrap the cloning process into a method that you create and call copy(). To accomplish this task, we need add only two components, an attribute to reference a Dog object and the copy() method itself.
Dog ref;
...
ref = (Dog) clone();
ref.setName(n);
return ref;
}
Note that in this strategy, the application will now call the copy() method. The clone() method is not called directly by the application; it is wrapped inside the copy() method. This approach allows you to leave the signature of the clone() method intact while allowing you to alter the attributes of the cloned object.
Note: Adding parameters to the clone() method creates a totally new method signature and does not override the original clone() method in the Object class.
The complete revision to the Dog class is presented in Listing 4.
// Class Dog class Dog implements Cloneable { String name; String breed; Dog ref; public Dog(String n, String b) { name = n; breed = b; } public Dog copy(String n, String b) { ref = (Dog)clone(); ref.setName(n); ref.setBreed(b); return ref; } public Object clone() { try { System.out.println("Inside clone"); return super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(e.toString()); } } public String getName() { return name; } public void setName(String n) { name = n; } public String getBreed() { return breed; } public void setBreed(String b) { breed = b; } }
Listing 4: Adding the copy() method to the Dog class
The change to the application itself is relatively minor. Instead of directly calling the clone() method, you now call the wrapper method copy().
spot = (Dog)fido.copy("spot", "Mutt");
The complete code listing for the application is presented in Listing 5. Note that you are passing parameters directly to the copy() method.
// Class Duplicate public class Duplicate { public static void main(String[] args) { Dog fido = new Dog("fido", "retriever"); Dog spot; spot = (Dog)fido.copy("spot", "mutt"); System.out.print("fido : = "); System.out.print("name = " + fido.getName()); System.out.println(", breed = " + fido.getBreed()); System.out.print("spot : = "); System.out.print("name = " + spot.getName()); System.out.println(", breed = " + spot.getBreed()); } }
Listing 5: Adding the copy() method to the application
As you can see in Figure 7, you now have two completely separate objects, and they have their own unique attributes.
Figure 7: Using the copy() method
Conclusion
In this article, you delved further into the concepts of cloning objects. Cloning implies that when an object is cloned, it is identical to the original object. Although this functionality is necessary, it is rare that you will actually need a second, identical object. In fact, in most cases, when you want to clone an object, you will most likely want to make specific changes to the new object.
In the examples presented here, you explore a technique to provide a copy mechanism for an object. One of the issues that you always need to be cognizant of is that objects are complex; they are not primitives like ints, doubles, and so forth. Thus, the copy process is not trivial and a certain amount of attention must be paid to the design of your methods.
Even though the objects you used in these examples are not complex objects (they did not contain other objects), you still needed to be careful about how you cloned and constructed them. In upcoming articles, you will explore how you deal with cloning objects that are much more complex.
References
About the Author
Matt Weisfeld is a faculty member at Cuyahoga Community College (Tri-C) in Cleveland, Ohio. Matt is a member of the Information Technology department, teaching programming languages such as C++, Java, C#, and .NET as well as various web technologies. Prior to joining Tri-C, Matt spent 20 years in the information technology industry, gaining experience in software development, project management, business development, corporate training, and part-time teaching. Matt holds an MS in computer science and an MBA in project management. Besides The Object-Oriented Thought Process, which is now in its second edition, Matt has published two other computer books, and more than a dozen articles in magazines and journals such as Dr. Dobb’s Journal, The C/C++ Users Journal, Software Development Magazine, Java Report, and the international journal Project Management. Matt has presented at conferences throughout the United States and Canada.