Download Copying Objects in Java

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
EE 497 Java Programming
H.I. Bozma
Copying Objects in Java
1
Introduction
•
Concepts and implementation of object copying in Java.
•
Copying in Java is not something is not used in general in beginning level programming.
Required for more advanced level of programming.
•
Understanding of identity and equality.
2
Copying arrays
Consider
• Put the brackets after the array name: Point points[ ];
•
Copying
• You cannot simply create a new pointer and point to the old one as
Point[] points2 = points;
•
3
Use arraycopy() method of the System class under java.lang package
Copying Objects
•
Object copying -- the clone() method.
•
All objects inherit clone() from the java.lang.Object class.
•
A common first mistake is to assume that cloning actually works by default.
- The class CloseEx
•
Compile normally but fail at runtime with a CloneNotSupportedException.
•
Although the clone method is available to all subclasses of java.lang.Object it is
not enabled by default.
•
To 'turn on' the clone method and prevent the CloneNotSupportedException from
being thrown your class must implement the interface java.lang.Cloneable.
EE 497 Java Programming
H.I. Bozma
•
This tells the JVM that is OK for it to copy your object. The Cloneable interface
is what is known as a marker interface.
•
Marker interfaces do not contain any methods, you do not need to implement any
additional methods.
•
Simply implementing the interface is enough to change the way the
Object::clone() method works.
Now the example compiles and runs normally. You will see two lines of output, one for the original object and one for
the copy. Note that the values after the '@' are different. The value after the '@' is the objects hash code. By default
this is the objects identifier. Think of it as the primary key for the object. On some Java VM's this may be the address
in memory of the object. The numbers are different because they are different objects. Cloning creates a new object
with details copied from the original.
Enable object copying by implementing the Cloneable interface.
4
Constructors
Its interesting to note that constructors are not called when a clone is created. Why not? Its not necessary or desirable
to call a constructor because you are copying an existing object. It can be assumed that the original object was
constructed correctly. Calling a constructor for an object that is going to have its fields copied over is a pointless
exercise. The sample program shown below outputs a message to the console each time the constructor is invoked. In
this example the constructor is invoked for the original object but not when the copy is made.
public class CloneSample implements Cloneable {
public CloneSample() {
System.out.println("Constructor called");
}
public static void main(String[] args) {
try {
CloneSample original = new CloneSample();
CloneSample copy = (CloneSample)original.clone();
System.out.println("original: " + original);
System.out.println("copy: " + copy);
}
catch(CloneNotSupportedException e) {
e.printStackTrace();
EE 497 Java Programming
H.I. Bozma
}
}
}
Constructors are not called during cloning.
5
Overriding clone()
The clone () method that we're inheriting from Object declares that it throws CloneNotSupportedException.
CloneNotSupportedException is a checked exception. This means that callers of clone() must wrap it in a try-catch
block. If we have implemented the Cloneable interface then we will never get these exceptions. Using the clone()
method from Object means that users of our class must write exception handling code that will never be invoked.
You can make things simpler for callers of clone() by overriding the inherited version. This allows you to change the
method signature and remove the throws CloneNotSupportedException clause. You must still invoke the superclass
version of clone. Here is what our sample program looks like:
public class CloneSample implements Cloneable {
public Object clone() {
try {
return super.clone();
}
catch(CloneNotSupportedException e) {
// we implement Cloneable, this exception will never be thrown
return null;
}
}
public static void main(String[] args) {
CloneSample original = new CloneSample();
CloneSample copy = (CloneSample)original.clone();
System.out.println("original: " + original);
System.out.println("copy: " + copy);
}
}
EE 497 Java Programming
H.I. Bozma
A second reason to override clone() has to do with visibility. Object.clone() is declared as a protected method, it can
only be invoked by subclass methods. When overriding a method you can change its visibility to make the method
more accessible. Declaring your overridden clone() method public allows any other class to make clones.
One thing that you cannot do when overriding clone() is to change the methods return type. It would be convenient to
make the clone method return CloneSample as casting the result would not be necessary. It is a requirement of Java
that when overriding a method you are not permitted to modify the return type, clone() must always return Object. You
must always need to cast the value returned from clone().
Override the clone() method to simplify coding.
6
Issues with Object Copying
Object.clone() is a native method written in C. You cannot view the source as it is implemented inside the Java Virtual
Machine. How does the method work? Simple, clone() takes a block of memory from the Java heap. The block is the
same size as the original object. It then performs a bitwise copy of all fields from the original object into fields in the
copy. That is, the fields in the copy are exact duplicates, bit for bit, of the fields in the original. This kind of copy is
known as a shallow copy.
Shallow Copy - Primitive Types
Lets take a simple example, a class that contains only fields of primitive type and look at the result of a clone()
operation. Our sample class is defined as:
public class CloneSample implements Cloneable {
private int number = 1;
private boolean flag = true;
public Object clone() {
try {
return super.clone();
}
catch(CloneNotSupportedException e) {
// we implement Cloneable, this exception will never be thrown
return null;
}
}
public static void main(String[] args) {
EE 497 Java Programming
H.I. Bozma
CloneSample original = new CloneSample();
CloneSample copy = (CloneSample)original.clone();
System.out.println("original: " + original);
System.out.println("copy: " + copy);
}
}
When clone is invoked the JVM will allocate another object from the Java heap, its size will be the same as the
original. The VM then copies all fields, bit by bit, from the original to the copy. In this example these are the values 1
and true. The clone() method returns when the copy process is complete. Any changes made to the copy, for example
setting the value of flag to false, will not change the values of fields contained in the original object. Shallow copying
of primitive values results in separate values being stored in the copy.
Shallow Copy - Reference Types
Things change when copying fields containing reference types. Fields that contain objects are actually pointers. You
may have been told that Java doesn't support pointers, this is not entirely true. Java uses pointers internally, its just that
the programmer can't manipulate the pointers. This is a key difference between Java and languages like C. Shallow
copies work bit by bit. In the case of reference types it is the pointer that is copied. The original and the copy will
contain pointers to the same object. For example, consider this:
public class CloneSample implements Cloneable {
private int number = 1;
private String name = "abc";
public Object clone() {
try {
return super.clone();
}
catch(CloneNotSupportedException e) {
// we implement Cloneable, this exception will never be thrown
return null;
}
}
EE 497 Java Programming
H.I. Bozma
public static void main(String[] args) {
CloneSample original = new CloneSample();
CloneSample copy = (CloneSample)original.clone();
System.out.println("original: " + original);
System.out.println("copy: " + copy);
}
}
In this example, cloning performs a shallow copy. The field name contains a reference to a String with value "abc",
think of it as a pointer to the String. When the bitwise copy is made the name field contains a copy of the pointer. That
is, the original and copy both reference the same String. Think of it like this:
Sharing a String between copies does not cause any problems because Strings are immutable. Immutable objects are
objects that once created cannot be modified. Strings cannot be changed once created. String methods like concat() and
replace() create new Strings, they do not modify the original. Wrapper classes like java.lang.Integer are another
example of immutable classes. Shallow copying fields that contain references to immutable types are not a problem.
Shallow copying fields referencing mutable types is a problem.
Fields containing primitive types and immutable reference
types can be safely copied using the shallow copy method.
7
Deep Copy
Mutable types are objects that can be modified. Collections such as java.util.Vector are mutable types and will cause
problems if not handled correctly when copied. Taking a shallow copy of an object containing a collection just copies
the pointer, the collection will be shared between the original and clone. For example, consider what happens when
cloning this object:
public class CloneSample implements Cloneable {
private int number = 1;
private Vector names = new Vector();
public void update(String data) {
// modify the collection
names.addElement(data);
}
EE 497 Java Programming
H.I. Bozma
public String toString() {
return "Sample@" + hashCode() + " contains " + names.size() + " names";
}
public static void main(String[] args) {
CloneSample original = new CloneSample();
original.update("abc");
CloneSample copy = (CloneSample)original.clone();
System.out.println("original: " + original);
System.out.println("copy: " + copy);
copy.update("def");
System.out.println("original: " + original);
System.out.println("copy: " + copy);
}
}
When you run this example you will see a problem. Calling update("abc") on the original object adds a String to the
collection. Calling update("def") against the clone results in a String being added to the original. This happens because
a shallow copy was performed, there is only a single Vector object being shared between the original and the clone.
Sharing mutable objects between clones is a common source of errors. The solution is to copy the Vector when
creating the clone. This is known as a deep copy. Deep copies use an overidden version of clone() to take further
copies of mutable objects. Consider the next code sample where a custom clone() method has been added:
public class CloneSample implements Cloneable {
private int number = 1;
EE 497 Java Programming
H.I. Bozma
private Vector names = new Vector();
public Object clone() {
CloneSample copy = null;
try {
copy = (CloneSample)super.clone();
// make the copy a little deeper
copy.names = (Vector)this.names.clone();
}
catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return copy;
}
public void update(String data) {
// modify the collection
names.addElement(data);
}
public String toString() {
return "Sample@" + hashCode() + " contains " + names.size() + " names";
}
public static void main(String[] args) {
CloneSample original = new CloneSample();
original.update("abc");
CloneSample copy = (CloneSample)original.clone();
System.out.println("original: " + original);
System.out.println("copy: " + copy);
EE 497 Java Programming
H.I. Bozma
copy.update("def");
System.out.println("original: " + original);
System.out.println("copy: " + copy);
}
}
8
Summary
9
References