What is the difference between:
Definition 1
data class Person (var name:String, var age:Int)
Definition 2
class Person (var name:String, var age:Int)
Definition 3
class Person (){
var name:String = ""
var age:Int = 1
}
In the 3 cases when I use the autocomplete, I saw the same methods available like a POJO... is this the same but in 3 different ways?
equals
, hashCode
, & toString
the most important difference between definition 1 and definitions 2 & 3 is that in definition 1, the equals
, hashcode
and toString
methods are overridden for you:
equals
and hashCode
methods test for structural equality
toString
method returns a nice, human-friendly stringCode example:
NOTE: in Kotlin, the ==
operator calls an object's .equals()
method. see 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
}
another difference between definitions 1 & 2 and definition 3 is that:
Code example:
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
methodFinally, another difference between definition 1 and definitions 2 & 3 is that in definition 1, a copy
method is generated for it. Here's an example of how it can be used:
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!
Briefly speaking:
name
and age
name
and age
""
and 1
to the properties name
and age
respectivelyDetailed answered
What is important to understand here is the concept of data class.
It is very common to create classes whose main purpose is to hold data. If you want your class to be convenient holder for your data you need to override the universal object methods:
toString()
- string representationequals()
- object equalityhashCode()
- hash containersNote: equals()
is used for structural equality and it is often implemented with hashCode()
.
Usually, the implementation of these methods is straightforward, and your IDE can help you to generate them automatically.
However, in Kotlin, you don't have to general all of these boilerplate code. If you add the modifier data
to your class, the necessary methods are automatically added for you. The equals()
and hashCode()
methods take into account all the properties declared in the primary constructor. toString()
will have the following format ClassName(parm1=value1, param2=value2, ...)
.
In addition, when you mark a class as a data class, the method copy()
is also automatically generated which allows you to make copies of an existing instance. This feature is very handy when you are using your instances as keys for a HashMap
or if you are dealing with multithreaded code.
Going back to your question:
copy()
method ready to usecopy()
method if you need themcopy()
method if you need them, and there is no point in implementing the copy()
method since your primary constructor has no parametersEven though the properties of a data class are not required to be val
, i.e., you can use var
as your are doing in your code, it is strongly recommended that you use read-only properties, so that you make the instances immutable.
Finally, componentN()
functions corresponding to the properties in their order of declaration are also generated by the compiler when you mark a class as a data class.
Just to add one more difference that Eric's accepted answer doesn't mention.
Data Classes can participate in destructuring declarations.
So if we have
class Person(val name: String, val age: Int)
data class Person2(val name: String, val age: Int)
And then
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With