Advertisement
  1. Code
  2. Coding Fundamentals
  3. Functional Programming

Котлин с нуля: классы и объекты

Scroll to top
Read Time: 21 min
This post is part of a series called Kotlin From Scratch.
Kotlin From Scratch: Advanced Functions
Kotlin From Scratch: Advanced Properties and Classes

() translation by (you can also view the original English article)

Kotlin - это современный язык программирования, который компилируется в байт-код Java. Он бесплатный и с открытым исходным кодом, и обещает сделать кодирование для Android еще более увлекательным.

В предыдущей статье вы узнали о расширенном использовании функций, таких как функции расширения, смыкания, функции высшего порядка и встроенные функции в Kotlin.

В этой статье вы познакомитесь с объектно-ориентированным программированием в Kotlin, изучив классы: конструкторы и свойства, приведение типов и более продвинутые функции классов, которые Kotlin упрощает.

 1. Классы

Класс - это программный блок, который группирует функции и данные для выполнения некоторых связанных задач. Мы объявляем класс в Kotlin, используя ключевое слово class - аналогично Java.

1
class Book

Предыдущий код - самое простое объявление класса - мы только что создали пустой класс с именем Book. Мы все еще можем создать экземпляр этого класса, даже если он не содержит тела, используя конструктор по умолчанию.

1
val book = Book()

Как вы можете заметить в приведенном выше коде, мы не использовали ключевое слово new для создания экземпляра этого класса - как обычно в других языках программирования. new не ключевое слово в Kotlin. Это делает наш исходный код сжатым при создании экземпляра класса. Но имейте в виду, что создание экземпляра класса Kotlin в Java потребует нового ключевого слова.

1
// In a Java file

2
Book book = new Book()

Конструкторы класса и свойства

Давайте посмотрим, как добавить конструктор и свойства в наш класс. Но сначала давайте посмотрим на типичный класс в Java:

1
/* Java */
2
public class Book  {
3
    private String title;
4
    private Long isbn;
5
    public Book(String title, Long isbn) {
6
        this.title = title;
7
        this.isbn = isbn;
8
    }
9
    public String getTitle() {
10
        return title;
11
    }
12
    public void setTitle(String title) {
13
        this.title = title;
14
    }
15
    public Long getIsbn() {
16
        return isbn;
17
    }
18
    public void setIsbn(Long isbn) {
19
        this.isbn = isbn;
20
    }
21
}

Глядя на наш класс модели Book выше, мы имеем следующее:

  • два поля: title и isbn
  •  один конструктор
  • методы получения и установки для двух полей (к счастью, IntelliJ IDEA может помочь нам сгенерировать эти методы)

Теперь давайте посмотрим, как вместо этого можно написать предыдущий код на Kotlin:

1
/* Kotlin */
2
class Book {
3
    var title: String
4
    var isbn: Long
5
6
    constructor(title: String, isbn: Long) {
7
        this.title = title
8
        this.isbn = isbn
9
    }
10
}

Довольно аккуратный класс! Теперь мы сократили количество строк кода с 20 до просто 9. Функция constructor() называется вторичным конструктором в Kotlin. Этот конструктор эквивалентен конструктору Java, который мы вызывали при создании экземпляра класса.

В Котлине нет понятия поля, с которым вы, возможно, знакомы; вместо этого он использует понятие «свойства». Например, у нас есть два изменяемых свойства (чтение-запись), объявленных с ключевым словом var: title и isbn в классе Book. (Если вам нужно освежить в памяти переменные в Kotlin, пожалуйста, посетите первый пост в этой серии: Variables, Basic Types, и Arrays).

Удивительно, что геттеры и сеттеры для этих свойств автоматически генерируются для нас компилятором Kotlin. Обратите внимание, что мы не указали никаких модификаторов видимости для этих свойств - поэтому по умолчанию они общедоступны. Другими словами, к ним можно получить доступ откуда угодно.

Давайте посмотрим на другую версию того же класса в Kotlin:

1
class Book constructor(title: String, isbn: Long) {
2
    var title: String
3
    var isbn: Long
4
5
    init {
6
       this.title = title
7
       this.isbn = isbn
8
    }
9
}

В этом коде мы удалили вторичный конструктор. Вместо этого мы объявили конструктор в заголовке класса, который называется первичным конструктором. Первичный конструктор не имеет места для размещения блока кода, поэтому мы используем модификатор init для инициализации входящих параметров из первичного конструктора. Обратите внимание, что блок init кода инициализации выполняется сразу при создании экземпляра класса.

Как вы можете видеть, наш код все еще содержит много шаблонов. Давайте уменьшим это далее:

1
class Book constructor(var title: String, var isbn: Long)

Наш класс Book теперь представляет собой всего одну строку кода. Это действительно круто! Обратите внимание, что в списке параметров первичного конструктора мы определили наши изменяемые свойства: title и isbn непосредственно внутри первичного конструктора с ключевым словом var.

Мы также можем добавить значения по умолчанию к любому из свойств класса прямо внутри конструктора.

1
class Book constructor(var title: String = "default value", var isbn: Long)

Фактически, мы также можем опустить ключевое слово constructor, но только если у него нет модификатора видимости (public, private или protected) или каких-либо аннотаций.

1
class Book (var title: String = "default value", var isbn: Long)

Очень аккуратный класс, должен сказать!

Теперь мы можем создать экземпляр класса следующим образом:

1
val book = Book("A Song of Ice and Fire", 9780007477159)
2
val book2 = Book(1234) // uses the title property's default value

 Доступ и настройка свойств

В Kotlin мы можем получить свойство с помощью book объектов класса, за которой следует разделитель . , а затем title имени свойства. Этот краткий стиль доступа к свойствам называется синтаксисом доступа к свойствам. Другими словами, нам не нужно вызывать метод получения свойства для доступа или вызывать метод установки для установки свойства в Kotlin - как мы делаем в Java.

1
println(book.title) // "A Song of Ice and Fire"

Поскольку свойство isbn объявлено с ключевым словом var (чтение-запись), мы также можем изменить значение свойства, используя оператор присваивания =.

1
book.isbn = 1234
2
println(book.isbn) // 1234

Давайте посмотрим на другой пример:

1
class Book (
2
    var title: String, 
3
    val isbn: Long
4
)
5
6
val book = Book("A Song of Ice and Fire", 9780007477159)
7
book.isbn = 1234 // error: read-only property

8
book.title = "Things Fall Apart" // reassigned title with value

Здесь мы обновили параметр isbn, чтобы он стал неизменным (только для чтения) - с помощью ключевого слова val. Мы создали экземпляр экземпляра book класса и присвоили свойству title значение «Things Fall Apart». Обратите внимание, что когда мы попытались переназначить значение свойства isbn на 1234, компилятор пожаловался. Это связано с тем, что свойство является неизменным, поскольку оно определено с помощью ключевого слова val.

 Совместимость Java

Имейте в виду, что, объявив параметр с модификатором var внутри основного конструктора, компилятор Kotlin (за кулисами) помог нам сгенерировать оба метода доступа: getter и setter. Если вы используете val, он будет генерировать только геттер.

1
/* Kotlin */
2
class Book (
3
    var title: String, 
4
    val isbn: Long
5
)

Это означает, что Java-вызывающие пользователи могут просто получить или установить поле свойства, вызвав метод setter или getter свойства соответственно. Помните, это зависит от модификатора, используемого для определения свойства Kotlin: var или val.

1
/* Java */
2
Book book = new Book("A Song of Ice and Fire", 9780385474542)
3
println(book.getTitle()) // "A Song of Ice and Fire"

4
book.setTitle("Things Fall Apart") // sets new value

5
println(book.getTitle()) // "Things Fall Apart"

6
7
book.getIsbn() // 9780385474542

8
book.setIsbn(4545454) // won't compile

 Пользовательские Getters и Setters

В этом разделе я покажу вам, как создать собственные средства доступа (геттеры и сеттеры) для свойства в Kotlin, если вы хотите. Создание пользовательского установщика может быть полезно, если вы хотите проверить или подтвердить значение до того, как оно будет установлено в свойство класса. И пользовательский метод получения свойств может быть полезен, когда вы хотите изменить или изменить значение, которое должно быть возвращено.

 Создание настраиваемого сеттера

Поскольку мы хотим создать свой собственный метод получения или установки для свойства, мы должны определить это свойство в теле класса вместо заголовка конструктора.

1
class Book (val isbn: Long) {
2
    var title = "default value"
3
}

Вот почему мы переместили изменяемое свойство title (чтение-запись) в тело класса и присвоили ему значение по умолчанию (иначе оно не будет компилироваться).

1
class Book (val isbn: Long) {
2
    var title = "default value"
3
    set(value) {
4
        if (!value.isNotEmpty()) {
5
            throw IllegalArgumentException("Title must not be empty")
6
        }
7
        field = value
8
    }
9
}

Вы можете видеть, что мы определили наш собственный метод set(value) для title прямо под определением свойства - обратите внимание, что вы не можете изменить эту сигнатуру метода set(), потому что это то, что компилятор ожидает как функцию установки собственного свойства.

Значение параметра, переданное методу set, представляет собой фактическое значение, которое было присвоено свойству пользователями - вы можете изменить имя параметра, если хотите, но значение является гораздо более предпочтительным. Мы проверили значение, проверив, является ли оно пустым. Если пусто, остановите выполнение и сгенерируйте исключение; в противном случае переназначьте значение специальной переменной field.

Это специальное field переменной поля внутри метода set является псевдонимом для базового поля свойства - вспомогательное поле - это просто поле, которое используется свойствами, когда вы хотите изменить или использовать данные этого поля. В отличие от value, вы не можете переименовать эту специальную переменную field.

 Создание пользовательского Getter

Создать собственный геттер для недвижимости в Котлине очень просто.

1
class Book (val isbn: Long) {
2
    var title = "default value"
3
    //... set method

4
    get() {
5
        return field.toUpperCase()
6
    }
7
}

Внутри метода get мы просто возвращаем измененное field - в нашем случае мы возвращаем название книги в верхнем регистре.

1
val book = Book(9780007477159)
2
book.title = "A Song of Ice and Fire"
3
println(book.title) // "A SONG OF ICE AND FIRE"

4
println(book.isbn) // 9780007477159

Обратите внимание, что каждый раз, когда мы устанавливаем значение для свойства title, выполняется его блок метода set - то же самое касается метода get каждый раз, когда мы его извлекаем.

Если вы хотите узнать о функциях-членах для класса Kotlin (вид функции, которая определена внутри класса, объекта или интерфейса), посетите пост «Больше возможностей с функциями» в этой серии.

 Подробнее о конструкторах

Как я уже говорил ранее, у нас есть два типа конструкторов в Kotlin: первичный и вторичный. У нас есть свобода объединить их обоих в один класс - как вы можете видеть в следующем примере:

1
class Car(val name: String, val plateNo: String) {
2
    var new: Boolean = true
3
4
    constructor(name: String, plateNo: String, new: Boolean) : this(name, plateNo) {
5
        this.new = new
6
    }
7
}

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

В приведенном выше коде мы устанавливаем значение по умолчанию new свойства для класса Car (помните, что new не является ключевым словом в Kotlin) - тогда мы можем использовать вторичный конструктор, чтобы изменить его, если мы захотим. В Kotlin каждый вторичный конструктор должен вызывать первичный конструктор или вызывать другой вторичный конструктор, который вызывает первичный конструктор - для этого мы используем ключевое слово this.

Также обратите внимание, что внутри класса может быть несколько вторичных конструкторов.

1
class Car(val name: String, val plateNo: String) {
2
    var new: Boolean? = null
3
    var colour: String = ""
4
5
    constructor(name: String, plateNo: String, new: Boolean) : this(name, plateNo) {
6
        this.new = new
7
    }
8
9
    constructor(name: String, plateNo: String, new: Boolean, colour: String ) :
10
            this(name, plateNo, new) {
11
        this.colour = colour
12
    }
13
}

Если класс расширяет super, то мы можем использовать ключевое слово super (аналогично Java) для вызова конструктора суперкласса (мы обсудим наследование в Kotlin в следующем посте).

1
// directly calls primary constructor

2
val car1 = Car("Peugeot 504", "XYZ234")
3
// directly calls 1st sec. constructor

4
val car2 = Car("Peugeot 504", "XYZ234", false)
5
// directly calls last sec. constructor

6
val car3 = Car("Peugeot 504", "XYZ234", false, "grey") 

Как я уже говорил ранее, для того, чтобы мы явно включили модификатор видимости в конструктор в классе, мы должны включить ключевое слово constructor - по умолчанию конструкторы являются общедоступными.

1
class Car private constructor(val name: String, val plateNo: String) {
2
//...

Здесь мы сделали конструктор закрытым - это означает, что пользователи не могут создавать экземпляры объекта, используя его конструктор напрямую. Это может быть полезно, если вы хотите, чтобы пользователи вместо этого вызывали другой метод (фабричный метод) для косвенного создания объектов.

2. Любые и ничтожные типы

В Kotlin самый верхний тип иерархии типов называется Any. Это эквивалентно типу Object Java. Это означает, что все классы в Kotlin явно наследуются от типа Any, включая String, Int, Double и так далее. Тип Any содержит три метода: equals, toString и hashcode.

Мы также можем использовать класс Nothing в Kotlin в функциях, которые всегда возвращают исключение, другими словами, для функций, которые не завершаются нормально. Когда функция возвращает Nothing, мы знаем, что она выдаст исключение. В Java не существует эквивалентного типа такого рода.

1
fun throwException(): Nothing {
2
    throw Exception("Exception message)
3
}

 Это может пригодиться при тестировании поведения обработки ошибок в модульных тестах.

3. Модификаторы видимости

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

Public

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

Private

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

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

1
class Account {
2
    private val amount: Double = 0.0
3
}

Protected

protected модификатор можно применять только к свойствам или функциям внутри класса, объекта или интерфейса - его нельзя применять к функциям, классам или интерфейсам верхнего уровня. Свойства или функции с этим модификатором доступны только внутри определяющего его класса и любого подкласса.

Internal

В проекте, в котором есть модуль (модуль Gradle или Maven), класс, объект, интерфейс или функция, указанные с помощью internal модификатора, объявленного внутри этого модуля, доступны только из этого модуля.

1
internal class Account {
2
    val amount: Double = 0.0
3
}

4. Smart Casting

Приведение означает получение объекта другого типа и преобразование его в другой тип объекта. Например, в Java мы используем оператор instanceof, чтобы определить, принадлежит ли конкретный тип объекта другому типу, прежде чем мы затем приведем его.

1
/* Java */
2
if (shape instanceof Circle) {
3
    Circle circle = (Circle) shape;
4
    circle.calCircumference(3.5); 
5
}

Как вы можете видеть, мы проверили, является ли экземпляр shape Circle, и затем нам нужно явно привести ссылку на shape к типу Circle, чтобы мы могли вызывать методы типа Circle.

Еще одна потрясающая вещь о Kotlin - это умение его компилятора, когда дело доходит до кастинга. Теперь посмотрим версию в Котлине.

1
/* Kotlin */
2
if (shape is Circle) {
3
    shape.calCircumference(3.5)
4
}

Довольно аккуратно! Компилятору полезно знать, что блок if будет выполняться только в том случае, если объект shape является экземпляром Circle, поэтому механизм приведения сделан для нас под капотом.  Теперь мы можем легко вызвать свойства или функции типа Circle внутри блока if.

1
if (shape is Circle && shape.hasRadius()) {
2
    println("Circle radius is {shape.radius}")
3
}

Здесь последнее условие после && в заголовке if будет вызываться, только если первое условие true. Если shape не является Circle, то последнее условие не будет оцениваться.

5. Explicit Casting

Мы можем использовать оператор as (или небезопасный оператор литья) для явного указания ссылки типа на другой тип в Kotlin.

1
val circle = shape as Circle
2
circle.calCircumference(4)

Если явная операция литья является незаконной, обратите внимание, что будет выбрано ClassCastException. Чтобы предотвратить исключение исключения при кастинге, мы можем использовать оператора безопасного литья (или оператор сбрасывания с нулевым значением) as?.

1
val circle: Circle? = shape as? Circle

as? оператор попытается применить к предполагаемому типу, и он возвращает значение null, если значение не может быть выбрано вместо исключения исключения. Помните, что аналогичный механизм обсуждался в разделе Nullability в столбцах Nullability, Loops и Conditions в этой серии. Прочитайте его для переподготовки.

6. Объекты

Объекты в Kotlin больше похожи на объекты JavaScript, чем объекты Java. Обратите внимание, что объект в Kotlin не является экземпляром определенного класса!

Объекты очень похожи на классы. Вот некоторые из характеристик объектов в Котлине:

  • Они могут иметь свойства, методы и блок init.
  • Эти свойства или методы могут иметь модификаторы видимости.
  • У них не могут быть конструкторы (первичные или вторичные).
  • Они могут расширять другие классы или реализовывать интерфейс.

Давайте теперь разберемся, как создать объект.

1
object Singleton {
2
    
3
    fun myFunc(): Unit {
4
        // do something

5
    }
6
}

Мы помещаем ключевое слово объекта перед именем объекта, который хотим создать. На самом деле, мы создаем синглтоны, когда мы создаем объекты в Котлин, используя конструкцию объекта, потому что существует только один экземпляр объекта. Вы узнаете больше об этом, когда мы обсудим совместимость объектов с Java.

Singleton - это шаблон проектирования программного обеспечения, который гарантирует, что у класса есть только один экземпляр, и этот класс обеспечивает глобальную точку доступа к нему. В любое время, когда несколько классов или клиентов запрашивают класс, они получают один и тот же экземпляр класса. Вы можете проверить мое сообщение об одном шаблоне на Java, чтобы узнать больше об этом.

Вы можете получить доступ к объекту или синглтону в любом месте вашего проекта, если вы импортируете его пакет.

1
Singleton.myFunc()

Если вы Java-кодер, так мы обычно создаем синглтоны:

1
public class Singleton  {
2
 
3
    private static Singleton INSTANCE = null;
4
 
5
    // other instance variables can be here

6
     
7
    private Singleton() {};
8
 
9
    public static synchronized Singleton getInstance() {
10
        if (INSTANCE == null) {
11
            INSTANCE = new Singleton();
12
        }
13
        return(INSTANCE);
14
    }
15
     
16
    // other instance methods can follow 

17
}

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

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

1
public final class APIConstants {
2
   
3
   public static final String baseUrl = "https://www.myapi.com/";
4
5
   private APIConstants() {}
6
}

Этот код на Java можно более кратко преобразовать в Kotlin следующим образом:

1
package com.chike.kotlin.constants
2
3
object APIConstants {
4
    val baseUrl: String = "http://www.myapi.com/"
5
}

Здесь мы объявили константу APIConstants с базой свойств baseUrl внутри пакета com.chike.kotlin.constants. Под капотом для нас создается приватный статический конечный элемент-член baseUrl для Java и инициализируется строковым URL-адресом.

Чтобы использовать эту константу в другом пакете в Котлине, просто импортируйте пакет.

1
import com.chike.kotlin.constants.APIConstants
2
3
APIConstants.baseUrl

Совместимость с Java

Kotlin преобразует объект в конечный класс Java под капотом. Этот класс имеет личное статическое поле INSTANCE, которое содержит один экземпляр (одиночный элемент) класса. Следующий код показывает, как просто пользователи могут вызывать объект Kotlin из Java.

1
/* Java */
2
Singleton.INSTANCE.myFunc()

Здесь класс Java, называемый Singleton, был сгенерирован с открытым статическим конечным членом INSTANCE, включая публичную конечную функцию myFunc().

Чтобы функция или свойство объекта в Kotlin были статическим членом генерируемого класса Java, мы используем @JvmStatic аннотация. Вот как это использовать:

1
object Singleton {
2
    
3
    @JvmStatic fun myFunc(): Unit {
4
        // do something

5
    }
6
}

Применяя @JvmStatic для myFunc(), компилятор превратил его в статическую функцию.

 Теперь Java-вызовы могут вызывать его как обычный статический член-вызов. Обратите внимание, что использование статического поля INSTANCE для вызова участников будет по-прежнему работать.

1
/* Java */
2
Singleton.myFunc()

7. Сопутствующие объекты

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

Поскольку Kotlin не поддерживает статические классы, методы или свойства, подобные тем, которые у нас есть на Java, команда Kotlin предоставила нам более мощную альтернативу, называемую сопутствующими объектами. Сопутствующий объект - это в основном объект, принадлежащий классу, этот класс известен как сопутствующий класс объекта. Это также означает, что характеристики, о которых я упоминал для объектов, также относятся к сопутствующим объектам.

Создание объекта-компаньона

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

1
class Person private constructor(var firstName: String, var lastName: String) {
2
3
    companion object {
4
        fun create(firstName: String, lastName: String): Person = Person(firstName, lastName)
5
    }
6
}

Здесь мы создали конструктор private - это означает, что пользователи вне класса не могут создать экземпляр напрямую. Внутри нашего блока объектов-компаньонов у нас есть функция create(), которая создает объект Person и возвращает его.

Вызов функции объекта Companion

companion экземпляр объекта ленив. Другими словами, он будет создан только в случае необходимости в первый раз Создание экземпляра объекта companion происходит, когда создается экземпляр класса companion или доступ к элементам companion объекта.

Давайте посмотрим, как вызвать функцию сопутствующего объекта в Kotlin.

1
val person = Person.create("Cersei", "Lannister")
2
println(person.firstName) // prints "Cersei"

Как вы можете видеть, это похоже на вызов статического метода в Java как обычно. Другими словами, мы просто вызываем класс, а затем вызываем его. Обратите внимание: кроме функций мы можем также иметь свойства внутри нашего объекта-компаньона.

1
class Person private constructor(var firstName: String, var lastName: String) {
2
    init {
3
        count++
4
    }
5
    
6
    companion object {
7
        var count: Int = 0
8
        fun create(firstName: String, lastName: String): Person = Person(firstName, lastName)
9
        
10
        init {
11
            println("Person companion object created")
12
        }
13
    }
14
15
}

Также обратите внимание, что класс companion имеет неограниченный доступ ко всем свойствам и функциям, объявленным в его сопутствующем объекте, тогда как сопутствующий объект не может получить доступ к членам класса. У нас может быть блок кода init внутри объекта companion - он вызывается немедленно, когда создается объект-компаньон.

1
Person.create("Arya", "Stark")
2
Person.create("Daenerys", "Targaryen")
3
println(Person.count)

Результат выполнения кода выше будет:

1
Person companion object created
2
2

Помните, что может существовать только один экземпляр объекта companion класса.

Мы также можем предоставить объекту-компаньону имя.

1
// ...

2
companion object Factory {
3
    var count: Int = 0
4
    fun create(firstName: String, lastName: String): Person = Person(firstName, lastName)
5
}
6
// ... 

Здесь мы дали ему имя под названием Factory. Затем мы можем назвать это так в Kotlin:

1
Person.Factory.create("Petyr", "Baelish")

Этот стиль является многословным, поэтому придерживаться предыдущего способа является очень предпочтительным. Но это может пригодиться при вызове функции или свойства объекта-компаньона с Java.

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

1
interface PersonFactory {
2
    fun create(firstName: String, lastName: String): Person
3
}
4
5
class Person private constructor(var firstName: String, var lastName: String) {
6
    
7
    companion object : PersonFactory {
8
        override fun create(firstName: String, lastName: String): Person {
9
            return Person(firstName, lastName)
10
        }
11
    }
12
}

Здесь у нас есть интерфейс PersonFactory с единственной функцией create(). Рассматривая новый измененный объект companion, он теперь реализует этот интерфейс (вы узнаете о интерфейсах и наследовании в Kotlin в более позднем посте).

Совместимость с Java

Под покрытием сопутствующие объекты компилируются аналогично тому, как компилируется объект Kotlin. В нашем собственном случае для нас создаются два класса: конечный класс Person и внутренний статический конечный класс Person$Companion.

Класс Person содержит последний статический член под названием Companion - это статическое поле является объектом внутреннего класса Person$Companion. Внутренний класс Person$Companion также имеет своих собственных членов, и одна из них является публичной конечной функцией с именем create().

Обратите внимание, что мы не дали нашему объекту-компаньону имя, поэтому сгенерированный статический внутренний класс был Companion. Если бы мы дали ему имя, то сгенерированное имя было бы именем, которое мы дали ему в Kotlin.

1
/* Java */
2
Person person = Person.Companion.create("Jon", "Snow");

Здесь объект-компаньон в Kotlin не имеет имени, поэтому мы используем имя Companion, предоставленное компилятором для вызывающих Java-программ, для его вызова.

Аннотация @JvmStatic, применяемая к элементу объекта-компаньона, работает аналогично тому, как это работает для обычного объекта.

Расширения сопутствующих объектов

Аналогично тому, как функции расширения могут расширять функциональность класса, мы также можем расширять функциональность сопутствующего объекта. (Если вы хотите освежить в расширенных функциях в Kotlin, посетите руководство по дополнительным функциям в этой серии).

1
class ClassA {
2
3
    companion object  {
4
5
    }
6
}
7
8
fun ClassA.Companion.extFunc() {
9
    // ... do implementation

10
}
11
12
ClassA.extFunc()

Здесь мы определили функцию расширения extFunc() для сопутствующего объекта ClassA.Companion. Другими словами, extfunc() является расширением сопутствующего объекта. Затем мы можем вызвать расширение, как если бы оно было функцией(это не так!) объекта-компаньона.

За кулисами компилятор создаст статическую служебную функцию extFunc(). Объект-получатель в качестве аргумента этой служебной функции - ClassA$Companion.

Заключение

В этом уроке вы узнали об основных классах и объектах в Kotlin. Мы рассказали о классах:

  • класс создания
  • конструктор
  • properties
  • модификаторы видимости
  • smart casting
  • explicit casting

Кроме того, вы узнали о том, как объекты и сопутствующие объекты в Kotlin могут легко заменить ваши статические методы, константы и синглтоны, которые вы кодируете в Java. Но это еще не все! Можно узнать еще больше о Котлине. В следующем посте я покажу вам еще больше интересных возможностей, которые есть у Kotlin для объектно-ориентированного программирования. До скорой встречи!

Чтобы узнать больше о языке Kotlin, я рекомендую посетить документацию Kotlin. Или ознакомьтесь с некоторыми другими нашими статьями по разработке приложений для Android здесь, на Envato Tuts+!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.