2013-04-07 31 views
6

Nếu tôi có lớp học giá trị này:Trong những trường hợp này, lớp giá trị Scala sẽ được "đóng hộp", phải không?

class ActionId(val value: Int) extends AnyVal 

Sau đó, trong tất cả các ví dụ dưới đây, một đối tượng sẽ được phân bổ cho các lớp giá trị? (Nó sẽ được "đóng hộp" - nó sẽ không chỉ đơn giản là được tháo để một đồng bằng bit số nguyên 32, phải không?)

  1. Một hàm trả về một giá trị lớp - lớp giá trị thoát phạm vi và sẽ do đó được "đóng hộp"?

    def someFunction(): ActionId = { 
        ... 
        return ActionId(123) 
    } 
    
  2. Một hàm trả về một đối tượng với một thành viên lớp giá trị - lớp giá trị thoát phạm vi và sẽ do đó được "đóng hộp"?

    case class Post(id: ActionId, ...) { ... } 
    
    def someFunction(): Post = { 
        ... 
        val somePost = Post(ActionId(123), ...) // ActionId will be "boxed", right? 
        return somePost 
    } 
    
  3. Thậm chí nếu đối tượng với một thành viên lớp giá trịkhông trả lại (không thực sự thoát khỏi phạm vi), lớp giá trị vẫn sẽ được "đóng hộp", khi nó được sử dụng như một thành viên của một lớp khác (như một trường trong lớp Post, trong ví dụ này)?

    def anotherFunction() { 
        ... 
        val somePost = Post(ActionId(123), ...) // "Boxed" here too, right? 
    
        // ... do something with somePost 
    
        // But don't: return somePost 
    
        // However some *other* similar functions *do* return `somePost` — so 
        // class `Post` must be able to box the ActionId? Hence it's boxed (above)? 
    } 
    

Về vấn đề này là this answer, mà nói rằng khi lớp giá trị không thoát khỏi phạm vi, nó có hiệu quả được inlined. Nó đề cập đến tài liệu quy trình cải tiến Scala SIP-15 để biết thêm chi tiết. Tuy nhiên như xa như tôi có thể nói SIP-15 thực sự không đề cập đến một ví dụ lớp giá trị mà thoát khỏi phạm vi sẽ được "đóng hộp". Nhưng tôi nghĩ có vẻ hợp lý rằng nó sẽ phải "đóng hộp". (Tại sao SIP không nêu rõ rằng nó sẽ được đóng hộp nếu nó thoát?)

Trả lời

10

Không ai trong số những ví dụ của bạn dẫn đến đấm bốc. Các lớp giá trị chỉ được đóng hộp với các generic, với mảng và khi được nhập là superclass/đặc điểm (ví dụ: Any/AnyVal)

Chúng được đóng chung với generics vì nếu không bạn không thể phân biệt chúng với giá trị (và nguyên thủy cần một hộp anyway). Cùng một thỏa thuận với Bất kỳ, và các siêu lớp/đặc điểm khác cần một hộp hoặc mối quan hệ kiểu là sai.

Chúng được đóng hộp với mảng vì mảng cần biết loại nội dung, nhưng JVM không hiểu khái niệm "loại giá trị". Vì vậy, bạn sẽ kết thúc với một mảng cho biết đó là loại được đóng hộp, nhưng mà Scala đã giả vờ là một mảng của loại giá trị; một quyết định đã được đưa ra (dựa trên các vấn đề trước đó với Array khi nó không chỉ là một mảng Java/JVM đơn giản) mà điều này sẽ dẫn đến quá nhiều lỗi và các trường hợp góc phức tạp.

Dưới đây là một ví dụ về ba cách để có được boxing:

trait Q extends Any {} 
class X(val x: String) extends AnyVal with Q {} 

// Array 
val a = Array(new X("salmon")) // boxed 

// Generic 
val b = Option(new X("minnow")) // boxed 

// Upcast 
val c = (new X("perch"): Any) // boxed 
val d = (new X("cod"): AnyVal) // boxed 
val e = (new X("herring"): Q) // boxed 

Mọi thứ khác - đi xung quanh thông qua các chức năng khác nhau, vv .-- không đòi hỏi boxing, bao gồm tất cả các ví dụ của bạn.

Mảng là một chút của một trường hợp đặc biệt bởi vì bạn có thể lưu trữ các nguyên thủy và kéo chúng ra một lần nữa như các lớp học giá trị với zero bytecode trên cao, nhưng rất nhiều overhead cú pháp:

class Y(val repr: String) extends AnyVal {} 
val y1 = new Y("apple") // unboxed 
val y2 = new Y("orange") // unboxed 
val ys: Array[String] = Array(y1.repr, y2.repr) // no overhead 
val y3 = new Y(ys(0))  // no overhead 
+0

Bạn có thể tưởng tượng hỗ trợ thư viện để giảm thiểu chi phí cú pháp khi giao dịch với mảng không? – ziggystar

+0

@ziggystar - Vâng, ngay bây giờ mọi thứ làm giảm chi phí cú pháp làm tăng chi phí đầu vào bytecode. Vì vậy, đó là một trò chơi khó chơi. Khi nó quan trọng, bạn có thể có một lớp giá trị kết thúc tốt đẹp mảng và thực hiện công việc cho bạn (với rất ít cú pháp cú pháp), nhưng tôi không chắc chắn về cách tạo ra một nhà máy tầm thường cho một lớp giá trị tạo ra mảng tương ứng lớp giá trị. Macros cuối cùng sẽ đến đó, tôi cho là vậy. –

+0

@RexKerr vậy điều này có nghĩa là không có lớp giá trị bên trong cấu trúc dữ liệu? Nói cách khác, Danh sách, Bản đồ, v.v. sẽ luôn khởi tạo lớp được đóng hộp? –

12

Trong cả ba trường hợp, sẽ không có quyền đấm nào cả.

Đó là khá dễ dàng để kiểm tra bằng cách tự của bạn:

class ActionId(val value: Int) extends AnyVal 
object Foo { 
    def someFunction(): ActionId = { 
    new ActionId(123) 
    } 
} 

Bây giờ cho phép chạy scalac, và chúng ta sẽ có một loạt các tập tin lớp học (file với bytecode). Cái mà chúng ta cần là Foo \ $.

» javap Foo\$ 
Compiled from "Boxing.scala" 
public final class Foo$ extends java.lang.Object{ 
    public static final Foo$ MODULE$; 
    public static {}; 
    public int someFunction(); 
} 

Như bạn có thể thấy, ngay cả khi lớp giá trị rò rỉ từ chức năng nói chung nó sẽ không được đóng hộp.

case class Post(id: ActionId, notion: String) 

object Foo2 { 
    def someFunction(): Post = { 
    Post(new ActionId(123), "So ActionID will be boxed?") 
    } 
} 

scalac => javap:

» javap Post 
Compiled from "Boxing.scala" 
public class Post extends java.lang.Object implements scala.Product,scala.Serializable{ 
    public static scala.Function1 tupled(); 
    public static scala.Function1 curried(); 
    public int id(); 
    public java.lang.String notion(); 
    public Post copy(int, java.lang.String); 
    public int copy$default$1(); 
    public java.lang.String copy$default$2(); 
    public java.lang.String productPrefix(); 
    public int productArity(); 
    public java.lang.Object productElement(int); 
    public scala.collection.Iterator productIterator(); 
    public boolean canEqual(java.lang.Object); 
    public int hashCode(); 
    public java.lang.String toString(); 
    public boolean equals(java.lang.Object); 
    public Post(int, java.lang.String); 
} 

Như bạn thấy id đây biểu diễn dưới dạng plain int (ví dụ phương pháp thứ ba).

Cuối cùng, sẽ đánh giá lớp được đóng hộp nếu đối tượng có thành viên hạng giá trị không được trả về (không thực sự thoát khỏi phạm vi)?

case class Post(id: ActionId, notion: String) 

object Foo3 { 
    def anotherFunction(): Unit { 
    val post = Post(new ActionId(123), "Will be boxed?") 
    } 
} 

Nếu chúng ta nhìn kỹ vào bytecode của phương pháp này, đây là những gì chúng ta sẽ thấy:

Code: 
    Stack=4, Locals=2, Args_size=1 
    0: new #15; //class Post 
    3: dup 
    4: bipush 123 
    6: ldC#17; //String Will be boxed? 
    8: invokespecial #20; //Method Post."<init>":(ILjava/lang/String;)V 
    11: astore_1 
    12: return 
    LocalVariableTable: 
    Start Length Slot Name Signature 
    0  13  0 this  LFoo3$; 
    12  0  1 post  LPost; 

Không có đấm bốc của int trong ActionId. Nếu nó sẽ hộp bạn sẽ thấy một cái gì đó như thế này:

Code: 
    Stack=5, Locals=2, Args_size=1 
    0: new #15; //class Post 
    3: dup 
    4: new #17; //class ActionId 
    7: dup 
    8: bipush 123 
    10: invokespecial #20; //Method ActionId."<init>":(I)V 
    13: ldC#22; //String Will be boxed? 
    15: invokespecial #25; //Method Post."<init>":(LActionId;Ljava/lang/String;)V 
    18: astore_1 
    19: return 
    LocalVariableTable: 
    Start Length Slot Name Signature 
    0  20  0 this  LFoo3$; 
    19  0  1 post  LPost; 

Bạn thấy đấy, sự khác biệt là so với bipush 123

4: new #17; //class ActionId 
    7: dup 
    8: bipush 123 
    10: invokespecial #20; //Method ActionId."<init>":(I)V 
+0

Bạn có thể cho một ví dụ khi một lớp giá trị sẽ hộp? – ziggystar

+0

@ziggystar nó sẽ được đóng hộp nếu một người sẽ đặt lớp giá trị vào bộ sưu tập, ví dụ: Danh sách –

2

Với một số tiềm ẩn phôi nó thực sự có thể nhận được xung quanh vấn đề mảng mà không cần cú pháp do rex-kerr yêu cầu. Tôi sử dụng nó kết hợp với How to reduce the number of objects created in Scala?

Y.scala:

import scala.language.implicitConversions 

class Y(val repr: String) extends AnyVal {} 
object Y { 
    implicit def stringToY (v:String) = new Y(v) 
    implicit def yToString (v:Y) = v.repr 
} 

tập tin chi tiết:

import Y._ 

val y1 = new Y("apple") // unboxed 
val y2 = new Y("orange") // unboxed 
val ys: Array[String] = Array(y1, y2) // Implicit cast 
val y3:Y = ys(0) 
Các vấn đề liên quan