- 在 Java 中,接口是一种完全 / 彻底 抽象的类型。
- 使用 interface 关键字代替 class 关键字的类。
- 也有资料上说 接口不是类,而是对希望实现这个接口的类的一组需求 / 约束 / 契约 / 协议。
- 经常听到某个服务的提供商说:如果你的类 符合某个特定接口,我就会履行这项服务。
- 接口有一个更加形象的叫法,那就是协议(contract )。
- 接口中,更多声明的方法。
- 因此,接口 本质是 方法声明的集合。
- 当然,有些接口也有没有明确说明的额外要求:
- 如:Comparable 接口的 compareTo 方法。
- 调用 x.compareTo(y) 时,必须能比较两个对象,并返回比较的结果,即 x 和 y 哪一个更大。
- 当 x 小于 y 时,返回一个负数;当 x 其等于 y 时,返回 0;否则返回一个正数。
一、接口 – 用implements表示一种协议(contract)关系
1、基本结构
- 接口:使用 interface 修饰的类。
package org.rainlotus.materials.javabase.a04_oop.interfaces;
/**
* @author zhangxw
*/
public interface Flyable {
/** 字段必须指定初始值:省略了 public static final */
String DESC = "能飞接口";
/** 抽象方法(无实现):省略了 public abstract */
abstract void fly();
/** 新增了默认方法:省略了 public
为了避免在接口中新增一个方法,之前所有的实现类都要去实现这个方法。*/
default void takeOff() {
System.out.println("准备起飞...");
}
/** 新增了静态方法:省略了 public。
随着 JDK 的发展,发现类库中出现了很多这样成组的 API。
Path 接口和 Paths 工具类、Collection 接口和 Collections 工具类
增加了静态方法后,可以减少很多这样的工具类。 */
static void logFlight() {
System.out.println("记录飞行日志");
}
/** 私有方法(Java 9+,接口内部复用代码) */
private void internalLog(String message) {
System.out.println("[LOG] " + message);
}
}
public class Bird implements Flyable {
@Override
public void fly() {
System.out.println("鸟儿扇动翅膀飞行");
}
}
public class Airplane implements Flyable {
@Override
public void fly() {
System.out.println("飞机通过引擎飞行");
}
}
2、接口的设计目的
- 1、定义行为规范
- 即:定义清晰的契约,解耦实现。
- 强调“能力”而非“身份”(如:Flyable 表示“能飞”),与实现类的类型无关。
- 如:鸟 和 飞机 都能飞。
- 2、解耦与多态
- 通过接口隔离实现细节,支持灵活替换(如:依赖注入)。
- 即:通过多实现支持灵活扩展。
- 3、替代多继承
- Java 不支持类多继承,但可通过多接口实现扩展功能。
- 接口实现了约定和实现相分离,可以降低代码间的耦合性,提高代码的可扩展性。
- 如:策略模式。
3、核心特性
- 1、接口不能有构造器也不能有初始化块。
- 2、接口里的 field 都是必须有初始值的静态常量。默认有 3 个修饰符:public static final 。
- 3、接口中抽象方法(没有方法体的方法),默认有 2 个修饰符:public abstract 。
- 4、接口中 default 方法 (default 修饰的有方法体的方法),默认有 1 个修饰符:public 。
- 5、接口中 static 方法 (static 修饰的有方法体的方法),默认有 1 个修饰符:public 。
- 6、接口里的内部类、内部接口、内部枚举、默认 2 个修饰符:public static 。
- 7、一个接口同时继承 N 个直接父接口。
4、接口 的使用
- 通过 实现,在子类中,实现抽象方法。
class Bird implements Flyable{
@Override
public void fly() {
System.out.println("会飞翔");
}
public static void main(String[] args) {
Flyable flyable = new Bird();
flyable.fly();
System.out.println(Flyable.DESC);
}
}
- 通过创建 接口 的 匿名内部类,在内部类中,实现抽象方法。
class Test {
public static void main(String[] args) {
Flyable flyable = new Flyable() {
@Override
public void fly() {
System.out.println("会飞翔");
}
};
flyable.fly();
System.out.println(Flyable.DESC);
}
}
5、Java 8+ 对接口的增强
- 默认方法:允许在接口中提供方法默认实现,避免破坏已有实现类。
public interface Vehicle {
default void start() {
System.out.println("车辆启动");
}
}
public class Car implements Vehicle {} // 无需实现 start()
- 静态方法:直接通过接口名调用,用于工具方法。
public interface MathUtils {
static int add(int a, int b) {
return a + b;
}
}
int sum = MathUtils.add(3, 5); // 直接调用
- 私有方法(Java 9+):在接口内部复用代码。
public interface Logger {
private void log(String level, String message) {
System.out.println("[" + level + "] " + message);
}
default void info(String message) {
log("INFO", message);
}
default void error(String message) {
log("ERROR", message);
}
}
二、接口的使用场景
1、标记接口(Marker Interface)
- 定义:无任何方法的接口,仅用于标识类型。
- 如:Serializable、Cloneable)。
public interface Loggable {} // 标记可记录日志的类
public class User implements Loggable {
// 类实现
}
2、函数式接口(Functional Interface)
- 定义:仅有一个抽象方法的接口,可用 @FunctionalInterface 注解标记。
- 支持 Lambda 表达式。
@FunctionalInterface
interface StringProcessor {
String process(String input);
default StringProcessor andThen(StringProcessor after) {
return input -> after.process(this.process(input));
}
}
public static void main(String[] args) {
StringProcessor toUpper = s -> s.toUpperCase();
StringProcessor addExclamation = s -> s + "!";
StringProcessor pipeline = toUpper.andThen(addExclamation);
System.out.println(pipeline.process("hello")); // 输出 "HELLO!"
}
3、接口的多重继承
- 接口可继承多个父接口:
public interface A { void methodA(); }
public interface B { void methodB(); }
public interface C extends A, B { void methodC(); }
public class D implements C {
@Override public void methodA() {}
@Override public void methodB() {}
@Override public void methodC() {}
}
4、接口 与 工厂模式
- 对象创建解耦:
interface DatabaseConnection {
void connect();
}
class MySQLConnection implements DatabaseConnection {
public void connect() { System.out.println("MySQL 连接"); }
}
interface ConnectionFactory {
DatabaseConnection createConnection();
}
class MySQLFactory implements ConnectionFactory {
public DatabaseConnection createConnection() {
return new MySQLConnection();
}
}
// 使用
ConnectionFactory factory = new MySQLFactory();
DatabaseConnection conn = factory.createConnection();
conn.connect();
5、接口与事件驱动架构
- 观察者模式(Observer Pattern):
interface EventListener {
void onEvent(Event event);
}
class EventDispatcher {
private List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
public void fireEvent(Event event) {
for (EventListener listener : listeners) {
listener.onEvent(event);
}
}
}
// 使用
EventDispatcher dispatcher = new EventDispatcher();
dispatcher.addListener(event -> System.out.println("事件处理: " + event));
dispatcher.fireEvent(new Event("data_updated"));
6、策略模式 – 完全满足 开闭原则
- 定义算法族,支持运行时动态切换:
public interface PaymentStrategy {
void pay(double amount);
}
public class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("信用卡支付:" + amount);
}
}
public class ShoppingCart {
private PaymentStrategy strategy;
public void setPaymentStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void checkout(double amount) {
strategy.pay(amount);
}
}
// 使用示例
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment());
cart.checkout(100.0);
三、抽象类 vs 接口
1、设计层面的对比
抽象类:描述“是什么”(is-a 关系)
- 本质:抽象类是对类的抽象,强调从属关系。
- 如:Animal 是 Dog 的基类,表示“狗是一种动物”。
- 代码复用:通过继承共享公共代码(如:字段、方法实现)。
- 控制子类行为:通过抽象方法强制子类实现特定逻辑,或通过模板方法定义算法骨架。
接口:描述“能做什么”
- 本质:接口是对行为的抽象,强调能力扩展。
- 如:Flyable 接口表示“能飞行”,无论实现者是鸟还是飞机。
- 解耦与多态:通过接口隔离实现细节,支持灵活替换(如:依赖注入)。
- 多继承替代:Java 不支持多继承,但可通过多接口实现扩展功能。
- 从类的继承层次上来看:
- 1、抽象类是一种自下而上的设计思路。
- 先有子类的代码重复,然后,再抽象成上层的抽象类 。
- 2、接口是一种自上而下的设计思路。
- 在编程时,一般都是先设计接口,再去考虑具体的实现 。
2、抽象类与接口的选择?
- 如果要表示一种 is-a 的关系 (是一个),并且是为了解决代码复用的问题,就用抽象类 ;
- 如果要表示一种 contract 的关系 (协议),并且是为了解决抽象而非代码复用的问题,就用接口。
决策因素 | 选择抽象类 | 选择接口 |
---|---|---|
需要共享代码 | ✅ 抽象类可提供具体方法实现 | ❌ 接口在 Java 8 前只能有抽象方法 |
需要多继承 | ❌ Java 单继承限制 | ✅ 类可实现多个接口 |
需要管理状态 | ✅ 抽象类可定义实例变量 | ❌ 接口只能有常量 |
需要构造器逻辑 | ✅ 抽象类可定义构造器初始化状态 | ❌ 接口无构造器 |
定义行为规范 | ❌ 更适合定义类别层次 | ✅ 接口天然适合定义行为 |
未来扩展性 | ❌ 修改抽象类可能影响所有子类 | ✅ 接口通过默认方法扩展更灵活 |
3、语法与功能对比
特性 | 抽象类 | 接口 |
---|---|---|
关键字 | abstract class | interface |
继承/实现 | 单继承(extends) | 多实现(implements) |
构造器 | ✅ 可定义,用于初始化成员变量 | ❌ 不能定义 |
成员变量 | 可包含实例变量(非 final)和常量 | 仅常量:默认 public static final |
方法类型 | 抽象方法、具体方法、静态方法、私有方法 | Java 8-:抽象方法、 Java 8+:支持默认方法、静态方法; Java 9+:支持私有方法 |
访问修饰符 | 支持 public、protected、private`、包级私有 | 方法默认 public。 Java 9+:方法支持 private) |
设计目的 | 定义类的层次结构(是什么),提供部分实现 | 定义行为契约(能做什么),支持多态扩展 |
典型应用 | 模板方法模式、共享状态管理、 复杂对象初始化 | 策略模式、回调机制、 API 设计、依赖注入 |
四、接口设计原则总结
原则 | 说明 |
---|---|
单一职责原则 | 每个接口应专注于单一功能领域。 如:Runnable 只定义运行,不混合其他职责。 |
接口隔离原则 | 避免庞大臃肿的接口,拆分细化。 如:List 与 RandomAccess 接口分离。 |
里氏替换原则 | 子类必须完全实现接口 契约,行为可替换父接口 |
依赖倒置原则 | 高层模块依赖抽象接口,而非具体实现 |
开闭原则 | 通过新增接口实现扩展,而非修改已有接口 |
五、常见问题
1、接口中可以有哪些元素?
A、接口不能有构造器也不能有初始化块
B、接口里的field,默认有3个修饰符:public static final
无论你写于不写,反正都有。
【接口里的 field,声明必须指定初始值
原因:final 修饰的类变量只能在声明时、静态初始化块中指定初始值。
又由于接口不包含初始化块,所以只能在声名时指定初始值。
】
C、接口里的 抽象方法,默认有 2 个修饰符 public abstract
无论你写于不写,反正都有。
接口里的方法不可能是 static,因为接口里的方法默认有 abstract 修饰。
D、接口里的 内部类、内部接口、内部枚举、默认也有两个修饰符:public static
无论你写于不写,反正都有。
E、一个接口可以有N个直接父接口。
有了接口:
A、接口可用于定义常量!
B、接口不能直接创建实例。
C、接口最大的用途就是让其它类来实现它。
2、接口里面可以写方法实现吗?
- Java 8-:抽象方法,不可以写方法实现。
- Java 8+:支持默认方法、静态方法,可以写方法实现
- Java 9+:支持私有方法,可以写方法实现
3、接口中只能定义常量和抽象方法吗?
- JDK1.7 之前,接口中只能定义 静态常量和公共的抽象方法
- JDK1.8 中,接口中可以定义 默认方法和静态方法
- JDK1.9 中,接口中可以定义 私有方法
4、接口能不能被 new ?
- 接口也可以直接 new,但要配合内部类来使用,一般是不可以 new 的。
5、接口默认方法和静态方法是什么?
public interface T001_InterfaceWithDefaultMethod {
default void defaultMethod(){
System.out.println("接口的 default 方法");
}
static void staticDefaultMethod(){
System.out.println("接口的 static 方法");
}
static void main(String[] args) {
staticDefaultMethod();
System.out.println(123);
}
}
class InterfaceImpl implements T001_InterfaceWithDefaultMethod{
@Override
public void defaultMethod() {
System.out.println("实现类的 default 方法");
}
public static void main(String[] args) {
T001_InterfaceWithDefaultMethod impl = new InterfaceImpl();
impl.defaultMethod();
}
}
6、接口为什么新增了默认方法和静态方法?
为了避免在接口中新增一个方法,之前 所有的实现类都要去实现这个方法。
随着 JDK 的发展,发现类库中出现了很多这样成组的 API
- Path 接口和 Paths 工具类、Collection 接口和 Collections 工具类
- 增加了接口静态方法后,可以减少很多这样的工具类。
7、接口默认方法有哪些注意的问题?
- 方法要有 default 进行修饰。
- 方法不能用 private 修饰。默认是省略了 public 修饰符。
- 方法必须要有方法体
8、解决 接口 默认方法 多继承 冲突 问题
- 1、超类优先。
- 如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。
package org.rainlotus.materials.javabase.a04_oop.interfaces;
/**
* 接口中,默认方法冲突
* @author zhangxw
*/
public class InterfaceDefaultMethodConflict {
public static void main(String[] args) {
C c = new C();
// 输出:父类 A 中的 foo 方法!!!
c.foo();
}
}
class A { public void foo(){System.out.println("父类 A 中的 foo 方法!!!");}}
interface B { default void foo() { System.out.println("接口 B 中的 foo 默认方法!!!"); } }
class C extends A implements B {
}
- 2、接口冲突。
- 如果一个接口提供了一个默认方法。另一个接口提供了一个同名而且参数类型相同的方法 (不论是否是默认方法),必须覆盖 这个方法来解决冲突。
package org.rainlotus.materials.javabase.a04_oop.interfaces;
/**
* 接口中,默认方法冲突
* @author zhangxw
*/
public class InterfaceDefaultMethodConflict {
public static void main(String[] args) { }
}
interface A { default void foo(){System.out.println("接口 A 中的 foo 默认方法!!!");}}
interface B { default void foo() { System.out.println("接口 B 中的 foo 默认方法!!!"); } }
class C implements A,B {
/**
* 必须重写 冲突的方法
*/
@Override
public void foo() {
System.out.println("自己实现 foo 方法");
B.super.foo();
A.super.foo();
}
}
interface A { void foo();}
interface B { default void foo() { System.out.println("B"); } }
interface C extends A, B {
/**
* 必须重写 冲突的方法
*/
@Override
default void foo() {
System.out.println("");
}
}