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,就是”父类/接口统一处理、子类各自实现策略”的模板
博客实战复盘:面向对象进阶的五步落地
把当天的知识点逐一嵌进控制台博客项目,每步独立验证。
五步演进
| 步 | 做了什么 | 知识点 |
|---|---|---|
| 1 | ArticleStatus 枚举 + Article 加 status/publish() + 菜单按状态查看 | 枚举 |
| 2 | pom 加 Lombok,Article 用 @Data 精简,createTime final | Lombok/final |
| 3 | ArticleRepository 拆成接口 + MemoryArticleRepository 实现 | 接口/面向接口编程 |
| 4 | Memory 实现饿汉式单例,入口 getInstance() | 单例 |
| 5 | printer 包:抽象类模板方法 + 表格/分隔线双子类,菜单切换展示 | 抽象类/模板方法/多态 |
关键设计决策
- Repository 接口化 = 给数据库留插座:
ArticleService构造器只依赖ArticleRepository接口,将来 MyBatis 版只需新建DbArticleRepository implements ArticleRepository,Service 一行不改——这就是「面向接口编程」的实战意义 - 数据层用单例:数据层是全局”数据库”,必须只有一份,否则多个入口各自
new数据就分裂了。验证方式:构造器打印「初始化」,运行只出现一次 = 实例唯一(比==更直观) - 模板方法把「表头→逐行→表尾」写死,
formatOne留给子类——和写作文案例一模一样,只是把”首尾段固定/中段变化”搬到了列表展示 - 表头表尾做成抽象 vs 默认空实现:最终选了抽象(强制统一);另一条路是默认空实现(灵活省事)——没有对错,是”强制统一 vs 灵活”的取舍,这正是设计模式的精髓
- 多态切换展示:
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/setStatus | Lombok 规则:类里有同名方法不覆盖生成;重复可删 |
演进方向
- Day01 复盘里”将来加
@Service/ 换 Mapper / 加注解”的计划,到这里已经把接口层和单例预先铺好,Spring 阶段接入成本更低 ArticleStatus枚举,将来就是数据库里status字段 + MyBatis 的枚举映射- “换展示实现”,将来就是 Spring 里「换 Bean 实现」(
@Qualifier/ 多实现注入)的控制反转雏形
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!
Java 基础 Day02:继承、多态、接口与设计模式
https://ryuki.icu/posts/java-basics-day02/相关文章智能推荐
1
Java 基础 Day01:基础语法、数组与面向对象入门
Java基础语法综合练习、数组操作与面向对象入门的学习笔记。
2
Java 基础 Day03:代码块、内部类、Lambda 与方法引用
Java代码块、内部类、Lambda、方法引用与 String、ArrayList 入门的学习笔记。
3
Java 进阶 Day02:自定义泛型与集合入门
Java自定义泛型类、泛型接口、泛型方法与通配符,以及包装类和 Collection 集合的基础用法。
4
Java 进阶 Day05:反射注解与动态代理
JavaJava 反射获取 Class 与操作类成分、泛型擦除证据、自定义注解与元注解解析、JDK 动态代理
5
ArrayList、线程池与 JVM:一次 Java 底层复盘
JavaArrayList 懒加载与 1.5 倍扩容、asList 与 toArray 的镜像坑、线程池核心线程数怎么定、JVM 运行时数据区、双亲委派、垃圾判定与三大回收算法,以及调优参数和排查工具速查。
随机文章随机推荐





















