Как стирается работа в Котлине?

В Kotlin следующий код компилируется:

class Foo {
    fun bar(foo: List<String>): String {
        return ""
    }

    fun bar(foo: List<Int>): Int {
        return 2;
    }
}

Этот код, однако, не делает:

class Foo {
    fun bar(foo: List<String>): String {
        return ""
    }

    fun bar(foo: List<Int>): String {
        return "2";
    }
}

При компиляции это приведет к следующей ошибке:

Error:(8, 5) Kotlin: Platform declaration clash: The following declarations have the same JVM signature (foo(Ljava/util/List;)Ljava/lang/String;):
    fun foo(layout: List<Int>): String
    fun foo(layout: List<String>): String

В Java ни один пример не будет компилироваться:

class Foo {
    String bar(List<Integer> foo) {
        return "";
    }

    Integer bar(List<String> foo) {
        return 2;
    }
}

class Foo {
    String bar(List<Integer> foo) {
        return "";
    }

    String bar(List<String> foo) {
        return "2";
    }
}

Неудивительно, что оба предыдущих фрагмента генерируют знакомую ошибку компилятора:

Error:(13, 12) java: name clash: bar(java.util.List<java.lang.String>) and bar(java.util.List<java.lang.Integer>) have the same erasure

Что меня удивляет, так это то, что первый пример Kotlin вообще работает, а во-вторых, если он работает, почему второй пример Kotlin не работает? Котлин рассматривает метод возвращаемого типа как часть его подписи? Кроме того, почему сигнатуры метода в Kotlin учитывают полный тип параметра, в отличие от Java?

Ответы

Ответ 1

На самом деле Котлин знает разницу между двумя методами в вашем примере, но jvm не будет. Вот почему это "платформа" столкновение.

Вы можете сделать свой второй пример с помощью аннотации @JvmName:

class Foo {
  @JvmName("barString") fun bar(foo: List<String>): String {
    return ""
  }

  @JvmName("barInt") fun bar(foo: List<Int>): String {
    return "2";
  }
}

Эта аннотация существует именно по этой причине. Вы можете прочитать больше в документации interop.

Ответ 2

В то время как ответ @Streloks верен, я хотел глубже понять, почему он работает.

Причина, по которой работает первый вариант, заключается в том, что он не запрещен в Java-байт-коде. В то время как компилятор Java жалуется на это, то есть спецификация языка Java не позволяет этого, код байта делает это, как также было описано в https://community.oracle.com/docs/DOC-983207 и в https://www.infoq.com/articles/Java-Bytecode-Bending-the-Rules. В байтовом коде каждый вызов метода ссылается на фактический тип возврата метода, который не так, когда вы пишете код.

К сожалению, я не мог найти фактический источник, почему это так.

В документе, касающемся разрешения имени Котлинса, содержатся некоторые интересные моменты, но я не видел вашего фактического дела.

Что действительно помогло мне понять это, был ответ от стирания @Yole на Kotlin - почему функции отличаются только общим типом, который компилируется, тогда как те, которые отличаются только типом возврата, не являются? , точнее, что компилятор kotlin не учитывает тип переменной при выборе способа вызова.

Таким образом, было преднамеренное дизайнерское решение о том, что указание типа на переменную не будет влиять на то, какой метод является вызываемым, а наоборот, то есть вызываемый метод (с или без общей информации) влияет на тип, который будет использоваться,

Применяя правило на следующих образцах, имеет смысл:

fun bar(foo: List<String>) = ""    (1)
fun bar(foo: List<Int>) = 2        (2)

val x = bar(listOf("")) --> uses (1), type of x becomes String
val y = bar(listOf(2))  --> uses (2), type of y becomes Int

Или иметь метод, предоставляющий общий тип, но даже не используя его:

fun bar(foo: List<*>) = ""         (3)
fun <T> bar(foo: List<*>) = 2      (4)

val x = bar(listOf(null))          --> uses (3) as no generic type was specified when calling the method, type of x becomes String
val y = bar<String>(listOf(null))  --> uses (4) as the generic type was specified, type of y becomes Int

И это также причина, по которой следующее не будет работать:

fun bar(foo: List<*>) = ""
fun bar(foo: List<*>) = 2

Это не компилируется, так как это приводит к конфликтующей перегрузке, поскольку тип назначенной переменной сам не учитывается при попытке идентифицировать метод, который должен быть вызван:

val x : String = bar(listOf(null)) // ambiguous, type of x is not relevant

Теперь о конфликте имени: как только вы используете одно и то же имя, тот же тип возвращаемого значения и те же параметры (чьи общие типы стираются), вы фактически получите ту же самую подпись метода в байтовом коде. Вот почему @JvmName становится необходимым. При этом вы фактически гарантируете, что в байтовом коде нет конфликтов имен.