暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Java实战-内存拷贝(等号、深浅拷贝、传参)

程序员的日记本 2020-08-18
866


Java中的等号赋值

//基本数据类型通过等号赋值,肯定会生成一个新的地址,互不干扰
int a = 10;
int b = a; //把 a 赋值给 b
b = 20;
System.out.println("a : " + a);
System.out.println("b : " + b);


//对象数据类型
User u = new User("zs", 10);
User u1 = u;
u.name = "ls";
u.age = 20;


//u修改后,u1的值也被修改;
//通过打印地址也可以看出等号赋值操作后,两者指向同一个堆内存地址
System.out.println(u + " " + u1);
System.out.println(u1.name);


//当u1赋值为null后,u和u1打印的地址已经不相等了
//因为在赋值的同时,Java底层应该是对引用变量(栈内存中)
//做了一个拷贝,拷贝模型如下,两者指向同一个 new User
//栈内存 堆内存
// u ------
// | new User
// u1 ------
//因此从栈中的内存可以看出u和u1已经是不一样的内容了,
//当u1 = null,不会对u产生任何影响
u1 = null;
System.out.println(u + " " + u1);

Java中的Clone

public static void main(String[] args) {
Men m = new Men("zs" , 12, new Son("ww" , 10));
try {
Men m1 = (Men)m.clone();
        //通过打印发现两者地址不一样了,修改值也互不干涉
//栈内存 堆内存(对一个Men对象)
// m ------ new Men
// |
// m1 ------ new Men
System.out.println("m1 == m " + m1 + " " + m);
m.name = "ls";
System.out.println("m1.name == m.name " + m1.name + " " + m.name);
        //假设在new Men中含有另一个对象,
//而另一个对象没有实现Cloneable接口,又会发生什么情况呢?
//打印后,会发现拷贝个 son 的地址是一样的,
//也就是说在这里的拷贝没有完成对值的拷贝
//通过对m的son赋值为null,发现和使用等号赋值后引用变量的变化一致,
//也就是说还是拷贝了一次引用变量


//栈内存 堆内存(不同的Men对象) 堆内存(同一个Son对象)
// m ------ new Men ----
// m.son
// | new Son
// m1 ------ new Men ----
// m1.son


Men m2 = (Men)m.clone();
System.out.println("m2.son == m.son " + m2.son + " " + m.son);
m.son = null;
System.out.println("m2.son == m.son " + m2.son + " " + m.son);
    } catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}


static class Men implements Cloneable{
String name;
int age;
Son son;
    public Men(String name , int age , Son son){
this.name = name;
this.age = age;
this.son = son;
}
    @Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
static class Son{
String name;
int age;
public Son(String name , int age){
this.name = name;
this.age = age;
}
}

如何实现深拷贝

//实现深拷贝方式一
//所有对象都实现Cloneable接口,重写Object中的clone方法
public void fullCopyTypeOne(){
try {
User u = new User("zs" , 10 , new IDCard("123" , "成都"));
User u1 = (User) u.clone();
u.idCard.address = "上海"; //对原数据进行修改
System.out.println("执行深拷贝后,对原数据进行修改后,拷贝后的数据:"+u1.idCard.address); //不影响深拷贝后的数据
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
//实现深拷贝方式二
//仿照C++,实现拷贝构造函数
public void fullCopyTypeTwo(){
User u = new User("zs" , 10 , new IDCard("123" , "成都"));
User u2 = new User(u);
u.idCard.idCard = "456";
System.out.println("执行深拷贝后,对原数据进行修改后,拷贝后的数据:"+u2.idCard.idCard); //不影响深拷贝后的数据
}

参数传递中内存拷贝

值传递

针对值传递是指基本变量的传递,肯定是执行了一次拷贝,基本变量肯定是一次深拷贝,因为执行拷贝后就是两个不同的内容了。


引用传递

引用传递其实是在Java底层对对象做了一次浅拷贝的动作,拷贝了引用变量,操作的堆对象一致。
/**
* 交换指定数组中指定下标的值
* @param arr
* @param index0
* @param index1
*/
public void swap(int[] arr , int index0 , int index1){
System.out.print("打印被传递过来的形参:");
for(int i : arr){
System.out.print(i+" ");
}
System.out.println();


int temp = arr[index0];
arr[index0] = arr[index1];
arr[index1] = temp;


System.out.print("打印对形参执行交换后:");
for(int i : arr){
System.out.print(i+" ");
}
System.out.println();
}
/**
* 将引用变量置为null
*/
public void change(int[] arr){
arr = null;
}
public static void main(String[] args) {
ParamerTransmit pt = new ParamerTransmit();
  int[] arr = new int[]{1,2};
pt.swap(arr , 0 , 1);
  System.out.print("打印调用swap后的数据:");
for(int i : arr){
System.out.print(i+" ");
}
System.out.println();
  /**
* 执行了代码后,可以看到通过对传递的形参所指向的对象
* 进行操作的同时,原数据发生了改变,表示形参所指向的对象
* 与原引用变量所指向的对象相同
*/
  System.out.println("调用change方法前arr地址: "+arr);


/**
*在change中置为null,对原始arr无影响,表示执行了一次浅拷贝,
*将原始变量的引用变量做了一次拷贝
*/
pt.change(arr);
System.out.println("调用change方法后arr地址: "+arr);
}

总结

浅拷贝指拷贝对象的引用变量,不对对象内存中的值进行拷贝;深拷贝就是生成一个与原始数据地址完全不一样的数据。

实战经验-合理应用深浅拷贝

1、在项目中合理利用深浅拷贝,完成副本的创建,使在不同线程中操作各自线程内的私有对象。
2、注意一个对象被传递给多个方法,修改一处导致多个方法中都会被修改。
3、引用传递的变量改变了指向的堆对象后,对原有数据不影响。

文中所有代码均可以在线下载

https://gitee.com/wsyjiamian/JavaDiary.git


文章转载自程序员的日记本,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论