- Наследование
- Переопределение методов класса
- Переопределение свойств класса
- Порядок инициализации производного класса
- Вызов функций и свойств суперкласса
- Правила переопределения
- 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
- Data classes
- Properties declared in the class body
- Copying
- Data classes and destructuring declarations
- Standard data classes
Наследование
Для всех классов в Kotlin родительским суперклассом является класс Any . Он также является родительским классом для любого класса, в котором не указан какой-либо другой родительский класс.
class Example // Неявно наследуется от Any
У Any есть три метода: equals() , hashCode() и toString() . Эти методы определены для всех классов в Kotlin.
По умолчанию все классы в Kotlin имеют статус final, который блокирует возможность наследования. Чтобы сделать класс наследуемым, его нужно пометить ключевым словом open .
open class Base // Класс открыт для наследования
Для явного объявления суперкласса мы помещаем его имя за знаком двоеточия в оглавлении класса:
open class Base(p: Int) class Derived(p: Int) : Base(p)
Если у класса есть основной конструктор, базовый тип может (и должен) быть проинициализирован там же, с использованием параметров основного конструктора.
Если у класса нет основного конструктора, тогда каждый последующий дополнительный конструктор должен включать в себя инициализацию базового типа с помощью ключевого слова super или давать отсылку на другой конструктор, который это делает. Примечательно, что любые дополнительные конструкторы могут ссылаться на разные конструкторы базового типа.
Переопределение методов класса
Kotlin требует явно указывать модификаторы и для членов, которые могут быть переопределены, и для самого переопределения.
open class Shape < open fun draw() < /*. */ >fun fill() < /*. */ >> class Circle() : Shape() < override fun draw() < /*. */ >>
Для Circle.draw() необходим модификатор override . В случае её отсутствия компилятор выдаст ошибку. Если у функции типа Shape.fill() нет модификатора open , объявление метода с такой же сигнатурой в производном классе невозможно, с override или без. Модификатор open не действует при добавлении к членам final класса (т.е. класса без модификатора open ).
Член класса, помеченный override , является сам по себе open, т.е. он может быть переопределён в производных классах. Если вы хотите запретить возможность переопределения такого члена, используйте final .
open class Rectangle() : Shape() < final override fun draw() < /*. */ >>
Переопределение свойств класса
Переопределение свойств работает также, как и переопределение методов; все свойства, унаследованные от суперкласса, должны быть помечены ключевым словом override , а также должны иметь совместимый тип. Каждое объявленное свойство может быть переопределено свойством с инициализацией или свойством с get -методом.
open class Shape < open val vertexCount: Int = 0 >class Rectangle : Shape()
Вы также можете переопределить свойство val свойством var , но не наоборот. Это разрешено, поскольку свойство val объявляет get -метод, а при переопределении его как var дополнительно объявляется set -метод в производном классе.
Обратите внимание, что ключевое слово override может быть использовано в основном конструкторе класса как часть объявления свойства.
interface Shape < val vertexCount: Int >class Rectangle(override val vertexCount: Int = 4) : Shape // Всегда имеет 4 вершины class Polygon : Shape < override var vertexCount: Int = 0 // Может быть установлено любое количество >
Порядок инициализации производного класса
При создании нового экземпляра класса в первую очередь выполняется инициализация базового класса (этому шагу предшествует только оценка аргументов, передаваемых в конструктор базового класса) и, таким образом, происходит до запуска логики инициализации производного класса.
open class Base(val name: String) < init < println("Инициализация класса Base") >open val size: Int = name.length.also < println("Инициализация свойства size в класса Base: $it") >> class Derived( name: String, val lastName: String, ) : Base(name.replaceFirstChar < it.uppercase() >.also < println("Аргументы, переданные в конструктор класса Base: $it") >) < init < println("Инициализация класса Derived") >override val size: Int = (super.size + lastName.length).also < println("Инициализация свойства size в классе Derived: $it") >> fun main()
Это означает, что свойства, объявленные или переопределенные в производном классе, не инициализированы к моменту вызова конструктора базового класса. Если какое-либо из этих свойств используется в логике инициализации базового класса (прямо или косвенно через другую переопределенную open реализацию члена класса), это может привести к некорректному поведению или сбою во время выполнения. Поэтому при разработке базового класса следует избегать использования членов с ключевым словом open в конструкторах, инициализации свойств и блоков инициализации ( init ).
Вызов функций и свойств суперкласса
Производный класс может вызывать реализацию функций и свойств своего суперкласса, используя ключевое слово super .
open class Rectangle < open fun draw() < println("Рисование прямоугольника") >val borderColor: String get() = "black" > class FilledRectangle : Rectangle() < override fun draw() < super.draw() println("Заполнение прямоугольника") >val fillColor: String get() = super.borderColor >
Во внутреннем классе доступ к суперклассу внешнего класса осуществляется при помощи ключевого слова super , за которым следует имя внешнего класса: super@Outer .
class FilledRectangle: Rectangle() < override fun draw() < val filler = Filler() filler.drawAndFill() >inner class Filler < fun fill() < println("Filling") >fun drawAndFill() < super@FilledRectangle.draw() // Вызывает реализацию функции draw() класса Rectangle fill() println("Нарисованный прямоугольник заполнен $цветом") // Используется реализация get()-метода свойства borderColor в классе > > >
Правила переопределения
В Kotlin правила наследования реализации определены следующим образом: если класс наследует многочисленные реализации одного и того члена от ближайших родительских классов, он должен переопределить этот член и обеспечить свою собственную реализацию (возможно, используя одну из унаследованных).
Для того чтобы отметить конкретный супертип (родительский класс), от которого мы наследуем данную реализацию, используйте ключевое слово super . Для задания имени родительского супертипа используются треугольные скобки, например super .
open class Rectangle < open fun draw() < /* . */ >> interface Polygon < fun draw() < /* . */ >// члены интерфейса открыты ('open') по умолчанию > class Square() : Rectangle(), Polygon < // Компилятор требует, чтобы функция draw() была переопределена: override fun draw() < super.draw() // вызов Rectangle.draw() super.draw() // вызов Polygon.draw() > >
Это нормально, наследоваться одновременно от Rectangle и Polygon , но так как у каждого из них есть своя реализация функции draw() , мы должны переопределить draw() в Square и обеспечить нашу собственную реализацию этого метода для устранения получившейся неоднозначности.
© 2015—2023 Open Source Community
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.
Data classes
It is not unusual to create classes whose main purpose is to hold data. In such classes, some standard functionality and some utility functions are often mechanically derivable from the data. In Kotlin, these are called data classes and are marked with data :
The compiler automatically derives the following members from all properties declared in the primary constructor:
- equals() / hashCode() pair
- toString() of the form «User(name=John, age=42)»
- componentN() functions corresponding to the properties in their order of declaration.
- copy() function (see below).
To ensure consistency and meaningful behavior of the generated code, data classes have to fulfill the following requirements:
- The primary constructor needs to have at least one parameter.
- All primary constructor parameters need to be marked as val or var .
- Data classes cannot be abstract, open, sealed, or inner.
Additionally, the generation of data class members follows these rules with regard to the members’ inheritance:
- If there are explicit implementations of equals() , hashCode() , or toString() in the data class body or final implementations in a superclass, then these functions are not generated, and the existing implementations are used.
- If a supertype has componentN() functions that are open and return compatible types, the corresponding functions are generated for the data class and override those of the supertype. If the functions of the supertype cannot be overridden due to incompatible signatures or due to their being final, an error is reported.
- Providing explicit implementations for the componentN() and copy() functions is not allowed.
Data classes may extend other classes (see Sealed classes for examples).
On the JVM, if the generated class needs to have a parameterless constructor, default values for the properties have to be specified (see Constructors).
Properties declared in the class body
The compiler only uses the properties defined inside the primary constructor for the automatically generated functions. To exclude a property from the generated implementations, declare it inside the class body:
Only the property name will be used inside the toString() , equals() , hashCode() , and copy() implementations, and there will only be one component function component1() . While two Person objects can have different ages, they will be treated as equal.
Copying
Use the copy() function to copy an object, allowing you to alter some of its properties while keeping the rest unchanged. The implementation of this function for the User class above would be as follows:
You can then write the following:
Data classes and destructuring declarations
Component functions generated for data classes make it possible to use them in destructuring declarations:
val jane = User(«Jane», 35) val (name, age) = jane println(«$name, $age years of age») // prints «Jane, 35 years of age»
Standard data classes
The standard library provides the Pair and Triple classes. In most cases, though, named data classes are a better design choice because they make the code more readable by providing meaningful names for the properties.