clone is a tricky method from java.lang.Object
class, which is used to create copy of an Object in Java. Intention of clone() method is
simple, to provide a cloning mechanism, but some how it's implementation became
tricky and has been widely criticized from long time. Anyway, we will not go to
classic debate of clone in Java, at-least for now; instead, we will try to
learn how clone method works in Java.
To be fair, understating cloning mechanism in Java is not easy and even
experienced Java programmer fail to explain how cloning of mutable object works,
or difference between deep copy and shallow copy in Java. In this three part article, we will first see working
of clone method in Java, and in second part we will learn how to override
clone method in Java, and finally we will discuss deep copy vs shallow copy mechanism. The reason I chose to make
this a three part article, is to keep focus on one thing at a time. Since clone() itself is
confusing enough, it's best to understand concept one by one. In this post, we
will learn what is clone method, what it does and How clone method works in
Java. By the way, clone() is one of the few fundamental methods defined by
objects, others being equals, hashcode(), toString() along with wait and notify methods.
What is clone of object in Java
An object which is returned by clone() method is known as clone of
original instance. A clone object should follow basic characteristics e.g. a.clone()
!= a, which means original and clone are two separate object in Java heap, a.clone().getClass()
== a.getClass() and clone.equals(a), which
means clone is exact copy of original object. These characteristic is followed
by a well behaved, correctly overridden clone() method in
Java, but it's not enforced by cloning mechanism. Which means, an object
returned by clone() method may violate any of these
rules. By following convention of returning object by calling super.clone(), when
overriding clone() method, you can ensure that it
follows first two characteristics. In order to follow third characteristic, you
must override equals method to
enforce logical comparison, instead of physical comparison exists in java.lang.Object. For
example, clone() method of Rectangle class in
this method return object, which has these characteristics, but if you run same
program by commenting equals(), you will see that
third invariant i.e. clone.equals(a) will return false. By the way there are couple of good items on Effective Java regarding effective use of clone method, I highly recommend to read those items after going through this article.
How Clone method works in Java
java.lang.Object provides default implementation
of clone() method in Java. It's declared as protected and native in Object
class, so implemented in native code. Since it's convention to return clone() of object
by calling super.clone() method, any cloning process eventually reaches to java.lang.Object clone()
method. This method, first checks if corresponding object implements Cloneable interface,
which is a marker interface. If that instance doesn't implements Cloneable then
it throws CloneNotSupported in Java, a checked
exception, which is always required to be handled while cloning an object. If
object pass this check, than java.lang.Object's clone() method
creates a shallow copy of object and returned it to the caller. Since Object
class' clone() method creates copy by creating
new instance, and then copying field-by-field, similar
to assignment operator, it's fine for primitives and Immutable object, but not
suited if your class contains some mutable data-structure e.g. Collection
classes like ArrayList or arrays. In that case, both
original object and copy of object will point to the same object in heap. You
can prevent this by using technique known as deep cloning, on which each
mutable field is cloned separately. In short, here is how clone method works in
Java :
1) Any class calls clone() method on instance, which
implements Cloneable and overrides protected clone() method
from Object class, to create a copy.
Rectangle rec = new Rectangle(30,
60);
logger.info(rec);
try
{
logger.info("Creating Copy of this object
using Clone method");
Rectangle copy = rec.clone();
logger.info("Copy " + copy);
} catch
(CloneNotSupportedException ex) {
logger.debug("Cloning is not supported for
this object");
}
2) Call to clone() method on Rectangle is
delegated to super.clone(), which can be a custom super class or by default java.lang.Object
@Override
protected
Rectangle clone() throws CloneNotSupportedException {
return
(Rectangle) super.clone();
}
3) Eventually call reaches to java.lang.Object's clone() method,
which verify if corresponding instance implements Cloneable interface,
if not then it throws CloneNotSupportedException, otherwise
it creates a field-by-field copy of instance of that class and returned to
caller.
So in order for clone() method to work properly, two
things need to happen, a Class should implement Cloneable interface
and should override clone() method of Object class. By
the way this was this was the simplest example of overriding clone method and
how it works, things gets more complicated with real object, which contains
mutable fields, arrays, collections, Immutable object and
primitives, which we will see in second part of this Java Cloning tutorial
series.
Java clone() method Example
In this article, we have not seen complexity of overriding clone method
in Java, as our Rectangle class is very simple and only
contains primitive fields, which means shallow cloning provided by Object's clone() method is
enough. But, this example is important to understand process of Object cloning
in Java, and How clone method works. Here is complete code of this clone() method
overriding example :
import org.apache.log4j.Logger;
/**
* Simple example of overriding clone() method
in Java to understand How Cloning of
* Object works in Java.
*
* @author
*/
public class JavaCloneTest
{
private
static final
Logger logger = Logger.getLogger(JavaCloneTest.class);
public
static void
main(String args[]) {
Rectangle rec = new Rectangle(30,
60);
logger.info(rec);
Rectangle copy = null;
try
{
logger.info("Creating Copy of this object
using Clone method");
copy = rec.clone();
logger.info("Copy " + copy);
} catch
(CloneNotSupportedException ex) {
logger.debug("Cloning is not supported for
this object");
}
//testing
properties of object returned by clone method in Java
logger.info("copy != rec : " +
(copy != rec));
logger.info("copy.getClass() ==
rec.getClass() : " + (copy.getClass()
== rec.getClass()));
logger.info("copy.equals(rec) : "
+ copy.equals(rec));
//Updating
fields in original object
rec.setHeight(100);
rec.setWidth(45);
logger.info("Original object :" +
rec);
logger.info("Clonned object :" + copy);
}
}
public class Rectangle
implements Cloneable{
private
int width;
private
int height;
public
Rectangle(int
w, int h){
width = w;
height = h;
}
public
void setHeight(int height) {
this.height = height;
}
public
void setWidth(int width) {
this.width = width;
}
public
int area(){
return
widthheight;
}
@Override
public
String toString(){
return
String.format("Rectangle [width: %d, height: %d, area: %d]",
width, height, area());
}
@Override
protected
Rectangle clone() throws CloneNotSupportedException {
return
(Rectangle) super.clone();
}
@Override
public
boolean equals(Object
obj) {
if
(obj == null) {
return
false;
}
if
(getClass() != obj.getClass()) {
return
false;
}
final
Rectangle other = (Rectangle) obj;
if
(this.width
!= other.width) {
return
false;
}
if (this.height != other.height)
{
return
false;
}
return
true;
}
@Override
public
int hashCode()
{
int
hash = 7;
hash = 47 hash + this.width;
hash = 47 hash + this.height;
return
hash;
}
}
Output:
2013-05-20
23:46:58,882
0
[main] INFO JavaCloneTest - Rectangle [width:
30, height:
60, area:
1800]
2013-05-20
23:46:58,882
0
[main] INFO JavaCloneTest - Creating Copy of this object using Clone method
2013-05-20
23:46:58,882
0
[main] INFO JavaCloneTest - Copy Rectangle [width:
30, height:
60, area:
1800]
2013-05-20
23:46:58,882
0
[main] INFO JavaCloneTest - copy != rec : true
2013-05-20
23:46:58,882
0
[main] INFO JavaCloneTest - copy.getClass()
== rec.getClass() : true
2013-05-20
23:46:58,882
0
[main] INFO JavaCloneTest - copy.equals(rec)
: true
2013-05-20
23:46:58,882
0
[main] INFO JavaCloneTest - Original object :Rectangle [width: 45,
height: 100,
area: 4500]
2013-05-20
23:46:58,882
0
[main] INFO JavaCloneTest - Cloned object :Rectangle [width:
30, height:
60, area:
1800]
From output, you can clearly see that cloned object has same attribute as
original object in Java. Also changing attribute of original object is not
affecting state of copy object, because they only contains primitive fields,
had then contain any mutable object, it would have affected both of them. You
can also see that it follow standard properties of cloned object i.e. clone
!= original, clone.getClass() == original.getClass() and clone.equals(original).
Things to Remember - Clone method in Java
1) Clone method is used to create a copy of object in Java. In order to
use clone() method, class must implement java.lang.Cloneable interface and override
protected clone() method from java.lang.Object. A
call to clone() method will result in CloneNotSupportedException, if that
class doesn't implement Cloneable interface.
2) No constructor is called during cloning of Object in Java.
3) Default implementation of clone() method in
Java provides "shallow copy"
of object, because it creates copy of Object by creating new instance and then
copying content by assignment, which means if your Class contains a mutable
field, then both original object and clone will refer to same internal object.
This can be dangerous, because any change made on that mutable field will
reflect in both original and copy object. In order to avoid this, override clone() method to
provide deep copy of object.
4) By convention, clone of an instance should be obtained by calling super.clone() method,
this will help to preserve invariant of object created by clone() method
i.e. clone != original and clone.getClass() ==
original.getClass(). Though these are not absolute requirement as
mentioned in Javadoc.
5) Shallow copy of an instance is find, until it only contains primitives
and Immutable objects, otherwise, you need to modify one or more mutable fields
of object returned by super.clone, before returning it to caller.
That's all on How clone method works in Java. Now we know, what is
clone and what is Cloneable interface, couple of things about
clone method and what does default implementation of clone method do
in Java. This information is enough to move ahead and read second part of this
Java cloning tutorial, on which we will learn, how to override clone() method in Java, for classes
composed with primitives, Mutable and Immutable objects in Java.
Recommended Book
Like most of important topics in Java, Joshua Bloch has shared some words of wisdom on object cloning and clone method in Java. I highly suggest going through those items on his evergreen Effective Java book.
Tidak ada komentar:
Posting Komentar