当前位置: 首页 > 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.jsqmd.com/news/675040/

相关文章:

  • Session Startup 执行机制详解
  • Blazor WebAssembly性能突破78%!2026企业刚需:如何用Server-Side Hybrid模式重构ERP前端(附Gartner验证基准)
  • 2026年质量好的颗粒输送机/车载输送机/吸粮输送机/曲阜输送机公司对比推荐 - 行业平台推荐
  • Phi-3.5-mini-instruct轻量推理优势:在低功耗GPU上实现<500ms首token延迟
  • 收藏!小白程序员必看:掌握 Claude 提示词缓存,降低 AI 代理成本 81%!
  • PyTorch 2.6镜像使用教程:手把手教你快速搭建GPU训练环境
  • 2026年质量好的青岛实木全屋定制/青岛小户型全屋定制/青岛旧房改造全屋定制/青岛高端全屋定制人气公司推荐 - 行业平台推荐
  • RAG检索质量提升秘籍:拆解链路,逐层优化,告别不稳定!
  • 个体防护装备活门性能测试系统
  • intv_ai_mk11入门指南:通用问答/解释说明/简短创作三大核心能力演示
  • 别再用过时预设了!2024年最新VSCO Film 1-7全套预设安装与使用避坑指南(含Camera Profiles缺失机型解决方案)
  • Dify API密钥越权访问事件频发?揭秘内置Permission Engine的4层拦截机制及绕过反制方案
  • 安全负责人必读:灵境AIDR如何用AI-BOM和全链路溯源破解智能体合规难题?
  • 在 Go 语言中声明包级(全局)映射的正确方法
  • 2026年热门的常州四害消杀/常州HACCP虫控服务推荐榜单公司 - 品牌宣传支持者
  • 进销存是什么意思?从0到1看懂进销存与进销存管理逻辑
  • 111113345
  • 从理论到误差分析:如何解读EKF在制导仿真中的位置、速度、加速度误差曲线
  • 像素史诗·智识终端Java开发环境快速配置:基于镜像的一站式解决方案
  • ROS驱动配置与Kinect连接指南
  • Windows组策略不生效?别慌!手把手教你用注册表精准定位与修复(附常用键值对照表)
  • 【Python3教程】Python3高级篇之re模块
  • 2026年知名的常州车间消杀除虫/常州消杀服务/常州酒店消杀优质公司推荐 - 行业平台推荐
  • 智能体AI前景光明但挑战重重,企业级系统构建要素有哪些?
  • 基于 eNSP 的校园网 NAT、DNS、HTTP 与访问控制综合实验
  • Phi-3.5-mini-instruct教育科技:编程作业自动批改+错因分析+改进提示
  • AUTOSAR通信栈实战:手把手配置CanTp与CanIf模块(含代码示例)
  • 可落地类量子虚实嵌套多时空子母体协同:全域计算底层范式
  • Zsh Alias Preview 预览 zsh 中的命令缩写
  • 郭老师-人脉的本质:你强,世界才温柔