
In diesem Artikel wird die Verwendung von Statik in Kotlin beschrieben.
Fangen wir an.
Kotlin hat keine statische Aufladung!
Dies ist in der offiziellen Dokumentation angegeben.
Und es scheint, dass dies den Artikel beenden könnte. Aber lassen Sie mich, wie so? Wenn Sie in Android Studio Java-Code in eine Kotlin-Datei einfügen, wird der Smart Converter die Magie ausführen, alles in Code in der richtigen Sprache umwandeln und es wird funktionieren! Aber was ist mit der vollständigen Kompatibilität mit Java?
Zu diesem Zeitpunkt wird jeder Entwickler, der etwas über das Fehlen von Statik in Kotlin erfährt, in die Dokumentation und in die Foren gehen, um dieses Problem zu beheben. Lassen Sie uns nachdenklich und sorgfältig zusammenkommen. Ich werde versuchen, bis zum Ende dieses Artikels so wenig Fragen wie möglich zu beantworten.
Was ist die Statik in Java? Es gibt:
- Klasse statische Felder
- statische Klassenmethoden
- statisch verschachtelte Klassen
Lassen Sie uns ein Experiment durchführen (dies ist das erste, was mir in den Sinn kommt).
Erstellen Sie eine einfache Java-Klasse:
public class SimpleClassJava1 { public static String staticField = "Hello, static!"; public static void setStaticValue (String value){ staticField = value; } }
Hier ist alles einfach: In der Klasse erstellen wir ein statisches Feld und eine statische Methode. Wir machen alles öffentlich für Experimente mit Zugang von außen. Wir verbinden das Feld und die Methode logisch.
Erstellen Sie nun eine leere Kotlin-Klasse und versuchen Sie, den gesamten Inhalt der SimpleClassJava1-Klasse in diese zu kopieren. Wir beantworten die resultierende Frage zur Konvertierung mit „Ja“ und sehen, was passiert ist:
class SimpleClassKotlin1 { var staticField = "Hello, static!" fun setStaticValue(value: String) { staticField = value } }
Es scheint, dass dies nicht genau das ist, was wir brauchen ... Um dies sicherzustellen, werden wir den Bytecode dieser Klasse in Java-Code konvertieren und sehen, was passiert ist:
public final class SimpleClassKotlin1 { @NotNull private String staticField = "Hello, static!"; @NotNull public final String getStaticField() { return this.staticField; } public final void setStaticField(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.staticField = var1; } public final void setStaticValue(@NotNull String value) { Intrinsics.checkParameterIsNotNull(value, "value"); this.staticField = value; } }
Ja Alles ist genau so, wie es schien. Hier riecht es nicht nach Statik. Der Konverter hat den statischen Modifikator in der Signatur einfach abgeschnitten, als wäre er nicht vorhanden. Für alle Fälle werden wir sofort eine Schlussfolgerung ziehen: Vertraue dem Konverter nicht blind, manchmal kann es unangenehme Überraschungen bringen.
Übrigens hätte die Konvertierung desselben Java-Codes in Kotlin vor etwa sechs Monaten zu einem etwas anderen Ergebnis geführt. Also nochmal: Vorsicht bei der automatischen Konvertierung!
Wir experimentieren weiter.
Wir gehen zu jeder Klasse auf Kotlin und versuchen, die statischen Elemente der Java-Klasse darin aufzurufen:
SimpleClassJava1.setStaticValue("hi!") SimpleClassJava1.staticField = "hello!!!"
So! Alles ist perfekt aufgerufen, selbst die automatische Vervollständigung des Codes sagt uns alles! Ziemlich neugierig.
Kommen wir nun zum wesentlicheren Teil. In der Tat haben die Schöpfer von Kotlin beschlossen, sich von der Statik in der Form zu entfernen, in der wir sie gewohnt sind. Warum wir genau das getan haben und nicht anders diskutieren - es gibt viele Streitigkeiten und Meinungen zu diesem Thema im Netzwerk. Wir werden nur herausfinden, wie wir damit leben sollen. Natürlich wurden wir nicht nur der Statik beraubt. Kotlin gibt uns eine Reihe von Werkzeugen, mit denen wir den Verlust kompensieren können. Sie sind für den Innenbereich geeignet. Und die versprochene volle Kompatibilität mit Java-Code. Lass uns gehen!
Das schnellste und einfachste, was Sie realisieren und verwenden können, ist die Alternative, die wir anstelle statischer Methoden anbieten - Funktionen auf Paketebene. Was ist das? Dies ist eine Funktion, die keiner Klasse angehört. Das heißt, diese Art von Logik befindet sich irgendwo im Paketraum in einem Vakuum. Wir können es in jeder Datei innerhalb des Pakets beschreiben, die uns interessiert. Benennen Sie diese Datei beispielsweise JustFun.kt und platzieren Sie sie im Paket
com.example.mytestapplication
package com.example.mytestapplication fun testFun(){
Konvertieren Sie den Bytecode dieser Datei in Java und schauen Sie hinein:
public final class JustFunKt { public static final void testFun() {
Wir sehen, dass in Java eine Klasse erstellt wird, deren Name den Namen der Datei berücksichtigt, in der die Funktion beschrieben wird, und die Funktion selbst wird zu einer statischen Methode.
Wenn wir nun die
testFun
Funktion in Kotlin von einer Klasse (oder derselben Funktion) aus aufrufen möchten, die sich im
package com.example.mytestapplication
(
package com.example.mytestapplication
dasselbe Paket wie die Funktion), können wir einfach ohne zusätzliche Tricks darauf zugreifen. Wenn wir es aus einem anderen Paket aufrufen, müssen wir importieren, was uns vertraut ist und normalerweise für Klassen gilt:
import com.example.pavka.mytestapplication.testFun
Wenn wir über das Aufrufen der Funktion t
estFun
aus Java-Code sprechen, müssen wir die Funktion immer importieren, unabhängig davon, von welchem Paket wir sie aufrufen:
import static com.example.pavka.mytestapplication.ForFunKt.testFun;
In der Dokumentation heißt es, dass in den meisten Fällen anstelle statischer Methoden die Verwendung von Funktionen auf Paketebene ausreicht. Meiner persönlichen Meinung nach (die nicht mit der Meinung aller anderen übereinstimmen muss) ist diese Methode zur Implementierung der Statik jedoch nur für kleine Projekte geeignet.
Es stellt sich heraus, dass diese Funktionen keiner Klasse explizit angehören. Visuell sieht ihr Aufruf wie ein Aufruf der Klassenmethode (oder ihres übergeordneten Elements) aus, in der wir uns befinden, was manchmal verwirrend sein kann. Nun und die Hauptsache - es kann nur eine Funktion mit diesem Namen im Paket geben. Selbst wenn wir versuchen, die gleichnamige Funktion in einer anderen Datei zu erstellen, gibt das System einen Fehler aus. Wenn wir über große Projekte sprechen, haben wir zum Beispiel ziemlich oft verschiedene Fabriken mit statischen Methoden mit demselben Namen.
Schauen wir uns andere Alternativen zur Implementierung statischer Methoden und Felder an.
Erinnern Sie sich an das statische Feld einer Klasse. Dies ist ein Klassenfeld, das zu der Klasse gehört, in der es deklariert ist, aber nicht zu einer bestimmten Instanz der Klasse gehört, dh es wird in einer einzelnen Instanz für die gesamte Klasse erstellt.
Kotlin bietet uns für diese Zwecke an, eine zusätzliche Entität zu verwenden, die auch in einer einzigen Kopie vorhanden ist. Mit anderen Worten, Singleton.
Kotlin hat ein Objektschlüsselwort, um Singletones zu deklarieren.
object MySingltoneClass {
Solche Objekte werden träge initialisiert, dh zum Zeitpunkt des ersten Aufrufs.
Ok, es gibt auch Singletones in Java. Wo sind die Statistiken?
Für jede Klasse in Kotlin können wir einen Begleiter oder ein Begleitobjekt erstellen. Ein Singleton, der an eine bestimmte Klasse gebunden ist. Dies kann mit zwei
companion object
zusammen erfolgen:
class SimpleClassKotlin1 { companion object{ var companionField = "Hello!" fun companionFun (vaue: String){
Hier haben wir die
SimpleClassKotlin1
Klasse, in der wir einen Singleton mit dem Objektschlüsselwort deklarieren und an das Objekt binden, in dem er mit dem Companion-Schlüsselwort deklariert ist. Hier können Sie darauf achten, dass im Gegensatz zur vorherigen Singleton-Deklaration (MySingltoneClass) der Name der Singleton-Klasse nicht angegeben wird. Wenn das Objekt als Begleiter deklariert ist, darf es seinen Namen nicht angeben. Dann wird es automatisch
Companion
. Bei Bedarf können wir eine Instanz der Companion-Klasse folgendermaßen abrufen:
val companionInstance = SimpleClassKotlin1.Companion
Ein Aufruf der Eigenschaften und Methoden einer Begleitklasse kann jedoch direkt über einen Aufruf der Klasse erfolgen, an die sie angehängt ist:
SimpleClassKotlin1.companionField SimpleClassKotlin1.companionFun("Hi!")
Es sieht schon so aus, als würde man statische Felder und Klassen aufrufen, oder?
Bei Bedarf können wir der Begleitklasse einen Namen geben, in der Praxis wird dies jedoch sehr selten durchgeführt. Von den interessanten Merkmalen der zugehörigen Klassen kann festgestellt werden, dass sie wie jede gewöhnliche Klasse Schnittstellen implementieren kann, was uns manchmal helfen kann, dem Code etwas mehr Ordnung zu verleihen:
interface FactoryInterface<T> { fun factoryMethod(): T } class SimpleClassKotlin1 { companion object : FactoryInterface<MyClass> { override fun factoryMethod(): MyClass = MyClass() } }
Eine Begleitklasse kann nur eine Klasse haben. Niemand verbietet uns jedoch, eine beliebige Anzahl von Singleton-Objekten innerhalb der Klasse zu deklarieren. In diesem Fall müssen wir jedoch den Namen dieser Klasse explizit angeben und diesen Namen entsprechend angeben, wenn wir auf die Felder und Methoden dieser Klasse verweisen.
Wenn wir von den als Objekt deklarierten Klassen sprechen, können wir sagen, dass wir auch verschachtelte Objekte in ihnen deklarieren können, aber wir können kein Begleitobjekt in ihnen deklarieren.
Es ist Zeit, "unter die Haube" zu schauen. Nehmen Sie an unserer einfachen Klasse teil:
class SimpleClassKotlin1 { companion object{ var companionField = "Hello!" fun companionFun (vaue: String){ } } object OneMoreObject { var value = 1 fun function(){ } }
Dekompilieren Sie nun den Bytecode in Java:
public final class SimpleClassKotlin1 { @NotNull private static String companionField = "Hello!"; public static final SimpleClassKotlin1.Companion Companion = new SimpleClassKotlin1.Companion((DefaultConstructorMarker)null); public static final class OneMoreObject { private static int value; public static final SimpleClassKotlin1.OneMoreObject INSTANCE; public final int getValue() { return value; } public final void setValue(int var1) { value = var1; } public final void function() { } static { SimpleClassKotlin1.OneMoreObject var0 = new SimpleClassKotlin1.OneMoreObject(); INSTANCE = var0; value = 1; } } public static final class Companion { @NotNull public final String getCompanionField() { return SimpleClassKotlin1.companionField; } public final void setCompanionField(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); SimpleClassKotlin1.companionField = var1; } public final void companionFun(@NotNull String vaue) { Intrinsics.checkParameterIsNotNull(vaue, "vaue"); } private Companion() { }
Wir schauen, was passiert ist.
Die Eigenschaft des Begleitobjekts wird als statisches Feld unserer Klasse dargestellt:
private static String companionField = "Hello!";
Dies scheint genau das zu sein, was wir wollten. Dieses Feld ist jedoch privat und wird über den Getter und Setter unserer Companion-Klasse aufgerufen, die hier als
public static final class
, und ihre Instanz wird als Konstante dargestellt:
public static final SimpleClassKotlin1.Companion Companion = new SimpleClassKotlin1.Companion((DefaultConstructorMarker)null);
Die CompanionFun-Funktion wurde nicht zur statischen Methode unserer Klasse (wahrscheinlich sollte dies nicht der Fall sein). Es blieb die Funktion eines Singletons, der in der SimpleClassKotlin1-Klasse initialisiert wurde. Wenn Sie jedoch darüber nachdenken, ist dies logischerweise ungefähr dasselbe.
Bei der
OneMoreObject
Klasse
OneMoreObject
Situation sehr ähnlich. Es ist nur erwähnenswert, dass hier im Gegensatz zum Begleiter das Feld der
SimpleClassKotlin1
nicht in die
SimpleClassKotlin1
Klasse
SimpleClassKotlin1
, sondern in
OneMoreObject
blieb, sondern auch statisch wurde und den generierten Getter und Setter erhielt.
Versuchen wir, all das zu verstehen.
Wenn wir statische Felder oder Klassenmethoden in Kotlin implementieren möchten, sollten wir dazu das in dieser Klasse deklarierte Companion-Objekt verwenden.
Das Aufrufen dieser Felder und Funktionen von Kotlin aus sieht genauso aus wie das Aufrufen der Statik in Java. Was aber, wenn wir versuchen, diese Felder und Funktionen in Java aufzurufen?
Die automatische Vervollständigung teilt uns mit, dass die folgenden Anrufe verfügbar sind:
SimpleClassKotlin1.Companion.companionFun("hello!"); SimpleClassKotlin1.Companion.setCompanionField("hello!"); SimpleClassKotlin1.Companion.getCompanionField();
Das heißt, hier gehen wir nicht davon aus, den Namen des Begleiters direkt anzugeben. Dementsprechend wird hier der Name verwendet, der dem Standard-Begleitobjekt zugewiesen wurde. Nicht sehr praktisch, oder?
Trotzdem haben die Entwickler von Kotlin es möglich gemacht, es in Java bekannter zu machen. Dafür gibt es mehrere Möglichkeiten.
@JvmField var companionField = "Hello!"
Wenn wir diese Annotation auf das
companionField
Feld unseres
companionField
Objekts anwenden, sehen wir beim Konvertieren von Bytecode in Java, dass das statische Feld
companionField
SimpleClassKotlin1 nicht mehr privat, sondern öffentlich ist und der Getter und Setter für CompanionField in der statischen
Companion
Klasse verschwunden sind. Jetzt können wir auf übliche Weise über Java-Code auf
companionField
zugreifen.
Die zweite Möglichkeit besteht darin, einen
lateinit
Modifikator für die Eigenschaften des Begleitobjekts anzugeben, Eigenschaften mit später Initialisierung. Vergessen Sie nicht, dass dies nur für var-Eigenschaften gilt und sein Typ nicht null und nicht primitiv sein sollte. Vergessen Sie nicht die Regeln für den Umgang mit solchen Eigenschaften.
lateinit var lateinitField: String
Und noch eine Möglichkeit: Wir können die Eigenschaft des Begleitobjekts als Konstante deklarieren, indem wir den Modifikator const angeben. Es ist leicht zu erraten, dass dies eine Val-Eigenschaft sein sollte.
const val myConstant = "CONSTANT"
In jedem dieser Fälle enthält der generierte Java-Code das übliche öffentliche statische Feld. Im Fall von const ist dieses Feld ebenfalls endgültig. Natürlich lohnt es sich zu verstehen, dass jeder dieser drei Fälle seinen eigenen logischen Zweck hat und nur der erste speziell für die einfache Verwendung mit Java entwickelt wurde. Der Rest erhält dieses „Brötchen“ wie in einer Last.
Es sollte separat beachtet werden, dass der const-Modifikator für Eigenschaften von Objekten, Begleitobjekten und für Eigenschaften der Paketebene verwendet werden kann. Im letzteren Fall erhalten wir dasselbe wie bei Verwendung der Funktionen auf Paketebene und mit denselben Einschränkungen. Java-Code wird mit einem statischen öffentlichen Feld in der Klasse generiert, dessen Name den Namen der Datei berücksichtigt, in der wir die Konstante beschrieben haben. Ein Paket kann nur eine Konstante mit dem angegebenen Namen haben.
Wenn die Funktion des Begleitobjekts beim Generieren von Java-Code auch in eine statische Methode konvertiert werden soll, müssen wir dazu die Annotation
@JvmStatic
auf diese Funktion anwenden.
Es ist auch zulässig, die Annotation
@JvmStatic
auf die Eigenschaften von Begleitobjekten (und nur auf Singleton-Objekte) anzuwenden. In diesem Fall wird die Eigenschaft nicht zu einem statischen Feld, sondern es wird ein statischer Getter und Setter für diese Eigenschaft generiert. Schauen Sie sich zum besseren Verständnis diese Kotlin-Klasse an:
class SimpleClassKotlin1 { companion object{ @JvmStatic fun companionFun (vaue: String){ } @JvmStatic var staticField = 1 } }
In diesem Fall sind die folgenden Aufrufe von Java aus gültig:
int x; SimpleClassKotlin1.companionFun("hello!"); x = SimpleClassKotlin1.getStaticField(); SimpleClassKotlin1.setStaticField(10); SimpleClassKotlin1.Companion.companionFun("hello"); x = SimpleClassKotlin1.Companion.getStaticField(); SimpleClassKotlin1.Companion.setStaticField(10);
Die folgenden Aufrufe sind von Kotlin gültig:
SimpleClassKotlin1.companionFun("hello!") SimpleClassKotlin1.staticField SimpleClassKotlin1.Companion.companionFun("hello!") SimpleClassKotlin1.Companion.staticField
Es ist klar, dass Sie für Java die ersten 3 und für Kotlin die ersten 2 verwenden sollten. Der Rest der Aufrufe ist nur gültig.
Nun bleibt letzteres zu klären. Was ist mit statisch verschachtelten Klassen? Hier ist alles einfach - das Analogon einer solchen Klasse in Kotlin ist eine regulär verschachtelte Klasse ohne Modifikatoren:
class SimpleClassKotlin1 { class LooksLikeNestedStatic { } }
Nach der Konvertierung des Bytecodes in Java sehen wir:
public final class SimpleClassKotlin1 { public static final class LooksLikeNestedStatic { } }
Das brauchen wir in der Tat. Wenn wir nicht möchten, dass die Klasse endgültig ist, geben wir im Kotlin-Code den offenen Modifikator dafür an. Ich erinnerte mich nur für den Fall daran.
Ich denke, Sie können zusammenfassen. In Kotlin selbst gibt es, wie gesagt, keine Statik in der Form, in der wir es gewohnt sind, sie zu sehen. Mit den vorgeschlagenen Tools können wir jedoch alle Arten von Statiken im generierten Java-Code implementieren. Volle Kompatibilität mit Java wird ebenfalls bereitgestellt, und wir können statische Felder und Methoden von Java-Klassen direkt von Kotlin aus aufrufen.
In den meisten Fällen erfordert die Implementierung einer Statistik in Kotlin einige weitere Codezeilen. Vielleicht ist dies einer der wenigen oder vielleicht der einzige Fall, in dem Sie mehr in Kotlin schreiben müssen. Man gewöhnt sich jedoch schnell daran.
Ich denke, dass Sie in Projekten, in denen Kotlin- und Java-Code gemeinsam genutzt werden, flexibel an die Wahl der verwendeten Sprache herangehen können. Zum Beispiel scheint mir Java besser zum Speichern von Konstanten geeignet zu sein. Aber auch hier lohnt es sich, wie in vielen anderen Dingen, sich vom gesunden Menschenverstand und den Regeln für das Schreiben von Code im Projekt leiten zu lassen.
Und am Ende des Artikels finden Sie solche Informationen. Vielleicht wird Kotlin auch in Zukunft noch einen statischen Modifikator haben, der viele Probleme beseitigt und das Leben der Entwickler erleichtert, und der Code ist kürzer. Ich habe diese Annahme getroffen, indem ich den entsprechenden Text in Absatz 17 der
Kotlin-Funktionsbeschreibungen gefunden habe .
Dieses Dokument stammt zwar aus dem Mai 2017 und ist auf dem Hof bereits Ende 2018.
Das ist alles für mich. Ich denke, dass das Thema detailliert aussortiert wurde. Fragen schreiben Sie in den Kommentaren.