Dostęp do funkcji rozszerzenia Kotlin z poziomu Java

157

Czy jest możliwy dostęp do funkcji rozszerzeń z kodu Java?

Zdefiniowałem funkcję rozszerzenia w pliku Kotlin.

package com.test.extensions

import com.test.model.MyModel

/**
 *
 */
public fun MyModel.bar(): Int {
    return this.name.length()
}

Gdzie MyModeljest (wygenerowana) klasa java. Teraz chciałem uzyskać do niego dostęp w moim normalnym kodzie java:

MyModel model = new MyModel();
model.bar();

Jednak to nie działa. IDE nie rozpozna bar()metody i kompilacja nie powiedzie się.

Co działa przy użyciu funkcji statycznej z kotlin:

public fun bar(): Int {
   return 2*2
}

używając, import com.test.extensions.ExtensionsPackagewięc moje IDE wydaje się być poprawnie skonfigurowane.

Przeszukałem cały plik Java-interop z dokumentacji kotlin i dużo googlowałem, ale nie mogłem go znaleźć.

Co ja robię źle? Czy to w ogóle możliwe?

Lovis
źródło
Proszę rozwinąć , nie działa ? Czy kompiluje się, zgłasza wyjątek, czy co? Ty też masz import package com.test.extensions.MyModel?
meskobalazs
@meskobalazs zobacz moją zredagowaną odpowiedź.
Lovis
@meskobalazs również, nie mogę tego nawet zaimportować. Mogę tylko importowaćcom.test.extensions.ExtensionsPackage
Lovis

Odpowiedzi:

230

Wszystkie funkcje Kotlin zadeklarowane w pliku zostaną domyślnie skompilowane do metod statycznych w klasie w tym samym pakiecie i z nazwą pochodzącą z pliku źródłowego Kotlin (pierwsza litera pisana wielkimi literami i rozszerzenie „.kt” zastąpione przyrostkiem „Kt” ) . Metody wygenerowane dla funkcji rozszerzających będą miały dodatkowy pierwszy parametr z typem odbiornika funkcji rozszerzającej.

Stosując to do pierwotnego pytania, kompilator Java zobaczy plik źródłowy Kotlin o nazwie example.kt

package com.test.extensions

public fun MyModel.bar(): Int { /* actual code */ }

tak jakby została zadeklarowana następująca klasa Java

package com.test.extensions

class ExampleKt {
    public static int bar(MyModel receiver) { /* actual code */ }
}

Ponieważ z punktu widzenia Javy nic się nie dzieje z klasą rozszerzoną, nie można po prostu użyć składni kropkowej, aby uzyskać dostęp do takich metod. Ale nadal można je wywołać jako zwykłe statyczne metody Java:

import com.test.extensions.ExampleKt;

MyModel model = new MyModel();
ExampleKt.bar(model);

Import statyczny może być użyty dla klasy ExampleKt:

import static com.test.extensions.ExampleKt.*;

MyModel model = new MyModel();
bar(model);
goodwinnk
źródło
1
Zrozumiałem, to znaczy, że nie mogę używać rozszerzenia z Kotlin do Java, chyba że napiszę funkcje statyczne?
AbdulMomen عبدالمؤمن
11
JFYI, Możesz także zmienić nazwę klasy za pomocą @file: JvmName ("<TheNameThatYouWant> Utils").
crgarridos,
3
co jeśli utworzę rozszerzenie z parametrami?
AmirG,
3
Parametry @AmirG będą zgodne z instancją. W tym przypadku byłoby tobar(model, param1, param2);
Tim
To była naprawdę szybka pomoc, wielkie dzięki
Vivek Gupta
31

Funkcje rozszerzające najwyższego poziomu Kotlin są kompilowane jako metody statyczne Java.

Podany plik Kotlin Extensions.ktw pakiecie foo.barzawierającym:

fun String.bar(): Int {
    ...
}

Odpowiedni kod Java wyglądałby tak:

package foo.bar;

class ExtensionsKt {
    public static int bar(String receiver) { 
        ...
    }
}

Chyba że Extensions.ktzawierał linię

@file:JvmName("DemoUtils")

W takim przypadku zostanie nazwana statyczna klasa Java DemoUtils

W Kotlinie metody rozszerzające można zadeklarować na inne sposoby. (Na przykład jako funkcja składowa lub jako rozszerzenie obiektu towarzyszącego).

Aleksandr Dubinsky
źródło
8

Mam plik Kotlin o nazwie NumberFormatting.kt, który ma następującą funkcję

fun Double.formattedFuelAmountString(): String? {
    val format = NumberFormat.getNumberInstance()
    format.minimumFractionDigits = 2
    format.maximumFractionDigits = 2
    val string = format.format(this)
    return string
}

W javie mam do niego łatwy dostęp przez plik NumberFormattingKt w następujący sposób po wymaganym imporcie import ....extensions.NumberFormattingKt;

String literString = NumberFormattingKt.formattedFuelAmountString(item.getAmount());
Ilker Baltaci
źródło
6

Zawsze możesz zobaczyć rzeczywisty kod Java, który jest generowany z twojego kodu Kotlin, przechodząc do Tools > Kotlin > Show Kotlin Bytecode, a następnie klikając Decompile. To może Ci ogromnie pomóc. W twoim przypadku kod Java będzie wyglądał tak, jeśli maszMyModelExtensions.kt

public final class MyModelExtensionsKt {
   public static final int bar(@NotNull MyModel $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return $receiver.getName().length();
   }
}

możesz to poprawić, używając @JvmNamena pliku zawierającym bar:

@file:JvmName("MyModels")
package io.sspinc.datahub.transformation

public fun MyModel.bar(): Int {
    return this.name.length
}

i da to taki kod:

public final class MyModels {
   public static final int bar(@NotNull MyModel $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return $receiver.getName().length();
   }
}

Używanie MyModelsjest zgodne z sugestiami Effective Java dotyczącymi klas narzędziowych. Możesz także zmienić nazwę metody w następujący sposób:

public fun MyModel.extractBar(): Int {
    return this.name.length
}

to od strony Javy będzie wyglądać idiomatycznie:

MyModels.extractBar(model);
Adam Arold
źródło
0

Musisz zduplikować swoje funkcje w plikach klas:

Utwórz plik Kotlin dla ex Utils.kt

Wpisz kod

  class Utils {
                companion object {
                    @JvmStatic
                    fun String.getLength(): Int {//duplicate of func for java
                        return this.length
                    }
                }
            }

        fun String.getLength(): Int {//kotlin extension function
            return this.length
        }

LUB

class Utils {
    companion object {

        @JvmStatic
        fun getLength(s: String): Int {//init func for java
            return s.length
        }
    }
}

fun String.getLength(): Int {//kotlin extension function
    return Utils.Companion.getLength(this)//calling java extension function in Companion
}

W kotlin użyj:

val str = ""
val lenth = str.getLength()

W Javie użyj tego:

String str = "";
 Integer lenth = Utils.getLength(str);
NickUnuchek
źródło
0

Mi to pasuje:

Kotlin kotlin

Kod Java wprowadź opis obrazu tutaj

Mój projekt to stary projekt na Androida stworzony w Javie; teraz utworzyłem pierwszy plik kotlin i dodałem zabawne rozszerzenia ciągów String.isNotNullOrEmpty (): Boolean {...}

i mogę to nazwać z pliku java za pomocą: StringUtilsKt.isNotNullOrEmpty (thestring).

Nazwa mojego pliku Kotlin to StringUtils

Cristian Díez
źródło
-1

Inne odpowiedzi tutaj dotyczą przypadku wywołania funkcji rozszerzenia znajdującej się na najwyższym poziomie pliku pakietu Kotlin.

Jednak w moim przypadku musiałem wywołać funkcję rozszerzenia znajdującą się wewnątrz klasy. W szczególności miałem do czynienia z przedmiotem.

Rozwiązaniem jest niezwykle proste .

Wszystko, co musisz zrobić, to oznaczyć swoją funkcję rozszerzenia jako @JvmStatic, i voila! Twój kod Java będzie mógł uzyskać do niego dostęp i go używać.

forresthopkinsa
źródło
Dlaczego głos przeciw? Moja odpowiedź jest poprawna i oryginalna.
forresthopkinsa
-2

Kiedy rozszerzasz taką klasę:

fun String.concatenatedLength(str: String): Int {
    return (this.length + str.length)
}

fun f() {
    var len = "one string".concatenatedLength("another string")
    println(len)
}

Skompiluje się do tego:

import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

public final class ExampleKt {
  public static final int concatenatedLength(@NotNull String $receiver, @NotNull String str) {
    Intrinsics.checkParameterIsNotNull((Object) $receiver, (String) "$receiver");
    Intrinsics.checkParameterIsNotNull((Object) str, (String) "str");
    return $receiver.length() + str.length();
  }

  public static final void f() {
    int len = ExampleKt.concatenatedLength("one string", "another string");
    System.out.println(len);
  }
}

Tutaj jest więcej przykładów .

Tomas Bjerre
źródło
-3

O ile wiem, nie jest to możliwe. Z mojego czytania dokumentów rozszerzeń wynika, że

public fun MyModel.bar(): Int {
    return this.name.length()
}

tworzy nową metodę z podpisem

public static int MyModelBar(MyModel obj) {
    return obj.name.length();
}

Następnie Kotlin mapuje tę funkcję na wywołania formularza myModel.bar(), gdzie if bar()nie znajduje się wMyModel klasie, szuka statycznych metod pasujących do sygnatury i schematu nazewnictwa, który generuje. Zauważ, że jest to tylko założenie z ich stwierdzeń, że rozszerzenia są importowane statycznie i nie zastępują zdefiniowanych metod. Nie dotarłem na tyle daleko, aby wiedzieć na pewno.

Tak więc, zakładając, że powyższe jest prawdą, nie ma możliwości wywołania rozszerzeń Kotlin ze zwykłego starego kodu Java, ponieważ kompilator zobaczy po prostu nieznaną metodę wywoływaną na obiekcie i wyświetli błąd.

johnw188
źródło
3
Ta odpowiedź jest nieprawidłowa, można uzyskać do nich dostęp. Zobacz zaakceptowaną odpowiedź.
Jayson Minard
Kompilator nie szuka metod statycznych (zwłaszcza statycznych metod Java). Szuka metod rozszerzających, które zostały zadeklarowane lub zaimportowane w tym samym pliku.
Kirill Rakhman,