Scala构造参数传递机制及对象存储构造参数原理咨询
先直接回应你的核心疑问:Scala的参数传递是值传递,而构造参数能在方法中被访问,是因为编译器自动将被引用的构造参数提升为了类的实例字段,而非像C++那样仅作为构造函数的局部变量。下面结合你的代码逐一拆解:
1. 为什么Scala构造参数s能在width方法中被调用?
你定义的LineElement类主构造参数s,虽然没有显式用val或var修饰,但因为在width方法中被引用了,Scala编译器会自动做一个"提升"操作:将s转化为类的私有不可变实例字段(等价于private val s: String)。
换句话说,编译器会把你的代码:
class LineElement(s: String) extends Element { var contents = Array(s) override def width = s.length // ...其他方法 }
悄悄改写为:
class LineElement(private val s: String) extends Element { var contents = Array(s) override def width = s.length // ...其他方法 }
这个s字段会被保存在LineElement对象的内存布局中,和普通的实例成员一样,不会在构造函数执行完毕后销毁——这和C中构造函数的局部变量有本质区别,C的局部变量会在构造函数栈帧销毁后被释放,自然无法在后续方法中访问。
2. 为什么修改contents后,s.length依然是3?
这里涉及两个关键点:
- Scala的String是不可变对象:你最初传递的
s是字符串"foo",这个对象一旦创建就无法被修改。 contents是var类型的引用:当你调用change_contents("abcd")时,只是让contents指向了一个新的数组Array("abcd"),但原来的s依然指向最初的"foo"字符串对象。
所以width方法返回的s.length始终是3,而w方法取的是contents(0).length,此时contents(0)是新的字符串"abcd",所以长度变成4——这完全符合你的运行结果。
3. Scala的参数传递到底是值传递还是引用传递?
结论:Scala只有值传递,但需要区分两种情况:
- 对于基本数据类型(如
Int、Boolean),传递的是值的副本,修改参数不会影响原变量。 - 对于引用类型(如
String、Array、自定义类对象),传递的是对象引用的副本:- 如果修改的是引用指向的对象内容(比如数组的元素),会影响原对象;
- 如果修改的是参数本身的引用(比如让参数指向一个新对象),不会影响原变量。
回到你的代码,创建LineElement("foo")时,传递的是"foo"这个字符串对象的引用副本,这个副本被赋值给构造参数s,随后被提升为实例字段保存下来。而contents = Array(s)则是创建了一个新数组,数组的第一个元素是s指向的"foo"引用。后续修改contents只是让它指向新数组,并不会改变s的引用指向。
内容的提问来源于stack exchange,提问作者hello_god

