scala object 单例实现——字节码分析
scala中天然支持单例,一个类可以声明为class或object。当声明为object后我们就可以拿它当单例使用了。虽然可以简单将object中的字段或方法理解为java中的static,还是想了解下字节码层面如何做到的。
scala
定义如下单例测试
object Single {
var f1=10
val f2="Test"
def add(a: Int, b: Int): Int = a + b
def sayHi(name: String): Unit = println(s"hi,$name")
}
字节码
将这个类编译后,产生Single.class和Singe$.class两个文件。使用javap -c -v -p 反编译后,关键部分的字节码如下
Single.class 仅仅提供了static了方法
public final class detail.object1.Single{
public static void sayHi(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #16 // Field detail/object1/Single$.MODULE$:Ldetail/object1/Single$;
3: aload_0
4: invokevirtual #18 // Method detail/object1/Single$.sayHi:(Ljava/lang/String;)V
7: return
public static int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=2
0: getstatic #16 // Field detail/object1/Single$.MODULE$:Ldetail/object1/Single$;
3: iload_0
4: iload_1
5: invokevirtual #22 // Method detail/object1/Single$.add:(II)I
8: ireturn
public static java.lang.String f2();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #16 // Field detail/object1/Single$.MODULE$:Ldetail/object1/Single$;
3: invokevirtual #26 // Method detail/object1/Single$.f2:()Ljava/lang/String;
6: areturn
public static void f1_$eq(int);
descriptor: (I)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #16 // Field detail/object1/Single$.MODULE$:Ldetail/object1/Single$;
3: iload_0
4: invokevirtual #30 // Method detail/object1/Single$.f1_$eq:(I)V
7: return
public static int f1();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #16 // Field detail/object1/Single$.MODULE$:Ldetail/object1/Single$;
3: invokevirtual #34 // Method detail/object1/Single$.f1:()I
6: ireturn
}
Single$.class真正的对象类
public final class detail.object1.Single$
{
public static final detail.object1.Single$ MODULE$;
descriptor: Ldetail/object1/Single$;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
private int f1;
descriptor: I
flags: ACC_PRIVATE
private final java.lang.String f2;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_FINAL
public static {};
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: new #2 // class detail/object1/Single$
3: invokespecial #12 // Method "<init>":()V
6: return
public int f1();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #19 // Field f1:I
4: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldetail/object1/Single$;
LineNumberTable:
line 11: 0
public void f1_$eq(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #19 // Field f1:I
5: return
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Ldetail/object1/Single$;
0 6 1 x$1 I
LineNumberTable:
line 11: 0
public java.lang.String f2();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #26 // Field f2:Ljava/lang/String;
4: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldetail/object1/Single$;
LineNumberTable:
line 12: 0
public int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this Ldetail/object1/Single$;
0 4 1 a I
0 4 2 b I
LineNumberTable:
line 13: 0
public void sayHi(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=8, locals=2, args_size=2
0: getstatic #37 // Field scala/Predef$.MODULE$:Lscala/Predef$;
3: new #39 // class scala/StringContext
6: dup
7: getstatic #37 // Field scala/Predef$.MODULE$:Lscala/Predef$;
10: iconst_2
11: anewarray #41 // class java/lang/String
14: dup
15: iconst_0
16: ldc #43 // String hi,
18: aastore
19: dup
20: iconst_1
21: ldc #45 // String
23: aastore
24: checkcast #47 // class "[Ljava/lang/Object;"
27: invokevirtual #51 // Method scala/Predef$.wrapRefArray:([Ljava/lang/Object;)Lscala/collection/mutable/WrappedArray;
30: invokespecial #54 // Method scala/StringContext."<init>":(Lscala/collection/Seq;)V
33: getstatic #37 // Field scala/Predef$.MODULE$:Lscala/Predef$;
36: iconst_1
37: anewarray #4 // class java/lang/Object
40: dup
41: iconst_0
42: aload_1
43: aastore
44: invokevirtual #58 // Method scala/Predef$.genericWrapArray:(Ljava/lang/Object;)Lscala/collection/mutable/WrappedArray;
47: invokevirtual #62 // Method scala/StringContext.s:(Lscala/collection/Seq;)Ljava/lang/String;
50: invokevirtual #66 // Method scala/Predef$.println:(Ljava/lang/Object;)V
53: return
LocalVariableTable:
Start Length Slot Name Signature
0 54 0 this Ldetail/object1/Single$;
0 54 1 name Ljava/lang/String;
LineNumberTable:
line 14: 0
private detail.object1.Single$();
descriptor: ()V
flags: ACC_PRIVATE
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #68 // Method java/lang/Object."<init>":()V
4: aload_0
5: putstatic #70 // Field MODULE$:Ldetail/object1/Single$;
8: aload_0
9: bipush 10
11: putfield #19 // Field f1:I
14: aload_0
15: ldc #72 // String Test
17: putfield #26 // Field f2:Ljava/lang/String;
20: return
LocalVariableTable:
Start Length Slot Name Signature
0 21 0 this Ldetail/object1/Single$;
LineNumberTable:
line 16: 0
line 11: 8
line 12: 14
}
java伪代码
将以上代码复现为java代码
# scala类Single反编译后的java版
//真正的单例类,实现类饿汉模式
public final class Single${
public static Single$ MODULE=new Single$();
private int f1;
private final String f2;
private Single$(){
f1=10;
f2="test";
}
//f1 getter
public int f1(){
return f1;
}
//f1 setter
public void f1_$eq(int a){
f1=a;
}
//f2 getter
public String f2(){
return f2;
}
public int add(int a,int b){
...
}
public void sayHi(String name){
...
}
}
public final class Single{
public static void sayHi(String name){
Single$.MODULE.sayHi(name);
}
public static int add(int a, int b){
return Single$.MODULE.add(a+b);
}
public static java.lang.String f2(){
return Single$.MODULE.f2();
}
public static void f1_$eq(int a){
Single$.MODULE.f1_$eq(a);
}
public static int f1(){
return Single$.MODULE.f1();
}
}
总结
scala的object总体实现机制为,假设object A,则首先定义一个类A , 这 个 类 就 是 真 正 的 单 例 类 , 和 j a v a 中 的 饿 汉 模 式 的 实 现 一 样 , 然 后 还 有 一 个 类 A 为 代 理 类 , 其 提 供 类 A ,这个类就是真正的单例类,和java中的饿汉模式的实现一样,然后还有一个类A为代理类,其提供类A ,这个类就是真正的单例类,和java中的饿汉模式的实现一样,然后还有一个类A为代理类,其提供类A所有方法的static版,并实际调用类A$对应的方法。
另外,类都有final关键字修饰,这点和scala中object不能继承一致。
还有一个问题,如果对object Single调用getClass,返回的是哪个呢?答案Single$,的确,因为这个类才是真正的主体类,Single只是代理而已。