Как стирается работа в Котлине?
В 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
становится необходимым. При этом вы фактически гарантируете, что в байтовом коде нет конфликтов имен.