kotlin数据类
这篇文章的目的不是指出Kotlin数据类设计中的一些重大缺陷,而是向您展示如何通过它们。 实际上,情况恰恰相反。 Kotlin文档中清楚地记录了这篇文章的内容。 我只是在这里向没有确切注意到其数据类如何工作的任何人突出此信息。
数据类对我们的开发人员来说非常方便,特别是那些来自Java的开发人员。 它们提供了几个生成的函数,使您可以用很少的代码编写功能齐全的类。 数据类提供以下生成的函数:
-
equals -
hashCode -
toString -
copy - componentN()函数 。
以上所有都是针对类的主构造函数中定义的属性生成的。 在此构造函数之外定义的任何内容都将被忽略。 这就是潜在的陷阱 。 但是,这只是一个陷阱,如果您不知道数据类如何工作。 正如我之前提到的,它已明确记录在案 ,您只需要注意这一点。 当然,您现在是。
如果您对定义数据类的方式不谨慎,则可能会在应用程序中发现一些错误。 equals和hashCode通常是必不可少的函数。 如果它们不能按预期工作,则肯定会出现错误。
下面是一个示例:
data class MyClass(val a: String, val b: Int) {
// property defined outside of primary constructor
lateinit var c: String } fun main() {
// create two equal objects
val myClass = MyClass( "abc" , 0 )
val myClass2 = myClass.copy()
// check their hashCodes are the same and that they equal each other
println( "myClass hashCode: ${myClass.hashCode()}" )
println( "myClass2 hashCode: ${myClass2.hashCode()}" )
println( "myClass == myClass2: ${myClass == myClass2}" )
// set the lateinit variables
myClass.c = "im a lateinit var"
myClass2.c = "i have a different value"
// have their hashCodes changed?
println( "myClass hashCode after setting lateinit var: ${myClass.hashCode()}" )
println( "myClass2 hashCode after setting lateinit var: ${myClass2.hashCode()}" )
// are they still equal to each other?
println( "myClass == myClass2 after setting lateinit vars: ${myClass == myClass2}" )
// sanity check to make sure I'm not being stupid
println( "sanity checking myClass.c: ${myClass.c}" )
println( "sanity checking myClass2.c: ${myClass2.c}" ) }
执行此示例输出:
myClass hashCode: 2986974 myClass hashCode: 2986974 myClass2 hashCode: 2986974 myClass == myClass2: true myClass hashCode after setting lateinit var: 2986974 myClass2 hashCode after setting lateinit var: 2986974 myClass == myClass2 after setting lateinit vars: true sanity checking myClass.c: im a lateinit var sanity checking myClass2.c: i have a different value
正如您所看到的,即使每个对象的c属性不同,它们的hashCode也是相同的,并且彼此相等。 如果您试图在Map或Set使用MyClass ,则条目相互碰撞的机会会增加。 话虽如此,这确实取决于您要实现的目标。 也许这正是您想要发生的事情。 在这种情况下,您将获得更多动力。
将c放入MyClass构造函数中会影响hashCode并equals实现。 然后, c将涉及对hashCode , equals和其余生成函数的任何调用。
另一个解决方案是手动实现生成的功能。 将类重写为:
data class MyClass(val a: String, val b: Int) {
lateinit var c: String
override fun equals(other: Any?): Boolean {
if ( this === other) return true
if (javaClass != other?.javaClass) return false
other as MyClass
if (a != other.a) return false
if (b != other.b) return false
if (c != other.c) return false
return true
}
override fun hashCode(): Int {
var result = a.hashCode()
result = 31 * result + b
result = 31 * result + c.hashCode()
return result
}
override fun toString(): String {
return "MyClass(a='$a', b=$b, c='$c')"
} }
这些实现由Intellij ly提供。 通过在每个重写函数中指定所有属性,现在可以使用主构造函数(本例中为c )中未包含的所有属性。 hashCode和equals现在可以更好地表示该类,并改进其在Map或Set 。
但是,又大又😏。 至少在我编写的代码中。 现在引入了一个错误。 c是一个lateinit var ,每个覆盖的函数现在都尝试访问它。 如果在设置c之前调用了这些函数中的任何一个,您将得到一个异常:
Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property c has not been initialized
at dev.lankydan.MyClass.hashCode(DataClasses.kt: 60 )
at dev.lankydan.DataClassesKt.main(DataClasses.kt: 72 )
at dev.lankydan.DataClassesKt.main(DataClasses.kt)
重写equals , hashCode和toString以容纳lateinit var将解决此错误:
data class MyClass(val a: String, val b: Int) {
lateinit var c: String
override fun equals(other: Any?): Boolean {
if ( this === other) return true
if (javaClass != other?.javaClass) return false
other as MyClass
if (a != other.a) return false
if (b != other.b) return false
if ( this ::c.isInitialized && (other as MyClass)::c.isInitialized && c != other.c) return false
return true
}
override fun hashCode(): Int {
var result = a.hashCode()
result = 31 * result + b
if ( this ::c.isInitialized) {
result = 31 * result + c.hashCode()
}
return result
}
override fun toString(): String {
return if ( this ::c.isInitialized) "MyClass(a='$a', b=$b, c='$c')"
else "MyClass(a='$a', b=$b)"
} }
即使未设置lateinit var ,该实现也可以安全使用。
是否要这样做取决于班级的要求。 不建议使用像我在Map这样的数据类。 如果您想这样做,可以。 只是要知道它是如何工作的。
如果您还没有这样做,建议您快速浏览有关此主题的文档 。 突出此信息是本文的目标。 并不是一些花哨的代码可以做一些神奇的事情。 取而代之的是,这对于Kotlin的工作方式来说是更为基本和根本的。 了解数据类在这方面的工作方式对于减少应用程序中的错误数量至关重要。
翻译自: https://www.javacodegeeks.com/2019/08/potential-traps-kotlins-data-classes.html
kotlin数据类
探讨Kotlin数据类的潜在陷阱,特别是在处理lateinit变量时,可能导致hashCode和equals函数的意外行为,影响在Map和Set中的表现。

1945

被折叠的 条评论
为什么被折叠?



