In these cases, the Scala value class will be “boxed”, right?

If I have this value class:

class ActionId(val value: Int) extends AnyVal 

Then, in all the examples below, will the object be allocated to the value class? (It will be “in the box” - it will not just expand on a prime 32-bit integer, right?)

  • A function that returns a class of values ​​— does the class of values ​​go out of scope and therefore will be “boxed”?

     def someFunction(): ActionId = { ... return ActionId(123) } 
  • A function that returns an object with a member of the value class — the value class goes out of scope and therefore will be “boxed”?

     case class Post(id: ActionId, ...) { ... } def someFunction(): Post = { ... val somePost = Post(ActionId(123), ...) // ActionId will be "boxed", right? return somePost } 
  • Even if an object with a member of the value class does not return (actually does not go out of scope), the value class will still be “boxed” when it is used as a member of another class (like a field in the Post class, in this example )?

     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 boxed (above)? } 

In this regard, this answer , which says that when the class of values ​​does not go beyond the scope, it actually becomes inline. He refers to the Scala SIP-15 Improvement Process Document for more details. However, as far as I can tell, SIP-15 does not actually mention that an instance of a value class that goes out of scope will be “boxed”. But I think it seems reasonable that it will need to be "inserted into the box." (Why doesn't the SIP explicitly state that it will be inserted into the box if it slips away?)

+6
memory-management scala
source share
3 answers

None of your examples will lead to boxing. Value classes only in a box with generics, with arrays, and when entered as a superclass / attribute (e.g. Any / AnyVal)

They are bundled with generics, because otherwise you will not be able to distinguish them from the value (and primitives need a box). The same goes for Any, and other superclasses / traits require a box or an incorrect type relation.

They are placed in an array with arrays because arrays must know the type of content, but the JVM does not understand the concept of “value type”. That way, you get an array that said it was a type that was boxed, but which Scala was pretending to be an array of type value; a decision was made (based on previous problems with Array when it was not just a simple Java / JVM array) that this would lead to too many subtle errors and angular cases.

Here is an example of three ways to get 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 

Everything else is going through various functions, etc. - does not require boxing, including all your examples.

Arrays are a special case because you can save the primitives and pull them out again as value classes with zero byte overhead, but with syntactic overhead:

 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 
+11
source share

In all three cases, there will be no boxing at all.

It is very easy to check yourself:

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

Now let's run scalac and we will have a bunch of class files (bytecode files). We need 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(); } 

As you can see, even if the class of values ​​leaks out of the function at all, it will not be placed in the box.

 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); } 

As you can see, id is presented here as a simple int (for example, the third method).

Finally, will the boxed class cost if the object with the member of the value class is not returned (really does not go out of scope)?

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

If we carefully consider the byte code of the method, here is what we will see:

 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; 

In ActionId there is no box for int. If it is in the box, you will see something like this:

 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; 

You see, the difference is bipush 123 vs

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

With some implicit drops, you can actually get around the array problem without the syntax required by rex-kerr. I used it in conjunction with 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 } 

Main file:

 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) 
+2
source share

All Articles