Какое намеренное использование @JvmSynthetic в Котлине?

Я натолкнулся на аннотацию @JvmSynthetic в kotlin-stdlib, и мне интересно, для чего она нужна, но, к сожалению, она не документирована. (UPD: это было в тот момент)

Насколько я понимаю, применение его к элементу программы добавит synthetic модификатор к соответствующим элементам байт-кода. Как следствие, элемент становится невидимым из Java:

class MyClass {
    @JvmSynthetic
    fun f() { }
}

Где-то в коде Java:

MyClass c = new MyClass();
c.f() // Error: cannot resolve method f()

Но те же элементы все еще видны в коде Kotlin:

val c = MyClass()
c.f() // OK

Является ли скрытое объявление из не-котлинских источников допустимым использованием @JvmSynthetic? Это предполагаемое использование? Каковы другие соответствующие случаи использования?

Поскольку @JvmSynthetic скрывает функции от Java, они также не могут быть переопределены в Java (и когда дело доходит до abstract члена, вызовы затем приводят к AbstractMethodError). Учитывая это, могу ли я использовать @JvmSynthetic чтобы запретить переопределение членов класса Kotlin в источниках Java?

Ответ 1

Во-первых, чтобы ответить на вопрос, что такое синтетические методы, давайте взглянем на спецификацию языка Java:

11. Конструкция, испускаемая компилятором Java, должна быть помечена как синтетическая, если она не соответствует конструкции, объявленной явно или неявно в исходном коде, если только испускаемая конструкция не является методом инициализации класса (JVMS §2.9).

Аннотация @JvmSynthetic делает именно это: предотвращает доступ из исходного кода. Метод по-прежнему будет отображаться в отражении, а затем будет помечен как синтетический.

Точнее, из документации Kotlin (выделено мое):

@JvmSynthetic

Устанавливает флаг ACC_SYNTHETIC для аннотированной цели в байт-коде Java.

Синтетические цели становятся недоступными для источников Java во время компиляции, но при этом остаются доступными для источников Kotlin. Маркировка цели как синтетической - это двоичное совместимое изменение, уже скомпилированный код Java сможет получить доступ к такой цели.

Эта аннотация предназначена для редких случаев, когда разработчику API необходимо скрыть конкретную цель Kotlin от Java API, оставляя ее частью API Kotlin, поэтому полученный API является идиоматическим для обоих.

Как описано в последнем абзаце, @JvmSynthetic - это инструмент для проектирования API, который позволяет разработчику библиотеки избегать автоматической генерации Java-эквивалентов. Вероятно, наиболее популярными вариантами использования являются функции только для Kotlin, такие как перегрузка операторов, методы componentN() или свойства, которые могут иметь более идиоматический способ представления в Java.

Следует отметить, что целью этих аннотаций являются установщики/получатели свойств, функции и поля - в основном все, что переводится в Java в метод.

@Target([
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER,
    AnnotationTarget.FIELD])
annotation actual class JvmSynthetic

Ответ 2

В простой Java методы synthetic генерируются компилятором javac. Обычно компилятор должен создавать синтетические методы для вложенных классов, когда поля, указанные с помощью частного модификатора, получают доступ к окружающему классу.

Учитывая следующий класс в java:

public final class SyntheticSample
{
    public static void main(final String[] args)
    {
        SyntheticSample.Nested nested = new SyntheticSample.Nested();
        out.println("String: " + nested.syntheticString);
    }

    private static final class Nested
    {
        private String syntheticString = "I'll become a method!";
    }
}

когда класс SyntheticSample обращается к полю nested.syntheticString, он действительно вызывает статический метод synthetic, сгенерированный компилятором (названный как-то вроде access$100).

Даже если Kotlin предоставляет аннотацию @JvmSynthetic, которая может "принудительно" создать синтетические методы, я советую не использовать ее в обычном "пользовательском" коде. Синтетические методы - это низкоуровневые трюки, созданные компилятором, и мы никогда не должны полагаться на такие вещи в повседневном коде. Я думаю, что там, чтобы поддерживать другие части стандартной библиотеки, но вы должны спросить у парней JetBrains прямо, если вам интересно (попробуйте официальный Kotlin Discussion Forum)