前言面向对象的三大特性——封装、继承、多态是每个Java开发者绕不开的基础。但我们如果只看理论很容易一看就会一写就废。今天我打算用一个学生-老师的场景把这几个核心概念串起来讲清楚。我们先简单看一下今天各个主角的定义接口Interface——定义规范继承Extends——复用代码方法重写Override——子类改造父类行为向上转型 / 向下转型——类型的转换规则多态Polymorphism——同一个调用不同行为一、接口只定规范不写实现public interface IStudent { // 常量 // 等价于 public static final String NAME 杨某人; // static → 类变量内存中只有一份所有实现类共享 // final → 不可修改赋值后永远是这个值 // 接口中不写修饰符编译器也会自动补上这三样 String NAME 杨某人; // 抽象方法 // 等价于 public abstract void study(); // 没有方法体 { }直接以分号结尾 // 谁实现这个接口谁就必须补上方法体 void study(); }接口是什么 我们可以打个形象的比方接口就是一份合同或规范。它只规定你必须会什么但不关心你怎么做到。就像现实中的学生这个概念只要是学生就必须会学习。但小学生学习和大学生学习的方式完全不同——接口只管你得有study() 这个方法具体怎么学由各个实现类自己决定。接口中的常量String NAME 杨某人看起来只是一个普通变量但编译器会自动给它加上 public static final三个修饰符static → 属于接口本身不依赖任何对象全局只有一份final → 不可修改赋值一次后永远不变接口不能 newnew IStudent() 会直接编译报错。因为接口只有方法声明没有方法体new 出来也没法用。二、实现类给接口的方法填身体public class Student implements IStudent { // 属性 private int score; // 私有属性子类继承但不可直接访问 // 构造方法 // 无参构造 — 子类默认调用这个 // 实际上这里隐含了 super()即调用 Object 类的构造方法 public Student() { System.out.println( [Student 无参构造] 创建了一个普通学生对象); } // 有参构造 public Student(int score) { this.score score; System.out.println( [Student 有参构造] 创建学生学分 score); } // getter / setter // 私有属性 score 只能通过这些公开方法访问 public void setScore(int score) { this.score score; } public int getScore() { return score; } // 实现接口中的抽象方法必须实现 // 这是对接口 study() 的第一次填身体 Override public void study() { System.out.println(NAME 正在学习来自 Student 的实现); } }implements 关键字的含义我Student 类承诺实现 IStudent 接口规定的所有方法。如果少实现一个编译器直接报错。封装体现在哪里score 属性用 private 修饰外部不能直接 obj.score 这样访问必须通过 getScore() / setScore()方法。这就是封装——数据藏起来通过方法控制访问。Override注解告诉编译器我是重写接口/父类的方法不是新方法。加了这个注解后如果你不小心把方法名写错了比如写成stdudy()编译器会立刻报错——帮你避免低级错误。三、继承子类拿到父类的全部还能加自己的public class UNStudent extends Student { // 构造方法 // 无参构造 public UNStudent() { // 这里隐式调用了 super() → Student 的无参构造 // 然后再调用 Object 的无参构造 // 调用链UNStudent() → Student() → Object() System.out.println( [UNStudent 无参构造] 创建了一个大学生对象); } public UNStudent(int score) { super(score); // 显式调用父类 Student 的有参构造 System.out.println( [UNStudent 有参构造] 创建大学生学分 score); } // 子类特有方法 // 这个方法父类没有只有 UNStudent 有 // —— 用于演示向下转型 public void print() { // getScore() 是父类的 public 方法子类可以直接用 // 但如果直接写 score就报错了score 是 private 的 System.out.println(学分 getScore()); } // 方法重写Override // 重写条件 // ① 有继承关系UNStudent extends Student // ② 方法名、参数、返回值类型完全一致 // ③ 访问权限不能比父类更严格 Override public void study() { // 调用父类的 study()保留父类原有功能 super.study(); // 在父类基础上新增子类独有的功能 System.out.println( 然后——大学生去图书馆深入自习UNStudent 新增的功能); } }构造方法链——先父后子创建 new UNStudent(89) 时构造方法调用顺序是Object() → Student(score) → UNStudent(score)就像建房子先打好地基Object再盖一楼Student最后盖二楼UNStudent。每层都在上一层的基础上建造。super.study() 的精髓很多人写重写时直接覆盖父类方法把父类的逻辑全丢了。更好的做法是先用 super.study()保留父类已有的功能再在后面加上子类独有的逻辑。就像大学生学习——普通学生的学习内容上课听讲仍然要做只是额外增加了去图书馆自习。方法重写的三个条件1. 有继承关系UNStudent extends Student2. 方法名、参数、返回值类型完全一致3. 访问权限不能比父类更严格父类是 public子类不能改成 private四、转型类型的升职与降职向上转型自动Student s1 new UNStudent(93); //子类对象赋值给父类引用自动发生 s1.study(); // 执行的是 UNStudent 重写后的版本 // s1.print(); // 编译错误s1 的类型是 Student没有 print() 方法向上转型就像大学生UNStudent当然可以被称为学生Student这是天经地义的不需要任何强制手段——编译器自动处理。但代价是视野变窄s1 的类型声明是 Student所以只能调用 Student 类中定义过的方法。UNStudent 独有的 print()方法虽然对象里有但编译器看不到——因为编译器只看声明类型不看实际类型。向下转型强制UNStudent un2 (UNStudent) s1; // 强制转型前提s1 本质上就是 UNStudent un2.print(); // 现在可以调用 UNStudent 特有的 print()方法了向下转型就像这个学生身份s1你得确认他真的就是大学生才能恢复他大学生的身份。危险如果对象本质上不是那个类型强转会抛 ClassCastExceptionStudent s2 new Student(); UNStudent un3 (UNStudent) s2; // 运行时报错s2 本质不是 UNStudent就像你不能把一个普通学生强行说成是大学生——他没有大学生的特有属性和行为。五、多态同一个方法千姿百态public void teach(Student s) { System.out.print(老师开始教 → ); s.study(); //运行时多态实际调用哪个 study()取决于 s 的真实类型 } /** * 教学接口版更进一步只认 IStudent 规范 * 连是不是 Student 类都不管了——只要你实现了 IStudent 接口我就能教 */ public void teachByInterface(IStudent s) { System.out.print(老师按接口开始教 → ); s.study(); } }测试代码Teacher t new Teacher(); // 同一个 teach 方法传入不同类型的对象 t.teach(new Student(91)); // 输出Student 版本的 study() t.teach(new UNStudent(95)); // 输出UNStudent 版本的 study()加了图书馆自习多态的核心teach() 方法的参数类型是Student但它不关心你传进来的是普通学生还是大学生——只要你是个学生我就能教。至于具体怎么学习study()执行哪个版本由运行时对象的真实类型决定。这种同一个方法调用不同对象产生不同行为的机制就是多态。多态的好处将来新增一个 HighSchoolStudent extends StudentTeacher.teach() 一行代码都不用改——直接传进去就能教。这就是对扩展开放对修改关闭。六、完整测试类public class Test { public static void main(String[] args) { // 【1】接口常量 System.out.println(接口常量 IStudent.NAME IStudent.NAME); // 【2】构造方法链 Student stu1 new Student(86); stu1.study(); UNStudent un1 new UNStudent(89); // 构造链Object → Student → UNStudent先父后子 un1.study(); // 输出父类版本 子类新增内容 // 【3】向上转型 Student s1 new UNStudent(93); // 自动转型 s1.study(); // 运行时执行 UNStudent 版本 // 【4】向下转型 UNStudent un2 (UNStudent) s1; // 强制转型 un2.print(); // 恢复子类特有方法的访问 // 【5】多态 Teacher t new Teacher(); t.teach(new Student(91)); // Student 版本 t.teach(new UNStudent(95)); // UNStudent 版本 // 【6】接口引用 IStudent is1 new Student(); IStudent is2 new UNStudent(); t.teachByInterface(is1); // 只认接口不关心具体类 t.teachByInterface(is2); } } }七总结我说句实话这个学生-老师类是我觉得最好上手写的代码。当然我相信没人能否定无论是你听了一节课或者说看了一篇文章你还是得亲自去写代码你才能真正弄懂每个知识点。所以我希望看完这篇文章的朋友快快去写代码吧在写代码的过程中真的你会有许多新的体验和想法所以这就是我们CS的学生一直强调的代码量的问题哪怕现在是AIAgent时代。建议第一遍可以照着写后面大家可以自己创建不同的类按自己的想法写