diff --git a/src/main/kotlin/codecollection/kotlinfeatures/DataClass.kt b/src/main/kotlin/codecollection/kotlinfeatures/DataClass.kt new file mode 100644 index 0000000..2573072 --- /dev/null +++ b/src/main/kotlin/codecollection/kotlinfeatures/DataClass.kt @@ -0,0 +1,141 @@ +// Data classes can't be abstract, open, sealed, or inner. +// The primary constructor must have at least one parameter. + +package codecollection.kotlinfeatures + +data class Person( + val firstName: String, + val age: Int, + val email: String, +) + +// Attention! Using 'var' is not recommended in the data classes. See the example. +data class MutablePerson( + var firstName: String, + var age: Int, + var email: String, +) + +data class Address( + var city: String, + var street: String, +) + +data class Employee( + val position: String = "Intern", // default property + val person: Person, + val address: Address, +) + +fun main() { + println("=== BASIC DATA CLASS FEATURES ===") + + // Create instances + val person1 = Person(firstName = "Alice", age = 30, email = "alice@example.com") + val person2 = Person(firstName = "Bob", age = 32, email = "bob@example.com") + val person3 = Person(firstName = "Alice", age = 30, email = "alice@example.com") + + demonstrateToString(person1, person2) + demonstrateToEquals(person1, person2, person3) + demonstrateToHashCode(person1, person2, person3) + demonstrateToCopy(person1) + demonstrateToDestructuring(person1) +} + +fun demonstrateToString(person1: Person, person2: Person) { + // toString() - automatically generated + println("\n=== toString() ===") + println("Person 1: $person1") + println("Person 2: $person2") + // Output: + // Person 1: Person(firstName=Alice, age=30, email=alice@example.com) + // Person 2: Person(firstName=Bob, age=32, email=bob@example.com) +} + +fun demonstrateToEquals(person1: Person, person2: Person, person3: Person) { + // equals() - automatically generated + println("\n=== equals() ===") + println("person1 == person3: ${person1 == person3}") // true + println("person1 == person2: ${person1 == person2}") // false +} + +fun demonstrateToHashCode(person1: Person, person2: Person, person3: Person) { + // hashCode() - automatically generated + println("\n=== hashCode() ===") + println("person1.hashCode() equals person3.hashCode(): ${person1.hashCode() == person3.hashCode()}") // true + + // Checking in HashSet + println("\n=== Checking in HashSet ===") + val peopleSet = hashSetOf(person1, person2) + println("set contains person1: ${peopleSet.contains(person1)}") // true + println("set contains person3: ${peopleSet.contains(person3)}") // true, because hashCode() and equals() are the same + // Output: + // set contains person1: true + // set contains person3: true + + // Checking in HashMap + println("\n=== Checking in HashMap ===") + + val salary = + hashMapOf( + person1 to 30000, + person2 to 27000, + ) + + println("person1 salary: ${salary[person1]}") // 30000 + println("person3 salary: ${salary[person3]}") // 30000, because person3 equals person1 (same hashCode and equals) + + // Important! The use "var" is not recommended in the data classes. + println("\n=== Mutation properties: ===") + val mutablePerson = MutablePerson("Charlie", 35, "charlie@example.com") + val roleMap = hashMapOf(mutablePerson to "Developer") + println("before mutation: ${mutablePerson.hashCode()}") + mutablePerson.age = 36 + println("after mutation: ${mutablePerson.hashCode()}") // Different! + println("Map size: ${roleMap.size}") // Map still contains the entry but can't find it. +} + +fun demonstrateToCopy(person1: Person) { + // copy() - automatically generated. + // Attention! It is shallow copy. See example + println("\n=== copy() ===") + + val person1Updated = person1.copy(age = 31, email = "new.alice@example.com") + + println("Updated person 1: $person1Updated") + // Output: Updated person 1: Person(firstName=Alice, age=31, email=new.alice@example.com) + + println("\n=== Shallow copy: ===") + val originalAddress = Address("New York", "Washington Street") + val employee1 = Employee("Developer", person1, originalAddress) + val employee2 = employee1.copy() + println("before mutation:") + println("employee1: $employee1") + println("employee2: $employee2") + // employee1: Employee(position=Developer, person=Person(firstName=Alice, age=30, email=alice@example.com), address=Address(city=New York, street=Washington Street)) + // employee2: Employee(position=Developer, person=Person(firstName=Alice, age=30, email=alice@example.com), address=Address(city=New York, street=Washington Street)) + + employee2.address.street = "Park Avenue" + println("after mutation:") + println("employee1: $employee1") // The street has changed too! + println("employee2: $employee2") + // employee1: Employee(position=Developer, person=Person(firstName=Alice, age=30, email=alice@example.com), address=Address(city=New York, street=Park Avenue)) + // employee2: Employee(position=Developer, person=Person(firstName=Alice, age=30, email=alice@example.com), address=Address(city=New York, street=Park Avenue)) +} + +fun demonstrateToDestructuring(person1: Person) { + // Destructuring - automatically generated componentN() functions corresponding to the properties in their order of declaration. + println("\n=== Destructuring: ===") + val (name, age, email) = person1 + println("Destructured: name=$name, age=$age, email=$email") + // Output: Destructured: name=Alice, age=30, email=alice@example.com + + // You can also use componentN() functions directly + println("name: ${person1.component1()}") + println("age: ${person1.component2()}") + println("email: ${person1.component3()}") + // Output: + // name: Alice + // age: 30 + // email: alice@example.com +} diff --git a/src/main/kotlin/codecollection/kotlinfeatures/README.md b/src/main/kotlin/codecollection/kotlinfeatures/README.md new file mode 100644 index 0000000..eba06a8 --- /dev/null +++ b/src/main/kotlin/codecollection/kotlinfeatures/README.md @@ -0,0 +1,23 @@ +# 🛠️ Kotlin Features + +This package contains examples of **Kotlin features**. + +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. + +Whether you're learning, referencing, or contributing - this collection is for you. + +--- + +## 🗂️ Available Features + +| Feature Name | Description | File | +|-----------------------|----------------------------|--------------------------------| +| Data classes | Kotlin data class features | [`DataClass.kt`](DataClass.kt) | +| _...more coming soon_ | + +--- + +## 🙌 Want to Help? + +- 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) +- Or submit your own idea as new [Issue](https://github.com/e5LA/kotlin-code-collection/issues/new)! \ No newline at end of file diff --git a/src/test/kotlin/codecollection/kotlinfeatures/DataClassTest.kt b/src/test/kotlin/codecollection/kotlinfeatures/DataClassTest.kt new file mode 100644 index 0000000..b81f3c5 --- /dev/null +++ b/src/test/kotlin/codecollection/kotlinfeatures/DataClassTest.kt @@ -0,0 +1,146 @@ +package codecollection.kotlinfeatures + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertNotSame +import kotlin.test.assertNull +import kotlin.test.assertSame +import kotlin.test.assertTrue + +class DataClassTest { + val person1 = Person(firstName = "Alice", age = 30, email = "alice@example.com") + val person2 = Person(firstName = "Bob", age = 32, email = "bob@example.com") + val person3 = Person(firstName = "Alice", age = 30, email = "alice@example.com") + + val mutablePerson = MutablePerson("Charlie", 35, "charlie@example.com") + + val address = Address("New York", "Washington Street") + + val employee1 = Employee("Developer", person1, address) + val employeeDefault = Employee(person = person1, address = address) + + @Test + fun `should create person with correct properties`() { + assertEquals("Alice", person1.firstName) + assertEquals(30, person1.age) + assertEquals("alice@example.com", person1.email) + } + + @Test + fun `toString() should return formatted string`() { + assertEquals( + "Person(firstName=Alice, age=30, email=alice@example.com)", + person1.toString(), + ) + } + + @Test + fun `equals should work correctly for same content`() { + assertEquals(person1, person3) + assertNotEquals(person1, person2) + } + + @Test + fun `hashCode should be same for equal objects`() { + assertEquals(person1.hashCode(), person3.hashCode()) + assertNotEquals(person1.hashCode(), person2.hashCode()) + } + + @Test + fun `should work correctly in hashset`() { + val peopleSet = hashSetOf(person1, person2) + assertTrue(peopleSet.contains(person1)) + assertTrue(peopleSet.contains(person2)) + assertTrue(peopleSet.contains(person3)) + assertEquals(2, peopleSet.size) + } + + @Test + fun `should work correctly in hashmap`() { + val salary = + hashMapOf( + person1 to 30000, + person2 to 27000, + ) + assertEquals(30000, salary[person1]) + assertEquals(27000, salary[person2]) + assertEquals(30000, salary[person3]) + assertEquals(2, salary.size) + } + + @Test + fun `mutation var properties should break hashmap key`() { + val testMap = hashMapOf(mutablePerson to "Developer") + assertEquals("Developer", testMap[mutablePerson]) + mutablePerson.age = 36 + assertNull(testMap[mutablePerson]) + } + + @Test + fun `mutation var properties should change hashCode()`() { + val originalHashCode = mutablePerson.hashCode() + mutablePerson.age = 36 + val newHashCode = mutablePerson.hashCode() + assertNotEquals(originalHashCode, newHashCode) + } + + @Test + fun `copy() should create new instance with identical properties`() { + val copy = person1.copy() + assertEquals(person1, copy) + assertNotSame(person1, copy) + } + + @Test + fun `copy() should create new instance with updated properties`() { + val updated = person1.copy(email = "new.alice@example.com") + assertEquals(person1.firstName, updated.firstName) + assertEquals(person1.age, updated.age) + assertEquals("new.alice@example.com", updated.email) + assertEquals("alice@example.com", person1.email) + } + + @Test + fun `copy() should perform shallow copy of reference types`() { + val employee2 = employee1.copy() + + assertNotSame(employee1, employee2) + assertSame(employee1.person, employee2.person) + assertSame(employee1.address, employee2.address) + } + + @Test + fun `mutation of referenced objects should affect the original and its copy`() { + val employee2 = employee1.copy() + employee2.address.street = "Park Avenue" + assertEquals("Park Avenue", employee1.address.street) + assertEquals("Park Avenue", employee2.address.street) + } + + @Test + fun `should use default value when not specified`() { + assertEquals("Intern", employeeDefault.position) + } + + @Test + fun `copy() use default value`() { + val copiedEmployee = employeeDefault.copy() + assertEquals("Intern", copiedEmployee.position) + } + + @Test + fun `destructuring should work correctly`() { + val (name, age, email) = person1 + assertEquals("Alice", name) + assertEquals(30, age) + assertEquals("alice@example.com", email) + } + + @Test + fun `componentN() should work correctly`() { + assertEquals("Alice", person1.component1()) + assertEquals(30, person1.component2()) + assertEquals("alice@example.com", person1.component3()) + } +}