Kotlin 中的单例对象是如何实现的?
结论
在类初始化的时候,会对单例对象进行赋值,所以是"饿汉式"。
代码
我们用如下代码进行探索。(请将代码保存为 AreaCalculator.kt
)
kotlin
object AreaCalculator {
val PI = Math.PI;
fun calculateArea(r: Double): Double {
return PI * r * r;
}
}
fun foo() {
AreaCalculator.calculateArea(1.0)
}
如下命令可以编译 AreaCalculator.kt
bash
kotlinc AreaCalculator.kt
编译后,会生成以下 class
文件
text
AreaCalculator.class
AreaCalculatorKt.class
查看 AreaCalculatorKt
类
如下命令可以查看 AreaCalculatorKt.class
的内容。
bash
javap -v -p AreaCalculatorKt
部分结果粘贴如下(常量池等部分已略去)
text
{
public static final void foo();
descriptor: ()V
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
Code:
stack=3, locals=0, args_size=0
0: getstatic #12 // Field AreaCalculator.INSTANCE:LAreaCalculator;
3: dconst_1
4: invokevirtual #16 // Method AreaCalculator.calculateArea:(D)D
7: pop2
8: return
LineNumberTable:
line 10: 0
line 11: 8
}
假如源码是 java
的,那么它应该是这个样子的 ⬇️
java
// 以下代码是我手动转化的,不保证绝对准确,仅供参考
public final class AreaCalculatorKt {
public static final void foo() {
AreaCalculator.INSTANCE.calculateArea(1.0d);
}
}
看来 foo()
方法会
- 先获取
AreaCalculator
类的单例对象(在INSTANCE
这个 静态字段 中), - 再通过它来调用
calculateArea(double)
方法
那我们继续查看 AreaCalculator
类的内容吧。
查看 AreaCalculator
类
如下命令可以查看 AreaCalculator.class
的内容。
bash
javap -v -p AreaCalculator
部分结果粘贴如下(常量池等部分已略去)
text
{
public static final AreaCalculator INSTANCE;
descriptor: LAreaCalculator;
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
RuntimeInvisibleAnnotations:
0: #27()
org.jetbrains.annotations.NotNull
private static final double PI;
descriptor: D
flags: (0x001a) ACC_PRIVATE, ACC_STATIC, ACC_FINAL
private AreaCalculator();
descriptor: ()V
flags: (0x0002) ACC_PRIVATE
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LAreaCalculator;
public final double getPI();
descriptor: ()D
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=1, args_size=1
0: getstatic #16 // Field PI:D
3: dreturn
LineNumberTable:
line 2: 0
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this LAreaCalculator;
public final double calculateArea(double);
descriptor: (D)D
flags: (0x0011) ACC_PUBLIC, ACC_FINAL
Code:
stack=4, locals=3, args_size=2
0: getstatic #16 // Field PI:D
3: dload_1
4: dmul
5: dload_1
6: dmul
7: dreturn
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 this LAreaCalculator;
0 8 1 r D
static {};
descriptor: ()V
flags: (0x0008) ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: new #2 // class AreaCalculator
3: dup
4: invokespecial #21 // Method "<init>":()V
7: putstatic #24 // Field INSTANCE:LAreaCalculator;
10: ldc2_w #25 // double 3.141592653589793d
13: putstatic #16 // Field PI:D
16: return
LineNumberTable:
line 2: 10
}
假如源码是 java
的,那么它应该是这个样子的 ⬇️
java
// 以下代码是我手动转化的,不保证绝对准确,仅供参考
public final class AreaCalculator {
// 单例对象
@org.jetbrains.annotations.NotNull
public static final AreaCalculator INSTANCE;
private static final double PI;
private AreaCalculator() {
super();
}
public final double getPI() {
return AreaCalculator.PI;
}
public final double calculateArea(double r) {
return AreaCalculator.PI * r * r;
}
// 在初始化的时候,对单例对象进行赋值,所以是"饿汉式"
static {
INSTANCE = new AreaCalculator();
PI = 3.141592653589793d;
}
}
由此可见,在 AreaCalculator
类进行初始化的时候,INSTANCE
字段会被赋值。 所以是"饿汉式"。
用 Java
代码验证
我们可以用如下 java
代码来进行验证。(请将代码保存为 SimpleTest.java
)
java
public class SimpleTest {
public static void main(String[] args) {
System.out.println(AreaCalculator.INSTANCE.calculateArea(1.0d));
}
}
如下命令可以编译 SimpleTest.java
并运行其中的 main
方法。
bash
javac SimpleTest.java && java SimpleTest
运行结果如下
