|
|
|
Sponsored Link •
|
|
Advertisement
|
Although the previous chapter covered object initialization in great detail, it didn't quite cover all
ways to initialize objects in Java, because it didn't cover all ways to create objects in Java.
Aside from the new operator, which was the focus of the last chapter, Java offers two
other ways to create objects: clone(), which is described in this chapter, and
newInstance(), which is described in Part IV.
The newInstance() method, a member of class Class, is
most often used in the context of class loaders and dynamic program extension. The
clone() method, a member of class Object, is Java's
mechanism for making copies of objects.
clone() MethodIn Java, the way to make an identical copy of an object is to invoke clone() on
that object. When you invoke clone(), it should either:
Object reference to a copy of the object upon which it is invoked, or
CloneNotSupportedException
Because clone() is declared in class Object, it is inherited
by every Java object. Object's implementation of clone() does one of two things,
depending upon whether or not the object implements the Cloneable interface. If
the object doesn't implement the Cloneable interface, Object's
implementation of clone() throws a
CloneNotSupportedException. Otherwise, it creates a new instance of the
object, with all the fields initialized to values identical to the object being cloned, and returns a reference
to the new object.
The Cloneable interface doesn't have any members. It is an empty interface,
used only to indicate cloning is supported by a class. Class Object doesn't implement
Cloneable. To enable cloning on a class of objects, the class of the object itself, or
one of its superclasses other than Object, must implement the
Cloneable interface.
In class Object, the clone() method is declared protected.
If all you do is implement Cloneable, only subclasses and members of the same
package will be able to invoke clone() on the object. To enable any class in any
package to access the clone() method, you'll have to override it and declare it
public, as is done below. (When you override a method, you can make it less private, but not more private.
Here, the protected clone() method in Object is being
overridden as a public method.)
// In Source Packet in file clone/ex1/CoffeeCup.java
class CoffeeCup implements Cloneable {
private int innerCoffee;
public Object clone() {
try {
return super.clone();
}
catch (CloneNotSupportedException e) {
// This should never happen
throw new InternalError(e.toString());
}
}
public void add(int amount) {
innerCoffee += amount;
}
public int releaseOneSip(int sipSize) {
int sip = sipSize;
if (innerCoffee < sipSize) {
sip = innerCoffee;
}
innerCoffee -= sip;
return sip;
}
public int spillEntireContents() {
int all = innerCoffee;
innerCoffee = 0;
return all;
}
}
You could make a copy of this CoffeeCup class, which implements
Cloneable, as follows:
// In Source Packet in file clone/ex1/Example1.java
class Example1 {
public static void main(String[] args) {
CoffeeCup original = new CoffeeCup();
original.add(75); // Original now contains 75 ml of coffee
CoffeeCup copy = (CoffeeCup) original.clone();
copy.releaseOneSip(25); // Copy now contains 50 ml of coffee
// Figure 15-1 shows the heap at this point in the program
int origAmount = original.spillEntireContents();
int copyAmount = copy.spillEntireContents();
System.out.println("Original has " + origAmount
+ " ml of coffee.");
System.out.println("Copy has " + copyAmount
+ " ml of coffee.");
}
}
In this example, a new CoffeeCup object is instantiated and given an initial 75
ml of coffee. The clone() method is then invoked on the
CoffeeCup object. Because class CoffeeCup declares a
clone() method, that method is executed when clone() is
invoked on the CoffeeCup object referred to by the original
reference. CoffeeCup's clone() does just one thing: invoke the
clone() method in CoffeeCup's superclass,
Object. The first thing Object's clone()
does is check to see whether the object's class implements the Cloneable interface.
This test passes because CoffeeCup, the object's class, does indeed implement
Cloneable. The clone() method then creates a new instance
of CoffeeCup, and initializes its one field, innerCoffee, to
75--the same value it has in the CoffeeCup object being cloned.
Object's clone()returns a reference to the new object, which is
then returned by CoffeeCup's clone().
The reference returned by clone() refers to a CoffeeCup
object, but the reference itself is of type Object. The code above downcasts the
returned reference from Object to CoffeeCup before assigning
it to local variable copy. At this point, both CoffeeCup objects--
original and copy--contain 75 ml of coffee. Finally, 25 ml is
removed from the copy, so it ends up with only 50 ml of coffee. A graphical representation of the result
inside the Java Virtual Machine of executing the first four statements in main() is
shown in Figure 15-1. (As mentioned in the last chapter, the native pointer to class information shown
here is just one potential way a Java Virtual Machine could connect instance data to its class information.)

Figure 15-1. Cloning a CoffeeCup.
CoffeeCup's clone() implementation surrounds the call to
Object's clone implementation with a try block so it can catch
CloneNotSupportedException. This exception should never actually be thrown
by Object's clone(), because in this case,
CoffeeCup correctly implements Cloneable. If
CoffeeCup's clone() didn't explicitly catch it, however, then
clone() would have to declare in a throws clause that it may
throw CloneNotSupportedException. This would force any method invoking
clone() on a CoffeeCup object to deal with the exception,
either by explicitly catching it or declaring it in their own throws clause. Thus,
CoffeeCup's clone() catches
CloneNotSupportedException to make it simpler for other methods to invoke
clone() on a CoffeeCup.
If you wish to enable cloning of an object that includes object references as part of its instance data,
you may have to do more work in clone() than just calling
super.clone(). Clone should return an independent copy of the object.
Object's clone() will copy the value of each instance variable
from the original object into the corresponding instance variables of the copy object. If one of those
variables is an object reference, the copy object will get a duplicate reference to the same object.
As an example, consider this version of CoffeeCup, in which the
innerCoffee field has been upgraded from a mere int to a full
fledged object reference:
// In Source Packet in file clone/ex2/CoffeeCup.java
class CoffeeCup implements Cloneable {
private Coffee innerCoffee = new Coffee(0);
public Object clone() {
try {
return super.clone();
}
catch (CloneNotSupportedException e) {
// This should never happen
throw new InternalError(e.toString());
}
}
public void add(int amount) {
innerCoffee.add(amount);
}
public int releaseOneSip(int sipSize) {
return innerCoffee.remove(sipSize);
}
public int spillEntireContents() {
return innerCoffee.removeAll();
}
}
// In Source Packet in file clone/ex2/Coffee.java
public class Coffee implements Cloneable {
private int volume; // Volume in milliliters
Coffee(int volume) {
this.volume = volume;
}
public Object clone() {
try {
return super.clone();
}
catch (CloneNotSupportedException e) {
// This should never happen
throw new InternalError(e.toString());
}
}
public void add(int amount) {
volume += amount;
}
public int remove(int amount) {
int v = amount;
if (volume < amount) {
v = volume;
}
volume -= v;
return v;
}
public int removeAll() {
int all = volume;
volume = 0;
return all;
}
}
Given these declarations of CoffeeCup and Coffee, there
would be a surprise waiting for any method that attempts to clone a CoffeeCup
object:
// In Source Packet in file clone/ex2/Example2.java
class Example2 {
public static void main(String[] args) {
CoffeeCup original = new CoffeeCup();
original.add(75); // Original now contains 75 ml of coffee
CoffeeCup copy = (CoffeeCup) original.clone();
copy.releaseOneSip(25);
// Copy now contains 50 ml of coffee.
// Unfortunately, so does original.
// Figure 15-2 shows the heap at this point in the program
int origAmount = original.spillEntireContents();
int copyAmount = copy.spillEntireContents();
System.out.println("Original has " + origAmount
+ " ml of coffee.");
System.out.println("Copy has " + copyAmount
+ " ml of coffee.");
}
}
Here, when releaseOneSip() is invoked on copy with a
parameter of 25 ml, that amount of coffee is correctly removed from the CoffeeCup
object referenced by copy. The trouble is that 25 ml of coffee is also removed from the
cup referenced by original. The reason is that both the original and copy objects
contain a reference to the same Coffee object. A graphical representation of the
result of these statements is shown in Figure 15-2.

Figure 15-2. Incorrect cloning of a CoffeeCup that contains object references.
To rectify this situation, you need to modify CoffeeCup's
clone() method:
// In Source Packet in file clone/ex3/CoffeeCup.java
class CoffeeCup implements Cloneable {
private Coffee innerCoffee = new Coffee(0);
public Object clone() {
CoffeeCup copyCup = null;
try {
copyCup = (CoffeeCup) super.clone();
}
catch (CloneNotSupportedException e) {
// this should never happen
throw new InternalError(e.toString());
}
copyCup.innerCoffee = (Coffee) innerCoffee.clone();
return copyCup;
}
public void add(int amount) {
innerCoffee.add(amount);
}
public int releaseOneSip(int sipSize) {
return innerCoffee.remove(sipSize);
}
public int spillEntireContents() {
return innerCoffee.removeAll();
}
}
In this version of clone(), Object's
clone() is invoked as before. But instead of simply returning the reference to the
new CoffeeCup object created by Object's
clone(), the new CoffeeCup object is modified before it is
returned. First, the Coffee object referenced by innerCoffee is
cloned. A reference to the cloned Coffee object is then stored in the
innerCoffee variable of the cloned CoffeeCup object . At this
point, the original object and the clone refer to their own Coffee objects, but those
Coffee objects are exact duplicates of each other.
If you now performed the same statements on this version of CoffeeCup, you
would once again have the expected behavior:
// In Source Packet in file clone/ex3/Example3.java
class Example3 {
public static void main(String[] args) {
CoffeeCup original = new CoffeeCup();
original.add(75); // original now contains 75 ml of coffee
CoffeeCup copy = (CoffeeCup) original.clone();
copy.releaseOneSip(25);
// Copy now contains 50 ml of coffee.
// Original still has 75 ml of coffee.
// Figure 15-3 shows the heap at this point in the program
int origAmount = original.spillEntireContents();
int copyAmount = copy.spillEntireContents();
System.out.println("Original has " + origAmount
+ " ml of coffee.");
System.out.println("Copy has " + copyAmount
+ " ml of coffee.");
}
}
Because the CoffeeCup objects referenced by original
and copy each have their own Coffee objects, when
copy's was reduced by 25 ml, original's wasn't affected. A
graphical representation of the result of these statements is shown in Figure 15-3.

Figure 15-3. Proper cloning of a CoffeeCup that contains object references.
These examples demonstrate the customary approach to writing clone(). The
first thing to do in any clone() method (besides Object's) is
invoke super.clone(). This will cause Object's
implementation of clone() to be executed first. This scheme is similar to that of
constructors, in which an invocation of the superclass's constructor is always executed first.
Object's clone() will create a new instance of the class and
copy the values contained in the original's instance data to the new object's instance data. Catching
CloneNotSupportedException is also a good idea, to make calling
clone() on that class of objects simpler to code.
When super.clone() returns, a clone() method should
make clones of any mutable objects referenced by its instance variables, and assign these clones to the
instance variables of the copy. A mutable object is one whose state can change over the
course of its lifetime. An object whose state can't change is immutable.
An example of an immutable object is String. You must give a value to a
String when you create it. Once created, a String's value can't
change over the lifetime of the String object. The same is true for the wrapper
objects Integer, Float, and so on. You assign them a value
when they are created, and there is no way to change it for the remainder of their lifetimes.
The real trouble with the clone() method shown above that didn't clone
Coffee was that Coffee is mutable. When the state of the
Coffee object changed (volume changed from 75 to 50), both
CoffeeCup objects saw their own internal state change. Had
CoffeeCup included an instance variable of type String, you
wouldn't have had to clone it because Strings are immutable. (In fact, you couldn't
have cloned it, because String doesn't implement Cloneable.
Since Strings are immutable, it doesn't make sense to clone them.)
Java's cloning mechanism enables you to allow cloning, allow cloning conditionally, or forbid cloning altogether. If you wish to completely forbid cloning, you have a few different approaches to choose from. To decide which way to forbid cloning upon a particular class of objects, you must know something about the class's superclasses.
If none of the superclasses implement Cloneable or override
Object's clone() method, you can prevent cloning of objects of
that class quite easily. Simply don't implement the Cloneable interface and don't
override the clone() method in that class. The class will inherit
Object's clone() implementation, which will throw
CloneNotSupportedException anytime clone() is
invoked on objects of that class. All the classes shown as examples in this book prior to the
CoffeeCup class declared immediately above used this method of preventing
cloning. By doing nothing, they disallowed cloning. Thus, forbidding cloning is the default behavior for
an object.
In cases where a superclass already implements Cloneable, and you don't want
the subclass to be cloned, you'll have to override clone() in the subclass and throw a
CloneNotSupportedException yourself. In this case, instances of the superclass
will be clonable, but instances of the subclass will not.
For those of you who know C++, Java's clone() method is what happened to
C++'s copy constructor. For those of you who don't know C++, a copy constructor is one which takes a
single parameter of the same type of the class. In the body of the copy constructor, you have to copy all
values from the object passed as a parameter to the object under construction. Like Java's
clone() method, in a C++ copy constructor, you should allocate new memory for
objects referenced (or pointed to) from instance (member) variables. For example, a copy constructor for
class CoffeeCup would be:
// In Source Packet in file clone/ex4/CoffeeCup.java
// Copy constructors are not the Java way...
class CoffeeCup {
private int innerCoffee;
public CoffeeCup(CoffeeCup cup) {
innerCoffee = cup.innerCoffee;
}
//...
}
One of the primary uses of the copy constructor in C++ is to pass objects by value. The copy constructor is used to create a copy of an object that is passed by value to a function. This is not an issue in Java, because all objects in Java programs are passed by reference.
If you are a C++ programmer and feel the urge to write a copy constructor in a Java class, STOP!
Close your eyes. Take a few deep breaths. Then--when you feel your ready--open your eyes, implement
Cloneable and write clone(). It will be OK.
|
Sponsored Links
|