作为一个 Android 开发初学者,每当别人提到“接口”这个词时,我内心总会毫不在意地想:“接口?不就是 implements 一下就完事了吗?”然而,真正开始做项目后才发现,原来接口的使用远不止定义几个方法那么简单。很多关于接口的应用技巧和设计思想,在实际开发中常常被我忽略。你是否也曾遇到过类似的情况呢?让我们一起来深入探讨一下吧!
一.通过接口使用实现类的对象
通过接口使用实现类的对象体现的是多态的理念,他的核心是面向接口编程.实现类必须实现接口中定义的所有抽象方法(除非该类是抽象类).
-
基本语法结构
接口名 变量名 = new 实现类构造器(); List<String> list = new ArrayList<>(); list.add("小明");
这行代码的意思是:
- 左边:接口 List —— 表示你“只关心行为”(比如添加、删除、查找等列表操作).
- 右边:实现类 ArrayList —— 提供具体的行为实现.
- 变量名 list —— 是你手中握住的“遥控器”,通过变量名你可以使用对应的方法.
这就是通过接口来使用实现类对象的经典方式.
-
设计思想
编程时面向接口,而非面向实现。
松耦合:更容易替换实现类
可扩展:可以随时替换为
LinkedList
、CopyOnWriteArrayList
等实现易测试:Mock 或 stub 方便替换实现
更清晰的 API 设计:只暴露该暴露的能力
-
自定义接口
// 1. 定义接口 public interface Animal { void makeSound(); } // 2. 实现类 public class Dog implements Animal { public void makeSound() { System.out.println("汪汪!"); } } // 3. 使用接口引用实现类对象 public class Test { public static void main(String[] args) { Animal a = new Dog(); // 接口引用指向实现类 a.makeSound(); // 输出:汪汪! } }
虽然我们拿的是
Animal
类型的遥控器,但背后控制的是Dog
这台机器。
二.接口作为参数或返回值
“接口作为参数或返回值”"是 Android 和 Java/Kotlin 编程中非常关键的一项技能,尤其是在 回调机制、事件处理、模块解耦 等场景中应用极广。
核心概念:
类型 | 说明 |
---|---|
作为参数 | 把某个接口实例传入函数中,用来回调或配置行为 |
作为返回值 | 函数返回一个接口实例,可以由调用者接收并使用 |
一、接口作为参数(监听按钮点击后执行某个回调逻辑)
定义一个接口:
interface OnConfirmListener {
fun onConfirm()
}
函数接受这个接口作为参数:
fun showDialog(listener: OnConfirmListener) {
// 模拟用户点击确认按钮
println("用户点击了确认按钮")
listener.onConfirm()
}
listener.onConfirm()
: 这是关键所在。showDialog
函数通过其接收到的 listener
参数(类型是接口),调用了接口中定义的 onConfirm()
方法。此时,showDialog
函数并不知道 listener
参数具体是哪个类的实例,它只知道这个实例有一个 onConfirm()
方法可以被调用。这正是多态的体现。
使用接口对象传递行为:
showDialog(object : OnConfirmListener {
override fun onConfirm() {
println("执行确认操作,比如提交表单")
}
})
showDialog(...)
: 这里调用了 showDialog
函数。
object : OnConfirmListener { ... }
: 这是 Kotlin 中的匿名对象 (Anonymous Object)。它是一个临时的、只使用一次的类实例,并且直接实现了 OnConfirmListener
接口。
override fun onConfirm() { ... }
: 在匿名对象内部,我们提供了 onConfirm()
方法的具体实现,也就是当确认事件发生时,我们希望执行的逻辑(例如,“执行确认操作,比如提交表单”)。
目的: 通过这种方式,我们将一个“行为”(onConfirm()
的实现)作为参数传递给了 showDialog
函数。showDialog
在合适的时机(模拟用户点击确认后)会“回调”这个行为。
Kotlin 中还可以使用 Lambda 简化(如果是 SAM 接口):
fun showDialog(onConfirm: () -> Unit) {
println("用户点击了确认按钮")
onConfirm()
}
showDialog {
println("Lambda方式的确认逻辑")
}
fun showDialog(onConfirm: () -> Unit)
: 这是 Kotlin 的一个强大特性:如果一个接口只包含一个抽象方法(称为 SAM - Single Abstract Method 接口),Kotlin 允许你使用 Lambda 表达式 来简化它的实现。这里的 onConfirm: () -> Unit
表示 onConfirm
是一个函数类型参数,它接受零个参数并返回 Unit
(相当于 Java 中的 void
)。
onConfirm()
: 调用传入的 Lambda 表达式。
showDialog { println("Lambda方式的确认逻辑") }
: 这是 Lambda 表达式的调用方式。它提供了一种更简洁、更具函数式编程风格的方式来实现回调。当函数类型参数是最后一个参数时,Kotlin 允许将 Lambda 放在圆括号外部(尾随 Lambda)。
二、接口作为返回值(你希望从某个类中获取“策略”或“处理逻辑”)
定义接口:
interface DataProcessor {
fun process(data: String)
}
函数返回接口对象:
fun getProcessor(): DataProcessor {
return object : DataProcessor {
override fun process(data: String) {
println("处理数据:$data")
}
}
}
fun getProcessor(): DataProcessor
: 定义了一个名为 getProcessor
的函数,它声明返回一个 DataProcessor
类型的对象。
return object : DataProcessor { ... }
: 这里,getProcessor
函数返回了一个匿名对象,这个匿名对象实现了 DataProcessor
接口。
override fun process(data: String) { println("处理数据:$data") }
: 这是匿名对象内部对 process
方法的具体实现。这个实现就是 getProcessor
函数所提供的“处理逻辑”。
调用方使用返回的接口对象:
val processor = getProcessor()
processor.process("Hello from interface!")
val processor = getProcessor()
: 调用 getProcessor()
函数,并将返回的 DataProcessor
接口对象赋值给 processor
变量。注意,processor
变量的静态类型是 DataProcessor
接口类型。
processor.process("Hello from interface!")
: 通过 processor
接口变量,调用了它所引用的实现类对象中的 process
方法。此时,执行的是 getProcessor
函数内部匿名对象里定义的 process
逻辑。
三.接口的默认方法与函数式接口
你问到了 Java 接口中两个非常现代且实用的特性:默认方法(default methods) 与 函数式接口(functional interfaces)。这两个概念如同接口的“双翼”,一个重在扩展性,一个重在简洁性,帮助接口在面向对象与函数式编程之间架起桥梁。
一、接口的默认方法(Default Method)
定义
Java 8 开始,接口中允许定义带有默认实现的方法,用 default
关键字修饰。
public interface Animal {
void makeSound();
// 默认方法:可以被实现类继承或重写
default void breathe() {
System.out.println("呼吸空气...");
}
}
示例
public class Dog implements Animal {
public void makeSound() {
System.out.println("汪汪!");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.makeSound(); // 汪汪!
a.breathe(); // 呼吸空气...
}
}
二、函数式接口(Functional Interface)
定义
函数式接口是只包含一个抽象方法的接口,适合用于 Lambda 表达式。
使用 @FunctionalInterface
注解可明确声明该意图:
@FunctionalInterface
public interface Calculator {
int calc(int a, int b); // 只允许一个抽象方法
default void printResult(int result) {
System.out.println("结果是:" + result);
}
}
示例:使用 Lambda 表达式
public class Main {
public static void main(String[] args) {
// 使用 Lambda 表达式实现函数式接口
Calculator add = (a, b) -> a + b;
Calculator mul = (a, b) -> a * b;
int r1 = add.calc(2, 3);
int r2 = mul.calc(4, 5);
add.printResult(r1); // 结果是:5
mul.printResult(r2); // 结果是:20
}
}
特性 | 接口默认方法 | 函数式接口(Functional Interface) | 接口多态(通过接口使用实现类的对象) | 接口作为参数或返回值 |
---|---|---|---|---|
出现版本 | Java 8 | Java 8 | Java 基础特性 | Java 基础特性 |
方法数量 | 可有多个方法(含默认实现) | 只能有一个抽象方法 | 接口中定义的所有抽象方法 | 接口中定义的抽象方法(可含默认方法) |
使用关键字 | default | @FunctionalInterface (建议加) | interface 、implements | interface + 参数/返回类型为接口 |
可否有默认方法 | ✅ 可以 | ✅ 可以 | ✅ 可以(Java 8 起) | ✅ 可以(Java 8 起) |
使用优势 | 向后兼容,减少重复逻辑 | 适配 Lambda,简洁表达函数逻辑 | 解耦结构、支持多态、便于扩展与维护 | 增强灵活性、支持回调、适配不同实现类 |
使用场景 | 接口升级、代码复用 | Lambda 表达式、方法引用、回调、流式操作等 | 多态调用、策略模式、面向接口设计、依赖注入等 | 回调机制、策略模式、工厂方法、依赖注入等 |
表现形式 | 在接口中用 default 修饰方法 | 用 Lambda 表达式实现:(a, b) -> a + b | 接口名 obj = new 实现类(); | void func(接口名 obj) / 接口名 getObj() |
通过上面的内容,我们一起回顾了在 Android 实际项目中常见的三个接口使用方式和一些实用技巧。接口虽然简单,但灵活运用起来却大有学问。希望这些内容能在你今后的开发过程中带来启发和帮助!