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() {}
}
}