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

后端 未结 3 636
-上瘾入骨i
-上瘾入骨i 2021-01-02 13:51

If I have this value class:

class ActionId(val value: Int) extends AnyVal

Then, in all the examples below, an object will be allocated for

相关标签:
3条回答
  • 2021-01-02 14:28

    With some implicit casts it is actually possible to get around the array-issue without the syntactic 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)
    
    0 讨论(0)
  • 2021-01-02 14:44

    None of your examples result in boxing. Value classes are only boxed with generics, with arrays, and when typed as a superclass/trait (e.g. Any/AnyVal)

    They're boxed with generics because otherwise you can't distinguish them from the value (and primitives need a box anyway). Same deal with Any, and other superclasses/traits need a box or the type relationship is wrong.

    They're boxed with arrays because arrays need to know the type of the contents, but the JVM doesn't understand the concept of a "value type". So you'd end up with an array that said it was the type being boxed, but which Scala was pretending was an array of the value type; a decision was made (based on previous issues with Array when it wasn't just a plain Java/JVM array) that this would lead to too many subtle bugs and corner cases.

    Here's an example of the 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--passing around through various functions, etc.--doesn't require boxing, including all of your examples.

    Arrays are a bit of a special case because you can store the primitives and pull them out again as value classes with zero bytecode overhead, but a lot of 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
    
    0 讨论(0)
  • 2021-01-02 14:46

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

    It's quite easy to check by your self:

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

    Now lets run scalac, and we will have a bunch of class files (files with bytecode). The one that we need is 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 value class leaks from function generally it wouldn't be boxed.

    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 here represented as plain int (e.g. third method).

    Finally, will value class boxed if the object with a value class member is not returned (doesn't really escape the scope)?

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

    If we look closely at bytecode 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;
    

    There is no boxing of int in ActionId. If it would box you would see something like this one:

    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 versus

       4:   new #17; //class ActionId
       7:   dup
       8:   bipush  123
       10:  invokespecial   #20; //Method ActionId."<init>":(I)V
    
    0 讨论(0)
提交回复
热议问题