java的深拷贝和浅拷贝

java的拷贝

最近碰到了一个关于List赋值的问题,其中涉及到了关于java的值传递和引用传递还有深拷贝和浅拷贝的问题。特此记录下。

List的赋值问题

首先,java的数据类型分为两大类,基本数据类型和引用数据类型,而引用数据类型包括类引用、接口和数组引用。其中List集合就属于接口应用。所以我们如果直接赋值,相当于是一个指针,但并未其分配具体的完整的对象所需要的空间。如果我们修改新的集合对象。原先的也会发生改变。

        List<String> list = new ArrayList<>();
        list.add("1");
        List<String> newList = list;
        newList.set(0,"2");
        System.out.println(list.get(0));
        System.out.println(newList.get(0));

在这里插入图片描述

通过结果我们可以发现 list的值也发生了改变 。

如果我们希望单纯的把值赋予给另外一个对象。有以下方式:

(1) addAll()方法

我们可以通过addAll方法 直接把另外一个集合的值添加到另外一个集合

 		List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        //List<Integer> newList = list;
        List<String> newList = new ArrayList<>();
        newList.addAll(list);
        newList.set(0,"3");
        newList.set(1,"4");
        System.out.println(list.get(0) + "=========" +list.get(1));
        System.out.println(newList.get(0) + "=========" + newList.get(1));

在这里插入图片描述

此时,我们改变新的list值,原来的没有发生改变。但是当我们存储的是类类型时。

 		Student stu1 = new Student("zhangsan",18);
        List<Student> stulist = new ArrayList<>();
        stulist.add(stu1);
        List<Student> copystulist = new ArrayList<>();
        System.out.println(stulist.get(0).toString());
        copystulist.addAll(stulist);
        copystulist.get(0).setAge(19);
        copystulist.get(0).setName("lisi");
        System.out.println(stulist.get(0).toString());
        System.out.println(copystulist.get(0).toString());

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rz7yBVfI-1623326631673)(C:\Users\LH\AppData\Roaming\Typora\typora-user-images\image-20210608184720785.png)]

我们发现此时的值依然发生了改变。 那么为什么会发生这种情况呢

根据查阅的资料可得,我们无论是通过addAll或者是循环赋值的方法此时会根据存储的数据类型不同有所改变。当类型为引用数据类型时,此时传递为浅拷贝,传递的是地址,所以我们在改变值后原先的也会发生改变。而String虽然是引用类型,但是我们知道String的值是不会发生改变的。我们重新改变它的值的时候相当于重新创建了一个新的对象,而原先的值没有发生改变,所以当存储的为String类型的时候值没有发生改变。而当存储的类型为包装类例如Integer、Double等值时,我们看源码,可以发现包装类并没有提供value对应的setter方法。我们可以看到Integer类中的value是通过final来进行修饰的。这说明一旦Integer类创建之后他的值就不能在被修改了。我们进行修改的时候Integer是创建一个新的类。所以值也没有发生改变。而当我们传递的是数组时,为引用类型,相当于传的是地址。所以此时原先的值也发生了改变。

private final double value;

我们通过这个问题,复习一下值传递和引用传递的过程。

在java方法中参数与列表有两种类型的参数,基本类型和引用类型。

基本类型:值存放在局部变量表中,无论如何修改只会修改当前栈的值,方法执行结束对方法外不会做任何改变。

引用数据类型:指针存放在局部变量表中,调用方法的时候,副本引用压栈,赋值仅改变副本的引用。但是此时两个引用指向同一个对象。所以在方法内修改了引用地址的对象,此时方法外的也会发生改变。

String和包装类对象依然为特殊对象。

我们通过这个问题。来复习一下java的深拷贝和浅拷贝的问题。

  • 浅拷贝:对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是直接将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改。不会影响另一个对象拷贝得到的数据。对于数据类型是引用数据类型的成员变量,比如数据、某个类的对象,那么浅拷贝会进行引用传递,也就是将该变量的引用值复制一份给新的对象,在这种情况下,在一个对象中修改该成员变量会影响到另外一个对象的该成员变量值。
  • 深拷贝:对于深拷贝来说,不仅要赋值对象的所有基本类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,也就是说,对象进行深拷贝要对整个对象图进行拷贝。

我们可以通过重写clone()方法进行浅拷贝,Object类是类结构的根类,其中有一个方法为protected Object clone() throws CloneNotSupportedException,这个方法就是进行的浅拷贝。有了这个浅拷贝模板,我们可以通过调用clone()方法来实现对象的浅拷贝。但是需要注意:1、Object类虽然有这个方法,但是这个方法是受保护的(被protected修饰),所以我们无法直接使用。2、使用clone方法的类必须实现Cloneable接口,否则会抛出异常CloneNotSupportedException。对于这两点,我们的解决方法是,在要使用clone方法的类中重写clone()方法,通过super.clone()调用Object类中的原clone方法。

我们可以看看下面的例子:

public class Student implements Cloneable{ //创建学生类
    private String name;
    private List<String> listInfo;
    private Age age;
    public Object clone(){
        Object obj = null;
        try{
            obj = super.clone();//调用Object的clone方法
        }catch (Exception e){
            e.printStackTrace();
        }
        return obj;
    }
}
public class Age {//创建年龄类
    private int age;
}
    @Test
    public void Test(){
        Age age = new Age(20);
        List<String> list = new ArrayList<>();
        list.add("1");
        Student stu = new Student("zhangsan",list,age);
        System.out.println("改变前" + stu.toString());
        Student stu1 = (Student)stu.clone();//浅拷贝后的类
        System.out.println("copy" + stu1.toString());
        List<String> copy = stu1.getListInfo();
        copy.set(0,"2");//修改值
        stu1.setName("lisi");
        age.setAge(99);
        System.out.println(stu.toString());
        System.out.println(stu1.toString());
    }

在这里插入图片描述

运行结果如上,Student类的成员变量设置了三种,一个String类型,一个集合类型,一个类类型(这里设置的稍微有点问题,应该加个基本数据类型,因为集合类型和类类型同属引用数据类型)。

通过分析结果,我们有如下发现:

  1. String类型是比较特殊的。虽然String类型属于引用类型,但是由于它本身的不可改变性。当我从zhangsan修改为lisi后,它相当于创建了一个新的变量,然后指向发生了改变。所以呈现出来的结果就是String没有改变。
  2. 对于引用数据类型传递的是地址,所以当我们修改了值之后,原先的也会改变。
  3. 对于基本数据类型,属于值传递,所以修改值后不会应吸纳过另一个对象的该属性。

接下来,我们谈谈深拷贝。

我们也可以通过重写Clone的方法来实现深拷贝,基本思路与浅拷贝类型,我们需要为类中的每一个对象都重写clone方法。最后在最顶层的类的重写的clone方法中调用所有的clone方法就可以。

例如:我们刚才创建了一个Student对象,通过clone的方式,我们可以实现基本数据数据的深拷贝。而类类型比如我们的Age类型只能实现浅拷贝 。可是我们发现Age类型里只有一个int 类型的基本数据类型。所以我们可以在对Age使用clone。所以就是对每一层的对象都使用浅拷贝就可以达到深拷贝的效果。

还是上面的例子:

public class Student implements Cloneable{ //创建学生类
    private String name;
    private List<String> listInfo;
    private Age age;
    public Object clone(){
        Object obj = null;
        try{
            obj = super.clone();//调用Object的clone方法
            Student stu = (Student)obj;
            stu.setAge((Age)stu.getAge().clone());//对age进行浅拷贝   也要在Age类里面实现clone方法
        }catch (Exception e){
            e.printStackTrace();
        }
        return obj;
    }
}

在这里插入图片描述

通过分析运行结果,我们发现把age设置为99之后,新的没有发生改变,依然是20.而旧的age属性变为了99.说明我们实现了Age的深拷贝。

当然,还有其它实现深拷贝或者浅拷贝的方式,就不在这里一一叙述了。大家有需要的可以继续查找其它资料。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值