Kotlin:不存在的静态


本文将讨论在Kotlin中使用静态方法。
让我们开始吧。
Kotlin没有静电!

官方文档中对此进行了说明。

看来这可以完成本文。 但是,让我怎么做? 毕竟,如果您将Java代码插入Android Studio中的Kotlin文件中,那么智能转换器将发挥作用,将所有内容以正确的语言转换为代码,然后它将起作用! 但是如何与Java完全兼容呢?

此时,任何了解Kotlin缺乏静态性的开发人员都将进入文档和论坛来解决此问题。 让我们沉思和艰苦地相聚。 在本文结尾处,我将尽量减少问题。

Java中的静态是什么? 有:
  • 类静态字段
  • 静态类方法
  • 静态嵌套类


让我们做一个实验(这是我想到的第一件事)。

创建一个简单的Java类:
public class SimpleClassJava1 { public static String staticField = "Hello, static!"; public static void setStaticValue (String value){ staticField = value; } } 

一切都很容易:在类中,我们创建了一个静态字段和一个静态方法。 我们公开进行所有操作,以进行外部访问的实验。 我们在逻辑上连接字段和方法。

现在创建一个空的Kotlin类,并尝试将SimpleClassJava1类的所有内容复制到其中。 我们对转换产生的问题回答“是”,然后看看发生了什么:

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

似乎这并不是我们真正需要的。为了确保这一点,我们将把此类的字节码转换为Java代码,然后看看发生了什么:
 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; } } 

是的 一切都看起来一样。 这里没有静电的味道。 转换器简单地将签名中的static修饰符斩掉,好像它不在那里一样。 以防万一,我们将立即得出一个结论:不要盲目地信任转换器,有时它会带来不愉快的惊喜。

顺便说一下,大约六个月前,将相同的Java代码转换为Kotlin会显示出稍微不同的结果。 再说一遍:注意自动转换!

我们做进一步的实验。

我们进入Kotlin上的任何类,并尝试在其中调用Java类的静态元素:
 SimpleClassJava1.setStaticValue("hi!") SimpleClassJava1.staticField = "hello!!!" 

这是怎么回事! 一切都被完美调用,甚至代码的自动完成也告诉了我们一切! 很好奇

现在,让我们继续进行更重要的部分。 确实,Kotlin的创造者决定以我们习惯使用它的形式摆脱静态。 为什么我们这样做,而我们不作其他讨论-网络中对此主题存在很多争议和意见。 我们将弄清楚如何使用它。 自然,我们不仅被剥夺了静电。 Kotlin为我们提供了一套可以弥补损失的工具。 它们适合在室内使用。 并承诺与Java代码完全兼容。 走吧

您可以意识到并开始使用的最快,最简单的方法是我们提供的替代方法,而不是静态方法-包级函数。 这是什么 这是一个不属于任何类的函数。 也就是说,这种逻辑在封装空间的某个地方处于真空中。 我们可以在包中感兴趣的任何文件中对其进行描述。 例如,将此文件命名为JustFun.kt并将其放在包com.example.mytestapplication
 package com.example.mytestapplication fun testFun(){ // some code } 


用Java转换此文件的字节码,并查看内部:
 public final class JustFunKt { public static final void testFun() { // some code } } 

我们看到在Java中创建了一个类,该类的名称考虑了描述函数的文件的名称,并且函数本身变成了静态方法。

现在,如果我们想从位于package com.example.mytestapplication (即与该函数相同的包)中的一个类(或相同的函数)调用Kotlin中的testFun函数,那么我们可以简单地访问它而无需其他技巧。 如果我们从另一个包中调用它,那么我们必须导入,这对我们来说是熟悉的,并且通常适用于类:
 import com.example.pavka.mytestapplication.testFun 

如果我们谈论从Java代码调用函数t estFun ,那么无论我们从哪个包调用它,我们总是需要导入该函数:
 import static com.example.pavka.mytestapplication.ForFunKt.testFun; 

该文档说,在大多数情况下,使用静态的方法而不是静态的方法,对于我们来说,使用包级函数就足够了。 但是,以我个人的观点(不一定与其他所有人的观点一致),这种实现静态的方法仅适用于小型项目。
事实证明,这些函数并不明确地属于任何类。 从视觉上看,它们的调用类似于对我们所位于的类方法(或其父类)的调用,有时可能会造成混淆。 好吧,最重要的是-包中只有一个具有该名称的函数。 即使我们尝试在另一个文件中创建相同名称的功能,系统也会给我们一个错误。 如果我们谈论大型项目,那么经常会有例如拥有相同名称的静态方法的不同工厂。

让我们看一下实现静态方法和字段的其他方法。

回想一下类的静态字段是什么。 类的此字段属于在其中声明该类的类,但不属于该类的特定实例,即,它是在整个类的单个实例中创建的。

为了达到这些目的,Kotlin为我们提供了一些额外的实体,这些实体也存在于单个副本中。 换句话说,单身人士。

Kotlin有一个object关键字来声明单调。

 object MySingltoneClass { // some code } 


此类对象被延迟初始化,即在第一次调用它们时。

好的,Java中也有单调,统计在哪里?

对于Kotlin中的任何类,我们都可以创建一个伴随对象或伴随对象。 与特定班级相关的单身人士。 可以同时使用2个companion object关键字companion object关键字来完成此操作:

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


在这里,我们有SimpleClassKotlin1类,在其中我们使用object关键字声明了一个单例,并将其绑定到随同关键字在其中声明它的对象。 在这里,您需要注意一个事实,与之前的单例声明(MySingltoneClass)不同,该单例类的名称未指明。 如果对象被声明为同伴,则不允许其名称。 然后它将自动命名为Companion 。 如有必要,我们可以通过以下方式获取伴随类的实例:
 val companionInstance = SimpleClassKotlin1.Companion 

但是,可以通过对附加类的属性和方法的调用来直接对其进行调用:
 SimpleClassKotlin1.companionField SimpleClassKotlin1.companionFun("Hi!") 

它看起来很像调用静态字段和类,对吗?

如有必要,我们可以给同伴类起一个名字,但是实际上很少这样做。 在伴随类的有趣特征中,应该指出的是,与任何普通类一样,它可以实现接口,有时可以帮助我们向代码添加更多顺序:

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


同伴班只能有一个班。 但是,没有人禁止我们在该类内声明任何数量的单例对象,但是在这种情况下,我们必须显式指定该类的名称,并因此在引用该类的字段和方法时指定该名称。

说到声明为对象的类,可以说我们还可以在其中声明嵌套对象,但是不能在其中声明伴侣对象。

是时候看起来“在幕后”了。 参加我们的简单课程:

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


现在在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(); } } } 

我们看看发生了什么。

伴随对象的属性表示为我们类的静态字段:
 private static String companionField = "Hello!"; 


这似乎正是我们想要的。 但是,此字段是私有的,可以通过我们的伴随类的getter和setter访问,该同名类在此处作为public static final class呈现,其实例作为常量呈现:
 public static final SimpleClassKotlin1.Companion Companion = new SimpleClassKotlin1.Companion((DefaultConstructorMarker)null); 


partnerFun函数并未成为我们类的静态方法(可能不会)。 它仍然是在SimpleClassKotlin1类中初始化的单例的功能。 但是,如果您考虑一下,那么从逻辑上讲这是同一件事。

对于OneMoreObjectOneMoreObject情况非常相似。 值得一提的是,与同伴不同,这里的类值字段没有移到SimpleClassKotlin1类,而是保留在OneMoreObject ,而且变为静态并接收生成的getter和setter。

让我们尝试理解以上所有内容。
如果我们想在Kotlin中实现静态字段或类方法,那么我们应该使用在此类内部声明的伴随对象。
从Kotlin调用这些字段和函数看起来与在Java中调用静态函数完全相同。 但是,如果我们尝试用Java调用这些字段和函数呢?

自动完成功能告诉我们可以进行以下调用:
 SimpleClassKotlin1.Companion.companionFun("hello!"); SimpleClassKotlin1.Companion.setCompanionField("hello!"); SimpleClassKotlin1.Companion.getCompanionField(); 

也就是说,在这里我们从直接指示同伴的名字开始别无他法。 因此,此处使用分配给默认伴随对象的名称。 不是很方便吧?

尽管如此,Kotlin的创建者还是有可能使它在Java中看起来更加熟悉。 有几种方法可以做到这一点。
 @JvmField var companionField = "Hello!" 

如果将这个注释应用于companionField对象的companionField字段,则在将字节码转换为Java时,我们会看到静态字段companionField字段SimpleClassKotlin1不再是私有的,而是公共的,并且companionField字段的getter和setter在静态Companion类中消失了。 现在,我们可以以通常的方式从Java代码访问companionField

第二种方法是为伴随lateinit属性(具有较晚初始化的属性)指定lateinit修饰符。 不要忘记,这仅适用于var-properties,并且其类型应为非null且不应为原始类型。 好吧,不要忘记处理此类属性的规则。

 lateinit var lateinitField: String 

还有一种方法:我们可以通过指定修饰符const来将伴随对象的属性声明为常量。 很容易猜到这应该是一个val属性。
 const val myConstant = "CONSTANT" 

在每种情况下,生成的Java代码将包含通常的公共静态字段,对于const,此字段也将是最终的。 当然,值得理解的是,这3种情况中的每一种都有其逻辑目的,并且只有第一种是专门为易于Java使用而设计的,其余的都像在负载中一样获得了这种“优惠”。

应当单独注意,const修饰符可用于对象,伴随对象的属性以及包级别的属性。 在后一种情况下,我们获得与使用包级功能相同的条件,并且具有相同的限制。 Java代码是使用类中的静态公共字段生成的,其名称考虑了我们在其中描述了常量的文件的名称。 一个包只能有一个具有指定名称的常量。

如果我们希望在生成Java代码时也将伴随对象的功能转换为静态方法,那么我们需要为此功能应用@JvmStatic批注。
也可以将@JvmStatic注释应用于伴随对象(仅单例对象)的属性。 在这种情况下,该属性不会变成静态字段,但是会为此属性生成一个静态的getter和setter。 为了更好的理解,请看以下Kotlin类:
 class SimpleClassKotlin1 { companion object{ @JvmStatic fun companionFun (vaue: String){ } @JvmStatic var staticField = 1 } } 


在这种情况下,以下调用对Java有效:
 int x; SimpleClassKotlin1.companionFun("hello!"); x = SimpleClassKotlin1.getStaticField(); SimpleClassKotlin1.setStaticField(10); SimpleClassKotlin1.Companion.companionFun("hello"); x = SimpleClassKotlin1.Companion.getStaticField(); SimpleClassKotlin1.Companion.setStaticField(10); 


以下通话在Kotlin中有效:
 SimpleClassKotlin1.companionFun("hello!") SimpleClassKotlin1.staticField SimpleClassKotlin1.Companion.companionFun("hello!") SimpleClassKotlin1.Companion.staticField 


很明显,对于Java,您应该使用前3个,对于Kotlin,请使用前2个。其余调用仅是有效的。

现在有待澄清后者。 静态嵌套类呢? 这里的一切都很简单-Kotlin中此类的类比是没有修饰符的常规嵌套类:
 class SimpleClassKotlin1 { class LooksLikeNestedStatic { } } 


将字节码转换为Java后,我们看到:
 public final class SimpleClassKotlin1 { public static final class LooksLikeNestedStatic { } } 


确实,这就是我们所需要的。 如果我们不希望类为最终类,那么在Kotlin代码中,我们为其指定open修饰符。 我记得这是为了以防万一。

我想你可以总结一下。 确实,正如所说的那样,在科特林本身中,我们习惯于看到它的形式没有任何静态。 但是建议的工具集允许我们在生成的Java代码中实现所有类型的静态函数。 还提供了与Java的完全兼容性,并且我们可以直接从Kotlin调用Java类的静态字段和方法。
在大多数情况下,在Kotlin中实现统计信息需要多几行代码。 当您需要用Kotlin编写更多文章时,这也许是少数几种,或者也许是唯一的情况。 但是,您很快就习惯了。
我认为在Kotlin和Java代码共享的项目中,您可以灵活地选择所用语言。 例如,在我看来,Java更适合存储常量。 但是在这里,与其他许多方面一样,值得以常识和项目中编写代码的规则为指导。

在本文的结尾,有这样的信息。 也许将来在Kotlin中会出现static修饰符,从而消除了许多问题,并使开发人员的工作更轻松,代码更短。 我通过在Kotlin功能说明的第17段中找到适当的文字来做出此假设。
是的,这份文件的日期是2017年5月,而院子里已经是2018年底了。

这就是我的全部。 我认为该主题已得到详细整理。 问题写在评论中。

Source: https://habr.com/ru/post/zh-CN430836/


All Articles