August 26, 2019

Введение в Kotlin: классы, конструкторы, методы и свойства, наследование

Введение в Kotlin: функции, переменные, условия, циклы

Продолжаем введение в главные конструкции языка Kotlin. Сегодня мы рассмотрим основные моменты, связанные с классами: их объявление, свойства и методы, конструкторы классов и наследование. Подробнее о классах в Kotlin можно почитать в официальной документации: классы и наследование, свойства и поля, функции и методы, функции-расширения.

Классы

Для создания класса в Kotlin используется ключевое слово class. Например, вот пустой класс Person:

class Person {

}

Классы обычно имеют переменные (называемые свойствами) и функции (называемые методами).

Добавление свойств

Давайте добавим два свойства к классу Person: name типа String и age типа Int.

var name: String = ""
var age: Int = 0

Как вы можете наблюдать, синтаксис создания переменных/свойств немного отличается от Java. Для создания переменных мы должны использовать ключевое слово var. Однако, если вы хотите использовать переменную только для чтения, используйте ключевое слово val (в Kotlin рекомендуется использовать val, если нет необходимости в изменении переменной).

В Kotlin’е делается различие между переменными, которые могут быть null и которые никогда не могут быть null. В нашем предыдущем примере обе переменные name и age не могут быть равны null. Если вы попытаетесь записать в них null, компилятор выдаст ошибку.

Для того чтобы создать переменную, которая может быть равна null, нужно после имени переменной поставить знак ?. Например:

var college: String? = null

Сейчас мы создадим объект класса Person:

var jake = Person()

Для создания объекта не нужно ключевое слово new. Доступ к переменным осуществляется так же, как и в Java:

jake.name = "Jake Hill"
jake.age = 24
jake.college = "Stephen's College"

Добавление методов

Создание функций в Kotlin осуществляется с помощью ключевого слова fun. Методы, добавляемые в классах, объявляются аналогично. Давайте добавим в класс Person простой метод isEligibleToVote, возвращающий Boolean-значение:

fun isEligibleToVote(): Boolean {
    // If age is greater or equal to 18
    // return true
    return age >= 18
}

Обратите внимание, что тип возвращаемого значения указывается после имени метода и аргументов через двоеточие. Вы можете вызвать метод так же, как в Java:

jake.isEligibleToVote()

Также метод isEligibleToVote() можно написать одной строчкой:

fun isEligibleToVote() = age >= 18

Использование конструкторов

Лучший способ для инициализации переменных это использование конструкторов. Синтаксис конструктора в Kotlin очень компактный:

class Person(var name: String, var age: Int, var college: String?) {

}

Класс с конструктором можно создать даже без фигурных скобок.

class Person(var name: String, var age: Int, var college: String?)

var jake = Person("Jake Hill", 24, "Stephen's College")

Для создания дополнительных (вторичных) конструкторов нужно использовать ключевое слово constructor. Вторичный конструктор делегирует вызов первичному конструктору с помощью ключевого слова this. Давайте добавим новое свойство email:

class Person(var name: String, var age: Int, var college: String?) {

    var email: String = ""

    constructor(name: String, age: Int, college: String?, email: String)
                      : this(name, age, college) {
        this.email = email
    }
}

Создание объекта через вторичный конструктор выглядит так же, как и с первичным конструктором:

var jake = Person("Jake Hill", 24, "Stephen's College", "[email protected]")

Наследование классов

Нужно знать несколько очень важных моментов о наследовании в Kotlin:

– вместо ключевого слова extends используется : (двоеточие);
– заголовок базового класса должен иметь аннотацию open в заголовке;
– если ваш базовый класс имеет конструкторы, то вы должны инициализировать эти конструкторы в заголовке.

Давайте создадим класс Employee – наследника класса Person. В коде ниже приведен пример, как рекомендуется объявлять классы, которые имеют большое количество полей:

open class Person(
    var name: String,
    var age: Int,
    var college: String?
) {
    ...
}

class Employee(
    name: String,
    age: Int,
    college: String?,
    var company: String
) : Person(name, age, college) {

}

Переопределение методов

В Kotlin вы должны явно указать, что метод может быть переопределен с помощью аннотации open у функции в базовом классе. В классе-наследнике вы должны указать, что метод переопределяет метод родителя.

Например, чтобы переопределить метод isEligibleToVote у класса Employee, нужно использовать следующий код:

override fun isEligibleToVote(): Boolean {
    // Always return true
    return true
}

Расширение классов

В Kotlin можно расширять классы, не изменяя код самого класса. Например, мы можем добавить функцию isTeenager к классу Person вне самого класса:

fun Person.isTeenager(): Boolean {
    // если возраст в пределах от 13 до 19, вернет true
    return age in 13..19
}

Эта функция особенно полезна, когда вы хотите расширить классы, которые не принадлежат к коду вашего проекта. Например, следующий фрагмент кода добавляет функцию containsSpaces к классу String:

fun String.containsSpaces(): Boolean {
    return this.indexOf(" ") != -1 
}

Создание статических методов

Kotlin не позволяет создавать статические методы, однако в Kotlin можно создавать функции, которые будут доступны всем классам. Их объявление происходит на уровне файла.

Давайте добавим функцию main, доступную всем нашим классам:

package ru.javahelp.kotlin

fun main(args:Array<String>) {

}

Сеттеры и геттеры для свойств

Вот так выглядит полный синтаксис объявления свойств:

var <propertyName>: <PropertyType> [= <property_initializer>]
    [<getter>]
    [<setter>]

Инициализация сеттеров и геттеров является необязательной. Если переменная инициализируется сразу же, то писать тип переменной не обязательно:

// error: explicit initializer required, default getter and setter implied
var allByDefault: Int?

// has type Int, default getter and setter
var initialized = 1

Между объявлением свойств только для чтения и объявлением изменяемых свойств есть два различия:

  1. Вместо var пишется val
  2. Свойство для чтения не имеет сеттера
// has type Int, default getter, must be initialized in constructor
val simple: Int?

// has type Int and a default getter
val inferredType = 1

Мы можем написать свои сеттеры и геттеры как обычные методы прямо при объявлении свойства:

val isEmpty: Boolean
    get() = this.size == 0

Кастомный сеттер выглядит так:

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        // parses the string and assigns values to other properties
        setDataFromString(value)
    }

По соглашению именем переменной в сеттере является value, но вы можете выбрать другое имя. Если вам нужно изменить видимость сеттера или геттера, то вы можете просто написать имя метода (set или get), не изменяя его стандартную реализацию:

var setterVisibility: String = "abc" // Initializer required, not a nullable type
    private set // the setter is private and has the default implementation 

var setterWithAnnotation: Any?
    @Inject set // annotate the setter with Inject

Late-Initialized свойства

Как правило, свойства объявленные как non-null должны быть инициализированы в конструкторе. Тем не менее, часто это бывает не удобно. Например, свойства могут быть инициализированы через Dependency Injection или в setup-методе unit-теста. Чтобы решить эту проблему, нужно отметить свойство модификатором lateinit:

public class MyTest {

    lateinit var subject: TestSubject

    @SetUp
    fun setup() {
        subject = TestSubject()
    }

    @Test
    fun test() {
        subject.method() // dereference directly
    }
}

Модификатор может применяться только к var-свойствам, которые объявлены в теле класса, а не в первичном конструкторе. Также тип поля должен быть ненулевым, а также не должен иметь примитивный тип.

При попытке доступа к lateinit-свойству до ее инициализации выбросится исключение, которое четко указывает на то, что переменная не была объявлена.

На этом вторая часть серии статей "Введение в Kotlin" заканчивается. Продолжение этой серии и другие статьи о Kotlin вы можете найти здесь.

Источники:

#1 Уроки Kotlin. Введение: классы, конструкторы, наследование

#2 Уроки Kotlin. Введение: поля и переменные