- 引用传递
public static void main(String[] args){
//引用传递
Student stu1 = new Student();
Student stu2 = stu1;
System.out.println(stu1);
System.out.println(stu2);
}
输出结果为:
test_java.Student@1540e19d
test_java.Student@1540e19d
两个student地址相同 也就是说他们是同一个对象 这就是引用的传递 也可称为引用的拷贝
- 浅拷贝
浅拷贝是按位拷贝对象,他会创建一个新的对象(参考上面的引用传递 并没有创建新的对象),新的对象有原始对象属性值的精确拷贝。如果属性是基本数据类型,拷贝的就是基本数据类型的值,如果属性是引用类型,拷贝的就是引用类型的引用地址。所以,如果基本数据类型改变,不会影响另一个对象,如果引用类型的数据发生改变,则另一个对象也会改变。
浅拷贝的条件:必须实现Cloneable接口,并且覆写clone()方法
Student类:
//学生类实现克隆的接口 才可以使用clone方法 克隆对象
class Student implements Cloneable{
private String name;
private int age;
public int getAge() {
return age;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return super.toString();
//return "name: "+name+" age: "+age;
}
@Override
protected Object clone(){
Student stu = null;
try {
stu = (Student)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return stu;
}
public static void main(String[] args){
Student stu1 = new Student();
stu1.setName("李三");
stu1.setAge(21);
Studnet stu2 = stu1;
System.out.println(stu1);
System.out.println(stu1);
}
输出的结果是:
test_java.Student@677327b6
test_java.Student@14ae5a5
从输出结果可以看出,两个student的对象地址不一样,实现了对象的拷贝
但是,在这里需要注意到,类中有一个String类型的饮用对象name,这里的String是同对象一起被拷贝过去了呢?
还是说两个student引用的依然是同一个name对象呢?测试一下:
System.out.println(stu1.getName().hashCode());
System.out.println(stu2.getName().hashCode());
输出结果:
1224543
1224543
可以看出,二者的name对象依然是同一个对象,这也就是典型的浅拷贝。
- 深拷贝
深拷贝会拷贝所有的属性,并且拷贝属性指向的动态分配的内存。当对象和它引用的对象一起拷贝是即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
static class Body implements Cloneable{
public Head head;
public Body() {}
public Body(Head head) {this.head = head;}
@Override
protected Object clone() throws CloneNotSupportedException {
Body newBody = (Body) super.clone();
newBody.head = (Head) head.clone();
return newBody;
}
}
static class Head implements Cloneable{
public Face face;
public Head() {}
public Head(Face face){this.face = face;}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Body body = new Body(new Head());
Body body1 = (Body) body.clone();
System.out.println("body == body1 : " + (body == body1) );
System.out.println("body.head == body1.head : " + (body.head == body1.head));
}
输出结果:
body == body1 : false
body.head == body1.head : false
由此可见, body和body1内的head引用指向了不同的Head对象, 也就是说在clone Body对象的同时, 也拷贝了它所引用的Head对象, 进行了深拷贝。
讨论
经过了上面的实践,可以得出结论:要实现深拷贝一个对象,不光该对象要实现Cloneable接口,实现clone方法;该对象中的引用对象也要实现Cloneable接口,并且实现clone方法。
那么,按照上面的结论, Body类组合了Head类, 而Head类组合了Face类,要想深拷贝Body类,必须在Body类的clone方法中将Head类也要拷贝一份,但是在拷贝Head类时,默认执行的是浅拷贝,也就是说Head中组合的Face对象并不会被拷贝。
只能说这不是彻底的深拷贝,但他还是属于深拷贝。也就是两个对象之间还有些许的联系,没有完全的独立。
clone在平时项目的开发中可能用的不是很频繁,但是区分深拷贝和浅拷贝会让我们对java内存结构和运行方式有更深的了解。至于彻底深拷贝,几乎是不可能实现的,原因已经在上一节中进行了说明。深拷贝和彻底深拷贝,在创建不可变对象时,可能对程序有着微妙的影响,可能会决定我们创建的不可变对象是不是真的不可变。clone的一个重要的应用也是用于不可变对象的创建。
alibaba的规范手册
【强制】关于基本数据类型与包装数据类型的使用标准如下:
1) 所有的 POJO 类属性必须使用包装数据类型。
2) RPC 方法的返回值和参数必须使用包装数据类型。
3) 所有的局部变量推荐使用基本数据类型。
说明: POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE 问题,或者入库检查,都由使用者来保证。
正例: 数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。反例: 某业务的交易报表上显示成交总额涨跌情况,即正负 x%, x 为基本数据类型,调用的RPC 服务,调用不成功时,返回的是默认值,页面显示: 0%,这是不合理的,应该显示成中划线-。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。【推荐】慎用 Object 的 clone 方法来拷贝对象。
说明: 对象的 clone 方法默认是浅拷贝,若想实现深拷贝需要重写 clone 方法实现属性对象的拷贝。