如果我有这个值类:
class ActionId(val value: Int) extends AnyVal
然后,在下面的所有例子中,将为值类分配一个对象? (它将被“装箱” - 它将不简单地解包为一个普通的32位整数,对吗?)
一个返回值类的函数 - 值类会转义范围,因此会被“装箱”吗?
def someFunction(): ActionId = {
...
return ActionId(123)
}
一个返回具有值类成员的对象的函数 - 值类会转义范围,因此会被“装箱”吗?
case class Post(id: ActionId, ...) { ... }
def someFunction(): Post = {
...
val somePost = Post(ActionId(123), ...) // ActionId will be "boxed", right?
return somePost
}
即使具有值类成员的对象未返回(并未真正转义范围),值类仍将“装箱” “,当它被用作另一个类的成员时(在此示例中作为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)?
}
与此相关的是this answer,它表示当值类没有超出范围时,它实际上是内联的。它引用了Scala改进流程文档SIP-15以获取更多详细信息。但是据我所知,SIP-15实际上没有提到逃避范围的值类实例将被“装箱”。但我认为它必须被“盒装”似乎是合理的。 (为什么SIP没有明确声明如果它逃脱会被装箱?)
答案 0 :(得分: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
答案 1 :(得分:12)
你的例子都没有导致拳击。值类只包含泛型,带数组,以及输入超类/特征时(例如Any / AnyVal)
它们带有泛型,因为否则你无法将它们与值区分开来(而且原语需要一个盒子)。与Any相同,其他超类/特征需要一个框或类型关系是错误的。
它们装有数组,因为数组需要知道内容的类型,但JVM不理解“值类型”的概念。所以你最终得到一个数组,说它是盒装的类型,但Scala假装的是一个值类型的数组;做出了一个决定(基于以前的问题,当它不仅仅是一个简单的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
答案 2 :(得分:2)
通过一些隐式转换,实际上可以绕过数组问题而不需要rex-kerr所需的语法。我将它与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
}
主档案:
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)