如何使用构造函数正确重载 kotlin 数据类

jak*_*ong 0 kotlin

我正在尝试在 kotlin 中创建一个嵌套数据类,其中包含其他几个数据类。然后访问主数据类来获取嵌套数据类的对象。

Person数据类将成为标准,但其他 3 个数据类是重载的(不确定我在这里是否正确使用了术语“重载”?)。

注意:我使用的IDE是intellij

我的意思是说:

data class User(val person: Person, val transport: Transport)
{
    constructor(person: Person, activity: Activity): this(person, activity)  //ERROR: There's a cycle in the delegation calls chain
    constructor(person: Person, absent: Absent): this(person, absent)  //ERROR: There's a cycle in the delegation calls chain
}

data class Activity(val game: String, val funLevel: Int)
data class Absent(val isSick: Boolean, val funLevel: Int)
data class Transport(val typeOfTransport: String)
data class Person(val name: String, val age: Int)

fun main(){
    val x = User(Person("foo", 5), Activity("basketball", 10))
    val y = User(Person("bar", 6), Absent(true, 0))
    val z = User(Person("loo", 6), Transport("Bus"))
    
    // Ultimately want to access the data objects like this
    println(x.person.name)
    println(x.activity.game)  // not working
    
}
Run Code Online (Sandbox Code Playgroud)

最终,我试图能够像这样轻松地调用该函数: 在此输入图像描述

有任何想法吗?

cac*_*acs 6

data class User(val person: Person, val transport: Transport)
Run Code Online (Sandbox Code Playgroud)

That bit in the parentheses there is the primary constructor - in fact that's shorthand, you can also write the class declaration like this:

data class User constructor(val person: Person, val transport: Transport)
Run Code Online (Sandbox Code Playgroud)

When you have a primary constructor, any secondary constructors have to call through to the primary one.

// need to somehow create a Transport to call the primary with
constructor(person: Person, activity: Activity): this(person, someTransport)

// can't do this - the primary constructor doesn't take (Person, Activity)
constructor(person: Person, activity: Activity): this(person, activity)
Run Code Online (Sandbox Code Playgroud)

That's because to actually create an instance of a class, you need to call its main constructor at some point - the secondaries can just do other stuff as well, but they need to actually instantiate the object.


You can omit the primary constructor (so the class can be instantiated with no arguments) and then your secondary constructors can do whatever they want:

class User {
    constructor(person: Person, activity: Activity) {
        // initialisation stuff
    }
    constructor(person: Person, absent: Absent) {
        // other initialisation stuff
    }
}
Run Code Online (Sandbox Code Playgroud)

but at the end of the day it's still calling that no-args constructor to actually create the User - it's up to you to do something with those different arguments passed into the different constructors, and create the same type of object no matter which is called.

Do you have all the possible properties, and just leave them null if no value was provided? Do you have a special set of classes representing the different combos of data for each constructor, and assign an instance of one of those to a userData property? You need to work out how to have a single class that can be instantiated with different combinations of data.


数据类很特殊需要一个主构造函数。这实际上是类中数据的定义,它的所有方便的重写和生成的方法(如equalshashCodecopy)都与主构造函数中定义的属性一起使用。

另一方面是任何未在主构造函数的参数中定义或派生的属性都不是其“数据”的一部分。如果您copy是数据类,则仅复制主构造函数中的属性。它对类的其余部分一无所知 - 因此,如果您依赖任何数据类功能,请注意默认情况下不包含其他属性。

因此,考虑到这一点,典型的数据类方法是将所有数据放在主构造函数中:

data class User(
    val person: Person,
    val transport: Transport? = null,
    val activity: Activity? = null,
    val absent: Absent? = null
) {
    constructor(person: Person, activity: Activity): this(person, null, activity, null)
    constructor(person: Person, absent: Absent): this(person, null, null, absent)
}
Run Code Online (Sandbox Code Playgroud)

这样,每个辅助构造函数都会调用主构造函数,并且所有数据都定义并包含在主构造函数中。有些东西可能会丢失!


但这有点尴尬:

this(person, null, activity, null)
Run Code Online (Sandbox Code Playgroud)

我们已经命名了参数,所以你可以尝试这个:

constructor(person: Person, activity: Activity): this(person, activity = activity)
Run Code Online (Sandbox Code Playgroud)

但这实际上会再次调用相同的辅助构造函数,因为它的签名(采用 aPerson和 an Activity)与您正在进行的调用匹配。(这就是为什么你会得到循环错误。)但是如果我们使用默认参数以这种方式做事,你可以完全避免辅助构造函数:

data class User(
    val person: Person,
    val transport: Transport? = null,
    val activity: Activity? = null,
    val absent: Absent? = null
)

// create an instance
User(somePerson, absent = someAbsent)
Run Code Online (Sandbox Code Playgroud)

但这种方式限制了您将其限制为某些组合的能力,例如 aPerson和/ /之一 。这通常是数据类的一个问题 - 您可以创建主构造函数并强制用户使用辅助构造函数或其他生成实例的函数,但该函数允许人们随心所欲地处理这些数据。TransportActivityAbsentprivatecopy

在这种情况下,您可能需要一个像注释中提到的IR42那样的密封类- 一种允许定义一组完全不同的子类的类型。我只是想概述一下这一切是如何运作的以及为什么您可能想尝试不同的方法