Kotlin单例&伴生对象&静态成员

单例(object)

Kotlin 中有一种特殊的类,它本身也是一个实例(单例),这种既是类又是对象的类需要使用object关键字声明(普通类声明使用class),它跟普通类一样,也可以实现接口和继承父类:

object MusicPlayer : Player(), OnStatusChangeListener {

    var state: Int = 0

    fun play(url: String) {
        ...
    }

    fun stop() {
        ...
    }

    override fun onMount(driver: Driver) {
    }

    override fun onUnmount(driver: Driver) {
    }
}

这种object类的方法可以通过类名直接调用:

fun main() {
    MusicPlayer.play("http://qqmusic.com/123213.mp3")
    MusicPlayer.stop()
}

借助Show Kotlin Bytecode工具,将上述 Kotlin 代码反编译成 Java 代码如下:

public final class MusicPlayer extends Player implements OnStatusChangeListener {
   public static final MusicPlayer INSTANCE;

   private MusicPlayer() {
   }

   static {
      MusicPlayer var0 = new MusicPlayer();
      INSTANCE = var0;
   }
   ...
}

可以发现,Kotlin 中的object本质上就是 Java 中简单的单例模式,同时它的构造方法是 private 的,因此object类不能自定义构造方法!

伴生对象(companion object)

日常开发中,经常会编写一些 Bean 类,用于描述一类事物,Bean 中会包含事物的属性,有的 Bean 类还会提供一系列"工具方法"来帮助开发者快速创建 Bean 对象,就比如在 Java 中,Integer.valueOf() 就可以帮助开发者快速将字符串转成整型,进入源码一看,可以发现这是一个使用了 static 关键字修饰的静态方法:

public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
}

我们知道,静态变量和静态函数在Java中有很大的作用,因为Java没有包级变量和包级函数,一切变量和函数都是在类里面,所以在写一些工具函数和全局变量时,都需要用到static关键字来修饰。
可是,在 Kotlin 中是没有static关键字的!!!
为什么没有?主要原因在于它允许包级变量和函数的存在。
想要实现通过类名.xxx()的方式调用工具方法,可以使用上述的object类,但object本身就是一个对象,我们无法在外部调用其构造方法,既然想拥有object类(通过类名调用方法)与class类(外部可以调用构造方法)的两种特性,这时伴生对象companion object就完全可以满足这个需求:

class Rectangle(val width: Int, val height: Int) {
    companion object {
        fun ofSize(width: Int, height: Int): Rectangle {
            return Rectangle(width, height)
        }

        fun ofRectangle(rectangle: Rectangle): Rectangle {
            return Rectangle(rectangle.width, rectangle.height)
        }
    }
}

fun main() {
    val width = 4
    val height = 5
    val rectangle1 = Rectangle(width, height) // 调用构造方法
    val rectangle2 = Rectangle.ofRectangle(rectangle1) // 通过类名调用方法
    val rectangle3 = Rectangle.ofSize(width, height) // 通过类名调用方法
}

伴生对象顾名思义,就是与类一起诞生的对象,类一加载,它的伴生对象也就被创建了,每个类都可以对应一个伴生对象,并且该伴生对象的成员全局独一份,也就是说伴生对象companion object也是一种单例,再次借助Show Kotlin Bytecode工具,将上述 Kotlin 代码反编译成 Java 代码如下:

public final class Rectangle {
   ...
   public static final Rectangle.Companion Companion = new Rectangle.Companion((DefaultConstructorMarker)null);

   public static final class Companion {
      @NotNull
      public final Rectangle ofSize(int width, int height) {
         return new Rectangle(width, height);
      }

      @NotNull
      public final Rectangle ofRectangle(@NotNull Rectangle rectangle) {
         Intrinsics.checkParameterIsNotNull(rectangle, "rectangle");
         return new Rectangle(rectangle.getWidth(), rectangle.getHeight());
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

可以发现,Kotlin 中的companion object其实对应到 Java 中也就只是 Rectangle 的一个静态内部类单例而已。

JVM静态成员(@JvmStatic)

我们把上面 Kotlin 的 main 方法中的代码用 Java 重写一遍:

int width = 4;
int height = 5;
Rectangle rectangle1 = new Rectangle(width,height);
Rectangle rectangle2 = Rectangle.Companion.ofSize(width,height);
Rectangle rectangle3 = Rectangle.Companion.ofRectangle(rectangle2);

发现 ofSize 和 ofRectangle 方法并不是通过 Rectangle 类直接调用的,结合上述反编译后的 Java代码,应该不难理解,因为 Kotlin 中 Rectangle 的伴生对象本质上就是这个 Rectangle.Companion 实例,ofSize 和 ofRectangle 都是 Rectangle.Companion 的方法,所以,用 Rectangle.Companion 对象调用它自己的方法这完全没有毛病,可是,难道就不能在 Java 中也像 Kotlin 那样,直接通过类名.xxx()的方法来调用伴生对象方法吗?答案肯定是有的啦,Kotlin 提供了@JvmStatic@JvmField,可以让伴生对象中的方法和属性在 Java中通过类名直接调用:

class Rectangle(val width: Int, val height: Int) {
    companion object {
        @JvmStatic
        fun ofSize(width: Int, height: Int): Rectangle {
            return Rectangle(width, height)
        }

        @JvmStatic
        fun ofRectangle(rectangle: Rectangle): Rectangle {
            return Rectangle(rectangle.width, rectangle.height)
        }

        @JvmField
        val Tag = "Rectangle"
    }
}

这次 Java 代码也可以使用 Rectangle 类名来调用 ofSize 和 ofRectangle 方法了:

int width = 4;
int height = 5;
Rectangle rectangle1 = new Rectangle(width, height);
Rectangle rectangle2 = Rectangle.ofSize(width, height);
Rectangle rectangle3 = Rectangle.ofRectangle(rectangle2);
System.out.println(Rectangle.Tag);

再来反编译一次,看其究竟是什么原理,原来就只是在 Rectangle 中增加了对应的 static 属性和方法而已:

public final class Rectangle {
   public static final Rectangle.Companion Companion = new Rectangle.Companion((DefaultConstructorMarker)null);

   @JvmField
   @NotNull
   public static final String Tag = "Rectangle";

   @JvmStatic
   @NotNull
   public static final Rectangle ofSize(int width, int height) {
      return Companion.ofSize(width, height);
   }

   @JvmStatic
   @NotNull
   public static final Rectangle ofRectangle(@NotNull Rectangle rectangle) {
      return Companion.ofRectangle(rectangle);
   }

   public static final class Companion {
      @JvmStatic
      @NotNull
      public final Rectangle ofSize(int width, int height) {
          ...
      }

      @JvmStatic
      @NotNull
      public final Rectangle ofRectangle(@NotNull Rectangle rectangle) {
          ...
      }
   }
}

通过阅读官方文档可以发现,@JvmStatic注解只能用在object单例和companion object伴生对象里,修饰单例和伴生对象内的属性和函数,用来告诉编译器将属性和函数编译为真正的JVM静态成员(在字节码层面会有所处理)。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 12
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值