https://hyperskill.
org/learn/step/8526
Computer science
Programming languages
Kotlin
Object-oriented programming
Object-oriented programming usage
Data class
Data class
Copy and ComponentN
Idiom
Conclusion
Related topics
Discussion
7 minutes read
How to make a simple class for storing data? In addition to storing information, it should be able to
compare and copy objects. Also, it would be very convenient to output data immediately. Normally,
for this functionality the class must have some methods: equals() and hashCode() for
comparison, copy() for copying, toString() for the string representation of the object and
componentN() functions corresponding to the properties in their order of declaration. But in
Kotlin you don't need to implement all of these functions, you can simply use the data class. Let’s
take a closer look at this type of class.
Data class
First of all, we need a class, so here is a nice Client class:
class Client(val name: String, val age: Int, val gender: String)
Right now it has 3 properties, so far so good! But in order to properly compare the objects (i.e., by
their properties) we need to implement equals() and hashCode() functions:
class Client(val name: String, val age: Int, val gender: String) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Client
if (name != other.name) return false
if (age != other.age) return false
if (gender != other.gender) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + age
result = 31 * result + gender.hashCode()
return result
}
}
Why do we need such a long piece of code just for standard stuff? That's the right question because
with the data class we can simplify it like this:
data class Client(val name: String, val age: Int, val gender: String)
Wait! Where are all my functions now? Actually, with the data keyword, you don't need them
anymore. It will magically work as if you have already implemented them all. This keyword will
also provide toString() ,copy() and componentN() functions with default behavior
(equals and hashCode). We'll look at copy() and componentN()a bit later, but right now you
need to remember several rules here:
1. You can only count on properties that are inside the constructor. For example, this modified
Client class:
data class Client(val name: String, val age: Int, val gender: String) {
var balance: Int = 0
}
All those functions won't consider balance field, because it isn't inside the constructor.
2. You can override all those functions, except for copy():
data class Client(val name: String, val age: Int, val gender: String) {
var balance: Int = 0
override fun toString(): String {
return "Client(name='$name', age=$age, gender='$gender',
balance=$balance)"
}
Now balance field is involved in the toString() function.
3. The primary constructor of a data class must have at least one parameter and all of those
parameters must be val or var.
Copy and ComponentN
To be honest, there isn't really a convenient way to copy an object in Java, but Kotlin is different.
For example, what if we have an instance of our Client class and we want the exact same client,
just with a different name? Easy!
fun main() {
val bob = Client("Bob", 29, "Male")
val john = bob.copy(name = "John")
println(bob)
println(john)
}
As you may see, we just used our copy() function, which will be provided automatically with the
data keyword. And the output will be the following:
Client(name='Bob', age=29, gender='Male', balance=0)
Client(name='John', age=29, gender='Male', balance=0)
The componentN() functions correspond to the properties in their order of declaration:
component1(), component2(), ..., for all data class properties. The Component functions generated
for data classes make it possible to use them in destructuring declarations
fun main() {
val bob = Client("Bob", 29, "Male")
println(bob.name) // Bob
println(bob.component1()) // Bob
println(bob.age) // 29
println(bob.component2()) // 29
println(bob.gender) // Male
println(bob.component3()) // Male
// destructuring
val (name, age, gender) = bob
println(name) // Bob
println(age) // 29
println(gender) // Male
}
Idiom
As we've demonstrated, the data class is a convenient way to organize data or to create DTOs (Data
Transfer Objects). So use it, with community approval!
data class Customer(val name: String, val email: String)
Conclusion
Now you know how to simplify boilerplate code with the data keyword. It helps not only to
shorten your code but also to save your time. Use it wisely!
Comments
UNDERSCORE USING
Just an addition for the componentN / destructured declaration section:As stated in
https://kotlinlang.org/docs/destructuring-declarations.html#underscore-for-unused-variables you
can "skip" components you aren't interested in by replacing them with a underscore( _ ) , as it just
goes from left to right instead of accessing specific properties. You also can ignore if there are more
components as you need to the right as long as you have enough to satisfy your declaration.So val
(name, age, gender) = bob could be written as val (_, age) = bob if all you want is the age. Of
course in that case it would be better to simply use bob.age but I bet this will be handy at some
point.
Q2: What is the need of "other as Client"?
Before this type casting is done the function equals(other: Any?) treats other as Any?To access the
objects attributes to compare (name, age, gender) the compiler needs to know that the type of other
should be Client.https://kotlinlang.org/docs/typecasts.html#unsafe-cast-operator To prevent an
exception in case other is null the check for the objects class is doneif (javaClass !=
other?.javaClass) return falsehttps://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/java-class.html
Q1: Why in this portion "if (this === other) return true" if === is replaced with == is a recursive
call?
Because the == is a check for equals(). Therefore calling it inside the overridden function will lead
to a recursive call of this function and hence a stack
overflow.https://kotlinlang.org/docs/equality.html
(file docx saved “why_choose_kotlin_over_java.docx”)
IDIOMS in Kotlin:
1. Creating Data Transfer Object (DTOs)
2. Default Value for Function Parameters
3. Filtering a List
4. Checking the presence of an element in a Collection
5. String Interpolation
6. if Expression
7. execute if not null
8. map nullable value if not null
9. execute a statement if null
10. if-not-null shorthanbd
11. if-not-null-else shorthand
12. try-catch expression
13. return on when statement
14. get first item of a possibly empty collection
➺Creating Data Transfer Object (DTOs)
POJOs : Plain Old Java Objects (used in Java)
POCOs : Plain Old CLR Objects (used in .NET)
When working with Kotlin, you’ll primarily use data classes to represent data entities, which
serve a similar purpose to POJOs in Java and POCOs in .NET.
Data classes in Kotlin automatically generate equals(), hashCode(), toString(), and copy()
methods, making them ideal for representing data transfer objects.
Press enter or click to view image in full size
data class Customer(val name: String, val email: String)
1. provides a Customer class with the following functionality:
2. getters (and setters in case of var s) for all properties
3. equals()
4. hashCode()
5. toString()
6. copy()
7. component1(), component2(), ..., for all properties (see Data classes)
IDIOMS
https://kotlinlang.org/docs/idioms.html
Create DTOs (POJOs/POCOs)
data class Customer(val name: String, val email: String)
provides a Customer class with the following functionality:
getters (and setters in case of vars) for all properties
equals()
hashCode()
toString()
copy()
component1(), component2(), ..., for all properties (see Data classes)
Default values for function parameters
fun foo(a: Int = 0, b: String = "") { ... }
Filter a list
val positives = list.filter { x -> x > 0 }
Or alternatively, even shorter:
val positives = list.filter { it > 0 }
Learn the difference between Java and Kotlin filtering.
Check the presence of an element in a collection
if ("
[email protected]" in emailsList) { ... }
String interpolation
println("Name $name")
Learn the difference between Java and Kotlin string concatenation.
Read standard input safely
// Reads a string and returns null if the input can't be converted into an integer. For
example: Hi there!
val wrongInt = readln().toIntOrNull()
println(wrongInt)
// null
// Reads a string that can be converted into an integer and returns an integer. For
example: 13
val correctInt = readln().toIntOrNull()
println(correctInt)
// 13
For more information, see Read standard input.
Instance checks
when (x) {
is Foo -> ...
is Bar -> ...
else -> ...
}
Read-only list
val list = listOf("a", "b", "c")
Read-only map
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
Access a map entry
println(map["key"])
map["key"] = value
Traverse a map or a list of pairs
for ((k, v) in map) {
println("$k -> $v")
}
k and v can be any convenient names, such as name and age.
Iterate over a range
for (i in 1..100) { ... } // closed-ended range: includes 100
for (i in 1..<100) { ... } // open-ended range: does not include 100
for (x in 2..10 step 2) { ... }
for (x in 10 downTo 1) { ... }
(1..10).forEach { ... }
Lazy property
val p: String by lazy { // the value is computed only on first access
// compute the string
}
Extension functions
fun String.spaceToCamelCase() { ... }
"Convert this to camelcase".spaceToCamelCase()
Create a singleton
object Resource {
val name = "Name"
}
Use inline value classes for
type-safe values
@JvmInline
value class EmployeeId(private val id: String)
@JvmInline
value class CustomerId(private val id: String)
If you accidentally mix up EmployeeId and CustomerId, a compilation error is
triggered.
The @JvmInline annotation is only needed for JVM backends.
Instantiate an abstract class
abstract class MyAbstractClass {
abstract fun doSomething()
abstract fun sleep()
}
fun main() {
val myObject = object : MyAbstractClass() {
override fun doSomething() {
// ...
}
override fun sleep() { // ...
}
}
myObject.doSomething()
}
If-not-null shorthand
val files = File("Test").listFiles()
println(files?.size) // size is printed if files is not null
If-not-null-else shorthand
val files = File("Test").listFiles()
// For simple fallback values:
println(files?.size ?: "empty") // if files is null, this prints "empty"
// To calculate a more complicated fallback value in a code block, use `run`
val filesSize = files?.size ?: run {
val someSize = getSomeSize()
someSize * 2
}
println(filesSize)
Execute an expression if null
val values = ...
val email = values["email"] ?: throw IllegalStateException("Email is missing!")
Get first item of a possibly
empty collection
val emails = ... // might be empty
val mainEmail = emails.firstOrNull() ?: ""
Learn the difference between Java and Kotlin first item getting.
Execute if not null
val value = ...
value?.let {
... // execute this block if not null
}
Execute if not null
val value = ...
value?.let {
... // execute this block if not null
}
Map nullable value if not null
val value = ...
val mapped = value?.let { transformValue(it) } ?: defaultValue
// defaultValue is returned if the value or the transform result is null.
Return on when statement
fun transform(color: String): Int {
return when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}
}
try-catch expression
fun test() {
val result = try {
count()
} catch (e: ArithmeticException) {
throw IllegalStateException(e)
}
// Working with result
}
if expression
val y = if (x == 1) {
"one"
} else if (x == 2) {
"two"
} else {
"other"
}
Builder-style usage of methods
that return Unit
fun arrayOfMinusOnes(size: Int): IntArray {
return IntArray(size).apply { fill(-1) }
}
Single-expression functions
fun theAnswer() = 42
This is equivalent to
fun theAnswer(): Int {
return 42
}
This can be effectively combined with other idioms, leading to shorter code. For example, with
the when expression:
fun transform(color: String): Int = when (color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}
Call multiple methods on an
object instance (with)
class Turtle {
fun penDown()
fun penUp()
fun turn(degrees: Double)
fun forward(pixels: Double)
}
val myTurtle = Turtle()
with(myTurtle) { //draw a 100 pix square
penDown()
for (i in 1..4) {
forward(100.0)
turn(90.0)
}
penUp()
}
Configure properties of an
object (apply)
val myRectangle = Rectangle().apply {
length = 4
breadth = 5
color = 0xFAFAFA
}
This is useful for configuring properties that aren't present in the object constructor.
Java 7's try-with-resources
val stream = Files.newInputStream(Paths.get("/some/file.txt"))
stream.buffered().reader().use { reader ->
println(reader.readText())
}
Generic function that requires
the generic type information
// public final class Gson {
// ...
// public <T> T fromJson(JsonElement json, Class<T> classOfT) throws
JsonSyntaxException {
// ...
inline fun <reified T: Any> Gson.fromJson(json: JsonElement): T =
this.fromJson(json, T::class.java)
Swap two variables
var a = 1
var b = 2
a = b.also { b = a }
Mark code as incomplete
(TODO)
Kotlin's standard library has a TODO() function that will always throw a NotImplementedError.
Its return type is Nothing so it can be used regardless of expected type. There's also an overload
that accepts a reason parameter:
fun calcTaxes(): BigDecimal = TODO("Waiting for feedback from accounting")
IntelliJ IDEA's kotlin plugin understands the semantics of TODO() and automatically adds a code
pointer in the TODO tool window.
What's next?
Solve Advent of Code puzzles using the idiomatic Kotlin style.
Learn how to perform typical tasks with strings in Java and Kotlin.
Learn how to perform typical tasks with collections in Java and Kotlin.
Learn how to handle nullability in Java and Kotlin.
copy() is not overridable