12

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?

enter image description here

lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228
josedlujan
  • 5,357
  • 2
  • 27
  • 49

4 Answers4

26

Difference in 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 string

Code 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
}

Difference in constructors

another difference between definitions 1 & 2 and definition 3 is that:

  • definitions 1 & 2 both have a constructor that takes 2 parameters
  • definition 3 only has a no argument constructor that assigns default values to the class members.

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
}

The copy method

Finally, 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!

Eric
  • 16,397
  • 8
  • 68
  • 76
  • The most important is the difference between definition 1 and definition 2, without the data i have the same result ? i try and is the same. – josedlujan Jul 13 '17 at 00:20
  • the most important difference between definition 1 and definition 2 is that the `equals`, `hashcode` and `tostring` methods are generated for you in definition 1. i have edited the answer to show an example of how they differ – Eric Jul 13 '17 at 00:30
  • I saw the hascode, equals and tostring in the definition 1 and definition 2. – josedlujan Jul 13 '17 at 00:37
  • oh yes, that's true. It is how they behave that is different. for example, if I use definition 1 to create two objects that have the same `name` and `age`, the statement `person1 == person2` will equal to `true`. but, if i used definition 2, the same statement would be `false`. this is because the equals method for data classes tests for structural equality. (that all members in both objects are the same) – Eric Jul 13 '17 at 00:40
  • Thanks a lot for your patient, why is false? – josedlujan Jul 13 '17 at 00:48
  • my pleasure :). the default `equals` method for normal classes test if two references are aliases, but not structural equality. for example, if i do `val person1 = Person1("alice",22); val person2 = person1`, then `person1 == person2` will be `true`... in the last example in my older comment, it was `false` because `person1` and `person2` were pointing to different objects. I have updated my answer. please take a look if you have not seen already. – Eric Jul 13 '17 at 00:57
  • sorry, i meant to write `val person1 = Person2("alice",22); val person2 = person1;` in my last comment...(using `Person2` instead of `Person1`) – Eric Jul 13 '17 at 05:44
  • 1
    Thanks a lot Eric. – josedlujan Jul 13 '17 at 19:46
1

Briefly speaking:

  • Definition 1: this is a data class which primary constructor takes two parameters name and age
  • Definition 2: this is just a class which primary constructor takes two parameters name and age
  • Definition 3: this is a class which constructor has no arguments and assigns default values "" and 1 to the properties name and age respectively

Detailed answered

What is important to understand here is the concept of data class.

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:

Note: 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:

  • Definition 1: you do not need to implement the universal object methods and you have also the copy() method ready to use
  • Definition 2: you will have to implement the universal object methods and the copy() method if you need them
  • Definition 3: you will have to implement the universal object methods and the copy() method if you need them, and there is no point in implementing the copy() method since your primary constructor has no parameters

Even 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.

lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228
1

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

}
Panagiotis Bougioukos
  • 15,955
  • 2
  • 30
  • 47
0

Definition 1 (data class Person(var name: String, var age: Int) is the equivalent of

/* 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)

Definition 2 (class Person(var name: String, var age: Int [no data modifier]) is the equivalent of

class Person(var name: String, var age: Int) {
}

val person = Person("Name", 123)

Definition 3 is the equivalent of

class Person() {
    var name: String = ""
    var age: Int = 1
}

val person = Person() // name = "", age = 1 (by default)
person.name = "Name"
person.age = 123
Mibac
  • 8,990
  • 5
  • 33
  • 57
  • Why I saw the hashcode, equals and tostring in the definition 1 and definition 2 ? – josedlujan Jul 13 '17 at 00:39
  • @josedlujan What do you mean? `hashCode`, `equals` and `toString` are automatically generated for classes with `data` modifier – Mibac Jul 13 '17 at 00:42
  • The picture in my question show the same when i use definition 1 and definition 2, both answers has hascode, equals and toString with or without data modifier. – josedlujan Jul 13 '17 at 00:46
  • @josedlujan Yeah they do because every class has it but when you don't override it then it won't do what you expect it to do. Try running this: ```val p1 = Person("", 0); val p2 = Person("", 0); fun main(args: Array) { println(p1 == p2) }```. This will print `false` even though these two persons are equal, right? That's the problem and that's why you need to override `equals` – Mibac Jul 13 '17 at 00:49