Avatar ·

Как вызвать функции-расширения Kotlin из Java и почему они не видны?

Всем привет! Я новичок в Kotlin, перехожу с Java. Написал пару функций-расширений для класса String в отдельном файле Utils.kt: ```kotlin // Utils.kt package com.example.utils fun String.isEmail(): Boolean = this.contains("@") fun String.capitalizeWords(): String = this.split(" ").joinToString(" ") { it.capitalize() } ``` В Kotlin всё работает отлично, но когда я пытаюсь вызвать эти функции из Java-кода, IDE подчёркивает их красным: ```java // Main.java import com.example.utils.UtilsKt; // импорт появился, но... public class Main { public static void main(String[] args) { String email = "test@example.com"; // Ошибка: Cannot resolve method 'isEmail' System.out.println(email.isEmail()); // И так тоже не работает: // System.out.println(UtilsKt.isEmail(email)); } } ``` Я читал, что Kotlin компилирует функции-расширения в статические методы, но почему они не видны как обычные методы класса String? В чём подвох? Как правильно вызывать их из Java? Нужно ли добавлять какие-то аннотации или менять объявление? Заранее спасибо за помощь! <<>>

Почему функции-расширения не видны напрямую в Java

Ключевая особенность функций-расширений Kotlin в том, что они не изменяют сам класс, а компилируются в статические методы с первым параметром — объектом, к которому применяется расширение. Java не поддерживает синтаксический сахар для вызова таких методов через точку, поэтому нужно использовать классический статический вызов.

Правильный способ вызова из Java

Ваш импорт com.example.utils.UtilsKt верен — это класс, который Kotlin создаёт для файла Utils.kt. Но вызывать метод нужно передавая объект как первый аргумент:

// Main.java
import com.example.utils.UtilsKt;

public class Main {
    public static void main(String[] args) {
        String email = "test@example.com";
        
        // Правильный вызов — передаём строку как аргумент
        boolean isValid = UtilsKt.isEmail(email);
        System.out.println(isValid); // true
        
        String name = "john doe";
        String capitalized = UtilsKt.capitalizeWords(name);
        System.out.println(capitalized); // John Doe
    }
}

Что происходит под капотом

Ваш код на Kotlin превращается в Java примерно так:

public final class UtilsKt {
    public static final boolean isEmail(String $this) {
        return $this.contains("@");
    }
    
    public static final String capitalizeWords(String $this) {
        // реализация
    }
}

Обратите внимание: параметр называется $this — именно так Kotlin обозначает объект-получатель расширения.

Как сделать вызов более естественным (опционально)

Если очень хочется писать email.isEmail() в Java, можно использовать аннотацию @JvmName и создать класс-обёртку, но это излишне. Рекомендую привыкнуть к статическому синтаксису.

Важные нюансы

  • Имя класса-файла: Если файл называется Utils.kt, класс будет UtilsKt. Можно изменить имя аннотацией @file:JvmName("Utils") в начале файла.
  • Перегрузка: Если в Kotlin есть функция-расширение и обычная функция с тем же именем, в Java они будут иметь разные сигнатуры.
  • Extension-свойства работают аналогично — превращаются в статические методы get/set.
  • Специальные типы: Расширения для Any? или generic-типов могут требовать дополнительных преобразований.

Пример с @file:JvmName

// Utils.kt
@file:JvmName("StringUtils")
package com.example.utils

fun String.isEmail(): Boolean = this.contains("@")

Теперь в Java:

import com.example.utils.StringUtils;
// ...
StringUtils.isEmail(email);

Этот подход делает код читаемее для Java-разработчиков, так как имя класса отражает его назначение.

<<>>

Альтернативный подход: использование объекта-компаньона

Если вам не нравится синтаксис статических методов, можно обернуть функции-расширения в объект-компаньон или обычный объект Kotlin, а затем вызывать их через имя объекта. Это особенно удобно, когда расширений много и они логически связаны.

// Utils.kt
package com.example.utils

object StringExtensions {
    fun String.isEmail(): Boolean = this.contains("@")
    fun String.capitalizeWords(): String = this.split(" ").joinToString(" ") { it.capitalize() }
}

В Java такой объект будет представлен как синглтон с методами экземпляра. Вызов будет выглядеть так:

import com.example.utils.StringExtensions;

public class Main {
    public static void main(String[] args) {
        String email = "test@example.com";
        boolean isValid = StringExtensions.INSTANCE.isEmail(email);
        // или через статический метод, если добавить @JvmStatic
    }
}

Ещё более элегантный вариант с @JvmStatic

Добавьте аннотацию к функциям внутри объекта:

object StringExtensions {
    @JvmStatic
    fun String.isEmail(): Boolean = this.contains("@")
}

Теперь в Java можно писать просто:

StringExtensions.isEmail(email);

Этот подход даёт более чистый синтаксис, но требует дополнительного объявления объекта. Выбирайте то, что удобнее для вашего проекта и команды.