对象拷贝

对象拷贝

对象拷贝(Object Copy)就是将一个对象的属性拷贝到另一个有着相同类类型的对象中去。在程序中拷贝对象是很常见的,主要是为了在新的上下文环境中复用对象的部分或全部数据。Java中有三种类型的对象拷贝:浅拷贝(Shallow Copy)、深拷贝(Deep Copy)、延迟拷贝(Lazy Copy)。

浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
enter description here
在上图中,SourceObject有一个int类型的属性 “field1”和一个引用类型属性”refObj”(引用ContainedObject类型的对象)。当对SourceObject做浅拷贝时,创建了CopiedObject,它有一个包含”field1”拷贝值的属性”field2”以及仍指向refObj本身的引用。由于”field1”是基本类型,所以只是将它的值拷贝给”field2”,但是由于”refObj”是一个引用类型, 所以CopiedObject指向”refObj”相同的地址。因此对SourceObject中的”refObj”所做的任何改变都会影响到CopiedObject。
实现浅拷贝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Student implements Cloneable { 
// 对象引用
private Subject subj;
private String name;
public Student(String s, String sub) {
name = s;
subj = new Subject(sub);
}
public Subject getSubj() {
return subj;
}
public String getName() {
return name;
}
public void setName(String s) {
name = s;
}
/**
* 重写clone()方法
* @return
*/
public Object clone() {
//浅拷贝
try {
// 直接调用父类的clone()方法
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}

深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
enter description here
实现深拷贝:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Student implements Cloneable { 
// 对象引用
private Subject subj;
private String name;
public Student(String s, String sub) {
name = s;
subj = new Subject(sub);
}
public Subject getSubj() {
return subj;
}
public String getName() {
return name;
}
public void setName(String s) {
name = s;
}
/**
* 重写clone()方法
*
* @return
*/
public Object clone() {
// 深拷贝,创建拷贝类的一个新对象,这样就和原始对象相互独立
Student s = new Student(name, subj.getName());
return s;
}
}

对象和对象的引用

对象一般存储在堆中,而引用存储在速度更快的堆栈中。

1
2
3
4
5
6
7
8
9
  class Vehicle {

int passengers;

int fuelcap;

int mpg;

}

用这个类创建对象, Vehicle veh1 = new Vehicle();
上述操作分为四步

  1. “new Vehicle”,是以Vehicle类为模板,在堆空间里创建一个Vehicle对象
  2. 末尾的()意味着,在对象创建后,立即调用Vehicle类的构造函数,对刚生成的对象进行初始化。
  3. 左边的“Vehicle veh 1”在栈内创建了一个Vehicle类引用变量。所谓Vehicle类引用,就是可以用来指向Vehicle对象的对象引用。
  4. =”操作符使对象引用指向刚创建的那个Vehicle对象。

上述操作等价于Vehicle veh1; veh1 = new Vehicle();

那么 Vehicle veh2; veh2 = veh1;首先创建一个Vehicle对象引用vehicle,然后将veh1引用复制给veh2引用。veh2就指向veh1所指向的对象。那么问题是第一个new Vehicle对象呢?它已成为垃圾回收机制的处理对象。至于什么时候真正被回收,那要看垃圾回收机制的心情了。

引用引发的问题

回到上篇博文的问题,首先为了简化理解用String进行等价替换复现问题。

1
2
3
4
5
6
7
8
9
   public void app(String s) {
String str = "sdf";
s = str;
}
public static void main(String[] args) {
String s = null;
new Solution().app(s);
System.out.println(s);
}

猜猜输出是什么?sdf吗?天真。还是null.
真是哔了。。为囊子呢?这个就是因为引用引发的问题。
用图来说明。
开始传参后,创建了S(形)引用并指向null关键字。
enter description here
执行S(形) = str后
enter description here
可以看出执行 S(形) = str 操作后, S(形)引用指向了“sdf”。而S(实)仍坚定的指向null。=号操作会复制引用,S(形)会指向str所指向的对象。
在JAVA里,“=”不能被看成是一个赋值语句,它不是在把一个对象赋给另外一个对象,它的执行过程实质上是将右边对象的地址传给了左边的引用,使得左边的引用指向了右边的对象。JAVA表面上看起来没有指针,但它的引用其实质就是一个指针,引用里面存放的并不是对象,而是该对象的地址,使得该引用指向了对象。在JAVA里,“=”语句不应该被翻译成赋值语句,因为它所执行的确实不是一个赋值的过程,而是一个传地址的过程,被译成赋值语句会造成很多误解,译得不准确。

总结

Java传参的问题,并且它的标准答案是Java只有一种参数传递方式:那就是按值传递,即Java中传递任何东西都是传值。如果传入方法的是基本类型的东西,你就得到此基本类型的一份拷贝。如果是传递引用,就得到引用的拷贝。

文章目录
  1. 1. 对象拷贝
    1. 1.1. 浅拷贝
    2. 1.2. 深拷贝
  2. 2. 对象和对象的引用
  3. 3. 引用引发的问题
  4. 4. 总结
|