case类的类参数修饰符
case类的类参数修饰符有两种:val和var,默认是val,比如:
case class User(name:String, pwd:String)
就相当于
case class User(val name:String, val pwd:String)
从翻译的字节码也可看出
{
private final java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_FINAL
private final java.lang.String pwd;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_FINAL
public java.lang.String name();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #39 // Field name:Ljava/lang/String;
4: areturn
public java.lang.String pwd();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #43 // Field pwd:Ljava/lang/String;
4: areturn
所以,默认情况下,case类是不能有成员变量的set方法的。
同时我们也注意到scala编译器自动为User生成了name()和pwd()两个public的get方法。在类之外这样调用:
def testUser = {
val u = User("wala", "123")
println(u.name + "=>" + u.pwd)
}
字节码层面调的其实是name()和pwd()方法,而并非真的在访问类的私有成员:
19: invokespecial #31 // Method scala/collection/mutable/StringBuilder."<init>":()V
22: aload_1
23: invokevirtual #35 // Method com/huawei/test/User.name:()Ljava/lang/String;
......
34: aload_1
35: invokevirtual #44 // Method com/huawei/test/User.pwd:()Ljava/lang/String;
38: invokevirtual #39 // Method scala/collection/mutable/StringBuilder.append:(Ljava/lang/Object;)Lscala/collection/mutable/StringBuilder;
41: invokevirtual #47 // Method scala/collection/mutable/StringBuilder.toString:()Ljava/lang/String;
44: invokevirtual #51 // Method scala/Predef$.println:(Ljava/lang/Object;)V
47: return
当然,我们也可以改变默认行为,将类参数设成var:
case class User(var name:String, var pwd:String)
这时,scala编译器就会为我们生成set方法,见以下字节码:
{
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
private java.lang.String pwd;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
public java.lang.String name();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #39 // Field name:Ljava/lang/String;
4: areturn
public void name_$eq(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #39 // Field name:Ljava/lang/String;
5: return
public java.lang.String pwd();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #46 // Field pwd:Ljava/lang/String;
4: areturn
public void pwd_$eq(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #46 // Field pwd:Ljava/lang/String;
5: return
可以看到,在name()和pwd()之外,编译器还为我们生成了name_$eq和pwd_$eq两个set方法。
这样调用:
def testUser = {
val u = User("wala", "123")
u.name = "chuchu"
println(u.name + "=>" + u.pwd)
}
相当于调用了name_$eq,而非直接访问成员变量。
非case类的类参数修饰符
非case类的类参数在明确指明了var和val的情形下,行为跟case类是类似的。但若不指明是var还是val,行为比较复杂,有时甚至不会生成成员变量,比如:
class User( name:String, pwd:String)
会为我们生成这样的字节码:
{
public com.huawei.test.User(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=1, locals=3, args_size=3
0: aload_0
1: invokespecial #13 // Method java/lang/Object."<init>":()V
4: return
}
可以看到,没有成员变量,也没有get、set方法,这样的类定义有啥用处?
但也不尽然,看下面的例子:
class User( name:String, pwd:String) {
def f = name + "$"
def g = pwd + "$"
}
生成的字节码如下:
{
private final java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_FINAL
private final java.lang.String pwd;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_FINAL
public java.lang.String f();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: new #15 // class scala/collection/mutable/StringBuilder
3: dup
4: invokespecial #19 // Method scala/collection/mutable/StringBuilder."<init>":()V
7: aload_0
8: getfield #21 // Field name:Ljava/lang/String;
11: invokevirtual #25 // Method scala/collection/mutable/StringBuilder.append:(Ljava/lang/Object;)Lscala/collection/mutable/StringBuilder;
14: ldc #27 // String $
16: invokevirtual #25 // Method scala/collection/mutable/StringBuilder.append:(Ljava/lang/Object;)Lscala/collection/mutable/StringBuilder;
19: invokevirtual #30 // Method scala/collection/mutable/StringBuilder.toString:()Ljava/lang/String;
22: areturn
public java.lang.String g();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: new #15 // class scala/collection/mutable/StringBuilder
3: dup
4: invokespecial #19 // Method scala/collection/mutable/StringBuilder."<init>":()V
7: aload_0
8: getfield #35 // Field pwd:Ljava/lang/String;
11: invokevirtual #25 // Method scala/collection/mutable/StringBuilder.append:(Ljava/lang/Object;)Lscala/collection/mutable/StringBuilder;
14: ldc #27 // String $
16: invokevirtual #25 // Method scala/collection/mutable/StringBuilder.append:(Ljava/lang/Object;)Lscala/collection/mutable/StringBuilder;
19: invokevirtual #30 // Method scala/collection/mutable/StringBuilder.toString:()Ljava/lang/String;
22: areturn
public com.huawei.test.User(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: aload_1
2: putfield #21 // Field name:Ljava/lang/String;
5: aload_0
6: aload_2
7: putfield #35 // Field pwd:Ljava/lang/String;
10: aload_0
11: invokespecial #37 // Method java/lang/Object."<init>":()V
14: return
}
scala编译器为我们生成了private final的成员变量,但并未生成任何get、set方法,只是在f、g这样的成员函数里才能访问name和pwd。所以,非case类的类参数未指定var和val时,所起的作用相当于仅生成private成员变量,而无任何的get、set方法。其实,这样的做法在某些场景下更符合数据封装的概念。