成员变量、成员方法与对象引用 | JavaSE
成员变量、成员方法与对象引用
一、学习目标
完成本章后,你应该能够:
- 理解类成员(Member)的基本概念。
- 能够区分成员变量与局部变量。
- 能够解释成员变量为什么属于对象状态的一部分。
- 能够定义并调用成员方法。
- 能够理解对象调用成员方法时,方法处理的是对应对象的数据。
- 能够使用
对象.成员变量和对象.成员方法(...)。 - 能够解释对象变量保存的是引用值,而不是对象内容本身。
- 能够分析两个引用变量指向同一个对象时的数据变化。
- 能够理解引用变量默认或显式为
null时的基本含义。 - 能够理解对象成员变量创建后的默认值。
- 能够绘制简单的“引用变量 → 对象”关系图。
二、核心知识
2.1 什么是类的成员
观察:
class Student {
String name;
double chinese;
double math;
void printTotalScore() {
System.out.println(chinese + math);
}
}
定义在类体中的:
name
chinese
math
printTotalScore()
都属于这个类的成员。
当前阶段主要学习两类:
成员变量
成员方法
2.2 什么是成员变量
例如:
class Student {
String name;
int age;
double score;
}
这些定义在:
类中、方法外
的变量,属于成员变量。
它们用于描述对象具有的数据。
例如:
Student
├── name
├── age
└── score
因此也经常称为:
字段
Field
在后面的 Java 文档、反射和框架中:
Field
会是非常重要的专业术语。
2.3 当前阶段主要关注实例成员变量
以后学习 static 后,成员变量还会进一步区分:
实例变量
类变量 / 静态变量
但本章暂时只讨论最基础的:
每个对象自己拥有一份的数据。
例如:
class Student {
String name;
int age;
}
创建:
Student s1 = new Student();
Student s2 = new Student();
会形成:
对象 A
├── name
└── age
对象 B
├── name
└── age
两个对象分别拥有自己的状态。
static 的共享成员规则留到后续专章。
2.4 使用对象访问成员变量
格式:
对象变量.成员变量
例如:
Student s1 = new Student();
s1.name = "小雨";
s1.age = 21;
读取:
System.out.println(s1.name);
System.out.println(s1.age);
可以理解为:
先通过 s1 找到对象
↓
再访问这个对象中的 name / age
2.5 点运算符 . 的含义
例如:
s1.name
和:
s1.printInfo()
这里的:
.
可以理解为:
访问这个对象的某个成员。
因此:
对象.成员变量
对象.成员方法(...)
是面向对象代码中最常见的结构之一。
2.6 成员变量创建后有默认值
例如:
class Student {
String name;
int age;
double score;
boolean graduated;
}
然后:
Student student = new Student();
即使还没有手动赋值,对象中的这些字段也已经获得默认值。
常见情况:
| 成员变量类型 | 默认值 |
| --------------------------- | ---------- |
| byte / short / int / long | 0 |
| float / double | 0.0 |
| char | '\u0000' |
| boolean | false |
| 引用类型 | null |
例如:
System.out.println(student.name);
System.out.println(student.age);
System.out.println(student.score);
System.out.println(student.graduated);
对应的初始状态大致是:
null
0
0.0
false
2.7 成员变量和局部变量不是一回事
观察:
class Student {
String name;
void printScore() {
double score = 100;
System.out.println(score);
}
}
这里:
String name;
是:
成员变量
而:
double score = 100;
定义在方法内部,是:
局部变量
2.8 成员变量与局部变量的基本区别
基础阶段可以先对比:
| 比较项 | 成员变量 | 局部变量 | | ------------ | -------------------- | ---------------------------------- | | 常见定义位置 | 类中、方法外 | 方法内部等局部区域 | | 主要作用 | 描述对象状态 | 服务于一次局部计算 | | 默认初始化 | 对象字段会获得默认值 | 普通局部变量不能依赖默认值直接读取 | | 生命周期 | 与对应对象状态有关 | 随局部执行区域存在 |
例如:
class Student {
int age;
void test() {
int number;
System.out.println(age);
// System.out.println(number);
}
}
age 创建对象后有:
0
而未明确赋值的局部变量:
number
不能直接读取。
2.9 成员方法
定义在类中的方法叫成员方法。
例如:
class Student {
String name;
double chinese;
double math;
void printTotalScore() {
System.out.println(chinese + math);
}
}
其中:
printTotalScore()
是成员方法。
它描述:
Student 对象能够完成什么功能。
2.10 对象如何调用成员方法
格式:
对象变量.成员方法(...)
例如:
Student s1 = new Student();
s1.name = "小雨";
s1.chinese = 100;
s1.math = 90;
s1.printTotalScore();
可以理解:
s1
↓
找到 Student 对象
↓
让这个对象执行 printTotalScore()
2.11 方法为什么可以直接使用成员变量
例如:
class Student {
String name;
double chinese;
double math;
void printTotalScore() {
System.out.println(
name + ":" + (chinese + math)
);
}
}
方法中没有重新定义:
name
chinese
math
却能够直接使用这些成员变量。
原因可以先理解为:
当前成员方法是在某个 Student 对象上执行,它可以处理当前对象自己的成员数据。
例如:
s1.printTotalScore();
处理的是:
s1 所引用 Student 对象的数据
而:
s2.printTotalScore();
处理的是:
s2 所引用 Student 对象的数据
这里更底层涉及当前对象引用,也就是下一章会深入学习的:
this
本章先不展开。
2.12 同一个成员方法可以作用于不同对象
定义一次:
void printTotalScore() {
System.out.println(
name + ":" + (chinese + math)
);
}
然后:
s1.printTotalScore();
s2.printTotalScore();
虽然执行的是同一个方法定义,但是处理的数据不同。
可以理解:
s1 调用
↓
处理对象 A 的数据
s2 调用
↓
处理对象 B 的数据
这就是:
类定义共同的行为,对象提供各自的状态。
2.13 对象引用到底是什么
观察:
Student s1 = new Student();
Java 中:
Student
是引用类型。
变量:
s1
保存的是:
一个引用值。
这个引用可以指向:
Student 对象
因此:
s1 ──→ Student 对象
2.14 为什么不推荐简单说“对象变量存地址”
源教学资料为了直观理解写道:
对象变量存储的是对象的地址。
作为早期内存示意图,这种说法容易理解。
但从 Java 语言层面,更准确的说法应该是:
引用类型变量保存引用值(reference value),这个引用可以指向对象。
我们没有必要假定 Java 语言向程序员暴露了某个可以直接参与普通整数计算的“真实物理内存地址”。
所以教程中统一使用:
对象引用
引用值
进行描述。
2.15 两个引用变量可以指向同一个对象
观察:
Student s1 = new Student();
s1.name = "小雨";
Student s2 = s1;
这里:
Student s2 = s1;
并没有:
new
所以没有创建新的 Student 对象。
只是把:
s1 当前保存的引用值
赋值给:
s2
于是:
s1 ──┐
├──→ Student 对象
s2 ──┘
2.16 一个引用修改对象,另一个引用也能看到
继续:
Student s1 = new Student();
s1.name = "小雨";
Student s2 = s1;
s2.name = "小星";
System.out.println(s1.name);
这里最终看到的是:
小星
原因不是:
修改 s2 导致自动同步 s1
而是:
s1和s2指向的是同一个对象。
真正被修改的是:
共同的 Student 对象
2.17 引用变量赋值与对象复制必须区分
例如:
Student s2 = s1;
做的是:
复制引用值
不是:
复制整个对象
所以:
对象数量:1
引用变量数量:2
这一点与上一章数组中的:
int[] b = a;
本质非常相似。
数组也是引用类型。
2.18 多次 new 才是多个对象
比较:
Student s1 = new Student();
Student s2 = s1;
这里只有:
1 次 new
→ 1 个 Student 对象
而:
Student s1 = new Student();
Student s2 = new Student();
有:
2 次 new
→ 2 个不同 Student 对象
这是非常重要的判断方法。
2.19 引用变量可以重新指向其他对象
例如:
Student s1 = new Student();
Student s2 = new Student();
s1 = s2;
最初:
s1 ──→ 对象 A
s2 ──→ 对象 B
执行:
s1 = s2;
以后:
s1 ──┐
├──→ 对象 B
s2 ──┘
s1 不再引用对象 A。
这里发生的是:
引用变量保存的引用值发生了变化。
2.20 什么是 null
引用变量还可以保存特殊值:
null
例如:
Student student = null;
表示:
student当前没有引用任何 Student 对象。
可以画成:
student
│
└──→ null
2.21 null 与“空对象”不是一回事
不要理解成:
null
=
有一个 Student 对象,只是里面什么都没有
不是。
Student student = null;
表示:
当前没有 Student 对象可以通过这个引用访问。
2.22 对 null 访问实例成员
例如:
Student student = null;
System.out.println(student.name);
或者:
student.printInfo();
运行时会出现:
NullPointerException
因为:
student
没有指向一个可以访问成员的对象。
所以以后看到:
NullPointerException
首先就应该思考:
我正在通过哪个引用访问对象?这个引用是否为 null?
2.23 对象成员变量也是可以引用其他对象的
例如:
class Student {
String name;
Book book;
}
这里:
book
本身也是一个引用类型成员变量。
创建:
Student student = new Student();
此时:
student.book
默认是:
null
以后:
student.book = new Book();
就可以形成:
student
↓
Student 对象
│
└── book ──→ Book 对象
这就是大型对象模型的基本组成方式:
对象之间通过引用建立关系。
三、使用方法
3.1 成员变量与成员方法
public class Student {
String name;
double chinese;
double math;
public void printTotalScore() {
double total =
chinese + math;
System.out.println(
name + "的总成绩:"
+ total
);
}
public void printAverageScore() {
double average =
(chinese + math) / 2;
System.out.println(
name + "的平均成绩:"
+ average
);
}
}
这里:
成员变量:
name
chinese
math
成员方法:
printTotalScore()
printAverageScore()
局部变量:
total
average
3.2 创建和使用对象
public class StudentTest {
public static void main(String[] args) {
Student s1 =
new Student();
s1.name = "小雨";
s1.chinese = 100;
s1.math = 98;
s1.printTotalScore();
s1.printAverageScore();
}
}
3.3 两个对象分别调用同一方法
Student s1 = new Student();
s1.name = "小雨";
s1.chinese = 100;
s1.math = 98;
Student s2 = new Student();
s2.name = "小星";
s2.chinese = 80;
s2.math = 90;
s1.printTotalScore();
s2.printTotalScore();
同一个:
printTotalScore()
分别处理两个对象自己的数据。
3.4 验证成员变量默认值
class User {
String username;
int age;
double balance;
boolean enabled;
}
测试:
public class UserTest {
public static void main(String[] args) {
User user = new User();
System.out.println(user.username);
System.out.println(user.age);
System.out.println(user.balance);
System.out.println(user.enabled);
}
}
当前重点是观察:
对象字段创建时具有默认值。
3.5 验证引用共享
public class ReferenceDemo {
public static void main(String[] args) {
Student s1 =
new Student();
s1.name = "小雨";
Student s2 = s1;
s2.name = "小星";
System.out.println(s1.name);
System.out.println(s2.name);
}
}
画图:
s1 ──┐
├──→ Student
s2 ──┘
name = "小星"
3.6 验证两个独立对象
Student s1 = new Student();
Student s2 = new Student();
s1.name = "小雨";
s2.name = "小星";
System.out.println(s1.name);
System.out.println(s2.name);
画图:
s1 ──→ Student A
name = 小雨
s2 ──→ Student B
name = 小星
3.7 对象数组
之前我们使用:
int[] numbers;
String[] names;
现在还可以:
Student[] students =
new Student[3];
注意:
Student[]
数组中保存的元素是:
Student 引用。
刚创建时:
students[0]
students[1]
students[2]
默认都是:
null
必须进一步:
students[0] = new Student();
才能让:
students[0]
引用一个真正的 Student 对象。
3.8 对象数组的基本模型
例如:
Student[] students =
new Student[2];
students[0] = new Student();
students[1] = new Student();
students[0].name = "小雨";
students[1].name = "小星";
结构:
students
↓
┌────────┬────────┐
│ ref │ ref │
└──┬─────┴──┬─────┘
│ │
↓ ↓
Student A Student B
name 小雨 name 小星
这正是:
数组
+
引用
+
对象
的组合。
四、原理与进阶
4.1 对象字段在实例创建时获得独立存储
每创建一个新的类实例,对象就具有对应实例字段。
例如:
Student s1 = new Student();
Student s2 = new Student();
如果 Student 有:
String name;
int age;
那么可以抽象为:
对象 A
├── name
└── age
对象 B
├── name
└── age
这就是为什么实例状态互相独立。
4.2 默认值发生在对象字段初始化阶段
例如:
Student s1 = new Student();
对象中的:
String → null
int → 0
double → 0.0
boolean → false
并不是:
JVM 随机留下了一块旧内存。
而是 Java 语言定义的默认初始化语义。
4.3 引用值不是对象本身
Java 的类型系统中:
基本类型变量
→ 保存基本类型值
引用类型变量
→ 保存引用值
例如:
int age = 20;
age 保存:
20
而:
Student s1 = new Student();
s1 保存:
对 Student 对象的引用
这两类变量的行为差异,将直接影响:
赋值
参数传递
对象共享
null
数组
集合
多态
4.4 引用赋值为什么会产生“联动”
例如:
Student a = new Student();
Student b = a;
之后:
b.age = 100;
通过:
a.age
也能看到 100。
不是:
a.age 自动从 b.age 同步
而是:
a 和 b
↓
引用同一个对象
对象只有一个。
4.5 Java 不保证程序员能直接看到“真实对象地址”
有些教学示意图会写:
0x12345678
表示对象地址。
这种图可以帮助理解:
不同引用是否指向同一对象
但不要误以为普通 Java 程序可以像 C 指针那样:
直接读取地址
地址加减
通过裸地址访问内存
Java 语言暴露的是:
reference
而不是供普通程序任意操作的裸内存指针。
4.6 对象引用与垃圾回收的初步联系
例如:
Student s1 =
new Student();
s1 =
new Student();
第一次创建的 Student 对象如果:
已经没有任何可达引用能够访问
那么它将来可能成为垃圾回收器处理的对象。
这里只建立认识:
对象
≠
永远随着创建它的变量一起存在
真正 GC、堆、可达性分析属于 JVM 专门知识,不在本章展开。
五、实践应用
真实项目中的对象通常不是孤立存在。
例如订单系统:
User
↓
Order
↓
OrderItem
↓
Product
博客:
Article
├── Author
├── Tag[]
└── Category
学校:
Student
├── ClassRoom
└── Course[]
背后的基础机制仍然是:
对象
+
成员变量
+
成员方法
+
对象引用
因此本章实际上是在建立:
整个 Java 对象图(Object Graph)的最小基础。
六、常见问题
6.1 成员变量一定需要自己初始化吗?
对象创建时,字段会得到 Java 定义的默认值。
但是业务上是否应该依赖:
0
null
false
作为有效状态,需要根据具体设计判断。
6.2 局部变量也有这些默认值吗?
不能这样理解。
普通局部变量必须在满足明确赋值规则以后才能读取。
例如:
int number;
// System.out.println(number);
不能直接读取。
6.3 成员方法为什么不用传 name 就能使用 name?
因为:
name
是对象的成员变量。
成员方法在某个对象上执行时,可以访问当前对象的实例成员。
更加完整的 this 解释放到下一章。
6.4 Student s2 = s1; 会创建新对象吗?
不会。
因为没有:
new Student()
它只是复制引用值。
6.5 Student s1 = new Student(); Student s2 = new Student(); 呢?
这里执行了两次:
new Student()
所以是两个不同对象。
6.6 null 是不是一个空 Student 对象?
不是。
null 表示:
当前引用没有指向对象。
6.7 为什么会出现 NullPointerException?
例如:
Student student = null;
student.printInfo();
程序想通过:
student
访问实例成员,但这个引用并没有指向任何 Student 对象。
6.8 对象变量里保存的是内存地址吗?
入门阶段可以用“地址”图辅助理解。
但准确 Java 术语应该说:
保存引用值。
不要把 Java 引用直接等同于可供程序员随意操作的裸内存地址。
6.9 s1 == s2 比较的是什么?
当两边都是引用类型变量时:
s1 == s2
关注的是:
两个引用是否指向同一个对象。
例如:
Student s1 = new Student();
Student s2 = s1;
则两者引用同一对象。
对象“业务内容是否相等”是另一个问题,未来学习 equals() 时再系统处理。
6.10 一个对象能不能引用另一个对象?
可以,而且非常常见。
例如:
class Order {
User user;
Product product;
}
对象关系正是大型 Java 系统建模的重要组成部分。
七、练习与验收
7.1 知识问答
- 什么是成员变量?
- 什么是成员方法?
- 成员变量主要描述对象的什么?
- 成员方法主要描述对象的什么?
- 成员变量和局部变量主要有哪些区别?
- 为什么对象字段创建后具有默认值?
- 成员方法为什么可以访问对象成员变量?
对象.成员中.应该如何理解?- 引用变量保存的是什么?
- 为什么“对象变量保存地址”只能作为入门类比?
Student b = a;是否复制对象?- 两个引用变量指向同一对象时,一个引用修改对象后为什么另一个能够看到?
- 什么是
null? null与“空对象”有什么区别?- 为什么访问 null 引用可能产生
NullPointerException? - 对象数组中保存的是 Student 对象本身还是 Student 引用?
7.2 代码阅读
禁止运行:
class Student {
String name;
int age;
}
public class ReferenceRead01 {
public static void main(String[] args) {
Student s1 =
new Student();
s1.name = "小雨";
s1.age = 21;
Student s2 = s1;
s2.name = "小星";
System.out.println(s1.name);
System.out.println(s2.name);
}
}
要求:
- 一共创建几个 Student 对象?
- 一共有几个 Student 引用变量?
s1和s2是否指向同一个对象?- 预测两行输出。
- 画引用关系图。
继续:
class User {
String name;
int age;
double balance;
boolean enabled;
}
public class FieldRead01 {
public static void main(String[] args) {
User user =
new User();
System.out.println(user.name);
System.out.println(user.age);
System.out.println(user.balance);
System.out.println(user.enabled);
}
}
要求:
- 不运行预测结果。
- 解释每个成员变量为什么已经有初始状态。
7.3 手写代码
- 创建
Student类,包含 3 个成员变量、2 个成员方法。 - 创建两个 Student 对象,分别保存不同数据。
- 设计
Product类,让成员方法能够直接使用商品自己的成员变量。 - 写代码证明两个不同
new Student()的数据互相独立。 - 写代码证明两个引用变量可以指向同一个 Student 对象。
- 创建长度为 3 的
Student[],并为三个位置分别创建 Student 对象。 - 画出对象数组对应的引用图。
7.4 Debug
分析:
Student[] students =
new Student[3];
students[0].name = "小雨";
要求:
- 为什么
new Student[3]不等于创建了 3 个 Student 对象? students[0]当前是什么?- 程序可能出现什么问题?
- 修复代码并画出引用结构。
继续:
Student s1 = new Student();
Student s2 = s1;
s2.age = 100;
某同学认为:
Java 自动把 s2 的 age 同步到了 s1。
要求:
- 判断这句话是否准确。
- 用对象数量和引用数量解释真实原因。
7.5 综合训练
将原来的平行数组学生系统:
String[] names = ...;
double[] chinese = ...;
double[] math = ...;
重构为:
Student[]
要求:
- Student 至少包含姓名、语文成绩、数学成绩。
- Student 至少提供总分与平均分行为。
- 创建至少 3 个 Student 对象。
- 将对象放入 Student 数组。
- 遍历数组并让每个对象输出自己的总分和平均分。
- 画出:
- Student 类
- Student[] 数组对象
- 三个 Student 对象
- 对应引用关系
7.6 本章验收
关闭资料后确认:
- [ ] 能区分成员变量和局部变量。
- [ ] 能说出常见实例成员变量默认值。
- [ ] 能从零定义成员方法。
- [ ] 能使用对象访问成员变量和调用成员方法。
- [ ] 能解释同一个方法为什么可以处理不同对象的数据。
- [ ] 能准确说明引用变量保存的是引用值。
- [ ] 不再机械地把 Java 引用说成“可直接操作的物理地址”。
- [ ] 能解释两个引用变量指向同一对象的行为。
- [ ] 能解释
null。 - [ ] 能定位基础 NullPointerException。
- [ ] 能画出对象数组和对象之间的引用关系。
如果看到:
Student s2 = s1;
仍然认为:
“Java 又复制了一份完整 Student 对象。”
那么本章的对象引用模型还没有真正建立。