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

10年老兵带你学Java(第5课):接口与抽象类 - 抽象与契约


本课目标

  • 抽象类:抽象概念的类
  • 接口:行为的契约
  • 两者的区别和使用场景
  • Java 8/9/11 接口新特性

上节课学了继承,继承是"是什么"的关系。
这节课学接口和抽象类,是"能做什么"的关系。


一、抽象类:抽象概念的类

1.1 什么是抽象类?

抽象类就是用abstract修饰的类,不能直接实例化(不能 new)。

为什么需要抽象类?

举个例子:

  • “动物"是一个抽象概念,你不能创造一个"动物”,只能说"这是猫"或"这是狗"
  • “形状"是一个抽象概念,你不能说"给我画一个形状”,只能说"画个圆"或"画个方"

抽象类的特点:

  1. abstract修饰
  2. 不能直接new创建对象
  3. 可以有抽象方法(没有方法体的方法)
  4. 也可以有普通方法和属性

1.2 抽象类的语法

// 抽象类:用abstract修饰publicabstractclassAnimal{protectedStringname;// 抽象方法:没有方法体,子类必须实现publicabstractvoidsound();// 普通方法:有方法体,子类可以直接继承publicvoideat(){System.out.println(name+"在吃东西");}// 构造方法(子类调用)publicAnimal(Stringname){this.name=name;}}

子类继承抽象类:

// 用extends继承抽象类publicclassCatextendsAnimal{publicCat(Stringname){super(name);// 调用父类构造}// 必须实现父类的抽象方法@Overridepublicvoidsound(){System.out.println(name+"喵喵喵");}}

使用:

publicclassMain{publicstaticvoidmain(String[]args){// 不能直接new抽象类// Animal a = new Animal(); // 错误!// 只能创建子类对象Animalcat=newCat("小花");cat.eat();// 继承来的方法cat.sound();// 重写的方法}}

1.3 抽象方法

抽象方法是只有方法签名、没有方法体的方法。

publicabstractclassAnimal{// 抽象方法:没有{}publicabstractvoidsound();publicabstractvoidmove();}

子类必须实现所有抽象方法,否则子类也必须是抽象类。


二、接口:行为的契约

2.1 什么是接口?

接口是一种行为规范,它定义了一组"能做什么"的方法,但不关心"怎么做"。

打个比方:

  • USB接口:定义了供电和数据传输的规范,不管你是U盘还是鼠标,只要符合这个接口就能用
  • Java接口:定义了一组方法的规范,不管哪个类实现,只要符合接口就能互换

2.2 接口的语法

// 定义接口:用interfacepublicinterfaceFlyable{// 接口中的方法默认是抽象的(Java 7以前)// 抽象方法:没有方法体voidfly();// 接口中的属性默认是常量(public static final)intMAX_SPEED=1000;}

类实现接口:用 implements

// 鸟实现飞接口publicclassBirdimplementsFlyable{@Overridepublicvoidfly(){System.out.println("鸟儿扇动翅膀飞翔");}}// 飞机实现飞接口publicclassAirplaneimplementsFlyable{@Overridepublicvoidfly(){System.out.println("飞机发动机推动飞翔");}}

使用:

publicclassMain{publicstaticvoidmain(String[]args){// 接口引用指向实现类对象Flyablef1=newBird();Flyablef2=newAirplane();f1.fly();// 鸟儿扇动翅膀飞翔f2.fly();// 飞机发动机推动飞翔}}

2.3 接口的特点

  1. 接口不能实例化(不能new)
  2. 接口的方法默认是抽象的(Java 7及以前)
  3. 接口的属性默认是常量(public static final)
  4. 一个类可以实现多个接口(弥补单继承的不足)
  5. 接口可以继承接口(多继承)

2.4 接口的多实现

// 接口1publicinterfaceRunnable{voidrun();}// 接口2publicinterfaceEatable{voideat();}// 一个类可以实现多个接口publicclassPersonimplementsRunnable,Eatable{@Overridepublicvoidrun(){System.out.println("人在跑步");}@Overridepublicvoideat(){System.out.println("人在吃饭");}}

这解决了单继承的问题——类只能继承一个父类,但可以实现多个接口。

2.5 接口的默认方法(Java 8)

Java 8开始,接口可以有默认方法:

publicinterfaceFlyable{// 抽象方法voidfly();// 默认方法:用default修饰,有方法体defaultvoidland(){System.out.println("安全降落");}}

作用:如果接口需要新增方法,旧实现类不用全部重写。

publicclassBirdimplementsFlyable{@Overridepublicvoidfly(){System.out.println("鸟儿飞翔");}// 不需要实现land(),可以直接用默认的}// 使用Flyableb=newBird();b.land();// 输出:安全降落

2.6 接口的静态方法(Java 8)

publicinterfaceMathUtil{staticintmax(inta,intb){returna>b?a:b;}}// 调用:直接用接口名调用intm=MathUtil.max(10,20);// 20

三、抽象类 vs 接口

3.1 核心区别

对比项抽象类接口
关键字abstract classinterface
继承/实现extends(单继承)implements(多实现)
属性无限制默认常量public static final
方法抽象 + 普通都可以Java 7:抽象方法;Java 8+:抽象 + 默认 + 静态
构造方法没有
什么时候用"是什么"的关系"能做什么"的关系

3.2 选择原则

用抽象类:

  • 类之间有明显的"是…的"关系(猫是动物)
  • 需要共享代码(普通方法)
  • 需要控制属性访问权限

用接口:

  • 类之间有相同的行为但没有继承关系
  • 需要多实现(一个类实现多个接口)
  • 需要解耦(面向接口编程)

3.3 经典例子:门

// 抽象类:门(抽象概念)abstractclassDoor{protectedStringmaterial;publicDoor(Stringmaterial){this.material=material;}// 开门(普通方法)publicvoidopen(){System.out.println("门打开了");}// 关门(普通方法)publicvoidclose(){System.out.println("门关上了");}// 报警(抽象方法,子类实现)publicabstractvoidalarm();}// 木门classWoodenDoorextendsDoor{publicWoodenDoor(){super("木头");}@Overridepublicvoidalarm(){System.out.println("木头门发出警报:哔哔哔");}}// 铁门classIronDoorextendsDoor{publicIronDoor(){super("铁");}@Overridepublicvoidalarm(){System.out.println("铁门发出警报:滴滴滴");}}

四、综合练习:宠物店系统

4.1 需求

用接口和抽象类,实现一个宠物店系统:

  • 抽象类 Pet:宠物的基本属性
  • 接口 Jumpable:能跳的动作
  • 接口 Eatable:能吃的动作

4.2 代码

// 抽象类:宠物abstractclassPet{protectedStringname;protectedintage;publicPet(Stringname,intage){this.name=name;this.age=age;}// 普通方法publicvoidshowInfo(){System.out.println("宠物名:"+name+",年龄:"+age);}}// 接口:能跳interfaceJumpable{voidjump();}// 接口:能吃interfaceEatable{voideat();}// 狗:继承Pet,实现Jumpable和EatableclassDogextendsPetimplementsJumpable,Eatable{publicDog(Stringname,intage){super(name,age);}@Overridepublicvoidjump(){System.out.println(name+"跳起来了!");}@Overridepublicvoideat(){System.out.println(name+"在吃狗粮");}}// 猫:继承Pet,实现Jumpable和EatableclassCatextendsPetimplementsJumpable,Eatable{publicCat(Stringname,intage){super(name,age);}@Overridepublicvoidjump(){System.out.println(name+"轻盈地跳上了桌子");}@Overridepublicvoideat(){System.out.println(name+"在吃猫粮");}}// 主程序publicclassMain{publicstaticvoidmain(String[]args){Pet[]pets={newDog("旺财",3),newCat("小白",2)};System.out.println("=== 宠物店 ===");for(Petpet:pets){pet.showInfo();// 强制类型转换后调用接口方法if(petinstanceofJumpable){((Jumpable)pet).jump();}if(petinstanceofEatable){((Eatable)pet).eat();}System.out.println();}// 统一管理:能跳的宠物System.out.println("=== 能跳的宠物 ===");for(Petpet:pets){if(petinstanceofJumpable){Jumpablej=(Jumpable)pet;j.jump();}}}}

4.3 运行结果

=== 宠物店 === 宠物名:旺财,年龄:3 旺财跳起来了! 旺财在吃狗粮 宠物名:小白,年龄:2 小白轻盈地跳上了桌子 小白在吃猫粮 === 能跳的宠物 === 旺财跳起来了! 小白轻盈地跳上了桌子

五、常见错误

❌ 错误1:抽象类能直接new

publicabstractclassAnimal{publicabstractvoidsound();}// 错误!抽象类不能实例化Animala=newAnimal();// 正确Animala=newCat();

❌ 错误2:接口属性不是常量

publicinterfaceFlyable{// 错误!这是常量,默认是 public static finalintMAX_SPEED=1000;// 如果写:intspeed;// 等价于 public static final int speed = 1000;}

❌ 错误3:接口方法漏写public

publicinterfaceFlyable{// Java 7以前,必须写publicvoidfly();// 等价于 public abstract void fly();// 错误!子类实现时需要public,这里不写会报错voidland();}

❌ 错误4:抽象方法没有全部实现

publicabstractclassAnimal{publicabstractvoidsound();publicabstractvoidmove();}// 错误!子类必须实现所有抽象方法classCatextendsAnimal{@Overridepublicvoidsound(){System.out.println("喵");}// 缺少move()的实现,报错!}// 正确classCatextendsAnimal{@Overridepublicvoidsound(){System.out.println("喵");}@Overridepublicvoidmove(){System.out.println("猫在走");}}

六、本课总结

抽象类:

  • abstract class定义
  • 单继承,有构造方法
  • 适合"是什么"的关系
  • 可以有抽象方法和普通方法

接口:

  • interface定义
  • 多实现,无构造方法
  • 适合"能做什么"的关系
  • Java 7:抽象方法;Java 8+:默认方法、静态方法

选择:

  • 有继承关系,用抽象类
  • 需要多实现,用接口
  • 既要继承又要多实现:先extends一个类,再implements多个接口

七、下节课预告

第6课:常用类

  • String 字符串
  • StringBuilder 可变字符串
  • 日期时间 API
  • 包装类

学完这课,你就能更高效地处理数据了。


关注我,跟着老兵学Java,少走弯路。

💬评论区聊聊:抽象类和接口,哪个更容易搞混?

http://www.cnnetsun.cn/news/2006401.html

相关文章:

  • 类设计--友元+静态成员+对象之间的关系
  • 前端基础:form和input用法详解
  • Linux内核SCSI错误处理实战:当你的硬盘IO卡住或报错时,内核到底做了什么?
  • 万象视界灵坛实战教程:将语义解析结果接入Elasticsearch构建视觉搜索系统
  • Educational Codeforces Round 120 (Rated for Div. 2) vp补题
  • Phi-3.5-mini-instruct步骤详解:从model path配置到Gradio端口映射
  • 无人机送货时如何‘看’得更远?聊聊MPC里的预测时域K和采样时间dt怎么调
  • 别再被公式吓跑!用大白话和Python代码图解GAMP算法的核心思想
  • 别再对着屏幕列表发愁了!手把手教你根据手头的OLED屏型号,快速找到正确的U8g2构造器
  • 别再暴力搜索了!用‘可行性剪枝’5分钟搞定洛谷P1025数的划分
  • 智能车电磁循迹:从吴恩达的机器学习课到我的小车,聊聊归一化为什么比差比和更香
  • **WebAssembly实战进阶:用Rust构建高性能前端模块并集成到React应用中**在现代Web开发中,
  • 告别枯燥理论!用Proteus 8.15 + 51汇编亲手“点亮”硬件:数码管、按键、LED全搞定
  • 从电平转换到总线仲裁:UART、RS232与RS485的硬件差异如何重塑软件编程思维
  • Android13 Activity启动流程深度解析:从进程间通信到窗口树构建
  • 从电位器到内部温度传感器:一个STM32 ADC多通道采集的完整项目实战(含代码与波形分析)
  • 【效率革命】18款免费3dMAX插件与脚本,解锁你的创意生产力
  • 【Qwen3.5 系列技术解析】:2026 年原生多模态旗舰模型全景报告
  • 告别Command Not Found:手把手教你配置Mac上的zsh与Oh My Zsh(含Homebrew、zsh-autosuggestions插件安装)
  • 保姆级教程:在Ubuntu 20.04上搞定Velodyne VLP-16雷达的ROS驱动与Rviz可视化(含网络配置避坑)
  • 武汉大学计算机复试通关指南:从机考到面试的实战策略
  • TypeScript的NonNullable《T》工具类型的实现原理
  • RWKV-7 (1.5B World)多场景落地:教育问答、跨境客服、内容创作三合一
  • 频谱分析避坑指南:为什么你补了零却提不高频率分辨率?
  • 从SENet到CBAM:通道注意力机制的‘进化史’与实战调参指南
  • 【企业级向量安全红线】:EF Core 10中必须禁用的5个默认配置、4类危险LINQ表达式及3种合规审计Checklist
  • Qt Widgets模块实战:QGridLayout栅格布局从入门到精通
  • Simulink数据管理踩坑记:为什么我们最终放弃了.sldd,改用Excel管理标定量?
  • Arduino新手必看:用ULN2003驱动28BYJ-48步进电机的完整指南(附代码)
  • Elasticsearch 向量数据库指南