Telegram Group & Telegram Channel
Продолжаем тренировать #насмотренность с inline-классами. В предыдущем посте я обещал рассказать про boxing inline-классов. Но сначала немного поговорим про примитивы.

Один из моих любимых вопросов на собеседовании — есть ли в Kotlin примитивы? Вот в Java есть примитив int и объект Integer, а в Kotlin только Int. На самом деле при компиляции под JVM компилятор сам решает что использовать. Когда возможно, использует примитив, но если тип используется в дженерике или объявлен как nullable, нужен объект и происходит boxing — примитив оборачивается в объект Integer.

Теперь вернёмся к inline-классам. Подобно примитивам, при работе с inline-классами Kotlin старается избежать лишних обёрток. Чтобы убедиться, что в конкретном случае не происходит boxing, можно посмотреть как код выглядит для JVM через Show Kotlin Bytecode > Decompile. Случаи, когда происходит boxing, подробно описаны в KEEP. Рекомендую прочитать полностью, а пока сосредоточимся на самых интересных моментах.

0️⃣ Нуллабельность

Если нужна нуллабельность, но хочется обойтись без boxing'а, можно создать специальное значение, которое будет заменять null. Помните Color.Unspecified в Compose? Это как раз оно. Кстати, недавно подобные специальные значения добавили и inline-классам входящим в состав TextStyle. В сообщении к коммиту есть бенчмарки показывающие сколько аллокаций удалось на этом сэкономить.

1️⃣ Создание подтипов

Когда в Kotlin появились sealed-интерфейсы, я подумал что они будут неплохо комбинироваться с inline-классами. Была мысль завести абстракцию чтобы одинаково работать со строками и текстовыми ресурсами:

sealed interface TextValue {
fun get(resources: Respurces): String

@JvmInline
value class Plain(val value: String) : TextValue {
fun get(resources: Resources) = value
}

@JvmInline
value class Res(@StringRes val resId: Int) : TextValue {
fun get(resources: Resources) = resources.getString(resId)
}
}


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

Вариантов минимизации boxing'а в подобных случаях как минимум два:

☝️ Если тип внутреннего значения у разных "подтипов" разный, можно хранить общий супертип. Так сделано в Result, внутри лежит Any?, а чтобы отличать контент от ошибки, создан вспомогательный тип Failure, в который оборачиваются ошибки.
✌️ Можно комбинировать тип и значение. Это хорошо работает с примитивами. Например, в Compose TextUnit может иметь размерность Sp и Em, но это всё один inline-класс. Информация о типе хранится в младших битах Long'а, а значение в следующих за ними битах. Другой пример такого подхода — Duration из stdlib.

2️⃣ Дженерики

С использованием inline-класса на позиции дженерика всё понятно — в этом случае без boxing'а не обойтись. Но дженерики указанные у самого inline-класса не влияют на boxing.

Классный пример применения inline-классов с дженериками — безопасные ID. Представим, что у нас есть несколько разных сущностей со строковыми ID и мы хотим чтобы на уровне контракта нельзя было по ID пользователя запросить товар и наоборот. В 2019 году Jake Wharton предложил создавать inline-классы для строгой типизации ID. С тех пор появилась возможность указывать дженерики у inline-классов и теперь можно не плодить отдельные классы-обёртки на каждый тип сущности, достаточно создать один inline-класс с дженериком:
@JvmInline
value class Id<out T>(val value: String)

data class User(val id: Id<User>)


Всем inline-классы в код!

UPD: @senk0n подсказывает, что Romain Guy недавно написал пример как можно в inline-класс запихнуть целую сетку 8х8, каждая ячейка которой может иметь значение 1 или 0.



group-telegram.com/rareilly/164
Create:
Last Update:

Продолжаем тренировать #насмотренность с inline-классами. В предыдущем посте я обещал рассказать про boxing inline-классов. Но сначала немного поговорим про примитивы.

Один из моих любимых вопросов на собеседовании — есть ли в Kotlin примитивы? Вот в Java есть примитив int и объект Integer, а в Kotlin только Int. На самом деле при компиляции под JVM компилятор сам решает что использовать. Когда возможно, использует примитив, но если тип используется в дженерике или объявлен как nullable, нужен объект и происходит boxing — примитив оборачивается в объект Integer.

Теперь вернёмся к inline-классам. Подобно примитивам, при работе с inline-классами Kotlin старается избежать лишних обёрток. Чтобы убедиться, что в конкретном случае не происходит boxing, можно посмотреть как код выглядит для JVM через Show Kotlin Bytecode > Decompile. Случаи, когда происходит boxing, подробно описаны в KEEP. Рекомендую прочитать полностью, а пока сосредоточимся на самых интересных моментах.

0️⃣ Нуллабельность

Если нужна нуллабельность, но хочется обойтись без boxing'а, можно создать специальное значение, которое будет заменять null. Помните Color.Unspecified в Compose? Это как раз оно. Кстати, недавно подобные специальные значения добавили и inline-классам входящим в состав TextStyle. В сообщении к коммиту есть бенчмарки показывающие сколько аллокаций удалось на этом сэкономить.

1️⃣ Создание подтипов

Когда в Kotlin появились sealed-интерфейсы, я подумал что они будут неплохо комбинироваться с inline-классами. Была мысль завести абстракцию чтобы одинаково работать со строками и текстовыми ресурсами:

sealed interface TextValue {
fun get(resources: Respurces): String

@JvmInline
value class Plain(val value: String) : TextValue {
fun get(resources: Resources) = value
}

@JvmInline
value class Res(@StringRes val resId: Int) : TextValue {
fun get(resources: Resources) = resources.getString(resId)
}
}


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

Вариантов минимизации boxing'а в подобных случаях как минимум два:

☝️ Если тип внутреннего значения у разных "подтипов" разный, можно хранить общий супертип. Так сделано в Result, внутри лежит Any?, а чтобы отличать контент от ошибки, создан вспомогательный тип Failure, в который оборачиваются ошибки.
✌️ Можно комбинировать тип и значение. Это хорошо работает с примитивами. Например, в Compose TextUnit может иметь размерность Sp и Em, но это всё один inline-класс. Информация о типе хранится в младших битах Long'а, а значение в следующих за ними битах. Другой пример такого подхода — Duration из stdlib.

2️⃣ Дженерики

С использованием inline-класса на позиции дженерика всё понятно — в этом случае без boxing'а не обойтись. Но дженерики указанные у самого inline-класса не влияют на boxing.

Классный пример применения inline-классов с дженериками — безопасные ID. Представим, что у нас есть несколько разных сущностей со строковыми ID и мы хотим чтобы на уровне контракта нельзя было по ID пользователя запросить товар и наоборот. В 2019 году Jake Wharton предложил создавать inline-классы для строгой типизации ID. С тех пор появилась возможность указывать дженерики у inline-классов и теперь можно не плодить отдельные классы-обёртки на каждый тип сущности, достаточно создать один inline-класс с дженериком:
@JvmInline
value class Id<out T>(val value: String)

data class User(val id: Id<User>)


Всем inline-классы в код!

UPD: @senk0n подсказывает, что Romain Guy недавно написал пример как можно в inline-класс запихнуть целую сетку 8х8, каждая ячейка которой может иметь значение 1 или 0.

BY Ra'Reilly - Заметки про Android и не только


Warning: Undefined variable $i in /var/www/group-telegram/post.php on line 260

Share with your friend now:
group-telegram.com/rareilly/164

View MORE
Open in Telegram


Telegram | DID YOU KNOW?

Date: |

Lastly, the web previews of t.me links have been given a new look, adding chat backgrounds and design elements from the fully-features Telegram Web client. So, uh, whenever I hear about Telegram, it’s always in relation to something bad. What gives? Either way, Durov says that he withdrew his resignation but that he was ousted from his company anyway. Subsequently, control of the company was reportedly handed to oligarchs Alisher Usmanov and Igor Sechin, both allegedly close associates of Russian leader Vladimir Putin. Investors took profits on Friday while they could ahead of the weekend, explained Tom Essaye, founder of Sevens Report Research. Saturday and Sunday could easily bring unfortunate news on the war front—and traders would rather be able to sell any recent winnings at Friday’s earlier prices than wait for a potentially lower price at Monday’s open. Following this, Sebi, in an order passed in January 2022, established that the administrators of a Telegram channel having a large subscriber base enticed the subscribers to act upon recommendations that were circulated by those administrators on the channel, leading to significant price and volume impact in various scrips.
from br


Telegram Ra'Reilly - Заметки про Android и не только
FROM American