5

我有一个包装类,可以存储 Long、Double 或 Boolean 原始值(以及为简单起见我已删除的其他一些内容)。我最初的幼稚实现只是将包装的值存储在 Any 类型的字段中,这导致值被装箱。

为了消除装箱并减少内存使用,我尝试使用泛型,但了解到由于类型擦除,这不会保存任何东西。所以我尝试使用@specialized,但得到了令人惊讶的结果。

以下代码使用 scalac 2.9.3 构建并在 JDK7 上运行。MemoryMeasurer 来自这里,我相信它是准确的。“填充”字段不重要;我只是用它来将基础对象(没有包装的值)填充到 16 个字节,所以我的各种尝试的效果更加清晰。

import objectexplorer.MemoryMeasurer

class GenericNonSpecialized[A] (wrapped: A, val padding: Int) {
  def getWrapped: Any = wrapped
}

class GenericSpecialized[@specialized(Long, Double, Boolean) A] (wrapped: A, val padding: Int) {
  def getWrapped: A = wrapped
}

class GenericSpecializedVal[@specialized(Long, Double, Boolean) A] (val wrapped: A, val padding: Int) {
  def getWrapped: A = wrapped
}

class NonGeneric(val wrapped: Long, padding: Int) {
}

object App {
  def main(args: Array[String]) {
    println(MemoryMeasurer.measureBytes(new GenericNonSpecialized(4L, 0)))
    // Expect: 48: NonSpecialized object (24 bytes) + boxed long (24 bytes)
    // Actual: 48

    // I expect all of the below to be 24 bytes: Object overhead (12 bytes) + Long (8 bytes) + Int (4 bytes),
    // but only the non-generic one is actually 24 bytes.

    println(MemoryMeasurer.measureBytes(new GenericSpecialized(4L, 0))) // 56

    println(MemoryMeasurer.measureBytes(new GenericSpecializedVal(4L, 0))) // 32

    println(MemoryMeasurer.measureBytes(new NonGeneric(4L, 0))) // 24
  }
}

问题:

  1. 如何创建一个使用 24 字节的通用包装器对象,如非通用等价物?(我最好的尝试,GenericSpecializedVal 使用 32)
  2. 为什么 GenericNonSpecialized 使用 56 个字节,但如果我添加“val”,将“包装”到实际字段中,它会下降到 32 个字节?
4

1 回答 1

7

不幸的是,特殊类继承自它们的非特殊父类,并且该类包含用于盒装副本的存储空间。因此,简短的回答是您不能以这种方式形成有效的包装器。

您可以在特征中声明数据:

trait Boxer[@specialized A]{ def boxed: A }

然后手动提供实现:

class BoxerInt(val boxed: Int) extends Boxer[Int]
class BoxerDouble(val boxed: Double) extends Boxer[Double]

然后编写 Boxer 伴侣来重载 apply 方法:

object Boxer {
  def apply(i: Int) = new BoxerInt(i)
  def apply(d: Double) = new BoxerDouble(d)
}

这样你就可以让它看起来你不需要做所有的工作:

val box = Boxer(5.0)

但它仍然不能与专业化的其他用途完全无缝(特别是在通用上下文中创建总是一个问题)。

于 2013-05-03T18:01:14.760 回答