Kotlin-Java interop: 'companion object' and 'private' visibility modifier
Заметка: данный пост представляет собой небольшое объяснение на частый вопрос "почему используешьprivate companion object
с вложеннымиprivate const val
?"
Дано
Рассмотрим пример с полями в companion object
:
class KotlinClassSample { companion object { @JvmField val PUBLIC_STATIC_FIELD = emptyList() val publicNonStaticField = emptyList() const val PUBLIC_STATIC_PRIMITIVE_FIELD = 0 // JvmField has no effect on a private property private val privateField = emptyList() private const val PRIVATE_STATIC_PRIMITIVE_FIELD = 0 } }
Проверим доступ к KotlinClassSample
коду из Java и Kotlin:
class JavaClassAccessTest { void callingKotlinFromJava() { // OK KotlinClassSample.PUBLIC_STATIC_FIELD; KotlinClassSample.PUBLIC_STATIC_PRIMITIVE_FIELD; KotlinClassSample.Companion.getPublicNonStaticField(); } }
class KotlinClassAccessTest { fun callingKotlinFromKotlin() { // OK KotlinClassSample.PUBLIC_STATIC_FIELD KotlinClassSample.PUBLIC_STATIC_PRIMITIVE_FIELD KotlinClassSample.publicNonStaticField } }
Ожидаемое поведение на основе модификатора доступа public
у companion object
подтвердилось.
Теперь сменим модификатор на private
и можем наблюдать следующее:
class JavaClassAccessTest { void callingKotlinFromJava() { // OK KotlinClassSample.PUBLIC_STATIC_FIELD; KotlinClassSample.PUBLIC_STATIC_PRIMITIVE_FIELD; // Error: 'Companion' has private access in 'KotlinClassSample' KotlinClassSample.Companion.getPublicNonStaticField(); // inaccessible } }
class KotlinClassAccessTest { fun callingKotlinFromKotlin() { // Error: Cannot access 'Companion': it is private in 'KotlinClassSample' KotlinClassSample.PUBLIC_STATIC_FIELD // inaccessible KotlinClassSample.PUBLIC_STATIC_PRIMITIVE_FIELD // inaccessible KotlinClassSample.publicNonStaticField // inaccessible } }
В голове у непосвященного человека может возникнуть резонный вопрос:
Решение
Чтобы понять "почему", декомпилируем KotlinClassSample
c public companion object
:
public final class KotlinClassSampleDecompiled { public static final CompanionObject Companion = new CompanionObject(); public static final int PUBLIC_STATIC_PRIMITIVE_FIELD = 0; public static final List PUBLIC_STATIC_FIELD = EmptyList(); private static final List publicNonStaticField = EmptyList(); private static final int PRIVATE_STATIC_PRIMITIVE_FIELD = 0; private static final List privateField = EmptyList(); public static final class CompanionObject { public List getPublicNonStaticField() { return KotlinClassSampleDecompiled.publicNonStaticField; } } }
И декомпилируем KotlinClassSample
c private companion object
:
public final class KotlinClassSampleDecompiled { private static final CompanionObject Companion = new CompanionObject(); public static final int PUBLIC_STATIC_PRIMITIVE_FIELD = 0; public static final List PUBLIC_STATIC_FIELD = EmptyList(); private static final List publicNonStaticField = EmptyList(); private static final int PRIVATE_STATIC_PRIMITIVE_FIELD = 0; private static final List privateField = EmptyList(); private static final class CompanionObject { public List getPublicNonStaticField() { return KotlinClassSampleDecompiled.publicNonStaticField; } } }
Заметка: содержимое Decompiled
классов упрощено для повышения читабельности.
Как можно видеть, применение модификатора private
к companion object
изменило видимость только у класса CompanionObject
, поля Companion
, и как следствие геттера CompanionObject#getPublicNonStaticField
.
Ответ
В общем-то магии никакой нет, и статические свойства объявленные в Kotlin через @JvmField
или const val
компилируются в Java как static final
поля родительского класса, в который вкладывается сгенерированный класс CompanionObject
.
Поэтому использование private companion object
с вложенными private const val
следует воспринимать нормально, возможно даже более "верно". Другое дело, если проект написан только на Kotlin, то private companion object
будет достаточно для ограничения видимости.
Заметка: на последующих этапах компиляции применяются оптимизации и базовые типы объявленные через const
будут подставленны в место их вызова, как шаблоны в C++, но это отдельная тема :)
Дополнительно прилагаю пример с методами, вывод аналогичен - статические функции\методы компилируются к родительскому классу, см. декомпилированный код внизу.
class KotlinClassSample { companion object { @JvmStatic fun publicStaticFun() = Unit fun publicNonStaticFun() = Unit @JvmStatic private fun privateStaticFun() = Unit private fun privateNonStaticFun() = Unit } }
Проверяем доступ к KotlinClassSample
коду из Java и Kotlin:
class JavaClassAccessTest { void callingKotlinFromJava() { // OK KotlinClassSample.publicStaticFun(); KotlinClassSample.Companion.publicStaticFun(); KotlinClassSample.Companion.publicNonStaticFun(); } }
class KotlinClassAccessTest { fun callingKotlinFromKotlin() { // OK KotlinClassSample.publicStaticFun() KotlinClassSample.publicNonStaticFun() } }
Изменим модификатор доступа у companion object
на private
и посмотрим, что изменилось:
class JavaClassAccessTest { void callingKotlinFromJava() { // OK KotlinClassSample.publicStaticFun(); // Error: 'KotlinClassSample.Companion' has private access KotlinClassSample.Companion.publicStaticFun(); // inaccessible KotlinClassSample.Companion.publicNonStaticFun(); // inaccessible } }
class KotlinClassAccessTest { fun callingKotlinFromKotlin() { // Error: Cannot access 'Companion': it is private in 'KotlinClassSample' KotlinClassSample.publicStaticFun() // inaccessible KotlinClassSample.publicNonStaticFun() // inaccessible } }
Декомпилированный KotlinClassSample
c public companion object
:
public final class KotlinClassSampleDecompiled { public static final CompanionObject Companion = new CompanionObject(); public static void publicStaticFun() { Companion.publicStaticFun(); } private static void privateStaticFun() { Companion.privateStaticFun(); } public static final class CompanionObject { public void publicStaticFun() {} public void publicNonStaticFun() {} private void privateStaticFun() {} private void privateNonStaticFun() {} } }
Декомпилированный KotlinClassSample
c private companion object
:
public final class KotlinClassSampleDecompiled { private static final CompanionObject Companion = new CompanionObject(); public static void publicStaticFun() { Companion.publicStaticFun(); } private static void privateStaticFun() { Companion.privateStaticFun(); } private static final class CompanionObject { public void publicStaticFun() {} public void publicNonStaticFun() {} private void privateStaticFun() {} private void privateNonStaticFun() {} } }