Вопросы и ответы для собеседования по Kotlin. Часть 1
Вопросы и ответы для собеседования по Kotlin. Часть 2
Вопросы и ответы для собеседования по Kotlin. Часть 3
Вопросы и ответы для собеседования по Kotlin. Часть 4 — вы находитесь здесь
Вопросы и ответы для собеседования по Kotlin. Часть 5 (скоро)
Список тем и вопросов:
1. Extensions (расширения)
2. Функции высшего порядка, лямбда-выражения, анонимные функции
Указатели на функции (Function references, Bound callable references)
Функции области видимости (Scope Functions) — let, run, with, apply, also
3. Встроенные (inline) функции, crossinline и noinline, reified
Почему reified возможно использовать только с inline-функциями?
Почему reified можно использовать только с обобщенными типами (generics)?
Extensions (расширения) — что это и для чего нужны?
Это функции, которые позволяют добавить функционал к уже определенным типам.
Kotlin предлагает нам концепцию «Extension Function»: мы можем к любому существующему типу добавить функцию-расширение, которая будет доступна через все объекты этого типа (но будет иметь доступ только к публичным частям).
Для добавления функционала класса, если он закрыт для расширения (например, лежит в сторонней библиотеке).
Для расширения
nullable
типов.Для расширения
companion object
.Для расширения свойств существующих классов.
Для конвертации моделей из одной в другую.
Для расширения функционала дженериков.
Подробнее: tproger.ru, kotlinlang.ru, metanit.com
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Как функции расширения работают под капотом?
По своей задумке, функция расширения Kotlin — это дополнительный метод для любого объекта, даже для потенциально несуществующего (нуллабельного). Этот инструмент является прямой реализацией переопределения методов паттерна проектирования Декоратор.
ВАЖНО: В функциях расширения мы можем обращаться к любым общедоступным свойствам и методам объекта, однако не можем обращаться к свойствам и методам с модификаторами private
и protected
.
ВАЖНО: Функции расширения не переопределяют функции, которые уже определены в классе. Если функция расширения имеет ту же сигнатуру, что и уже имеющаяся функция класса, то компилятор просто будет игнорировать подобную функцию расширения.
Определение аналогично определению обычной функции за тем исключением, что после слова fun
идет название типа, для которого определяется функция, и через точку название функции. Определим пару функций расширения к типам Int
и String
:
fun main() {
val hello: String = "hello world"
println(hello.wordCount('l')) // 3
println(hello.wordCount('o')) // 2
println(4.square()) // 16
println(6.square()) // 36
}
fun String.wordCount(c: Char) : Int {
var count = 0
for(n in this) {
if(n == c) count++
}
return count
}
fun Int.square(): Int {
return this * this
}
Для типа Int
определена функция возведения в квадрат. В каждой функции расширения через ключевое слово this
мы можем ссылаться на текущий объект того типа, для которого создается функция. Например, в функции:
fun Int.square(): Int {
return this * this
}
Через this
обращаемся к тому объекту, для которого будет вызываться функция. И затем вы можем вызвать ее следующим образом:
4.square() // 16
Для типа String
определена функция wordCount
, которая подсчитывает, сколько встречается определенный символ в строке.
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Как extensions выглядят в Java?
Функции расширения для JVM являются static final
методами.
Extensions в Kotlin не имеют прямого эквивалента в Java. Однако, компилятор Kotlin генерирует соответствующий статический метод в классе-компаньоне, который может быть вызван из Java кода в качестве статического метода этого класса.
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Функции высшего порядка
Функции высшего порядка в Котлин — это функции, которые принимают другие функции в качестве аргументов и/или возвращают функции в качестве результата. Эти принимаемые и возвращаемые функции обычно являются лямбда-выражениями.
Функции высшего порядка являются основой концепции функционального программирования.
Подробнее: kotlinlang.ru
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Лямбда-выражения и анонимные функции
Лямбда-выражения и анонимные функции — это функции без имени, которые могут быть переданы в качестве аргумента другим функциям. Их можно объявлять отдельно: сохранить в переменной и вызывать в нужном месте. Также их можно писать прямо внутри выражения, минуя отдельное объявление.
Лямбда-выражения и анонимные функции облегчают написание функций высшего порядка и улучшают читаемость кода. Они позволяют определить функцию на месте её вызова, без необходимости создавать отдельный метод.
Синтаксис лямбда-выражения:{ параметры -> тело_лямбды }
, где:
параметры: список параметров, разделенных запятой, которые могут использоваться внутри тела лямбда-выражения.
тело_лямбды: выражение или блок выражений, которые могут использоваться для выполнения логики лямбда-выражения. Если тело лямбда-выражения состоит из нескольких выражений, то они должны быть заключены в блок
{}
.
Синтаксис декларации анонимной функции полностью совпадает с синтаксисом обычной функции, но у первой нет имени.
fun defaultFun(x: Int, y: Int): Int = x + y // Именованная функция
val f = fun(x: Int, y: Int): Int = x + y // Анонимная функция
1. Лямбда-выражения
Лямбда-выражения всегда окружены фигурными скобками. Список параметров отделяется от тела лямбды ->
.
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 5)) // 6
Параметров может быть несколько, как в примере выше, либо может вовсе не быть, тогда левая часть вместе со стрелкой ->
опускаются.
val number = { 1 }
println(number()) // 1
Если в лямбда-выражении только один параметр, при этом его тип может быть выведен автоматически, то объявление параметра можно опустить вместе с ->
. В данном случае для обращения к параметру будет создано имя по умолчанию — it
.
var double = { x: Int -> x * 2 }
double = { it * 2 + 1 }
println(double(4)) // 9
Значение из лямбда-выражения можно вернуть явно — при помощи оператора return
. Либо неявно будет возвращено значение последнего (а возможно и единственного) выражения.
Если лямбда-выражение является последним аргументом функции, то оно может быть вынесено за круглые скобки. Если же оно является единственным аргументом функции, то круглые скобки можно опустить. Такой синтаксис также известен как trailing lambda.
Все три формы записи означают одно и тоже:
// лямбда передается в качестве аргумента
people.maxBy({ р: Person -> p.age })
// лямбда вынесена за скобки, т.к. является последним аргументом функции
people.maxBy() { р: Person -> p.age }
// скобки опущены, т.к. лямбда - единственный аргумент функции
people.maxBy { р: Person -> p.age }
Если параметр лямбды не используется, разрешено применять подчеркивание вместо его имени:
map.forEach { _, value -> println("$value!") }
2. Анонимные функции
Анонимная функция объявляется также как и обычная функция, но без указания имени.
// анонимная функция в виде выражения
fun(x: Int, y: Int): Int = x + y
// та же функция, но в виде блока
fun(x: Int, y: Int): Int {
return x + y
}
В отличие от обычных функций, в анонимной функции можно опустить тип параметров, если его можно вывести из контекста.
val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter(fun(n) = n % 2 == 0)
Здесь мы используем метод filter
списка numbers
, чтобы отобрать только четные числа. В качестве аргумента для метода filter
мы передаем анонимную функцию с одним параметром. Тип параметра не указывается явно, но компилятор может вывести, что параметр должен иметь тип Int
, так как мы оперируем целочисленными значениями в лямбда-выражении.
Анонимные функции следуют тем же правилам, что и обычные функции: для функций с телом-выражением возвращаемый тип и оператор return
могут быть опущены, но для функций с телом-блоком они должны быть указаны явно.
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Разница между лямбда-выражением и анонимной функцией
1. Синтаксис
Лямбда-выражения определяются заключением их в фигурные скобки в виде { параметры -> тело }
.
Анонимные функции определяются через ключевое слово fun
как обычные функции, хотя не имеют имени.
2. Поведение оператора return
без метки
В лямбда-выражении использование оператора
return
без метки приводит к возврату из обрамляющей (внешней) функции, а не из самого лямбда-выражения (т.е. полностью завершает работу этой функции и код, указанный после оператораreturn
никогда не выполнится). Это называется нелокальным возвратом (non-local return), и может иметь неожиданное поведение и привести к ошибкам. В лямбда-выражениях рекомендуется использовать метки для явного указания точки возврата.В анонимной функции
return
без метки приводит к возврату только из самой анонимной функции (а не из внешней функции), продолжая выполнение кода после вызова анонимной функции в обрамляющей функции. Анонимные функции ведут себя как ожидается для классических функций с явным операторомreturn
.
Таким образом, поведение оператора return
без метки различается только в том, как завершается выполнение функции, в которой вызывается выражение. В лямбда-выражении, вызывающая функция завершается полностью, а в анонимной функции только сама анонимная функция.
3. Поведение оператора return
с меткой
Оператор return
с меткой позволяет указать точное место, из которого нужно вернуться при вызове return
.
Если использовать
return@label
в лямбда-выражении, то возврат будет осуществляться из конкретной лямбды, к которой применена метка. Вместо нелокального возврата, который происходит при использованииreturn
без метки,return
с меткой завершит только ту лямбду, которая соответствует указанной метке, и выполнение кода продолжится после этой лямбды во внешней функции. Метка позволяет читать и понимать код проще, так как явно указывает, откуда происходит возврат.В анонимных функциях
return
без метки уже осуществляет возврат из самой анонимной функции. Однако, при использовании меткиreturn@label
вы также можете контролировать возврат из анонимной функции в сложных сценариях (например, при работе с несколькими вложенными функциями).
В обоих случаях использование оператора return
с меткой показывает точку возврата и делает код более явным и контролируемым.
Подробнее о возврате к меткам: kotlinlang.ru
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Что такое функциональный тип, какие у него ограничения?
Язык Kotlin допускает объявлять тип анонимных функций или лямбда выражений — функциональный.
Функциональный тип — это тип данных, который позволяет работать с функциями как с обычными объектами, передавать функции в качестве аргументов и возвращать их из функций. Синтаксис функционального типа в Котлин представлен списком типов параметров, разделенных запятой, затем оператором ->
и типом возвращаемого значения функции.
Пример функционального типа: (a: Int, b: Int) -> Int
Здесь функциональный тип описывает функцию с двумя параметрами типа Int
и возвращаемым значением типа Int
.
Функциональный тип может быть использован для создания переменных, которые могут хранить ссылки на функции. А также поддерживает перегрузку, что позволяет иметь несколько функций с различными сигнатурами, но с одинаковым именем.
Ограничения функционального типа:
Тип передаваемой функции должен быть определен явно, чтобы компилятор мог проверить типы аргументов и возвращаемых значений.
Функциональный тип может содержать только один тип возвращаемого значения.
Функциональный тип не может содержать более 22 параметров из-за ограничения JVM.
Функциональный тип не поддерживает неявные преобразования типов.
Несмотря на эти ограничения, функциональные типы позволяют обрабатывать функции как объекты, что повышает гибкость и выразительность кода. Пример определения функционального типа:
// определение функционального типа
typealias Operation = (Int, Int) -> Int
// использование функционального типа
fun calculate(op: Operation, a: Int, b: Int): Int {
return op(a, b)
}
// пример вызова функции calculate
val sum: Operation = { x, y -> x + y }
calculate(sum, 10, 5) // результат: 15
Код из примера определяет функциональный тип Operation
, который представляет собой функцию, принимающую два аргумента типа Int
и возвращающую значение типа Int
. Затем создается функция calculate
, которая принимает три параметра: функцию op
типа Operation
и два аргумента типа Int
. Внутри функции calculate
вызывается переданная функция op
с переданными аргументами a
и b
, и результат возвращается из функции calculate
. В конце кода создается переменная sum
, которая содержит лямбда-выражение, реализующее операцию сложения. Далее вызывается функция calculate
с параметрами sum
, 10
и 5
, что приводит к вызову функции sum
с аргументами 10
и 5
, и результатом является число 15
.
Подробнее: kotlinlang.ru
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Как работают SAM-conversions?
Single Abstract Method (SAM) интерфейсы — это интерфейсы только с одним абстрактным методом (функциональные интерфейсы). Kotlin поддерживает соглашение SAM — автоматическую конвертацию функций и lambda между Kotlin и Java.
SAM-conversions позволяют использовать Java-интерфейсы с единственным абстрактным методом в Kotlin, как если бы это были функциональные типы. В Kotlin вы можете использовать такие интерфейсы для создания лямбда-выражений без явного определения функционального типа.
При использовании интерфейса с единственным абстрактным методом в качестве функционального интерфейса в Java, вы можете передавать его экземпляры вместо лямбда-выражений. Это тоже возможно в Kotlin, но на самом деле Kotlin предоставляет более простой синтаксис для этого. Когда вам нужно использовать функциональный интерфейс в Kotlin, вы можете передать lambda-выражение, которое соответствует сигнатуре единственного метода интерфейса, вместо экземпляра интерфейса. Компилятор сам преобразует лямбда-выражение в экземпляр интерфейса, используя функцию-расширение метода invoke
интерфейса. Пример:
interface OnClickListener {
fun onClick(view: View)
}
class Button {
fun setOnClickListener(listener: OnClickListener) {
// ...
}
}
val button = Button()
button.setOnClickListener { view ->
// обработка нажатия кнопки
}
В этом примере мы определяем интерфейс OnClickListener
с единственным абстрактным методом onClick
. Затем мы создаем класс Button
, который может иметь слушатель, реализующий данный интерфейс. После этого мы создаем экземпляр Button
и передаем лямбда-выражение с соответствующей сигнатурой в качестве слушателя. Компилятор автоматически преобразует это лямбда-выражение в экземпляр интерфейса OnClickListener
, используя функцию-расширение invoke
интерфейса.
Подробнее: kotlinlang.ru, hr-vector.com, coderlessons.com
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Указатели на функции (Function references, Bound callable references)
В языке Kotlin есть возможность работать с функциями как с объектами. Функции можно сохранять в переменные, передавать как аргументы и возвращать из других функций. Для этого можно использовать функциональные ссылки (Function references), которые представляют собой указатель на функцию.
1. Function references
Синтаксис функциональной ссылки имеет следующий вид: ::function_name
. Указатели на функции представляют собой сокращенную форму записи вызова функции. Вместо того, чтобы объявлять лямбда-выражение и передавать его как аргумент функции, можно использовать ссылку на существующий метод. Например, у нас есть класс Person
с методом getName()
:
class Person(val name: String) {
fun getName(): String = name
}
Тогда мы можем использовать указатель на метод getName()
вместо лямбда-выражения:
val persons = listOf(Person("Alice"), Person("Bob"))
val names = persons.map(Person::getName)
2. Bound callable references
Bound callable references (привязанные ссылки) — это то же самое понятие, что и указатели на методы, но в случае, когда метод вызывается на экземпляре класса. В этом варианте мы можем использовать ссылку на метод, связанную с конкретным экземпляром класса. Для создания привязанной ссылки на метод используется следующий синтаксис: <object_name>::<method_name>
.
Допустим, что у нас есть экземпляр класса person
типа Person
. Тогда мы можем использовать ссылку на метод getName()
для получения его имени:
val person = Person("Alice")
val name = person::getName
Здесь name
будет ссылаться на метод getName()
объекта person
.
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Функции области видимости (Scope Functions) — let, run, with, apply, also
В Kotlin есть 5 функций: let, run, with, apply, also
, объединенных общим названием Scope Function (функции области видимости). Все они используются для одной цели — выполнить какой-то блок кода для конкретного объекта. При вызове подобной функции создается временная область видимости. В этой области видимости можно обращаться к объекту без использования его имени.
В основном эти 5 функций отличаются только 2 параметрами:
способ ссылки на контекстный объект (
this
илиit
)возвращаемое значение (объект контекста или результат лямбды)
1. Способ ссылки на объект
Внутри лямбда-выражения, которое передается функции области видимости, объект контекста доступен по краткой ссылке, а не по его фактическому имени. Каждая функция области видимости использует один из двух способов доступа к объекту контекста: как лямбда-получатель (this
) или как лямбда-аргумент (it
).
Различие между this
и it
заключается в том, что:
this
используется, когда мы работаем с объектом, на котором вызывается функция (run
,with
иapply
)it
используется, когда мы работаем с аргументом, переданным в функцию (let
иalso
)
Обычно this
используется для установки свойств или вызова методов объектов, а it
— для выполнения операций с аргументами.
2. Возвращаемое значение
Функции области видимости также отличаются по значению, которое они возвращают:
apply
иalso
возвращают объект контекста. Это позволяет использовать эти функции для создания цепочек вызовов методов и свойств объекта.let
,run
иwith
возвращают результат лямбды. Результат может быть использован для сохранения в переменную или передачи в дальнейшую цепочку вызовов.
Шпаргалка по выбору функции
Функция |
Ссылка на объект |
Возвращаемое значение |
Является функцией-расширением |
---|---|---|---|
let |
it |
Результат лямбды |
Да |
run |
this |
Результат лямбды |
Да |
run |
- |
Результат лямбды |
Нет: может быть вызвана без объекта |
with |
this |
Результат лямбды |
Нет: принимает объект в качестве аргумента |
apply |
this |
Контекстный объект |
Да |
also |
it |
Контекстный объект |
Да |
Если таблицы недостаточно:
Выполнить блок кода для значения, отличного от null -
let
.Использовать результат выполнения цепочки вызовов -
let
.Инициализация и настройка объекта -
apply
.Настройка объекта и получение результата выполнения операций -
run
.Выполнение операций, для которых требуется временная область видимости -
run
без субъекта вызова.Добавление объекту дополнительных значений -
also
.Группировка всех функций, вызываемых для объекта:
with
.
Все функции области видимости являются inline
, то есть их тело вставляется в место, где они вызываются, что позволяет исключить накладные расходы на вызов метода.
Подробнее: metanit.com, bimlibik.github.io, kotlinlang.ru, habr.com
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Что такое inline функции, в чем их преимущество?
В Kotlin есть два типа функций: обычные и встроенные. Обычные функции похожи на функции в других языках программирования. Но встроенные функции имеют модификатор inline
. Это позволяет компилятору подставить тело функции прямо в место её вызова.
Как работают inline функции?
Использование анонимных функций (лямбда-выражений) в Kotlin приводит к дополнительным затратам памяти. При использовании лямбда-выражения создается объект FunctionN
(где N
— количество параметров в лямбда-выражении), который содержит ссылку на само лямбда-выражение и может содержать захваченные переменные. При передаче лямбда-выражения в качестве параметра метода также создается новый объект FunctionN
, что приводит к дополнительным затратам памяти.
Поэтому, чтобы избежать создания дополнительных объектов при передаче лямбда-выражений в функцию в качестве параметра, можно использовать встраивание (inline). Ключевое слово inline
позволяет компилятору подставить тело функции непосредственно в место её вызова, вместо того, чтобы создавать объекты функций. Таким образом можно уменьшить затраты на создание объектов и улучшить производительность приложения.
Пример синтаксиса inline-функций с лямбдой:
inline fun functionName(parameter1: Type1, parameter2: Type2, ..., parameterN: TypeN, block: () -> Unit): ReturnType {
// function body
}
Модификатор inline
влияет и на функцию, и на лямбду, переданную ей: они обе будут встроены в место вызова.
Подробнее: kotlinlang.ru, bimlibik.github.io, medium.com
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Модификатор noinline
Если же вы хотите, чтобы некоторые лямбды, переданные inline-функции, не были встроены, то отметьте их модификатором noinline
.
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ...
}
Разница между ними в том, что встраиваемая лямбда может быть вызвана только внутри inline-функции, либо может быть передана в качестве встраиваемого аргумента. В то время как с noinline-функциями можно работать без ограничений: хранить внутри полей, передавать куда-либо и т.д.
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Что такое нелокальный return?
В Котлин non-local return — это механизм, который позволяет выйти из внешней функции или лямбда-выражения и вернуться к вызывающему коду, обходя оставшуюся часть текущей функции или лямбда-выражения. Он работает по-разному в зависимости от того, является ли функция inline или не-inline.
В не-inline функциях:
Если внутри функции есть лямбда-выражение, non-local return из лямбда-выражения может привести к нелокальному завершению внешней функции.
Для использования non-local return внутри лямбда-выражения в не-inline функции, необходимо использовать метку (label) и оператор
return@label
.
В inline-функциях:
В inline-функциях, лямбда-выражения становятся частью кода функции и имеют локальный контроль над потоком управления.
Оператор
return
внутри лямбда-выражения в inline-функции приведет только к завершению самого лямбда-выражения, не влияя на внешнюю функцию.
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Как работает модификатор crossinline?
crossinline — ключевое слово, которое используется для указания, что лямбда-выражение не может содержать нелокальных return
, даже если оно передано в inline-функцию.
Когда мы передаем лямбда-выражение в функцию в качестве параметра, мы можем использовать оператор return
внутри лямбды, чтобы выйти из цикла или функции, в которой вызывается лямбда. Однако, если мы передаем лямбда-выражение в inline-функцию, код лямбда-выражения может быть вставлен прямо в место вызова функции. В этом случае, если в лямбде используется оператор return
, это может привести к выходу из внешней функции, что не всегда желательно.
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Когда нужно использовать crossinline, а когда noinline?
Модификаторы crossinline
и noinline
используются для управления поведением лямбда-выражений, переданных в качестве параметров функций. Они позволяют указывать, может ли лямбда-выражение содержать операторы return
.
Модификатор
crossinline
используется для указания того, что лямбда-выражение не может содержать операторыreturn
, даже если функция, принимающая лямбда-выражение, инлайновая.Модификатор
noinline
, с другой стороны, указывает на то, что лямбда-выражение может быть сохранено как объект функции, а не выполнено внутри вызывающей функции. Это может быть полезно в случае, когда вы хотите использовать лямбда-выражение где-то ещё, например, как параметр для другой функции.
ВЫВОД: crossinline
должен использоваться только тогда, когда вы уверены в том, что оператор return
не будет использоваться внутри лямбда-выражения. Если лямбда-выражение должно содержать оператор return
, то следует использовать ключевое слово noinline
вместо crossinline
.
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Ключевое слово reified
reified — это ключевое слово, которое может быть использовано только в inline-функциях. reified
позволяет получить информацию о типе generic-параметра во время выполнения программы. В обычном случае, информация о типах стирается и недоступна во время выполнения, но с помощью reified
можно сохранять эту информацию и использовать в других частях приложения.
Несколько простых примеров применения:
1. Получить доступ к типу параметра во время выполнения
fun main() {
printType<String>() // String
printType<Int>() // Int
}
private inline fun <reified T> printType() {
println(T::class.simpleName)
}
В этом примере мы определяем функцию printType()
с типовым параметром T
, который мы указываем с помощью reified
. Внутри функции мы можем получить тип T
во время выполнения, используя T::class
. Затем выводим название типа на экран с помощью simpleName
. Когда мы вызываем функцию printType()
с типом String
или Int
, она выводит соответствующий тип на экран.
2. reified
вместе с is
для проверки типа аргумента во время выполнения
fun main() {
println(isOfType<Int>(1)) // true
println(isOfType<Int>("Hello")) // false
}
private inline fun <reified T> isOfType(value: Any): Boolean {
return value is T
}
Здесь мы определяем функцию isOfType()
, которая принимает значение типа Any
и возвращает true
, если оно является типом T
. Мы используем reified
, чтобы получить доступ к типу T
во время выполнения. Затем мы используем оператор is
для проверки типа значения и возвращаем соответствующее boolean значение.
3. Получить список элементов перечисления
enum class Color { RED, GREEN, BLUE }
fun main() {
printEnumValues<Color>() // RED, GREEN, BLUE
}
private inline fun <reified T : Enum<T>> printEnumValues() {
enumValues<T>().forEach { value ->
println(value)
}
}
Определяем функцию printEnumValues()
, которая выводит список элементов перечисления типа T
. Мы применяем reified
, чтобы получить доступ к типу T
во время выполнения. Затем используем enumValues<T>()
, чтобы получить список всех значений перечисления типа T
. Внутри цикла выводим каждое значение на экран. Когда мы вызываем функцию printEnumValues()
с типом Color
, она выводит "RED", "GREEN" и "BLUE" в консоль.
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Почему reified возможно использовать только с inline-функциями?
Ключевое слово reified
используется только с inline-функциями, т.к. оно позволяет получить доступ к информации о типе-параметре на этапе выполнения программы, что невозможно для обычных (non-inline) функций.
inline-функции в Kotlin позволяют копировать тело функции непосредственно в вызывающий код. Это позволяет избежать накладных расходов на создание объектов и вызовы функций при каждом вызове.
Именно reified
в комбинации с inline
позволяет сохранить информацию о типе-параметре и передать ее внутрь функции в рантайме, что было бы невозможно без inline
.
Также стоит отметить, что ключевое слово reified
можно применять только с обобщенными типами (дженериками).
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Почему reified можно использовать только с обобщенными типами (generics)?
Ключевое слово reified
в языке Kotlin можно использовать только с обобщенными типами, потому что оно предназначено для решения конкретной проблемы, связанной именно с дженериками.
Одна из особенностей обобщенных типов заключается в том, что информация о типе становится недоступной на этапе выполнения программы. Вместо этого на этапе компиляции создается код, который работает с типом-параметром "на уровне объекта", то есть как с любым другим объектом, не зная его конкретного типа.
Именно reified
позволяет сохранить информацию о типе-параметре на этапе выполнения программы. И дает нам возможность проверять типы в рантайме, создавать объекты типа-параметра и передавать их в качестве параметров других функций.
Таким образом, ключевое слово reified
не имеет смысла применять к необобщенным типам, поскольку они уже доступны в качестве конкретных объектов на этапе выполнения.
—————— ↑↑↑ к списку вопросов ↑↑↑ ——————
Почему нельзя все функции сделать inline?
Технически, можно попробовать сделать все функции inline
. Однако, это может привести к ряду негативных последствий:
1. Увеличение размера скомпилированного кода
Поскольку inline-функции заменяются на их вызовы во время компиляции, это может привести к увеличению размера скомпилированного кода, особенно если функции содержат большой объем кода.
2. Увеличение времени компиляции
inline-функции могут увеличить время компиляции проекта, так как компилятору нужно заменить каждый вызов функции на ее код.
3. Увеличение расхода памяти
Если функции являются часто используемыми и содержат большой объем кода, использование inline-функций может привести к увеличению расхода памяти.
4. Ограничения на использование лямбда-выражений
Применение inline-функций может ограничить возможности использования лямбда-выражений. Например, в inline-функциях нельзя использовать функции, которые не могут быть inlined, такие как protected-функции или функции из других модулей.
5. Ограничения на использование рекурсии
Использование inline-функций может ограничить возможность использования рекурсии, так как замена рекурсивного вызова функции на ее код может привести к бесконечной рекурсии или переполнению стека.
ВЫВОД: если все функции сделать inline
, это может привести к экспоненциальному росту размера кода и увеличению времени компиляции. Поэтому необходимо выбирать оптимальную стратегию для каждой конкретной функции в зависимости от ее предполагаемого использования.
Вопросы и ответы для собеседования по Kotlin. Часть 1
Вопросы и ответы для собеседования по Kotlin. Часть 2
Вопросы и ответы для собеседования по Kotlin. Часть 3
Вопросы и ответы для собеседования по Kotlin. Часть 4 — вы находитесь здесь
Вопросы и ответы для собеседования по Kotlin. Часть 5 (скоро)