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<T>{
    fun create():T
}

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

fun main(arg:Array<String>){
    val f:Factory<MyClass> = 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, "<set-?>");
         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中文网站 扩展篇

伴生对象

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358