视频加载失败

Java 基础 Day02:继承、多态、接口与设计模式

2840 字
14 分钟
Java 基础 Day02:继承、多态、接口与设计模式

继承 extends#

认识继承#

  • 父类 People(name/sex),子类 Teacher(加 skill)、Consultant(加 number)
  • 子类自动拥有父类的非私有成员:t.setName("张三") 直接能用
  • 关键理解:子类对象 = 子类设计图 + 父类设计图共同创建,内存里父类那部分数据也真实存在(后面构造器一节呼应)

访问修饰符#

private void privateMethod() {} // 只能本类访问
void method() {} // default:本类 + 同包
protected void protectedMethod() {} // 本类 + 子类 + 同包
public void publicMethod() {} // 任意位置
修饰符本类同包子类不同包
private✅❌❌❌
default(不写)✅✅❌❌
protected✅✅✅❌
public✅✅✅✅

注:protected 的”子类”指跨包继承时子类内部能访问,不代表任意位置都能通过对象访问。实际项目里成员变量基本用 private,对外走 getter/setter。

继承的注意事项#

  • 单继承:Java 一个类只能继承一个父类(不支持多继承),但支持多层继承(A ← B ← C)
  • 所有类直接或间接继承 Object
  • 就近原则:同名变量/方法,优先用最近的——name(局部)→ this.name(本类)→ super.name(父类)
class Zi extends Fu {
String name = "zi";
public void show() {
String name = "show";
System.out.println(name); // show 局部
System.out.println(this.name); // zi 本类成员
System.out.println(super.name); // fu 父类成员
run(); // zi run 本类优先
super.run(); // fu run 指定父类
}
}

方法重写#

  • 重写条件:方法名、参数列表必须相同;返回类型可以不同(但要兼容,协变返回类型);修饰符权限不能比父类更严格(只能相同或更宽松,如父类 protected 子类可 public);@Override 标注让编译器帮检查
  • super.方法():在子类重写方法里调用被覆盖的父类版本(super.eat())
  • 经典应用场景:重写 Object.toString()——默认打印内存地址,重写后打印对象内容(Student{name=张三, age=18, sex=男})

继承下的构造器#

  • 子类构造器必须调用父类构造器:第一行默认隐式 super()(调父类无参构造)
  • 父类无无参构造时,子类必须显式 super(参数) 指定
  • super 和 this 都必须写在构造器第一行,且不能同时出现
class Zi extends Fu {
public Zi() {
super(555); // 先调父类构造器
System.out.println("子类无参构造器"); // 再执行自己的
}
}
  • 父类只有有参构造时,子类用 super(name, sex) 把父类部分交给父类构造器初始化,再用 this.skill = skill 初始化子类独有部分
  • 兄弟构造器 this(参数):一个构造器调用同类的另一个构造器,复用初始化逻辑
public Student(String name, int age) {
this(name, age, "上海大学"); // 转发给 3 参构造器
}
public Student(String name, int age, String school) {
this.name = name; this.age = age; this.school = school;
}

多态#

认识多态#

  • 对象多态:父类引用指向子类对象 Animal a1 = new Wolf();
  • 行为多态:同一个 eat(),不同子类不同表现(狼”嚼嚼嚼”、龟”叫叫叫”)
  • 口诀:编译看左边,运行看右边——调方法时看引用类型(左边)是否允许调,真正执行看对象类型(右边)的实现
  • 成员变量例外:编译看左边,运行也看左边!a1.name 拿到的是父类的 "动物",不是子类的 "狼"——因为变量是编译期绑定的,方法才有多态(动态绑定)

多态的应用#

  • 方法参数声明为父类类型,就能接收任意子类对象 → 解耦(调用方随便换实现,不用改方法)
public static void eaeet(Animal w) { // 传 Wolf / Tortoise 都行
w.eat();
}
  • 多态前提:必须有继承关系 + 子类重写方法
  • 局限:父类类型引用不能调用子类独有功能(a2.run() 编译报错)

强制转换(向下转型)#

  • 想调子类独有方法,把父类引用强转回子类类型:Tortoise t1 = (Tortoise) a2; → t1.run()
  • 强转前要用 instanceof 判断,否则转错类型运行时会抛 ClassCastException:if (a2 instanceof Tortoise) { ... }
  • 编译期规则:兄弟类(Wolf/Tortoise)之间直接强转或 instanceof,编译器直接报「不可转换的类型」——只有当引用变量是父类类型时,强转/判断才有意义

综合案例:加油站卡片#

  • 父类 Card:车主信息(name/cardId/phone/money)+ saveMoney / payMoney
  • 子类 GoldCard:重写 payMoney,打 8 折,满额打印洗车票
  • 子类 SilverCard:重写 payMoney,打 9 折
  • 多态传参:pay(Card d) 一个方法同时服务金卡/银卡
  • Lombok:父类用 @Data / @NoArgsConstructor / @AllArgsConstructor 一键生成 getter/setter/构造器(@Data 还包括 toString/equals/hashCode),替代手写
  • 扣款入口做了金额校验:格式错或负数时重新输入(inputMoney() 重试逻辑)

final 关键字#

  • 修饰类 → 不能被继承(final class A,class B extends A 报错)
  • 修饰方法 → 不能被重写
  • 修饰变量 → 只能赋值一次(常量)

三种”不能”:类不能有儿子、方法不能变、变量不能改。

单例模式#

  • 饿汉式:类加载时就创建好实例,线程安全
public class A {
public static final A a = new A(); // 类加载即创建,全局唯一
private A() {} // 1. 私有化构造器,禁止 new
public static A getInstance() { return a; }
}
  • 懒汉式:第一次调用 getInstance() 才创建,省资源但线程不安全(多线程下会创建多个,学并发时再优化)
public class B {
private static B b;
private B() {}
public static B getInstance() {
if (b == null) b = new B(); // 用的时候才 new
return b;
}
}
  • 验证:a1 == a2 为 true(同一个对象,比较的是地址)

枚举 enum#

  • 枚举类:第一行罗列对象名,本质是编译期就建好的常量对象:public enum A { X, Y, Z; }
  • 对比常量类:Constant.UP = 1 只是 int,参数传 99 也能进去(约束不住);用枚举 Direction 做参数类型,只能传枚举值,类型安全
public void move2(Direction direction) {
switch (direction) {
case UP: System.out.println("向上移动"); break;
case DOWN: System.out.println("向下移动"); break;
// ...
default: System.out.println("error");
}
}
  • 使用场景:方向、状态机、有限取值集合(如订单状态)

抽象类 abstract#

认识抽象类#

  • 抽象类不能创建对象(new A() 报错)
  • 抽象方法没有方法体,必须 abstract 修饰,子类必须重写(B extends A 重写 show())

抽象类的使用场景#

  • Animal 定义抽象 cry(),Cat 喵喵、Dog 汪汪——每个动物叫声不同,父类没法给通用实现,只能定”规则”(方法签名),让子类各自实现
  • 父类类型引用子类对象:Animal a = new Dog(); a.cry();——抽象类也能玩多态

模板方法设计模式#

  • 场景:老师和学生都要写作文《我的爸爸》,首尾两段一样,中间一段各不相同
  • 做法:父类把固定骨架写死在具体方法 write() 里,把变化的部分抽成抽象方法 writeContent() 交给子类填
public abstract class People {
public void write() { // 模板方法:骨架固定
System.out.println("《我的爸爸》");
System.out.println("一个好人");
writeContent(); // 变化的部分,子类去实现
System.out.println("太好了");
}
public abstract void writeContent();
}
People p = new Teacher(); p.write(); // 老师写的
People p1 = new Student(); p1.write(); // 学生写的

接口 interface#

认识接口#

  • JDK 8 之前:接口里只能定义常量 + 抽象方法
  • 解决单继承的不足:一个类只能继承一个父类,但可以实现多个接口——class Test implements A, B,把 A、B 所有抽象方法都重写即可
  • JDK 8 之后新增三种方法:
    • default 方法:实现类对象直接调用,可选择性重写
    • static 方法:只能通过接口名调用(A.washHands())
    • private 方法:在接口内部被 default 方法复用

接口小案例:面向接口编程#

  • ClassDate 接口定义能力:printStudentInfo() + printAverageScore()
  • 两套实现随意切换:
    • ClassDateImpl1:打印全班信息 + 平均分
    • ClassDateImpl12:打印男女人数 + 去掉最高最低分的平均分
  • 调用方只面对接口 ClassDate,换成哪套实现都不影响调用代码——这就是接口的好处(解耦)
ClassDate cdi = new ClassDateImpl1(allStudents); // 方案一
cdi.printStudentInfo();
ClassDate cdi2 = new ClassDateImpl12(allStudents); // 换成方案二
cdi2.printAverageScore();

与 Day01 的衔接#

  • Day01 的工具类 VerifyCodeUtil(私有构造器 + 静态方法),本质就是单例/静态工具思想的雏形
  • Day01 博客项目的”数据类 + 操作类分离”,到这里升级为面向接口编程:把操作类抽象成接口,换一套实现(如换数据源)不动调用方——这正是 Spring 里”接口 + 实现类”(如 UserMapper 接口)的雏形
  • 加油卡案例的 Card 父类 + 金卡/银卡子类 + 重写 payMoney,就是”父类/接口统一处理、子类各自实现策略”的模板

博客实战复盘:面向对象进阶的五步落地#

把当天的知识点逐一嵌进控制台博客项目,每步独立验证。

五步演进#

步做了什么知识点
1ArticleStatus 枚举 + Article 加 status/publish() + 菜单按状态查看枚举
2pom 加 Lombok,Article 用 @Data 精简,createTime finalLombok/final
3ArticleRepository 拆成接口 + MemoryArticleRepository 实现接口/面向接口编程
4Memory 实现饿汉式单例,入口 getInstance()单例
5printer 包:抽象类模板方法 + 表格/分隔线双子类,菜单切换展示抽象类/模板方法/多态

关键设计决策#

  1. Repository 接口化 = 给数据库留插座:ArticleService 构造器只依赖 ArticleRepository 接口,将来 MyBatis 版只需新建 DbArticleRepository implements ArticleRepository,Service 一行不改——这就是「面向接口编程」的实战意义
  2. 数据层用单例:数据层是全局”数据库”,必须只有一份,否则多个入口各自 new 数据就分裂了。验证方式:构造器打印「初始化」,运行只出现一次 = 实例唯一(比 == 更直观)
  3. 模板方法把「表头→逐行→表尾」写死,formatOne 留给子类——和写作文案例一模一样,只是把”首尾段固定/中段变化”搬到了列表展示
  4. 表头表尾做成抽象 vs 默认空实现:最终选了抽象(强制统一);另一条路是默认空实现(灵活省事)——没有对错,是”强制统一 vs 灵活”的取舍,这正是设计模式的精髓
  5. 多态切换展示:printer = (choice == 1) ? new SimplePrinter() : new DecoratedPrinter(),getClass().getSimpleName() 打印运行时的真实类名——「编译看左边运行看右边」的直接证据

踩坑与解法#

坑表现解法
卡在 listByStatus() 没思路方法空着不知道怎么填三小步拆解:问用户 → 翻译成枚举 → 查+打印(借鉴已写过的 showDetail/search)
枚举引用不一致DRAFT 走 import static、PUBLISHED 没写统一风格:全部 ArticleStatus.XXX,删掉 static import
空结果无提示某状态下没文章,屏幕一片空白加 if (result.isEmpty()) 提示,复用 search() 的套路
方法名大小写写了 PrintHeader(大写开头)Java 惯例小驼峰,统一 printHeader
手写 getter 与 Lombok 重复@Data 已有但手写了 getStatus/setStatusLombok 规则:类里有同名方法不覆盖生成;重复可删

演进方向#

  • Day01 复盘里”将来加 @Service / 换 Mapper / 加注解”的计划,到这里已经把接口层和单例预先铺好,Spring 阶段接入成本更低
  • ArticleStatus 枚举,将来就是数据库里 status 字段 + MyBatis 的枚举映射
  • “换展示实现”,将来就是 Spring 里「换 Bean 实现」(@Qualifier / 多实现注入)的控制反转雏形

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
Java 基础 Day02:继承、多态、接口与设计模式
https://ryuki.icu/posts/java-basics-day02/
作者
RK
发布于
2026-08-04
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
RK
Long may the sunshine
公告
Long may the sunshine
分类
标签
最新动态
站点天气
天气加载中…
站点统计
文章
27
分类
6
标签
59
总字数
39,139
篝火已燃
0 天
最后活动
0 天前
每日一签