java对象克隆工具类_Java-对象克隆

1. 为什么要克隆

在java中,我们通过直接=等号赋值的方法来拷贝,如果是基本数据类型是没有问题的,例如

int i = 1;

int j = 0;

j = i; // 直接=等号赋值,这样是没有问题的

但是如果是引用数据类型,我们拷贝的就是引用,而不是真正的对象

拷贝引用的后果就是这两个引用指的还是同一个对象

1. 例如如下代码;

class Person {

private String personName;

private int age;

private Integer salary;

private Car car;

getset方法,构造器,toString方法

}

class Car{

private String carName;

private int price;

getset方法,构造器,toString方法

}

public class CloneTest{

public static void main(String[] args) {

Person p1 = new Person("zxj",21,8000,new Car("bmw",200000));

Person p2 = p1;

System.out.println(p1);

System.out.println(p2);

p2.setAge(10);

System.out.println(p1);

System.out.println(p2);

}

}

2. 打印输出

我们可以看到我们仅仅修改了p2的年龄,但是p1的年龄也随之修改了,这就是需要克隆的原因,我们需要一个新的Person对象。

Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=10, salary=8000, car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=10, salary=8000, car=Car{carName='bmw', price=200000}}

2. 我们先说第一种实现方法,浅拷贝:实现Cloneable接口,并重写Clone方法

注意:clone方法是Object类的方法,并不是Cloneable接口的方法,实现接口只是为了说明这个类是可克隆的。

1. 我们重写clone方法,并实现接口,clone方法中调用super.clone()

我们可以看一下父类的clone方法,诶,是native修饰的本地方法,看不了~

class Person implements Cloneable {

private String personName;

private int age;

private Integer salary;

private Car car;

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

getset方法,构造器,toString方法

2. 我们之后在调用p1.clone()方法来对p2进行赋值

public class CloneTest{

public static void main(String[] args) {

Person p1 = new Person("zxj",21,8000,new Car("bmw",200000));

Person p2 = null;

try {

//会抛异常我们给catch掉

//clone方法返回是Object类型的,所以需要强转成Person类型

p2 = (Person) p1.clone();

} catch (CloneNotSupportedException e) {

e.printStackTrace();

}

System.out.println(p1);

System.out.println(p2);

p2.setAge(10);

System.out.println(p1);

System.out.println(p2);

}

}

3. 这时我们再来运行,我们可以看到这次修改只修改了p2的age的值

Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=10, salary=8000, car=Car{carName='bmw', price=200000}}

4. 但是,当我们修改car的price的值的时候,发现也是相同的问题,

p2.getCar().setPrice(1000);

所以我们也需要为Car重复上面的那一套,实现Cloneable接口,并重写clone方法

3. 我们接下来说第二种方法,深拷贝

对于第一种来说,我们表面上看似是没有什么问题,但是我们修改的仅仅是基础数据类型,如果我们修改引用数据类型,例如List,Car,等等,

我们会发现依旧是修改p2之后,p1也跟着修改

例如:

我们在Person类中添加一个List list字段,代码就不放了

public class CloneTest{

public static void main(String[] args) {

Person p1 = new Person("zxj",21,8000,new ArrayList(),new Car("bmw",200000));

Person p2 = null;

try {

p2 = (Person) p1.clone();

} catch (CloneNotSupportedException e) {

e.printStackTrace();

}

System.out.println(p1);

System.out.println(p2);

p2.getList().add("1");

p2.getCar().setCarName("宝马");

p2.getCar().setPrice(100);

System.out.println(p1);

System.out.println(p2);

}

}

此时我们可以观察输出的结果:我们可以看到,对于list我们对p2的list执行的add,但是影响到了p1的list,还有car的carName

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='宝马', price=100}}

Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='宝马', price=100}}

这是因为我们在在clone的时候,实际上时对类中的每个属性都进行一次=等号,赋值

age = age; // 对于基础数据来说,就和开篇提到的,是没有问题的

list = list;// 此时可以看出对于引用数据类型,此时直接等号还是赋值的是地址,还是会有影响

car = car;

这就又回到了我们最开始的问题,如果是引用类型,复制的就是引用,而不是对象

1. 我们先说一种解决办法

我们在Person调用clone方法的时候,再调用一次Car的clone方法

class Person implements Cloneable {

。。。省略了其他属性

private List list;

private Car car;

@Override

protected Object clone() throws CloneNotSupportedException {

//首先拿到克隆到的person对象

Person person = (Person) super.clone();

//接下来我们对克隆到的person对象的car对象进行复制,调用原本car的clone方法进行复制

person.car = (Car) this.car.clone();

//然后将我们修改后的克隆person对象返回出去

return person;

}

我们再进行测试:我们可以看到,对于car来说,我们的拷贝是没有问题了,但是这样子是不是太麻烦了!!!

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='宝马', price=100}}

2. 我们就说另外一种方法,也是正经的方法,通过序列化实现,不知道序列化是啥的可以看我之前的一篇文章

1. 实现Serializable 接口

首先对于序列化来说,我们的类都必须实现Serializable 接口

class Person implements Serializable {

private String personName;

private int age;

private Integer salary;

private List list;

private Car car;

//依旧getset等等的方法就不写啦

}

class Car implements Serializable{

private String carName;

private int price;

}

public class CloneTest1{

public static void main(String[] args) {

Person p1 = new Person("zxj",21,8000,new ArrayList(),new Car("bmw",200000));

Person p2 = null;

System.out.println(p1);

System.out.println(p2);

System.out.println(p1);

System.out.println(p2);

}

}

2. 抽取工具类

因为Clone方法比较通用,我们可以抽取成一个工具类出来

class CopyUtils{

private CopyUtils() { }

@SuppressWarnings("unchecked")

public static T clone(T obj) throws Exception {

//字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。

ByteArrayOutputStream bout = new ByteArrayOutputStream();

//使用对象输出流进行包装

ObjectOutputStream oos = new ObjectOutputStream(bout);

//使用对象流将我们的对象输出到缓存中

oos.writeObject(obj);

//toByteArray();创建一个新分配的字节数组。数组的大小和当前输出流的大小,内容是当前输出流的拷贝。

//new ByteArrayInputStream(...);字节数组输入流在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中,接收字节数组作为参数创建。

ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());

ObjectInputStream ois = new ObjectInputStream(bin);

//读取一个对象,并返回出去

return (T) ois.readObject();

}

}

3. 接下来就是测试啦:

我们看到无论是list还是我们的car修改都是只修改本身的

public class CloneTest1{

public static void main(String[] args) {

Person p1 = new Person("zxj",21,8000,new ArrayList(),new Car("bmw",200000));

Person p2 = null;

try {

p2 = CopyUtils.clone(p1);

} catch (Exception e) {

e.printStackTrace();

}

System.out.println(p1);

System.out.println(p2);

p2.getCar().setCarName("宝马");

p2.getList().add("1");

System.out.println(p1);

System.out.println(p2);

}

}

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[], car=Car{carName='bmw', price=200000}}

Person{personName='zxj', age=21, salary=8000, list=[1], car=Car{carName='宝马', price=200000}}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
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对象克隆工具类的实现,我们可以方便地实现对象克隆操作,提高代码的可复用性和效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值