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 String
s are immutable. (In fact, you couldn't
have cloned it, because String
doesn't implement Cloneable
.
Since String
s 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
|