526 lines
28 KiB
Markdown
526 lines
28 KiB
Markdown
---
|
||
title: Kotlin из Java
|
||
description: Аннотации @JvmStatic, @JvmOverloads, @JvmName, @JvmField, @Throws и то, как конструкции Kotlin выглядят из Java
|
||
tags:
|
||
- kotlin
|
||
- java-interop
|
||
---
|
||
|
||
Обратное направление сложнее: в Kotlin есть конструкции, которых нет в JVM, и компилятор представляет их по-своему. Без аннотаций ваш API окажется неудобным для Java-кода.
|
||
|
||
```kotlin
|
||
class Greeter(val name: String) {
|
||
fun greet(greeting: String = "Привет") = "$greeting, $name!"
|
||
}
|
||
|
||
fun main() {
|
||
println(Greeter("Мир").greet()) // Привет, Мир!
|
||
}
|
||
```
|
||
Из Java этот же класс выглядит так:
|
||
```java
|
||
// Java
|
||
Greeter g = new Greeter("Мир");
|
||
System.out.println(g.greet("Привет")); // параметр обязателен!
|
||
// g.greet(); // не скомпилируется
|
||
```
|
||
Значение по умолчанию существует только для Kotlin: в байт-коде есть `greet(String)` и синтетический `greet$default(Greeter, String, int, Object)`, вызывать который вручную нельзя.
|
||
|
||
## Карта проблем и аннотаций
|
||
| Конструкция Kotlin | Из Java без аннотации | Аннотация |
|
||
|---|---|---|
|
||
| значение по умолчанию | один метод со всеми параметрами | `@JvmOverloads` |
|
||
| `companion object` | `Cls.Companion.method()` | `@JvmStatic` |
|
||
| `object` | `Singleton.INSTANCE.method()` | `@JvmStatic` |
|
||
| `val` в `companion object` | `Cls.Companion.getX()` | `@JvmField` |
|
||
| `const val` | уже поле - ничего не нужно | - |
|
||
| функция верхнего уровня | `FileNameKt.fn()` | `@JvmName` на файле |
|
||
| свойство | `getX()` / `setX()` | `@get:JvmName` |
|
||
| `suspend fun` | лишний параметр `Continuation` | не решается |
|
||
| `value class` | манглированное имя | не решается |
|
||
| исключение | не видно в `throws` | `@Throws` |
|
||
| перегрузки с дженериками | конфликт стирания | `@JvmName` |
|
||
|
||
```mermaid
|
||
graph TD
|
||
A["Kotlin API"] --> B{"Вызывается из Java?"}
|
||
B -->|"нет"| C["аннотации не нужны"]
|
||
B -->|"да"| D["проверить каждый пункт"]
|
||
D --> E["значения по умолчанию<br/>@JvmOverloads"]
|
||
D --> F["companion / object<br/>@JvmStatic"]
|
||
D --> G["константы<br/>@JvmField или const"]
|
||
D --> H["исключения<br/>@Throws"]
|
||
D --> I["имена<br/>@JvmName"]
|
||
```
|
||
|
||
## @JvmOverloads
|
||
Генерирует перегрузку для каждого значения по умолчанию:
|
||
```kotlin
|
||
class Greeter(val name: String) {
|
||
@JvmOverloads
|
||
fun greet(greeting: String = "Привет", punct: String = "!") = "$greeting, $name$punct"
|
||
}
|
||
|
||
fun main() {
|
||
val g = Greeter("Мир")
|
||
println(g.greet()) // Привет, Мир!
|
||
println(g.greet("Здравствуй")) // Здравствуй, Мир!
|
||
println(g.greet("Hi", ".")) // Hi, Мир.
|
||
}
|
||
```
|
||
В байт-коде появляются три метода:
|
||
```
|
||
public final String greet(String, String) // полный
|
||
public final String greet(String) // punct по умолчанию
|
||
public final String greet() // оба по умолчанию
|
||
```
|
||
Перегрузки создаются **справа налево**: параметры отбрасываются с конца. Поэтому `greet(punct = ".")` из Java невозможно - такой перегрузки нет. Это ограничение влияет на порядок параметров: самые часто переопределяемые ставьте первыми.
|
||
|
||
Для конструктора аннотация работает так же:
|
||
```kotlin
|
||
class Config @JvmOverloads constructor(
|
||
val host: String = "localhost",
|
||
val port: Int = 8080,
|
||
) {
|
||
override fun toString() = "$host:$port"
|
||
}
|
||
|
||
fun main() {
|
||
println(Config()) // localhost:8080
|
||
println(Config("example.com")) // example.com:8080
|
||
println(Config("example.com", 443)) // example.com:443
|
||
}
|
||
```
|
||
|
||
## @JvmStatic
|
||
Без неё члены `companion object` доступны только через `.Companion`:
|
||
```kotlin
|
||
class Repository private constructor(val id: String) {
|
||
companion object {
|
||
@JvmStatic
|
||
fun create(id: String) = Repository(id)
|
||
|
||
fun createNoStatic(id: String) = Repository(id)
|
||
}
|
||
}
|
||
|
||
fun main() {
|
||
println(Repository.create("1").id) // 1
|
||
println(Repository.createNoStatic("2").id) // 2
|
||
}
|
||
```
|
||
Из Kotlin обе формы одинаковы. Из Java - нет:
|
||
```java
|
||
// Java
|
||
Repository.create("1"); // работает: статический метод
|
||
Repository.Companion.createNoStatic("2"); // без @JvmStatic только так
|
||
```
|
||
То же для `object`:
|
||
```kotlin
|
||
object Registry {
|
||
private val items = mutableListOf<String>()
|
||
|
||
@JvmStatic
|
||
fun register(item: String) { items += item }
|
||
|
||
fun size() = items.size
|
||
}
|
||
|
||
fun main() {
|
||
Registry.register("a")
|
||
println(Registry.size()) // 1
|
||
}
|
||
```
|
||
Из Java: `Registry.register("a")` против `Registry.INSTANCE.size()`.
|
||
|
||
## @JvmField и const
|
||
Свойство в `companion object` компилируется в приватное поле с геттером на `Companion`. Три варианта:
|
||
```kotlin
|
||
class Limits {
|
||
companion object {
|
||
const val MAX = 100 // примитив/String, compile-time
|
||
@JvmField val DEFAULT = "значение" // поле, доступно напрямую
|
||
val COMPUTED = "вычисляется".uppercase() // только через геттер
|
||
}
|
||
}
|
||
|
||
fun main() {
|
||
println(Limits.MAX) // 100
|
||
println(Limits.DEFAULT) // значение
|
||
println(Limits.COMPUTED) // ВЫЧИСЛЯЕТСЯ
|
||
}
|
||
```
|
||
Из Java:
|
||
```java
|
||
// Java
|
||
Limits.MAX; // public static final int
|
||
Limits.DEFAULT; // public static final String
|
||
Limits.Companion.getCOMPUTED(); // только геттер
|
||
```
|
||
`const val` требует примитив или `String` и инициализацию константным выражением - зато встраивается в место вызова (и потому меняет ABI при изменении значения). `@JvmField` работает с любым типом, но несовместим с `private`, `open`, `override`, `lateinit` и делегированными свойствами.
|
||
|
||
## @JvmName
|
||
Задаёт имя в байт-коде. Три точки применения.
|
||
|
||
**Файл.** Функции верхнего уровня попадают в класс `ИмяФайлаKt`:
|
||
```kotlin
|
||
@file:JvmName("StringUtils")
|
||
|
||
fun String.slugify() = lowercase().replace(" ", "-")
|
||
|
||
fun main() {
|
||
println("Привет Мир".slugify()) // привет-мир
|
||
}
|
||
```
|
||
Без аннотации Java-код обращался бы к `SlugifyKt.slugify(s)`, с ней - `StringUtils.slugify(s)`. Расширения становятся статическими методами с получателем первым параметром.
|
||
|
||
**Аксессор свойства:**
|
||
```kotlin
|
||
class Account(private val raw: String) {
|
||
@get:JvmName("fetchBalance")
|
||
val balance: String get() = raw
|
||
}
|
||
|
||
fun main() {
|
||
println(Account("1000").balance) // 1000
|
||
}
|
||
```
|
||
Из Java метод называется `fetchBalance()`, а не `getBalance()`.
|
||
|
||
**Обход конфликта стирания.** Две функции, различающиеся только параметром дженерика, в байт-коде имеют одинаковую сигнатуру:
|
||
```kotlin
|
||
@JvmName("sumInts")
|
||
fun List<Int>.sumAll() = sum()
|
||
|
||
@JvmName("sumDoubles")
|
||
fun List<Double>.sumAll() = sum()
|
||
|
||
fun main() {
|
||
println(listOf(1, 2, 3).sumAll()) // 6
|
||
println(listOf(1.5, 2.5).sumAll()) // 4.0
|
||
}
|
||
```
|
||
Без `@JvmName` компилятор откажется: «platform declaration clash». Это одна из немногих ситуаций, когда аннотация нужна даже для чисто Kotlin-кода. Подробнее: [Reified и inline](../Дженерики/Reified%20и%20inline.md)
|
||
|
||
## @Throws
|
||
В Kotlin нет проверяемых исключений, поэтому в байт-коде нет `throws`:
|
||
```kotlin
|
||
import java.io.IOException
|
||
|
||
class Loader {
|
||
@Throws(IOException::class)
|
||
fun load(path: String): String {
|
||
if (path.isEmpty()) throw IOException("пустой путь")
|
||
return "данные из $path"
|
||
}
|
||
|
||
fun loadNoThrows(path: String): String {
|
||
if (path.isEmpty()) throw IOException("пустой путь")
|
||
return "данные из $path"
|
||
}
|
||
}
|
||
|
||
fun main() {
|
||
val loader = Loader()
|
||
println(loader.load("/tmp/file")) // данные из /tmp/file
|
||
println(runCatching { loader.loadNoThrows("") }.isFailure) // true
|
||
}
|
||
```
|
||
Из Java разница принципиальна:
|
||
```java
|
||
// Java
|
||
try { loader.load(""); }
|
||
catch (IOException e) { } // компилируется
|
||
|
||
try { loader.loadNoThrows(""); }
|
||
catch (IOException e) { } // ОШИБКА: exception is never thrown
|
||
```
|
||
Java-компилятор запрещает ловить проверяемое исключение, которое метод не объявляет. Исключение всё равно вылетит в рантайме - просто обработать его можно только через `catch (Exception e)`.
|
||
|
||
## Как выглядят остальные конструкции
|
||
### Nullability
|
||
```kotlin
|
||
class Validator {
|
||
fun check(input: String): Boolean = input.isNotEmpty()
|
||
fun find(key: String): String? = if (key == "a") "найдено" else null
|
||
}
|
||
|
||
fun main() {
|
||
val v = Validator()
|
||
println(v.check("текст")) // true
|
||
println(v.find("b")) // null
|
||
}
|
||
```
|
||
`check(String)` получает аннотацию `@NotNull` на параметре и проверку `Intrinsics.checkNotNullParameter` в начале тела. Java-код, передавший `null`, получит `NullPointerException` с сообщением «Parameter specified as non-null is null» - сразу, а не при первом использовании. `find` возвращает `@Nullable String`, и IDE предупредит Java-код.
|
||
|
||
### Свойства
|
||
```kotlin
|
||
class User {
|
||
var name: String = "аноним"
|
||
val id: Int = 1
|
||
var internalState: String = "скрыто"
|
||
private set
|
||
}
|
||
|
||
fun main() {
|
||
val u = User()
|
||
u.name = "Иван"
|
||
println("${u.name} ${u.id} ${u.internalState}") // Иван 1 скрыто
|
||
}
|
||
```
|
||
Из Java: `getName()`/`setName()`, `getId()` без сеттера, `getInternalState()` без сеттера (приватный сеттер в байт-коде приватен). Само поле всегда приватно - прямого доступа из Java нет.
|
||
|
||
### Функции-расширения
|
||
```kotlin
|
||
fun List<String>.firstOrDefault(default: String) = firstOrNull() ?: default
|
||
|
||
fun main() {
|
||
println(listOf("a").firstOrDefault("нет")) // a
|
||
println(emptyList<String>().firstOrDefault("нет")) // нет
|
||
}
|
||
```
|
||
Из Java это статический метод с получателем первым аргументом: `FileNameKt.firstOrDefault(list, "нет")`. Никакого синтаксического сахара - расширение существует только в Kotlin.
|
||
|
||
### suspend-функции
|
||
```kotlin
|
||
import kotlinx.coroutines.delay
|
||
import kotlinx.coroutines.runBlocking
|
||
|
||
suspend fun fetch(id: Int): String {
|
||
delay(10)
|
||
return "ответ для $id"
|
||
}
|
||
|
||
fun main() = runBlocking {
|
||
println(fetch(1)) // ответ для 1
|
||
}
|
||
```
|
||
В байт-коде сигнатура становится `Object fetch(int, Continuation)`, возвращая либо результат, либо маркер `COROUTINE_SUSPENDED`. Вызвать такое из Java практически невозможно. Для Java-клиентов делают обёртку:
|
||
```kotlin
|
||
import kotlinx.coroutines.CoroutineScope
|
||
import kotlinx.coroutines.Dispatchers
|
||
import kotlinx.coroutines.delay
|
||
import kotlinx.coroutines.future.future
|
||
import java.util.concurrent.CompletableFuture
|
||
|
||
class Api(private val scope: CoroutineScope) {
|
||
suspend fun fetch(id: Int): String {
|
||
delay(10)
|
||
return "ответ для $id"
|
||
}
|
||
|
||
// для Java: CompletableFuture вместо suspend
|
||
fun fetchAsync(id: Int): CompletableFuture<String> =
|
||
scope.future { fetch(id) }
|
||
}
|
||
|
||
fun main() {
|
||
val scope = CoroutineScope(Dispatchers.Default)
|
||
println(Api(scope).fetchAsync(7).get()) // ответ для 7
|
||
}
|
||
```
|
||
Нужна зависимость `kotlinx-coroutines-jdk8`. Подробнее: [Suspend-функции](../Корутины/Suspend-функции.md)
|
||
|
||
### value class
|
||
```kotlin
|
||
@JvmInline
|
||
value class UserId(val value: Int)
|
||
|
||
fun process(id: UserId) = "обработан ${id.value}"
|
||
|
||
fun main() {
|
||
println(process(UserId(42))) // обработан 42
|
||
}
|
||
```
|
||
В байт-коде `process` получает имя `process-4ZD5Yi0(int)` - суффикс нужен, чтобы отличать перегрузки с разными value-классами над одним примитивом. Вызвать такое имя из Java нельзя: дефис недопустим в идентификаторе. Для Java-API добавляют обычную перегрузку:
|
||
```kotlin
|
||
@JvmInline
|
||
value class Money(val cents: Long)
|
||
|
||
fun charge(amount: Money) = "списано ${amount.cents}"
|
||
|
||
@JvmName("chargeCents")
|
||
fun charge(cents: Long) = charge(Money(cents))
|
||
|
||
fun main() {
|
||
println(charge(Money(500))) // списано 500
|
||
println(charge(500L)) // списано 500
|
||
}
|
||
```
|
||
Подробнее: [Value class](../Синтаксис%20и%20типы/Базовые%20типы%20ООП/Value%20class.md)
|
||
|
||
### internal
|
||
```kotlin
|
||
class Service {
|
||
internal fun helper() = "вспомогательное"
|
||
fun public() = helper()
|
||
}
|
||
|
||
fun main() {
|
||
println(Service().public()) // вспомогательное
|
||
}
|
||
```
|
||
`internal` компилируется в `public` с манглированным именем вида `helper$main` (суффикс - имя модуля). Java-код технически может вызвать `helper$main()`. Это не изоляция, а соглашение, которое соблюдает только Kotlin-компилятор. Подробнее: [Модификаторы видимости](../ООП/Модификаторы%20видимости.md)
|
||
|
||
### data class
|
||
```kotlin
|
||
data class Point(val x: Int, val y: Int)
|
||
|
||
fun main() {
|
||
val p = Point(1, 2)
|
||
println(p) // Point(x=1, y=2)
|
||
println(p.copy(y = 5)) // Point(x=1, y=5)
|
||
val (x, y) = p
|
||
println("$x $y") // 1 2
|
||
}
|
||
```
|
||
Из Java доступны `getX()`, `getY()`, `equals`, `hashCode`, `toString`, `component1()`, `component2()` и `copy(int, int)`. Но `copy` с одним аргументом недоступна - это те же значения по умолчанию, и `@JvmOverloads` на сгенерированном `copy` поставить нельзя. Деструктуризации в Java нет. Подробнее: [Data class](../Синтаксис%20и%20типы/Базовые%20типы%20ООП/Data%20class.md)
|
||
|
||
### sealed
|
||
```kotlin
|
||
sealed interface Result
|
||
data class Ok(val value: String) : Result
|
||
data class Err(val message: String) : Result
|
||
|
||
fun describe(r: Result) = when (r) {
|
||
is Ok -> "успех: ${r.value}"
|
||
is Err -> "ошибка: ${r.message}"
|
||
}
|
||
|
||
fun main() {
|
||
println(describe(Ok("данные"))) // успех: данные
|
||
println(describe(Err("сбой"))) // ошибка: сбой
|
||
}
|
||
```
|
||
Из Java `sealed interface` Kotlin - обычный интерфейс (при JVM-таргете 17+ может компилироваться в настоящий `sealed`). Исчерпывающего `switch` Java не получит - гарантия полноты обеспечивается только Kotlin-компилятором. Подробнее: [Sealed class и interface](../Синтаксис%20и%20типы/Базовые%20типы%20ООП/Sealed%20class%20и%20interface.md)
|
||
|
||
## Чеклист публичного API для Java-клиентов
|
||
1. `@JvmOverloads` на всех функциях со значениями по умолчанию.
|
||
2. `@JvmStatic` на членах `companion object` и `object`.
|
||
3. `const val` или `@JvmField` для констант.
|
||
4. `@Throws` там, где Java-код должен ловить проверяемое исключение.
|
||
5. `@file:JvmName` для файлов с функциями верхнего уровня.
|
||
6. Нет `suspend` в публичном API - только обёртки (`CompletableFuture`, колбэки).
|
||
7. Нет `value class` в сигнатурах - или перегрузка с `@JvmName`.
|
||
8. Возвращать `List`, а не `MutableList`, и отдавать копию, если Java может изменить.
|
||
9. Nullable-типы объявлять явно - Java-IDE прочитает аннотации.
|
||
10. Проверить результат через `javap` на реальном классе.
|
||
|
||
```kotlin
|
||
// пример API, готового к вызову из Java
|
||
@file:JvmName("UserApi")
|
||
|
||
class UserService @JvmOverloads constructor(
|
||
private val baseUrl: String = "https://api.example.com",
|
||
) {
|
||
@Throws(IllegalArgumentException::class)
|
||
@JvmOverloads
|
||
fun find(id: String, includeDeleted: Boolean = false): String {
|
||
require(id.isNotBlank()) { "id не может быть пустым" }
|
||
return "$baseUrl/users/$id?deleted=$includeDeleted"
|
||
}
|
||
|
||
companion object {
|
||
const val DEFAULT_TIMEOUT_MS = 5_000L
|
||
|
||
@JvmStatic
|
||
fun default() = UserService()
|
||
}
|
||
}
|
||
|
||
fun main() {
|
||
println(UserService.default().find("42"))
|
||
println(UserService.DEFAULT_TIMEOUT_MS)
|
||
// https://api.example.com/users/42?deleted=false
|
||
// 5000
|
||
}
|
||
```
|
||
|
||
## Проверка результата
|
||
Единственный надёжный способ увидеть реальный API - посмотреть байт-код:
|
||
```
|
||
kotlinc Api.kt -d out
|
||
javap -cp out com.example.UserApi
|
||
javap -cp out 'com.example.UserService$Companion'
|
||
```
|
||
`javap` покажет точные сигнатуры, включая синтетические методы `$default` и манглированные имена. Это стоит делать перед релизом библиотеки. Подробнее: [Kotlin в байт-коде](Kotlin%20в%20байт-коде.md)
|
||
|
||
> [!info] Отличия от Java
|
||
> | Java | Kotlin из Java |
|
||
> |---|---|
|
||
> | `static` метод | `@JvmStatic` в `companion object` |
|
||
> | `public static final` поле | `const val` или `@JvmField` |
|
||
> | перегрузки пишутся руками | `@JvmOverloads` генерирует |
|
||
> | `throws` в сигнатуре | `@Throws` |
|
||
> | имя класса = имя файла | `@file:JvmName` |
|
||
> | утилитный класс со `static` | функции верхнего уровня |
|
||
> | геттер пишется руками | генерируется из свойства |
|
||
>
|
||
> **Аннотации `@Jvm*` - это не костыли, а явное управление ABI.** В Java представление в байт-коде однозначно: что написано, то и скомпилируется. В Kotlin между исходником и байт-кодом есть зазор, и аннотации дают над ним контроль. Плата за выразительность языка: `@JvmOverloads` существует потому, что в JVM нет значений по умолчанию, а не из-за недоработки.
|
||
>
|
||
> **Значения по умолчанию в Kotlin устроены иначе, чем перегрузки в Java.** В Java N необязательных параметров требуют N перегрузок, каждую с делегированием. В Kotlin - один метод и синтетический `$default`, принимающий битовую маску переданных аргументов. Отсюда и ограничение `@JvmOverloads`: перегрузки генерируются только отбрасыванием с конца, потому что произвольные комбинации дали бы экспоненциальное число методов.
|
||
>
|
||
> **`companion object` - настоящий объект, а не пространство имён.** В Java `static` - модификатор: метод принадлежит классу. В Kotlin companion - отдельный экземпляр класса `Cls$Companion`, у него есть тип, он может реализовывать интерфейсы и быть получателем расширений. Поэтому без `@JvmStatic` вызов идёт через `Cls.Companion`. Подробнее: [Object и companion object](../Синтаксис%20и%20типы/Базовые%20типы%20ООП/Object%20и%20companion%20object.md)
|
||
>
|
||
> **Отсутствие проверяемых исключений нельзя «включить» обратно.** `@Throws` только добавляет запись в атрибут `Exceptions` метода - сам Kotlin-компилятор её не проверяет. Фактически это документация для Java-компилятора: без неё Java не разрешит `catch`, с ней - потребует обработки.
|
||
>
|
||
> **`suspend` принципиально несовместим с Java.** Корутины реализованы через CPS-преобразование на этапе компиляции Kotlin: функция превращается в конечный автомат с параметром `Continuation`. Java-компилятор этого преобразования не делает, поэтому написать Java-вызывающий код для `suspend`-функции нельзя - только обёртки. Это самая жёсткая граница interop. Подробнее: [Корутины против потоков](../Корутины/Корутины%20против%20потоков.md)
|
||
>
|
||
> Сравнить: [Инкапсуляция в Java](../../Программирование%20на%20Java/ООП/Инкапсуляция/Инкапсуляция.md), [Система исключений в Java](../../Программирование%20на%20Java/Исключения/Система%20исключений.md)
|
||
|
||
> [!warning] Подводные камни
|
||
> **`@JvmOverloads` генерирует перегрузки только справа налево.** Для `fun f(a: Int = 1, b: Int = 2, c: Int = 3)` появятся `f(a,b,c)`, `f(a,b)`, `f(a)`, `f()` - но не `f(c)` и не `f(a,c)`. Из Java нельзя передать только третий параметр. Ставьте чаще переопределяемые параметры раньше.
|
||
>
|
||
> **Добавление `@JvmStatic` или `@JvmOverloads` позже ломает совместимость.** `@JvmStatic` не удаляет метод с `Companion`, но меняет рекомендуемую точку входа; снятие аннотации удаляет статический метод и роняет скомпилированный Java-код с `NoSuchMethodError`. Решайте до релиза.
|
||
>
|
||
> **Изменение `const val` требует перекомпиляции клиентов.** Значение встраивается в место вызова, поэтому Java-код, собранный против старой версии, продолжит использовать старое число. Для значений, которые могут меняться, лучше `@JvmField val`.
|
||
>
|
||
> **`@JvmField` несовместим с половиной модификаторов:**
|
||
> ```kotlin
|
||
> class Limits {
|
||
> companion object {
|
||
> @JvmField val OK = "работает"
|
||
> // @JvmField private val BAD = "" // ошибка: не может быть private
|
||
> // @JvmField lateinit var LATE: String // ошибка: не для lateinit
|
||
> }
|
||
> }
|
||
>
|
||
> fun main() {
|
||
> println(Limits.OK) // работает
|
||
> }
|
||
> ```
|
||
> Запрещено для `private`, `open`, `override`, `const`, `lateinit` и делегированных свойств.
|
||
>
|
||
> **`copy()` у `data class` из Java требует все аргументы.** `@JvmOverloads` на сгенерированный `copy` поставить нельзя. Если Java-коду нужна частичная копия, пишите свой метод или билдер.
|
||
>
|
||
> **Манглированные имена value-классов недоступны из Java.** `fun takeId(id: UserId)` компилируется в `takeId-4ZD5Yi0(int)`; дефис делает имя невызываемым в Java. Это проявится только при попытке интеграции - Kotlin-компилятор не предупредит.
|
||
>
|
||
> **`internal` виден из Java.** `internalFun$main()` - публичный метод в байт-коде. Если нужна настоящая закрытость для Java-кода в том же проекте, используйте `private` или выносите в отдельный модуль.
|
||
>
|
||
> **`List<T>` из Kotlin в Java имеет `add()`.** Тип стирается до `java.util.List`, и Java-код может вызвать мутирующий метод. Падение будет в рантайме с `UnsupportedOperationException`. Для публичного API возвращайте `Collections.unmodifiableList(...)` или копию.
|
||
>
|
||
> **Платформенные объявления конфликтуют при стирании:**
|
||
> ```kotlin
|
||
> @JvmName("countStrings")
|
||
> fun List<String>.count2() = size
|
||
>
|
||
> @JvmName("countNumbers")
|
||
> fun List<Int>.count2() = size
|
||
>
|
||
> fun main() {
|
||
> println(listOf("a").count2()) // 1
|
||
> println(listOf(1, 2).count2()) // 2
|
||
> }
|
||
> ```
|
||
> Без `@JvmName` будет ошибка компиляции «platform declaration clash» - обе функции дают `count2(List)`. Это проявляется и в чисто Kotlin-проекте.
|
||
>
|
||
> **`@file:JvmName` должна стоять до `package`.** Файловые аннотации идут самой первой строкой файла, иначе компилятор их не примет.
|
||
>
|
||
> **`@get:JvmName` на свойстве в интерфейсе или `open` классе запрещена.** Переименование аксессора сломало бы переопределение, поэтому компилятор его не допускает для `open`/`override` членов.
|
||
>
|
||
> **Делегированные свойства из Java выглядят как обычные.** `by lazy` даёт `getX()`, но поле хранит `Lazy<T>` под именем `x$delegate`. Прямого доступа к значению нет, и `@JvmField` не применить. Подробнее: [Делегирование](../ООП/Делегирование.md)
|
||
>
|
||
> **Расширения не переопределяются и не видны как методы.** `fun Foo.bar()` из Java - это `FileKt.bar(foo)`. Java-код не найдёт `foo.bar()`, а IDE не предложит. Если метод нужен Java-клиентам, делайте его членом класса.
|
||
|
||
Далее: [Kotlin в байт-коде](Kotlin%20в%20байт-коде.md)
|