对值传递和引用传递、深拷贝浅拷贝的记录
我觉得对于值传递引用传递和深拷贝浅拷贝可以看作是一类问题,下面大体记录一下,如有错误,请指正。
值传递:形参和实参的内容不同,传递过程就是将实参的值复制一份传递到函数中,这样如果在函数中对该值(形参的值)进行了操作将不会影响实参的值
引用传递:形参和实参的内容相同,传递过程就是将实参的地址传到函数中,形参和实参始终指向同一块内存地址,也就是说操作的其实都是源数据,所以方法的执行将会影响到实际对象
看下面例子:
public class Student {
public String id;
public Student(String id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
'}';
}
}
public class MyTest {
public static void main(String[] args) {
int mInt = 1;
Student student = new Student("1");
test(mInt, student);
System.out.println(mInt);
System.out.println(student);
Integer integer = 1;
String string = "1";
test2(integer, string);
System.out.println(integer);
System.out.println(string);
}
private static void test(int mInt, Student student) {
mInt = 5;
student.id = "123";
}
private static void test2(Integer mInteger, String mString) {
mInteger = 5;
mString = "123";
}
}
输出:
1
Student{id='123'}
1
1
通过上面代码输出可以看到,上面两个方法Student在方法中改变值后其外面的值也发生了改变,int、Integer、String在方法中修改后外面的值没发生改变。得出如下结论:
基本数据类型就是值传递
引用数据类型是引用传递
对于String、基本数据类型的包装类等immutable类型可以理解为值传递,但我觉得也是引用传递,但是在修改新对象的值时,会重新创建一个对象,原有对象保持不变。
浅拷贝:如果属性是基本数据类型,拷贝的就是基本数据类型的值;如果属性是引用数据类型(内存地址),拷贝的就是内存地址即数据的引用。而其所指向对象的引用仍然指向原来的对象。
深拷贝:复制出来的所有变量都含有与原来的对象相同的值,那些引用其他对象的变量将指向复制出来的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。深拷贝相比于浅拷贝速度较慢并且花销较大。
看下面代码。
public class People {
public String name;
public int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class MyTest {
public static void main(String[] args) {
int mInt1 = 5;
int mInt2 = mInt1;
mInt2 = 10;
System.out.println(mInt1);
System.out.println(mInt2);
String mString1 = "5";
String mString2 = mString1;
mString2 = "10";
System.out.println(mString1);
System.out.println(mString2);
People srcPeople = new People("Bill", 26);
People destPeople = srcPeople;
destPeople.age = 30;
System.out.println(srcPeople);
System.out.println(destPeople);
}
}
输出:
5
10
5
10
People{name='Bill', age=30}
People{name='Bill', age=30}
通过输出可以得到如下结论:
int和String赋值后改变新的变量的值,原来的值并没有发生改变。而对于People对象,赋值后改变新变量的值原来的值也发生了改变。说明对于基本数据类型拷贝的是基本数据类型的值;对于String类型,拷贝的是其引用地址,但在修改新对象的值时,会从字符串池中生成一个新的字符串,原有对象保持不变;对于引用数据类型,拷贝的是其引用地址。
那对于上面例子我想改变destPeople的值而不影响srcPeople的值该如何做呢。
方案一 对于要拷贝的对象实现Cloneable接口,并重现Object类的clone()方法,调用super.clone(),然后赋值是使用x…clone():
public class People implements Cloneable {
public String name;
public int age;
public People(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected People clone() {
try {
return (People) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class MyTest {
public static void main(String[] args) {
People srcPeople = new People("Bill", 26);
People destPeople = srcPeople.clone();
destPeople.age = 30;
System.out.println(srcPeople);
System.out.println(destPeople);
}
}
输出:
People{name='Bill', age=26}
People{name='Bill', age=30}
上面People中的成局变量只有String和int类型,如果People的成员变量有其他对象(比如下面Student对象)或集合的话,Student也需要实现Cloneable接口重现clone()方法,集合的话需要重新创建一个对象重新赋值,具体看下面代码:
public class Student implements Cloneable {
public String id;
public Student(String id) {
this.id = id;
}
@Override
protected Student clone() {
try {
return (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
'}';
}
}
public class People implements Cloneable {
public String name;
public int age;
public Student student;
public List<Student> list;
public People(String name, int age) {
this.name = name;
this.age = age;
}
public void setStudent(Student student) {
this.student = student;
}
public void setList(List<Student> list) {
this.list = list;
}
@Override
protected People clone() {
try {
People people = (People) super.clone();
people.student = student.clone();
List<Student> newList = new ArrayList<>();
for (Student s : list) {
newList.add(s.clone());
}
people.list = newList;
return people;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", student=" + student +
", list=" + list +
'}';
}
}
方案二,通过序列化实现对象的拷贝,对要拷贝的对象实现Serializable接口,然后通过调用CloneUtils的clone()即可,看下面代码:
// 拷贝工具类
public class CloneUtils {
public static <T extends Serializable> T clone(T srcData) {
T cloneData = null;
ByteArrayOutputStream byteArrayOutputStream = null;
ObjectOutputStream objectOutputStream = null;
ByteArrayInputStream byteArrayInputStream = null;
ObjectInputStream objectInputStream = null;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(srcData);
byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
objectInputStream = new ObjectInputStream(byteArrayInputStream);
cloneData = (T) objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
close(objectInputStream);
close(byteArrayInputStream);
close(byteArrayOutputStream);
close(objectOutputStream);
}
return cloneData;
}
private static void close(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class Student implements Serializable {
private static final long serialVersionUID = -7477774394301373131L;
public String id;
public Student(String id) {
this.id = id;
}
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
'}';
}
}
public class People implements Serializable {
private static final long serialVersionUID = 7638893927457830038L;
public String name;
public int age;
public Student student;
public List<Student> list;
public People(String name, int age) {
this.name = name;
this.age = age;
}
public void setStudent(Student student) {
this.student = student;
}
public void setList(List<Student> list) {
this.list = list;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", student=" + student +
", list=" + list +
'}';
}
}
public class MyTest {
public static void main(String[] args) {
People srcPeople = new People("Bill", 26);
People destPeople = CloneUtils.clone(srcPeople);
System.out.println(srcPeople);
System.out.println(destPeople);
}
}