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