值类型与数组

在之前的值类型的一些细节 里说过“值类型在赋值给数组时会被装箱”,这个不严谨,当时上下文是自定义的值类型会被装箱,而系统原生的几个值类型不会,因为它们有默认值。

比如原生的Int,Unit,Double 等,创建好数组后,都以它们的默认值来填充的:

// Int的默认值是0
scala> val arr = new Array[Int](1)
arr: Array[Int] = Array(0)

// Unit的默认值是()
scala> val arr = new Array[Unit](1)
arr: Array[Unit] = Array(())

// Double的默认值是0.0
scala> val arr = new Array[Double](1)
arr: Array[Double] = Array(0.0)

而自定义的值类型,在创建一个数组后,则是用null填充的:

// 定义值类型A
scala> class A(val str:String) extends AnyVal
defined class A

// 创建A类型的数组,默认用null填充
scala> val arr = new Array[A](1)
arr: Array[A] = Array(null)

或许会怀疑上面的例子里,A内部包的是一个String类型,这是个引用类型,null是String的默认值,所以用null填充

// 把A内部换成Int
scala> class A(val i:Int) extends AnyVal
defined class A

// 默认还是用null填充,而不是Int的默认值0
scala> val arr = new Array[A](1)
arr: Array[A] = Array(null)

所以,自定义的值类型数组初始值都是null,与值类型的内部数据无关。
因为自定义的值类型赋给数组时会装箱,它们都是被当作引用来对待的。

但对值类型赋值时是不能用null来赋值的,不管是系统原生的值类型还是自定义的值类型。

// 对刚才的值类型数组的元素赋值
scala> arr(0) = null
<console>:10: error: type mismatch;
 found   : Null(null)
 required: A
          arr(0) = null
                   ^

这点初看有些矛盾,自定义值类型数组,既然都被装箱对待,且初始值为null,却又不允许用null来赋值?

展开看,在数组初始化时它被装箱为引用类型,访问arr(0)时,它是引用类型,值为null;而修改arr(0)时编译器又严格的按照值类型对待,不管它其实已经被装箱为引用类型,不能把null赋给它。 虽然别扭,但对于赋值操作编译器还是保持简单一致的原则。

赋值时把null赋给值类型的变量,编译器无法把Null类型造型为值类型A,这点参考:“Null与Nothing,造型问题” 一文

数组中对值类型元素初始化都做装箱处理,但对var变量默认赋值时却不是这样

scala> class A(val i:Int) extends AnyVal

scala> var a:A = _
a: A = A@0 //默认赋值是 A(0)

scala> a == 0
res0: Boolean = false

scala> a == new A(0)
res1: Boolean = true

默认值是值类型内部数据的默认值, 内部数据如果是引用类型,初始值为A(null),注意在验证时容易碰到scala的一个bug

scala> class A(val s:String) extends AnyVal

// repl下触发scala的一个bug,实际这条语句是没有问题的
scala> var a:A = _  
java.lang.NullPointerException
    at A$.hashCode$extension(<console>:7)
    at A.hashCode(<console>:7)

可以放到文件里编译,运行来看:

class A(val s:String) extends AnyVal

object Main extends App{
  var a:A = _
  println(a == null)
  println(a.s == null)
}

Leave a Reply

Your email address will not be published. Required fields are marked *