一、数据在内存中的存储
1、什么是基本类型和引用类型?
(1)基本类型:就是值类型,变量所对应的内存区域中存储的是值。
(2)引用类型:就是地址类型,变量所对应的内存区域中存储的是地址,真正的数据存放在地址 对应的内存区域里。
2、在方法中声明的变量(局部变量)
每当程序调用方法时,系统都会为该方法建立一个方法栈,在方法中声明的变量,即局部变量就放在方法栈中,方法结束后,系统会释放方法栈,在方法中声明的变量随着栈的销毁而结束,这就是局部变量只能在方法中有效的原因。
在方法中声明的变量可以是基本类型变量,也可以是引用类型变量。
(1)当声明基本类型变量时,其变量名和值是放在栈中。
(2)当声明引用类型变量时,该变量会存储一个内存地址,该内存地址指向所引用的对象。引用 变量名是放在栈中,指向的对象是放在堆中。
3、在类中声明的变量(成员变量)
也叫全局变量是放在堆中的,不会随着某个方法执行结束而销毁。
同样在类中声明的变量可以是基本类型变量,也可以是引用类型变量。
(1)当声明基本类型变量时,其变量名和值是放在堆中。
(2)当声明引用类型变量时,引用变量名和指向的对象都是放在堆中。
二、拷贝
所谓拷贝,就是赋值。把一个变量赋给另外一个变量,就是把变量的内容进行拷贝。把一个对象的值赋给另外一个对象,就是把一个对象拷贝一份。
1、基本类型变量的赋值
基本类型变量赋值时,赋的是数据,不存在深拷贝和浅拷贝的问题。
public class Test {
public static void main(String[] args) {
int x = 20;
int y = x;
System.out.println("此时x和y都是20:");
System.out.println(x);
System.out.println(y);
y = 40;
System.out.println("如果改变y的值,x的值不会改变:");
System.out.println(x);
System.out.println(y);
}
}
2、引用类型变量的赋值(引用拷贝)
引用类型变量赋值时,赋的值是地址。
public class CopyTest {
public static void main(String[] args) {
int[] age1 = new int[]{11,22,33};
int[] age2 = age1;
System.out.println(age1);
System.out.println(age2);
System.out.println(age1[1]);
System.out.println(age2[1]);
age2[1] = 40;
System.out.println(age1[1]);
System.out.println(age2[1]);
}
}
由输出结果可以看出,age1和age2的地址值是相同的,age1和age2只是引用而已,它们都指向了一个相同的对象int[]{11,22,33}
。 这就叫做引用拷贝。创建一个指向对象的引用变量的拷贝,并没有创建出一个新的对象。
如果改变age2所引用的数据,age2[1] = 40时,age1[1]也变为40。原因就是age1和age2引用了同一块内存区域。
只是把age1的地址拷贝一份给了age2,并没有把age1的数据拷贝一份。拷贝的深度不够。
3.对象拷贝
创建一个新的对象,这个对象是对原始对象的精确拷贝。
深拷贝和浅拷贝都是对象拷贝。
实现对象拷贝的类,必须实现Cloneable接口,并覆写clone()方法。
java中的clone
clone即复制, 在Java中, clone方法被对象调用,所以会复制对象。所谓的复制对象,首先要分配一个和原对象同样大小的空间,在这个空间中创建一个新的对象。
在java中,有两种方式可以创建对象:
1. 使用new操作符创建一个对象
new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把它的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
2. 使用clone方法复制一个对象
clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和原对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。
public class Person implements Cloneable {
private int age;
private String name;
public Person(int age,String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person(23,"张三");
Person p2 = p1;
System.out.println(p1);
System.out.println(p2);
}
如果把对象p1直接赋给对象p2,那这只是引用拷贝,它们的地址值是相同的,并没有创建出一个新的对象。
如果改变p2所引用的数据,p1所引用的数据也会跟着改变。
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person(23,"张三");
Person p2 = (Person) p1.clone();
System.out.println(p1);
System.out.println(p2);
}
使用clone()方法后,对象p1和对象p2的地址是不相同的,说明创建了新的对象,因此实现了对象拷贝。
System.out.println(p1.getName());
System.out.println(p2.getName());
p2.setName("李四");
System.out.println(p1.getName());
System.out.println(p2.getName());
如果改变p2所引用的数据,p1所引用的数据不会跟着改变。
4.浅拷贝
拷贝出来的对象仍然保留原有对象的所有引用。
特点:只要任意一个拷贝对象(或原有对象)中的引用发生改变,所有的对象都会受到影响。
public class Student implements Cloneable {
private int age;
private String name;
private Person person;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person(23,"张三");
Student student1 = new Student();
student1.setAge(20);
student1.setName("jack");
student1.setPerson(person);
Student student2 = (Student) student1.clone();
System.out.println(student2.getAge());
System.out.println(student2.getName());
System.out.println(student2.getPerson().getAge());
System.out.println(student2.getPerson().getName());
person.setName("李四");
System.out.println(student1.getPerson().getName());
System.out.println(student2.getPerson().getName());
}
对student1进行对象拷贝,创建了新的对象student2。student1中包含指向Person(23,"张三")的引用,student2中也包含了指向Person(23,"张三")的引用。当改变person的Name为"李四"之后,student1和student2中person的Name也改变为"李四"。
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。“里面的对象”会在原来的对象和它的拷贝对象之间共享。
浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
两个引用student1和student2指向不同的两个对象,它们中的两个person引用指向的是同一个对象。
5.深拷贝
拷贝出来的对象产生了所有引用的新的对象。
特点:修改任意一个对象,不会对其他对象产生影响。
public class Student implements Cloneable {
private int age;
private String name;
private Person person;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public Object clone() throws CloneNotSupportedException{
//浅拷贝时:return super.clone();
//深拷贝:
Student student = (Student) super.clone();
student.setPerson((Person) student.getPerson().clone());
return student;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person(23,"张三");
Student student1 = new Student();
student1.setAge(20);
student1.setName("jack");
student1.setPerson(person);
Student student2 = (Student) student1.clone();
System.out.println(student2.getAge());
System.out.println(student2.getName());
System.out.println(student2.getPerson().getAge());
System.out.println(student2.getPerson().getName());
person.setName("李四");
System.out.println(student1.getPerson().getName());
System.out.println(student2.getPerson().getName());
}
从运行结果看,与浅拷贝的案例不一样的是,当改变person的Name为"李四"之后,student1中person的Name改变为"李四",但是student2中person的Name没有改变,仍然是拷贝过来的“张三”。
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
深拷贝把要复制的对象所引用的对象都复制了一遍。
两个引用student1和student2指向不同的两个对象,它们中的两个person引用指向的也是两个对象。但对person对象的修改只能影响student1对象。