Kotlin: statique qui n'existe pas


Cet article parlera de l'utilisation de la statique dans Kotlin.
Commençons.
Kotlin n'a pas de statique!

Ceci est indiqué dans la documentation officielle.

Et il semblerait que cela pourrait terminer l'article. Mais laissez-moi, comment ça? Après tout, si dans Android Studio vous insérez du code Java dans un fichier Kotlin, le convertisseur intelligent fera la magie, transformera tout en code dans le bon langage et cela fonctionnera! Mais qu'en est-il de la compatibilité totale avec Java?

À ce stade, tout développeur, se renseignant sur le manque de statique dans Kotlin, ira dans la documentation et les forums pour traiter ce problème. Rassemblons-nous, pensivement et minutieusement. J'essaierai de garder le moins de questions possible d'ici la fin de cet article.

Quelle est la statique en Java? Il y a:
  • champs statiques de classe
  • méthodes de classe statique
  • classes imbriquées statiques


Faisons une expérience (c'est la première chose qui me vient à l'esprit).

Créez une classe Java simple:
public class SimpleClassJava1 { public static String staticField = "Hello, static!"; public static void setStaticValue (String value){ staticField = value; } } 

Tout est facile ici: dans la classe, nous créons un champ statique et une méthode statique. Nous faisons tout publiquement pour des expériences avec accès depuis l'extérieur. Nous relions logiquement le terrain et la méthode.

Créez maintenant une classe Kotlin vide et essayez d'y copier tout le contenu de la classe SimpleClassJava1. Nous répondons «oui» à la question qui en résulte sur la conversion et voyons ce qui s'est passé:

 class SimpleClassKotlin1 { var staticField = "Hello, static!" fun setStaticValue(value: String) { staticField = value } } 

Il semble que ce ne soit pas exactement ce dont nous avons besoin ... Pour nous en assurer, nous allons convertir le bytecode de cette classe en code Java et voir ce qui s'est passé:
 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; } } 

Oui Tout est exactement comme il semblait. Ça ne sent pas l'électricité statique ici. Le convertisseur a simplement coupé le modificateur statique dans la signature, comme s'il n'était pas là. Juste au cas où, nous tirerons immédiatement une conclusion: ne faites pas aveuglément confiance au convertisseur, il peut parfois apporter des surprises désagréables.

Soit dit en passant, il y a environ six mois, la conversion du même code Java en Kotlin aurait donné un résultat légèrement différent. Encore une fois: soyez prudent avec la conversion automatique!

Nous expérimentons davantage.

Nous allons dans n'importe quelle classe sur Kotlin et essayons d'y appeler les éléments statiques de la classe Java:
 SimpleClassJava1.setStaticValue("hi!") SimpleClassJava1.staticField = "hello!!!" 

Voici comment! Tout est parfaitement appelé, même l'auto-complétion du code nous dit tout! Assez curieux.

Passons maintenant à la partie la plus substantielle. En effet, les créateurs de Kotlin ont décidé de s'éloigner du statique sous la forme dans laquelle nous sommes habitués à l'utiliser. Pourquoi nous avons fait exactement cela et nous ne discuterons pas autrement - il y a beaucoup de différends et d'opinions à ce sujet dans le réseau. Nous allons juste découvrir comment vivre avec. Naturellement, nous n'avons pas seulement été privés de statique. Kotlin nous donne un ensemble d'outils avec lesquels nous pouvons compenser les pertes. Ils conviennent à une utilisation en intérieur. Et la pleine compatibilité promise avec le code Java. C'est parti!

La chose la plus rapide et la plus simple que vous pouvez réaliser et commencer à utiliser est l'alternative qui nous est proposée au lieu des méthodes statiques - les fonctions au niveau du package. Qu'est ce que c'est Il s'agit d'une fonction qui n'appartient à aucune classe. Autrement dit, ce type de logique qui est dans le vide quelque part dans l'espace du package. Nous pouvons le décrire dans n'importe quel fichier du package qui nous intéresse. Par exemple, nommez ce fichier JustFun.kt et placez-le dans le package com.example.mytestapplication
 package com.example.mytestapplication fun testFun(){ // some code } 


Convertissez le bytecode de ce fichier en Java et regardez à l'intérieur:
 public final class JustFunKt { public static final void testFun() { // some code } } 

On voit qu'en Java se crée une classe dont le nom prend en compte le nom du fichier dans lequel la fonction est décrite, et la fonction elle-même se transforme en méthode statique.

Maintenant, si nous voulons appeler la fonction testFun dans Kotlin à partir d'une classe (ou de la même fonction) située dans le package package com.example.mytestapplication (c'est-à-dire le même package que la fonction), nous pouvons simplement y accéder sans astuces supplémentaires. Si nous l'appelons à partir d'un autre package, nous devons importer, ce qui nous est familier et généralement applicable aux classes:
 import com.example.pavka.mytestapplication.testFun 

Si nous parlons d'appeler la fonction t estFun partir du code Java, nous devons toujours importer la fonction, quel que soit le package à partir duquel nous l'appelons:
 import static com.example.pavka.mytestapplication.ForFunKt.testFun; 

La documentation indique que dans la plupart des cas, au lieu de méthodes statiques, il nous suffit d'utiliser des fonctions au niveau du package. Cependant, à mon avis personnel (qui ne doit pas nécessairement coïncider avec l'avis de tout le monde), cette méthode de mise en œuvre de la statique ne convient qu'aux petits projets.
Il s'avère que ces fonctions n'appartiennent explicitement à aucune classe. Visuellement, leur appel ressemble à un appel à la méthode de classe (ou à son parent) dans laquelle nous nous trouvons, ce qui peut parfois prêter à confusion. Eh bien et l'essentiel - il ne peut y avoir qu'une seule fonction avec ce nom dans le package. Même si nous essayons de créer la fonction du même nom dans un autre fichier, le système nous donnera une erreur. Si nous parlons de grands projets, nous avons souvent, par exemple, différentes usines ayant des méthodes statiques du même nom.

Examinons d'autres alternatives pour implémenter des méthodes et des champs statiques.

Rappelez-vous ce qu'est le champ statique d'une classe. Ce champ de la classe appartient à la classe dans laquelle il est déclaré, mais n'appartient pas à une instance spécifique de la classe, c'est-à-dire qu'il est créé en une seule instance pour la classe entière.

Kotlin nous propose à ces fins d'utiliser une entité supplémentaire, qui existe également en un seul exemplaire. En d'autres termes, singleton.

Kotlin a un mot-clé objet pour déclarer les singletones.

 object MySingltoneClass { // some code } 


De tels objets sont initialisés paresseusement, c'est-à-dire au moment du premier appel à eux.

Ok, il y a aussi des singletones en Java, où sont les statistiques?

Pour n'importe quelle classe dans Kotlin, nous pouvons créer un compagnon ou un objet compagnon. Un singleton lié à une classe spécifique. Cela peut être fait en utilisant deux mots clés companion object ensemble:

 class SimpleClassKotlin1 { companion object{ var companionField = "Hello!" fun companionFun (vaue: String){ // some code } } } 


Nous avons ici la classe SimpleClassKotlin1 , à l'intérieur de laquelle nous déclarons un singleton avec le mot-clé object et le lions à l'objet à l'intérieur duquel il est déclaré avec le mot-clé compagnon. Ici, vous pouvez faire attention au fait que, contrairement à la précédente déclaration singleton (MySingltoneClass), le nom de la classe singleton n'est pas indiqué. Si l'objet est déclaré compagnon, il est autorisé de ne pas indiquer son nom. Ensuite, il sera automatiquement nommé Companion . Si nécessaire, nous pouvons obtenir une instance de la classe compagnon de cette façon:
 val companionInstance = SimpleClassKotlin1.Companion 

Cependant, un appel aux propriétés et méthodes d'une classe compagnon peut se faire directement, via un appel à la classe à laquelle elle est attachée:
 SimpleClassKotlin1.companionField SimpleClassKotlin1.companionFun("Hi!") 

Cela ressemble déjà beaucoup à l'appel de champs et de classes statiques, non?

Si nécessaire, nous pouvons donner un nom à la classe compagnon, mais en pratique, cela est très rarement fait. Parmi les caractéristiques intéressantes des classes d'accompagnement, on peut noter que, comme toute classe ordinaire, elle peut implémenter des interfaces, ce qui peut parfois nous aider à ajouter un peu plus d'ordre au code:

 interface FactoryInterface<T> { fun factoryMethod(): T } class SimpleClassKotlin1 { companion object : FactoryInterface<MyClass> { override fun factoryMethod(): MyClass = MyClass() } } 


Une classe compagnon ne peut avoir qu'une seule classe. Cependant, personne ne nous interdit de déclarer un nombre quelconque d'objets singleton à l'intérieur de la classe, mais dans ce cas, nous devons explicitement spécifier le nom de cette classe et, en conséquence, indiquer ce nom en faisant référence aux champs et à la méthode de cette classe.

En parlant de classes déclarées comme objet, nous pouvons dire que nous pouvons également y déclarer des objets imbriqués, mais nous ne pouvons pas y déclarer d'objets compagnons.

Il est temps de regarder "sous le capot". Prenez notre cours simple:

 class SimpleClassKotlin1 { companion object{ var companionField = "Hello!" fun companionFun (vaue: String){ } } object OneMoreObject { var value = 1 fun function(){ } } 


Décompilez maintenant son bytecode en 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() { } // $FF: synthetic method public Companion(DefaultConstructorMarker $constructor_marker) { this(); } } } 

Nous regardons ce qui s'est passé.

La propriété de l'objet compagnon est représentée comme un champ statique de notre classe:
 private static String companionField = "Hello!"; 


Cela semble être exactement ce que nous voulions. Cependant, ce champ est privé et accessible via le getter et le setter de notre classe compagnon, qui est présenté ici comme une public static final class , et son instance est présentée comme une constante:
 public static final SimpleClassKotlin1.Companion Companion = new SimpleClassKotlin1.Companion((DefaultConstructorMarker)null); 


La fonction compagnonFun n'est pas devenue la méthode statique de notre classe (probablement pas). Il restait la fonction d'un singleton initialisé dans la classe SimpleClassKotlin1. Cependant, si vous y réfléchissez, il s'agit logiquement de la même chose.

Avec la classe OneMoreObject situation est très similaire. Il convient de noter seulement qu'ici, contrairement au compagnon, le champ de la classe de valeur n'est pas passé à la classe SimpleClassKotlin1 , mais est resté dans OneMoreObject , mais est également devenu statique et a reçu le getter et le setter générés.

Essayons de comprendre tout ce qui précède.
Si nous voulons implémenter des champs statiques ou des méthodes de classe dans Kotlin, alors pour cela nous devons utiliser l'objet compagnon déclaré à l'intérieur de cette classe.
L'appel de ces champs et fonctions à partir de Kotlin ressemblera exactement à l'appel de la statique en Java. Mais que faire si nous essayons d'appeler ces champs et fonctions en Java?

La saisie semi-automatique nous indique que les appels suivants sont disponibles:
 SimpleClassKotlin1.Companion.companionFun("hello!"); SimpleClassKotlin1.Companion.setCompanionField("hello!"); SimpleClassKotlin1.Companion.getCompanionField(); 

Autrement dit, ici, nous n'allons nulle part en indiquant directement le nom du compagnon. Par conséquent, le nom attribué à l'objet compagnon par défaut est utilisé ici. Pas très pratique, non?

Néanmoins, les créateurs de Kotlin ont permis de le rendre plus familier en Java. Et il existe plusieurs façons de procéder.
 @JvmField var companionField = "Hello!" 

Si nous appliquons cette annotation au champ companionField de notre objet companionField , puis lors de la conversion de code d'octet en Java, nous constatons que le champ statique companionField SimpleClassKotlin1 n'est plus privé, mais public, et le getter et le setter pour compagnonField sont passés dans la classe Companion statique. Maintenant, nous pouvons accéder à companionField partir du code Java de la manière habituelle.

La deuxième façon consiste à spécifier un modificateur lateinit pour les propriétés de l' lateinit compagnon, propriétés avec initialisation tardive. N'oubliez pas que cela s'applique uniquement aux propriétés var et que son type doit être non nul et ne doit pas être primitif. Eh bien, n'oubliez pas les règles de gestion de ces propriétés.

 lateinit var lateinitField: String 

Et encore une façon: nous pouvons déclarer la propriété de l'objet compagnon une constante en spécifiant le mod const. Il est facile de deviner qu'il doit s'agir d'une propriété val.
 const val myConstant = "CONSTANT" 

Dans chacun de ces cas, le code Java généré contiendra le champ statique public habituel, dans le cas de const, ce champ sera également définitif. Bien sûr, il vaut la peine de comprendre que chacun de ces 3 cas a son propre objectif logique, et seul le premier d'entre eux est conçu spécifiquement pour être facile à utiliser avec Java, les autres obtiennent ce "chignon" comme s'il était chargé.

Il convient de noter séparément que le modificateur const peut être utilisé pour les propriétés des objets, des objets compagnons et pour les propriétés du niveau de package. Dans ce dernier cas, nous obtenons la même chose que l'utilisation des fonctions au niveau du package et avec les mêmes restrictions. Le code Java est généré avec un champ public statique dans la classe, dont le nom prend en compte le nom du fichier dans lequel nous avons décrit la constante. Un package ne peut avoir qu'une seule constante avec le nom spécifié.

Si nous voulons que la fonction de l'objet compagnon soit également convertie en une méthode statique lors de la génération de code Java, nous devons pour cela appliquer l'annotation @JvmStatic à cette fonction.
Il est également possible d'appliquer l'annotation @JvmStatic aux propriétés des objets compagnons (et uniquement des objets singleton). Dans ce cas, la propriété ne se transformera pas en champ statique, mais un getter et un setter statiques pour cette propriété seront générés. Pour une meilleure compréhension, regardez cette classe Kotlin:
 class SimpleClassKotlin1 { companion object{ @JvmStatic fun companionFun (vaue: String){ } @JvmStatic var staticField = 1 } } 


Dans ce cas, les appels suivants sont valides depuis Java:
 int x; SimpleClassKotlin1.companionFun("hello!"); x = SimpleClassKotlin1.getStaticField(); SimpleClassKotlin1.setStaticField(10); SimpleClassKotlin1.Companion.companionFun("hello"); x = SimpleClassKotlin1.Companion.getStaticField(); SimpleClassKotlin1.Companion.setStaticField(10); 


Les appels suivants sont valables depuis Kotlin:
 SimpleClassKotlin1.companionFun("hello!") SimpleClassKotlin1.staticField SimpleClassKotlin1.Companion.companionFun("hello!") SimpleClassKotlin1.Companion.staticField 


Il est clair que pour Java, vous devez utiliser les 3 premiers et pour Kotlin les 2 premiers. Le reste des appels est juste valide.

Reste maintenant à clarifier ce dernier. Qu'en est-il des classes imbriquées statiques? Tout est simple ici - l'analogue d'une telle classe dans Kotlin est une classe imbriquée régulière sans modificateurs:
 class SimpleClassKotlin1 { class LooksLikeNestedStatic { } } 


Après avoir converti le bytecode en Java, nous voyons:
 public final class SimpleClassKotlin1 { public static final class LooksLikeNestedStatic { } } 


En effet, c'est ce dont nous avons besoin. Si nous ne voulons pas que la classe soit finale, alors dans le code Kotlin, nous spécifions le modificateur open pour elle. Je m'en suis souvenu juste au cas où.

Je pense que vous pouvez résumer. En effet, dans Kotlin lui-même, comme on l'a dit, il n'y a pas de statique dans la forme sous laquelle nous sommes habitués à le voir. Mais l'ensemble d'outils proposé nous permet d'implémenter tous les types de statiques dans le code Java généré. Une compatibilité totale avec Java est également fournie, et nous pouvons appeler directement les champs statiques et les méthodes des classes Java à partir de Kotlin.
Dans la plupart des cas, l'implémentation d'une statistique dans Kotlin nécessite quelques lignes de code supplémentaires. C'est peut-être l'un des rares, ou peut-être le seul cas où vous avez besoin d'écrire plus en Kotlin. Cependant, vous vous y habituez rapidement.
Je pense que dans les projets où Kotlin et le code Java sont partagés, vous pouvez aborder avec souplesse le choix du langage utilisé. Par exemple, il me semble que Java est plus adapté au stockage de constantes. Mais ici, comme dans bien d'autres choses, il vaut la peine d'être guidé par le bon sens et les règles d'écriture du code dans le projet.

Et à la fin de l'article, voici ces informations. Peut-être qu'à l'avenir, Kotlin aura toujours un modificateur statique qui élimine beaucoup de problèmes et facilite la vie des développeurs, et le code est plus court. J'ai fait cette hypothèse en trouvant le texte approprié au paragraphe 17 des descriptions des fonctionnalités de Kotlin .
Certes, ce document date de mai 2017, et dans la cour est déjà fin 2018.

C’est tout pour moi. Je pense que le sujet a été réglé en détail. Des questions sont écrites dans les commentaires.

Source: https://habr.com/ru/post/fr430836/


All Articles