Skip to content

Commit 4fc201b

Browse files
KonstantinKhane5LA
andauthored
feat: Kotlin Data classes features (#48)
Co-authored-by: e5LA <[email protected]>
1 parent b4514ee commit 4fc201b

File tree

3 files changed

+310
-0
lines changed

3 files changed

+310
-0
lines changed
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Data classes can't be abstract, open, sealed, or inner.
2+
// The primary constructor must have at least one parameter.
3+
4+
package codecollection.kotlinfeatures
5+
6+
data class Person(
7+
val firstName: String,
8+
val age: Int,
9+
val email: String,
10+
)
11+
12+
// Attention! Using 'var' is not recommended in the data classes. See the example.
13+
data class MutablePerson(
14+
var firstName: String,
15+
var age: Int,
16+
var email: String,
17+
)
18+
19+
data class Address(
20+
var city: String,
21+
var street: String,
22+
)
23+
24+
data class Employee(
25+
val position: String = "Intern", // default property
26+
val person: Person,
27+
val address: Address,
28+
)
29+
30+
fun main() {
31+
println("=== BASIC DATA CLASS FEATURES ===")
32+
33+
// Create instances
34+
val person1 = Person(firstName = "Alice", age = 30, email = "[email protected]")
35+
val person2 = Person(firstName = "Bob", age = 32, email = "[email protected]")
36+
val person3 = Person(firstName = "Alice", age = 30, email = "[email protected]")
37+
38+
demonstrateToString(person1, person2)
39+
demonstrateToEquals(person1, person2, person3)
40+
demonstrateToHashCode(person1, person2, person3)
41+
demonstrateToCopy(person1)
42+
demonstrateToDestructuring(person1)
43+
}
44+
45+
fun demonstrateToString(person1: Person, person2: Person) {
46+
// toString() - automatically generated
47+
println("\n=== toString() ===")
48+
println("Person 1: $person1")
49+
println("Person 2: $person2")
50+
// Output:
51+
// Person 1: Person(firstName=Alice, age=30, [email protected])
52+
// Person 2: Person(firstName=Bob, age=32, [email protected])
53+
}
54+
55+
fun demonstrateToEquals(person1: Person, person2: Person, person3: Person) {
56+
// equals() - automatically generated
57+
println("\n=== equals() ===")
58+
println("person1 == person3: ${person1 == person3}") // true
59+
println("person1 == person2: ${person1 == person2}") // false
60+
}
61+
62+
fun demonstrateToHashCode(person1: Person, person2: Person, person3: Person) {
63+
// hashCode() - automatically generated
64+
println("\n=== hashCode() ===")
65+
println("person1.hashCode() equals person3.hashCode(): ${person1.hashCode() == person3.hashCode()}") // true
66+
67+
// Checking in HashSet
68+
println("\n=== Checking in HashSet ===")
69+
val peopleSet = hashSetOf(person1, person2)
70+
println("set contains person1: ${peopleSet.contains(person1)}") // true
71+
println("set contains person3: ${peopleSet.contains(person3)}") // true, because hashCode() and equals() are the same
72+
// Output:
73+
// set contains person1: true
74+
// set contains person3: true
75+
76+
// Checking in HashMap
77+
println("\n=== Checking in HashMap ===")
78+
79+
val salary =
80+
hashMapOf(
81+
person1 to 30000,
82+
person2 to 27000,
83+
)
84+
85+
println("person1 salary: ${salary[person1]}") // 30000
86+
println("person3 salary: ${salary[person3]}") // 30000, because person3 equals person1 (same hashCode and equals)
87+
88+
// Important! The use "var" is not recommended in the data classes.
89+
println("\n=== Mutation properties: ===")
90+
val mutablePerson = MutablePerson("Charlie", 35, "[email protected]")
91+
val roleMap = hashMapOf(mutablePerson to "Developer")
92+
println("before mutation: ${mutablePerson.hashCode()}")
93+
mutablePerson.age = 36
94+
println("after mutation: ${mutablePerson.hashCode()}") // Different!
95+
println("Map size: ${roleMap.size}") // Map still contains the entry but can't find it.
96+
}
97+
98+
fun demonstrateToCopy(person1: Person) {
99+
// copy() - automatically generated.
100+
// Attention! It is shallow copy. See example
101+
println("\n=== copy() ===")
102+
103+
val person1Updated = person1.copy(age = 31, email = "[email protected]")
104+
105+
println("Updated person 1: $person1Updated")
106+
// Output: Updated person 1: Person(firstName=Alice, age=31, [email protected])
107+
108+
println("\n=== Shallow copy: ===")
109+
val originalAddress = Address("New York", "Washington Street")
110+
val employee1 = Employee("Developer", person1, originalAddress)
111+
val employee2 = employee1.copy()
112+
println("before mutation:")
113+
println("employee1: $employee1")
114+
println("employee2: $employee2")
115+
// employee1: Employee(position=Developer, person=Person(firstName=Alice, age=30, [email protected]), address=Address(city=New York, street=Washington Street))
116+
// employee2: Employee(position=Developer, person=Person(firstName=Alice, age=30, [email protected]), address=Address(city=New York, street=Washington Street))
117+
118+
employee2.address.street = "Park Avenue"
119+
println("after mutation:")
120+
println("employee1: $employee1") // The street has changed too!
121+
println("employee2: $employee2")
122+
// employee1: Employee(position=Developer, person=Person(firstName=Alice, age=30, [email protected]), address=Address(city=New York, street=Park Avenue))
123+
// employee2: Employee(position=Developer, person=Person(firstName=Alice, age=30, [email protected]), address=Address(city=New York, street=Park Avenue))
124+
}
125+
126+
fun demonstrateToDestructuring(person1: Person) {
127+
// Destructuring - automatically generated componentN() functions corresponding to the properties in their order of declaration.
128+
println("\n=== Destructuring: ===")
129+
val (name, age, email) = person1
130+
println("Destructured: name=$name, age=$age, email=$email")
131+
// Output: Destructured: name=Alice, age=30, [email protected]
132+
133+
// You can also use componentN() functions directly
134+
println("name: ${person1.component1()}")
135+
println("age: ${person1.component2()}")
136+
println("email: ${person1.component3()}")
137+
// Output:
138+
// name: Alice
139+
// age: 30
140+
141+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# 🛠️ Kotlin Features
2+
3+
This package contains examples of **Kotlin features**.
4+
5+
Each feature is implemented in its own file and is tested with a corresponding unit test - keeping the code clean, minimal, and easy to understand.
6+
7+
Whether you're learning, referencing, or contributing - this collection is for you.
8+
9+
---
10+
11+
## 🗂️ Available Features
12+
13+
| Feature Name | Description | File |
14+
|-----------------------|----------------------------|--------------------------------|
15+
| Data classes | Kotlin data class features | [`DataClass.kt`](DataClass.kt) |
16+
| _...more coming soon_ |
17+
18+
---
19+
20+
## 🙌 Want to Help?
21+
22+
- Check out the [issues labeled `type: kotlinfeature`](https://github.com/e5LA/kotlin-code-collection/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22type%3A%20kotlinfeature%22)
23+
- Or submit your own idea as new [Issue](https://github.com/e5LA/kotlin-code-collection/issues/new)!
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package codecollection.kotlinfeatures
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
import kotlin.test.assertNotEquals
6+
import kotlin.test.assertNotSame
7+
import kotlin.test.assertNull
8+
import kotlin.test.assertSame
9+
import kotlin.test.assertTrue
10+
11+
class DataClassTest {
12+
val person1 = Person(firstName = "Alice", age = 30, email = "[email protected]")
13+
val person2 = Person(firstName = "Bob", age = 32, email = "[email protected]")
14+
val person3 = Person(firstName = "Alice", age = 30, email = "[email protected]")
15+
16+
val mutablePerson = MutablePerson("Charlie", 35, "[email protected]")
17+
18+
val address = Address("New York", "Washington Street")
19+
20+
val employee1 = Employee("Developer", person1, address)
21+
val employeeDefault = Employee(person = person1, address = address)
22+
23+
@Test
24+
fun `should create person with correct properties`() {
25+
assertEquals("Alice", person1.firstName)
26+
assertEquals(30, person1.age)
27+
assertEquals("[email protected]", person1.email)
28+
}
29+
30+
@Test
31+
fun `toString() should return formatted string`() {
32+
assertEquals(
33+
"Person(firstName=Alice, age=30, [email protected])",
34+
person1.toString(),
35+
)
36+
}
37+
38+
@Test
39+
fun `equals should work correctly for same content`() {
40+
assertEquals(person1, person3)
41+
assertNotEquals(person1, person2)
42+
}
43+
44+
@Test
45+
fun `hashCode should be same for equal objects`() {
46+
assertEquals(person1.hashCode(), person3.hashCode())
47+
assertNotEquals(person1.hashCode(), person2.hashCode())
48+
}
49+
50+
@Test
51+
fun `should work correctly in hashset`() {
52+
val peopleSet = hashSetOf(person1, person2)
53+
assertTrue(peopleSet.contains(person1))
54+
assertTrue(peopleSet.contains(person2))
55+
assertTrue(peopleSet.contains(person3))
56+
assertEquals(2, peopleSet.size)
57+
}
58+
59+
@Test
60+
fun `should work correctly in hashmap`() {
61+
val salary =
62+
hashMapOf(
63+
person1 to 30000,
64+
person2 to 27000,
65+
)
66+
assertEquals(30000, salary[person1])
67+
assertEquals(27000, salary[person2])
68+
assertEquals(30000, salary[person3])
69+
assertEquals(2, salary.size)
70+
}
71+
72+
@Test
73+
fun `mutation var properties should break hashmap key`() {
74+
val testMap = hashMapOf(mutablePerson to "Developer")
75+
assertEquals("Developer", testMap[mutablePerson])
76+
mutablePerson.age = 36
77+
assertNull(testMap[mutablePerson])
78+
}
79+
80+
@Test
81+
fun `mutation var properties should change hashCode()`() {
82+
val originalHashCode = mutablePerson.hashCode()
83+
mutablePerson.age = 36
84+
val newHashCode = mutablePerson.hashCode()
85+
assertNotEquals(originalHashCode, newHashCode)
86+
}
87+
88+
@Test
89+
fun `copy() should create new instance with identical properties`() {
90+
val copy = person1.copy()
91+
assertEquals(person1, copy)
92+
assertNotSame(person1, copy)
93+
}
94+
95+
@Test
96+
fun `copy() should create new instance with updated properties`() {
97+
val updated = person1.copy(email = "[email protected]")
98+
assertEquals(person1.firstName, updated.firstName)
99+
assertEquals(person1.age, updated.age)
100+
assertEquals("[email protected]", updated.email)
101+
assertEquals("[email protected]", person1.email)
102+
}
103+
104+
@Test
105+
fun `copy() should perform shallow copy of reference types`() {
106+
val employee2 = employee1.copy()
107+
108+
assertNotSame(employee1, employee2)
109+
assertSame(employee1.person, employee2.person)
110+
assertSame(employee1.address, employee2.address)
111+
}
112+
113+
@Test
114+
fun `mutation of referenced objects should affect the original and its copy`() {
115+
val employee2 = employee1.copy()
116+
employee2.address.street = "Park Avenue"
117+
assertEquals("Park Avenue", employee1.address.street)
118+
assertEquals("Park Avenue", employee2.address.street)
119+
}
120+
121+
@Test
122+
fun `should use default value when not specified`() {
123+
assertEquals("Intern", employeeDefault.position)
124+
}
125+
126+
@Test
127+
fun `copy() use default value`() {
128+
val copiedEmployee = employeeDefault.copy()
129+
assertEquals("Intern", copiedEmployee.position)
130+
}
131+
132+
@Test
133+
fun `destructuring should work correctly`() {
134+
val (name, age, email) = person1
135+
assertEquals("Alice", name)
136+
assertEquals(30, age)
137+
assertEquals("[email protected]", email)
138+
}
139+
140+
@Test
141+
fun `componentN() should work correctly`() {
142+
assertEquals("Alice", person1.component1())
143+
assertEquals(30, person1.component2())
144+
assertEquals("[email protected]", person1.component3())
145+
}
146+
}

0 commit comments

Comments
 (0)