接口
接口的由来
兔子不会游泳, 游泳这个方法放在动物类中, 显然是不合适的. 而青蛙和狗都有游泳这个方法, 那么如果青蛙和狗各自写自己的游泳方法, 则可能导致这个方法不统一.

于是就可以定义一个接口, 在其中规定了游泳这个方法的书写规则, 让青蛙和狗各自去实现.
而看家这个方法又是狗这个类所特有的, 就不要放进接口中.
所以接口放的就是部分类公共的方法, 给这些方法规定一个统一的规则.

接口定义的不是继承的规则, 只是一个功能, 或者说是一种规则, 是对行为的抽象.

接口的定义和形式
接口用关键字 interface 来定义: public interface 接口名 {}
接口里面是抽象方法, 所以接口不能实例化.
接口和类之间是实现关系, 通过 implements 关键字来表示: public class 类名 implements 接口名 {}
接口的子类也叫实现类. 实现类要么重写接口中的所有抽象方法, 要么实现类本身也是一个抽象类. 一般都是重写接口中的所有抽象方法.
接口和类之间的实现关系, 可以是单实现, 也可以是多实现.
多实现: public class 类名 implements 接口名 1, 接口名 2 {}
实现类可以在继承一个类的同时实现多个接口: public class 类名 extends 父类 implements 接口名 1, 接口名 2 {}
新建接口文件:

如果新建接口文件时, 忘了选择 interface 选项, 可以在建立完文件后将 class 改为 interface, 文件图标也会跟着改过来.

练习:
编写带有接口和抽象类的标准 Javabean 类
青蛙 属性: 名字, 年龄 行为: 吃虫子, 蛙泳
狗 属性: 名字, 年龄 行为: 吃骨头, 狗刨
兔子 属性: 名字, 年龄 行为: 吃胡萝卜

Javabean 类:
public abstract class Animal { String name; int age; public Animal() { } public Animal(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } // 三个子类都有吃这个方法, 而具体的方法体不同, 则将吃这个方法抽取到父类中, 且定义为抽象类, 强制子类重写 public abstract void eat(); }
接口:
public interface Swim { public abstract void swim(); }
Javabean 类:
public class Dog extends Animal implements Swim { public Dog() { } public Dog(String name, int age) { super(name, age); } @Override public void eat() { System.out.println("狗吃骨头. "); } @Override public void swim() { System.out.println("狗在狗刨. "); } }
Javabean 类:
public class Frog extends Animal implements Swim { public Frog() { } public Frog(String name, int age) { super(name, age); } @Override public void swim() { System.out.println("青蛙在蛙泳. "); } @Override public void eat() { System.out.println("青蛙在吃虫子. "); } }
Javabean 类:
public class Rabbit extends Animal { public Rabbit() { } public Rabbit(String name, int age) { super(name, age); } @Override public void eat() { System.out.println("兔子在吃胡萝卜. "); } }
测试类:
public class Test { public static void main(String[] args) { Frog f = new Frog("小青", 23); System.out.println(f.getName() + ", " + f.getAge()); f.eat(); f.swim(); Rabbit r = new Rabbit("小白", 21); System.out.println(r.getName() + ", " + r.getAge()); r.eat(); // r.swim(); } }
执行结果:
小青, 23 青蛙在吃虫子. 青蛙在蛙泳. 小白, 21 兔子在吃胡萝卜.
接口中成员的特点
成员变量
接口中的成员变量只能是常量.
默认修饰符: public static final
. 默认指的是就算不写, 也默认是存在的. 接口是一种规则, 规则是不能被改变的, 所以接口里的成员变量都是常量, 所以用 final 修饰. 用 static 来修饰是为了方便调用, 用 接口名.常量名
就可以调用. public 表示公共的, 即所有的地方都可以使用接口里的常量.
多个子类的共有属性是抽取到父类中的, 而不是抽取到接口中, 所以接口中不会有类似于 name, age 等成员变量的.
构造方法
没有构造方法. 因为接口不能创建对象, 并且接口也不需要给子类的成员变量赋值.
成员方法
在 JDK 7 之前, 只能写抽象方法, 默认修饰符为 public abstract
, 即哪怕不写虚拟机也会自动帮你加上.
JDK 8: 接口中可以定义有方法体的方法.
JDK 9: 接口中可以定义私有方法.
程序示例:
接口:
public interface Inter { int a = 10; }
测试类:
public class Test { public static void main(String[] args) { System.out.println(Inter.a); // 打印 10, 此处是用 接口名.变量名 的形式来调用的, 由此可以说明是用 static 来修饰的. // Inter.a = 20; // 报错: cannot assign a value to final variable 'a' 由此说明是用 final 修饰的. } }
接口和类之间的关系:
类和类的关系: 继承关系, 只能单继承, 不能多继承, 但是可以多层继承
类和接口的关系: 实现关系, 可以单实现, 也可以多实现, 还可以在继承一个类的同时实现多个接口. 如果一个类实现了多个接口, 那么就要重写全部的抽象方法.
接口和接口的关系: 继承关系, 可以单继承, 也可以多继承. 如果实现类实现了最下面的子接口的话, 那么就需要重写所有的抽象方法.
如果实现多个接口时, 多个接口有相同的方法, 那么在子类中只需要重写一次重复的方法.
接口 1:
public interface Inter1 { public abstract void method1(); public abstract void method_abc(); }
接口 2:
public interface Inter2 { public abstract void method2(); public abstract void method_abc(); }
接口 3:
public interface Inter3 extends Inter2, Inter1 { public abstract void method3(); }
实现类:
public class InterImpl implements Inter3 { // 该类实现了最下面的子接口, 需要重写所有的方法 @Override public void method1() { } @Override public void method2() { } @Override public void method_abc() { // 重复的方法只需要实现一遍 } @Override public void method3() { } }
接口 3 继承自接口 1 和接口 2, 实现类实现了接口 3, 则实现类中要重写三个接口的全部方法.
练习:
编写带有接口和抽象类的标准 Javabean 类
我们现在有乒乓球运动员和篮球运动员, 乒乓球教练和篮球教练.
为了出国交流, 跟乒乓球相关的人员都需要学习英语.
请用所有知识分析, 在这个案例中, 哪些是具体类, 哪些是抽象类, 哪些是接口?
乒乓球运动员: 姓名, 年龄, 学打乒乓球, 说英语
篮球运动员: 姓名, 年龄, 学打篮球
乒乓球教练: 姓名, 年龄, 教打乒乓球, 说英语
篮球教练: 姓名, 年龄, 教打篮球

Javabean 类:
// 因为现在我不想让外界去直接创建人的对象 // 因为直接创建顶层父类人的对象此时是没有意义的 // 所以我就把他写为抽象的. public abstract class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
public abstract class Sporter extends Person { public Sporter() { } public Sporter(String name, int age) { super(name, age); } public abstract void study(); }
public abstract class Coach extends Person { public Coach() { } public Coach(String name, int age) { super(name, age); } public abstract void teach(); }
public class PingpangSporter extends Sporter implements English { public PingpangSporter() { } public PingpangSporter(String name, int age) { super(name, age); } @Override public void study() { System.out.println("乒乓球运动员" + this.getName() + "在学打乒乓球."); } @Override public void speakEnglish() { System.out.println("乒乓球运动员" + this.getName() + "在学说英语."); } }
public class BasketballSporter extends Sporter { public BasketballSporter() { } public BasketballSporter(String name, int age) { super(name, age); } @Override public void study() { System.out.println("篮球运动员" + this.getName() + "在学习打篮球."); } }
public class PingpangCoach extends Coach implements English { public PingpangCoach() { } public PingpangCoach(String name, int age) { super(name, age); } @Override public void teach() { System.out.println("乒乓球教练" + this.getName() + "在教别人打乒乓球."); } @Override public void speakEnglish() { System.out.println("乒乓球教练" + this.getName() + "在学说英语."); } }
public class BasketballCoach extends Coach { public BasketballCoach() { } public BasketballCoach(String name, int age) { super(name, age); } @Override public void teach() { System.out.println("篮球教练" + this.getName() + "在教别人打篮球."); } }
接口类:
public interface English { public abstract void speakEnglish(); }
测试类:
public class Test { public static void main(String[] args) { PingpangSporter pingpangSporter = new PingpangSporter("福原爱", 23); System.out.println(pingpangSporter.getName() + ", " + pingpangSporter.getAge()); pingpangSporter.speakEnglish(); pingpangSporter.study(); System.out.println("----------------------------------------"); PingpangCoach pingpangCoach = new PingpangCoach("不懂球的胖子", 56); System.out.println(pingpangCoach.getName() + ", " + pingpangCoach.getAge()); pingpangCoach.speakEnglish(); pingpangCoach.teach(); System.out.println("----------------------------------------"); BasketballSporter basketballSporter = new BasketballSporter("乔丹", 21); System.out.println(basketballSporter.getName() + ", " + basketballSporter.getAge()); basketballSporter.study(); System.out.println("----------------------------------------"); BasketballCoach basketballCoach = new BasketballCoach("姚明", 42); System.out.println(basketballCoach.getName() + ", " + basketballCoach.getAge()); basketballCoach.teach(); } }
执行结果:
福原爱, 23 乒乓球运动员福原爱在学说英语. 乒乓球运动员福原爱在学打乒乓球. ---------------------------------------- 不懂球的胖子, 56 乒乓球教练不懂球的胖子在学说英语. 乒乓球教练不懂球的胖子在教别人打乒乓球. ---------------------------------------- 乔丹, 21 篮球运动员乔丹在学习打篮球. ---------------------------------------- 姚明, 42 篮球教练姚明在教别人打篮球.
JDK 8 开始接口中新增的方法
JDK 7 之前, 接口中只能定义抽象方法.
JDK 8 的新特性: 接口中可以定义有方法体的方法, 分两种, 一种是默认的方法, 一种是静态的方法.
JDK 9 的新特性: 接口中可以定义私有方法, 即用 private 修饰.
Java 为什么这么设计?
按照 JDK 7 的规定, 如果接口变了, 那么所有的实现类都需要进行修改.
接口中有方法体的方法, 是在接口升级时, 为了兼容性而使用的. 接口添加了有方法体的方法后, 实现类不需要立马重写, 不重写也不会报错, 当这个实现类要用到这个方法的时候再去重写即可.
JDK 8 开始, 允许在接口中定义默认方法, 用关键字 default 修饰. 作用就是解决接口升级带来的兼容性问题.
接口中默认方法的定义格式:
public default 返回值类型 方法名 (参数列表) {}
例如:
public default void show () {}
默认方法不是抽象方法, 不会被强制重写, 但是如果被重写, 重写的时候要去掉 default 关键字.
public 是默认修饰符, 可以省略, 但是 default 不能被省略.
如果一个实现类实现了多个接口, 多个接口中存在相同名字的默认方法, 子类就必须对这个方法进行重写.
程序示例:
接口:
public interface Inter { // 抽象方法 public abstract void method(); // 默认方法 public default void show() { System.out.println("接口的默认方法 ---- show"); } }
实现:
public class ImplInter implements Inter{ // 重写抽象方法 @Override public void method() { System.out.println("实现类的 method 方法. "); } }
测试类:
public class Test { public static void main(String[] args) { ImplInter ii = new ImplInter(); ii.method(); ii.show(); // 类并没有重写接口里面的默认方法, 但是类的对象也可以调用这个默认方法 } }
执行结果:
实现类的 method 方法. 接口的默认方法 ---- show
程序示例:
接口:
public interface Inter { // 抽象方法 public abstract void method(); // 默认方法 public default void show() { System.out.println("接口的默认方法 ---- show"); } }
实现类:
public class ImplInter implements Inter { // 重写抽象方法 @Override public void method() { System.out.println("实现类的 method 方法. "); } // 重写接口的默认方法 @Override public void show() { System.out.println("重写接口的默认方法. "); } }
测试类:
public class Test { public static void main(String[] args) { ImplInter ii = new ImplInter(); ii.method(); ii.show(); } }
执行结果:
实现类的 method 方法. 重写接口的默认方法.
如果两个接口中有同名的默认方法, 则实现了这两个接口的类必须重写这个方法, 否则类的对象在调用这个默认方法时, 不知道是调用哪个接口中的默认方法. 程序示例:
第一个接口:
public interface InterA { public abstract void methodA(); public default void show() { System.out.println("InterA 里面的 show()"); } }
第二个接口:
public interface InterB { public abstract void methodB(); public default void show() { System.out.println("InterB 里面的 show()"); } }
实现类:
public class InterImpl implements InterA, InterB { // 重写抽象方法 @Override public void methodA() { } // 重写抽象方法 @Override public void methodB() { } @Override public void show() { // 这个 default 方法被强制重写, 因为两个接口都有这个方法 System.out.println("实现类中的重写的默认方法"); } }
测试类:
public class Test { public static void main(String[] args) { InterImpl inter = new InterImpl(); inter.show(); // 实现类中的重写的默认方法 } }
JDK 8 开始允许在接口中定义静态方法, 用 static 修饰.
接口中的静态方法的定义格式:
public static 返回值类型 方法名 (参数列表) {}
例如:
public static void show () {}
接口中静态方法的注意事项:
静态方法只能通过接口名调用, 不能通过实现类名或对象名来调用.
public 可以省略, 但是 static 不能被省略. 如果 static 省略不写, 则会被当为抽象方法, 因为 abstract 是默认的, 不写就默认是 abstract.
静态方法不需要重写. 静态方法不能被重写, 强制重写则代码报错.
程序示例:
接口代码:
public interface Inter { // 接口中的抽象方法 public abstract void method(); // 接口中的静态方法 public static void show() { System.out.println("接口中的静态方法"); } }
实现类的代码:
public class InterImpl implements Inter{ // 重写接口的抽象方法 @Override public void method() { System.out.println("InterImpl 重写的抽象方法."); } // 接口的静态方法不需要重写 // 这个不是重写, 只是实现类里面有一个和接口里面同名的静态方法 // 如果调用时, 用 InterImpl.show(), 则调用的是子类里面的 show() 方法 // 用 Inter.show(), 则调用的是接口里面的 show() 方法 public static void show(){ System.out.println("InterImpl 的 show() 方法."); } }
测试类:
public class Test { public static void main(String[] args) { // 调用接口中的 show() 方法 Inter.show(); // 接口中的静态方法 // 调用实现类中的静态方法 InterImpl.show(); // InterImpl 的 show() 方法. } }
JDK 9 开始接口中新增的方法
在 JDK 9 之后, 接口中可以写私有方法.
私有方法分两种: 普通的和静态的.
接口中私有方法的定义格式:
普通私有方法, 是给默认方法服务的:
格式: private 返回值类型 方法名(参数列表) {}
范例: private void show() {}
加了 static 的私有方法, 即静态的私有方法, 是给静态方法服务的:
格式: private static 返回值类型 方法名(参数列表) {}
范例: private static void method() {}
程序示例:
接口:
public interface Inter { public default void show1() { System.out.println("show1 方法开始执行. "); System.out.println("记录程序在开始执行之后的各种细节, 这里有 100 行代码. "); } public default void show2() { System.out.println("show2 方法开始执行. "); System.out.println("记录程序在开始执行之后的各种细节, 这里有 100 行代码. "); } }
显然两个方法中有大量的重复代码, 可以进行抽取.
public interface Inter { public default void show1() { System.out.println("show1 方法开始执行. "); show3(); } public default void show2() { System.out.println("show2 方法开始执行. "); show3(); } public default void show3() { System.out.println("记录程序在开始执行之后的各种细节, 这里有 100 行代码. "); } }
抽取出来之后在 show1() 和 show2() 中调用这个抽取出来的 show3() 即可.
但是这个方法不希望被外面的其他类调用, 因为毫无意义. 因此规定其为 private 并删掉 default.
public interface Inter { public default void show1() { System.out.println("show1 方法开始执行. "); show3(); } public default void show2() { System.out.println("show2 方法开始执行. "); show3(); } private void show3() { // 这就是普通的私有方法, 即没有用 static 修饰的私有方法 System.out.println("记录程序在开始执行之后的各种细节, 这里有 100 行代码. "); } }
针对静态方法则需要静态私有方法为其服务:
public interface Inter { public static void show1() { System.out.println("show1 方法开始执行. "); show4(); } public static void show2() { System.out.println("show2 方法开始执行. "); show4(); } private void show3() { System.out.println("记录程序在开始执行之后的各种细节, 这里有 100 行代码. "); } private static void show4() { // 加 static 修饰的静态私有方法 System.out.println("记录程序在开始执行之后的各种细节, 这里有 100 行代码. "); } }
当一个方法的形参是接口时, 可以传递接口所有实现类的对象, 这种方式称为接口多态.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术