static 静态成员与工具类 | JavaSE
static 静态成员与工具类
一、学习目标
学完本章,你应该能够:
- 能够解释
static的基本含义,理解“属于类”和“属于对象”的区别。 - 能够区分静态变量(Static Field / Class Variable)与实例变量(Instance Variable)。
- 能够解释为什么静态变量可以被同一个类的所有对象共享。
- 能够正确使用
类名.静态成员和对象.实例成员。 - 能够区分静态方法与实例方法,并判断两者能够直接访问哪些成员。
- 能够解释为什么静态方法中不能直接使用
this。 - 能够独立设计一个由静态方法组成的工具类(Utility Class)。
- 能够使用静态变量完成对象数量统计等共享数据场景。
二、核心知识
2.1 为什么需要 static
先看一个没有 static 的类:
public class Student {
String schoolName;
String name;
}
创建三个学生:
Student s1 = new Student();
s1.schoolName = "山西大学";
s1.name = "张三";
Student s2 = new Student();
s2.schoolName = "山西大学";
s2.name = "李四";
Student s3 = new Student();
s3.schoolName = "山西大学";
s3.name = "王五";
这里:
name
显然应该属于每一个具体学生。
因为每个学生姓名不同:
张三
李四
王五
但是:
schoolName
却可能完全相同。
如果整个类的所有对象都共享同一份学校名称,那么让每个对象分别保存一份:
"山西大学"
就不符合数据本身的含义。
这时就可以使用:
static
把数据定义为:
类级别共享的数据。
2.2 static 是什么
static 是 Java 中的关键字,可以修饰类的成员。
当前阶段重点学习:
static 成员变量
static 成员方法
例如:
public class Student {
static String schoolName;
String name;
}
其中:
static String schoolName;
属于类。
而:
String name;
属于对象。
可以建立最基本的模型:
Student 类
│
├── static schoolName
│ 整个类共享
│
├── Student 对象 s1
│ └── name
│
├── Student 对象 s2
│ └── name
│
└── Student 对象 s3
└── name
2.3 静态变量与实例变量
成员变量根据是否具有 static 修饰,可以分成两类。
| 类型 | 是否有 static | 属于谁 | 数据特点 | | -------- | ------------- | ------ | ---------------- | | 静态变量 | 有 | 类 | 同一个类共享 | | 实例变量 | 无 | 对象 | 每个对象各自拥有 |
例如:
public class Student {
static String schoolName;
String name;
int age;
}
其中:
Student.schoolName
属于:
Student 类
而:
s1.name
s1.age
属于:
s1 对象
另一个对象:
s2
则拥有自己的:
s2.name
s2.age
2.4 静态变量是共享的
观察:
public class Student {
static String schoolName;
String name;
}
测试:
public class Test {
public static void main(String[] args) {
Student.schoolName = "山西大学";
Student s1 = new Student();
s1.name = "张三";
Student s2 = new Student();
s2.name = "李四";
System.out.println(Student.schoolName);
}
}
静态变量:
schoolName
并不是:
s1 一份
s2 一份
而是:
Student 类共享一份
因此,如果修改:
Student.schoolName = "星雨大学";
再次访问:
System.out.println(Student.schoolName);
看到的就是修改后的共享数据。
2.5 为什么静态变量推荐使用类名访问
静态变量推荐:
Student.schoolName
而不是:
s1.schoolName
Java 语法允许在很多情况下通过对象引用访问静态成员:
s1.schoolName = "山西大学";
但是不推荐。
因为这会产生一种错误视觉:
schoolName 好像属于 s1 对象
实际上它属于:
Student 类
因此代码应该主动表达真实语义:
Student.schoolName = "山西大学";
推荐记忆:
静态成员 → 类名.静态成员
实例成员 → 对象.实例成员
2.6 静态变量什么时候使用
判断一个变量是否应该设计成 static,可以问一个问题:
这份数据应该属于每个对象各自一份,还是整个类共享一份?
例如学生:
姓名
年龄
成绩
不同对象数据不同:
private String name;
private int age;
private double score;
通常属于实例变量。
而:
学校名称
学生对象创建总数
系统统一配置
如果在当前设计中整个类只需要共享一份,就可能适合静态变量。
例如:
public class User {
static int count;
private String username;
}
这里:
username
属于每个用户。
但:
count
表示整个 User 类共享的统计数据。
2.7 使用 static 统计对象数量
例如:
public class User {
static int count = 0;
private String username;
public User(String username) {
this.username = username;
count++;
}
}
创建对象:
new User("A");
new User("B");
new User("C");
每创建一次对象:
count++;
操作的都是同一个:
User.count
因此它可以用来保存:
当前程序运行过程中创建过多少个
User对象。
访问:
System.out.println(User.count);
这里体现的就是:
实例数据 → 每个对象独立
类级统计数据 → 所有对象共享
2.8 static 修饰成员方法
static 也可以修饰方法。
例如:
public class MathUtil {
public static int add(int a, int b) {
return a + b;
}
}
这个方法叫:
静态方法(Static Method),也常称类方法。
调用:
int result = MathUtil.add(10, 20);
不需要:
MathUtil util = new MathUtil();
再:
util.add(10, 20);
因为:
add()
本身就是类级成员。
2.9 实例方法
没有 static 修饰的成员方法称为实例方法(Instance Method)。
例如:
public class Student {
private String name;
public void study() {
System.out.println(name + "正在学习");
}
}
调用:
Student student = new Student();
student.study();
实例方法需要依赖具体对象。
因为:
study()
执行时很可能需要访问这个对象自己的状态:
name
2.10 静态方法与实例方法对比
| 对比 | 静态方法 | 实例方法 |
| ----------------- | ------------- | ------------- |
| static | 有 | 无 |
| 属于 | 类 | 对象 |
| 推荐调用方式 | 类名.方法() | 对象.方法() |
| 是否依赖具体对象 | 通常不依赖 | 依赖 |
| 能否直接使用 this | 不能 | 可以 |
例如:
public class Student {
static String schoolName = "山西大学";
String name;
public static void printSchool() {
System.out.println(schoolName);
}
public void printInfo() {
System.out.println(name);
System.out.println(schoolName);
}
}
调用:
Student.printSchool();
Student student = new Student();
student.name = "张三";
student.printInfo();
2.11 static 方法能直接访问什么
这是本章最重要的规则之一。
静态方法
静态方法中可以直接访问:
静态成员
例如:
public class Demo {
static int count = 10;
public static void show() {
System.out.println(count);
}
}
合法。
但是静态方法不能直接访问实例成员:
public class Demo {
String name;
public static void show() {
System.out.println(name);
}
}
这段代码会产生编译错误。
为什么?
因为:
name
属于对象。
但是:
show()
可以这样调用:
Demo.show();
此时根本没有指定:
到底是哪一个 Demo 对象的 name
假设存在:
Demo d1 = new Demo();
Demo d2 = new Demo();
那么静态方法如果直接写:
name
Java 无法判断你想访问:
d1.name
还是:
d2.name
因此不允许直接访问。
2.12 静态方法并不是永远不能访问实例变量
一定要区分:
不能直接访问实例成员。
和:
完全不能访问实例成员。
静态方法内部如果明确拥有一个对象,就可以通过对象访问:
public class Student {
String name;
public static void print(Student student) {
System.out.println(student.name);
}
}
这里:
student.name
已经明确指出:
访问的是参数
student所引用对象的name。
因此是合法的。
真正的问题不是:
static 永远不能碰实例数据
而是:
static 上下文本身没有隐含的当前对象
2.13 实例方法能访问什么
实例方法中可以直接访问:
实例成员
静态成员
例如:
public class Student {
static String schoolName = "山西大学";
String name;
public void printInfo() {
System.out.println(name);
System.out.println(schoolName);
}
}
原因在于实例方法执行时存在一个具体当前对象。
所以:
name
可以确定是哪一个对象的数据。
同时静态成员本来就属于整个类,因此也可以访问。
2.14 static 方法为什么不能直接使用 this
前面已经学过:
this
代表当前对象。
例如:
public void show() {
System.out.println(this);
}
但是:
public static void show() {
System.out.println(this);
}
不能编译。
因为静态方法可以直接通过:
Demo.show();
调用。
调用过程中可能根本没有 Demo 对象。
既然不存在:
当前对象
自然就不存在:
this
可以总结为:
实例方法
↓
存在当前对象
↓
存在 this
静态方法
↓
不依赖某个当前对象
↓
没有 this
三、使用方法
3.1 设计共享学校名称
public class Student {
static String schoolName = "山西大学";
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public void printInfo() {
System.out.println(
"姓名:" + name
+ ",年龄:" + age
+ ",学校:" + schoolName
);
}
}
测试:
public class Test {
public static void main(String[] args) {
Student s1 = new Student("张三", 18);
Student s2 = new Student("李四", 20);
s1.printInfo();
s2.printInfo();
Student.schoolName = "星雨大学";
s1.printInfo();
s2.printInfo();
}
}
这里:
name、age
各对象独立。
而:
schoolName
所有学生共享。
3.2 设计工具类
工具类(Utility Class)通常用于提供:
与某个具体对象状态无关、可以被不同代码重复使用的通用功能。
例如:
public class ArrayUtil {
private ArrayUtil() {
}
public static int max(int[] numbers) {
int max = numbers[0];
for (int number : numbers) {
if (number > max) {
max = number;
}
}
return max;
}
public static int min(int[] numbers) {
int min = numbers[0];
for (int number : numbers) {
if (number < min) {
min = number;
}
}
return min;
}
public static int sum(int[] numbers) {
int sum = 0;
for (int number : numbers) {
sum += number;
}
return sum;
}
public static double average(int[] numbers) {
return sum(numbers) * 1.0 / numbers.length;
}
}
使用:
public class Test {
public static void main(String[] args) {
int[] numbers = {10, 30, 20, 50, 40};
System.out.println(ArrayUtil.max(numbers));
System.out.println(ArrayUtil.min(numbers));
System.out.println(ArrayUtil.sum(numbers));
System.out.println(ArrayUtil.average(numbers));
}
}
调用者不需要:
new ArrayUtil()
而是直接:
ArrayUtil.max(numbers);
语义非常清楚:
调用
ArrayUtil提供的数组工具功能。
3.3 为什么工具类构造器常私有化
前面的工具类:
public class ArrayUtil {
private ArrayUtil() {
}
}
故意把构造器声明为:
private
这样外部不能:
new ArrayUtil();
为什么?
因为这个类中没有需要保存到对象里的实例状态。
如果所有功能都通过:
ArrayUtil.xxx()
提供,那么创建:
ArrayUtil util = new ArrayUtil();
本身没有明确业务意义。
因此私有化构造器可以直接表达设计意图:
这个类只作为工具功能集合使用,不用于创建业务对象。
3.4 验证码工具类
例如:
public class VerifyCodeUtil {
private VerifyCodeUtil() {
}
public static String getCode(int n) {
String code = "";
for (int i = 0; i < n; i++) {
int type = (int) (Math.random() * 3);
switch (type) {
case 0 -> {
int number = (int) (Math.random() * 10);
code += number;
}
case 1 -> {
char letter =
(char) ('A' + (int) (Math.random() * 26));
code += letter;
}
default -> {
char letter =
(char) ('a' + (int) (Math.random() * 26));
code += letter;
}
}
}
return code;
}
}
调用:
String code = VerifyCodeUtil.getCode(6);
System.out.println(code);
这里不需要验证码工具对象自身保存任何状态,因此设计成静态工具方法比较自然。
四、原理与进阶
4.1 “属于类”比“只有一份”更重要
初学阶段经常说:
静态变量只有一份。
这个说法有助于理解共享。
但学习时更应该首先掌握:
静态成员属于类,而不是某个具体对象。
“共享”其实是这个语义产生的结果。
所以代码:
Student.schoolName
比:
s1.schoolName
更能够表达设计本质。
4.2 static 不是“所有对象的公共变量”这么简单
不能仅仅看到:
很多对象都需要
就使用 static。
例如:
name
age
balance
score
即使每个对象都有这些字段,也不能因此变成静态。
判断标准是:
这些对象应该共享同一个值,还是每个对象拥有自己的状态?
例如:
BankAccount.balance
显然不同银行账户通常有不同余额。
如果写:
static double balance;
就意味着:
所有账户共享同一个余额。
通常完全错误。
4.3 static 不代表“全局变量可以随便用”
静态变量很容易被程序中不同位置访问和修改。
例如:
public static int count;
如果设计不当,就可能导致:
- 数据来源不清楚;
- 修改位置过多;
- 状态难以追踪;
- 类之间产生隐藏耦合。
所以不能因为:
static 调用方便
就把大量业务状态全部做成静态变量。
静态变量应当服务于明确的:
类级状态
共享状态
而不是成为“到处都能访问的数据仓库”。
4.4 工具类为什么适合 static
考虑:
int max = ArrayUtil.max(numbers);
这个操作只取决于:
输入数组
而不依赖:
某个 ArrayUtil 对象自己的状态
这种函数式、无对象状态的功能就非常适合静态方法。
反过来,如果某个功能需要保存不同对象自己的状态,例如:
student.study();
account.withdraw();
player.attack();
通常更适合实例方法。
4.5 不要把“节省内存”作为工具类 static 的主要理由
工具类使用静态方法,最重要的理由不是单纯追求:
省几个对象的内存
而是:
这个行为本身不依赖某个工具对象的实例状态,因此从模型和调用语义上属于类级功能。
例如:
ArrayUtil.max(numbers);
比:
new ArrayUtil().max(numbers);
更准确地表达:
这里没有一个真正有业务意义的
ArrayUtil对象。
这比单纯背诵“static 更省内存”更加重要。
五、实践应用
5.1 对象计数器
public class Student {
private static int count = 0;
private String name;
public Student(String name) {
this.name = name;
count++;
}
public static int getCount() {
return count;
}
public String getName() {
return name;
}
}
测试:
new Student("张三");
new Student("李四");
new Student("王五");
System.out.println(Student.getCount());
这里:
name
是对象状态。
而:
count
是类级统计状态。
5.2 为什么 main 方法是 static
Java 程序入口写作:
public static void main(String[] args) {
}
这里的:
static
意味着:
JVM 启动程序时不需要先由我们创建某个启动类对象,再调用 main。
而可以从类级入口开始执行。
初学阶段只需要理解这一层语义即可。
5.3 static 与面向对象并不冲突
有些初学者会产生误解:
学了面向对象以后是不是所有方法都必须通过对象调用?
不是。
Java 类中既可以存在:
对象级成员
也可以存在:
类级成员
关键是判断:
这个数据或行为真正属于谁?
例如:
student.study();
显然属于某个学生对象。
而:
Math.max(10, 20);
则并不需要创建一个具有独立状态的“Math 对象”。
六、常见问题
6.1 static 可以修饰什么?
本章重点掌握:
成员变量
成员方法
后续还会在其他知识中遇到更多 static 使用场景,本章不提前展开。
6.2 静态变量和实例变量最大的区别是什么?
静态变量属于:
类
实例变量属于:
对象
因此静态变量通常是共享状态,实例变量通常保存不同对象自己的状态。
6.3 静态变量应该用对象访问吗?
语法上某些情况下可以:
student.schoolName
但不推荐。
推荐:
Student.schoolName
因为它能准确表达:
schoolName属于Student类。
6.4 静态方法必须创建对象才能调用吗?
不需要。
推荐:
ArrayUtil.max(numbers);
6.5 可以使用对象调用静态方法吗?
Java 语法允许某些形式:
util.max(numbers);
但不推荐。
应该写成:
ArrayUtil.max(numbers);
6.6 static 方法为什么不能直接访问实例变量?
因为静态方法执行时没有隐含的当前对象。
而实例变量必须依附于一个具体对象。
因此 Java 不知道应该访问哪个对象的数据。
6.7 static 方法完全不能访问对象数据吗?
不是。
只要明确拥有对象引用,就可以:
public static void print(Student student) {
System.out.println(student.getName());
}
不能做的是:
System.out.println(name);
这种没有指定对象的直接访问。
6.8 实例方法能访问静态变量吗?
可以。
因为静态变量属于类,而实例对象本身也属于这个类。
但为了表达清晰,在一些场景中仍然可以主动写:
Student.schoolName
6.9 静态方法中为什么不能使用 this?
因为:
this
代表当前对象。
静态方法不依赖具体当前对象,因此没有 this。
6.10 所有通用方法都应该 static 吗?
不是。
要判断该方法是否:
不依赖具体对象状态
如果某个方法的行为明显依赖某个对象:
account.withdraw();
student.study();
player.attack();
那么实例方法更加合理。
6.11 工具类为什么要私有构造器?
如果一个工具类没有创建对象的意义,通过:
private ToolClass() {
}
可以阻止外部无意义地创建实例。
七、练习与验收
7.1 知识问答
static是什么?- static 修饰成员变量以后,该变量属于类还是对象?
- 什么是静态变量?
- 什么是实例变量?
- 静态变量和实例变量的共享关系有什么区别?
- 什么场景适合使用静态变量?
- 为什么学生姓名不应该轻易设计成 static?
- 为什么学校名称可能适合设计成 static?
- 静态变量推荐使用什么方式访问?
- 为什么不推荐
对象.静态变量? - 什么是静态方法?
- 什么是实例方法?
- 为什么工具类中的方法经常使用 static?
- 静态方法能直接访问哪些成员?
- 静态方法为什么不能直接访问实例成员?
- 静态方法能否通过对象引用访问实例成员?
- 实例方法能否直接访问静态成员?
- 为什么静态方法中不能出现
this? - 工具类为什么常将构造器私有化?
- static 是否应该被理解成一种“为了省内存的技巧”?为什么?
7.2 代码阅读
阅读:
class User {
static int count = 0;
String name;
public User(String name) {
this.name = name;
count++;
}
}
public class Test {
public static void main(String[] args) {
new User("A");
new User("B");
new User("C");
System.out.println(User.count);
}
}
不运行程序,回答:
- 一共创建了几个
User对象? count属于对象还是类?- 三个对象是否分别拥有不同的
count? - 最后一行输出什么?
- 如果去掉
static,还能否使用User.count?
阅读:
class Student {
static String school = "A大学";
String name;
public static void showSchool() {
System.out.println(school);
}
public void showInfo() {
System.out.println(name);
System.out.println(school);
}
}
回答:
showSchool()为什么可以直接访问school?- 如果在
showSchool()中直接输出name会发生什么? showInfo()为什么既能访问name又能访问school?showSchool()推荐通过什么方式调用?showInfo()推荐通过什么方式调用?
7.3 手写代码
任务一:对象统计
设计:
User
要求:
- 包含实例变量
username。 - 包含静态变量
count。 - 每创建一个对象,
count自动增加。 - 提供静态方法查询总对象数量。
- 创建至少五个对象进行测试。
任务二:ArrayUtil
从零设计:
ArrayUtil
要求提供:
max(int[] arr)
min(int[] arr)
sum(int[] arr)
average(int[] arr)
要求:
- 所有功能采用静态方法。
- 外部不能创建
ArrayUtil对象。 - 使用
类名.方法()调用。 - 不依赖 IDE 自动生成代码。
任务三:VerifyCodeUtil
设计:
VerifyCodeUtil
提供:
public static String getCode(int n)
要求:
- 返回指定长度验证码。
- 验证码可以由数字、大写字母、小写字母组成。
- 工具类不能被外部实例化。
- 编写测试类连续调用 5 次。
7.4 Debug
下面代码存在问题:
public class Student {
private String name;
public static void printName() {
System.out.println(name);
}
}
回答:
- 为什么不能编译?
name属于谁?printName()属于谁?- 应该如何修改设计?
再分析:
public class Tool {
public Tool() {
}
public int add(int a, int b) {
return a + b;
}
}
业务需求:
Tool只是提供无状态的通用计算功能。
回答:
- 当前为什么每次使用都可能需要创建对象?
add()是否适合改成静态方法?- 构造器是否有必要公开?
- 修改后的调用方式应该是什么?
7.5 综合训练
设计一个:
Student
要求:
name 每个学生不同
age 每个学生不同
schoolName 全部学生共享
count 记录创建学生总数
并提供:
printInfo()
getCount()
要求你自行判断:
- 哪些成员应该 static;
- 哪些成员不应该 static;
- 哪些方法应该 static;
- 哪些方法应该是实例方法。
完成后解释每一个设计决策。
7.6 本章验收
不查看资料,检查自己是否能够:
- [ ] 解释 static 的核心含义。
- [ ] 区分静态变量与实例变量。
- [ ] 判断一个数据应该属于类还是对象。
- [ ] 正确使用
类名.静态变量。 - [ ] 正确使用
对象.实例变量。 - [ ] 独立写出静态方法。
- [ ] 独立写出实例方法。
- [ ] 解释静态方法为什么不能直接访问实例成员。
- [ ] 解释实例方法为什么可以访问静态成员。
- [ ] 解释静态方法为什么没有
this。 - [ ] 使用 static 完成对象计数。
- [ ] 从零手写
ArrayUtil。 - [ ] 从零手写
VerifyCodeUtil。 - [ ] 正确私有化工具类构造器。
如果你只是记住:
static = 静态
但无法判断一个成员到底应该属于“类”还是“对象”,那么这一章还没有真正掌握。