前言
CV程序员:熟练使用ctrl+c和ctrl+v或者command+c和command+v的程序员,CV程序员分两种,第一种见多识广,游走在各种搜索引擎、博客以及项目中,搜寻着自己想要的代码,第一种是把自己的代码从一个类复制到另一个类。
我觉得如果是作为一个刚起步的程序员来说,CV别人的代码挺好的,因为至少他具备了搜索以及自己解决问题的能力。这种能力和学习方式是非常重要的,因为很多问题都需要自己去解决一遍才会慢慢积累经验,向更高的水平进阶,但是要注意的是懂得回顾和反思,虽然已经把代码CV过来了,但是事后需要思考代码为什么要这么写,而不是解决了就丢一旁。日积跬步,终有一天你的代码也会被别人用来CV。而如果是CV自己的代码就要注意了,因为你在写着冗余的代码,需要思考为什么有这么多重复的代码,是否可以做一些抽象设计或者运用一些设计模式来避免代码的冗余。
今天要讲的是CV程序员都要掌握的对象拷贝,主要是从以下三个方面开始讲解:
克隆什么?
怎么克隆?
为什么要克隆?
深拷贝和浅拷贝区别是什么?
克隆什么?
要讲克隆,先弄清楚第一个问题:克隆什么东西?这还用回答吗,当然是克隆对象。但是到底克隆的是什么类型的对象?
我们知道在 Java中基本数据类型和引用数据类型之分,基本数据类型与引用数据类型最本质的区别是:
- 基本数据类型直接包含值,在被创建时,在栈上给其划分一块内存,将数值直接存储在栈上。
- 引用数据类型被创建时,首先要在栈上给其引用(句柄)分配一块内存,而对象的具体信息都存储在堆内存上,然后由栈上面的引用指向堆中对象的地址,可以理解为栈上面保存的是堆中对象的地址。
而我们所说的克隆,可以分这两种类型的对象来讨论。
偷偷引入一个知识点:
装箱:将基本数据类型转换为引用数据类型。比如int转化为Integer。
拆箱:将引用数据类型转换为基本数据类型。比如Integer转化为int。
怎么克隆?
弄清楚了要克隆什么,那么我们来看看怎么克隆。还是分为克隆基本类型对象和引用类型对象:
基本类型对象克隆
下面是一个基本类型的克隆示例:
public static void main(String[] args) {
int a = 1;
// 用"="克隆
int b = a;
System.out.println("a=" + a);
System.out.println("b=" + b);
// 测试是否修改了a的值
b = 2;
System.out.println("修改了b的值后,a=" + a);
System.out.println("修改了b的值后,b=" + b);
}
运行结果
对于基本类型来说,直接用“=”就能完成克隆,并且这样克隆出来的对象是完全独立于原来的对象的。
引用类型对象
引用类型对象的克隆有两种方法
- 用”=“进行克隆
- 用Object类的clone方法进行克隆
下面先定义一个类:
public class School implements Cloneable {
/**
* 校名
*/
String name;
/**
* 校长
*/
String president;
public School(String name, String president) {
this.name = name;
this.president = president;
}
public void setName(String name) {
this.name = name;
}
public void setPresident(String president) {
this.president = president;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "School:[name=" + name + "," +
"president=" + president + "]";
}
}
用”=“进行克隆
示例
public static void main(String[] args) throws CloneNotSupportedException {
School school = new School("家里蹲", "crazyhzm");
// 用"="克隆
School schoolDuplicate = school;
System.out.println("school的hashcode=" + school.hashCode());
System.out.println("schoolDuplicate的hashcode=" + schoolDuplicate.hashCode());
System.out.println("school的值=" + school.toString());
System.out.println("schoolDuplicate的值=" + schoolDuplicate.toString());
// 修改 schoolDuplicate的值
schoolDuplicate.setPresident("加点代码调调味");
System.out.println("修改schoolDuplicate的值后,school的值=" + school.toString());
System.out.println("修改schoolDuplicate的值后,schoolDuplicate的值=" + schoolDuplicate.toString());
}
运行结果
从运行结果看,用“=”进行克隆,其实只是克隆了原对象的内存地址,schoolDuplicate和school对象都指向了同一块内存区域,
用Object类的Clone方法进行克隆
示例
public static void main(String[] args) throws CloneNotSupportedException {
School school = new School("家里蹲", "crazyhzm");
// 用"clone"克隆
School schoolDuplicateForClone = (School) school.clone();
System.out.println("school的hashcode=" + school.hashCode());
System.out.println("schoolDuplicateForClone的hashcode=" + schoolDuplicateForClone.hashCode());
System.out.println("school的值=" + school.toString());
System.out.println("schoolDuplicateForClone的值=" + schoolDuplicateForClone.toString());
// 修改 schoolDuplicateForClone的值
schoolDuplicateForClone.setPresident("点个赞");
System.out.println("修改schoolDuplicateForClone的值后,school的值=" + school.toString());
System.out.println("修改schoolDuplicateForClone的值后,schoolDuplicateForClone的值=" + schoolDuplicateForClone.toString());
}
运行结果
为什么要克隆?
了解了克隆什么以及怎么克隆后,我们到底为什么要使用克隆?
从JDK1.0版本开始,Java语言就提供了克隆机制。要知道我们为什么要克隆,先来了解一下克隆这种行为有什么方法替代,对于CV程序员来说,如果不能 复制黏贴,那就知道自己把代码一行行自己敲上去。那么对于对象的克隆来说,无非就是自己new一个新的对象,然后将其中的属性一个一个赋值过去,如果遇到引用数据类型,还需要继续new,继续赋值。当想要创建的新对象的属性值与原来的对象的属性值保持一致时,这种手动new,再一个一个赋值的行为就看似有点繁琐了,这种时候我们就可以用到克隆。从上面怎么克隆可以看到会出现两种不一样的结果,在克隆中,需要搞清楚什么是深拷贝和浅拷贝。
深拷贝和浅拷贝区别是什么?
在这里姑且把克隆改为拷贝,它存在深拷贝和浅拷贝,我们知道一个对象的属性中可能存在着另一个对象的引用,而这个对象是引用数据类型的。而浅克隆后的对象中非基本对象和原对象指向同一块内存,因此对这些非基本对象的修改会同时更改克隆前后的对象。深克隆可以实现完全的克隆,可以用反射的方式或序列化的方式实现。具体是怎么样的,我修改一下上述的示例来介绍:
浅拷贝
下面还是School类的定义,但是新增了一个Grade属性。
public class School implements Cloneable {
/**
* 校名
*/
String name;
/**
* 校长
*/
String president;
Grade grade;
public School(String name, String president) {
this.name = name;
this.president = president;
}
public School(String name, String president, Grade grade) {
this.name = name;
this.president = president;
this.grade = grade;
}
public void setName(String name) {
this.name = name;
}
public void setPresident(String president) {
this.president = president;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", president='" + president + '\'' +
", grade=" + grade.toString() +
'}';
}
}
Grade类定义:
public class Grade {
/**
* 年级名称
*/
String name;
public Grade(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Grade{" +
"name='" + name + '\'' +
'}';
}
}
示例代码
public static void main(String[] args) throws CloneNotSupportedException {
Grade grade = new Grade("一年级");
School school = new School("家里蹲", "crazyhzm", grade);
// 用"clone"克隆
School schoolDuplicateForClone = (School) school.clone();
System.out.println("school的hashcode=" + school.hashCode());
System.out.println("school.grade的hashcode=" + school.grade.hashCode());
System.out.println("schoolDuplicateForClone的hashcode=" + schoolDuplicateForClone.hashCode());
System.out.println("schoolDuplicateForClone.grade的hashcode=" + schoolDuplicateForClone.grade.hashCode());
System.out.println("school的值=" + school.toString());
System.out.println("schoolDuplicateForClone的值=" + schoolDuplicateForClone.toString());
// 修改 schoolDuplicateForClone的值
schoolDuplicateForClone.setPresident("点个赞");
schoolDuplicateForClone.grade.setName("二年级");
System.out.println("修改schoolDuplicateForClone的值后,school的值=" + school.toString());
System.out.println("修改schoolDuplicateForClone的值后,schoolDuplicateForClone的值=" + schoolDuplicateForClone.toString());
}
运行结果
从运行结果可以看到,School的拷贝对象修改了年级名称后,被拷贝对象的值也被修改了,并且可以从他们的hashcode也能看出来,在拷贝的时候都指向了同一个内存区域,这就是所谓的浅拷贝。
深拷贝
深拷贝我们稍微修改一下上述的School以及Grade代码
school类
修改了clone方法。
public class School implements Cloneable {
/**
* 校名
*/
String name;
/**
* 校长
*/
String president;
Grade grade;
public School(String name, String president) {
this.name = name;
this.president = president;
}
public School(String name, String president, Grade grade) {
this.name = name;
this.president = president;
this.grade = grade;
}
public void setName(String name) {
this.name = name;
}
public void setPresident(String president) {
this.president = president;
}
@Override
protected Object clone() throws CloneNotSupportedException {
School school = (School) super.clone();
school.grade = (Grade) grade.clone();
return school;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", president='" + president + '\'' +
", grade=" + grade.toString() +
'}';
}
}
Grade类
Grade实现了Cloneable接口。
public class Grade implements Cloneable{
/**
* 年级名称
*/
String name;
public Grade(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Grade{" +
"name='" + name + '\'' +
'}';
}
}
还是执行上述示例代码,运行后结果为:
可以看到school中grade属性也被拷贝,修改年级名称后,原对象的数据并没有随之改变,这就是深拷贝。