Hãy chia tách một bộ ví dụ để chúng tôi có thể thấy chúng khác nhau như thế nào. (Nếu sử dụng RC1, biên dịch với -no-specialization
để giữ cho mọi thứ dễ hiểu hơn.)
class Close {
var n = 5
def method(i: Int) = i+n
def function = (i: Int) => i+5
def closure = (i: Int) => i+n
def mixed(m: Int) = (i: Int) => i+m
}
Đầu tiên, chúng ta hãy xem những gì method
làm:
public int method(int);
Code:
0: iload_1
1: aload_0
2: invokevirtual #17; //Method n:()I
5: iadd
6: ireturn
Khá đơn giản. Đó là một phương pháp. Tải tham số, gọi hàm getter cho n
, thêm, trả về. Trông giống như Java.
Làm thế nào về function
?Nó không thực sự đóng bất kỳ dữ liệu nào, nhưng nó là một chức năng ẩn danh (được gọi là Close$$anonfun$function$1
). Nếu chúng ta bỏ qua bất kỳ chuyên môn, các nhà xây dựng và áp dụng được quan tâm nhất:
public scala.Function1 function();
Code:
0: new #34; //class Close$$anonfun$function$1
3: dup
4: aload_0
5: invokespecial #35; //Method Close$$anonfun$function$1."<init>":(LClose;)V
8: areturn
public Close$$anonfun$function$1(Close);
Code:
0: aload_0
1: invokespecial #43; //Method scala/runtime/AbstractFunction1."<init>":()V
4: return
public final java.lang.Object apply(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: invokestatic #26; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
5: invokevirtual #28; //Method apply:(I)I
8: invokestatic #32; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
11: areturn
public final int apply(int);
Code:
0: iload_1
1: iconst_5
2: iadd
3: ireturn
Vì vậy, bạn tải một "này" con trỏ và tạo ra một đối tượng mới mà có lớp kèm theo như là đối số của nó. Đây là tiêu chuẩn cho bất kỳ lớp bên trong, thực sự. Hàm này không cần phải làm bất cứ điều gì với lớp bên ngoài nên nó chỉ gọi hàm tạo của siêu. Sau đó, khi gọi áp dụng, bạn thực hiện thủ thuật hộp/unbox và sau đó gọi toán học thực tế - nghĩa là, chỉ cần thêm 5.
Nhưng điều gì sẽ xảy ra nếu chúng ta sử dụng đóng biến trong Close? Thiết lập là giống hệt nhau, nhưng bây giờ các nhà xây dựng Close$$anonfun$closure$1
trông như thế này:
public Close$$anonfun$closure$1(Close);
Code:
0: aload_1
1: ifnonnull 12
4: new #48; //class java/lang/NullPointerException
7: dup
8: invokespecial #50; //Method java/lang/NullPointerException."<init>":()V
11: athrow
12: aload_0
13: aload_1
14: putfield #18; //Field $outer:LClose;
17: aload_0
18: invokespecial #53; //Method scala/runtime/AbstractFunction1."<init>":()V
21: return
Nghĩa là, nó sẽ kiểm tra để đảm bảo rằng các đầu vào là không null (tức là lớp ngoài cùng là phi null) và lưu nó trong một lĩnh vực. Bây giờ khi nói đến thời gian để áp dụng nó, sau khi boxing/unboxing wrapper:
public final int apply(int);
Code:
0: iload_1
1: aload_0
2: getfield #18; //Field $outer:LClose;
5: invokevirtual #24; //Method Close.n:()I
8: iadd
9: ireturn
bạn thấy rằng nó sử dụng lĩnh vực đó để tham khảo các lớp cha mẹ, và gọi các getter cho n
. Thêm, trả lại, xong. Vì vậy, đóng cửa là dễ dàng đủ: constructor chức năng vô danh chỉ cần lưu các lớp kèm theo trong một lĩnh vực tư nhân.
Bây giờ, điều gì sẽ xảy ra nếu chúng tôi đóng không phải một biến nội bộ, nhưng là một đối số phương pháp? Đó là những gì Close$$anonfun$mixed$1
thực hiện. Đầu tiên, hãy nhìn vào những gì các phương pháp mixed
làm:
public scala.Function1 mixed(int);
Code:
0: new #39; //class Close$$anonfun$mixed$1
3: dup
4: aload_0
5: iload_1
6: invokespecial #42; //Method Close$$anonfun$mixed$1."<init>":(LClose;I)V
9: areturn
Nó tải tham số m
trước khi gọi các nhà xây dựng! Vì vậy, không có gì ngạc nhiên khi hàm tạo trông như sau:
public Close$$anonfun$mixed$1(Close, int);
Code:
0: aload_0
1: iload_2
2: putfield #18; //Field m$1:I
5: aload_0
6: invokespecial #43; //Method scala/runtime/AbstractFunction1."<init>":()V
9: return
nơi thông số đó được lưu trong trường riêng tư. Không có tham chiếu đến lớp bên ngoài được giữ bởi vì chúng ta không cần nó. Và bạn không nên ngạc nhiên bằng cách áp dụng:
public final int apply(int);
Code:
0: iload_1
1: aload_0
2: getfield #18; //Field m$1:I
5: iadd
6: ireturn
Có, chúng tôi chỉ tải trường được lưu trữ đó và thực hiện phép tính của chúng tôi.
Tôi không chắc bạn đang làm gì để không nhìn thấy điều này với ví dụ của bạn - các đối tượng hơi phức tạp vì chúng có cả các lớp MyObject
và MyObject$
và các phương thức được phân chia giữa hai cách theo cách có thể không trực quan. Nhưng áp dụng chắc chắn áp dụng mọi thứ, và tổng thể toàn bộ hệ thống hoạt động khá nhiều theo cách bạn mong đợi nó (sau khi bạn ngồi xuống và suy nghĩ về nó thực sự khó khăn cho một thời gian thực sự dài).
Bạn có chắc là bạn đang sử dụng 'javap -c 'ClassName $$ anonfun $ etcetc'' để làm việc giải mã? –