Java13:Java抽象类与接口:从设计思想到实战应用

发布时间:2026/7/13 2:43:42
Java13:Java抽象类与接口:从设计思想到实战应用 目录写在前面一、抽象类当父类无法具体实现时1.1 从一个困惑说起1.2 抽象类的定义1.3 抽象类的核心特性1.4 抽象类存在的意义二、接口行为的契约2.1 接口的概念2.2 接口的定义与使用2.3 接口的特性2.4 接口的多态之美2.5 接口之间的继承2.6 接口的实战对象比较三、抽象类 vs 接口如何选择四、Object类所有类的终极父类4.1 toString方法对象的自我介绍4.2 equals方法对象内容的比较4.3 hashCode方法对象的散列码五、总结写在前面在前几篇文章中我们学习了面向对象的三大特性封装、继承和多态。你已经能够用继承来复用代码用多态来让程序更灵活。但在实际开发中我们会遇到一些更棘手的设计问题某个父类的方法在父类中根本无法给出有意义的实现但又必须让子类去实现——比如图形的draw()方法图形本身怎么画一个类需要同时具备多种能力但Java又不支持多继承——比如一只鸭子既要会飞、又要会跑、还要会游泳怎么办这两个问题的答案就是我们今天要学习的两个重要概念抽象类和接口。一、抽象类当父类无法具体实现时1.1 从一个困惑说起假设我们要设计一个图形绘制系统。按照之前学过的知识我们会定义一个Shape父类然后让Rect、Circle、Triangle等子类去继承它。但问题是Shape类中的draw()方法该怎么实现呢形状只是一个抽象的概念根本没办法画出一个形状。强行写一个空的实现又显得毫无意义。class Shape { public void draw() { // 这里该写什么呢什么都不写似乎很奇怪 } }抽象类就是为解决这类问题而生的。1.2 抽象类的定义在Java中使用abstract关键字修饰的类称为抽象类使用abstract修饰的方法称为抽象方法。abstract class Shape { // 抽象方法没有方法体 public abstract void draw(); // 普通方法抽象类中也可以包含普通方法 public void printInfo() { System.out.println(我是一个图形); } }抽象方法的特征只有方法声明没有方法体没有大括号直接以分号结尾。1.3 抽象类的核心特性特性一抽象类不能直接实例化Shape shape new Shape(); // 编译错误Shape是抽象的无法实例化抽象类就像一张不完整的蓝图——它指明了子类应该有什么但自己没有能力独立存在。特性二抽象方法不能是private的abstract class Shape { abstract private void draw(); // 编译错误非法组合 }抽象方法是要被子类重写的而private方法对子类不可见自然无法重写。特性三抽象方法不能被final和static修饰abstract final void methodA(); // 编译错误 abstract static void methodB(); // 编译错误final阻止重写static属于类而非对象——两者都与抽象方法的初衷矛盾。特性四子类必须重写父类的所有抽象方法否则子类也必须是抽象类// 正确做法 class Rect extends Shape { Override public void draw() { System.out.println(绘制矩形); } } // 错误做法没有重写抽象方法 class Circle extends Shape { // 编译错误Circle必须要么是抽象类要么实现draw()方法 }特性五抽象类中可以包含构造方法抽象类虽然不能实例化但可以有构造方法供子类创建对象时初始化父类的成员变量。abstract class Animal { protected String name; public Animal(String name) { this.name name; } public abstract void bark(); } class Dog extends Animal { public Dog(String name) { super(name); // 调用父类构造方法 } Override public void bark() { System.out.println(name 汪汪汪); } }1.4 抽象类存在的意义有人可能会问普通类也可以被继承普通方法也可以被重写为什么非要搞一个抽象类出来答案是抽象类提供了一层编译器的校验。假设我们不使用抽象类而是用普通类加空方法体class Shape { public void draw() { // 空的啥也不做 } }如果某个子类忘记重写draw()方法编译器不会报错。但运行时调用该子类的draw()什么都不会发生——这种静默失败很难排查。而使用抽象类后如果子类没有重写抽象方法编译器直接报错问题在编译阶段就被发现了。很多语法存在的意义就是为了预防出错。就像final关键字一样——你不用它也能工作但用了它就能在犯错时得到及时的提醒。二、接口行为的契约2.1 接口的概念如果说抽象类是对是什么的描述那接口就是对能干什么的规定。现实生活中的接口无处不在USB接口、电源插座、HDMI接口……它们都定义了一套规范——只要符合规范任何设备都能接入。在Java中接口就是多个类的公共行为规范。它是一种引用数据类型定义了一组抽象方法由实现类来完成具体实现。2.2 接口的定义与使用// 定义接口 public interface USB { void openDevice(); // 打开设备 void closeDevice(); // 关闭设备 } // 实现接口 class Mouse implements USB { Override public void openDevice() { System.out.println(打开鼠标); } Override public void closeDevice() { System.out.println(关闭鼠标); } public void click() { System.out.println(鼠标点击); } } class KeyBoard implements USB { Override public void openDevice() { System.out.println(打开键盘); } Override public void closeDevice() { System.out.println(关闭键盘); } public void input() { System.out.println(键盘输入); } }命名规范接口名一般以大写字母I开头如IUSB、IRunnable接口名通常使用形容词如Runnable、Comparable、Cloneable2.3 接口的特性特性一接口不能直接实例化USB usb new USB(); // 编译错误特性二接口中的方法默认是public abstractpublic interface USB { void openDevice(); // 实际上是 public abstract void openDevice(); }接口中的方法即使不写public abstract编译器也会自动补上。所以在实现类中重写方法时必须使用public修饰符。特性三接口中的变量默认是public static finalpublic interface USB { double VERSION 3.0; // 实际上是 public static final double VERSION 3.0; } // 使用 System.out.println(USB.VERSION); // 通过接口名直接访问 // USB.VERSION 2.0; // 编译错误final变量不能修改特性四接口中不能有构造方法和静态代码块接口不是类没有构造方法也没有实例代码块和静态代码块。特性五一个类可以实现多个接口这是接口最强大的特性之一——弥补了Java单继承的不足。interface IRunning { void run(); } interface ISwimming { void swim(); } interface IFlying { void fly(); } // 青蛙既能跑又能游 class Frog extends Animal implements IRunning, ISwimming { Override public void run() { System.out.println(name 在跳跃前进); } Override public void swim() { System.out.println(name 在游泳); } } // 鸭子能跑、能游、还能飞 class Duck extends Animal implements IRunning, ISwimming, IFlying { Override public void run() { System.out.println(name 在摇摆走路); } Override public void swim() { System.out.println(name 在水面划水); } Override public void fly() { System.out.println(name 在拍翅膀飞翔); } }2.4 接口的多态之美有了接口之后我们可以写出更优雅的多态代码public static void walk(IRunning runner) { System.out.println(我带小伙伴去散步); runner.run(); } // 调用时只要是会跑的都能传进来 walk(new Cat(小猫)); // 猫用四条腿跑 walk(new Frog(青蛙)); // 青蛙跳跃前进 walk(new Robot(机器人)); // 机器人用轮子跑接口的多态优势调用者不需要关心具体类型只需要关心对象是否具备某种能力。这种设计让代码的扩展性大大增强——将来就算新增一个会跑的汽车类walk方法也不需要做任何修改。2.5 接口之间的继承接口与接口之间可以使用extends关键字实现多继承interface IRunning { void run(); } interface ISwimming { void swim(); } // 两栖接口继承了跑步和游泳 interface IAmphibious extends IRunning, ISwimming { // 没有新增方法只是将两个接口合并 } // 实现IAmphibious接口的类需要同时实现run和swim class Frog implements IAmphibious { Override public void run() { /* ... */ } Override public void swim() { /* ... */ } }2.6 接口的实战对象比较在实际开发中我们经常需要对对象进行排序或比较。Java提供了两个常用的接口Comparable和Comparator。方式一使用Comparable接口自然排序让待比较的类实现Comparable接口重写compareTo方法class Student implements ComparableStudent { public String name; public int score; public Student(String name, int score) { this.name name; this.score score; } Override public int compareTo(Student other) { // 按分数升序排列 return this.score - other.score; } Override public String toString() { return [ name : score ]; } } // 使用 Student s1 new Student(张三, 90); Student s2 new Student(李四, 80); System.out.println(s1.compareTo(s2)); // 正数表示s1大于s2方式二使用Comparator接口定制排序当我们需要多种比较方式时比如按分数比较、按姓名比较可以使用Comparator// 按分数比较 class ScoreComparator implements ComparatorStudent { Override public int compare(Student o1, Student o2) { return o1.score - o2.score; } } // 按姓名比较 class NameComparator implements ComparatorStudent { Override public int compare(Student o1, Student o2) { return o1.name.compareTo(o2.name); } } // 使用 Student s1 new Student(张三, 90); Student s2 new Student(李四, 80); ScoreComparator scoreComp new ScoreComparator(); System.out.println(scoreComp.compare(s1, s2)); // 按分数比较 NameComparator nameComp new NameComparator(); System.out.println(nameComp.compare(s1, s2)); // 按姓名比较三、抽象类 vs 接口如何选择这是面试中的高频问题也是实际开发中经常需要做的决策。对比维度抽象类接口关键字abstract classinterface继承/实现extends单继承implements多实现构造方法可以有不能有成员变量可以是任意类型只能是public static final方法类型抽象方法 普通方法抽象方法JDK8后可包含default和static方法访问权限各种权限默认public设计思想is-a关系是什么can-do关系能干什么选择指南如果类之间有明确的层级关系is-a用抽象类。比如狗是动物猫是动物。如果只是定义一组行为规范can-do用接口。比如会飞的、会跑的、会游泳的。如果需要在子类中共享代码或状态用抽象类。比如所有动物都有名字和年龄。如果希望一个类具备多种能力用接口。因为一个类可以实现多个接口但只能继承一个抽象类。四、Object类所有类的终极父类在Java中除了Object类本身所有的类都直接或间接地继承自Object类。也就是说Object是所有类的根父类。class Person {} // 虽然没有写extends但默认继承ObjectObject类中定义了一些非常重要的方法所有类都可以重写它们。4.1 toString方法对象的自我介绍Person p new Person(张三, 20); System.out.println(p); // 默认输出Person1a2b3c4d默认的toString输出的是类名哈希码对我们来说没有可读性。所以通常需要重写Override public String toString() { return Person{name name , age age }; }4.2 equals方法对象内容的比较比较的是引用地址而equals默认也是比较地址。要比较对象的内容必须重写equalsOverride public boolean equals(Object obj) { if (this obj) return true; if (obj null || getClass() ! obj.getClass()) return false; Person person (Person) obj; return age person.age Objects.equals(name, person.name); }4.3 hashCode方法对象的散列码hashCode返回对象的哈希码在散列表如HashMap中用于确定对象的存储位置。重要规则如果两个对象通过equals方法比较相等那么它们的hashCode必须相等。反之不要求。Override public int hashCode() { return Objects.hash(name, age); }五、总结今天我们学习了Java中两个重要的概念抽象类用abstract修饰不能实例化可以包含抽象方法和普通方法子类必须重写所有抽象方法接口用interface定义所有方法默认是public abstract所有变量默认是public static final一个类可以实现多个接口抽象类 vs 接口抽象类适合is-a关系接口适合can-do关系抽象类单继承接口多实现Object类所有类的根父类提供了toString、equals、hashCode等重要方法抽象类和接口是Java面向对象设计的核心工具。掌握了它们你就能写出更具扩展性、更符合设计原则的代码。下一篇文章我们将学习Java中的字符串和String类敬请期待