Java中的类根据赋值对象与被赋值对象是否共享对象的实际数据内存空间,分为值型类和引用型类。
Java中将一个对象赋值给另一个对象时,如果这个对象是值对象(所谓的值对象就是由值型类生成的对象),则这两个对象的实际数据存储空间是独立的,各自保存数据的拷贝。因此分别修改这两个对象不会相互影响。如果这个对象是引用型对象(所谓的引用型对象就是由引用型类生成的对象),则这两个对象的实际数据存储空间是相同的,两者共享实际数据存储区。因此,分别修改这两个对象时,实质上同时修改了另一个对象的数据。此时,赋值前后看似有两个对象,实际上同一个对象的两个不同引用而已,两者所指向的对象数据区是相同的。
Java中的值型类包括表示基本数据类型的类(例如Integer,Long,Double,Float,Boolean,Byte,String,Character等),除此之外的其他Java内置类以及所有的用户自定义类均属于引用型类。
先看一个小实验:
Integer b1=new Integer(123); //新建一个Integer类的对象
Integer b2=b1; //将对象b1赋值给b2
b2=456; //修改b的值
System.out.println(b1); //观察b1的值是否在被b2修改了。 以上处的运行结果是123,表明Integer类型是值类型。
StuInfo s1,s2; //观察自定义类型StuInfo,其定义见稍后代码。
s1=new StuInfo(18001,"张一"); //新建一个StuInfo类的对象
s2=s1; //将对象s1赋值给s2
s2.setId(123); //修改s2的值
System.out.println(s1); //观察s1的值是否被s2修改了。 以上处的运行结果是“学号:123 姓名:张一”,表明Integer类是引用型类。
明白了以上现象后,在实际编程中应该要特别注意。例如,在将对象添加到链表ArrayList后,此对象和链表中对应的元素其实都是指向相同的对象数据存储空间,也就是说此对象和链表中对应的元素是同一对象数据区的两个引用,通过任何其中一个引用修改对象数据区,实际上也修改了另一个引用的对象数据区,因为两个引用指向同一对象数据区。
下面的程序展示了这种现象。
import java.util.ArrayList;
public class MyTest {
public static void main(String[] args) {
StuInfo s1,s2,s3;
ArrayList<StuInfo> list=new ArrayList<StuInfo>();
s1=new StuInfo(18001,"张一"); //新建3个对象s1,s2,s3
s2=new StuInfo(18002,"王二");
s3=new StuInfo(18003,"赵三");
list.add(s1); //将3个对象s1,s2,s3添加到链表list中
list.add(s2);
list.add(s3);
for(StuInfo e:list) //输出链表中的对象
System.out.println(e);
System.out.println();
s1.setId(11); //修改对象s1,s2,s3的值
s1.setName("张十一");
s2.setId(22);
s2.setName("王二二");
s3.setId(33);
s3.setName("赵三三");
for(StuInfo e:list) //观察链表list中的对象值是否发生变化。结果是确实发生了改变。
System.out.println(e); //也就是修改s1,s2,s3的值实际上修改了链表中对应元素的值。
System.out.println();
for(StuInfo e:list) //修改链表中对象的值
e.setId(e.getId()+10);
System.out.println(s1); //观察对象s1,s2,s3的值是否发生了改变。结果是确实发生了改变。
System.out.println(s2); //也就是修改链表中对应元素的值实际上修改了的对象s1,s2,s3的值。
System.out.println(s3);
}
}
//以下为StuInfo类的定义
class StuInfo{
private int id;
private String name;
public StuInfo(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "学号:"+id+"\t姓名:"+name;
}
}
以上程序的运行结果:
学号:18001 姓名:张一
学号:18002 姓名:王二
学号:18003 姓名:赵三
学号:11 姓名:张十一
学号:22 姓名:王二二
学号:33 姓名:赵三三
学号:21 姓名:张十一
学号:32 姓名:王二二
学号:43 姓名:赵三三
如果实际编程中需要避免这种两个对象引用同一对象数据区的情形发生,解决办法有2个。
办法1:覆盖(override)该类的clone()方法(在该clone()方法中,先new一个新对象,然后将对象的所有数据成员分别一一赋值给新对象,最后返回新对象),那么在原来给对象赋值的操作,改为调用clone()方法。例如,将s2=s1语句改为s2=s1.clone(),即可。
办法2:实现方法1中clone()函数的方式设计一个构造函数,该构造函数的参数是同类对象。这种构造函数有时称为“拷贝构造函数”。