单例(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开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)
PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题