Zugriff auf Kotlin-Erweiterungsfunktionen von Java aus

155

Ist es möglich, über Java-Code auf Erweiterungsfunktionen zuzugreifen?

Ich habe die Erweiterungsfunktion in einer Kotlin-Datei definiert.

package com.test.extensions

import com.test.model.MyModel

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

Wo MyModelist eine (generierte) Java-Klasse? Jetzt wollte ich in meinem normalen Java-Code darauf zugreifen:

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

Das funktioniert jedoch nicht. Die IDE erkennt die bar()Methode nicht und die Kompilierung schlägt fehl.

Was funktioniert, ist die Verwendung einer statischen Funktion von kotlin:

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

durch die Verwendung import com.test.extensions.ExtensionsPackageso scheint mein IDE korrekt konfiguriert werden.

Ich habe die gesamte Java-Interop-Datei aus den Kotlin-Dokumenten durchsucht und auch viel gegoogelt, aber ich konnte sie nicht finden.

Was mache ich falsch? Ist das überhaupt möglich?

Lovis
quelle
Bitte näher erläutern funktioniert nicht ? Kompiliert es, löst es eine Ausnahme aus oder was? Hast du auch import package com.test.extensions.MyModel?
Meskobalazs
@meskobalazs siehe meine bearbeitete Antwort.
Lovis
@meskobalazs auch, das kann ich gar nicht importieren. Ich kann nur importierencom.test.extensions.ExtensionsPackage
Lovis

Antworten:

229

Alle in einer Datei deklarierten Kotlin-Funktionen werden standardmäßig zu statischen Methoden in einer Klasse innerhalb desselben Pakets und mit einem Namen kompiliert, der von der Kotlin-Quelldatei abgeleitet ist (Großbuchstabe groß geschrieben und Erweiterung ".kt" durch das Suffix "Kt" ersetzt ). . Für Erweiterungsfunktionen generierte Methoden haben einen zusätzlichen ersten Parameter mit dem Empfängertyp der Erweiterungsfunktion.

Wenn der Java-Compiler es auf die ursprüngliche Frage anwendet, wird die Kotlin-Quelldatei mit dem Namen example.kt angezeigt

package com.test.extensions

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

als ob die folgende Java-Klasse deklariert wäre

package com.test.extensions

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

Da aus Java-Sicht mit der erweiterten Klasse nichts passiert, können Sie nicht einfach die Punktsyntax verwenden, um auf solche Methoden zuzugreifen. Sie können jedoch weiterhin als normale statische Java-Methoden aufgerufen werden:

import com.test.extensions.ExampleKt;

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

Der statische Import kann für die ExampleKt-Klasse verwendet werden:

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

MyModel model = new MyModel();
bar(model);
goodwinnk
quelle
1
Ich habe verstanden, dass ich die Erweiterung von Kotlin nach Java nur verwenden kann, wenn ich statische Funktionen schreibe.
AbdulMomen
11
JFYI, Sie können den Namen der Klasse auch mit @file ändern: JvmName ("<TheNameThatYouWant> Utils").
Crgarridos
3
Was ist, wenn ich eine Erweiterung mit Parametern erstelle?
AmirG
3
@ AdmirG-Parameter folgen der Instanz. In diesem Fall wäre esbar(model, param1, param2);
Tim
Dies war wirklich eine schnelle Hilfe, vielen Dank
Vivek Gupta
31

Kotlin-Erweiterungsfunktionen der obersten Ebene werden als statische Java-Methoden kompiliert.

Gegebene Kotlin-Datei Extensions.ktin Paket foo.barmit:

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

Der entsprechende Java-Code wäre:

package foo.bar;

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

Es sei denn, das heißt, Extensions.ktdie Zeile enthalten

@file:JvmName("DemoUtils")

In diesem Fall würde die statische Java-Klasse benannt DemoUtils

In Kotlin können Erweiterungsmethoden auf andere Weise deklariert werden. (Zum Beispiel als Elementfunktion oder als Erweiterung eines Begleitobjekts.)

Aleksandr Dubinsky
quelle
8

Ich habe eine Kotlin-Datei namens NumberFormatting.kt, die die folgende Funktion hat

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

In Java greife ich nach dem erforderlichen Import einfach über die Datei NumberFormattingKt wie folgt darauf zu import ....extensions.NumberFormattingKt;

String literString = NumberFormattingKt.formattedFuelAmountString(item.getAmount());
Ilker Baltaci
quelle
6

Sie können immer den tatsächlichen Java-Code sehen, der aus Ihrem Kotlin-Code generiert wird, indem Sie auf Tools > Kotlin > Show Kotlin Bytecodeklicken und dann auf klicken Decompile. Dies kann Ihnen enorm helfen. In Ihrem Fall sieht der Java-Code folgendermaßen ausMyModelExtensions.kt

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

Sie können dies verbessern, indem Sie @JvmNamefür die Datei Folgendes verwenden bar:

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

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

und es wird zu diesem Code führen:

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

Die Verwendung entspricht den MyModelsVorschlägen von Effective Java für Dienstprogrammklassen. Sie können Ihre Methode auch folgendermaßen umbenennen:

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

dann wird es von der Java-Seite idiomatisch aussehen:

MyModels.extractBar(model);
Adam Arold
quelle
0

Sie müssen Ihre Funktionen in den Klassendateien duplizieren:

Erstellen Sie eine Kotlin-Datei, z. B. Utils.kt

Gib den code ein

  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
        }

ODER

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
}

In Kotlin verwenden:

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

Verwenden Sie in Java Folgendes:

String str = "";
 Integer lenth = Utils.getLength(str);
NickUnuchek
quelle
0

Für mich geht das:

Kotlin Kotlin

Java-Code Geben Sie hier die Bildbeschreibung ein

Mein Projekt ist ein altes Android-Projekt, das mit Java erstellt wurde. Jetzt habe ich die erste Kotlin-Datei erstellt und String-Erweiterungen hinzugefügt. fun String.isNotNullOrEmpty (): Boolean {...}

und ich könnte es aus der Java-Datei aufrufen, indem ich: StringUtilsKt.isNotNullOrEmpty (thestring).

Mein Kotlin-Dateiname ist StringUtils

Cristian Díez
quelle
-1

Die anderen Antworten hier beziehen sich auf den Fall des Aufrufs einer Erweiterungsfunktion, die sich auf der obersten Ebene einer Kotlin-Paketdatei befindet.

Mein Fall war jedoch, dass ich eine Erweiterungsfunktion innerhalb einer Klasse aufrufen musste. Insbesondere habe ich mich mit einem Objekt befasst.

Die Lösung ist unglaublich einfach .

Alles was Sie tun müssen, ist Ihre Erweiterungsfunktion als @JvmStaticund voila zu kommentieren! Ihr Java-Code kann darauf zugreifen und ihn verwenden.

forresthopkinsa
quelle
Warum das Downvote? Meine Antwort ist richtig und originell.
Forresthopkinsa
-2

Wenn Sie eine Klasse wie diese erweitern:

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

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

Es wird dazu kompiliert:

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);
  }
}

Hier finden Sie weitere Beispiele .

Tomas Bjerre
quelle
-3

Soweit ich das beurteilen kann, ist dies nicht möglich. Aus meiner Lektüre der Erweiterungsdokumente geht hervor, dass

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

erstellt eine neue Methode mit der Signatur

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

Dann ordnet Kotlin diese Funktion Aufrufen des Formulars zu myModel.bar(). Wenn bar()sie in der MyModelKlasse nicht gefunden werden , sucht sie nach statischen Methoden, die mit dem ausgegebenen Signatur- und Namensschema übereinstimmen. Beachten Sie, dass dies nur eine Annahme aus ihren Aussagen über statisch importierte Erweiterungen ist, die definierte Methoden nicht überschreiben. Ich bin in ihrer Quelle nicht weit genug gekommen, um es sicher zu wissen.

Unter der Annahme, dass das oben Gesagte zutrifft, können Kotlin-Erweiterungen nicht aus einfachem alten Java-Code aufgerufen werden, da der Compiler nur eine unbekannte Methode für ein Objekt aufruft und einen Fehler ausgibt.

johnw188
quelle
3
Diese Antwort ist nicht korrekt, sie können abgerufen werden. Siehe akzeptierte Antwort.
Jayson Minard
Und der Compiler sucht nicht nach statischen Methoden (insbesondere nicht nach statischen Java-Methoden). Es wird nach Erweiterungsmethoden gesucht, die in derselben Datei deklariert oder importiert wurden.
Kirill Rakhman