Java把内存划分成两种:一种是堆内存,一种是栈内存。
堆(heap):主要用于存储实例化的对象,数组。由JVM动态分配内存空间,堆内存还可以用来存放由new创建的对象和数组。一个JVM只有一个堆内存,线程是可以共享数据的。
栈(stack):主要用于存储局部变量和对象的引用变量,每个线程都会有一个独立的栈空间,所以线程之间是不共享数据的。
@Data
@AllArgsConstructor
public class Person {
private String name;
private int age;
private String email;
private String desc;
}
class PersonApp {
public static void main(String[] args) {
Person person = new Person("张三", 22, "156xxxx2775@163.com", "我是张三");
//复制对象
Person person1 = person;
person1.setName("李四");
System.out.println("person" + person + " " + person.hashCode());
System.out.println("person1" + person1 + " " + person1.hashCode());
Console.log(person == person1);
}
}
输出: personPerson(name=李四, age=20, email=156xxxx2775@163.com, desc=我是张三) 1528617539 person1Person(name=李四, age=20, email=156xxxx2775@163.com, desc=我是张三) 1528617539 true
直接赋值是Java开发中最简单且常用的方式,通过Demo输出可见,原对象person赋值给新对象person1,并给person1对象的某个字段进行重新赋值,其hashCode是一致的,说明在Java的中并没有创建新的内存地址,而是复制了原对象的引用地址而已。
图片源自
@Data
@AllArgsConstructor
public class Person implements Cloneable {
/**
* 基本类型属性
*/
private String name;
private int age;
private String email;
private PersonDesc personDesc;
public void setDesc(String desc) {
this.personDesc.setDesc(desc);
}
public Person(String name, int age, String email, String desc) {
this.name = name;
this.age = age;
this.email = email;
this.personDesc = new PersonDesc();
this.personDesc.setDesc(desc);
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* 引用类型属性
*
* @return
* @throws CloneNotSupportedException
*/
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person("张三", 20, "123456@qq.com", "Java开发");
Person person1 = (Person) person.clone();
person1.setName("Clone 张三S");
person1.setAge(23);
person1.setDesc("JavaScript");
System.out.println(person + " " + person.hashCode());
System.out.println(person1 + " " + person1.hashCode());
System.out.println(person == person1);
}
}
输出:
Person(name=张三, age=20, email=123456@qq.com, personDesc=PersonDesc(desc=JavaScript)) 1110312536 Person(name=Clone 张三S, age=23, email=123456@qq.com, personDesc=PersonDesc(desc=JavaScript)) 57334109 false
浅拷贝在原对象中的基本类型拷贝中,会复制一份到克隆对象,并在堆中开辟新的内存空间,对于引用类型,则会拷贝引用对象的内存地址,并不会把引用类型也克隆一份到堆内存中,由于原对象和克隆对象是引用的内存地址,因此如果两对象的任何一方改变这个地址,就会影响到另一个对象。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PersonDesc implements Cloneable {
private String desc;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Person.class
@Data
@AllArgsConstructor
public class Person implements Cloneable {
/**
* 基本类型属性
*/
private String name;
private int age;
private String email;
private PersonDesc personDesc;
public void setDesc(String desc) {
this.personDesc.setDesc(desc);
}
public Person(String name, int age, String email, String desc) {
this.name = name;
this.age = age;
this.email = email;
this.personDesc = new PersonDesc();
this.personDesc.setDesc(desc);
}
/**
* 深克隆的方式
*
* @return
* @throws CloneNotSupportedException
*/
@Override
public Object clone() throws CloneNotSupportedException {
Person person = (Person) super.clone();
//对引用对象进行克隆
person.personDesc = (PersonDesc) personDesc.clone();
return person;
}
/**
* 引用类型属性
*
* @return
* @throws CloneNotSupportedException
*/
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person("张三", 20, "123456@qq.com", "Java开发");
Person person1 = (Person) person.clone();
person1.setName("Clone 张三S");
person1.setAge(23);
person1.setDesc("JavaScript");
System.out.println(person + " " + person.hashCode());
System.out.println(person1 + " " + person1.hashCode());
System.out.println(person == person1);
}
}
输出:
Person(name=张三, age=20, email=123456@qq.com, personDesc=PersonDesc(desc=Java开发)) 2056507198 Person(name=Clone 张三S, age=23, email=123456@qq.com, personDesc=PersonDesc(desc=JavaScript)) 57334109 false
2.实现 Serializable 接口方式
Person.class
@Data
@AllArgsConstructor
public class Person implements Serializable {
private static final long serialVersionUID = 369285298572941L;
private String name;
private int age;
private String email;
private PersonDesc personDesc;
public Person(String name, int age, String email, String desc) {
this.name = name;
this.age = age;
this.email = email;
this.personDesc = new PersonDesc();
this.personDesc.setDesc(desc);
}
@Override
public Person clone() {
Person person = null;
try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
person = (Person) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return person;
}
public void setDesc(String desc) {
this.personDesc.setDesc(desc);
}
public static class PersonApp {
public static void main(String[] args) throws Exception {
// 初始化一个对象
Person person = new Person("平头", 20, "123456@qq.com", "技术");
// 复制对象
Person person1 = (Person) person.clone();
// 改变 person1 的属性值
person1.setName("我是平头的克隆对象");
// 修改 person age 的值
person1.setAge(22);
person1.setDesc("我已经关注了技术");
System.out.println("person对象:" + person);
System.out.println();
System.out.println("person1对象:" + person1);
}
}
}
PersonDesc.class
@Data
public class PersonDesc implements Serializable {
private static final long serialVersionUID = 872390113109L;
private String desc;
}
深拷贝是把所有的属性都在内存堆里去创建新的内存地址,克隆对象与原对象是两个不同的对象。