August 26, 2019

Введение в Kotlin: интерфейсы, модификаторы доступа, вложенные классы, ключевые слова this и object

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

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

Интерфейсы

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

Интерфейс определяется с помощью ключевого слова interface:

interface MyInterface {
    fun bar()
    fun foo() {
        // optional body
    } 
}

Реализация интерфейсов

Класс или объект может реализовать один или несколько интерфейсов. Синтаксис при реализации такой же, как и при наследовании.

class Child : MyInterface {
    fun bar() {
        // body
    }
}

Свойства в интерфейсах

Вы можете объявить свойства в интерфейсе. Свойства объявляются в интерфейсе как абстрактные:

interface MyInterface {

    val property: Int // abstract
    val propertyWithImplementation: String
        get() = "foo"

    fun foo() {
        print(property)
    }
} 

class Child : MyInterface {
    override val property: Int = 29
}

Решение проблем с переопределением

При реализации нескольких интерфейсов могут возникнуть конфликты в именах методов. Например:

interface A {
    fun foo() { print("A") }
    fun bar()
}

interface B {
    fun foo() { print("B") }
    fun bar() { print("bar") }
}

class C : A {
    override fun bar() { print("bar") }
}

class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }
}

Интерфейсы А и В и объявляют функции foo() и bar(). Оба интерфейса реализуют функцию foo(), однако функцию bar() реализует только интерфейс B. В классе C мы реализовали метод bar(), т. к. реализация его является обязательной. В классе D мы реализуем оба интерфейса (A и B), в связи с чем возникает конфликт. Метод foo() реализуют оба интерфейса, и компилятор не знает, какой из них выбрать. Мы должны явно указать, какой из методов нужно вызывать.

Модификаторы доступа

Классы, объекты, интерфейсы, конструкторы, функции, свойства и их сеттеры имеют модификаторы доступа (геттеры всегда имеют такую же видимость, как и свойства).

Это модификаторы доступа в Kotlin: private, protected, internal и public. По умолчанию используется модификатор public.

Пакеты

Функции, свойства и классы, объекты и интерфейсы могут быть объявлены как “top-level”, т. е. находиться внутри пакета:

// имя файла: example.kt
package foo

fun baz() {}
class Bar {}

— Если вы не укажете модификатор, по умолчанию будет использован модификатор public и все объявления будут доступны во всем пакете.

— Если вы укажете в качестве модификатора private, объявления будут видны только внутри файла, содержащего декларацию.

— Если вы укажете в качестве модификатора internal, объявления будут видны во всем модуле.

— Модификатор protected не доступен для «top-level»-объявлений.

Примеры:

// имя файла: example.kt
package foo

private fun foo() {} // виден внутри файла example.kt

public var bar: Int = 5 // виден везде
    private set // сеттер виден только в файле example.kt

internal val baz = 6 // видно внутри модуля

Классы и интерфейсы

При объявлении внутри класса:

  • private — объявления видны только внутри этого класса (в том числе для всех его членов);
  • protected — так же, как и private, + видимость в подклассах;
  • internal — все клиенты внутри этого модуля могут видеть класс;
  • public —все клиенты могут видеть класс.

Примечание для программистов Java: внешний класс не видит private-поля своих внутренних классов в Kotlin.

Примеры:

open class Outer {
    private val a = 1
    protected val b = 2
    internal val c = 3
    val d = 4 // public по умолчанию

    protected class Nested {
        public val e: Int = 5
    }
}

class Subclass : Outer() {
    // a не виден
    // b, c и d видны
    // Nested и e видны
}

class Unrelated(o: Outer) {
    // o.a, o.b не видны
    // o.c и o.d видны
    // Outer.Nested не виден, и Nested::e не виден
}

Конструкторы

Чтобы указать видимость основного конструктора класса, используйте следующий синтаксис (обратите внимание, что вы должны добавить ключевое слово constructor):

class C private constructor(a: Int) { ... }

Здесь конструктор является приватным.

Локальные переменные

Локальные переменные, функции и классы не могут иметь модификаторы доступа.

Модули

Модификатор internal означает видимость внутри модуля:

  • модуль IntelliJ IDEA (или Android Studio);
  • проект Maven или Gradle;
  • набор файлов, скомпилированных одним вызовом Ant задачи.

Вложенные классы

Классы могут быть вложенными один в другой:

class Outer {

    private val bar: Int = 1

    class Nested {
        fun foo() = 2
    }
}

val demo = Outer.Nested().foo() // == 2

Внутренние классы

Вложенный класс может быть помечен как inner для доступа к элементам внешнего класса. Внутренние классы содержат ссылку на объект внешнего класса:

class Outer {

    private val bar: Int = 1

    inner class Inner {
        fun foo() = bar
    }
}

val demo = Outer().Inner().foo() // == 1

Из примера видно, что класс Inner имеет доступ к переменным класса Outer.

Ключевое слово this

Ключевое слово this используется в классах для обозначения текущего экземпляра класса. Т. е. слово this возвращает объект, в котором оно вызывается.

Пример использования ключевого слова this:

class Human (var age: Integer) {
    fun isSimiliarAge(age:Integer): Boolean = this.age == age
}

Метод isSimiliarAge проверяет, являются ли два человека ровесниками. Пример выглядит немного глупо, т. к. в метод можно было передать также объект класса Human и брать возраст у него, но ничего более толкового в голову не пришло.

Перечисления enum

Синтаксис создания перечислений в Kotlin очень похож на аналогичный в Java, например:

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

Также перечисления в Kotlin могут иметь переопределяемые функции:

enum class ProtocolState {

    ERROR {
        override fun signal() = "Ошибка загрузки"
    },

    SUCCESS {
        override fun signal() = "Все ок"
    };

    abstract fun signal(): String
}

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

Так же как и в Java, мы можем получить константу по имени или же получить список всех констант:

EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>

Метод valueOf() может выбросить IllegalArgumentException, если значение не найдено.

Синглтоны и аналоги статических методов

Kotlin не имеет модификатора static, а это значит, что мы не можем создавать статические переменные и методы. Хорошо, а как нам тогда создать Singleton в Kotlin?

К счастью, object может справиться с этим. Ключевое слово companion object используется для доступа к членам конкретного класса (не объекта).

class MyClass {
    companion object Factory { 
        val info = "This is factory" 
        fun getMoreInfo(): String { 
            return "This is factory fun" 
        }
    } 
} 

MyClass.info // This is factory
MyClass.getMoreInfo() // This is factory fun

Каждая переменная или метод, расположенные внутри companion object, могут вызываться по имени класса (типа статических). И сейчас мы создадим класс-синглтон с использованием паттерна «ленивая загрузка» (аналогичным образом создается класс-синглтон в Java):

class Singleton private constructor() {

    init {
        println("This is a singleton")
    }

    private object Holder { 
        val INSTANCE = Singleton() 
    }

    companion object { 
        val instance: Singleton by lazy { 
            Holder.INSTANCE 
        } 
    }

    var b: String? = null 
}
  • Приватный конструктор используется для того, чтобы гарантировать, что объект данного класса будет создан только внутри этого класса.
  • Метод init будет вызван при загрузке данного класса, т. е. при первом вызове Singleton.instance.
  • Holder object & lazy instance используются для создания единственного экземпляра класса.

Пример использования:

var first = Singleton.instance // This is a singleton

first.b = "hello singleton"

var second = Singleton.instance
println(second.b) // hello singleton

Но в Kotlin создать Singleton можно гораздо проще! Для этого достаточно только слова object:

object Singleton { 

    init { 
        println("This ($this) is a singleton") 
    } 

    var b: String? = null 
}

Пример использования:

var first = Singleton // This is a singleton 

first.b = "hello singleton" 

var second = Singleton 
println(second.b) // hello singleton 

На этом третья часть серия статей "Введение в Kotlin" заканчивается. Следующая часть серии доступна здесь.

Источники:

#3 Уроки Kotlin. Введение: интерфейсы

#4 Уроки Kotlin. Введение: модификаторы доступа

#5 Уроки Kotlin. Введение: вложенные классы

#6 Уроки Kotlin. Введение: ключевое слово this

#7 Уроки Kotlin. Введение: перечисления enum

Singleton в Kotlin