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数据类