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/

关注我,获取400个的赚钱金点子,轻松开启程序员的副业生涯

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Java对象克隆工具类是指能够帮助我们实现对象克隆操作的工具类。 在Java对象克隆是指创建一个与原始对象具有相同属性和值的新对象。这个新对象可以是原始对象的完全副本,即改变新对象不会影响原始对象。为了实现对象克隆Java提供了Cloneable接口和clone()方法。 下面是一个简单的Java对象克隆工具类的示例代码: ``` public class CloneUtil { public static <T> T clone(T source) { try { // 判断对象是否实现了Cloneable接口 if (source instanceof Cloneable) { // 通过调用clone()方法进行对象克隆 Method cloneMethod = source.getClass().getMethod("clone"); return (T) cloneMethod.invoke(source); } } catch (Exception e) { e.printStackTrace(); } return null; } } ``` 在上面的代码,我们定义了一个泛型方法clone(),它接受一个参数source,表示要克隆的原始对象。然后我们首先使用instanceof运算符来判断source是否实现了Cloneable接口,如果是,则通过反射获取clone()方法,并调用它来进行对象克隆。最后返回克隆后的新对象。 使用该工具类进行对象克隆的示例代码如下: ``` public class Main { public static void main(String[] args) { Person person1 = new Person("Alice", 25); // 使用克隆工具类进行对象克隆 Person person2 = CloneUtil.clone(person1); System.out.println(person1); System.out.println(person2); System.out.println(person1 == person2); } } ``` 在上面的示例代码,我们创建了一个Person对象person1,并将其克隆到person2。然后分别打印person1、person2以及判断person1和person2是否为同一个对象。 通过上述Java对象克隆工具类的实现,我们可以方便地实现对象克隆操作,提高代码的可复用性和效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值