3.3 封装性
封装就是把属性藏起来(private 上锁),再开两个受控的门(getter 看、setter 改)——让"改数据"这件事必须经过类的安检,垃圾数据进不了门。
1. 为什么需要封装——3.2 留下的隐患
先看 3.2 写的 Car 类,它有个大问题:
public class Car {
String brand; // 品牌
double price; // 价格
}
属性直接暴露在外,谁拿到对象谁就能随手改:
public static void main(String[] args) {
Car car = new Car();
car.brand = "宝马";
car.price = 200000; // 合理
car.price = -99999; // 价格是负的?!
car.brand = "一点五辆宝马"; // 随便塞什么都行
System.out.println(car.price); // 输出 -99999.0,程序照跑
}
car.price = -99999; 这行编译不会报错——在 Java 眼里它完全合法(double 格子确实装得下 -99999)。但业务上是垃圾数据。编译器管不了业务规则,所以得让类自己管。
这不是某个人的错,是设计的漏洞:改数据这件事"谁来改、什么时候改、改成什么"完全失控。封装就是补这个洞的。
2. private——给属性上锁
第一步,把属性藏起来。改一个关键字:
public class Car {
private String brand; // 私有:只有本类内部能访问
private double price;
}
private 是访问修饰符,意思是"私有的"——出了这个类,谁都不许碰。验证一下:
Car car = new Car();
car.price = -99999; // 编译报错!
java: price 在 Car 中是 private 访问控制
好消息:垃圾数据当场被拦下了。坏消息:合法的赋值(car.price = 200000;)也被拦了——门全关死了,自己人都进不来。
所以还需要第二步:开两扇受控的门。
3. getter 和 setter——两扇带门卫的门
给每个私有属性配两个公开方法:
public class Car {
private String brand;
private double price;
// setter:写入器——别人改值走这扇门,门里有安检
public void setPrice(double p) {
if (p >= 0) { // 安检:业务规则写在这
price = p; // 合法,收下
} else {
System.out.println("价格不能为负,赋值失败");
}
}
// getter:读取器——别人看值走这扇门
public double getPrice() {
return price;
}
}
调用方(TestCar)现在长这样:
Car car = new Car();
car.setPrice(200000); // 门卫检查:合法 → 收下
car.setPrice(-99999); // 门卫检查:负数 → 拒绝,打印提示
double p = car.getPrice(); // 只准看,不准直接摸
- 属性
price→ 读的方法叫getPrice(),写的方法叫setPrice(参数) - 格式:
get/set+ 属性名首字母大写 - boolean 属性的 getter 惯例用
isXxx(),比如isRich()
这个格式是全 Java 圈的统一惯例,不是语法强制的,但没人敢不遵守——很多框架就靠这个名字找方法。
为什么要分两扇门? 因为门是可以单独控制的:
- 只给 getter 不给 setter → 这属性只读(比如身份证号,出生定了就不改)
- setter 里加安检 → 垃圾数据进不来
- getter 里加工 → 比如返回
price * 0.9(对外的"会员价"),内部真实价没人知道
4. this 关键字——"我这个对象的"
上面 setter 里的参数名 p 其实偷懒了。标准写法参数名应该和属性名一致:
public void setPrice(double price) { // 参数也叫 price
if (price >= 0) {
this.price = price; // this.price 是属性,price 是参数
}
}
问题来了:两个 price,到底谁是谁?
- 就近原则:方法里直接写
price,指的是参数(离得近的那个) this.price指的是当前对象的属性——"我这个对象的 price"
把 this 读成"我这个"。this.price = price; 的意思是:"把我这个对象的 price 属性,赋成传进来的参数 price"。左边是属性,右边是参数,靠 this 区分。
省略 this 行不行?行,但只在不撞名的时候:
public void setPrice(double p) {
price = p; // 不撞名,this 可以省
}
this 指的不是 Car 这个图纸,而是当前正在执行方法的那个具体对象。car1.setPrice(...) 执行时,this 就是 car1;car2.setPrice(...) 执行时,this 就是 car2。3.2 学的"new 一次造一辆",每辆车各有自己的 price 格子,this 就是站在某辆车面前说"你这辆车"。
5. 封装的标准姿势(三件套)
以后每写一个类,标准流程就三步,一个都不能少:
| 步骤 | 代码 | 作用 |
|---|---|---|
| ① 属性全部 private | private double price; |
上锁,外界摸不到 |
| ② 配 getter | public double getPrice() |
开"看"的门 |
| ③ 配 setter | public void setPrice(double price) |
开"改"的门,门里安检 |
方法一般 public(方法是对外的功能接口,藏着别人没法用),属性一般 private(属性是内部状态,藏着才安全)——记住这句口诀。
省的是眼前几行代码,赔的是将来整个项目。举例:项目跑了一年,100 处代码直接改 car.price,现在要求"价格改动必须写日志"——你得翻 100 个地方。封装后只有 setter 一个门,改一处,全项目生效。封装就是把"变化"圈在一个地方。这是它真正的价值,安检只是顺手的好处。
6. IDEA 快捷生成——门卫不用手写
getter/setter 是纯格式化代码,IDEA 一键生成(保姆式):
- 在类里右键 → Generate(或按 Alt + Insert)
- 弹出菜单选 Getter and Setter
- 按住 Ctrl 点选要生成属性的 getter 和 setter(或 Ctrl+A 全选)
- 回车——全部生成完毕
IDEA 生成的 setter 是最朴素版(没有安检):this.price = price; 直接赋值。安检的业务规则永远要你手动补——机器懂格式,不懂你的业务。
7. 动手部分:把 3.2 的 Car 封装一遍
第 1 步:在 IDEA02 项目 src 下新建类 Student(换个人物练,顺便巩固)。
第 2 步:抄入:
public class Student {
private String name;
private int score;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setScore(int score) {
if (score >= 0 && score <= 100) { // 分数必须在 0~100 之间
this.score = score;
} else {
System.out.println("分数不合法(0~100),赋值失败");
}
}
public int getScore() {
return score;
}
public void showInfo() {
System.out.println("姓名:" + name + ",分数:" + score);
}
}
第 3 步:新建测试类 TestStudent:
public class TestStudent {
public static void main(String[] args) {
Student stu = new Student();
stu.setName("波仔");
stu.setScore(92);
stu.setScore(150); // 越界 → 门卫拒绝
stu.showInfo(); // 姓名:波仔,分数:92(150 没进得来)
}
}
看到什么算成功:控制台先打印"分数不合法(0~100),赋值失败",再打印"姓名:波仔,分数:92"。150 被门卫拦在门外——这就是封装的现场。
三个 30 秒实验:
- 把
stu.setScore(92)改成stu.score = 92——编译报错,亲眼看看"上锁"长什么样 - 用 Alt + Insert 一键重新生成 getter/setter,对比手写的(生成版 setter 没安检,别用它覆盖你加了安检的)
- 把 setter 安检里的
else分支删掉会怎样?——不合法的值被默默忽略,连提示都没有(不报错但更迷惑,所以提示很重要)
8. 常见误区
只上锁不开门,属性彻底没法用——那不叫封装,叫自断后路。封装 = 藏起来 + 受控地暴露,两个动作缺一不可。
不是语法规定,是工程惯例。Java 语法只管 private 挡外人,getter/setter 是全行业的标准接口约定,框架靠它工作,不写寸步难行。
参数名和属性名撞名时,this 不能省(省了就全指参数,自己给自己赋值,属性永远收不到值)。不撞名时省略没问题,但初学建议全写上——写清楚了才不会糊涂。
new 造对象和访问属性是两回事。private 锁的是属性访问(car.price 这种点号直取),不影响你造对象。想连 new 都管控,那是后面"构造方法"的事。
if (score >= 0 && score <= 100) { stu.setScore(score); } ——安检写在调用方,等于门卫失业:谁绕过你这个 if,垃圾数据照样进。规则必须写在类的 setter 里,和属性锁在一起才跑不掉。
9. 自测题
private 把属性限制在本类内部可访问,类外(包括 main 方法里)用点号直接访问会编译报错。只锁不开门,外界既不能读也不能写——属性成了摆设。所以封装必须配 getter/setter。
左边 this.price 是当前对象的属性(堆里那辆车自己的格子);右边 price 是方法参数(刚传进来的值)。this 就是"我这个",靠它区分撞名的两个变量。
都不会。安检代码发现 150 不在 0~100 之间,走 else 分支打印提示,赋值语句没执行,属性保持原值。程序照常跑完——这正是封装的价值:垃圾数据被拦,程序不崩。
public String getName() { return name; }——返回值类型是 String(和属性类型一致),方法名 getName(get + 首字母大写的属性名)。没有参数,因为它不需要你给它什么,只要把值交出去。
因为 setter 是属性的唯一入口(private 锁死了其他路),安检写在门里就谁也绕不过去;写在调用方则只是"某一个调用者自觉",任何新代码或别人绕开它直接调 setter,垃圾数据照样进——规则和门锁在一起,才是真的门禁。
10. 下一步学什么
- ☑
3.3 封装性✅ 你已完成,垃圾数据从此进不了门 - ☑
3.4 构造方法✅ 已完成——"想连 new 都管控"的答案来了:出生瞬间的状态由构造方法管,private 管出生后的属性访问,两道闸门配合,详见 3.4 构造方法
相关笔记
- 3.2 类与对象 ← 上一篇:属性裸奔的隐患,这节就是来堵它的
- 3.1 面向对象的思想 ← 图纸与实物的思想根基
- 2.2 Java中的变量 ← 属性就是成员变量
- 2.6 方法 ← getter/setter 本质就是方法
- 📇 Java基础 · 索引
- 🏠 返回主页