- Classes
- Constructors
- Secondary constructors
- Creating instances of classes
- Class members
- Inheritance
- Abstract classes
- Companion objects
- Наследование companion object kotlin
- Наследование анонимных объектом
- Анонимный объект как аргумент функции
- Анонимный объект как результат функции
- Companion objects vs nested objects и зачем вообще нужны компаньоны
- Погружение: смысл компаньонов.
Classes
Classes in Kotlin are declared using the keyword class :
The class declaration consists of the class name, the class header (specifying its type parameters, the primary constructor, and some other things), and the class body surrounded by curly braces. Both the header and the body are optional; if the class has no body, the curly braces can be omitted.
Constructors
A class in Kotlin can have a primary constructor and one or more secondary constructors. The primary constructor is a part of the class header, and it goes after the class name and optional type parameters.
If the primary constructor does not have any annotations or visibility modifiers, the constructor keyword can be omitted:
The primary constructor cannot contain any code. Initialization code can be placed in initializer blocks prefixed with the init keyword.
During the initialization of an instance, the initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers:
Primary constructor parameters can be used in the initializer blocks. They can also be used in property initializers declared in the class body:
Kotlin has a concise syntax for declaring properties and initializing them from the primary constructor:
Such declarations can also include default values of the class properties:
You can use a trailing comma when you declare class properties:
Much like regular properties, properties declared in the primary constructor can be mutable ( var ) or read-only ( val ).
If the constructor has annotations or visibility modifiers, the constructor keyword is required and the modifiers go before it:
Secondary constructors
A class can also declare secondary constructors, which are prefixed with constructor :
If the class has a primary constructor, each secondary constructor needs to delegate to the primary constructor, either directly or indirectly through another secondary constructor(s). Delegation to another constructor of the same class is done using the this keyword:
class Person(val name: String) < val children: MutableList
Code in initializer blocks effectively becomes part of the primary constructor. Delegation to the primary constructor happens at the moment of access to the first statement of a secondary constructor, so the code in all initializer blocks and property initializers is executed before the body of the secondary constructor.
Even if the class has no primary constructor, the delegation still happens implicitly, and the initializer blocks are still executed:
If a non-abstract class does not declare any constructors (primary or secondary), it will have a generated primary constructor with no arguments. The visibility of the constructor will be public.
If you don’t want your class to have a public constructor, declare an empty primary constructor with non-default visibility:
On the JVM, if all of the primary constructor parameters have default values, the compiler will generate an additional parameterless constructor which will use the default values. This makes it easier to use Kotlin with libraries such as Jackson or JPA that create class instances through parameterless constructors.
Creating instances of classes
To create an instance of a class, call the constructor as if it were a regular function:
Kotlin does not have a new keyword.
The process of creating instances of nested, inner, and anonymous inner classes is described in Nested classes.
Class members
Inheritance
Classes can be derived from each other and form inheritance hierarchies. Learn more about inheritance in Kotlin.
Abstract classes
A class may be declared abstract , along with some or all of its members. An abstract member does not have an implementation in its class. You don’t need to annotate abstract classes or functions with open .
You can override a non-abstract open member with an abstract one.
Companion objects
If you need to write a function that can be called without having a class instance but that needs access to the internals of a class (such as a factory method), you can write it as a member of an object declaration inside that class.
Even more specifically, if you declare a companion object inside your class, you can access its members using only the class name as a qualifier.
Наследование companion object kotlin
Иногда возникает необходимость создать объект некоторого класса, который больше нигде в программе не используется. То есть класс необходим только для создания только одного объекта. В этом случае мы, конечно, можем, как и обычно, определить класс и затем создать объект этого класса. Но Kotlin для таких ситуаций предоставлять возможность определить объект анонимного класса.
Анонимные классы не используют ключевое слово class для определения. Они не имеют имени, но как и обычные классы могут наслдовать другие классы или применять интерфейсы. Объекты анонимных классов называют анонимыми объктами.
Для определения анонимного объекта применяется ключевое слово object :
fun main() < val tom = object < val name = "Tom" var age = 37 fun sayHello()< println("Hi, my name is $name") >> println("Name: $ Age: $") tom.sayHello() >
После ключевого слова object идет блок кода в фигурных скобках, в которые помещается определение объекта. Как и в обычном классе, анонимный объект может содержать свойства, функции. И далее по имени переменной мы можем обращаться к свойствам и функциям этого объекта.
Наследование анонимных объектом
При наследовании после слова object через двоеточия указывается имя наследуемого класса или его первичный конструктор:
fun main() < val tom = object : Person("Tom")< val company = "JetBrains" override fun sayHello()< println("Hi, my name is $name. I work in $company") >> tom.sayHello() // Hi, my name is Tom. I work in JetBrains > open class Person(val name: String) < open fun sayHello()< println("Hi, my name is $name") >>
Здесь класс анонимного объекта наследует класс Person и переопределяет его функцию sayHello() .
Анонимный объект как аргумент функции
Анонимный объект может передаваться в качестве аргумента в вызов функции:
fun main() < hello( object : Person("Sam")< val company = "JetBrains" override fun sayHello()< println("Hi, my name is $name. I work in $company") >>) > fun hello(person: Person) < person.sayHello() >open class Person(val name: String)
Здесь поскольку класс анонимного объекта наследуется от класса Person, мы можем передавать этот анонимный объект параметру функции, который имеет тип Person.
Анонимный объект как результат функции
Функция может возвращать анонимный объект:
fun main() < val tom = createPerson("Tom", "JetBrains") tom.sayHello() >private fun createPerson(_name: String, _company: String) = object
Однако тут есть нюансы. Чтобы мы могли обращаться к свойствам и функциям анонимного объекта, функция, которая возвращает этот объект, должна быть приватной, как в примере выше.
Если функция имеет модификатор public или private inline , то в этом случае свойства и функции анонимного класса (за исключением унаследованных) недоступны:
fun main() < val tom = createPerson("Tom", "JetBrains") println(tom.name) // норм - свойство name унаследовано от Person println(tom.company) // ! Ошибка - свойство недоступно >private inline fun createPerson(_name: String, _comp: String) = object: Person(_name) < val company = _comp >open class Person(val name: String)
В данном случае функция createPerson() имеет модификатор private inline , поэтому у анонимного объекта будут доступны только унаследованные свойства и функции от класса Person, но собственные свойства и функции будут не доступны.
Companion objects vs nested objects и зачем вообще нужны компаньоны
Очевидный ответ: для эмуляции static методов, которые были в Java (и для interop’а используется аннотация @JvmStatic, генерирующая подобные джаве статик методы).
Но почему от static отказались в Kotlin и каково концептуальное значичение companion, ведь в Kotlin обычно всё делается не только для обеспечения interop с Java.
Самое интересное, что если объявить nested object — то это будет почти тоже самое… Дак всё же зачем?
Посмотрим на реализацию изнутри и сравним companion и nested objects:
class Example < private val classMember = "class" companion object < private val companionMember = "companion object" fun hello() = println("companion world") >object Nested < private val nestedMember = "nested object" fun hello() = println("nested world") >>
public final class Example < private final String classMember = "class"; private static final String companionMember = "companion object"; public static final Example.Companion companion = new Example.Companion(); public static final class Companion < public final void hello() < System.out.println("companion world"); >> public static final class Nested < private static final String nestedMember = "nested object"; public static final Example.Nested INSTANCE = new Nested(); public final void hello() < System.out.println("nested world"); >> >
Как видим, companion — суть «компаньон»: создаётся вместе с оболочкой, даже его поля хранятся в оболочке. Тогда как вложенный объект — именно котлиновский object class, с теми же свойствами: lazy initialization и прочее.
Погружение: смысл компаньонов.
Все упомянутые ниже пункты одинаково применимы как к вложенным объектам, так и к компаньонам, поэтому всё различие между ними в синтаксисе и в вышеупомянутой более тесной связи с оболочкой. (а вообще, ходят слухи, что компаньоны могут удалить в следующих версиях)
- Вместо статических методов в Котлин рекомендуется использовать функции расширения или функции верхнего уровня (уровня файла). Тогда namespace’ом является package.
- При том что функции расширения часто инлайнятся компилятором, повышая производительность
open class Parent < fun turnDown() = println("Turn down") >open class CompanionParent < fun forWhat() = println("for what") >class Wrapper: Parent() < companion object : CompanionParent() fun what() < turnDown() forWhat() >>
Что? Множественное наследование? Д… неееее, но похоже 🙂
- Многие минусы статических членов класса не касаются компаньонов, например:
- Невозможность переопределение методов + путаница, вызываемая этим. Статические методы определяются ранним связыванием, то есть во время компиляции, тогда как обычные поздним — во время выполнения: объявив переменную родительского типа и вызвав статический метод, вызовется именно статический метод объявленного родительского типа — не важно, что в переменной хранится наследник с «переопределением»
- Статические поля не сериализуются + путаница, опять же. Компаньон же объект и есть объект.
- Сложности создания mock объектов при тестировании классов со static методами (из-за проблем с переопределением статических методов)