Java零基础入门:封装与继承 —— 从“裸奔“到“穿衣服“,从“重复造轮子“到“站在巨人肩膀上“

  • 封装(Encapsulation)

为什么需要封装?

我们不妨想想一下我们去银行存钱,

❌ 没有封装的世界:

你直接走进金库,自己往里面放钱、拿钱

没人监督 → 可能放少拿多 → 乱套了

✅ 有封装的世界:

你把钱交给柜台,柜员帮你操作金库

柜员会检查金额是否合法、记录流水

你不能直接碰金库 → 安全可控

所以封装就是把数据"锁"起来,只留一个受控的入口和出口。

没有封装的问题例如:

public class Person {

String name;

int age;

}

// 使用

Person p = new Person();

p.name = "张三";

p.age = -200; // ❌ 年龄怎么可能是-200?但编译器不报错!

这就导致了外部代码可以直接访问成员变量,想赋什么值就赋什么值,没有任何限制。

封装的实现:private + getter/setter

public class Person {

// 1. 成员变量用 private 修饰 → 外面不能直接访问

private String name;

private int age;

// 2. 提供 public 的 getter(获取)和 setter(修改)

// getter:获取姓名

public String getName() {

return name;

}

// setter:设置姓名(带校验)

public void setName(String name) {

if (name == null || name.trim().length() == 0) {

System.out.println("姓名不能为空!");

return;

}

this.name = name;

}

// getter:获取年龄

public int getAge() {

return age;

}

// setter:设置年龄(带校验)

public void setAge(int age) {

if (age < 0 || age > 150) {

System.out.println("年龄必须在0-150之间!");

return;

}

this.age = age;

}

}

// 使用

public class TestPerson {

public static void main(String\[\] args) {

Person p = new Person();

p.setName("张三");

p.setAge(20);

System.out.println(p.getName() + "," + p.getAge() + "岁");

// 输出:张三,20岁

p.setAge(-200); // 年龄必须在0-150之间!

System.out.println("年龄仍然是:" + p.getAge()); // 20

}

}

所以封装有三大好处:

好处 说明
数据安全​ 外部不能直接改数据,必须通过 setter,setter 里可以加校验
隐藏实现​ 外部只需要知道"怎么用",不需要知道"内部怎么存"
易于维护​ 想改内部逻辑,只需要改 setter/getter,外面代码不用动

封装的命名规范:

private 成员变量:直接写名字

public getter:get + 首字母大写的变量名(boolean 可以用 is)

public setter:set + 首字母大写的变量名

示例:

private String name; → getName() / setName(String name)

private int age; → getAge() / setAge(int age)

private boolean alive; → isAlive() / setAlive(boolean alive)

  • 继承

为什么需要继承呢?

想象你在设计游戏角色:

// ❌ 没有继承:每个类重复写相同的代码

class Warrior {

String name;

int hp;

int level;

void move() { System.out.println("移动"); }

void attack() { System.out.println("攻击"); }

}

class Mage {

String name; // 重复

int hp; // 重复

int level; // 重复

void move() { System.out.println("移动"); } // 重复

void attack() { System.out.println("攻击"); } // 重复

void castSpell() { System.out.println("施法"); }

}

就会遇到name、hp、level、move()、attack() 在每个类里都写了一遍。

那有继承的写法呢

// 父类:提取公共部分

class Character {

String name;

int hp;

int level;

void move() { System.out.println(name + "移动"); }

void attack() { System.out.println(name + "攻击"); }

}

// 子类:只写自己特有的

class Warrior extends Character {

void useSword() { System.out.println(name + "挥剑"); }

}

class Mage extends Character {

void castSpell() { System.out.println(name + "施法"); }

}

继承的核心概念

概念 说明
父类(超类/Super Class) 被继承的类,提供公共特征和行为
子类(派生类/Sub Class) 继承父类的类,拥有父类所有非private成员
extends 关键字 表示"继承"关系
继承关系 is-a 关系:子类 "是一个" 父类

Character(角色) ← 父类

├── Warrior(战士) ← 子类,"战士是一个角色"

├── Mage(法师) ← 子类,"法师是一个角色"

└── Archer(弓箭手) ← 子类,"弓箭手是一个角色"

继承语法; 修饰符 class 子类名 extends 父类名 {

// 子类特有的成员

}

示例:

// 父类

public class Animal {

String name;

int age;

public void eat() {

System.out.println(name + "在吃东西");

}

public void sleep() {

System.out.println(name + "在睡觉");

}

}

// 子类

public class Dog extends Animal {

String breed; // 品种(子类特有)

public void wagTail() { // 子类特有方法

System.out.println(name + "摇尾巴");

}

}

// 测试

public class TestInheritance {

public static void main(String\[\] args) {

Dog dog = new Dog();

dog.name = "旺财"; // 继承自 Animal

dog.age = 3; // 继承自 Animal

dog.breed = "金毛"; // Dog 自己的

dog.eat(); // 调用继承来的方法 → 旺财在吃东西

dog.sleep(); // 调用继承来的方法 → 旺财在睡觉

dog.wagTail(); // 调用自己的方法 → 旺财摇尾巴

}

}

什么能被继承?什么不能?

成员 能否被继承 说明
public 成员 ✅ 能 完全继承
protected 成员 ✅ 能 后面学访问修饰符时详细讲
default(包访问) ⚠️ 同包能 不同包不能
private 成员 ❌ 不能 子类看不到
构造方法 ❌ 不能 子类必须自己写,但可以调用父类构造
static 成员 ✅ 能(但不是继承,是共享) 通过类名访问
  • 编码练习

设计思路:

Animal(父类)

├── 属性:name, age

├── 方法:eat(), sleep(), makeSound()

│

├── Dog(子类)

│ ├── 特有属性:breed

│ ├── 重写 makeSound()

│ └── 特有方法:fetch()

│

└── Cat(子类)

├── 特有属性:color

├── 重写 makeSound()

└── 特有方法:climb()

完整代码:

// ========== 父类 Animal ==========

public class Animal {

// 成员变量

protected String name; // protected:子类可以直接访问

protected int age;

// 构造方法

public Animal() {

System.out.println("Animal 无参构造被调用");

}

public Animal(String name, int age) {

this.name = name;

this.age = age;

System.out.println("Animal 有参构造被调用");

}

// 通用方法

public void eat() {

System.out.println(name + "在吃东西");

}

public void sleep() {

System.out.println(name + "在睡觉");

}

// 通用方法(设计为可被重写的)

public void makeSound() {

System.out.println(name + "发出声音");

}

// getter/setter

public String getName() { return name; }

public void setName(String name) { this.name = name; }

public int getAge() { return age; }

public void setAge(int age) { this.age = age; }

}

// ========== 子类 Dog ==========

public class Dog extends Animal {

private String breed; // 品种

// 构造方法

public Dog() {

super(); // 调用父类无参构造(可省略,编译器自动加)

System.out.println("Dog 无参构造被调用");

}

public Dog(String name, int age, String breed) {

super(name, age); // 调用父类有参构造

this.breed = breed;

System.out.println("Dog 有参构造被调用");

}

// 重写 makeSound()

@Override

public void makeSound() {

System.out.println(name + ":汪汪汪!");

}

// 子类特有方法

public void fetch() {

System.out.println(name + "在捡球");

}

public String getBreed() { return breed; }

public void setBreed(String breed) { this.breed = breed; }

}

// ========== 子类 Cat ==========

public class Cat extends Animal {

private String color; // 颜色

// 构造方法

public Cat() {

super(); // 调用父类无参构造

System.out.println("Cat 无参构造被调用");

}

public Cat(String name, int age, String color) {

super(name, age); // 调用父类有参构造

this.color = color;

System.out.println("Cat 有参构造被调用");

}

// 重写 makeSound()

@Override

public void makeSound() {

System.out.println(name + ":喵喵喵~");

}

// 子类特有方法

public void climb() {

System.out.println(name + "在爬树");

}

public String getColor() { return color; }

public void setColor(String color) { this.color = color; }

}

// ========== 测试类 ==========

public class TestAnimal {

public static void main(String\[\] args) {

System.out.println("===== 创建 Dog =====");

Dog dog = new Dog("旺财", 3, "金毛");

dog.eat(); // 继承来的

dog.sleep(); // 继承来的

dog.makeSound(); // 重写后的 → 旺财:汪汪汪!

dog.fetch(); // 自己的

System.out.println("品种:" + dog.getBreed());

System.out.println("\n===== 创建 Cat =====");

Cat cat = new Cat("咪咪", 2, "橘色");

cat.eat(); // 继承来的

cat.sleep(); // 继承来的

cat.makeSound(); // 重写后的 → 咪咪:喵喵喵~

cat.climb(); // 自己的

System.out.println("颜色:" + cat.getColor());

System.out.println("\n===== 多态预览 =====");

// 父类引用指向子类对象(后面多态章节会详细讲)

Animal a1 = new Dog("小黑", 1, "拉布拉多");

Animal a2 = new Cat("小花", 4, "黑白");

a1.makeSound(); // 小黑:汪汪汪!

a2.makeSound(); // 小花:喵喵喵~

}

}

  • 方法重写(Override)

什么是方法重写?

子类对父类已有的方法重新实现,方法签名完全相同。

就像你继承了爸爸的"做饭"技能,但你有自己的做法:

爸爸的做饭 → 炒青菜

你的做饭 → 做红烧肉(同名,但内容不同)

重写规则(必须满足):

规则 说明
方法名必须相同​ 这是重写的前提
参数列表必须完全相同​ 类型、个数、顺序都一样
返回值类型必须兼容​ 相同,或是父类返回值的子类
访问修饰符不能更严格​ 父类 public → 子类不能 private
不能重写 private 方法 子类看不到父类的 private 方法
不能重写 static 方法 static 方法属于类,不参与重写

@Override 注解(强烈推荐):

@Override

public void makeSound() {

System.out.println("汪汪汪!");

}

@Override 告诉编译器:"我这个方法是要重写父类的"。如果写错了(比如方法名拼错),编译器会报错提醒你。

重写 vs 重载 ------ 对比速查

对比项 重写(Override) 重载(Overload)
发生位置 父子类之间 同一个类中
方法名 相同 相同
参数列表 必须完全相同​ 必须不同​
返回值 必须兼容 可以不同
访问修饰符 不能更严格 随便
目的 子类改变/扩展父类行为 同名方法适应不同参数
  • super修饰词

super 是什么?

super 代表"父类对象",用来访问父类的成员。

用法 语法 说明
调用父类构造方法 super(参数) 必须写在子类构造方法第一行
调用父类普通方法 super.方法名() 当子类重写了父类方法,想调用父类原版
访问父类成员变量 super.成员变量 当子类有同名变量时(较少用)

用法1:调用父类构造方法

public class Dog extends Animal {

private String breed;

public Dog(String name, int age, String breed) {

super(name, age); // 调用父类 Animal(String, int) 构造

this.breed = breed;

}

}

new Dog("旺财", 3, "金毛")

│

├─ 先执行 super(name, age) → 调用 Animal 的构造

│ └─ Animal 构造:this.name = "旺财"; this.age = 3;

│

└─ 再执行 Dog 构造剩余部分:this.breed = "金毛";

super(参数) 必须写在构造方法第一行。如果不写,编译器自动加 super()(调用父类无参构造)。如果父类没有无参构造,子类必须手动写 super(参数),否则编译报错。

用法2:调用父类被重写的方法

public class Dog extends Animal {

@Override

public void makeSound() {

super.makeSound(); // 先调用父类的 makeSound()

System.out.println(name + ":汪汪汪!");

}

}

调用 dog.makeSound()

→ 先执行 Animal 的 makeSound() → 输出 "旺财发出声音"

→ 再执行 Dog 自己的逻辑 → 输出 "旺财:汪汪汪!"

这在扩展父类行为时非常有用------不是完全替换,而是在父类基础上加东西。

用法3:访问父类成员变量(同名时)

public class Animal {

protected String name = "动物";

}

public class Dog extends Animal {

private String name = "狗"; // 和父类同名

public void printNames() {

System.out.println("子类的 name:" + this.name); // "狗"

System.out.println("父类的 name:" + super.name); // "动物"

}

}

  • super 和 this 的区别(重点对比)
对比维度 this super
代表什么 当前对象​ 父类对象​
调用构造方法 this(参数) → 调用本类其他构造 super(参数) → 调用父类构造
调用成员方法 this.方法() → 调用本类方法 super.方法() → 调用父类方法
访问成员变量 this.变量 → 本类变量 super.变量 → 父类变量
能否同时用 this() 和 super() 不能同时出现在构造方法中 同上
必须写在第一行 this() 必须第一行 super() 必须第一行
默认行为 不写时编译器自动加 this. 不写时如果找不到本类成员,自动向上找父类

图解:this 和 super 在内存中的关系

栈内存 堆内存

───── ─────

dog ──→ 0x100 ──→ Dog 对象 {

Animal部分 ← super 指向这里

name = "旺财"

age = 3

Dog部分 ← this 指向这里

breed = "金毛"

}

this 指向整个对象,super 指向对象中"属于父类的那部分"。

  • 综合实战:员工管理系统

// ========== 父类:Employee ==========

public class Employee {

protected String id;

protected String name;

protected double salary;

public Employee() {}

public Employee(String id, String name, double salary) {

this.id = id;

this.name = name;

this.salary = salary;

}

// 通用方法

public void work() {

System.out.println(name + "在工作");

}

public void showInfo() {

System.out.println("工号:" + id + ",姓名:" + name + ",月薪:" + salary);

}

// 计算年薪(通用逻辑)

public double calculateYearlySalary() {

return salary * 12;

}

}

// ========== 子类:Manager ==========

public class Manager extends Employee {

private double bonus; // 奖金(经理特有)

public Manager() {}

public Manager(String id, String name, double salary, double bonus) {

super(id, name, salary); // 调用父类构造

this.bonus = bonus;

}

// 重写工作方法

@Override

public void work() {

System.out.println(name + "在管理团队");

}

// 重写年薪计算(加奖金)

@Override

public double calculateYearlySalary() {

return super.calculateYearlySalary() + bonus; // 父类逻辑 + 奖金

}

// 经理特有方法

public void holdMeeting() {

System.out.println(name + "在开会");

}

public double getBonus() { return bonus; }

public void setBonus(double bonus) { this.bonus = bonus; }

}

// ========== 子类:Developer ==========

public class Developer extends Employee {

private String programmingLanguage; // 编程语言

public Developer() {}

public Developer(String id, String name, double salary, String language) {

super(id, name, salary);

this.programmingLanguage = language;

}

// 重写工作方法

@Override

public void work() {

System.out.println(name + "在写" + programmingLanguage + "代码");

}

// 开发者特有方法

public void debug() {

System.out.println(name + "在调试代码");

}

public String getProgrammingLanguage() { return programmingLanguage; }

public void setProgrammingLanguage(String lang) { this.programmingLanguage = lang; }

}

// ========== 测试 ==========

public class TestEmployee {

public static void main(String\[\] args) {

Manager mgr = new Manager("M001", "王经理", 20000, 50000);

Developer dev = new Developer("D001", "李开发", 15000, "Java");

mgr.work(); // 王经理在管理团队

mgr.holdMeeting();

mgr.showInfo();

System.out.println("经理年薪:" + mgr.calculateYearlySalary());

System.out.println();

dev.work(); // 李开发在写Java代码

dev.debug();

dev.showInfo();

System.out.println("开发者年薪:" + dev.calculateYearlySalary());

}

}

  • 每日练习

练习1(基础):定义 Vehicle 父类

属性:brand(品牌)、speed(速度)

方法:run() 打印行驶信息

子类 Car 和 Bike 继承 Vehicle

Car 重写 run() 输出"汽车在公路上行驶"

Bike 重写 run() 输出"自行车在自行车道上行驶"

练习2(进阶):定义 Shape 父类

方法:getArea() 和 getPerimeter()(返回0,作为默认实现)

子类 Rectangle 和 Circle 继承 Shape

各自重写 getArea() 和 getPerimeter()

在子类构造方法中使用 super 调用父类构造

练习3(挑战):定义 Account 银行账号体系

父类 Account:账号、余额、存款、取款

子类 SavingAccount(储蓄账户):有利息率,取款有限制

子类 CreditAccount(信用账户):有信用额度,可以透支

在子类中重写 withdraw() 方法

使用 super 调用父类的 withdraw() 复用逻辑

相关推荐
Escalating_xu1 小时前
【C 语言】深入理解指针(4):回调函数、qsort、void * 与泛型排序的模拟实现
java·c语言·开发语言
Shaoshing1 小时前
雪花算法
java
w_zero_one1 小时前
链表(4)
java·数据结构·算法
吠品1 小时前
GitHub 周榜拆解:决策小模型扎堆上位
c语言·开发语言·算法
见叶之秋2 小时前
C++ 泛型世界的两块拼图:容器适配器与仿函数
java·开发语言
MayBaymax2 小时前
MQ 基础概念与架构
java·中间件·架构·java-rocketmq
逗脑IDE2 小时前
零基础学ESP32:RFID无线射频卡——让ESP32拥有“刷卡“能力!
开发语言·网络·人工智能·python·esp32·硬件开发
Dovis(誓平步青云)2 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
小师兄吃牛肉2 小时前
C语言篇:内联函数
c语言·开发语言