【克隆】JAVA使用Cloneable接口和Serializable接口实现对象复制

JAVA使用Cloneable接口和Serializable接口实现对象复制


在java中复制某个值,如果是基本或者包装类型很简单,把一个变量的值赋给另外一个变量就可以了

  • 简单复制
//创建一个对象
int i1=1;
int i2;
//把i1的值赋给i2
i2=i1;

直接用 = 赋值,而这种方式也适用于所有的基本类型和包装类型,而当赋值的对象是 对象 或者 集合 的时候,这种方式可能会有点问题

  • 复制对象

先创建一个user对象

public class User{
    private String name;
    private Integer age;
}

再创建一个测试类

public class CloneTest{

    public static void main(String[] args) {
        User user = new User("aaa", 18);
        System.out.println(user);

        User user2 = new User();
        user2=user;
        System.out.println(user2);
     }
}

输出:

User{name='aaa', age=18}
User{name='aaa', age=18}

看似已经复制好了,但是别急,我们进行一下修改

public class CloneTest{

    public static void main(String[] args) {
        User user = new User("aaa", 18);
        System.out.println(user);

        User user2 = new User();
        user2=user;
        System.out.println(user2);

		//在这里修改user2的age字段的值
        user2.setAge(20);

        System.out.println("修改后的结果");
        System.out.println(user);
        System.out.println(user2);
    }
}

再次输出:

User{name='aaa', age=18}
User{name='aaa', age=18}
修改后的结果
User{name='aaa', age=20}
User{name='aaa', age=20}

结果发现,不仅是user2修改了,还修改了原来的user

为什么?

打印这句话试试:

System.out.println(user==user2);

//控制台输出的结果
true

不难发现,我们在进行对象赋值复制user的时候,其实底层是把user对象的地址(指针)赋给了user2,他们都指向了同一个内存地址,所以修改了user2的属性后,user也跟着一起改变

那么问题来了,应该怎么去复制对象呢?

答:

用工具(如hutool等)或者手动赋值(创建一个新的对象,给对象的每个属性手动赋值)

  • 手动赋值在属性很多的时候会很麻烦,也容易遗漏出错
  • 使用工具类引入相关的包,调用方法就可以

但是呢~上面的我们先不讨论
今天我们要讨论的是java给我们提供的一个专门用于复制的接口: Cloneable接口

你一定要说别的方法的话那我只能说:还没写,兄嘚!

Cloneable接口

Cloneable接口是Object类(所有类的父类)提供的一个标记接口,里面没有定义任何内容。主要是和Object的clone方法配合使用。如果没有实现这个接口,而调用了clone方法,会抛出CloneNotSupportException
其中克隆分成浅克隆和深克隆

概念不说那么多,直接看怎么用就有一个大概的了解了

浅克隆

首先user实现Cloneable接口(getter等就省略了):

public class User implements Cloneable{
    private String name;
    private Integer age;

	//getter等方法略
   
}

同时重写clone方法

	@Override
    public Object clone(){
        User user=null;
        try {
             user = (User)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return user;
    }

这时候修改一下测试类的复制写法

        User user = new User("aaa", 18);
        System.out.println(user);

        User user2 = new User();
        //就是这里进行复制
        user2 =(User) user.clone();
        System.out.println(user2);

        user2.setAge(20);

        System.out.println("修改后的结果");
        System.out.println(user);
        System.out.println(user2);

        System.out.println(user == user2);

这时候看看输出的结果:

User{name='aaa', age=18}
User{name='aaa', age=18}
修改后的结果
User{name='aaa', age=18}
User{name='aaa', age=20}
false

很明显,调用clone方法进行复制,user和user2是两个不同的对象,修改user2的属性不会影响到user对象中属性的值

浅克隆只能复制对象中的属性,但是对于属性是对象或者是集合,那复制后也会有问题:

  • 请看下面的代码:

我们再创建一个部门类

public class Dept {
    private String deptName;
}

在User类中引入Dept类

public class User implements Cloneable{
    private String name;
    private Integer age;

	//引入部门类
    private Dept dept;
}

测试类复制User类后,修改user2中的属性与dept属性

User{name='aaa', age=18, dept=Dept{deptName='dept'}}
User{name='aaa', age=18, dept=Dept{deptName='dept'}}
这时候的user和user2内容为:
User{name='aaa', age=18, dept=Dept{deptName='dept2'}}
User{name='aaa', age=20, dept=Dept{deptName='dept2'}}

可以看到修改user2的age属性不会影响到user的age属性,但是修改user2的dept对象属性会把user的dept属性也一起修改了

原因: 浅复制只是复制了addr变量的引用,并无真正的开辟另外一块空间,将值复制后再将引用返回给新对象。因此,为了达到真正的复制对象,而不是纯粹引用复制。咱们须要将dept类可复制化,而且修改clone方法。

深克隆

在深克隆中,除了对象自己被复制外,对象所包含的全部成员变量也将复制,Java中要使用深克隆,可以使用经过覆盖Object类的clone()方法实现,或者使用序列化(Serialization)等方式来实现

下面先使用clone方法实现

  • 修改Dept类,实现Cloneable接口
public class Dept implements Cloneable{
    private String deptName;

    @Override
    public Object clone(){
        Dept dept=null;
        try {
            dept = (Dept) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return dept;
    }
}    

修改User类的克隆方法

	@Override
    public Object clone(){
        User user=null;
        try {
            //浅克隆
             user = (User)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        //深克隆
        //dept属性的clone已经进行了tryCatch了
        user.dept = (Dept)dept.clone();
        return user;
    }

测试代码跟之前一样就行

运行测试类,调用clone方法,修改user2的dept属性,观察结果:

User{name='aaa', age=18, dept=Dept{deptName='dept'}}
User{name='aaa', age=18, dept=Dept{deptName='dept'}}
这时候的user和user2内容为:
User{name='aaa', age=18, dept=Dept{deptName='dept'}}
User{name='aaa', age=20, dept=Dept{deptName='dept2'}}

可以看到深克隆把类里面的对象属性也一起克隆了

Serializable接口

深克隆-解决多层克隆问题

上面说过,除了clone方法,还可以使用序列化方式进行深克隆。同时上面的演示会有一个问题,如果Dept类中还引用了其它的类属性,假如引入的这些类里面还有别的引用类型,使用clone方法就会很麻烦

这时候我们可以使用序列化解决多层克隆的问题

我们准备三个类EmpBossCompany,员工,员工的老板,老板所属的公司

公司类实现序列化接口

public class Company implements Serializable {
    private String name;
}

老板类,里面引用了公司类,并且实现了序列化接口

public class Boss implements Serializable {

    private String manage;

    private Company company;
}

员工类,里面引用了老板类,并且实现了序列化接口,并且在员工类中写了序列化克隆的方法(也可以在测试类中写,这里弄成了一个方法)

public class Emp implements Serializable {

    private String empName;

    private Boss boss;

	public Emp empClone() {
          Emp emp = null;
          try {
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
              ObjectOutputStream oos = new ObjectOutputStream(baos);
              oos.writeObject(this);
              //将流序列化成对象
              ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
              ObjectInputStream ois = new ObjectInputStream(bais);
              emp = (Emp) ois.readObject();
          } catch (IOException e) {
                  e.printStackTrace();
          } catch (ClassNotFoundException e) {
                 e.printStackTrace();
          }
          return emp;
    }

}

测试类中进行测试

public class CloneTest {

    public static void main(String[] args) {
        //创建emp
        Company com = new Company("某公司");
        Boss boss = new Boss("某老板", com);
        Emp emp = new Emp("工具人1",boss);
        System.out.println(emp);
        //克隆得到emp2
        Emp emp2 = emp.empClone();
        //查看
        System.out.println(emp==emp2);
        System.out.println(emp);
        System.out.println(emp2);

        //修改emp
        com.setName("某公司1");

        //修改emp2
        emp2.setEmpName("工具人2");
        emp2.getBoss().setManage("某老板2");
        emp2.getBoss().getCompany().setName("某公司22");
        //查看修改结果
        System.out.println(emp);
        System.out.println(emp2);
    }
}

老规矩,查看输出:

Emp{empName='工具人1', boss=Boss{manage='某老板', company=Company{name='某公司'}}}
//这里其实就已经说明这两个类不一样了
false
Emp{empName='工具人1', boss=Boss{manage='某老板', company=Company{name='某公司'}}}
Emp{empName='工具人1', boss=Boss{manage='某老板', company=Company{name='某公司'}}}
//修改的结果
Emp{empName='工具人1', boss=Boss{manage='某老板', company=Company{name='某公司1'}}}
Emp{empName='工具人2', boss=Boss{manage='某老板2', company=Company{name='某公司22'}}}

欧克,使用序列化实现了深克隆,emp2修改引用的类属性,不会影响到原来的emp

最后

总结一下
1、克隆分成深克隆和浅克隆
2、克隆可以使用实现Cloneable接口方式或者序列化方式
3、使用Cloneable接口方式可以实现浅克隆,实现深克隆,如果类中还引用了其它的类属性,那就要使用实现深克隆,这时候使用clone方法就会很麻烦。
4、使用序列化方式,实现Serializable接口,可以真正地实现深克隆

  • 30
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值