9

如果我有这个值类:

class ActionId(val value: Int) extends AnyVal

那么,在下面的所有示例中,都会为值类分配一个对象吗?(它将被“装箱”——它不会被简单地解包为一个普通的 32 位整数,对吗?)

  1. 一个返回值类的函数——值类逃脱了作用域并因此被“装箱”?

    def someFunction(): ActionId = {
      ...
      return ActionId(123)
    }
    
  2. 一个返回具有值类成员的对象的函数——值类逃脱了作用域并因此被“装箱”?

    case class Post(id: ActionId, ...) { ... }
    
    def someFunction(): Post = {
      ...
      val somePost = Post(ActionId(123), ...) // ActionId will be "boxed", right?
      return somePost
    }
    
  3. 即使没有返回具有值类成员的对象(并没有真正脱离范围),值类仍然会被“装箱”,当它用作另一个类的成员时(作为类中的一个字段,在这个例子中)?Post

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

与此相关的是这个答案,它表示当值类没有逃脱范围时,它实际上是被内联的。有关详细信息,请参阅Scala 改进过程文档SIP-15 。然而,据我所知,SIP-15 实际上并没有提到超出范围的值类实例将被“装箱”。但我认为它必须被“装箱”似乎是合理的。(为什么 SIP 没有明确声明如果逃逸会被装箱?)

4

3 回答 3

13

您的所有示例都不会导致拳击。值类仅与泛型、数组以及作为超类/特征类型(例如 Any/AnyVal)一起装箱

它们与泛型一起装箱,因为否则您无法将它们与值区分开来(并且原语无论如何都需要一个盒子)。与 Any 相同,其他超类/特征需要一个框或类型关系错误。

它们用数组装箱,因为数组需要知道内容的类型,但 JVM 不理解“值类型”的概念。所以你最终会得到一个数组,它说它是被装箱的类型,但 Scala 假装是一个值类型的数组;做出了一个决定(基于 Array 之前的问题,当它不仅仅是一个普通的 Java/JVM 数组时)这将导致太多微妙的错误和极端情况。

以下是获得拳击的三种方法的示例:

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

其他一切——通过各种功能传递,等等——不需要装箱,包括你所有的例子。

数组有点特殊,因为您可以存储原语并将它们作为值类再次提取出来,字节码开销为零,但语法开销很大:

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
于 2013-04-07T20:36:28.873 回答
12

在这三种情况下,根本不会有拳击。

自己检查很容易:

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

现在让我们运行 scalac,我们将有一堆类文件(带有字节码的文件)。我们需要的是 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();
}

如您所见,即使值类从函数中泄漏,通常也不会被装箱。

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

如您所见,此处的 id 表示为纯 int(例如第三种方法)。

最后,如果没有返回具有值类成员的对象(并没有真正脱离范围),值类是否会被装箱?

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

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

如果我们仔细查看该方法的字节码,我们将看到以下内容:

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;

ActionId 中没有 int 的装箱。如果它会装箱,您会看到类似这样的内容:

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;

你看,区别bipush 123在于

   4:   new #17; //class ActionId
   7:   dup
   8:   bipush  123
   10:  invokespecial   #20; //Method ActionId."<init>":(I)V
于 2013-04-07T09:45:53.333 回答
3

通过一些隐式转换,实际上可以绕过数组问题,而无需 rex-kerr 所需的语法。我将它与如何减少在 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
}

主文件:

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)
于 2015-04-23T10:36:14.877 回答