如何在 Scala 中获取类型的默认值?

Posted

技术标签:

【中文标题】如何在 Scala 中获取类型的默认值?【英文标题】:How can I obtain the default value for a type in Scala? 【发布时间】:2011-07-12 17:32:08 【问题描述】:

我正在尝试编写一个 Scala 函数,该函数返回一个类型的默认值(值类型为 0、0.0、false、'\0' 等,引用类型为 null)。我想出了这个:

def defaultValue[U]: U = 
  class Default[U]  var default: U = _ 
  new Default[U].default

虽然如果直接调用它会很好地工作,但当通过本身是通用的函数调用时,即使对于值类型,它也会返回 null,如此 REPL 会话中所示:

Welcome to Scala version 2.8.1.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_24).
Type in expressions to have them evaluated.
Type :help for more information.

scala> def defaultValue[U]: U =  class Default[U] var default: U = _ ; new Default[U].default 
defaultValue: [U]U

scala> defaultValue[Boolean] // direct call works
res0: Boolean = false

scala> var res: Any = 0
res: Any = 0

scala> def setRes[U] =  res = defaultValue[U]; defaultValue[U] 
setRes: [U]U

scala> setRes[Boolean] // returns a Boolean, but...
res1: Boolean = false

scala> res
res2: Any = null // ... sets the res variable to null.

谁能给我解释一下:

    为什么会发生这种情况(以及为什么编译器/解释器在没有足够的信息返回真正的布尔值时不会抱怨);和 我该如何解决?

【问题讨论】:

我找到了一种使用 ClassManifests 并匹配擦除的方法,但它看起来并不漂亮。 谁愿意赌原因不是类型擦除?无论如何,既然你只有有限的许多已知的期望行为,为什么不对其进行硬编码呢?丑陋,是的,但可能是最有效的。 【参考方案1】:

这是您问题的更精简版本:

scala> defaultValue[Boolean]: Any
res0: Any = null

scala> defaultValue[Boolean]: Boolean
res1: Boolean = false

第一个版本适用于您调用 res = defaultValue[U] 时,因为即使 U 是布尔类型,res 也是 Any

如果你使用-Xprint:all 选项编译这个小程序

object Test 
  def defaultValue[U]: U =  class Default[U] var default: U = _ ; new Default[U].default 

  def main(args:Array[String]) 
    val any = defaultValue[Boolean]: Any
    println(any)
    val bool = defaultValue[Boolean]: Boolean
    println(bool)
  

您会在擦除阶段之前看到:

val any: Any = (Test.this.defaultValue[Boolean](): Any);
scala.this.Predef.println(any);
val bool: Boolean = (Test.this.defaultValue[Boolean](): Boolean);
scala.this.Predef.println(bool)

然后在擦除阶段结束时:

val any: java.lang.Object = (Test.this.defaultValue(): java.lang.Object);
scala.this.Predef.println(any);
val bool: Boolean = (scala.Boolean.unbox(Test.this.defaultValue()): Boolean);
scala.this.Predef.println(scala.Boolean.box(bool))

所以发生的情况是 defaultValue[Boolean] 在这两种情况下都返回 null,但是当返回类型是布尔值时,null 被拆箱为 false。您可以在 REPL 中验证:

scala> Boolean.unbox(null)
res0: Boolean = false

scala> null.asInstanceOf[Boolean]
res1: Boolean = false

编辑:我有一个想法 - 不是我推荐它。不确定您的用例是什么(res = false 对我来说似乎更容易......)

scala> def f[@specialized U] =  class X  var x: U = _ ; (new X).x 
f: [U]U

scala> var res: Any = _
res: Any = null

scala> def g[@specialized U] =  res = f[U]; f[U] 
g: [U]U

scala> g[Boolean]
res0: Boolean = false

scala> res
res1: Any = false

【讨论】:

谢谢,这是一个很好的解释为什么。现在为什么编译器不能在编译时告诉我更多信息?就像,看人,你想要一个布尔值,但在这里我可能会给你空值,抱歉? 不确定def f[T]: T 的其他用例不是您所拥有的。没有任何传递的参数很难实现 T 。所以可能没有警告,因为没有人想过这样做。 @specialized 技巧很好,但很危险,因为一旦您从非专门的上下文中包装对 f 的调用,它就会崩溃。这可能会导致难以追踪的错误。 所以简单地调用 null.asInstanceOf[B] 应该可以完美地完成这项工作。【参考方案2】:

为了记录,这是我发现(目前)唯一可以使这项工作可靠的方法。欢迎改进。

def defaultValue[T: ClassManifest]: T = classManifest[T].erasure.toString match 
  case "void" => ().asInstanceOf[T]
  case "boolean" => false.asInstanceOf[T]
  case "byte" => (0: Byte).asInstanceOf[T]
  case "short" => (0: Short).asInstanceOf[T]
  case "char" => '\0'.asInstanceOf[T]
  case "int" => 0.asInstanceOf[T]
  case "long" => 0L.asInstanceOf[T]
  case "float" => 0.0F.asInstanceOf[T]
  case "double" => 0.0.asInstanceOf[T]
  case _ => null.asInstanceOf[T]

我知道即使T <: NotNull 我也会得到空值,这是一个问题。再说一次,用_NotNull 子类初始化变量存在问题。

【讨论】:

【参考方案3】:

我知道已经有“最佳答案”,但真正简单的呢:

def defaultValue[U: ClassManifest]: U = new Array[U](1)(0)

它似乎有效,尽管由于创建了一个临时数组对象而有点昂贵。有谁知道它给出错误值的任何情况?

我一直在寻找“更便宜”的替代品,但这个问题告诉我可能没有。

【讨论】:

【参考方案4】:

我写了一篇关于为 Scala 构建默认机制的博文。你可以找到它here。

如果您不希望 Option[_] 默认为 NoneString"" 等,则从对象 Default 中删除相应的隐含。

【讨论】:

【参考方案5】:

您可以创建自己的Default type-class 来处理这个问题。这是代码的样子。我为返回空集合而不是 null 的 scala 集合添加了特殊处理。

import scala.collection.immutable

class Default[+A](val default: A)

trait LowerPriorityImplicits 
  // Stop AnyRefs from ***ing with AnyVals
  implicit def defaultNull[A <: AnyRef]:Default[A] = new Default[A](null.asInstanceOf[A])  


object Default extends LowerPriorityImplicits 
  implicit object DefaultDouble extends Default[Double](0.0)
  implicit object DefaultFloat extends Default[Float](0.0F)
  implicit object DefaultInt extends Default[Int](0)
  implicit object DefaultLong extends Default[Long](0L)
  implicit object DefaultShort extends Default[Short](0)
  implicit object DefaultByte extends Default[Byte](0)
  implicit object DefaultChar extends Default[Char]('\u0000')
  implicit object DefaultBoolean extends Default[Boolean](false)
  implicit object DefaultUnit extends Default[Unit](())

  implicit def defaultSeq[A]: Default[immutable.Seq[A]] = new Default[immutable.Seq[A]](immutable.Seq())
  implicit def defaultSet[A]: Default[Set[A]] = new Default[Set[A]](Set())
  implicit def defaultMap[A, B]: Default[Map[A, B]] = new Default[Map[A, B]](Map[A, B]())
  implicit def defaultOption[A]: Default[Option[A]] = new Default[Option[A]](None)

  def value[A](implicit value: Default[A]): A = value.default

这些是在 repl 中使用 this 的结果。请注意,String 的默认值可以通过创建新的隐式 Default[String] 来覆盖。

scala> Default.value[Int]
res0: Int = 0

scala> Default.value[Boolean]
res1: Boolean = false

scala> Default.value[String]
res2: String = null

scala> Default.value[Set[Int]]
res3: Set[Int] = Set()

scala> Default.value[immutable.Seq[Int]]
res4: scala.collection.immutable.Seq[Int] = List()

scala> Default.value[String]
res5: String = null

scala> Default.value[AnyRef]
res6: AnyRef = null

scala> implicit val emptyStringAsDefault:Default[String] = new Default[String]("")
emptyStringAsDefault: Default[String] = Default@7d78d7b4

scala> Default.value[String]
res7: String = ""

【讨论】:

以上是关于如何在 Scala 中获取类型的默认值?的主要内容,如果未能解决你的问题,请参考以下文章

Scala学习一

Scala:如何使用默认值初始化对象

Scala 自定义集合返回 null 列表作为默认值

在 pyspark 的 Scala UDF 中使用默认参数值?

scala 基础四 scala 的函数扩展 默认参数,不定长参数,带名参数

Scala:使用具有默认值的 HashMap