Введение в 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