java中对象的克隆

克隆是完全复制另一个物体,在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/

你可能感兴趣的:(java,克隆,浅克隆,深克隆)