Так что это довольно занудный блог, и он должен быть быстрым, но я просто хотел поделиться своими выводами.
Как вы, возможно, знаете, доступ к частным методам или полям в Java из вложенных или анонимных внутренних классов приводит к созданию синтетических методов доступа . Доклад Джейка Уортона о скрытых затратах Java стоит посмотреть здесь, если вы понятия не имеете, о чем я говорю.
Эти синтетические средства доступа генерируются компилятором и имеют большие недостатки в Android — потенциально добавляя сотни, если не тысячи ненужных методов по всему проекту, каждый из которых способствует превышению предела dex. Прямой доступ к этим методам или полям также может быть в 7 раз быстрее, чем при использовании геттера, к которому нечего принюхиваться.
Следовательно, в Blockchain мы используем аннотацию Thunk , чтобы сигнализировать разработчикам о том, что мы изменили область действия поля или метода, чтобы избежать создания синтетических средств доступа. В Android Studio также встроена удобная проверка, позволяющая выделить случаи, которые вы, возможно, пропустили:
Это довольно хорошо известно. Но является ли это проблемой в новой горячности, Котлин?
Короче говоря, да.
В конечном счете, это не должно быть слишком удивительно: компилятор Kotlin сводится к эквивалентному байт-коду Java, так что и здесь это действительно проблема. Я полагал, что это будет но я хотел проверить, так что вот что я нашел:
class SyntheticAccessorTest { private var counter = 0 fun main(args: Array) { val someClass = SomeClass(object : SomeInterface { override fun doSomething() { printSomething() } override fun doSomethingElse() { counter++ } }) } private fun printSomething() { print("Something") } } class SomeClass(var listener: SomeInterface) interface SomeInterface { fun doSomething() fun doSomethingElse() }
Здесь у нас есть простой тестовый класс, который вызывает конструктор другого простого класса и принимает анонимную реализацию интерфейса
. В одном из обратных вызовов мы вызываем функцию, которая является частной
, в другом мы увеличиваем счетчик, который также является частным
. К сожалению, из-за этого результирующий файл .class
будет абсолютно иметь синтетические методы доступа, и как только мы разберемся в этом, легко понять, почему.
В Android Studio, если мы выберем Байт-код Kotlin
а затем Декомпилируем
, мы получаем это Ява (Я немного упростил это для удобства чтения):
public final class SyntheticAccessorTest { private int counter; public final void main(@NotNull String[] args) { new SomeClass(new SomeInterface() { public void doSomething() { SyntheticAccessorTest.this.printSomething(); } public void doSomethingElse() { int var1; SyntheticAccessorTest.this.counter = (var1 = SyntheticAccessorTest.this.counter) + 1; } }); } private final void printSomething() { String var1 = "Something"; System.out.print(var1); } }
Если мы затем декомпилируем файл с помощью javap -p -c SyntheticAccessorTest.class
, затем мы получаем это:
public final class SyntheticAccessorTest { private int counter; public final void main(java.lang.String[]); Code: [...] private final void printSomething(); Code: [...] public SyntheticAccessorTest(); Code: [...] public static final void access$printSomething(SyntheticAccessorTest); Code: [...] public static final int access$getCounter$p(SyntheticAccessorTest); Code: [...] public static final void access$setCounter$p(SyntheticAccessorTest, int); Code: [...] }
И вот они: проблема заключается в этих методах access$
. На самом деле мы получаем два для поля counter
; один для set
, один для get
. Вы можете легко увидеть, как это начинает быстро складываться в проекте Android.
Если мы изменим оба счетчика
и print Something()
на внутренний
, мы получаем это вместо:
public final class SyntheticAccessorTest { private int counter; public final int getCounter$Test_Project(); Code: [...] public final void setCounter$Test_Project(int); Code: [...] public final void main(java.lang.String[]); Code: [...] public final void printSomething$Test_Project(); Code: [...] public SyntheticAccessorTest(); Code: [...] }
Лучше. Но что здесь интересно, так это то, что, хотя мы теряем синтетический метод доступа для print Something()
, мы по-прежнему получаем два метода для установки и получения свойства counter
. Это связано с тем, что Kotlin автоматически добавляет геттеры и сеттеры в свойства, и действительно, если мы проверим сгенерированную Java, мы увидим:
public final int getCounter$production_sources_for_module_Test_Project() { return this.counter; } public final void setCounter$production_sources_for_module_Test_Project(int var1) { this.counter = var1; }
Кажется ли это ненужным или нет, зависит от вас. В большинстве классов Java вы, вероятно, в любом случае написали бы сеттеры и геттеры, таким образом, во многих ситуациях вы на самом деле не получили никаких ненужных методов. Однако в данном случае, поскольку доступ к свойству осуществляется внутри родительского класса: это явно излишне. Применение той же проверки к эквивалентному Java-коду приводит всего к 3 методам; на два меньше, чем у Kotlin.
Если вас это очень беспокоит, есть простое решение; добавьте аннотацию @JvmField
к свойству, и средство получения/установки не будет сгенерировано (спасибо Кириллу Рахману за предупреждение).
Так что да, вам действительно нужно быть осторожным при доступе к частным функциям в Kotlin по той же причине, что и в Java, и, по-видимому, простое использование свойств сопряжено со скрытыми затратами. К сожалению, проверка этого еще не проведена, но я надеюсь, что IntelliJ/Google добавит это довольно скоро. В то же время внимательно проверьте свой код, если вас беспокоит количество методов dex.
Оригинал: “https://dev.to/ditn/synthetic-accessors-in-kotlin”