克隆是完全复制另一个物体,在java中,意味着创建一个和原对象有相似状态的对象。clone()方法就提供了这个功能,在这篇文章中,我们将探索java 克隆的一些最重要的方面。这篇文章是mini guides的一个部分
详细解释克隆
克隆是创建原对象的一个复制,字典上的意思是“精确的复制”。默认情况下,java的克隆是一个字段一个字段的复制。例如:对象类调用的clone()方法,对将要复制的类的结构并没有一个清晰的了解。那么当克隆的时候,JVM将会做以下的事情:
1、如果这个类只有原生数据类型成员,那么一个原对象的完全复制会被创建,新对象的引用将会被返回。
2、如果这个对象包含类类型,那么只有这些对象的引用会被复制,因此克隆对象和原对象的成员的引用指向同一个对象。
除了默认的情况,你可以重写这些行为并指定自己的方式,这需要覆盖clone()方法,让我们看看是怎么样克隆的?
java克隆的构造
支持克隆对象的每一种语言都有自己的克隆规则,java也是。在java中,如果一个类需要支持克隆,那么它需要做两件事情。
1、你必须实现Cloneable接口。
2、你必须在类中重写clone()方法
在java文档中给出了下边的解释:
/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;
1、第一个语句保证了克隆对象会有独立的内存地址分配
2、第二个语句表明原生对象和克隆对象的需要具有相同的类型,但是不是强制的
3、第三个语句表明原生对象和克隆对象使用equals()时应该是相等的,但是也是不是强制的。
让我们看看实际的代码:
我们的第一个类Employee有三个属性 Id,name和department
public class Employee implements Cloneable{
private int empoyeeId;
private String employeeName;
private Department department;
public Employee(int id, String name, Department dept)
{
this.empoyeeId = id;
this.employeeName = name;
this.department = dept;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//Accessor/mutators methods will go there
}
我们的Department类有两个属性,id和name
public class Department
{
private int id;
private String name;
public Department(int id, String name)
{
this.id = id;
this.name = name;
}
//Accessor/mutators methods will go there
}
所以如果我们想克隆Employee类,那么我们需要做像下边的事情:
public class TestCloning {
public static void main(String[] args) throws CloneNotSupportedException
{
Department dept = new Department(1, "Human Resource");
Employee original = new Employee(1, "Admin", dept);
//Lets create a clone of original object
Employee cloned = (Employee) original.clone();
//Let verify using employee id, if cloning actually workded
System.out.println(cloned.getEmpoyeeId());
//Verify JDK's rules
//Must be true and objects must have different memory addresses
System.out.println(original != cloned);
//As we are returning same class; so it should be true
System.out.println(original.getClass() == cloned.getClass());
//Default equals method checks for refernces so it should be false. If we want to make it true,
//we need to override equals method in Employee class.
System.out.println(original.equals(cloned));
}
}
Output:
1
true
true
false
我们已经成功克隆了Employee对象,但是,记住我们已经有同一个对象的两个引用了,现在这连个都会改变在不同的应用中改变状态。
public class TestCloning {
public static void main(String[] args) throws CloneNotSupportedException {
Department hr = new Department(1, "Human Resource");
Employee original = new Employee(1, "Admin", hr);
Employee cloned = (Employee) original.clone();
//Let change the department name in cloned object and we will verify in original object
cloned.getDepartment().setName("Finance");
System.out.println(original.getDepartment().getName());
}
}
Output: Finance
克隆对象和原生对象的值都改变了,如果允许这么做可能会引起系统的损毁,任何人都可以克隆你的对象,并改变你对象的引用,这将会是灾难性的,那么怎么阻止它呢?
我们可以使用深度克隆和
复制构造来解决这个问题,我们会在这篇文章的后边学习
浅克隆
这是java中默认的实现的方式,复写clone方法时,如果你没有克隆所有的对象类型(不是原生类型),那么你就是浅克隆。所有的上边的例子都是浅克隆,因为我们没有在Employee类的clone方法中克隆Department对象,现在我们来介绍深克隆。
深克隆
在大多数情况下,这是最想要的行为:我们克隆的对象的行为的改变不会影响到原生对象的值。
我们来看一下在我们的例子中如何来做:
//Modified clone() method in Employee class
@Override
protected Object clone() throws CloneNotSupportedException {
Employee cloned = (Employee)super.clone();
cloned.setDepartment((Department)cloned.getDepartment().clone());
return cloned;
}
我修改了Employee中的clone方法并在Department类中的clone方法中增加了如下代码:
//Defined clone method in Department class.
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
现在我们来测试一下:
public class TestCloning {
public static void main(String[] args) throws CloneNotSupportedException {
Department hr = new Department(1, "Human Resource");
Employee original = new Employee(1, "Admin", hr);
Employee cloned = (Employee) original.clone();
//Let change the department name in cloned object and we will verify in original object
cloned.getDepartment().setName("Finance");
System.out.println(original.getDepartment().getName());
}
}
Output:
Human Resource
这里克隆对象的改变并没有改变原生对象的值
那么深克隆需要满足下边的情况:
1、没有必要单独复制primitive
2、原生对象中的所有类成员都必须支持clone方法,在原生对象的成员类中都要调用super.clone()方法
3、如果类成员变量不支持clone,那么它必须创建那个成员类的新的实例,并把它的属性一个一个的复制到新的对象中去,新的类成员对象在clone对象中被设置。
使用复制构造
复制构造是类中特殊的构造方法,它需要把自己的类型当做参数,所以当你传递一个类的实例给复构造时,这个构造会返回参数给出的类型的实例,我们来看下边这个例子:
public class PointOne {
private Integer x;
private Integer y;
public PointOne(PointOne point){
this.x = point.x;
this.y = point.y;
}
}
这个方法看起来很简单,但是当继承的时候就不是这样了。当你定义一个类继承上边这个类,你需要定义相似的构造方法,在子类中,你需要复制子类的属性并且传递给父类的构造传递参数
public class PointTwo extends PointOne{
private Integer z;
public PointTwo(PointTwo point){
super(point); //Call Super class constructor here
this.z = point.z;
}
}
这样就可以了吗?不行,问题是继承是运行时的行为,所以在我们的例子中,如果一些类传递PointTwo的实例在PointOne的构造中,你将会得到PointOne的实例。我们来看下边的代码:
class Test
{
public static void main(String[] args)
{
PointOne one = new PointOne(1,2);
PointTwo two = new PointTwo(1,2,3);
PointOne clone1 = new PointOne(one);
PointOne clone2 = new PointOne(two);
//Let check for class types
System.out.println(clone1.getClass());
System.out.println(clone2.getClass());
}
}
Output:
class corejava.cloning.PointOne
class corejava.cloning.PointOne
创建复制构造的另一种方式是利用静态工厂方法,他们把类的类型作为参数,使用类的其他构造方法创建实例。那么这些工厂方法将会复制所有的状态数据到新的类实例中去。
public class PointOne implements Cloneable{
private Integer x;
private Integer y;
public PointOne(Integer x, Integer y)
{
this.x = x;
this.y = y;
}
public PointOne copyPoint(PointOne point) throws CloneNotSupportedException
{
if(!(point instanceof Cloneable))
{
throw new CloneNotSupportedException("Invalid cloning");
}
//Can do multiple other things here
return new PointOne(point.x, point.y);
}
}
序列化的克隆
这是深克隆的另一种简单的方式,这种方法,你只需要序列化需要克隆的对象并反序列化就可以了。显然需要克隆的对象应该实现序列化接口。在进行下一步之前,我需要提醒的是它不能被轻易的使用,首先序列化有高昂的代价,它可能是clone方法的上百倍,第二并非所有的对象都是可以序列化的,第三类序列化并不是一个可以依赖的,它并不可靠。
@SuppressWarnings("unchecked")
public static T clone(T t) throws Exception {
//Check if T is instance of Serializeble other throw CloneNotSupportedException
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//Serialize it
serializeToOutputStream(t, bos);
byte[] bytes = bos.toByteArray();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
//Deserialize it and return the new instance
return (T)ois.readObject();
}
使用apche Commons克隆
apche commons也实现了深克隆的功能,如果你感兴趣可以查看文档说明,下边这是使用的一个简单的例子
SomeObject cloned = org.apache.commons.lang.SerializationUtils.clone(someObject);
最佳实践
1、当你不知道你是否可以调用clone方法的时候,你可以使用 class is instance of Cloneable来检查:
if(obj1 instanceof Cloneable){
obj2 = obj1.clone();
}
//Dont do this. Cloneabe dont have any methods
obj2 = (Cloneable)obj1.clone();
2、被克隆的对象中没有构造方法被调用,你应该负责所有的成员变量被恰当的设置,如果你在构造方法中跟踪对象的调用,那么这也是一个增加计数器的地方
原文地址:http://howtodoinjava.com/2012/11/08/a-guide-to-object-cloning-in-java/
关注我,获取400个的赚钱金点子,轻松开启程序员的副业生涯