что такое companion objects kotlin

Рефакторинг функций расширения в Kotlin: использование объекта-компаньона

что такое companion objects kotlin. Смотреть фото что такое companion objects kotlin. Смотреть картинку что такое companion objects kotlin. Картинка про что такое companion objects kotlin. Фото что такое companion objects kotlin

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

Точка отсчёта

Допустим, у нас есть такая функция:

Описанный далее подход к рефакторингу можно применять и к функциям верхнего уровня, а также к методам синглтона:

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

Тестируемость

Функции расширения по сути являются @JVMStatic методами конкретного вспомогательного класса. Вот Java-эквивалент этого метода:

Вообще мы используем старомодный синглтон (определённый в области видимости класса с помощью статического модификатора), к которому можем обращаться из любого места кода, чтобы воспользоваться его логикой. А в чём главная проблема синглтонов? В тестируемости.

Рассмотрим такой случай:

Решить эту проблему можно с помощью Robolectric или запуска теста на устройстве. Но нужно ли нам это? Эти варианты тестирования займут намного больше времени.

Усложнение логики

Что ещё может произойти со вспомогательными функциями? Они могут сильно усложниться, а мы поздно это заметим. В контексте предыдущего примера представим, что у нас появилось новое требование: при каждой проверке доступности аппаратной биометрии нужно также проверять результат А/В-теста, чтобы активировать функциональность.

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

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

Проблема большой кодовой базы

А что мешает нам просто реализовать новый класс для обработки логики и внедрить в конструктор каждого класса, который его использует? Мешает размер пул-реквеста.

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

Из-за всех этих изменений ваш пул-реквест может раздуться до гигантских размеров — и его будет сложно проверить. К тому же возрастёт риск пропустить ошибку.

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

Замена функции расширения на синглтон

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

Волшебство объекта-компаньона интерфейса

Теперь у нас есть класс, с которым можно работать. Поскольку мы стремимся к тестируемости, в будущем мы заменим прямые использования класса BiometricsUtils на интерфейс. Сейчас интерфейс выглядит так:

Вернёмся к варианту с параметрами в методе и в конце дополнительным этапом мигрируем на вариант без них.

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

Нам поможет объект-компаньон.

К счастью, требование использовать Companion отменили. И теперь мы можем обращаться к объектам-компаньонам в привычной манере — как к статическим функциям Java.

Более того, компилятор Kotlin достаточно сообразителен, чтобы различать вызовы методов интерфейса и его компаньона.

И поскольку объекты-компаньоны в Kotlin — это обычный синглтон-класс, они могут расширять классы и интерфейсы. Это приводит нас к объекту-компаньону, который реализует интерфейс своего родителя:

Внедрение интерфейса в качестве значения по умолчанию

Раз у нас теперь есть интерфейс, мы можем передать его в качестве параметра конструктора.

Убираем значение по умолчанию

Теперь можно убрать значение по умолчанию параметра biometricsUtils и через DI-систему подставить реальное значение.

Улучшаем интерфейс

Добавим новую реализацию BiometricsUtils :

Теперь через DI-систему предоставим новый класс:

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

Заключение

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

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

Источник

Kotlin, компиляция в байткод и производительность (часть 2)

что такое companion objects kotlin. Смотреть фото что такое companion objects kotlin. Смотреть картинку что такое companion objects kotlin. Картинка про что такое companion objects kotlin. Фото что такое companion objects kotlin

Это продолжение публикации. Первую часть можно посмотреть тут

Содержание:

Циклы:

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

1..10 тут это диапазон по которому происходит итерация. Компилятор Kotlin достаточно умный, он понимает что мы собираемся в данном случае делать и поэтому убирает весь лишний оверхед. Код компилируется в обычный цикл while с переменной счетчика цикла. Никаких итераторов, никакого оверхеда, все достаточно компактно.

Похожий цикл по массиву (который в Kotlin записывается в виде Array ), компилируется аналогичным образом в цикл for.

Немного другая ситуация возникает, когда происходит перебор элементов из списка:

В этом случае приходится использовать итератор:

Таким образом, в зависимости от того по каким элементам происходит перебор, компилятор Kotlin сам выбирает самый эффективный способ преобразовать цикл в байткод.

Ниже приведено сравнение производительности для циклов с аналогичными решениями в Java:

Циклы

что такое companion objects kotlin. Смотреть фото что такое companion objects kotlin. Смотреть картинку что такое companion objects kotlin. Картинка про что такое companion objects kotlin. Фото что такое companion objects kotlin

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

When — это аналог switch из Java, только с большей функциональностью. Рассмотрим ниже несколько примеров и то, во что они компилируются:

Для такого простого случая результирующий код компилируется в обычный switch, тут никакой магии не происходит:

Если же немного изменить пример выше, и добавить константы:

То код в этом случае уже компилируется в следующий вид:

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

Существует также возможность использовать модификатор const для констант, известных на момент компиляции.

Тогда в этом случае компилятор уже правильно оптимизирует when:

Если же заменить константы на Enum:

То код, также как в первом случае, будет компилироваться в switch (практический такой же как в случае перебора enum в Java).

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

Посмотрим на сравнение производительности решений на Kotlin и Java:

что такое companion objects kotlin. Смотреть фото что такое companion objects kotlin. Смотреть картинку что такое companion objects kotlin. Картинка про что такое companion objects kotlin. Фото что такое companion objects kotlin

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

Делегаты

Делегирование — это хорошая альтернатива наследованию, и Kotlin поддерживает его прямо из коробки. Рассмотрим простой пример с делегированием класса:

Класс Derived в конструкторе получает экземпляр класса, реализующий интерфейс Base, и в свою очередь делегирует реализацию всех методов интерфейса Base к передаваемому экземпляру. Декомпилированный код класса Derived будет выглядеть следующим образом:

В конструктор класса передается экземпляр класса, который запоминается в неизменяемом внутреннем поле. Также переопределяется метод print интерфейса Base, в котором просто происходит вызов метода из делегата. Все достаточно просто.

Существует также возможность делегировать не только реализацию всего класса, но и отдельных его свойств (а с версии 1.1 еще возможно делегировать инициализацию в локальных переменных).

Компилируется в код:

При инициализации класса DeleteExample создается экземпляр класса Delegate, сохраняемый в поле name$delegate. И далее вызов функции getName переадресовывается к вызову функции getValue из name$delegate.

В Kotlin есть уже несколько стандартных делегатов:

— lazy, для ленивых вычислений значения поля.
— observable, который позволяет получать уведомления обо всех изменения значения поля
— map, используемый для инициализации значений поля из значений Map.

Object и companion object

В Kotlin нет модификатора static для методов и полей. Вместо них, по большей части, рекомендуется использовать функции на уровне файла. Если же нужно объявить функции, которые можно вызывать без экземпляра класса, то для этого есть object и companion object. Рассмотрим на примерах как они выглядят в байткоде:

Простое объявление object с одним методом выглядит следующим образом:

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

Компилируется к вызову INSTANCE:

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

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

Компилятор Kotlin упрощает вызовы, но из Java, правда, выглядит уже не так красиво. К счастью, есть возможность объявить методы по настоящему статическими. Для этого существует аннотация @JvmStatic. Ее можно добавить как к методам object, так и к методам companion object. Рассмотрим на примере object:

В этом случае метод staticFun будет действительно объявлен статическим:

Для методов из companion object тоже можно добавить аннотацию @JvmStatic:

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

Как показано выше, Kotlin предоставляет различные возможности для объявления как статических методов так и методов компаньонов. Вызов статических методов чуть быстрее, поэтому в местах, где важна производительность, все же лучше ставить аннотации @JvmStatic на методы (но все равно не стоит рассчитывать на большой выигрыш в быстродействии)

lateinit свойства

Иногда возникает ситуация, когда нужно объявить notnull свойство в классе, значение для которого мы не можем сразу указать. Но при инициализации notnull поля мы обязаны присвоить ему значение по умолчанию, либо сделать свойство Nullable и записать в него null. Чтобы не переходить к nullable, в Kotlin существует специальный модификатор lateinit, который говорит компилятору Kotlin о том, что мы обязуемся сами позднее инициализировать свойство.

Если же мы попробуем обратиться к свойству без инициализации, то будет брошено исключение UninitializedPropertyAccessException. Подобная функциональность работает достаточно просто:

В getter вставляется дополнительная проверка значения свойства, и если в нем хранится null, то кидается исключение. Кстати именно из-за этого в Kotlin нельзя сделать lateinit свойство с типом Int, Long и других типов, которые соответствуют примитивным типам Java.

coroutines

В версии Kotlin 1.1 появилась новая функциональность, называемая корутины (coroutines). С ее помощью можно легко писать асинхронный код в синхронном виде. Помимо основной библиотеки (kotlinx-coroutines-core) для поддержки прерываний, есть еще и большой набор библиотек с различными расширениями:

kotlinx-coroutines-jdk8 — дополнительная библиотека для JDK8
kotlinx-coroutines-nio — расширения для асинхронного IO из JDK7+.

kotlinx-coroutines-reactive — утилиты для реактивных стримов
kotlinx-coroutines-reactor — утилиты для Reactor
kotlinx-coroutines-rx1 — утилиты для RxJava 1.x
kotlinx-coroutines-rx2 — утилиты для RxJava 2.x

kotlinx-coroutines-android — UI контекст для Android.
kotlinx-coroutines-javafx — JavaFx контекст для JavaFX UI приложений.
kotlinx-coroutines-swing — Swing контекст для Swing UI приложений.

Примечание: Функциональность пока находится в экспериментальной стадии, поэтому все сказанное ниже еще может измениться.

Для того, чтобы обозначить, что функция может быть прервана и использована в контексте прерывания, используется модификатор suspend

Декомпилированный код выглядит следующим образом:

Получается практически исходная функция, за исключением того, что еще передается один дополнительный параметр, реализующий интерфейс Continuation.

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

Корутины компилируются в конечный автомат (state machine). Рассмотрим на примере:

Функции foo и bar возвращают CompletableFuture, на которых вызывается suspend функция await. Декомпилировать в Java такой код не получится (по большей части из-за goto), поэтому рассмотрим его в псевдокоде:

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

Все исходные коды на Kotlin доступны в github. Можно открыть их у себя и поэкспериментировать с кодом, параллельно просматривая, в какой итоговый байткод компилируются исходники.

Выводы

Производительность приложений на Kotlin будет не сильно хуже, чем на Java, а с использованием модификатора inline может даже оказаться лучше. Компилятор во всех местах старается генерировать наиболее оптимизированный байткод. Поэтому не стоит бояться, что при переходе на Kotlin вы получите большое ухудшение производительности. А в особо критичных местах, зная во что компилируется Kotlin, всегда можно переписать код на более подходящий вариант. Небольшая плата за то, что язык позволяет реализовывать сложные конструкции в достаточно лаконичном и простом виде.

Спасибо за внимание! Надеюсь вам понравилась статья. Прошу всех тех, кто заметил какие-либо ошибки или неточности написать мне об этом в личном сообщении.

Источник

Kotlin Companion Object with Examples

In this tutorial, we are going to learn about Kotlin companion object with the help of examples. Companion objects are same as “object” but declared within a class.

The main difference between an object and companion object is, In object, methods and properties are tied to class’s instance but in the case of companion object, they are tied with the class.

Another difference is a companion object is a proper object. It can have its own supertypes and can be assigned to variables as well.

In the companion object case, the approach is the same as the object declaration. I would highly suggest you go through Kotlin object declaration first.

This is a normal object declaration.

Kotlin Companion Object Examples

Now I want to declare the object Client data inside a Class. How can I do it. Here we go.

When the code compiles all the fields and methods are converted to static fields and methods.

Now the question is how to access the properties and methods from the companion object. We can do it simply by using the class name.

We are using the normal class name and accessing the companion object Client code. The output.

The CustomClass is a normal class. It has all the properties of a normal class. We all know that a normal class can have its own methods. Let me show you.

NOTE: The companion object and its methods are only accessible via Class name not by Class instance.

Java and Companion Object

That is all from Kotlin Companion Object.

Источник

In the previous lesson, Arena with a mage in Kotlin (inheritance and polymorphism), we put our knowledge of inheritance and polymorphism to the test. In today’s tutorial, we’re going to go over static class members in Kotlin. Until now, we only used data, states, being carried by an instance. Properties, which we’ve defined, belonged to an instance and were unique to each instance. OOP, however, allows us to declare properties and methods on a class itself. We call these members static, sometimes called class members, who are independent of an instance.

WARNING! Today’s lesson will show you that static members are approaches that actually violate the object-oriented principles. OOP includes them only for special cases and in general everything can be written without static members. We always have to think carefully before adding static members. Generally, I would recommend for you not to use static members ever, unless you’re absolutely sure what you’re doing. Like global variables, static members are something that enable you to write bad code and violate good practices. Today, we’ll go over this topic just to make you understand some static methods and classes in Kotlin, not to write your own. Use this knowledge wisely, there will be less evil in the world.

Static (class) properties

We can declare various elements as static, let’s start with properties. As I mentioned in the introduction, static elements belong to a class, not to an instance. So the data stored there can be read even if an instance of the class has not been declared. Basically, we could say that static properties are shared among all class instances, but even that wouldn’t be accurate since they aren’t actually related to instances.

The class is simple, it represents a user in a system. Each user instance has its own name, password, and carries information about whether the user is logged in. In order for the user to log-in, we call the logIn() method which takes a password as a parameter. The method verifies whether it’s the right password. If the person behind the keyboard is really that user, it logs him/her in. It returns true / false depending on whether the login was successful. In reality, the password would be also hashed, but we won’t do any hashing this time around.

When a new user registers, the system tells him/her what the minimum length of their password must be. This number should be stored somewhere. During user registration, we still wouldn’t have its instance. The object has not been created and would only be created after the form has been completely filled and submitted. Therefore, we can’t use a public minimalPasswordLength property in the User class for this purpose. Either way, we still need a minimum password length stored in the User class somewhere since it, naturally, belongs there. We’ll make this value a static property using the companion object block:

Now, let’s switch to Main.kt and print the property to the console. We access this property directly on the class:

We can see the property really belongs to the class. We can access it from different places in the code without having to create a user. On the contrary, we wouldn’t be able to access it on the user instance:

IntelliJ will report an error and the code won’t be compiled.

Numbering the instances

The class stores the next instance’s id by itself. We assign this id to a new instance in the constructor and increase it by 1, which prepares it for the next instance. Not only properties can be static, this approach could be applied to a variety of class members.

Static methods

Let’s make another example, just to clarify. During user registration, we need to know the minimum password length before we create its instance. Also, it would be great if we could check the password before the program creates the user. A full check, in this case, would include checking whether the length is correct, making sure it doesn’t contain accent characters, verifying that there is at least one number in it, and so on. To do this, we’ll create a static validatePassword() utility method:

Let’s try to call the method on the User class:

Viewer beware! The validatePassword() method belongs to the class. Meaning that we can’t access any instance properties in it. Those properties don’t exist in the class context, rather in the context of an instance. It wouldn’t make any sense to use the user’s name in our method! You can try it out if you’d like to get a visual confirmation of its impossibility.

Password validation can be achieved without knowledge of static members. We could create a UserValidator class and write methods in it accordingly. Then, we’d have to create its instance to be able to call those methods. It’d be a bit confusing because the «concept of a user» would be unnecessarily split up into two classes. Now, thanks to static members, all of the information is neatly organized in one place.

Finally, we’ll test our static properties. The main() method will look like this:

Private constructor

If a class contains only utility methods or it doesn’t make sense to create an instance of it, for example we’ll never run two consoles at a time, we call it static class/object. Kotlin lets us to mark the class as static by using the object keyword. This object is then not possible to instantiate (we can’t create an instance of such class).

Let’s create a test object A which we won’t be able to instantiate. We’ll replace class by the object keyword:

Let’s try to instantiate the A object:

We’ll get an error because instantiation is prohibited. All properties of the object are static and therefore it doesn’t make sense to create an instance of it; it wouldn’t contain anything.

Kotlin also supports the static constructor which is called when the class is registered. We create it the same way as a class constructor, only in object.

If we wanted to have such constructor in a regular class and initialize static variables in it, we can do so:

Static registry

Let’s create a simple static class. We could create a class containing only utility methods and properties. However, I’ve decided I’ll have us create a static registry. Which is basically how you share important data between classes without making an instance.

Let’s say we’re making a more robust application, e.g. a digital journal. The application would be multilingual, its features would include adding bookmarks, choosing folders for storing files, color schemes and even running at system startup. It would have adjustable settings, e.g. picking the first day of the week (Sunday/Monday), which would be accessed from various parts in the code. Without knowledge of static members, we’d have to pass the settings instance to all objects (calendar, tasks, notes. ) through the constructor.

One workaround would be to use an object to store these settings. It’d be accessible everywhere in the program, without even having to create an instance. It’d contain all the necessary settings that would be used by other objects. Our object would end up looking something like this:

Now we’ll put our class to use even though we don’t have a full journal application. Let’s make a Calendar class and verify that we can truly access Settings there. We’ll add a method to it that returns all of the settings:

Last of all, we’ll print it all to the console:

We see that calendar instance has no problem accessing all program settings.

Again, be careful, this code can be improperly used to share unencapsulated data. We must only use it in specific situations. Most data exchanges should happen using parameterized instance constructors, not through static members.

Static members appear very often in design patterns, we’ve already mentioned them in our lessons. We’ll get in further detail in the approaches that bring object-oriented programming to perfection later on. For now, this will do. I don’t want to overwhelm you что такое companion objects kotlin. Смотреть фото что такое companion objects kotlin. Смотреть картинку что такое companion objects kotlin. Картинка про что такое companion objects kotlin. Фото что такое companion objects kotlinIn the next lesson, Properties in Kotlin, we’ll look at what properties are in Kotlin.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *