一、先了解一下JVM的存储结构吧~
Java数据类型主要分为基本类型和引用类型两大类,如下的代码是如何存储的呢?
int a = 10;
int b = 20;
String c = "hello";
String d = "world";
d = c;
二、浅拷贝
1、浅拷贝例子
package test;
public class Person implements Cloneable {
private String name;
private int age;
private LikeInfo likeInfo;
public Person(String name, int age, LikeInfo likeInfo) {
this.name = name;
this.age = age;
this.likeInfo = likeInfo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public LikeInfo getLikeInfo() {
return likeInfo;
}
public void setLikeInfo(LikeInfo likeInfo) {
this.likeInfo = likeInfo;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", likeInfo=" + likeInfo +
'}';
}
private static class LikeInfo {
private String book;
private String song;
public LikeInfo(String book, String song) {
this.book = book;
this.song = song;
}
public String getBook() {
return book;
}
public void setBook(String book) {
this.book = book;
}
public String getSong() {
return song;
}
public void setSong(String song) {
this.song = song;
}
@Override
public String toString() {
return "LikeInfo{" +
"book='" + book + '\'' +
", song='" + song + '\'' +
'}';
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person("小李", 20, new LikeInfo("白夜行", "七里香"));
Person p2 = (Person) p1.clone();
System.out.println("p1:" + p1.toString());
System.out.println("p2:" + p2.toString());
p1.setAge(25);
p1.getLikeInfo().setSong("稻香");
System.out.println(">>>>>>修改后");
System.out.println("p1:" + p1.toString());
System.out.println("p2:" + p2.toString());
}
}
输出:
p1:Person{name='小李', age=20, likeInfo=LikeInfo{book='白夜行', song='七里香'}}
p2:Person{name='小李', age=20, likeInfo=LikeInfo{book='白夜行', song='七里香'}}
>>>>>>修改后
p1:Person{name='小李', age=25, likeInfo=LikeInfo{book='白夜行', song='稻香'}}
p2:Person{name='小李', age=20, likeInfo=LikeInfo{book='白夜行', song='稻香'}}
2、浅拷贝例子解析
(1)、Person实现Cloneable 接口,使用clone()克隆方法时要注意类型转换以及抛出异常CloneNotSupportedException
(2)、测试案例先新建一个Person对象p1,并由p1拷贝出一个新对象p2,但是可以看到修改p1的LikeInfo属性后,p2属性也发生了变化,说明p1和p2的LikeInfo属性引用对象其实是同一个
3、浅拷贝结论
创建一个新对象,然后将当前对象的非静态字段复制到该新对象,如果字段是基本类型,那么对该字段执行复制;如果该字段是引用类型的,则复制引用但不复制引用的对象。因此,引用类型对象在浅拷贝前后其实引用的是同一个对象,如对其进行修改,则浅拷贝前后的对象都发生变化。
三、深拷贝
1、修该为深拷贝
package test;
public class Person implements Cloneable {
private String name;
private int age;
private LikeInfo likeInfo;
public Person(String name, int age, LikeInfo likeInfo) {
this.name = name;
this.age = age;
this.likeInfo = likeInfo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public LikeInfo getLikeInfo() {
return likeInfo;
}
public void setLikeInfo(LikeInfo likeInfo) {
this.likeInfo = likeInfo;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", likeInfo=" + likeInfo +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person p = (Person) super.clone();
p.setLikeInfo((LikeInfo) getLikeInfo().clone());
return p;
}
private static class LikeInfo implements Cloneable{
private String book;
private String song;
public LikeInfo(String book, String song) {
this.book = book;
this.song = song;
}
public String getBook() {
return book;
}
public void setBook(String book) {
this.book = book;
}
public String getSong() {
return song;
}
public void setSong(String song) {
this.song = song;
}
@Override
public String toString() {
return "LikeInfo{" +
"book='" + book + '\'' +
", song='" + song + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person("小李", 20, new LikeInfo("白夜行", "七里香"));
Person p2 = (Person) p1.clone();
System.out.println("p1:" + p1.toString());
System.out.println("p2:" + p2.toString());
p1.setAge(25);
p1.getLikeInfo().setSong("稻香");
System.out.println(">>>>>>修改后");
System.out.println("p1:" + p1.toString());
System.out.println("p2:" + p2.toString());
}
}
输出:
p1:Person{name='小李', age=20, likeInfo=LikeInfo{book='白夜行', song='七里香'}}
p2:Person{name='小李', age=20, likeInfo=LikeInfo{book='白夜行', song='七里香'}}
>>>>>>修改后
p1:Person{name='小李', age=25, likeInfo=LikeInfo{book='白夜行', song='稻香'}}
p2:Person{name='小李', age=20, likeInfo=LikeInfo{book='白夜行', song='七里香'}}
2、深拷贝例子解析
由浅拷贝的例子修改为深拷贝的方法:
(1)、引用类型的字段需要实现Cloneable接口
(2)、重写clone()方法,将引用类型字段也进行一次克隆
3、深拷贝结论
创建一个对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是基本类型还是引用类型,都复制独立的一份。当修改其中一个对象的任何内容时,都不会影响另一个对象的内容。
四、clone() 的替代方案
使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
package test;
public class Person {
private String name;
private int age;
private LikeInfo likeInfo;
public Person(Person source) {
this.name = source.name;
this.age = source.age;
LikeInfo likeInfo = source.likeInfo;
this.likeInfo = new LikeInfo(likeInfo.book, likeInfo.song);
}
public Person(String name, int age, LikeInfo likeInfo) {
this.name = name;
this.age = age;
this.likeInfo = likeInfo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public LikeInfo getLikeInfo() {
return likeInfo;
}
public void setLikeInfo(LikeInfo likeInfo) {
this.likeInfo = likeInfo;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", likeInfo=" + likeInfo +
'}';
}
private static class LikeInfo {
private String book;
private String song;
public LikeInfo(String book, String song) {
this.book = book;
this.song = song;
}
public String getBook() {
return book;
}
public void setBook(String book) {
this.book = book;
}
public String getSong() {
return song;
}
public void setSong(String song) {
this.song = song;
}
@Override
public String toString() {
return "LikeInfo{" +
"book='" + book + '\'' +
", song='" + song + '\'' +
'}';
}
}
public static void main(String[] args) {
Person p1 = new Person("小李", 20, new LikeInfo("白夜行", "七里香"));
Person p2 = new Person(p1);
System.out.println("p1:" + p1.toString());
System.out.println("p2:" + p2.toString());
p1.setAge(25);
p1.getLikeInfo().setSong("稻香");
System.out.println(">>>>>>修改后");
System.out.println("p1:" + p1.toString());
System.out.println("p2:" + p2.toString());
}
}
输出:
p1:Person{name='小李', age=20, likeInfo=LikeInfo{book='白夜行', song='七里香'}}
p2:Person{name='小李', age=20, likeInfo=LikeInfo{book='白夜行', song='七里香'}}
>>>>>>修改后
p1:Person{name='小李', age=25, likeInfo=LikeInfo{book='白夜行', song='稻香'}}
p2:Person{name='小李', age=20, likeInfo=LikeInfo{book='白夜行', song='七里香'}}