在 Java 语言中,除八种基本的数据类型外,其他的数据类型统称为引用类型(Reference Type),包括类、接口、数组、枚举和注解类型。之所以称为引用类型,是因为这些类型的数据在保存和使用过程中采用了“引用”的方式,与基本数据类型的情况完全不同。
“引用类型”(Reference Type)也称为“复合类型”(Complex Type)或“构造类型”(Constructed Type)。
一个基本类型数据只占有一个定长的连续存储空间,保存单一的值。如下:
int a = 10;
上面语句运行时,系统会在内存中为声明的变量 a 分配 32 位存储空间,且将值设置为 10。如果是引用类型,则数据存储的情况就要复杂得多。如下:
// 定义用户实体类 public class User { /** 用户ID */ private int id = -1; /** 用户姓名 */ private String name = ""; public User(int id) { this.id = id; } public String show() { return "id=" + id + " 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; } } // 使用定义的用户实体类 public class TestReferenceType { public static void main(String[] args) { User user; user = new User(100); user.setName("Tom"); System.out.println(user.show()); } }
输出结果:
id=100 name=Tom
运行上面代码时,执行时如下:
(1)User user; // 系统为新声明的变量 user 分配长度为 32 位存储空间,此时变量 user 尚未被赋值,不能使用。
(2)user = new User(100); // 系统执行该语句等号右边的表达式,使用 new 操作符调用 User 的构造方法创建一个 User 类的对象,注意:对象的创建过程后续将详细介绍。
(3)user.setName("Tom"); // 调用 user 变量的连个 set 方法,为 user 设置 id 和 name 属性。
(4)System.out.println(user.show()); // 调用 user 变量的 show() 方法显示设置的 id 和 name 属性值。
为一个对象分配空间也就是为其封装的所有属性分配存储空间。可以简单地认为一个 Java 对象占用的存储空间大小就是其所有属性所需要的存储空间总和。分配存储空间后,系统将自动对新建对象的所有属性进行默认初始化,规则如下表:
属性类型 | 默认初始值 |
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
char | '\u0000' |
float | 0.0F |
double | 0.0D |
boolean | false |
引用类型 | null |
默认初始化操作是系统强制进行的,程序员无法进行干预或者取消,实际上是将所有属性对应的存储空间的二进制位全部设置为0。其中,null 是一个 Java 关键字,可以作为一个特殊的常量值被赋值给任何引用类型变量,它表示一个空的引用 —— 即不指向任何对象。上面示例默认初始化后内存图如下:
注意:此时变量和新建对象之间还没有建立任何联系
显示初始化也是对新建对象的属性赋值,其值来自于属性在声明时被指定的缺省值。如下:
public class User { /** 用户ID */ private int id = -1; /** 用户姓名 */ private String name = ""; }
上面语句中,显示初始化 id 为 -1,name 为空字符串。上面示例显示化初始化后内存如下图:
注意:如果属性在声明时未指定缺省值,则不对其进行显示初始化。
上面示例中用如下代码创建 User 对象:
user = new User(100);
该构造方法中的 this.id = id; 语句执行时将实参 100 赋值给 id 属性,至此新对象的创建工作才算完成。此时这个新建对象和先前声明的变量 user 间还是没有建立任何关联。上面示例执行完构造函数后内存如下图:
接下来该执行第二条语句中的赋值操作:
user = new User(100);
上面语句中,将新创建的 User 对象赋值给 user 变量,此时两者存在了关联关系。内存结构如下图:
在前面我们介绍过一个变量的地址空间仅为 4Byte(32位),那么它是怎样装下引用对象的呢?实际上赋值给 user 变量的并不是新建对象封装的具体信息,而是该对象的哈希码值 —— 每一个 Java 对象都对应一个唯一的整数编号作为其内部表示,以实现对 Java 对象的快速检索。例如:
User user; user = new User(100); user.setName("Tom"); System.out.println(user.show()); System.out.println(Integer.toHexString(user.hashCode()));
输出结果:
id=100 name=Tom 3764951d
上面代码可知,user 对象的哈希代码十六进制为 3764951d。由于哈希码值能够用来定位一个对象,因为也被称为对象的“句柄”(Handle)。
由于变量 user 记录了一个对象的“句柄”,因而我们就可以通过 user 找到对象并进行操作。变量 user 和真正对象之间的这种关系我们称为“引用(Reference)”—— user 变量引用了该对象,因此我们称 user 为引用类型变量。user 变量是对 Java 对象的一个引用,user 变量占用的 32 位存储空间为引用空间。