http://www.developer.com/

Back to article

Object Integrity & Security: Duplicating Objects: Part 2


June 11, 2007

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.

Click here to start at the beginning of the series.

In keeping with the code examples used in the previous articles, Java will be the language used to implement the concepts in code. One of the reasons that I like to use Java is because you can download the Java compiler for personal use at the Sun Microsystems Web site http://java.sun.com/. You can download the standard edition, J2SE 5.0, at http://java.sun.com/j2se/1.5.0/download.jsp to compile and execute these applications. I often reference the Java J2SE 5.0 API documentation and I recommend that you explore the Java API further. Code listings are provided for all examples in this article as well as figures and output (when appropriate). See the first article in this series for detailed descriptions for compiling and running all the code examples.

The code examples in this series are meant to be a hands-on experience. There are many code listings and figures of the output produced from these code examples. Please boot up your computer and run these exercises as you read through the text.

For the past several months, my articles have been exploring various issues regarding object integrity, security, and performance. In this month's article, you continue with this theme and begin a discussion on how objects are duplicated. This is not a trivial issue. Whereas copying primitives such as numbers is mostly straightforward, copying objects is a more complicated task. Completely duplicating an object is a multi-step process.

Cloning Objects

Please review the final step in the process in cloning objects that you covered in the previous article.

The object class in Java provides a method called clone() that performs the physical copy of an object. Taking a look at the Java documentation, you see the definition for clone() (remember that all Java objects inherit from the object class).

java.lang Class Object

java.lang.Object

public class Object

Class Object is the root of the class hierarchy. Every class has Object as a superclass. All objects, including arrays, implement the methods of this class.

Since:

JDK1.0

See Also:

Class

Method Summary
protected Object
clone()

Creates and returns a copy of this object.

http://java.sun.com/j2se/1.5.0/docs/api/index.html

In Listing 1, pay special attention to the fact that the Dog class implements the Clonable interface, and that you must make provisions to catch the CloneNotSupportedException exception. For a more detailed description of this listing, you can reference last month's article: http://www.developer.com/security/article.php/3675326.

// Class Dog
class Dog implements Cloneable {

   String name;
   String breed;

   public Dog(String n, String b) {

      name  = n;
      breed = b;
   }

   public Object clone() {
      try {
         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;

   }

}

Listing 1: The Dog Class with the clone() Override

Also note the casting as it appears in Listing 2. When the clone() method returns the object, the object in this case is of type Dog. Thus, you need to cast the return to the type Dog.

// Class Duplicate
public class Duplicate {

   public static void main(String[] args) {

      Dog fido = new Dog("fido", "retriever");

      Dog spot;

      spot = (Dog) fido.clone();

      System.out.println("name = " + fido.getName());
      System.out.println("name = " + spot.getName());


   }
}

Listing 2: The Duplicate Class with the clone() method

After compiling and running this application, you can run the code that produces the output in Figure 1.

Figure 1: Cloning an Object

The important concept here is that there are now two totally separate objects. You can represent this graphically to illustrate how the objects are represented in memory. Diagram 1 shows that in this case you not only have two references, you also have two separate objects. This was not the case when you simply created a reference and pointed the reference to the original object memory.

Diagram 1: An Object with two References and two Objects (same content)

Cloning Inconsistencies

When you look at Diagram 1, you may notice one inconsistency that I pointed out in the previous article, the spot object still has the name defined as fido. In fact, the output in Figure 1 confirms this. This makes sense because you did clone the object, and this implies that all of the information is duplicated. Given this fact, how do you prove that the objects are indeed separate? Change the value of one, but not the other, as seen in Listing 3. Logically, you need to change the name of the spot object to "spot". You use the setName() method.

// Class Duplicate
public class Duplicate {

   public static void main(String[] args) {

      Dog fido = new Dog("fido", "retriever");

      Dog spot;

      spot = (Dog) fido.clone();

      spot.setName("spot");

      System.out.println("name = " + fido.getName());
      System.out.println("name = " + spot.getName());


   }
}

Listing 3: Change the name for the spot reference

When you execute this application, as seen in Figure 2, you now get the output you expect.



Click here for a larger image.

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.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date