21-Java|interface接口
概述
接口(Interface) 是 Java 中用于定义行为契约的一种机制。它定义了一组方法,而这些方法不包含实现(除非是 default 方法或 static 方法)。接口允许类实现这些方法并提供具体的功能,实现了代码的灵活性和模块化。
接口的特点和作用:
- 抽象行为:接口定义了一组抽象方法,这些方法没有实现,由实现接口的类负责具体实现。接口可以看作是一组行为的约定。
- 多实现支持:类只能继承一个父类,但可以实现多个接口。接口为 Java 提供了类多重行为继承的机制,弥补了类不支持多继承的限制。
- 实现解耦:接口可以将方法的定义和实现分开,增强了代码的解耦性。通过接口,类可以在不依赖具体实现的情况下使用其他类提供的功能。
- 设计灵活性:接口可以定义多个类都应该遵循的行为。不同的类可以实现同一个接口,但实现细节可以不同,这提高了设计的灵活性。
- 多态性支持:接口为 Java 的多态性提供了支持,可以通过接口类型引用对象,使代码更加通用。
接口的组成:
- 抽象方法:接口中的方法默认是
public abstract,即这些方法必须在实现类中实现。 - 默认方法(default 方法):接口可以包含带有默认实现的
default方法。实现类可以选择继承或重写这些方法。 - 静态方法:接口可以包含
static方法,静态方法只能通过接口名调用,而不能通过接口的实现类调用。 - 常量:接口中的变量默认是
public static final,即它们是常量,必须初始化并且不可更改。
接口示例:
1 | // 定义接口 |
Java 接口的优点:
- 实现多重继承:通过接口,一个类可以实现多个接口,达到多继承的效果,而不会引发多继承的复杂性问题。
- 松耦合设计:接口可以将系统中的各个模块解耦,使模块之间的依赖更灵活,通过接口定义行为,而不关心具体实现细节。
- 提高代码的可维护性:接口使得程序更加清晰,接口实现类可以根据需要进行更换,而不影响使用接口的其他部分。
- 支持回调机制:通过接口,可以实现回调机制,例如 Java 中的事件处理、线程执行等都是通过接口回调实现的。
Java 接口的常见用途:
- 定义行为契约:接口用于定义一个类必须实现的一组方法。例如
Runnable接口定义了run()方法,任何实现Runnable接口的类都必须提供run()方法的实现。 - 回调机制:例如
ActionListener接口用于定义用户界面中的动作事件处理机制。 - 多态性:接口可以用于实现多态性,允许不同的类通过相同的接口引用实现不同的行为。
- 与类库的交互:Java 的许多标准库通过接口定义类的行为,使得库可以扩展和应用于不同的实现。
总结:
接口在 Java 中是一个强大的工具,用于定义类应该具备的行为,而不限定具体的实现方式。通过接口,Java 实现了面向对象编程中的多态性、抽象化和模块化,同时提供了多重继承的灵活性而避免了多继承的复杂性问题。
接口规则
Java 接口是一种用于定义类必须遵循的契约。它提供了一种抽象的方式来定义行为而不实现具体的功能。接口的规则有助于实现代码的解耦、模块化和灵活性。
以下是 Java 接口的一些主要规则:
接口不能有实例化对象
- 接口是纯抽象的,不能被直接实例化。你不能使用
new关键字来创建接口的对象。
接口中的方法默认是public abstract**
- 在接口中定义的方法默认是
public abstract,即这些方法必须被实现类提供实现。无需显式声明public abstract。
接口中的变量默认是 public static final
- 接口中的变量默认是常量,即
public static final。它们必须被赋值,并且是不可修改的。
接口可以包含 default方法
- 接口可以包含带有默认实现的
default方法。实现类可以选择继承这个默认方法或重写它。
接口可以包含 static 方法
- 接口可以包含
static方法,且只能通过接口名直接调用,不能通过实现类或对象来调用。
接口可以继承多个接口
- 接口支持多继承。一个接口可以继承多个其他接口,并且必须实现所有父接口中的抽象方法。
接口是一种契约
- 实现接口的类必须实现接口中的所有抽象方法,否则该类必须声明为抽象类。
类实现接口
当一个类实现接口时,它承诺将提供该接口中定义的所有抽象方法的具体实现。如果类没有实现接口中的所有方法,那么这个类必须是抽象的。
基本步骤:
- 使用
implements关键字- 类通过
implements关键字来实现接口,并且可以实现多个接口。
- 类通过
- 实现所有的抽象方法
- 如果一个类实现了接口,它必须实现接口中的所有抽象方法。如果不实现,则必须将该类声明为抽象类。
- 可以选择重写
default方法- 如果接口提供了
default方法,实现类可以选择继承它,或者对其进行重写。
- 如果接口提供了
示例代码:
1 | // 定义接口 |
接口的多实现
在 Java 中,多实现是指一个类可以同时实现多个接口。这与类的继承不同,Java 中不允许类多继承,但可以通过实现多个接口来实现类的多实现。多实现让类能够从多个接口中获取不同的功能契约,从而实现代码的灵活性和模块化。
特点:
- 一个类可以实现多个接口:使用
implements关键字,并用逗号分隔接口名称,一个类可以同时实现多个接口。 - 必须实现接口中的所有方法:类在实现多个接口时,必须为所有接口中的抽象方法提供具体实现。
- 支持多种行为组合:通过实现不同接口,类可以实现多种不同的行为接口,满足多重职责。
示例代码:
1 | // 定义第一个接口 |
解释:
InterfaceA和InterfaceB:定义了两个接口,分别包含抽象方法methodA()和methodB()。MyClass:通过implements关键字同时实现了InterfaceA和InterfaceB。为了符合接口契约,MyClass必须提供这两个接口的抽象方法的具体实现。- 实现的灵活性:通过实现多个接口,
MyClass类获得了多个接口的能力,可以在实例化对象时调用不同接口的方法。
接口多实现的优势:
- 灵活性:一个类可以实现多个不同的接口,这样可以灵活地混合多个功能或行为。
- 避免类多继承的问题:类多继承可能会导致”钻石继承问题”(多重继承导致父类方法冲突),而接口的多实现则不会,因为接口只是定义了契约,不涉及具体的实现逻辑。
- 更好地组织代码:通过接口实现,类可以分离出不同的职责,不同的接口可以定义不同的功能契约,而类可以选择性地实现它们。
接口的多继承
在 Java 中,接口支持多继承,即一个接口可以继承多个其他接口。这与类不同,类不支持多继承,但接口之间可以进行多继承,并且可以通过这种方式实现代码的复用和接口的扩展
特点:
- 接口可以继承多个接口:一个接口可以通过
extends关键字继承多个接口,而不必实现它们的具体方法。 - 接口继承后可以扩展新方法:子接口可以定义新方法,或者继承并合并父接口中的方法。
- 接口中的默认方法(
default方法)也可以被继承:如果父接口有默认方法,子接口可以选择继承它,或者重写它。
示例代码:
1 | // 定义第一个父接口 |
解释:
InterfaceA和InterfaceB:定义了两个父接口,分别包含methodA()和methodB()。ChildInterface:通过extends关键字同时继承了InterfaceA和InterfaceB,并且扩展了一个新方法methodC()。MyClass:实现了ChildInterface,因此需要实现所有继承来的方法:methodA()、methodB()和methodC()。
要点:
- 方法合并:如果多个父接口中定义了签名相同的抽象方法,子接口会继承该方法,且只需要在实现类中实现一次。
- 默认方法冲突:如果多个父接口中有同名的
default方法,则子接口或实现类必须重写该方法以解决冲突,类似于类实现多个接口的情况。
接口的多继承让 Java 中的接口更加灵活,能够实现接口间的复用,同时避免了类多继承带来的复杂性。
相关冲突
默认方法冲突
说明:
在 Java 中,如果一个类继承了一个父类并且实现了一个接口,而该接口中提供了默认方法(default 方法),且父类中也有一个同名的方法,那么在子类中将会遇到多继承冲突的情况。为了避免冲突,你需要在子类中显式地重写该方法,并选择调用父类的方法或接口的默认方法。
示例代码:
1 | // 定义父类 |
解释:
Parent类中有一个sayHello()方法。MyInterface接口中提供了一个default的sayHello()方法。Child类同时继承了Parent类并实现了MyInterface接口,因此会产生方法冲突。- 在
Child类中,我们必须重写sayHello()方法,并在方法内部使用super.sayHello()调用父类的方法,或使用MyInterface.super.sayHello()来调用接口的默认方法。
在这个例子中,Child 类通过显式重写 sayHello() 解决了冲突问题,并可以选择调用父类方法或者接口中的默认方法。
接口冲突
说明:
当一个类同时实现了多个接口,而这些接口中包含了签名相同的默认方法时,会发生方法冲突。为了处理这个问题,Java 要求在子类中显式地重写该方法,决定要调用哪个接口的默认方法,或者提供自己的实现。
示例代码:
1 | // 定义第一个接口并提供默认方法 |
解释:
InterfaceA和InterfaceB都提供了签名相同的默认方法sayHello()。MyClass类同时实现了这两个接口,因此会产生冲突。- 在
MyClass中必须显式重写sayHello()方法,以解决接口中的冲突。- 可以选择调用
InterfaceA的默认方法:InterfaceA.super.sayHello()。 - 可以选择调用
InterfaceB的默认方法:InterfaceB.super.sayHello()。 - 也可以提供自己的实现,覆盖这两个接口的默认方法。
- 可以选择调用
这样,子类通过重写来明确选择调用哪个接口的默认方法,或者直接提供一个新的实现,避免了方法冲突。
常量冲突问题
说明:
当子类继承自父类且实现多个接口,而父类中有一个与接口中常量同名的成员变量时,可能会产生命名冲突。在这种情况下,由于接口中的常量是 public static final 的,并且父类的成员变量也在子类中可见,你需要显式地使用接口名或父类名来访问它们,以区分冲突的成员。
代码示例:
1 | // 定义父类,包含一个与接口常量同名的成员变量 |
解释:
Parent类中有一个成员变量NAME。InterfaceA和InterfaceB中都有一个名为NAME的常量。- 在
Child类中,这些同名的变量和常量会产生冲突,因此需要显式地使用各自的上下文来区分它们。- 使用
super.NAME访问父类Parent中的成员变量NAME。 - 使用
InterfaceA.NAME访问接口InterfaceA中的常量NAME。 - 使用
InterfaceB.NAME访问接口InterfaceB中的常量NAME。
- 使用
这种方式解决了成员变量和接口常量同名导致的冲突问题。通过明确指明使用的是哪个上下文中的 NAME,避免了歧义。
接口体现多态性
- 接口定义统一的行为:接口定义了一组方法,而具体实现类根据自己的特性来实现这些方法。
- 使用接口类型引用对象:通过接口类型来引用不同的实现类对象,利用接口引用来调用方法,而实际执行的是引用对象的实现方法。
- 接口引用的多态性:接口引用可以指向任何实现了该接口的类对象,这样调用方法时,可以根据引用指向的对象类型来决定执行哪个具体的实现方法。
定义接口:
1 | // 定义接口 |
不同类实现相同的接口:
1 | // 实现接口的类1:Dog |
使用接口类型实现多态性:
1 | public class Main { |
接口与抽象类的对比
| 特性 | 接口 (Interface) | 抽象类 (Abstract Class) |
|---|---|---|
| 定义 | 定义行为契约,方法没有实现(除非是 default或 static方法) | 可以包含部分实现的方法,也可以包含抽象方法 |
| 关键字 | interface | abstract class |
| 多重继承 | 一个类可以实现多个接口 | 一个类只能继承一个抽象类 |
| 方法类型 | 只能有抽象方法、default方法和 static方法 | 可以有抽象方法、具体方法、static方法和 final方法 |
| 变量 | 只能包含 public static final常量 | 可以包含任何类型的变量(static、final、实例变量等) |
| 构造函数 | 没有构造函数 | 可以有构造函数 |
| 访问修饰符 | 接口中的方法默认是 public,不能有其他访问修饰符 | 抽象类中的方法可以有任何访问修饰符(public、protected、private) |
| 实现约束 | 实现类必须实现接口中所有的抽象方法 | 子类可以选择实现抽象类中的部分或全部抽象方法 |
| 使用场景 | 用于定义一组行为,由多个类实现相同的行为规范 | 用于表示具有相似属性和行为的类的共有部分 |
| 默认方法 | 允许使用 default关键字提供默认方法实现 | 不支持默认方法(只能包含具体实现的方法) |
| 静态方法 | 允许使用 static关键字定义静态方法 | 支持静态方法 |
| 多态性 | 通过接口实现多态性,类可以实现多个接口 | 通过继承实现多态性,只能继承一个抽象类 |
| 适用性 | 更适合行为抽象(行为的契约) | 更适合类的概念抽象(部分实现和共同特征的封装) |
总结:
- 接口 更注重定义行为契约,允许类实现多个接口,从而支持多重继承,是更灵活的选择。
- 抽象类 更适合表示有相似属性和行为的类,支持部分实现,允许子类继承和扩展。
这两者的使用取决于具体需求,接口更适用于行为统一,而抽象类更适用于类的继承层次。
练习
匿名&多态
留意查看以下代码的四种不同写法、体现出接口使用的多态性
1 | package com.base8._interface.apply; |
排错 1
1 | interface A { |
解析:这里编译不通过,报错信息Reference to 'x' is ambiguous, both 'B.x' and 'A.x' match对 x 的引用不明确,类和接口是并列的结构,没有就近原则,编译器不知道是用接口 A 中的 x 还是用父类 B 中的 x,实际开发中一般不会遇到这种问题,命名的时候理论上不会相同。
修改后代码:
1 | interface A { |
排错2
1 | package com.base.learn; |
首先解释代码:先是定义了两个接口,并且都有 play 抽象方法,然后定义了 Rollable 接口去继承了这两个接口,同时自身 new 了一个类 Ball 的对象,其次 Ball 类又实现了 Rollable 接口,直接定义了一个属性并且写了 get 方法,还定义了一个构造器,然后实现了 play 方法。
考点1:这个实现的 paly 方法,到底是实现的那个接口的?
答:因为两个接口的抽象方法重名,所以相当于两个都实现了
考点2:上述代码中的错误之处在那儿?
答:因为在接口中定义的是全局常量,省略了 public static final,后面在 Ball 类中对 ball 常量做了修改操作,所以编译器报错。