Kotlin: Как получить доступ к методам доступа для делегатов и setValue?
Мне было интересно, как делегированные свойства ( "by -Keyword" ) работают под капотом.
Я получаю, что по контракту делегат (правая сторона "by" ) должен реализовать метод get и setValue (...), но как это может обеспечить компилятор и как эти методы могут быть доступны во время выполнения?
Моя первоначальная мысль заключалась в том, что, очевидно, делегаты должны мне внедрить какой-то "SuperDelegate" -Interface, но, похоже, это не так.
Таким образом, единственный вариант, который я знаю, - это использовать Reflection для доступа к этим методам, возможно, реализованным на низком уровне внутри самого языка. Я считаю это несколько странным, поскольку, по моему мнению, это будет довольно неэффективно. Также API Reflection не является даже частью stdlib, что делает его еще более странным.
Я предполагаю, что последний уже является (частью) ответа. Поэтому позвольте мне также задать вам следующее: почему нет интерфейса SuperDelegate, который объявляет методы getter и setter, которые мы вынуждены использовать в любом случае? Разве это не было бы намного чище?
В вопросе
не имеет значения следующее:
Описанный интерфейс уже определен в ReadOnlyProperty и ReadWriteProperty. Чтобы решить, какой из них использовать, можно было бы сделать зависимым от того, есть ли у нас val/var. Или даже опустите это, поскольку вызов метода setValue в val предотвращается компилятором и использует только интерфейс ReadWriteProperty как SuperDelegate.
Возможно, когда требуется, чтобы делегат реализовал определенный интерфейс, конструкция была бы менее гибкой. Хотя это предполагает, что класс, используемый в качестве делегата, возможно, не осознает того, что он используется как таковой, что я считаю маловероятным с учетом конкретных требований к необходимым методам. И если вы все еще настаиваете, вот сумасшедшая мысль: почему бы даже не зайти так далеко, чтобы этот класс реализовал требуемый интерфейс через Extension ( Я знаю, что это невозможно сейчас, но, черт возьми, почему бы и нет? Вероятно, там хороший "почему бы нет", пожалуйста, дайте мне знать как примечание).
Ответы
Ответ 1
Соглашение делегатов (getValue
+ setValue
) реализовано со стороны компилятора, и в действительности во время выполнения не выполняется ни одна из его логики разрешения: вызовы соответствующих методов объекта-делегата помещаются непосредственно в сгенерированный байт-код,
Посмотрим на байт-код, сгенерированный для класса с делегированным свойством (вы можете сделать это с помощью инструмента просмотра байт-кода, встроенного в IntelliJ IDEA):
class C {
val x by lazy { 123 }
}
В сгенерированном байт-коде мы можем найти следующее:
-
Это поле класса C
, в котором хранится ссылка на объект делегата:
// access flags 0x12
private final Lkotlin/Lazy; x$delegate
-
Это часть конструктора (<init>
), которая инициализировала поле делегата, передавая функцию конструктору Lazy
:
ALOAD 0
GETSTATIC C$x$2.INSTANCE : LC$x$2;
CHECKCAST kotlin/jvm/functions/Function0
INVOKESTATIC kotlin/LazyKt.lazy (Lkotlin/jvm/functions/Function0;)Lkotlin/Lazy;
PUTFIELD C.x$delegate : Lkotlin/Lazy;
-
И это код getX()
:
L0
ALOAD 0
GETFIELD C.x$delegate : Lkotlin/Lazy;
ASTORE 1
ALOAD 0
ASTORE 2
GETSTATIC C.$$delegatedProperties : [Lkotlin/reflect/KProperty;
ICONST_0
AALOAD
ASTORE 3
L1
ALOAD 1
INVOKEINTERFACE kotlin/Lazy.getValue ()Ljava/lang/Object;
L2
CHECKCAST java/lang/Number
INVOKEVIRTUAL java/lang/Number.intValue ()I
IRETURN
Вы можете увидеть вызов метода getValue
Lazy
, который помещается непосредственно в байт-код. Фактически, компилятор разрешает метод с правильной сигнатурой для соглашения делегата и генерирует getter, который вызывает этот метод.
Это соглашение не единственное, реализованное на стороне компилятора: есть также iterator
, compareTo
, invoke
и другие операторы, которые могут быть перегружены - все они схожи, но генерация кода логика для них проще, чем у делегатов.
Обратите внимание, однако, что ни один из них не требует реализации интерфейса: оператор compareTo
может быть определен для типа, не реализующего Comparable<T>
, а iterator()
не требует тип будет реализацией Iterable<T>
, они в любом случае разрешены во время компиляции.
В то время как подход к интерфейсам может быть более чистым, чем соглашение операторов, это уменьшит гибкость: например, функции расширения не могут использоваться потому что они не могут быть скомпилированы в методы, переопределяющие интерфейс.
Ответ 2
Если вы посмотрите на сгенерированный байт-код Kotlin, вы увидите, что в классе, в котором используется делегат, создается приватное поле, а метод get
и set
для свойства просто вызывает соответствующий метод в этом поле делегата.
Поскольку класс делегата известен во время компиляции, никакого отражения не должно быть, просто вызовы метода.