前言
在前面的 Java 学习中,我们已经接触了面向对象编程的一些基础内容:
-
类和对象
-
构造方法
-
this 关键字
-
对象属性初始化
学到这里,我们已经可以创建一个 Student 类,并通过对象访问其中的属性。
例如:
Student stu = new Student();
stu.name = "洋洋";
stu.age = 20;
这样的代码看起来非常简单。
但问题也很明显:
如果对象的属性可以被外部代码随意修改,那么下面这样的代码同样可能出现:
stu.age = -100;
从 Java 语法角度来看,这并不一定报错。
但是从业务逻辑来看,学生年龄显然不应该是负数。
这就引出了 Java 面向对象中的一个核心思想:
封装(Encapsulation)。
本文是《Java学习笔记》第09篇,主要学习:
-
什么是 Java 封装
-
为什么成员变量通常使用 private
-
getter 和 setter 方法的作用
-
如何通过 setter 进行数据校验
-
构造方法和封装如何配合
-
为什么不是所有属性都必须提供 setter
-
封装在实际 Java 后端开发中的应用
一、什么是封装?
封装是 Java 面向对象三大基本特征之一。
面向对象常见的三个核心特征包括:
封装
继承
多态
封装可以简单理解为:
隐藏对象内部的实现细节,通过规定好的方法访问和修改对象中的数据。
例如现实生活中的手机。
我们使用手机拍照时,只需要点击“拍照”按钮。
至于内部:
-
摄像头如何工作
-
图像如何处理
-
数据如何保存
普通用户并不需要直接操作这些内部结构。
用户只需要通过手机提供的功能使用设备。
Java 中的封装也是类似的思想。
对象内部的数据不应该让外部代码随意修改,而应该通过合理的方法控制访问。
二、为什么需要封装?
先看一个没有进行封装的类。
public class Student {
String name;
int age;
}
创建对象:
public class Main {
public static void main(String[] args) {
Student stu = new Student();
stu.name = "洋洋";
stu.age = 20;
System.out.println(stu.name);
System.out.println(stu.age);
}
}
运行结果:
洋洋
20
目前没有问题。
但是现在修改年龄:
stu.age = -100;
从程序设计角度来看,这就出现了问题。
因为外部代码可以直接修改 Student 对象内部的数据。
如果对象中保存的不是年龄,而是:
-
用户密码
-
银行余额
-
商品库存
-
订单状态
-
学生成绩
那么允许任意代码直接修改这些数据,就会带来很大的维护风险。
因此,我们需要限制外部代码直接访问属性。
这时候就会用到:
private
三、private关键字
private 是 Java 中的访问修饰符。
使用 private 修饰的成员,只能在当前类内部直接访问。
例如:
public class Student {
private String name;
private int age;
}
现在如果在 Main 类中这样写:
Student stu = new Student();
stu.name = "洋洋";
IDE 会直接提示错误。
原因是:
name
已经被声明为 private。
外部类不能直接访问。
private的基本作用
可以简单理解为:
private = 限制外部直接访问
这样就可以防止外部代码随意修改对象内部的数据。
但是问题又来了:
既然不能直接访问属性,那么应该怎么给对象赋值?
答案就是:
getter 和 setter 方法。
四、什么是setter方法?
setter 方法主要用于:
修改对象中的属性值。
例如:
public class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
}
现在外部不能直接写:
stu.age = 20;
但是可以通过:
stu.setAge(20);
设置年龄。
setter命名习惯
一般情况下:
属性:
name
对应:
setName()
属性:
age
对应:
setAge()
因此:
set + 属性名
通常就是 setter 方法。
五、setter真正的价值:控制数据是否合法
如果 setter 只是这样:
public void setAge(int age) {
this.age = age;
}
看起来似乎只是把:
stu.age = 20;
换成:
stu.setAge(20);
代码反而更多了。
但 setter 最大的价值并不是“换一种赋值方式”。
而是:
可以在修改数据之前进行控制。
例如:
public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("年龄必须在0~150之间");
}
}
现在:
stu.setAge(20);
可以正常保存。
但是:
stu.setAge(-100);
会输出:
年龄必须在0~150之间
错误数据不会直接进入对象。
这就是封装非常重要的作用之一:
保护对象内部状态。
六、什么是getter方法?
如果属性使用 private 修饰,那么外部不仅不能修改,也不能直接读取。
例如:
private int age;
外部不能这样读取:
System.out.println(stu.age);
如果需要获取数据,可以提供 getter 方法。
例如:
public int getAge() {
return age;
}
调用:
System.out.println(stu.getAge());
即可获取年龄。
同样:
private String name;
对应:
public String getName() {
return name;
}
getter和setter可以这样记
get → 获取
set → 设置
所以:
getName()
表示获取姓名。
setName()
表示设置姓名。
七、getter和setter完整写法
完整的 Student 类:
public class Student {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("年龄必须在0~150之间");
}
}
public int getAge() {
return age;
}
}
使用:
public class Main {
public static void main(String[] args) {
Student stu = new Student();
stu.setName("洋洋");
stu.setAge(20);
System.out.println(stu.getName());
System.out.println(stu.getAge());
}
}

运行结果:
洋洋
20

八、this关键字在setter中的作用
在 setter 中经常会看到:
this.name = name;
这里两个 name 的含义不同。
例如:
public class Student {
private String name;
public void setName(String name) {
this.name = name;
}
}
其中:
this.name
表示:
当前对象的成员变量 name。
右边的:
name
表示:
setName方法接收到的参数。
因此:
this.name = name;
完整理解就是:
把传入的name参数保存到当前对象的name属性中。
九、封装和构造方法如何结合?
上一篇学习了构造方法。
例如:
public Student(String name, int age) {
this.name = name;
this.age = age;
}
创建对象:
Student stu = new Student("洋洋", 20);
但是这里存在一个问题。
如果写:
Student stu = new Student("洋洋", -100);
错误的数据仍然可以进入对象。
如果 setAge() 中已经存在年龄校验:
public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("年龄必须在0~150之间");
}
}
那么构造方法可以直接复用:
public Student(String name, int age) {
this.name = name;
setAge(age);
}
这样无论:
通过构造方法:
new Student("洋洋", 20);
还是通过 setter:
stu.setAge(20);
年龄都使用同一套校验规则。
这样可以减少重复代码,也避免多个地方的校验规则不一致。
十、完整可运行案例
下面通过一个完整案例理解 Java 封装。
Student.java
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
setAge(age);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("年龄必须在0~150之间");
}
}
public int getAge() {
return age;
}
public void introduce() {
System.out.println(
"大家好,我叫" + name + ",今年" + age + "岁"
);
}
}
Main.java
public class Main {
public static void main(String[] args) {
Student stu = new Student();
stu.setName("洋洋");
stu.setAge(20);
System.out.println("姓名:" + stu.getName());
System.out.println("年龄:" + stu.getAge());
stu.introduce();
}
}

运行结果:
姓名:洋洋
年龄:20
大家好,我叫洋洋,今年20岁

如果修改为:
stu.setAge(-20);

运行:
年龄必须在0~150之间

错误数据不会被直接保存。
十一、为什么不直接使用public属性?
例如:
public class Student {
public String name;
public int age;
}
这样外部可以直接:
stu.age = -100;
对象完全无法阻止。
如果项目规模较大:
某个属性可能会在几十个甚至上百个位置被修改。
一旦数据出现问题,很难定位到底是哪段代码造成的。
而通过封装:
private int age;
再统一通过:
setAge()
修改。
就可以把所有规则集中管理。
十二、是不是所有private属性都必须有getter和setter?
不是。
这是学习 Java 封装时非常容易形成的误区。
很多初学教程会写:
private String name;
private int age;
然后自动生成:
getName()
setName()
getAge()
setAge()
但真正的封装并不是机械地生成 getter 和 setter。
应该根据业务需求决定:
-
属性是否允许读取
-
属性是否允许修改
-
修改时需要满足什么条件
示例:只读属性
假设用户ID创建以后不允许修改:
public class User {
private int id;
public User(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
这里:
提供:
getId()
但是没有:
setId()
因此外部可以读取ID。
但不能随意修改ID。
这才是封装真正的价值:
控制对象对外暴露的能力。
十三、封装并不等于getter和setter
这一点非常重要。
封装真正的思想不是:
private + getter + setter
而是:
隐藏内部状态,通过合理的接口控制对象行为。
例如银行账户:
public class BankAccount {
private double balance;
}
如果简单提供:
public void setBalance(double balance) {
this.balance = balance;
}
其实仍然存在问题。
因为外部可以:
account.setBalance(100000000);
对于银行余额来说,这显然不是合理的业务设计。
更好的方式可能是:
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
以及:
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
}
}
外部不能直接“设置余额”。
只能通过:
存款。
取款。
修改余额。
这才体现了封装真正的设计思想。
十四、封装在Java后端开发中的应用
Java后端开发中,封装非常常见。
例如用户实体:
public class User {
private Long id;
private String username;
private String password;
}
通常不会直接把所有成员变量暴露给外部。
因为不同数据具有不同的业务规则。
例如:
用户名:
可能不能为空。
密码:
可能需要加密。
用户ID:
可能由数据库自动生成。
账号状态:
可能只能通过特定业务流程修改。
因此,在实际开发中:
对象不仅负责保存数据,还应该负责维护自己的有效状态。
十五、封装的主要优点
1. 保护数据
防止外部代码直接修改对象内部状态。
2. 增加数据校验
例如:
if (age >= 0 && age <= 150)
可以阻止错误数据进入对象。
3. 降低代码耦合
外部代码只需要调用公开方法。
不需要关心内部具体实现。
4. 提高维护性
以后修改内部逻辑时:
只需要修改类内部代码。
调用者可以保持不变。
5. 控制对象行为
根据业务需要决定:
哪些数据可以读取。
哪些数据可以修改。
哪些数据完全不允许外部访问。
十六、Java封装常见问题
1. private属性一定安全吗?
private 可以限制其他类直接访问成员变量。
但它不是安全机制的全部。
如果提供了没有限制的 setter:
public void setAge(int age) {
this.age = age;
}
外部仍然可以传入错误数据。
所以:
真正的数据安全还需要配合业务校验。
2. setter一定要有吗?
不一定。
如果属性不允许外部修改,就不需要提供 setter。
3. getter一定要有吗?
也不一定。
如果某个属性属于内部实现细节,不应该暴露给外部,也可以不提供 getter。
4. private只能修饰属性吗?
不是。
private 还可以修饰方法、构造方法以及嵌套类型等成员。
例如:
private void checkAge() {
}
表示这个方法只能在当前类内部使用。
十七、总结
本文学习了 Java 面向对象中的封装。
核心知识包括:
什么是封装
private 关键字的作用
getter方法的作用
setter方法的作用
如何使用setter进行数据校验
构造方法如何复用setter逻辑
为什么不是每个属性都需要getter和setter
封装在实际Java开发中的作用
最重要的一点是:
封装并不是简单地把属性改成private,然后生成getter和setter。
真正的封装应该是:
隐藏对象内部状态,并通过合理的方法控制外部如何访问和修改数据。
学习到这里,Java面向对象也开始从:
“如何创建一个类”
逐渐进入:
“如何设计一个合理的类”。
这也是理解后续继承、多态以及Java后端开发中对象设计的重要基础。





