Kotlin中的四元组、五元组等解构方式

44

我正在寻找一种清晰的方法来创建可析构的内联对象。kotlin.Pairkotlin.Triple涵盖了许多使用情况,但有时需要传递更多的对象。

一个样例用例是RX的zip函数,需要将多个I/O调用的结果映射到另一个对象中:

Single
    .zip(repositoryA.loadData(someId),
         repositoryB.loadData(someId),
         repositoryC.loadAll(),
         repositoryD.loadAll()),
         { objectA, objectB, objectsC, objectsD -> /*some Kotlin magic*/ }
    )
    .map { (objectA, objectB, objectsC, objectsD) -> /*do the mapping*/ }

我试图弄清楚"一些 Kotlin 魔法"部分会放什么。如果只有3个存储库,那就是

Triple(objectA, objectB, objectsC)

我需要为此创建一个新的数据类吗?对于任何n元组情况,是否有其他方法?


除了三元组,您真的应该考虑使用数据类。在那个点上,您发送的数据很可能无法由元组充分建模。 - aeskreis
4个回答

52

基础知识

让我们看看解构是如何工作的:

Kotlin为此定义了一种惯例,即componentX() operator函数是Kotlin中许多地方使用惯例原则的示例。这些componentX()函数由编译器用于解构声明中变量的初始化。

例如,在Pair<A,B>中,这些函数如下所示:

operator fun component1(): A = first 

operator fun component2(): B = second

如您所见,这些是特别处理的函数运算符。 这些componentX()函数可以由开发人员提供,并将自动由编译器为data类生成。顺便提一下,Pair也是这样的data类。
因此,每当需要超过三个元素时,请使用data类。
例如,定义为以下内容的类MultiComponent:
data class MultiComponent(val x: Int, val y: Int, val z: Int, val a: Int, val b: Int, val c: Int)

它将被编译为一个具有函数 component1(), component2(), ..., component6() 的类,并且可以在解构声明中使用:

val (q, w, e, r, t, z) = MultiComponent(1, 2, 3, 4, 5, 6)

这是Kotlin的一个不错特性。我越来越喜欢Kotlin,因为它在大多数Python特性上实现方式有些相似。虽然不完全相同,但总归是有点相似。其中一个Pythonism是返回多个值的函数或方法,这在Java中非常难以实现,但是使用Kotlin的数据类却很容易。数据类例子:data class MultiComponent(objectA:A, objectB:B, objectsC;c, objectsD:D) fun someFunction(...):Multocomponent - Reijo Korhonen

36

我发现最简单的方法是仅仅用代码生成所需的n元组。使用场景包括功能扩展方法,如记忆化等。

data class NTuple2<T1, T2>(val t1: T1, val t2: T2)

data class NTuple3<T1, T2, T3>(val t1: T1, val t2: T2, val t3: T3)

data class NTuple4<T1, T2, T3, T4>(val t1: T1, val t2: T2, val t3: T3, val t4: T4)

data class NTuple5<T1, T2, T3, T4, T5>(val t1: T1, val t2: T2, val t3: T3, val t4: T4, val t5: T5)

data class NTuple6<T1, T2, T3, T4, T5, T6>(val t1: T1, val t2: T2, val t3: T3, val t4: T4, val t5: T5, val t6: T6)

然后生成必要的构造辅助函数:

infix fun <T1, T2> T1.then(t2: T2): NTuple2<T1, T2>
{
    return NTuple2(this, t2)
}

infix fun <T1, T2, T3> NTuple2<T1, T2>.then(t3: T3): NTuple3<T1, T2, T3>
{
    return NTuple3(this.t1, this.t2, t3)
}

infix fun <T1, T2, T3, T4> NTuple3<T1, T2, T3>.then(t4: T4): NTuple4<T1, T2, T3, T4>
{
    return NTuple4(this.t1, this.t2, this.t3, t4)
}

infix fun <T1, T2, T3, T4, T5> NTuple4<T1, T2, T3, T4>.then(t5: T5): NTuple5<T1, T2, T3, T4, T5>
{
    return NTuple5(this.t1, this.t2, this.t3, this.t4, t5)
}

infix fun <T1, T2, T3, T4, T5, T6> NTuple5<T1, T2, T3, T4, T5>.then(t6: T6): NTuple6<T1, T2, T3, T4, T5, T6>
{
    return NTuple6(this.t1, this.t2, this.t3, this.t4, this.t5, t6)
}

那么我接下来可以这样做:

val nTuple4 = 1 then 2 then "foo" then "bar"

导致:

val nTuple4: NTuple4<Int, Int, String, String>

3
不错的中缀函数运用。不过在那个点,你也可以定义一个重载的 tuple(..) 方法。重复的 then 使它看起来有点冗长啰嗦。 - 0cd
同意,但真正的价值在于当您已经有一个元组时; 例如 val nTuple6 = nTuple4 then 7 then 8 - 但确实,构造函数助手也会很有帮助。 - Dan Lugg
我觉得我误解了你的评论 @0cd — 重新阅读后,我相信你是在提到重载 rangeTo 以使用元组组件之间的 ..?如果是这样,绝对没问题!唯一的问题是对于 Int 的现有重载,在其中 1 .. 2 会导致一个范围,而不是 NTuple<Int, Int>。你需要写成 NTuple(1, 2) .. 3。除此之外,同意它更加简洁。 - Dan Lugg
1
哦,我只是想说可以创建像tuple(T1 v1)、tuple(T1 v1, T2 v2)等方法。现在我意识到不应该使用..,因为那可能会导致混淆。我也喜欢你提出的“then”版本 :) 只是分享另一个选项。 - 0cd
啊,明白了 :-) 一个带有n元重载的tupleOf函数。绝对是另一个有用且更熟悉的API,就像listOfsetOf等一样;也是个好主意。我把我的示例藏在了一个我已经有一段时间没碰过的公共库里,如果我还没有添加的话,我得回去补上。 - undefined

17
与Scala不同,Kotlin没有定义超过3个值的n元组。你已经正确地识别了PairTriple
根据这篇博客文章,Kotlin更喜欢使用数据类来处理这些用例。所以是的,你必须定义一个数据类才能做到你想要的事情,没有Quadruple。我个人认为,定义自己的数据类更清晰,并且最终编译和使用方式将与假设的Quadruple相同,在内部也是如此。
至于解构数据类,Kotlin也支持:
data class Thingy(val a: String, val b: String, val c: String, val d: String)
val t = Thingy("A", "B", "C", "D")
val (aa, bb, cc, dd) = t

17
你可以将这些kotlin类添加到你的项目中,它们的工作方式与PairTriple完全相同,但你可以传递更多的对象。 Quadruple(允许你传递4个对象)。
import java.io.Serializable

/**
 * Created by nalcalag on 09/02/2019.
 * 
 * Represents a quartet of values
 *
 * There is no meaning attached to values in this class, it can be used for any purpose.
 * Quadruple exhibits value semantics
 *
 * @param A type of the first value.
 * @param B type of the second value.
 * @param C type of the third value.
 * @param D type of the fourth value.
 * @property first First value.
 * @property second Second value.
 * @property third Third value.
 * @property fourth Fourth value.
 */
data class Quadruple<out A, out B, out C, out D>(
        val first: A,
        val second: B,
        val third: C,
        val fourth: D
) : Serializable {

    /**
     * Returns string representation of the [Quadruple] including its [first], [second], [third] and [fourth] values.
     */
    override fun toString(): String = "($first, $second, $third, $fourth)"
}

/**
 * Converts this quadruple into a list.
 */
fun <T> Quadruple<T, T, T, T>.toList(): List<T> = listOf(first, second, third, fourth)

五元组(可以传递5个对象)

import java.io.Serializable

/**
 * Created by nalcalag on 09/02/2019.
 * 
 * Represents a quintet of values
 *
 * There is no meaning attached to values in this class, it can be used for any purpose.
 * Quintuple exhibits value semantics
 *
 * @param A type of the first value.
 * @param B type of the second value.
 * @param C type of the third value.
 * @param D type of the fourth value.
 * @param E type of the fifth value.
 * @property first First value.
 * @property second Second value.
 * @property third Third value.
 * @property fourth Fourth value.
 * @property fifth Fifth value.
 */
data class Quintuple<out A, out B, out C, out D, out E>(
        val first: A,
        val second: B,
        val third: C,
        val fourth: D,
        val fifth: E
) : Serializable {

    /**
     * Returns string representation of the [Quintuple] including its [first], [second], [third], [fourth] and [fifth] values.
     */
    override fun toString(): String = "($first, $second, $third, $fourth, $fifth)"
}

/**
 * Converts this quintuple into a list.
 */
fun <T> Quintuple<T, T, T, T, T>.toList(): List<T> = listOf(first, second, third, fourth, fifth)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接