- Kotlin data vs open class
- All classes in Kotlin are by default final
- A clash between keyword definitions
- Some scenarios and possible ways to tackle the problem
- Final thoughts
- Kotlin Data Class Inheritance: Extend a Data Class
- Use the data Keyword to Define Class in Kotlin
- Requirements to Declare a Data Class in Kotlin
- Example of Data Class in Kotlin
- Extend Data Class in Kotlin
- Use the Abstract Class to Extend Data Class in Kotlin
- Use the Interface to Extend Data Class in Kotlin
- Use the Open Classes to Extend Data Class in Kotlin
- Классы данных
- Свойства, объявленные в теле класса
- Копирование
- Классы данных и мульти-декларации
- Стандартные классы данных
Kotlin data vs open class
One of the many handy features that the Kotlin language offers is the data keyword. When we declare a class with the data keyword the compiler implements the equals(Object o),hashCode() and toString() methods, thus saving us from the trouble to do it manually. The official documentation provides comprehensive examples and nowadays it has become a widely known language feature, so an example can be left outside of the scope of this post.
All classes in Kotlin are by default final
Another thing that Kotlin does behind the scenes is to compile every class that you declare as final, unless you add the open keyword to it. We can see how the language is influenced from Effective Java 2nd Edition(Edition 3 is out as well!). More specifically, items 16 and 17 state the following:
Item 16: Favor composition over inheritance Item 17: Design and document for inheritance or else prohibit it
A clash between keyword definitions
This is a very nice feature of the language, as it tries to protect us from the various perils of improper use of inheritance, as it is described in the book. Unfortunately, things begin to become messy when you try to declare a class both open and data. The compiler will complain that a data class cannot be at the same time open, giving us an error. Let’s stop for a moment and consider why the architects of the language decided to impose such an obstacle. First of all, we will try to remember some wise words from the Growing object-oriented systems, driven by tests book from Steve Freeman and Nat Pryce. Chapter 2 presents a distinction between values and objects. Objects communicate between each other by sending messages and have some specific behavior depending on their state. If their state changes, so does their behavior. On the other hand values — to paraphrase — are just bags of data, used for computations. Now that the definition and distinction between objects and values is clear, we can see that it does makes sense for Kotlin to impose such rules when using the data and open keywords. A data class is a value, holding only — preferably immutable — data without behavior. Since it makes sense to extend a class in order to change its behavior and the data class has no behavior, why should we be able to extend it? Although the reasons for this design decision are quite valid, the imposed restrictions make the transition from Java to Koltin difficult. We may have in our codebase a class that’s a perfect candidate to become a data class, but if for some reason this class is extended by another class, you cannot make that transition instantly, as you have to fix first the fact that the class is extended. The problem becomes even worse when we have a deep nested hierarchy.
Some scenarios and possible ways to tackle the problem
One way to deal with this, is to check if the class has any fields or not. If it doesn’t have and it just acts as a marker, we could go and delete that class, trace the compiler errors and replace with the parent class. Indeed we can use this technique, provided that child class is not used in any instanceOf checks. In that case the problem becomes even worse especially when the parent class is involved in the instanceOf checks as well. Here we have a behavioural change in the system which we cannot fix just by replacing the check with the parent class, one that is very difficult to change. The sins of the past have finally caught up with us. Now let’s consider the case where the child class extends the parent just to inherit some or all the fields of the parent and adds its own as well. Then we could try to use some kind of composition depending on the case.
Final thoughts
Maybe the nastiest of the problems described above is the instanceOf checks. If we could find a way to get past these checks problem, then replacing the children with the parent or passing the parent as a dependency becomes less difficult. Unfortunately, this post does not provide a final solution, but rather tries to reason with Kotlin’s implementation decisions and explore some thoughts on how to tackle the problems that arise during a Java to Kotlin migration.
Kotlin Data Class Inheritance: Extend a Data Class
- Use the data Keyword to Define Class in Kotlin
- Requirements to Declare a Data Class in Kotlin
- Example of Data Class in Kotlin
- Extend Data Class in Kotlin
- Use the Abstract Class to Extend Data Class in Kotlin
- Use the Interface to Extend Data Class in Kotlin
- Use the Open Classes to Extend Data Class in Kotlin
The data class in Kotlin is the class that holds an object’s data. This tutorial will show how to extend a data class to leverage the concept of inheritance in Kotlin.
Use the data Keyword to Define Class in Kotlin
data class Tutorials (var name: String, val year: Int)
Declaring a data class in Kotlin automatically generates functions like equals() , toString() , and hashcode() .
Requirements to Declare a Data Class in Kotlin
- There should be one or more parameters in the primary constructor .
- The parameters should be initialized as var or val .
- We cannot declare the class as abstract , open , sealed , or inner .
Example of Data Class in Kotlin
Use the main function to declare variable properties in data class .
data class Tutorial(val tutorial_name: String, val year: Int) fun main(args: Array) val tut = Tutorial("Kotlin", 2022) println("Tutorial Name = $ ") println("Year = $ ") >
Tutorial Name = Kotlin Year = 2022
Extend Data Class in Kotlin
Data classes are the replacements of POJOs in Java. Hence, it is natural to think that they would allow for inheritance in Java and Kotlin.
The inheritance of data classes in Kotlin doesn’t execute well. Hence, it is advised not to use inheritance by extending the data class in Kotlin.
But we can use abstract class and interface .
Use the Abstract Class to Extend Data Class in Kotlin
The abstract class can declare all the parameters as abstract in the parent class and then override them in the child class .
fun main(args: Array) abstract class Tutorial abstract var year: Int abstract var name: String > data class Book ( override var year: Int = 2022, override var name: String = "Kotlin Tutorials", var isbn: String ) : Tutorial() >
The above code won’t throw any error as it extends the data class . It will create a corresponding class and enable the data class to extend from it.
Use the Interface to Extend Data Class in Kotlin
We can also extend a data class in Kotlin with the help of an interface .
interface Base_Interface val item:String > interface Derived_Interface : Base_Interface val derived_item:String > fun main(args: Array) data class B(override val item:String) : Base_Interface data class D(override val derived_item:String, private val b:Base_Interface) : Derived_Interface, Base_Interface by b val b = B("Hello ") val d = D("World!", b) print(d.item) //from the base class print(d.derived_item) //from the derived class >
Use the Open Classes to Extend Data Class in Kotlin
Another way to extend a data class is by declaring all the parent class properties as open .
We can then use the same override method to override the properties in the sub-class and extend the data class.
fun main(args: Array) open class ParentClass var var1 = false var var2: String? = null > data class ChildClass( var var3: Long ) : ParentClass() >
Kailash Vaviya is a freelance writer who started writing in 2019 and has never stopped since then as he fell in love with it. He has a soft corner for technology and likes to read, learn, and write about it. His content is focused on providing information to help build a brand presence and gain engagement.
Классы данных
Нередко мы создаём классы, единственным назначением которых является хранение данных. Функционал и некоторые служебные функции таких классов зависят от самих данных, которые в них хранятся. В Kotlin они называются классами данных и помечены data .
data class User(val name: String, val age: Int)
Компилятор автоматически формирует следующие члены данного класса из свойств, объявленных в основном конструкторе:
- пару функций equals() / hashCode() ,
- функцию toString() в форме «User(name=John, age=42)» ,
- компонентные функции componentN(), которые соответствуют свойствам, в соответствии с порядком их объявления,
- функцию copy() (см. ниже).
Для обеспечения согласованности и осмысленного поведения сгенерированного кода классы данных должны удовлетворять следующим требованиям:
- Основной конструктор должен иметь как минимум один параметр;
- Все параметры основного конструктора должны быть отмечены, как val или var ;
- Классы данных не могут быть абстрактными, open, sealed или inner;
Дополнительно, генерация членов классов данных при наследовании подчиняется следующим правилам:
- Если существуют явные реализации equals() , hashCode() или toString() в теле класса данных или final реализации в суперклассе, то эти функции не генерируются, а используются существующие реализации;
- Если суперкласс включает функции componentN() , которые являются открытыми и возвращают совместимые типы, соответствующие компонентные функции создаются для класса данных и переопределяют функции суперкласса. Если функции суперкласса не могут быть переопределены из-за несовместимости сигнатур или потому что они final , выдаётся сообщение об ошибке;
- Предоставление явных реализаций для функций componentN() и copy() не допускается.
Классы данных могут расширять другие классы (см. примеры в статье Изолированные классы).
On the JVM, if the generated class needs to have a parameterless constructor, default values for the properties have > to be specified (see [Constructors](classes.md#constructors)). —>
Для того чтобы у сгенерированного в JVM класса был конструктор без параметров, значения всех свойств должны быть заданы по умолчанию (см. Конструкторы).
data class User(val name: String = "", val age: Int = 0)
Свойства, объявленные в теле класса
Компилятор использует только свойства, определенные в основном конструкторе для автоматически созданных функций. Чтобы исключить свойство из автоматически созданной реализации, объявите его в теле класса:
data class Person(val name: String)
Только свойство name будет учитываться в реализациях функций toString() , equals() , hashCode() и copy() , и будет создана только одна компонентная функция component1() . Даже если два объекта класса Person будут иметь разные значения свойств age , они будут считаться равными.
val person1 = Person("John") val person2 = Person("John") person1.age = 10 person2.age = 20
Копирование
Используйте функцию copy() для копирования объекта, что позволит изменить только некоторые его свойств, оставив остальные неизменными. Для написанного выше класса User такая реализация будет выглядеть следующим образом:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
val jack = User(name = "Jack", age = 1) val olderJack = jack.copy(age = 2)
Классы данных и мульти-декларации
Сгенерированные для классов данных компонентные функции позволяют использовать их в мульти-декларациях.
val jane = User("Jane", 35) val (name, age) = jane println("$name, $age years of age") // выводит "Jane, 35 years of age"
Стандартные классы данных
Стандартная библиотека предоставляет классы Pair и Triple . Однако, в большинстве случаев, именованные классы данных являются лучшим решением, потому что делают код более читаемым, избегая малосодержательные имена для свойств.
© 2015—2023 Open Source Community