Kotlin 中的数据 类
Data classes in Kotlin
有什么区别:
定义 1
data class Person (var name:String, var age:Int)
定义 2
class Person (var name:String, var age:Int)
定义 3
class Person (){
var name:String = ""
var age:Int = 1
}
在我使用自动完成的 3 种情况下,我看到了与 POJO 一样可用的相同方法...这是相同的但有 3 种不同的方式吗?
equals
、hashCode
和 toString
的差异
定义 1 和 定义 2 和 3 之间最重要的区别是 定义 1,equals
、hashcode
和 toString
方法已为您覆盖:
equals
和 hashCode
方法测试 结构相等性
toString
方法 returns 一个漂亮的、人性化的字符串
代码示例:
注意:在 Kotlin 中,==
运算符调用对象的 .equals()
方法。见 operator overloading on kotlinlang.org for more info.
data class Person1 (var name:String, var age:Int)
class Person2 (var name:String, var age:Int)
@Test fun test1()
{
val alice1 = Person1("Alice", 22)
val alice2 = Person1("Alice", 22)
val bob = Person1("bob", 23)
// alice1 and alice2 are structurally equal, so this returns true.
println(alice1 == alice2) // true
// alice1 and bob are NOT structurally equal, so this returns false.
println(alice1 == bob) // false
// the toString method for data classes are generated for you.
println(alice1) // Person1(name=Alice, age=22)
}
@Test fun test2()
{
val alice1 = Person2("Alice", 22)
val alice2 = Person2("Alice", 22)
val bob = Person2("bob", 23)
// even though alice1 and alice2 are structurally equal, this returns false.
println(alice1 == alice2) // false
println(alice1 == bob) // false
// the toString method for normal classes are NOT generated for you.
println(alice1) // Person2@1ed6993a
}
构造函数的差异
定义 1 和 2 和 定义 3 之间的另一个区别是:
- 定义 1 和 2 都有一个带有 2 个参数的构造函数
- 定义 3 只有一个无参数构造函数,它为 class 成员分配默认值。
代码示例:
data class Person1 (var name:String, var age:Int)
class Person2 (var name:String, var age:Int)
class Person3 ()
{
var name:String = ""
var age:Int = 1
}
@Test fun test3()
{
Person1("alice",22) // OK
Person2("bob",23) // OK
Person3("charlie",22) // error
Person1() // error
Person2() // error
Person3() // OK
}
copy
方法
最后,定义 1 和 定义 2 & 3 的另一个区别是 定义 1,为它生成一个copy
method。这是一个如何使用它的例子:
val jack = Person1("Jack", 1)
val olderJack = jack.copy(age = 2)
// jack.age = 1
// olderJack.age = 2
Check out the official documentation for data classes on kotlinlang.org!
定义 1 (data class Person(var name: String, var age: Int
) 等价于
/* every class by default in kotlin is final but a data class CAN'T be open */
final class Person(var name: String, var age: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Person
if (name != other.name) return false
if (age != other.age) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + age
return result
}
}
val person = Person("Name", 123)
定义 2(class Person(var name: String, var age: Int
[无 data
修饰符])等同于
class Person(var name: String, var age: Int) {
}
val person = Person("Name", 123)
定义 3 等价于
class Person() {
var name: String = ""
var age: Int = 1
}
val person = Person() // name = "", age = 1 (by default)
person.name = "Name"
person.age = 123
简单地说:
- 定义1:这是一个数据class,主构造函数有两个参数
name
和age
- 定义 2: 这只是一个 class 主构造函数有两个参数
name
和 age
- 定义 3: 这是一个 class 构造函数,它没有参数,并且将默认值
""
和 1
分配给属性 name
和age
分别
详细回答
这里要理解的重点是data class的概念。
数据Class
创建主要目的是保存数据的 classes 是很常见的。如果您希望您的 class 成为方便的数据持有者,您需要重写 通用对象方法 :
toString()
- 字符串表示
equals()
- 对象相等
hashCode()
- 哈希容器
注意:equals()
用于structural equality,通常用hashCode()
实现。
通常,这些方法的实现很简单,您的IDE可以帮助您自动生成它们。
但是,在 Kotlin 中,您不必通用化所有这些样板代码。如果您将修饰符 data
添加到您的 class,则会自动为您添加必要的方法。 equals()
和 hashCode()
方法考虑了在主构造函数中声明的所有属性。 toString()
将具有以下格式 ClassName(parm1=value1, param2=value2, ...)
.
此外,当您将class标记为数据class时,也会自动生成方法copy()
,让您制作现有实例的副本。当您将实例用作 HashMap
的键或者处理多线程代码时,此功能非常方便。
回到你的问题:
- 定义 1:您不需要实现通用对象方法,并且您还可以使用
copy()
方法
- 定义 2: 您必须实现通用对象方法和
copy()
方法(如果需要)
- 定义3:你必须实现通用对象方法和
copy()
方法,如果你需要的话,实现[=没有意义28=] 方法,因为你的主构造函数没有参数
即使数据 class 的属性不需要是 val
,即您可以在代码中使用 var
,但强烈建议您使用 read-only 属性,以便使实例不可变。
最后,当您将 class 标记为 数据 class 时,编译器也会生成按声明顺序对应属性的 componentN()
函数.
再补充一点,Eric 接受的答案没有提到。
数据类可以参与解构声明。
所以如果我们有
class Person(val name: String, val age: Int)
data class Person2(val name: String, val age: Int)
然后
fun main() {
val person = Person("Kal", 34); //normal class instance
val person2 = Person2("Kal", 34); //data class instance
val (name, age) = person; //This does not compile and shows error
//Destructuring declaration initializer of type Employee must have a 'component1()' function
//Destructuring declaration initializer of type Employee must have a 'component2()' function
val (name2, age2) = person2; //no problem here
}
有什么区别:
定义 1
data class Person (var name:String, var age:Int)
定义 2
class Person (var name:String, var age:Int)
定义 3
class Person (){
var name:String = ""
var age:Int = 1
}
在我使用自动完成的 3 种情况下,我看到了与 POJO 一样可用的相同方法...这是相同的但有 3 种不同的方式吗?
equals
、hashCode
和 toString
的差异
定义 1 和 定义 2 和 3 之间最重要的区别是 定义 1,equals
、hashcode
和 toString
方法已为您覆盖:
equals
和hashCode
方法测试 结构相等性toString
方法 returns 一个漂亮的、人性化的字符串
代码示例:
注意:在 Kotlin 中,==
运算符调用对象的 .equals()
方法。见 operator overloading on kotlinlang.org for more info.
data class Person1 (var name:String, var age:Int)
class Person2 (var name:String, var age:Int)
@Test fun test1()
{
val alice1 = Person1("Alice", 22)
val alice2 = Person1("Alice", 22)
val bob = Person1("bob", 23)
// alice1 and alice2 are structurally equal, so this returns true.
println(alice1 == alice2) // true
// alice1 and bob are NOT structurally equal, so this returns false.
println(alice1 == bob) // false
// the toString method for data classes are generated for you.
println(alice1) // Person1(name=Alice, age=22)
}
@Test fun test2()
{
val alice1 = Person2("Alice", 22)
val alice2 = Person2("Alice", 22)
val bob = Person2("bob", 23)
// even though alice1 and alice2 are structurally equal, this returns false.
println(alice1 == alice2) // false
println(alice1 == bob) // false
// the toString method for normal classes are NOT generated for you.
println(alice1) // Person2@1ed6993a
}
构造函数的差异
定义 1 和 2 和 定义 3 之间的另一个区别是:
- 定义 1 和 2 都有一个带有 2 个参数的构造函数
- 定义 3 只有一个无参数构造函数,它为 class 成员分配默认值。
代码示例:
data class Person1 (var name:String, var age:Int)
class Person2 (var name:String, var age:Int)
class Person3 ()
{
var name:String = ""
var age:Int = 1
}
@Test fun test3()
{
Person1("alice",22) // OK
Person2("bob",23) // OK
Person3("charlie",22) // error
Person1() // error
Person2() // error
Person3() // OK
}
copy
方法
最后,定义 1 和 定义 2 & 3 的另一个区别是 定义 1,为它生成一个copy
method。这是一个如何使用它的例子:
val jack = Person1("Jack", 1)
val olderJack = jack.copy(age = 2)
// jack.age = 1
// olderJack.age = 2
Check out the official documentation for data classes on kotlinlang.org!
定义 1 (data class Person(var name: String, var age: Int
) 等价于
/* every class by default in kotlin is final but a data class CAN'T be open */
final class Person(var name: String, var age: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Person
if (name != other.name) return false
if (age != other.age) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + age
return result
}
}
val person = Person("Name", 123)
定义 2(class Person(var name: String, var age: Int
[无 data
修饰符])等同于
class Person(var name: String, var age: Int) {
}
val person = Person("Name", 123)
定义 3 等价于
class Person() {
var name: String = ""
var age: Int = 1
}
val person = Person() // name = "", age = 1 (by default)
person.name = "Name"
person.age = 123
简单地说:
- 定义1:这是一个数据class,主构造函数有两个参数
name
和age
- 定义 2: 这只是一个 class 主构造函数有两个参数
name
和age
- 定义 3: 这是一个 class 构造函数,它没有参数,并且将默认值
""
和1
分配给属性name
和age
分别
详细回答
这里要理解的重点是data class的概念。
数据Class
创建主要目的是保存数据的 classes 是很常见的。如果您希望您的 class 成为方便的数据持有者,您需要重写 通用对象方法 :
toString()
- 字符串表示equals()
- 对象相等hashCode()
- 哈希容器
注意:equals()
用于structural equality,通常用hashCode()
实现。
通常,这些方法的实现很简单,您的IDE可以帮助您自动生成它们。
但是,在 Kotlin 中,您不必通用化所有这些样板代码。如果您将修饰符 data
添加到您的 class,则会自动为您添加必要的方法。 equals()
和 hashCode()
方法考虑了在主构造函数中声明的所有属性。 toString()
将具有以下格式 ClassName(parm1=value1, param2=value2, ...)
.
此外,当您将class标记为数据class时,也会自动生成方法copy()
,让您制作现有实例的副本。当您将实例用作 HashMap
的键或者处理多线程代码时,此功能非常方便。
回到你的问题:
- 定义 1:您不需要实现通用对象方法,并且您还可以使用
copy()
方法 - 定义 2: 您必须实现通用对象方法和
copy()
方法(如果需要) - 定义3:你必须实现通用对象方法和
copy()
方法,如果你需要的话,实现[=没有意义28=] 方法,因为你的主构造函数没有参数
即使数据 class 的属性不需要是 val
,即您可以在代码中使用 var
,但强烈建议您使用 read-only 属性,以便使实例不可变。
最后,当您将 class 标记为 数据 class 时,编译器也会生成按声明顺序对应属性的 componentN()
函数.
再补充一点,Eric 接受的答案没有提到。
数据类可以参与解构声明。
所以如果我们有
class Person(val name: String, val age: Int)
data class Person2(val name: String, val age: Int)
然后
fun main() {
val person = Person("Kal", 34); //normal class instance
val person2 = Person2("Kal", 34); //data class instance
val (name, age) = person; //This does not compile and shows error
//Destructuring declaration initializer of type Employee must have a 'component1()' function
//Destructuring declaration initializer of type Employee must have a 'component2()' function
val (name2, age2) = person2; //no problem here
}