Kotlin——伴生对象

学习kotlin是上个月的事了,自己当时也没有记笔记,发现等开始写项目的时候又不会用了。所以对一些模糊的点,比较难的点还是总结一下,加深印象,还利于后面复习。

伴生对象:工厂方法和静态成员

这个是从来没有在java中有的一个概念。

Kotlin中的类不能拥有静态成员。java的static关键字并不是kotlin语言的一部分。可以替代的是kotlin中依赖包级别函数和对象声明。大多数情况下,推荐顶层函数。但是顶层函数不能访问类的private成员,因此当我们需要写一个可以在没有类实例的情况下调用但是需要访问类内部的函数,可以将其写成需要类中的对象声明的成员。就想到了静态工厂方法。

类内部的对象声明可以用companion关键字标记。这样就获得了直接通过容器类名称来访问这个对象的方法和属性的能力。不需要显示地指明对象的名称。看起来很像java中的静态方法调用。例子如下:

class MyClass{
    companion object Factory{
        fun create():MyClass = MyClass()
    }
}

该伴生对象的成员可通过只使用类名作为限定符来调用:

val instance = MyClass.create()
//等价于
val instance = MyClass.Factory.create()

可以省略伴生对象的名称,在这种情况下将使用名称:Companion:

class MyClass{
    companion object{ }
}
val x = MyClass.Companion

其自身所用的类的名称可用作该类的伴生对象的引用:

class MyClass1{
    companion object Named{ }
}
val x = MyClass1

class MyClass2{
    companion object{ }
}
val y = MyClass2

伴生对象的成员虽然看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员,而且还可以实现如下的接口:

interface Factory{
    fun create():T
}

class MyClass{
    companion object : Factory{
        override fun create():MyClass = MyClass()
        
        var name = "default"
        val myClass = MyClass()
        fun sayClass(){
            print("hello say")
        }
    }
}

fun main(arg:Array){
    val f:Factory = MyClass
}

刚开始单看这段代码获取有些云里雾里的,不如就把它转换成我们最为熟悉的java代码去看看,就会一目了然了。编译器自带的工具很好用。

//Factory接口
public interface Factory {
   Object create();
}

public final class MyClass {
   @NotNull
   private static String name = "default";
   @NotNull
   private static final MyClass myClass = new MyClass();
    //伴生对象在外部类中被定义为静态字段且不可修改
   public static final MyClass.Companion Companion = new MyClass.Companion((DefaultConstructorMarker)null);
    
    //没有定义伴生对象的名字默认为Companion
    public static final class Companion implements Factory {
      @NotNull
      public MyClass create() {
         return new MyClass();
      }

      // $FF: synthetic method
      // $FF: bridge method
      public Object create() {
         return this.create();
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
      
      @NotNull
      public final String getName() {
         return MyClass.name;
      }

      public final void setName(@NotNull String var1) {
         Intrinsics.checkParameterIsNotNull(var1, "");
         MyClass.name = var1;
      }

      @NotNull
      public final MyClass getMyClass() {
         return MyClass.myClass;
      }

      public final void sayClass() {
         String var1 = "hello say";
         System.out.print(var1);
      }
   }  
}

public final class TestKt {
   public static final void main(@NotNull String[] arg) {
      Intrinsics.checkParameterIsNotNull(arg, "arg");
      //实例化MyClass对象的时候本质是MyClass.Companion
      Factory f = (Factory)MyClass.Companion;
   }
}

从kotlin代码编译为java代码后可以知道:

  • 使用伴生对象实际上是在这个类内部创建了一个名为 Companion的静态单例内部类
  • 伴生对象中定义的属性会直接编译为外部类的私有静态字段,var和val的区别就是无有final
  • 函数会被编译为伴生对象的方法

在jvm平台,如果使用@JvmStatic注解,你可以将伴生对象的成员生成为真正的静态方法和字段。

伴生对象的扩展

如果一个类定义有一个伴生对象,也可以为伴生对象定义扩展函数与属性,就像伴生对象的常规成员一样,可以只使用类名作为限定符来调用伴生对象的扩展成员:

class MyClass{
    companion object{ }
}

fun MyClass.Companion.printCompanion(){
    print("companion")
}

fun main(){
    MyClass.printCompanion()
}

伴生对象在工厂方法中的应用

在学习kotlin中最大的感受就是语法简单了很多,很多东西都可以代替java,但是其实和java的区别也比较大,要理解其中的原理先去学习每种语法在平常写代码的时候可以在哪些地方使用。

伴生对象可以访问类中的所有private,包括private构造方法,它是实现工厂模式的理想选择。

简单来说,在平常的开发中我们创建一个对象,因为不同的参数就会写很多个不同参数的构造方法。在构造方法很多的情况下我们就需要查api,看每个参数对应的含义是什么。因此为了解决这种情况,我们根据不同情况把创建对象包装起来,调用根据此功能命名的方法。代码如下:

  • 定义一个拥有多个从构造方法的类
class User{

    val nickName : String
    
    constructor(name:String){
        nickName = name
    }
    
    constructor(qqEmail:String){
        nickName = getNameFromQq(qqEmail)
    }
    
    constructor(weChact:String){
        nickName = getNameFromWechat(weChat)
    }
}

这里的功能就是在创建用户昵称的时候,根据不同的登录方式在本地进行处理,生成新的用户名。

  • 使用工厂方法来代替从构造方法
//主构造方法标记为私有
class User private constructor(val nickName:String){
    companion object{
        fun createNameFromLocal(name:String){
            User(name)
        }
        
        fun createNameFromQq(qqEmail:String){
            User(getNameFromQq(qqEmail))
        }
        
        fun createNameFromWechat(weChact:String){
            User(getNameFromWechat(weChat))
        }
    }
}

现在就可以通过用途来创建不同的用户。

参考文章

kotlin 顶层函数和扩展函数

kotlin中文网站 扩展篇

伴生对象

你可能感兴趣的:(Kotlin——伴生对象)