2010-04-19 38 views
18

Tôi hiện đang xem xét triển khai đóng cửa bằng các ngôn ngữ khác nhau. Tuy nhiên, khi nói đến Scala, tôi không thể tìm thấy bất kỳ tài liệu nào về cách đóng cửa được ánh xạ tới các đối tượng Java.Làm thế nào là đóng cửa Scala chuyển đổi sang các đối tượng Java?

Nó cũng được ghi lại rằng các hàm Scala được ánh xạ tới các đối tượng FunctionN. Tôi giả định rằng tham chiếu đến biến tự do của đóng phải được lưu trữ ở đâu đó trong đối tượng hàm đó (vì nó được thực hiện trong C++ 0x, ví dụ).

Tôi cũng đã cố gắng biên soạn sau đây với scalac và sau đó dịch ngược các tập tin lớp học với JD:

object ClosureExample extends Application { 
    def addN(n: Int) = (a: Int) => a + n 
    var add5 = addN(5) 
    println(add5(20)) 
} 

Trong nguồn decompiled, tôi thấy một kiểu phụ vô danh của Function1, mà nên được đóng cửa của tôi. Nhưng phương thức apply() trống và lớp ẩn danh không có các trường (có khả năng lưu trữ các biến đóng). Tôi cho rằng decompiler không quản lý để có được những phần thú vị ra khỏi các tập tin lớp ...

Bây giờ đến câu hỏi:

  • Bạn có biết làm thế nào việc chuyển đổi được thực hiện một cách chính xác?
  • Bạn có biết tài liệu được ghi ở đâu không?
  • Bạn có ý tưởng nào khác về cách tôi có thể giải quyết bí ẩn không?
+1

Bạn có chắc là bạn đang sử dụng 'javap -c 'ClassName $$ anonfun $ etcetc'' để làm việc giải mã? –

Trả lời

31

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ó 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 MyObjectMyObject$ 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).

+0

Câu trả lời đáng yêu! –

+0

Cảm ơn rất nhiều, điều đó rất chi tiết. Tôi đã cố gắng sử dụng một trình biên dịch ngược để xây dựng lại mã Java (nó được gọi là JD), bởi vì tôi không quen với bytecode, và nó không hoạt động.Tôi nghĩ rằng trình giải mã không xử lý các lớp ẩn danh một cách chính xác. Bây giờ tôi đã học được những điều cơ bản về Java bytecode, câu trả lời của bạn rất dễ hiểu. Bạn có biết bất kỳ tài liệu chính thức nào về việc chuyển đổi này không? – theDmi

+0

@iguana: Tôi không biết bất kỳ nơi nào việc chuyển đổi được ghi lại chi tiết. Tôi có xu hướng tin bytecode nhiều hơn các tài liệu. –

5

Không giống như các lớp bên trong vô danh của Java mà giả đóng cửa và không thể sửa đổi các biến xuất hiện phải đóng cửa vào môi trường xung quanh của nó, đóng cửa của Scala là có thật, vì vậy mã đóng cửa trực tiếp tham chiếu các giá trị trong môi trường xung quanh . Các giá trị như vậy được biên dịch khác nhau khi chúng được tham chiếu từ một đóng để làm cho điều này có thể (vì không có cách nào để mã phương thức truy cập người dân từ bất kỳ khung kích hoạt nào khác với khung hiện tại).

Ngược lại, trong Java giá trị của chúng được sao chép vào các trường trong lớp bên trong, đó là lý do ngôn ngữ yêu cầu giá trị ban đầu trong môi trường kèm theo là final, vì vậy chúng không bao giờ có thể phân tách. Bởi vì tất cả các tham chiếu của hàm Scala/literal đối với các giá trị trong môi trường kèm theo đều nằm trong mã của phương thức apply() của hàm theo nghĩa đen, chúng không xuất hiện dưới dạng các trường trong lớp con thực tế Function được tạo cho hàm theo nghĩa đen.

Tôi không biết bạn đang giải mã như thế nào, nhưng chi tiết về cách bạn làm như vậy có thể giải thích tại sao bạn không thấy bất kỳ mã nào cho phần thân của phương thức apply().

+0

Cảm ơn, đó là điều tôi nghi ngờ. Tôi sẽ điều tra thêm về vấn đề giải mã của tôi. Bạn có bất kỳ nguồn chính thức nào không, nơi hành vi này được ghi lại? Điều đó sẽ rất hữu ích, bởi vì tôi cần một cái gì đó tôi có thể trích dẫn :-). – theDmi

+0

Không có gì tôi viết là có thẩm quyền. Tôi cho rằng tôi nên nói rằng ... Có góc biên dịch Scala (http://www.sts.tu-harburg.de/people/mi.garcia/ScalaCompilerCorner/) là một nguồn tài nguyên tốt và có thể chứa thông tin như vậy. –

Các vấn đề liên quan