【问题标题】:kotlin data class constructors not getting picked upkotlin 数据类构造函数没有被拾取
【发布时间】:2022-11-13 14:04:42
【问题描述】:

我正在 kotlin 中创建一个数据类


data class User(val name: String, val age: Int)
{
    constructor(name: String, age: Int, size: String): this(name, age) {
    }

}

在我的主要功能中,我可以这样访问对象:

fun main(){

    val x = User("foo", 5, "M")
    println(x.name)
    println(x.age)
    println(x.size) // does not work
}

我的问题是我无法访问size

我想要做的是,创建一个data class,其中顶级参数是将被访问的常见项目,并且在构造函数中,具有适合某些情况的附加参数。目的是让我可以做类似的事情

// something along the lines of 
if (!haveSize()){
    val person = User("foo", 5, "M")
} else {
     val person = User("foo", 5)
    }
}

有任何想法吗?

【问题讨论】:

  • size 不是User 的属性,它只是一个构造函数参数。它不存在于构造函数之外,因此如果它是一个属性,您将无法访问它。应该是财产吗?
  • 使用默认参数值是否无法实现您的目标?例如:数据类 User(val name: String, val age: Int, val size: String? = null )
  • 您好,感谢您的回复 - 但我意识到我的问题和代码没有准确描述我的问题陈述。我编辑了它。你能看看吗?
  • 您不能使用默认参数值实现您的目标吗?例如:数据类 User(val name: String, val detailsOne: OneDetails? = null, val detailsTwo: TwoDetails? = null ) ?然后你可以这样说: "1" -> anotherCaller(User(mt, detailsOne = one))
  • 不,我不能。因为OneDetailsTwoDetails 将传入不同类型的值。所以我想我想看看是否可以在数据类上执行overloading

标签: kotlin data-class


【解决方案1】:

在 Kotlin 中,您不需要单独的构造函数来定义可选的构造函数参数。您可以使用默认值在单个构造函数中定义它们,或者使它们可以为空,如下所示:


data class User(val name: String, val age: Int, val size: String = "M")
fun main(){
    val x = User("foo", 5, "L")
    val y = User("foo", 5)

    println(x.size) // "L" from call site
    println(y.size) // "M" from default param
}

【讨论】:

  • 您好,感谢您的回复 - 但我意识到我的问题和代码没有准确描述我的问题陈述。我编辑了它。你能看看吗?
【解决方案2】:

您不能访问大小变量,因为这是来自辅助构造,但我们有替代变体。

data class User(var name: String, var age: Int) {

 var size: String

 init {
     size = "size"
  }

constructor(name: String, age: Int, size: String) : this(name, age) {
    this.size = size
  }
}

【讨论】:

  • 这将是一个非常合适的 Java 答案,但在 Kotlin 中,使用可选参数而不是创建多个构造函数更为可取。
  • 您好,感谢您的回复 - 但我意识到我的问题和代码没有准确描述我的问题陈述。我编辑了它。你能看看吗?
【解决方案3】:

简而言之,您希望拥有一个可以作为有限数量选项之一的属性。这可以使用genericssealed inheritance 来解决。

泛型

这里我添加了一个接口MountDetails,带有一个通用参数T。有一个属性val c,其类型为T

data class User(
    val mountOptions: MountOptions,
    val mountDetails: MountDetails<*>,
)

data class MountOptions(
    val a: String,
    val b: String
)

interface MountDetails<T : Any> {
    val c: T
}

data class MountOneDetails(override val c: Int) : MountDetails<Int>

data class MountTwoDetails(override val c: String) : MountDetails<String>

因为实现MountDetailsMountOneDetailsMountTwoDetails)将T的类型指定为IntString,所以val c始终可以访问。

fun anotherCaller(user: User) {
    println(user.mountOptions.a)
    println(user.mountOptions.b)

    println(user.mountDetails)
}

fun main() {
    val mt = MountOptions("foo", "bar")

    val mountOneDetails = MountOneDetails(111)
    anotherCaller(User(mt, mountOneDetails))

    val mountTwoDetails = MountTwoDetails("mount two")
    anotherCaller(User(mt, mountTwoDetails))
}

输出:

foo
bar
MountOneDetails(c=111)
foo
bar
MountTwoDetails(c=mount two)

不过,泛型也有缺点。如果有很多泛型参数会很混乱,并且由于类型擦除,在运行时很难确定类的类型。

密封继承

由于您只有有限数量的安装细节,一个更简洁的解决方案是sealed classes and interfaces

data class User(val mountOptions: MountOptions)

sealed interface MountOptions {
    val a: String
    val b: String
}

data class MountOneOptions(
    override val a: String,
    override val b: String,
    val integerData: Int,
) : MountOptions

data class MountTwoOptions(
    override val a: String,
    override val b: String,
    val stringData: String,
) : MountOptions

这里的好处是类更少,类型更具体。添加或删除额外的挂载细节也很容易,任何exhaustive when statements 都会导致编译器错误。

fun anotherCaller(user: User) {
    println(user.mountOptions.a)
    println(user.mountOptions.b)

    // use an exhaustive when to determine the actual type
    when (user.mountOptions) {
        is MountOneOptions -> println(user.mountOptions.integerData)
        is MountTwoOptions -> println(user.mountOptions.stringData)
        // no need for an 'else' branch
    }
}

fun main() {

    val mountOne = MountOneOptions("foo", "bar", 111)
    anotherCaller(User(mountOne))

    val mountTwo = MountTwoOptions("foo", "bar", "mount two")
    anotherCaller(User(mountTwo))
}

输出:

foo
bar
111
foo
bar
mount two

【讨论】:

    【解决方案4】:

    这实际上是 Hubert Grzeskowiak 提供的“默认值”答案,已根据您的示例进行了调整:

    data class OneDetails(val c: Int)
    data class TwoDetails(val c: String)
    data class MountOptions(val a: String, val b: String)
    
    data class User(
        val mountOptions: MountOptions,
        val detailsOne: OneDetails? = null,
        val detailsTwo: TwoDetails? = null
    )
    
    fun main() {
        fun anotherCaller(user: User) = println(user)
        
        val mt = MountOptions("foo", "bar")
        val one = OneDetails(1)
        val two = TwoDetails("2")
    
        val switch = "0"
        when (switch) {
            "0" -> anotherCaller(User(mt))
            "1" -> anotherCaller(User(mt, detailsOne = one))
            "2" -> anotherCaller(User(mt, detailsTwo = two))
            "12" -> anotherCaller(User(mt, detailsOne = one, detailsTwo = two))
            else -> throw IllegalArgumentException(switch)
        }
    }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      • 2017-11-07
      • 1970-01-01
      • 2019-03-18
      • 1970-01-01
      相关资源
      最近更新 更多