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 classTriple 天然拥有标准方法支持。但 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 类型

既然 MatchTriple 的别名,我们仍可为其添加扩展函数:

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,可以让你的代码更简洁、更具表达力,尤其是在快速原型开发或内部逻辑流转中大放异彩。

示例完整源码见:GitHub - Baeldung/kotlin-tutorials


原始标题:Working With Triple in Kotlin