SAM
概念
1. SAM(Single Abstract Method)
所有只有一个抽象方法的接口,不管是谁写的、加没加注解,都是 SAM。
不是接口,不是类型,是一个规则:
一个接口里只有一个抽象方法
满足这个规则,就叫 SAM 类型。
// Java 里天然 SAM
public interface Runnable {
void run(); // 只有这一个抽象方法
}
2. 函数式接口(Functional Interface)
就是满足 SAM 规则的接口
- Java 里:加
@FunctionalInterface标记 - Kotlin 里:用
fun interface声明
它们本质是接口,只是长得像函数。
// Kotlin 函数式接口 = SAM 接口
fun interface Calculator {
fun calc(a: Int, b: Int): Int
}
3. 函数类型(Function Type)
Kotlin 原生的“函数形状”类型,不是接口,是语言内置类型:
() -> Unit(Int) -> Stringsuspend () -> Result<Unit>
它是纯函数式的轻量类型,没有类、没有接口、没有方法。
极简记忆
- SAM:接口规则(单一抽象方法)
- 函数式接口:满足 SAM 的接口、被标记/命名为函数式的 SAM
- 函数类型:Kotlin 内置的函数签名类型,不是接口
- SAM 转换:Kotlin 把 lambda/函数类型 自动转成 函数式接口 的语法糖
- 所有单方法接口都符合 SAM,都能用 lambda 简化
一句话记忆:函数类型是原生函数,函数式接口是 SAM 接口,SAM 转换是它们之间的桥。
- 抽象方法数量 = 1 → 就是 SAM
- 有默认方法 / 静态方法 / 静态内部类 → 不影响 SAM
- 只要是 SAM,Kotlin 就可以用 lambda 直接 SAM 转换
- 带默认方法、静态方法的函数式接口,100% 符合 SAM。
SAM、函数类型 和 函数式接口 是什么关系?
一句话总关系
- SAM = 一种接口规则:只有一个抽象方法的接口
- 函数式接口 = 满足 SAM 规则的接口
- 函数类型 = Kotlin 原生的“函数签名类型”
- SAM 转换 = Kotlin 允许把 lambda/函数类型 自动转成 函数式接口
它们是三层关系 + 一座桥梁。
三者真正的关系(核心图)
SAM 规则
↓
函数式接口(满足 SAM 的接口)
↘
SAM 转换 ←—————— 桥梁
↗
函数类型 / lambda
关系拆解(非常关键)
1. 函数式接口 必须满足 SAM
- 函数式接口 = SAM 接口
- 不满足 SAM → 不能叫函数式接口
2. 函数类型 和 SAM 无关
函数类型是 Kotlin 内置类型,根本不是接口,所以和 SAM 没关系。
3. SAM 转换 = 把函数类型 / lambda → 自动变成 函数式接口
这就是 Kotlin 做的语法糖:
// 函数类型
val f: (Int, Int) -> Int = { a, b -> a + b }
// 函数式接口(SAM)
fun interface Calculator {
fun calc(a: Int, b: Int): Int
}
// SAM 转换:函数类型 → 自动转成接口实例
val c: Calculator = f
等价于:
val c = Calculator { a, b -> a + b }
除了函数式接口符合sam 还有哪些符合SAM?
先说结论: 只要是「只有一个抽象方法的接口」,不管是 Java 还是 Kotlin 写的,全都符合 SAM。 不一定要叫“函数式接口”,也不一定要加任何注解。
到底什么才算 SAM?
满足这两条就是 SAM 类型:
- 是接口(不是类、不是抽象类)
- 里面只有一个抽象方法
- 可以有默认方法、静态方法,不影响
- 可以不加
@FunctionalInterface、不加fun interface
所以:
- Java 里的普通单方法接口 = SAM
- Kotlin 普通单方法接口 = SAM(1.4+)
- 加了 @FunctionalInterface 的 Java 接口 = SAM
- 加了 fun interface 的 Kotlin 接口 = SAM
它们全都支持 SAM 转换。
常见符合 SAM 的接口(不全是“函数式接口”)
1. Java 标准库里大量普通接口
这些都不是特意标为函数式接口,但天然是 SAM:
RunnableCallable<V>ActionListenerComparator<T>FileFilterFilenameFilterInvocationHandler
示例:
// 普通 Java 接口,也是 SAM,可以直接 lambda
val r: Runnable = { println("hi") }
2. Android 里各种监听接口
全是 SAM,只是没人叫它们“函数式接口”:
View.OnClickListenerView.OnLongClickListenerTextWatcher(部分方法有默认实现后也算)RecyclerView.OnScrollListener
button.setOnClickListener { } // 典型 SAM 转换
3. 你自己写的普通单方法接口
不加 fun interface、不加任何注解,照样是 SAM:
// 普通 Kotlin 接口,只有一个抽象方法 → SAM
interface MyCallback {
fun onResult(data: String)
}
// 直接 lambda,自动 SAM 转换
val callback: MyCallback = { data -> println(data) }
注意: Kotlin 1.4+ 开始,普通单方法接口也支持 SAM 转换, 不再必须写
fun interface。
哪些符合【SAM 规则】?
所有 只有 1 个抽象方法 的接口 不管是 Kotlin / Java 不管有没有注解 不管叫不叫函数式接口 只要 = 1 个抽象方法 → 就是 SAM
哪些是【函数式接口】?
只有 2 种:
- Kotlin:用
fun interface定义的接口 - Java:加了
@FunctionalInterface注解的接口
除了 FunctionN,还有哪些是【函数式接口】?
1. Java 标准库自带(大量!)
@FunctionalInterface
public interface Runnable { void run(); }
@FunctionalInterface
public interface Consumer<T> { void accept(T t); }
@FunctionalInterface
public interface Predicate<T> { boolean test(T t); }
@FunctionalInterface
public interface Function<T,R> { R apply(T t); }
@FunctionalInterface
public interface Supplier<T> { T get(); }
@FunctionalInterface
public interface Comparator<T> { int compare(T a,T b); }
✅ 这些全部是函数式接口 ✅ 全部是 SAM ✅ 全部支持 lambda
2. Kotlin 你自己写的(fun interface)
fun interface MyClick { fun onClick() }
fun interface MyCallback { fun onResult(data: String) }
✅ 只要加 fun interface → 自动 = 函数式接口
3. Kotlin 内置 FunctionN(你已经知道)
Function0
Function1
Function2
...
✅ 也是函数式接口
除了函数式接口,还有哪些符合 SAM 规则?
超级重点:
SAM ≠ 函数式接口SAM 范围更大!
只要接口里 只有 1 个抽象方法,就算没加注解、没加 fun,依然是 SAM!
1. Java 普通单方法接口(无注解)
public interface MyTask { void execute(); }
✅ 是 SAM ❌ 不是函数式接口(没加 @FunctionalInterface)
2. Android 里大量监听(全是 SAM)
public interface OnClickListener { void onClick(View v); }
public interface OnLongClickListener { boolean onLongClick(View v); }
✅ 全是 SAM ❌ 都不是函数式接口
3. Kotlin 普通单方法接口(不加 fun)
interface MyAction { fun doAction() }
✅ 是 SAM(1个抽象方法) ❌ 不是函数式接口(没有 fun interface)
SAM和函数式接口最清晰分类(背这个就够)
【函数式接口】(小圈)
fun interface@FunctionalInterface- FunctionN
- Runnable / Consumer / Predicate / Function / Supplier
【SAM 接口】(大圈)
包含上面所有 + 下面这些:
- 任何只有 1 个抽象方法的 Java 接口
- 任何只有 1 个抽象方法的 Kotlin 普通接口
- Android 各种 Listener
- 你自己写的单方法接口
哪些不算 SAM?
- 有 两个或以上抽象方法 的接口
- 抽象类(哪怕只有一个抽象方法)
- 类、枚举、object
- Kotlin 函数类型
() -> Unit(它根本不是接口)
有默认方法、静态方法的函数式接口符合 SAM 的例子
SAM 只看:有且仅有一个抽象方法。 不管有多少默认方法、静态方法,都依然是 SAM,依然支持 SAM 转换。
1. Java 标准库自带的经典例子
① Comparator<T>(最经典)
抽象方法只有 1 个:compare(a, b) 但里面一堆默认方法、静态方法:
@FunctionalInterface
public interface Comparator<T> {
// 唯一抽象方法 ← 满足 SAM
int compare(T o1, T o2);
// 默认方法(不影响 SAM)
default Comparator<T> reversed() { ... }
default Comparator<T> thenComparing(...) { ... }
// 静态方法(也不影响)
static <T extends Comparable<? super T>> Comparator<T> naturalOrder() { ... }
static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) { ... }
}
在 Kotlin 里照样可以 SAM 转换:
val cmp: Comparator<Int> = { a, b -> a - b }
② Iterable 的迭代器相关不算,但看这个:Consumer<T>
@FunctionalInterface
public interface Consumer<T> {
// 唯一抽象方法
void accept(T t);
// 默认方法
default Consumer<T> andThen(Consumer<? super T> after) { ... }
}
依然是 SAM,依然能 lambda:
val c: Consumer<String> = { println(it) }
2. 你自己写一个:带默认 + 静态方法的 SAM 接口
Java 版本
@FunctionalInterface
public interface Worker {
// 唯一抽象方法 → SAM 核心
void doWork();
// 默认方法
default void rest() {
System.out.println("休息一下");
}
// 静态方法
static void start() {
System.out.println("开始工作");
}
}
在 Kotlin 中使用:
// SAM 转换成功
val worker: Worker = { println("干活") }
worker.doWork() // 自己实现的
worker.rest() // 默认方法
Worker.start() // 静态方法
3. Kotlin 版本:fun interface + 默认方法
Kotlin 也完全支持:
fun interface Calculator {
// 唯一抽象方法
fun calculate(a: Int, b: Int): Int
// 默认方法
fun addOne(a: Int, b: Int): Int {
return calculate(a, b) + 1
}
// 伴生对象里写静态方法
companion object {
fun create(): Calculator = Calculator { a, b -> a + b }
}
}
使用:
// SAM 转换
val cal: Calculator = { a, b -> a * b }
cal.calculate(2,3) // 6
cal.addOne(2,3) // 7
Calculator.create() // 静态方法
4. 再给一个 Android 里常见的
View.OnClickListener
public interface OnClickListener {
// 唯一抽象方法
void onClick(View v);
// 未来可能加默认方法,依然是 SAM
}
所以你才能写:
btn.setOnClickListener { v -> ... }
函数式接口 底层是什么?
在 Kotlin / JVM 底层,函数式接口(SAM)用 lambda 写出来,本质还是匿名内部类,但做了优化,不是每次都 new 一个新对象。
1. 先看最直观的结论
你写的 SAM 转换 lambda:
val runnable: Runnable = { println("test") }
底层等价于:
val runnable = object : Runnable {
override fun run() {
println("test")
}
}
所以: ✅ 函数式接口的 lambda,底层依然是匿名内部类实例 ✅ 它是一个真正的接口实现类对象 ✅ 不是函数类型,不是指针,不是特殊魔法
2. 但有一个关键优化:不会每次都 new
普通匿名内部类:
// 每次调用都会 new 一个新对象
button.setOnClickListener { ... }
SAM lambda 在 Kotlin 里会被编译器优化:
- 如果 lambda 没有捕获外部变量(无闭包) → 编译器会生成单例,只创建一次,复用对象
- 如果 lambda 捕获了变量 → 每次都会 new 一个新实例(和匿名内部类一样)
这是和手写 object : Runnable 最大的区别。
3. 函数类型 () -> Unit 底层是什么?
这个和 SAM 完全不一样,必须区分:
函数类型 () -> Unit 底层是:
Function0<Unit>
它是 JVM 上的一个真实类,Kotlin 内置的:
public interface Function0<out R> {
R invoke();
}
所以:
- 函数类型 = FunctionN 接口的实例
- SAM 接口 = 你自己定义的接口的实例
但它们最终都是对象,只是接口不同。
4. 一张表彻底分清
| 写法 | 底层是什么 | 是否每次 new 对象? |
|---|---|---|
手写匿名内部类object : Runnable | 匿名内部类 | 每次都 new |
SAM 转换 lambdaRunnable = { ... } | 同样是 Runnable 实现类 | 无捕获 → 单例(只创建1次) 有捕获 → 每次 new |
函数类型 lambda() -> Unit = { ... } | Function0 实例 | 无捕获 → 单例(只创建1次) 有捕获 → 每次 new |
5. 最精炼总结(背这个)
- 函数式接口(SAM)的 lambda,底层仍然是匿名内部类对象
- 它是接口的实现实例,不是函数,不是特殊类型
- 编译器会优化:不捕获变量时复用单例,捕获变量时才每次新建
- 函数类型
() -> Unit底层是FunctionN,和 SAM 不是同一个东西
一句话: SAM lambda 看着像函数,跑起来还是个接口对象。
如果你愿意,我可以给你看一段 反编译后的 Java 代码,让你亲眼看到它到底变成了什么类。
FunctionN到底是函数类型 还是 函数式接口?
- FunctionN 是 Kotlin 内置的函数式接口(SAM 接口)
- 函数类型
() -> R、(T) -> R是 Kotlin 语法层面的写法 - 编译到 JVM 字节码时,函数类型 会变成 FunctionN 接口
它们是语法层面 vs 字节码层面的关系,不是两个东西。
1. 先看 FunctionN 到底是什么
打开 Kotlin 标准库源码会看到:
// Function0.java
public interface Function0<out R> {
R invoke();
}
// Function1.java
public interface Function1<in T, out R> {
R invoke(T t);
}
很明显:
- 是接口
- 只有一个抽象方法
invoke() - 满足 SAM 规则
- 是标准的函数式接口
所以: ✅ FunctionN 是函数式接口(SAM)
2. 函数类型是什么?
函数类型是 Kotlin 语言层面的语法糖:
() -> Unit→ 对应Function0<Unit>(Int) -> String→ 对应Function1<Int, String>(A, B) -> C→ 对应Function2<A, B, C>
它不是真实存在的 JVM 类型,编译后一律变成 FunctionN 接口。
3. 终极关系(必须背)
- FunctionN = JVM 字节码层面的函数式接口(SAM)
- 函数类型
(T) -> R= Kotlin 语法层面的别名 - lambda 表达式 → 编译后就是 FunctionN 的实现类
也就是说:
函数类型 在底层 就是 FunctionN 这个函数式接口。
4. 用一句最通俗的话总结
你写:
val f: (Int) -> String = { it.toString() }编译器心里想:
Function1<Integer, String> f = ...;
所以:
- FunctionN 是函数式接口
- 函数类型是它的语法糖
- 本质是同一个东西
5. 结论
FunctionN 到底是函数类型 还是 函数式接口?
标准答案:
FunctionN 是函数式接口(SAM 接口); 函数类型是 Kotlin 为 FunctionN 设计的语法表示。 二者本质等价,只是层级不同。
函数式接口和函数类型有什么区别?
函数式接口 = 你自己定义的接口(SAM)函数类型 = Kotlin 内置的通用函数形状(语法糖)
1. 先看最直观的对比
1. 函数式接口(你写的)
fun interface Click {
fun onClick()
}
- 是接口
- 有名字:
Click - 有方法名:
onClick() - 是SAM
- 必须通过 SAM 转换 变成 lambda
2. 函数类型(Kotlin 自带的)
() -> Unit
- 是Kotlin 语法
- 没有名字
- 没有方法名,只有一个固定的
invoke() - 底层是
Function0接口 - 直接用 lambda,不需要转换
2. 5 个核心区别(背这个就够)
1. 本质不同
- 函数式接口 = 接口(面向对象)
- 函数类型 = 语言内置类型(函数式)
2. 来源不同
- 函数式接口:你自己定义
- 函数类型:Kotlin 天生自带
3. 方法名不同
- 函数式接口:方法名随便起(onClick / call / run)
- 函数类型:方法名固定叫
invoke()
4. 使用方式不同
- 函数式接口:需要 SAM 转换
- 函数类型:直接用 lambda
5. 用途不同
- 函数式接口:用于 Java 互操作、自定义语义
- 函数类型:用于 Kotlin 内部简洁传函数
3. 最关键的底层真相(超级重要)
函数类型 底层 = FunctionN 接口 = 也是函数式接口!
也就是说:
函数类型 本质上就是 Kotlin 官方给你预定义好的函数式接口
写:
() -> Unit
底层就是:
public interface Function0 {
void invoke();
}
4. 那为什么还要分两个?
因为:
函数式接口 = 自定义、有名字、有业务含义
函数类型 = 通用、简洁、无名字、Kotlin 专属
例子:
Click→ 表示点击(有含义)() -> Unit→ 表示“一个无参函数”(无含义)
函数类型 是不是 SAM?
不是。 SAM 是接口规则,函数类型是原生类型,完全不是一类东西。
函数式接口 是不是 SAM?
是。 函数式接口 = SAM 接口。
lambda 是函数类型还是函数式接口?
lambda 本身无类型,但:
- 赋值给
() -> Unit→ 变成函数类型 - 赋值给
Runnable→ 通过 SAM 转换 变成函数式接口实例
用 fun interface 这个定义的就是函数式接口?
对!100% 正确!
在 Kotlin 里:
只要用 fun interface 定义的接口 = 一定是 函数式接口
一定符合 SAM = 一定支持 SAM 转换(lambda 直接用)
1. 最核心规则(背下来)
fun interface 接口名 {
// 必须有且只有一个抽象方法
}
满足这个写法 → 自动成为:
- SAM 接口(单抽象方法)
- 函数式接口
- 支持 lambda 直接赋值(SAM 转换)
2. 例子一看就懂
✅ 正确:函数式接口
fun interface MyAction {
fun run() // 只有一个抽象方法
}
// 直接用 lambda ✅
val action: MyAction = { println("run") }
❌ 错误:编译器直接报错
fun interface MyAction {
fun run()
fun test() // 两个抽象方法 → 编译失败
}
fun interface 强制你只能有一个抽象方法 这就是它的作用。
3. 和普通接口的区别
| 写法 | 是不是函数式接口 | 能不能用 lambda |
|---|---|---|
fun interface | ✅ 是 | ✅ 可以 |
interface | ❌ 不是 | Kotlin 1.4+ 也可以,但不标准 |
4. 最终结论(最精简)
在 Kotlin 中:
fun interface = 函数式接口 = SAM 接口
一定可以用 lambda!
SAM 是一个抽象函数 那两个以上的抽象函数叫什么?
- 只有 1 个抽象方法 → SAM 接口(Single Abstract Method)
- ≥ 2 个抽象方法 → 就叫普通接口 / 多方法接口没有专门的英文缩写名字
简单对应关系
| 抽象方法数量 | 叫什么 | 支持 Kotlin SAM 转换(lambda) |
|---|---|---|
| 1 个 | SAM 接口 | ✅ 支持 |
| 2 个及以上 | 普通接口 | ❌ 不支持 |
举个例子
1 个抽象方法 → SAM
interface A {
fun run() // 只有1个 → SAM
}
2 个抽象方法 → 普通接口(没有特殊名字)
interface B {
fun run()
fun cancel() // 2个 → 就是普通接口
}
这种接口不能用 lambda,只能老老实实写匿名内部类:
// 报错!不能 SAM 转换
val b: B = { ... }
// 只能这样写
val b = object : B {
override fun run() {}
override fun cancel() {}
}
超简总结
- SAM = 单抽象方法接口
- 多抽象方法接口 = 普通接口
- 没有类似 SAM 这种专用简称
SAM 存在的意义 函数式接口的意义 函数类型的意义?
先前置定义,防止混淆:
- SAM:是规则 —— 接口仅有一个抽象方法(语法结构)
- 函数式接口:是被标记过的SAM —— Java:
@FunctionalInterface,Kotlin:fun interface - 函数类型:Kotlin原生语法
() -> Unit,底层是FunctionN
1. SAM 的存在意义(最底层规则)
一句话: SAM 就是为了让「接口」能用 Lambda。
详细解释
Java 早期没有Lambda,只能写匿名内部类; Java8 想加 Lambda,但不能改JVM,不能新增语法,只能复用现有接口。 发现:
只要接口只有一个抽象方法,编译器就能看懂你Lambda想实现哪个方法。
这就是 SAM 的初衷:
- 不用新增JVM类型,兼容老代码
- 单一抽象方法 → Lambda能唯一绑定这个方法
- 提供语法简化,干掉冗长匿名内部类
- Android各种点击监听、
Runnable全部能简化
核心
SAM = Lambda能简写接口的前置条件。 没有SAM,就没有Java/Kotlin的Lambda简写接口。
2. 函数式接口 的存在意义(给SAM加约束+语义)
SAM是天生结构;函数式接口是人为规范好的SAM,安全、有语义、可扩展、兼容Java。
SAM只是客观结构(碰巧只有一个抽象方法); 函数式接口是人为定义、强制约束的SAM。
1. 强制语法校验
Java @FunctionalInterface、Kotlin fun interface:
- 你写两个抽象方法 → 编译直接报错
- 防止后期维护不小心多加方法,破坏SAM结构,Lambda全部失效
2. 赋予业务语义
fun interface OnClickListener { fun onClick(v:View) }
接口名有含义:点击事件。 对比裸的 (View)->Unit,可读性极强,适合Java调用、回调、Android接口设计。
3. 支持默认方法、静态方法
可以扩展能力,比如 Comparator 一堆默认方法,依然保持SAM。
3. 函数类型 的存在意义(Kotlin纯函数式设计)
一句话: 摆脱Java接口束缚,提供原生、极简、专门用来传递函数的类型。 写法:(Int,String)->Boolean
存在目的
- 不需要定义接口,直接描述函数签名,极简
- Kotlin高阶函数底层依赖它,集合
map/filter全部靠它 - 专属于Kotlin,天然支持suspend挂起函数(Java做不到)
- 不用SAM转换,原生就是函数,写法最简单
底层:编译成FunctionN(也是SAM接口)
函数类型 = Kotlin官方预制好的一批函数式接口,语法简化给你用。
区别直白版
- 函数式接口:有名字、有自定义方法、给Java交互用
- 函数类型:无名字、固定invoke、纯Kotlin内部用
4. 三层层级关系 + 各自意义汇总
SAM 意义:规则。保证Lambda可以映射到接口唯一抽象方法,Lambda语法可行的基础。
函数式接口 意义:规范后的SAM。编译校验、业务语义、可扩展默认方法、负责Kotlin与Java互操作。
函数类型 意义:Kotlin原生函数类型。极简、不用定义接口、高阶函数核心、纯Kotlin内部使用。
函数类型有简化成lambda的形式么?
有,而且函数类型本来就是为 lambda 服务的。 一句话: 函数类型 = 类型;lambda = 这个类型的具体值 / 写法。
它们天生就是一对,不需要 SAM 转换,直接就能用。
函数类型完全可以、也经常简化成 lambda 表达式。
比如:
函数类型:
() -> Unit对应 lambda:
{ }函数类型:
(Int) -> String对应 lambda:
{ num -> num.toString() }
这是 Kotlin 最原生、最自然、最常用的写法, 不需要任何接口,不需要 SAM,直接写就行。
对比一看就懂
① 函数式接口(需要 SAM 转换)
fun interface Callback {
fun call()
}
// 这里是 SAM 转换
val cb: Callback = { println("hi") }
② 函数类型(直接就是 lambda,天然支持)
// 函数类型 ← 直接赋值 lambda
val f: () -> Unit = { println("hi") }
没有任何区别,只是函数类型更简洁,不需要定义接口。
高阶函数里最常见(你天天都在用)
list.map { it * 2 }
这里:
map接收的参数类型是 函数类型(T) -> R- 你传的
{ it * 2 }就是 lambda
这就是函数类型简化为 lambda 的最典型例子。
关键区别(一定要记住)
- 函数式接口用 lambda:需要走 SAM 转换
- 函数类型用 lambda:直接就是它本身,不需要转换
函数类型不仅可以简化成 lambda, 而且 lambda 本来就是函数类型最标准、最简洁的写法。
- 函数类型 = 杯子
- lambda = 杯子里装的水
它们天生配套,不用任何中间层。
Java匿名内部类 → SAM → Lambda → 函数式接口 → 函数类型演进图?
1. Java 早期
匿名内部类(又臭又长)
↓
2. Java 发现规律
单方法接口 = SAM(只是结构,没名字)
↓
3. Java 8
给 SAM 起名字:@FunctionalInterface 函数式接口
→ 支持 Lambda 简写(SAM 转换诞生)
↓
4. Kotlin 出现
不想每次都定义接口,太麻烦
→ 直接内置一套函数类型:() -> Unit、(T) -> R …
↓
5. 最终形态
函数类型 = 自带 Lambda,不用接口、不用转换
1. Java 匿名内部类
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 巨长
}
});
问题:太啰嗦。
2. 人们发现:很多接口只有一个抽象方法
→ 给这种结构起名:SAM(Single Abstract Method)
SAM 不是技术,是规律。
3. Java 8 利用 SAM 规则
推出:
@FunctionalInterface(函数式接口)- 允许用 Lambda 代替匿名内部类 → 这就是 SAM 转换
button.setOnClickListener(v -> { });
意义:终于能简写了。
4. Kotlin 觉得还不够
每次用函数都要定义接口太烦 → 直接内置函数类型: () -> Unit、(Int) -> String、(A,B) -> C
底层还是 FunctionN 这种函数式接口,但用户不用关心。
5. 最终结果
val f: () -> Unit = { }
不用接口、不用SAM转换、直接Lambda。
这就是现代 Kotlin 最简洁形态。
匿名内部类 → 发现 SAM 规则 → 诞生函数式接口 → 支持 Lambda → Kotlin 再简化 → 函数类型(自带 Lambda,终极形态)
如果你需要,我可以再给你画一张三者关系闭环图: SAM ↔ 函数式接口 ↔ 函数类型,彻底闭环不混淆。
1. SAM 规则
- 只有一个抽象方法的接口
- 是最底层基础
- 决定了能不能用 Lambda
2. 函数式接口
- 是遵守 SAM 规则的接口
- Kotlin:
fun interface - Java:
@FunctionalInterface - 有名字、有语义、可扩展默认方法
3. FunctionN 接口
Function0/Function1…- 是 Kotlin 内置的函数式接口
- 也是 SAM
- 是函数类型的底层真身
4. 函数类型
() -> Unit、(T) -> R- 是 Kotlin 给 FunctionN 做的语法糖
- 最简洁、最易用
- 不需要定义接口
5. Lambda
- 是值 / 写法
- 既能赋值给 函数类型
- 也能通过 SAM 转换 赋值给 函数式接口
lambda能赋值给函数式接口和SAM么?
能!完全可以
而且这就是 SAM 转换 干的唯一一件事。
lambda 可以直接赋值给函数式接口, 前提是:函数式接口是 SAM(单抽象方法)。
lambda 能赋值给 → 所有 SAM 接口 SAM 接口 = 能接收 lambda 的接口
lambda 既能赋值给 SAM,也能赋值给函数式接口,也能赋值给函数类型。
最直观例子
1. 定义一个函数式接口(Kotlin)
fun interface Action {
fun doIt()
}
2. lambda 直接赋值给它 ✅
val act: Action = {
println("我是 lambda,被赋值给了函数式接口")
}
3. 调用
act.doIt()
这就叫 SAM 转换。
为什么可以?
因为:
- 函数式接口 只有一个抽象方法
- 编译器知道 lambda 就是在实现这个方法
- 自动帮你生成匿名内部类
再给你一个 Java 接口的例子(更常见)
val runnable: Runnable = { println("运行") }
Runnable 是 Java 的函数式接口, lambda 照样能直接赋值。
反过来:函数类型不能赋值给随便一个接口
val f: () -> Unit = { ... }
// 下面这句会报错!
val act: Action = f
函数类型 不能直接赋值 给自定义函数式接口, 但 lambda 可以。
lambda → 可以直接赋值给 函数式接口(SAM 转换) 函数类型 → 不能直接赋值给 函数式接口