1. 概述

在编程中,我们有时需要把多个元素组合在一起,这些元素之间可能有关联,也可能没有。为了应对这种场景,可以使用元组(Tuple) —— 一种有序的对象集合,支持不同类型的数据。

之前我们介绍过一个 Java 的第三方库 javatuples,它提供了对元组的支持。而在 Scala 中,元组是语言内置的特性,属于不可变数据结构。

本文将带你了解如何在 Scala 中创建元组、访问其元素,并介绍一些常用的元组方法以及适用场景。

2. 创建元组

创建元组非常简单,不需要任何模板代码。只需要把元素用圆括号括起来即可:

val tuple: (String, Int) = ("Joe", 34)

上面这行代码创建了一个包含 StringInt 类型元素的元组。

✅ 也可以使用箭头语法创建二元组(Pair):

val stillTuple: (String, Int) = "Joe" -> 34

在 Scala 2 中,元组由一系列类实现:从 Tuple2Tuple22。通常我们不会直接使用这些类,但了解它们的存在有助于理解底层机制。

⚠️ 注意:Scala 2 中最多支持 22 个元素的元组。超过这个限制会编译报错。

✅ 在 Scala 3 中,这个限制被移除了,引入了 TupleXXL,可以支持超过 22 个元素的元组。

创建更多元素的元组也很简单:

val tuple3: (String, Int, Boolean) = ("Joe", 34, true)
val tuple4: (String, Int, Boolean, Char) = ("Joe", 34, true, 'A')

📌 注意:(String, Int, Boolean)Tuple3[String, Int, Boolean] 的语法糖。

3. 访问元组中的元素

访问元组元素有两种常见方式:

方式一:使用下标访问(通过 _1, _2 等)

val name = tuple._1 
val age = tuple._2

方式二(Scala 3 新增):像访问 List 或 Array 一样使用索引

val name = tuple(0)

方式三:使用模式匹配解构赋值

val (userName, userAge) = tuple

这会将元组的第一个元素赋值给 userName(类型为 String),第二个元素赋给 userAge(类型为 Int)。

⚠️ 如果你只关心部分元素,可以用下划线 _ 忽略其他值:

val (_, myAge) = tuple

4. 常见使用场景

元组在需要返回多个值传递多个值时非常有用,特别是当这些值之间没有强关联、不值得定义一个 case class 时。

4.1. 返回多个值

一个典型例子是 partition 方法:

def partition[A](xs: List[A])(predicate: A => Boolean): (List[A], List[A]) = {
  xs.foldRight((List.empty[A], List.empty[A])) {
    case (a, (lefts, rights)) =>
      if (predicate(a)) (a :: lefts, rights) else (lefts, a :: rights)
  }
}

val (evens, odds) = partition(List(1, 3, 4, 5, 2))(_ % 2 == 0)

这个方法返回两个列表:一个包含满足条件的元素,另一个包含不满足的。

4.2. 传递多个参数

可以将元组作为参数传入函数。Scala 提供了 tupled() 方法,用于将多参数函数转换为接受元组的函数:

val data = Map(
  "Joe" -> 34,
  "Mike" -> 16,
  "Kelly" -> 21
)

case class User(name: String, isAdult: Boolean)

val createUser: (String, Int) => User = (name, age) => User(name, age >= 18)
val users = data.map(createUser.tupled)

如果忘记调用 tupled(),会报类型不匹配的错误:

type mismatch; 
found : (String, Int) => User 
required: ((String, Int)) => ? 
val users = data.map(createUser)

编译器提示我们:试图把两个参数传给一个只接受元组参数的函数。

5. Scala 3 中的元组增强

Scala 3 对元组进行了大幅增强,使其行为更接近于一个支持索引的集合。

一些新特性如下:

val tuple = (1, "Baeldung", false)

assert(tuple(0) == 1)
assert(tuple.head == 1)
assert(tuple.tail == ("Baeldung", false))
assert(tuple.last == false)
assert(tuple.take(2) == (1, "Baeldung"))
assert(tuple.toList == List(1, "Baeldung", false))
assert(tuple ++ ("Hello", "World") == (1, "Baeldung", false, "Hello", "World"))

✅ 此外,Scala 3 引入了 Match Type 特性来支持这些功能。

还有一个很实用的功能:从 case class 生成元组

final case class User(id: Long, name: String, active: Boolean)
val baeldung = User(1, "baeldung", true)
val userTuple = Tuple.fromProductTyped(baeldung)
assert(userTuple == (1, "baeldung", true))

6. 总结

在这篇文章中,我们学习了 Scala 中的元组类型,包括如何创建、访问元素,以及一些常用的元组方法和典型使用场景。

✅ 我们还介绍了 Scala 3 中对元组的重大改进,使其功能更加强大和灵活。

📌 源码地址:


原始标题:A Guide to Scala Tuples