1. 概述
在 Kotlin 开发中,我们时常需要将多个值组合在一起返回或传递。虽然 Pair
可以处理两个元素的场景,但当我们需要携带 三个相关值 时,Triple
就显得尤为优雅和实用。
本文将深入探讨 Kotlin 标准库中的 Triple
类型,帮助你理解它的设计、用法以及如何在实际项目中灵活运用,避免“造轮子”踩坑。
2. Triple 类解析
Kotlin 的 Pair
适用于成对数据,而 Triple
正如其名,是 Pair
的三元扩展——它能容纳三个不同类型的值。
查看源码定义:
public data class Triple<out A, out B, out C>(
public val first: A,
public val second: B,
public val third: C
) : Serializable
关键特性如下:
✅ data class:自动生成 equals()
、hashCode()
、toString()
和 copy()
方法
✅ 不可变(immutable):所有属性均为 val
,线程安全,适合函数式编程风格
✅ 泛型支持:可容纳不同类型,如 Triple<Int, String, Long>
❌ 无法继承:Triple
是 final 类,不能被继承或扩展为子类
创建与解构
创建一个 Triple
非常直观:
val triple = Triple(42, "Kotlin", Long.MAX_VALUE)
更酷的是支持解构声明(destructuring declarations),让代码更清晰:
val (a, b, c) = Triple(42, "Kotlin", Long.MAX_VALUE)
assertTrue { a is Int }
assertEquals(42, a)
assertTrue { b is String }
assertEquals("Kotlin", b)
assertTrue { c is Long }
assertEquals(Long.MAX_VALUE, c)
这种写法在遍历集合或函数返回多值时特别好用,比封装 POJO 轻量得多。
3. toString()、toList() 与自定义扩展函数
作为 data class
,Triple
天然拥有标准方法支持。但 Kotlin 还为其定制了一些便捷功能。
✅ 重写的 toString()
默认的 toString()
输出格式为 (first, second, third)
,语义清晰:
val t = Triple("A", "B", "C")
assertEquals("(A, B, C)", t.toString())
推荐使用字符串模板提升可读性:
assertEquals("(A, B, C)", "$t") // 更简洁
✅ 内置扩展 toList()
当三个元素类型相同时,可通过 toList()
转换为 List<T>
:
val t = Triple("Java", "Kotlin", "Python")
assertEquals(listOf("Java", "Kotlin", "Python"), t.toList())
⚠️ 注意:该方法仅适用于同类型元素,即 Triple<T, T, T>
,否则编译报错。
✅ 自定义扩展函数
我们可以为 Triple
添加扩展函数来增强实用性。例如实现元素反转:
fun <T> Triple<T, T, T>.reverse() = Triple(third, second, first)
// 使用示例
val t = Triple("x", "y", "z")
assertEquals(Triple("z", "y", "x"), t.reverse())
这体现了 Kotlin 扩展函数的强大——无需修改源码即可增加行为。
4. 实际应用场景示例
理论不如实战。下面我们通过一个真实感十足的例子展示 Triple
的价值。
场景设定
假设我们要构建一个简单的比赛匹配系统,每场比赛包含:
- 两名玩家(Player)
- 开始时间(String)
先定义 Player
类:
data class Player(
val name: String,
var score: Int
)
初始化几位选手:
val kent = Player("Kent", 42)
val eric = Player("Eric", 42)
val tom = Player("Tom", 20)
val john = Player("John", 32)
使用 Triple 表达 Match
若只为临时传递这三个字段,专门建一个 Match
类显得过于笨重。此时 Triple
是轻量级优选:
val matchesTomorrow = listOf(
Triple(kent, tom, "9:00"),
Triple(eric, john, "10:00"),
Triple(kent, eric, "17:00"),
Triple(tom, john, "18:00"),
)
查询某时段的比赛也很简单:
val matchAt10 = matchesTomorrow.first { it.third == "10:00" }
assertEquals(eric, matchAt10.first)
assertEquals(john, matchAt10.second)
提升可读性:使用 typealias
直接用 Triple<Player, Player, String>
可读性差。我们可以通过类型别名优化:
typealias Match = Triple<Player, Player, String>
重构后代码立刻变得语义明确:
val matchesTomorrow2 = listOf(
Match(kent, tom, "9:00"),
Match(eric, john, "10:00"),
Match(kent, eric, "17:00"),
Match(tom, john, "18:00"),
)
val matchAt17 = matchesTomorrow2.first { it.third == "17:00" }
assertEquals(kent, matchAt17.first)
assertEquals(eric, matchAt17.second)
增强功能:扩展 Match 类型
既然 Match
是 Triple
的别名,我们仍可为其添加扩展函数:
fun Match.firstWin() = apply {
first.score++
second.score--
}
fun Match.secondWin() = apply {
first.score--
second.score++
}
调用时如同原生方法般自然:
matchAt17.secondWin()
assertEquals(41, matchAt17.first.score)
assertEquals(43, matchAt17.second.score)
✅ 小结:通过 typealias + extension function
,我们既保持了轻量结构,又获得了类级别的行为封装能力,这才是 Kotlin 的精髓所在。
5. 总结
Triple
是 Kotlin 中一个看似简单却极具实用价值的工具类,适用于:
- 函数需返回三个关联值的场景
- 临时数据聚合(避免创建额外类)
- 结合
typealias
和扩展函数模拟轻量 DTO
但它也有局限:
- ❌ 不适合超过三个字段的情况(建议改用 data class)
- ❌ 元素访问依赖
first/second/third
,语义不如命名字段清晰(可通过 typealias 缓解)
合理使用 Triple
,可以让你的代码更简洁、更具表达力,尤其是在快速原型开发或内部逻辑流转中大放异彩。