浅拷贝和深拷贝?仔细搞懂你

1.序言

在java语言中,如果需要拷贝一个对象时,分两种类型的拷贝:浅拷贝深拷贝

 在拷贝的同时又不想改变原有对象的属性值 ,就只能是深拷贝      

两者的区别如下:

  • 浅拷贝:只是拷贝了源对象的地址,所以源对象的值发生变化时,拷贝对象的值也会发生变化。

  • 深拷贝:只是拷贝了源对象的值,所以即使源对象的值发生变化时,拷贝对象的值也不会改变。

2.浅拷贝

浅拷贝示例如下:

public static void main(String[] args) {
    Big big = new Big("1", new Address("path1"));
    Big big3 = big;
    big3.setStart("3");
    big3.getAddress().setPath("path3");
    System.out.println(big);
    System.out.println(big3);
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Big {
    private String start;
    private Address address;
}

执行结构如下:

Big(start=3, address=Address(path=path3))
Big(start=3, address=Address(path=path3))

可以清晰的发现拷贝对象big3,在设置新值的时候,改变了原有被拷贝对象的值,这就是典型的浅拷贝

3.深拷贝

深拷贝经过总结,常见的有6种实现方法

  • 构造方法
  • 重写clone方法
  • Apache Commonss Lang序列化
  • Gson序列化
  • Jackson序列化
  • 自定义序列化

 

3.1.构造方法

通过在调用构造函数进行深拷贝,形参如果是基本类型和字符串则直接赋值,如果是对象则重新new一个。

Big big = new Big("1", new Address("path1"));
Big big2 = new Big("2", new Address("path2"));

优缺点上图所示,比较常见的用法,但是局限性很大

3.2.实现Cloneable接口,重写Clone方法

这个地方需要注意三个点

  • 需要实现Cloneable接口,重写Clone方法,包括被拷贝的对象和对象中的引用对象,例如Test和Address
  • 引用对象属性需要重新赋值,例如  test.address = address.clone();
  • 通过原有对象的clone方法生成新对象,例如  Test test2 = test.clone();
public static void main(String[] args) {
    Test test = new Test("1", new Address("path1"));
    Test test2 = test.clone();
    test2.setStart("2");
    test2.getAddress().setPath("path2");

    System.out.println(test);
    System.out.println(test2);
}


@Data
@AllArgsConstructor
@NoArgsConstructor
class Test implements Cloneable {
    private String start;
    private Address address;

    @Override
    protected Test clone(){
        Test test = null;
        try {
            test = (Test) super.clone();
            test.address = address.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return test;
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Address implements Cloneable{
    private String path;

    @Override
    protected Address clone() {
        Address address = null;
        try {
            address = (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return address;
    }
}

3.3.Apache序列化

Java提供了序列化的能力,可以先将源对象进行序列化,再反序列化生成拷贝对象。但是,使用序列化的前提是拷贝的类需要实现Serializable接口。Apache Commons Lang包对Java序列化进行了封装,我们可以直接使用它。

需要注意三个点:

  • 实现Serializable接口,包括被拷贝的对象和对象中的引用对象,例如Demo和Address
  • 引入Apache第三方Jar包
  • 生成方式:Demo demo2 = SerializationUtils.clone(demo);
public static void main(String[] args) {
    Demo demo = new Demo("1", new Address("path1"));
    Demo demo2 = SerializationUtils.clone(demo);

    demo2.setStart("2");
    demo2.getAddress().setPath("path2");
    System.out.println(demo);
    System.out.println(demo2);
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Demo implements Serializable {
    private String start;
    private Address address;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Address implements Serializable{
    private String path;

    @Override
    protected Address clone() {
        Address address = null;
        try {
            address = (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return address;
    }
}

3.4.Gson序列化

Gson可以将对象序列化成JSON,也可以将JSON反序列化成对象,所以我们可以用它进行深拷贝。

需要注意两个点:

  • 引入Google第三方Jar包
  • 生成方式:
    Gson gson = new Gson();
    Big big2 = gson.fromJson(gson.toJson(big), Big.class);

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.9.0</version>
</dependency>

public static void main(String[] args) {
    Big big = new Big("1", new Address("path1"));

    Gson gson = new Gson();
    Big big2 = gson.fromJson(gson.toJson(big), Big.class);

    big2.setStart("2");
    big2.getAddress().setPath("path2");

    System.out.println(big);
    System.out.println(big2);
}


@Data
@AllArgsConstructor
@NoArgsConstructor
class Big {
    private String start;
    private Address address;
}

3.5.Jackson序列化

Jackson与Gson相似,可以将对象序列化成JSON。不过拷贝的类需要有默认的无参构造函数。这里不再细说了。

public static void main(String[] args) {
    Food food1 =new Food("百事可乐","冰");
    //拷贝
    ObjectMapper objectMapper = new ObjectMapper();
 
    Food food2 =objectMapper.readValue(objectMapper.writeValueAsString(food1), Food.class));
 
    food2.setType("常温");
}

3.6.自定义序列化

在对象类中自定义方法,实现序列化和反序列化

需要注意的是

  • 实现Serializable接口,包括被拷贝的对象和对象中的引用对象
  • 需要增加自定义的deepClone方法,包括被拷贝的对象和对象中的引用对象

@Data
@AllArgsConstructor
@NoArgsConstructor
class Demo implements Serializable {
    private String start;
    private Address address;

    public Object deepClone(){
        ByteArrayInputStream bis=null;
        ObjectInputStream ois=null;
        ByteArrayOutputStream bos=null;
        ObjectOutputStream oos=null;
       try {
           //序列化
           bos=new ByteArrayOutputStream();
           ObjectOutputStream objectOutputStream = oos = new ObjectOutputStream(bos);
           oos.writeObject(this);
           //反序列化
           bis=new ByteArrayInputStream(bos.toByteArray());
           ois=new ObjectInputStream(bis);
           Person p= (Person) ois.readObject();
           return p;
       } catch (IOException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } finally {
           try {
               bos.close();
               oos.close();
               bis.close();
               ois.close();
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
       return null;
    }
    
}

总结:推荐Apache和Gson方式,毕竟都是常用的工具类,代码契合度高

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彼岸花@开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值