阅读完需:约 8 分钟
单例
单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
关键点:
- 一个类只有一个实例 , 这是最基本的
- 它必须自行创建这个实例
- 它必须自行向整个系统提供这个实例
两种实现方式:
- 懒汉模式 : 需要用的时候,才会创建对象
- 饿汉式:加载类的时候,就创建了对象


只需要类前面添加object
关键字即可,object
定义类等价于java的恶汉式的单例模式
object Singleton {
var x: Int = 2
fun y(){ }
init {
//object不能定义构造函数,但可以定义init块
}
}
调用:
fun main() {
Singleton.x
Singleton.y()
}
object不能定义构造函数,但可以定义init
块


@JvmStatic
和 @JvmField


单例(object):
object Singleton {
@JvmField var x: Int = 2 //生成java的静态成员 不会生成getter和setter方法
@JvmStatic fun y(){ } //生成java的静态方法
}
普通kotlin类(非单例)中使用使用JvmField
和JvmStatic
:
class Foo {
//普通类中使用JvmField和JvmStatic
companion object {
@JvmField var x: Int = 2
@JvmStatic fun y(){ }
}
}
注意,加的注解@JvmField
和@JvmStatic
只针对java平台的可用,其他平台并不可行。
单例的object类仍可以继承类:

object Singleton: Runnable{
override fun run() {
}
}
伴生对象
因为 Kotlin 没有 static 这个概念,尝试着在有 static 概念的 Java 中调用 Kotlin 的 companion object 中的方法或者访问当中的公开属性,是无法做不到的(除非你用 @JvmStatic 进行注释来生成真正的静态方法和变量)。
companion object 并不是所谓的静态写法(很关键)
可以把伴生对象看成是一个普通Java类的单例对象,因为它最终也是按照普通类对象进行编译的,只不过默认给你生成了一个唯一的实例。
在类中定义的对象(object)声明,可使用 companion
修饰,这样此对象(object)就是伴生对象了。如下例子:

class NumberTest {
companion object Obj {
var flag = false
fun plus(num1: Int, num2: Int): Int {
return num1 + num2
}
}
}
在本例中,类 NumberTest 中就声明了一个伴生对象,包含属性 flag
和 方法 plus()
。但是一个类(class)中最多只能定义一个伴生对象。
调用
使用 companion
关键字修改的对象之后,伴生对象就相当于是外部类的对象,我们可以使用类直接调用,如下:
fun main(args: Array<String>) {
println(NumberTest.plus(1, 2)) // 3
println(NumberTest.flag) // false
}
从上面的代码可以看出调用的时候与伴生对象的名称没有多大关系,所以名称 Obj
可以省略不写。
伴生对象的作用
通过上面的 NumberTest.plus(1, 2)
和 NumberTest.flag
代码不难看出来,类似于 Java 中使用类访问静态成员的语法。因为 Kotlin 取消了 static
关键字,所以 Kotlin 引入伴生对象来弥补没有静态成员的不足。可见,伴生对象的主要作用就是为其所在的外部类模拟静态成员。
与 Java 代码共存
知道了伴生对象的特点之后,那么我们如何与 Java 代码共存呢?
public class NumberJavaTest {
public static void main(String[] args) {
System.out.println(NumberTest.Obj.plus(2, 3)); // 5
// System.out.println(NumberTest.Companion.plus(2, 3));
NumberTest.Obj.setFlag(true);
// NumberTest.Companion.setFlag(true);
System.out.println(NumberTest.Obj.getFlag()); // true
// System.out.println(NumberTest.Companion.getFlag());
}
}
- 如果声明伴生对象有名称,则使用:
类名.伴生对象名.方法名()
类名.半生对象名.属性的setter,getter方法
例:NumberTest.Obj.plus(2, 3)
-
如果声明伴生对象无名称,则采用
Companion
关键字调用:
类名.Companion.方法名()
类名.Companion.属性的setter,getter方法
例:NumberTest.Companion.getFlag()
@JvmField 和 @JvmStatic 的使用(伴生对象)
我们知道了可以在 Java 代码中调用 Kotlin 中伴生对象的成员,类似于 Java 类中的静态成员。但是看上去和 Java 中的还是略有区别,因为类名和方法名/属性setter,getter方法名之间多了个伴生对象的名称或者 Companion 关键字。如何使其在调用的时候与 Java 中的调用看上去一样呢?
Kotlin 为我们提供了 @JvmField
和 @JvmStatic
两个注解。@JvmField
使用在属性上,@JvmStatic
使用在方法上。如:


class NumberTest {
companion object {
@JvmField
var flag = false
@JvmStatic
fun plus(num1: Int, num2: Int): Int {
return num1 + num2
}
}
}
这样我们在 Java 代码中调用的时候就和 Java 类调用静态成员的形式一致了,Kotlin 代码调用方式不变:
public static void main(String[] args) {
System.out.println(NumberTest.plus(2, 3));
NumberTest.flag = true;
System.out.println(NumberTest.flag);
}
const 关键字
在伴生对象中,我们可能需要声明一个常量,目的是等同于 Java 中的静态常量。有两种方式,一种是上面所提到的使用 @JvmField
注解,另一种则是使用 const
关键字修饰。这两种声明方式都等同于 Java 中 static final 所修饰的变量。如下代码:
companion object {
const val m = 2
@JvmField
val n = 3
}
// java 代码中调用:
System.out.println(NumberTest.m);
System.out.println(NumberTest.n);
如果不使用 const 修饰的常量,我们需要引用伴生对象来调用,而且必须调用 getter 方法。
companion object {
const val k = 2
}
// java 代码中调用:
System.out.println(NumberTest.Companion.getK());
而以上 const 关键字使用的影响只是在 Java 中调用方式不同,在 Kotlin 中并无影响。
伴生对象的扩展
如果了解 Kotlin 的话,应该知道在 Kotlin 中,对象时可以被扩展的。在 Kotlin 中,如果类中包含伴生对象,则 Kotlin 允许伴生对象扩展方法和属性。也就是为伴生对象所在的外部类扩展静态成员,访问方式一致。
接着上面的列子,为上面的 NumberTest
类扩展一个 minus
方法:
扩展方法
fun NumberTest.Companion.minus(str: String): Int {
if (str != null && str.isNotEmpty()) {
return try {
str.toInt()
} catch (e: Exception) {
0
}
}
return 0
}
通过例子我们看出,我们可以通过类名去扩展方法,如果伴生对象有名称的话,使用 类名.伴生对象名.方法名()
来扩展,否则使用 类名.Companion.方法名()
来扩展即可。
扩展属性
var NumberTest.Companion.number
get() = 3
set(value) {
// set 方法并没有 field 可以用来存储 value
this.plus(value, 2)
}
val NumberTest.Companion.str
get() = "这是一个扩展属性"
同样,我们也可以扩展属性,但是扩展属性有以下几个特点:
-
扩展属性不能有初始值,没有
field
来存储属性值; - 扩展属性因为没字段来存储值,所以为计算属性;
- 扩展 var 属性必须提供 setter 和 getter 方法,扩展 val 属性必须提供 getter 属性。
总结:
- 每个类可以最多有一个半生对象;
- 伴生对象的成员类似于 Java 的静态成员;
- 使用 const 关键字修饰常量,类似于 Java 中的 static final修饰。
- 可以使用 @JvmField 和 @JvmStatic 类似于 Java 中调用静态属性和静态方法;
- 伴生对象可以扩展属性和扩展方法。