第13章 抽象类与接口

📅 2026/8/21 12:41:53
第13章 抽象类与接口
第13章 抽象类与接口多态是行为而抽象类与接口是行为规范——先定义规则再让具体类实现。它们是 Java 实现面向抽象编程的两大武器。本节理清两者的区别和使用场景。一、抽象方法父类中的方法子类必须重写父类自己却无法给出具体实现——这时用抽象方法abstractvoidsound();// 没有方法体只有声明抽象方法以分号结束没有大括号。任何含有抽象方法的类必须是抽象类。**为什么需要抽象方法**因为父类不知道该怎么说。动物都要叫但猫叫喵、狗叫汪父类给不出统一实现只能立个规矩谁继承我谁就得自己实现——这就是先定规范、后补细节。// ❌ 抽象方法不能有方法体// abstract void sound() { } // 编译错误abstract 方法不能有大括号一个类可以有多个抽象方法子类必须全部实现abstractclassShape{abstractdoublearea();// 面积每个图形算法不同abstractdoubleperimeter();// 周长每个图形算法不同}classCircleextendsShape{doubler;Circle(doubler){this.rr;}Overridedoublearea(){returnMath.PI*r*r;}Overridedoubleperimeter(){return2*Math.PI*r;}}classSquareextendsShape{doubleside;Square(doubleside){this.sideside;}Overridedoublearea(){returnside*side;}Overridedoubleperimeter(){return4*side;}}System.out.println(newCircle(1).area());// 3.141592653589793System.out.println(newSquare(2).perimeter());// 8.0二、抽象类用abstract修饰的类就是抽象类abstractclassAnimal{Stringname;// 普通方法可以有实现voideat(){System.out.println(name 在吃东西);}// 抽象方法没有实现子类必须重写abstractvoidsound();}classDogextendsAnimal{Overridevoidsound(){System.out.println(汪汪);}}要点抽象类不能 new不能实例化——它是半成品模板抽象类可以有构造器子类创建时调用抽象方法只能定义在抽象类中子类必须实现所有抽象方法否则子类也要声明为抽象类抽象类可以没有抽象方法纯模板类也可以有普通方法、成员变量**为什么抽象类不能 new**因为它可能含有没写完的抽象方法new 出来调用sound()时不知道该执行什么。只能由子类补全后用子类创建对象。抽象类可以有构造器和成员变量子类创建对象时先调用父类构造器abstractclassAnimal{Stringname;Animal(Stringname){// 抽象类可以有构造器this.namename;// 子类 new 的时候会先调用它}abstractvoidsound();}classDogextendsAnimal{Dog(Stringname){super(name);// 显式调用父类构造器}Overridevoidsound(){System.out.println(name汪汪);}}AnimalanewDog(旺财);a.sound();// 旺财汪汪抽象类里的普通方法还可以直接调用抽象方法——这就是多态运行时执行的是子类的实现abstractclassAnimal{voiddescribe(){System.out.print(我是一只动物叫声是);sound();// 调用抽象方法运行时跳到子类的实现}abstractvoidsound();}classCatextendsAnimal{voidsound(){System.out.println(喵喵);}}newCat().describe();// 我是一只动物叫声是喵喵三、接口接口interface比抽象类更纯粹——它只定义行为规范方法签名不关心实现// 定义接口interfaceFlyable{voidfly();// 接口方法默认是 public abstract}interfaceSwimmable{voidswim();}// 类实现接口用 implements可以实现多个classDuckimplementsFlyable,Swimmable{Overridepublicvoidfly(){System.out.println(鸭子会飞);}Overridepublicvoidswim(){System.out.println(鸭子会游泳);}}接口的规则接口中的方法默认是public abstract没有方法体接口中的变量默认是public static final常量一个类可以实现多个接口——弥补 Java 单继承的不足接口不能实例化也没有构造器实现接口的类必须实现所有抽象方法除非是抽象类接口里的变量都是常量用接口名直接访问interfaceConfig{intMAX_SIZE1024;// 等价于 public static final int MAX_SIZE 1024;}System.out.println(Config.MAX_SIZE);// 1024接口本质是能力的清单。同一个类可以实现多个接口把多种能力组合起来interfacePayable{voidpay();}// 能力可支付interfaceRefundable{voidrefund();}// 能力可退款classWeChatPayimplementsPayable,Refundable{Overridepublicvoidpay(){System.out.println(微信支付成功);}Overridepublicvoidrefund(){System.out.println(退款成功);}}接口的默认方法与静态方法JDK 8接口可以有带实现的方法interfaceFlyable{voidfly();// 默认方法实现类可以不改defaultvoidtakeOff(){System.out.println(准备起飞);}// 静态方法用接口名直接调用staticvoidinfo(){System.out.println(这是飞行接口);}}default方法让接口可以平滑增加新方法而不破坏已有实现类——JDK 8 给集合框架加新特性就是靠它。这套机制从 JDK 8 引入后一直保留JDK 11、JDK 17 同样支持只要项目用的 JDK 是 8 或更高版本default 和 static 方法都能放心用。四、典型应用Comparable 排序接口让学生按成绩排序classStudentimplementsComparableStudent{Stringname;doublescore;Student(Stringname,doublescore){this.namename;this.scorescore;}OverridepublicintcompareTo(Studentother){// 负数this 小0相等正数this 大returnDouble.compare(this.score,other.score);}OverridepublicStringtoString(){returnname:score;}}Student[]students{newStudent(张三,85),newStudent(李四,92),newStudent(王五,78)};Arrays.sort(students);// 按成绩升序System.out.println(Arrays.toString(students));// [王五:78.0, 张三:85.0, 李四:92.0]Comparable就是可比较的能力规范Arrays.sort 只认这个接口——这就是面向接口编程。想换排序规则比如按姓名不需要改 Student 类另配一个 Comparator 即可// 匿名内部类写法JDK 8 及以后都支持Arrays.sort(students,newComparatorStudent(){Overridepublicintcompare(Studenta,Studentb){returna.name.compareTo(b.name);// 按姓名排序}});System.out.println(Arrays.toString(students));// [张三:85.0, 李四:92.0, 王五:78.0]// 更简洁的 lambda 写法JDK 8Arrays.sort(students,(a,b)-a.name.compareTo(b.name));五、扩展知识5.1 抽象类 vs 接口对比表这是面试高频题对比表背下来对比项抽象类接口关键字abstract classinterface继承方式extends单继承implements多实现方法可有抽象方法 普通方法抽象方法 default/static 方法成员变量普通变量、常量只有常量构造器有没有本质是什么is-a描述共性能做什么can-do定义能力使用场景一类事物的共性模板跨类别的能力规范会飞、会游泳选择原则想让多个不相关类共享行为规范 → 接口如 Comparable、Runnable类之间是is-a关系且有公共状态/方法实现 → 抽象类如 Animal → Dog优先用接口设计灵活一个类可以实现多个两者不冲突可以搭配用抽象类沉淀公共代码接口补充能力5.2 模板方法模式初识抽象类常用于模板方法模式定义算法骨架细节交给子类abstractclassGame{// 模板方法定义游戏流程骨架publicfinalvoidplay(){start();playRound();end();}abstractvoidstart();abstractvoidplayRound();abstractvoidend();}classChessextendsGame{voidstart(){System.out.println(国际象棋开始);}voidplayRound(){System.out.println(下棋中...);}voidend(){System.out.println(游戏结束);}}newChess().play();// 国际象棋开始// 下棋中...// 游戏结束流程骨架固定具体环节由子类各自实现。再看一个冲饮料的例子它多了个钩子方法子类可以重写它来改变流程走向abstractclassBeverage{// 模板方法流程固定子类不能改finalpublicfinalvoidprepare(){boilWater();brew();pourInCup();if(wantCondiments()){// 钩子方法默认 trueaddCondiments();}}voidboilWater(){System.out.println(烧水);}abstractvoidbrew();voidpourInCup(){System.out.println(倒入杯子);}booleanwantCondiments(){returntrue;}// 钩子默认要加料abstractvoidaddCondiments();}classCoffeeextendsBeverage{voidbrew(){System.out.println(冲泡咖啡粉);}voidaddCondiments(){System.out.println(加糖和牛奶);}}classTeaextendsBeverage{voidbrew(){System.out.println(冲泡茶叶);}voidaddCondiments(){System.out.println(加柠檬);}OverridebooleanwantCondiments(){returnfalse;}// 纯茶不加料}newCoffee().prepare();// 烧水// 冲泡咖啡粉// 倒入杯子// 加糖和牛奶5.3 接口的多继承能力随意组合Java 的类只能单继承但接口可以继承接口规范可以层层叠加interfaceWalkable{voidwalk();}interfaceJoggingextendsWalkable{voidjog();}// 接口继承接口规范叠加// 实现 Jogging 的类walk() 和 jog() 都要实现classRobotimplementsJogging{publicvoidwalk(){System.out.println(机器人走路);}publicvoidjog(){System.out.println(机器人慢跑);}}再配合一个类可实现多个接口就能像搭积木一样组合多种能力比层层继承类灵活得多。5.4 sealed限制继承名单JDK 17默认情况下任何类都能继承你的类。有些业务希望只有这几个子类其他人不许继承——JDK 17 的sealed正好解决这个问题// 需 JDK 17 才能运行sealedinterfaceShapepermitsCircle,Square{}finalclassCircleimplementsShape{}finalclassSquareimplementsShape{}// class Triangle implements Shape {} // 编译错误不在 permits 名单里适用场景支付方式只有微信、支付宝、银行卡三种订单状态只有待支付、已支付、已取消——这类枚举式的继承范围用 sealed 约束最合适。JDK 8、JDK 11 不支持此写法只有 JDK 17 能运行初学了解即可。六、易错点易错 1抽象类不能直接 new// ❌ 编译错误抽象类不能实例化// Animal a new Animal();// ✅ 用子类对象父类引用指向它AnimalanewDog(旺财);易错 2子类没实现全部抽象方法// ❌ 编译错误Dog 没实现 sound()自己又不是抽象类// class Dog extends Animal { }// ✅ 要么实现全部抽象方法……classDogextendsAnimal{Dog(Stringname){super(name);}voidsound(){System.out.println(汪汪);}}// ✅ ……要么自己也声明成抽象类但这样 Dog 还是不能 new// abstract class Dog extends Animal { }易错 3接口方法忘记实现// ❌ 编译错误Duck 没有实现 fly()// class Duck implements Flyable { }// ✅ 实现接口的所有抽象方法classDuckimplementsFlyable{publicvoidfly(){System.out.println(鸭子飞);}}// 注意接口方法默认是 public实现时也要写 public易错 4default 方法多接口冲突interfaceA{defaultvoidhello(){System.out.println(A);}}interfaceB{defaultvoidhello(){System.out.println(B);}}// ❌ 编译错误C 同时继承了两个同名 default 方法必须自己解决// class C implements A, B { }// ✅ 重写并在方法里明确指定调用哪一个classCimplementsA,B{Overridepublicvoidhello(){A.super.hello();// 明确调用 A 的默认方法}}newC().hello();// A易错 5认为抽象类必须有抽象方法// ❌ 误区抽象类必须至少有一个抽象方法// ✅ 事实抽象类可以全是普通方法照样合法abstractclassTemplate{voidstep1(){System.out.println(第一步);}voidstep2(){System.out.println(第二步);}}// 它的作用仅仅是禁止 new只能被继承子类直接复用这些普通方法七、小结抽象方法只有声明没有实现含抽象方法的类必须声明为抽象类抽象类不能 new可以有构造器、普通方法、成员变量子类必须实现所有抽象方法接口是纯规范方法默认 public abstract变量默认 public static final一个类可实现多个接口抽象类描述是什么接口描述能做什么优先用接口JDK 8 起接口支持 default 和 static 方法JDK 17 起可用 sealed 限定继承名单模板方法模式抽象类定骨架final 模板方法 钩子方法子类填细节下一节学习 String 与 StringBuilder。下一篇第14章 String 与 StringBuilder