Рубрики
Без рубрики

Синтетические аксессуары в Kotlin

Оценка некоторых скрытых затрат в Kotlin. С тегами kotlin, android, java.

Так что это довольно занудный блог, и он должен быть быстрым, но я просто хотел поделиться своими выводами.

Как вы, возможно, знаете, доступ к частным методам или полям в 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”