当前位置: 首页 > news >正文

Java 面向对象进阶:抽象类与接口的舞蹈

Java 面向对象进阶:抽象类与接口的舞蹈 🕺💃

在 Java 的面向对象编程中,抽象类和接口是实现多态性和代码组织的强大工具。它们允许我们定义更通用、更灵活的结构。今天,我们将一起探索抽象类和接口的概念、用法以及它们之间的区别与联系。

👻 抽象方法:只有骨架,没有实现

  • abstract 关键字修饰。

  • 抽象方法只有方法的定义(包括方法名、参数列表和返回值类型),没有具体的实现(连 {} 都没有)。

    abstract void eat(); // 抽象方法
    

🏛️ 抽象类:不完整的类

  • abstract 关键字修饰。

  • 包含抽象方法的类必须是抽象类。但是,不包含抽象方法的类也可以声明为抽象类(虽然没有抽象方法,但一旦声明为抽象类,就不能被实例化)。

  • 不能被实例化 (new 对象):抽象类只是一个概念或模板,本身不完整,所以不能直接创建对象。

  • 需要被继承:抽象类存在的意义就是被子类继承。派生类(子类)继承抽象类后,有两种选择:

    • 必须重写所有抽象方法:将父类中不完整的抽象方法补全,使派生类成为完整的类,可以被实例化。这是最常见的使用方式。
    • 👻 也声明为抽象类:如果派生类仍然没有重写父类中的所有抽象方法,或者出于设计需要,派生类也可以继续声明为抽象类。这种情况一般不常用,除非是构建一个更深层次的抽象体系。
  • 抽象类的意义

    • 封装共有的属性和行为:像普通类一样,抽象类可以封装子类共有的成员变量和普通方法,实现代码复用。
    • 提供统一入口并强制重写:可以包含抽象方法,为所有派生类提供一个统一的方法签名(入口),并且强制派生类必须提供自己的实现(重写)。这保证了派生类都具备某种特定的行为,但具体实现由子类决定。
    public abstract class Animal { // 抽象类String name;int age;String color;Animal(){}Animal(String name,int age,String color){this.name = name;this.age = age;this.color = color;}void drink(){ // 普通方法System.out.println(color+"色的"+age+"岁的"+name+"正在喝水...");}abstract void eat(); // 抽象方法
    }// Dog 类继承抽象类 Animal
    public class Dog extends Animal{Dog(){}Dog(String name,int age,String color){super(name,age,color);}void lookHome(){ // Dog 特有的方法System.out.println(color+"色的"+age+"岁的狗狗"+name+"正在看家...");}// 重写抽象方法 eat(),提供具体的实现void eat(){System.out.println(color+"色的"+age+"岁的狗狗"+name+"正在吃肯头...");}
    }
    

🌐 接口:定义规范与能力

  • 是一种引用数据类型

  • interface 关键字定义。

  • 主要包含抽象方法:在 Java 8 之前,接口只能包含常量和抽象方法。Java 8 以后引入了默认方法(default)、静态方法(static)和私有方法(private),但这些通常被视为接口的扩展,核心还是抽象方法。(常量、默认方法、静态方法、私有方法 - 暂时搁置,后续会深入学习)

    • 接口中的方法默认都是 public abstract 的,即使你不显式写出来。
    • 接口中的方法不能有方法体。
    interface Inter {abstract void show(); // 抽象方法void test(); // 默认是 public abstract void test();// void say(){} // 编译错误,抽象方法不能有方法体
    }
    
  • 不能被实例化:接口也是一种规范或契约,本身没有具体的实现,所以不能直接创建对象。

    public class InterfaceDemo {public static void main(String[] args) {// Inter o = new Inter(); // 编译错误,接口不能被实例化}
    }
    
  • 需要被实现/继承:接口需要被类实现(使用 implements 关键字)或者被其他接口继承(使用 extends 关键字)。实现类(派生类)必须重写接口中的所有抽象方法

    • 注意:重写接口中的方法时,必须加上 public 访问修饰符。这是因为接口中的方法默认是 public 的,而子类重写父类(或实现接口)的方法时,访问权限不能低于父类(或接口)。
    interface Inter {void show();void test();
    }class InterImpl implements Inter { // InterImpl 实现 Inter 接口public void show(){ // 重写接口中的抽象方法时,必须加 public// 具体实现}public void test(){ // 重写接口中的抽象方法时,必须加 public// 具体实现}
    }
    
  • 多实现:一个类可以实现多个接口,用逗号分隔。这弥补了 Java 单一继承的不足,允许类拥有多种不同的能力或遵守多种规范。

    • 如果一个类既继承类又实现接口,应先继承后实现
    interface Inter1{void show();
    }
    interface Inter2{void test();
    }
    abstract class Aoo{abstract void say();
    }// Boo 类继承 Aoo 抽象类,并实现 Inter1 和 Inter2 接口
    class Boo extends Aoo implements Inter1,Inter2{public void show(){// 实现 Inter1 的 show 方法}public void test(){// 实现 Inter2 的 test 方法}void say(){// 实现 Aoo 的抽象方法 say}
    }
    
  • 接口继承接口:接口之间可以使用 extends 关键字进行继承。一个接口可以继承多个父接口。

    // 接口继承接口
    interface Inter3{void show();
    }
    interface Inter4 extends Inter3{ // Inter4 继承 Inter3void test(); // Inter4 拥有 show() 和 test() 两个抽象方法
    }class Coo implements Inter4{ // Coo 实现 Inter4 接口public void test(){// 实现 test 方法}public void show(){// 实现 show 方法}
    }
    

🐾 综合练习:动物园(续)与游泳能力

在之前的动物园基础上,我们为部分动物添加游泳的能力,使用接口来表示这种能力。

// 定义一个 Swim 接口,表示“游泳”的能力
public interface Swim {void swim(); // 游泳方法,默认 public abstract
}// Dog 类继承 Animal,并实现 Swim 接口
public class Dog extends Animal implements Swim {Dog(String name,int age,String color){super(name,age,color);}void lookHome(){System.out.println(color+"色的"+age+"岁的狗狗"+name+"正在看家...");}void eat(){System.out.println(color+"色的"+age+"岁的狗狗"+name+"正在吃肯头...");}// 实现 Swim 接口中的 swim 方法public void swim(){System.out.println(color+"色的"+age+"岁的狗狗"+name+"正在游泳...");}
}// Fish 类继承 Animal,并实现 Swim 接口
public class Fish extends Animal implements Swim {Fish(String name,int age,String color){super(name,age,color);}void eat(){System.out.println(color+"色的"+age+"岁的小鱼"+name+"正在吃小虾...");}// 实现 Swim 接口中的 swim 方法public void swim(){System.out.println(color+"色的"+age+"岁的小鱼"+name+"正在游泳...");}
}// Chick 类只继承 Animal,不实现 Swim 接口 (小鸡不会游泳)
public class Chick extends Animal {Chick(String name,int age,String color){super(name,age,color);}void layEggs(){System.out.println(color+"色的"+age+"岁的小鸡"+name+"正在下蛋...");}void eat(){System.out.println(color+"色的"+age+"岁的小鸡"+name+"正在吃小米...");}
}public class SwimTest {public static void main(String[] args) {Dog dog = new Dog("小黑", 2, "黑");dog.eat();dog.drink();dog.swim();     // Dog 具备游泳能力dog.lookHome();System.out.println("---");Chick chick = new Chick("小白", 1, "白");chick.eat();chick.drink();chick.layEggs();// chick.swim(); // 编译错误,Chick 没有实现 Swim 接口,不具备游泳能力System.out.println("---");Fish fish = new Fish("小金", 1, "金");fish.eat();fish.drink();fish.swim();  // Fish 具备游泳能力}
}

📦 引用类型数组:存放对象的容器

引用类型数组是存放对象的数组。在使用和访问时,需要注意与基本类型数组的区别:

  • 区别 1:元素需要实例化:给引用类型数组的元素赋值时,需要 new 一个对象。数组创建时,元素的默认值是 null

    Dog[] dogs = new Dog[3]; // 创建一个 Dog 数组,包含 3 个 Dog 引用,默认为 null
    dogs[0] = new Dog("小黑",2,"黑"); // 创建一个 Dog 对象并赋给第一个元素
    dogs[1] = new Dog("小白",1,"白"); // 创建另一个 Dog 对象并赋给第二个元素
    dogs[2] = new Dog("小灰",3,"灰"); // 创建第三个 Dog 对象并赋给第三个元素
    
  • 区别 2:通过点运算符访问成员:访问引用类型数组的元素的属性或调用方法时,需要使用点运算符 (.)。

    System.out.println(dogs[0].name); // 输出第一个 Dog 对象的 name 属性
    dogs[1].age = 4; // 修改第二个 Dog 对象的 age 属性
    dogs[2].swim(); // 调用第三个 Dog 对象的 swim 方法
    
    public class RefArrayDemo {public static void main(String[] args) {Dog[] dogs = new Dog[3];dogs[0] = new Dog("小黑",2,"黑");dogs[1] = new Dog("小白",1,"白");dogs[2] = new Dog("小灰",3,"灰");System.out.println(dogs[0].name);dogs[1].age = 4;dogs[2].swim();System.out.println("-------------------------");for(int i=0;i<dogs.length;i++){ // 遍历 dogs 数组System.out.println(dogs[i].name);dogs[i].eat();}Chick[] chicks = new Chick[2];chicks[0] = new Chick("小花",1,"花");chicks[1] = new Chick("大花",2,"花");for(int i=0;i<chicks.length;i++){ // 遍历 chicks 数组System.out.println(chicks[i].name);chicks[i].layEggs();}Fish[] fish = new Fish[4];fish[0] = new Fish("小金",2,"金");fish[1] = new Fish("大金",4,"白");fish[2] = new Fish("小绿",1,"绿");fish[3] = new Fish("小红",3,"红");for(int i=0;i<fish.length;i++){ // 遍历 fish 数组System.out.println(fish[i].color);fish[i].swim();}}
    }
    
  • NullPointerException (空指针异常):如果引用类型数组的元素为 null(还没有创建对象并赋值),然后尝试通过这个 null 引用去访问成员或调用方法,就会发生 NullPointerException。这表示你试图操作一个不存在的对象。

💼 综合练习:达内员工管理系统(设计思路)

这是一个运用抽象类和接口进行面向对象设计的典型案例。

需求分析:

有四类员工:教研总监、讲师、项目经理、班主任。他们有一些共同的属性(名字、年龄、工资)和行为(上班打卡、下班打卡、完成工作),同时也有各自特有的行为(解决企业问题、培训企业员工、编辑书籍)。

设计思路:

  1. 抽共性到超类:将所有员工共有的属性(名字、年龄、工资)和行为(上班打卡、下班打卡)抽到一个雇员超类中。
  2. 处理差异行为:对于“完成工作”这个行为,不同员工的实现方式不同,所以将其设计为抽象方法放在雇员超类中。这样,每个子类都必须提供自己的“完成工作”的具体实现。
  3. 将部分共性行为抽到接口:对于部分员工共有的行为,例如“解决企业问题”、“培训企业员工”、“编辑书籍”,这些是不同的“能力”,适合用接口来定义。
    • 定义一个企业顾问接口,包含“解决企业问题()”和“培训企业员工()”两个抽象方法。
    • 定义一个技术作者接口,包含“编辑书籍()”一个抽象方法。
  4. 构建派生类:根据需求和设计,创建具体的员工类,它们继承雇员超类,并根据需要实现对应的接口。
    • 教研总监类:继承雇员超类,实现企业顾问接口和技术作者接口。需要重写雇员超类中的抽象方法“完成工作()”,以及企业顾问和技术作者接口中的共4个抽象方法。
    • 讲师类:继承雇员超类,实现企业顾问接口和技术作者接口。需要重写雇员超类中的抽象方法“完成工作()”,以及企业顾问和技术作者接口中的共4个抽象方法。
    • 项目经理类:继承雇员超类,实现技术作者接口。需要重写雇员超类中的抽象方法“完成工作()”,以及技术作者接口中的1个抽象方法,共2个抽象方法。
    • 班主任:继承雇员超类。需要重写雇员超类中的抽象方法“完成工作()”,共1个抽象方法。

设计规则总结(适合初学者):

  • 将所有派生类共有的属性和行为,抽到超类中(抽共性)。
  • 若派生类的行为代码都一样,设计普通方法
  • 若派生类的行为代码不一样,设计抽象方法
  • 将部分派生类共有的行为,抽到接口中。

类间关系总结:

  • 类和类:继承 (extends)
  • 接口和接口:继承 (extends)
  • 类和接口:实现 (implements)

好的,我已经识别了图片中的文字内容。

总结

  • 抽象方法和抽象类:
    1. 抽象方法:由 abstract 修饰,只有方法定义,没有具体实现
    2. 抽象类:包含抽象方法的类必须是抽象类,abstract 修饰,不能被实例化
  • 接口:
    1. 由 interface 定义,只能包含抽象方法 (目前课程内容为止)
    2. 不能被实例化,实现接口必须重写所有抽象方法,可以实现多个接口
  • 引用类型数组,与基本类型数组两点区别:
    1. 给引用类型数组元素赋值时,需要 new 个对象
    2. 访问引用类型数组的元素的属性/行为时,需要打点访问

提示

  • 抽象类的应用场景
  • 接口的应用场景
  • 抽象类与接口的区别
  • 引用类型数组如何给元素赋值
  • 如何访问引用类型元素数组的属性或行为

相关文章:

  • 前端扫盲HTML
  • 目标跟踪相关综述文章
  • 震荡指标工具
  • 桌面端进程通信
  • 记录算法笔记(2025.5.17)验证二叉搜索树
  • 高效视频理解的临时移位模块(Temporal Shift Module)
  • 【LUT技术专题】针对降噪优化的通道感知轻量级LUT算法:DnLUT
  • AI Agent | Coze 插件使用指南:从功能解析到实操步骤
  • Linux 文件权限 (rwx) 详解
  • AI Agent开发第69课-彻底消除RAG知识库幻觉(3)-手撕“重排序”
  • React Fiber 架构深度解析:时间切片与性能优化的核心引擎
  • windows系统各版本下载
  • Vivado2024.2+Modelsim仿真环境搭建大全(保姆式说明)
  • bitmap/hyperloglog/GEO详解与案例实战
  • 【MySQL进阶】如何在ubuntu下安装MySQL数据库
  • Java—— 异常详解
  • 机器学习中的过拟合及示例
  • 计算机图形学编程(使用OpenGL和C++)(第2版)学习笔记 12.曲面细分
  • MySQL初阶:sql事务和索引
  • 深入解析Spring Boot与Redis集成:高效缓存实践
  • LPR名副其实吗?如果有所偏离又该如何调整?
  • “上海-日喀则”援藏入境旅游包机在沪首航
  • 山东茌平民企巨头实控人省外再出手:斥资16亿拿下山西一宗探矿权
  • 江南考古文脉探寻
  • 芬兰西南部两架直升机相撞坠毁,第一批救援队已抵达现场
  • 广西鹿寨一水文站“倒刺扶手”存安全隐患,官方通报处理情况