关于Scala子类继承父类属性的问题释疑---子类构造器参数不要与父类属性同名

今天和朋友聊到scala继承的时候,发现有一些问题,我也没有想清楚。无论从那个角度想,都觉得有点怪怪的,帮贴出来,跟大家分享一下,帮忙指出理解不到位的地方。

先来说一下问题代码,比较简单,就是一个简单的继承:

class SuperClass(var name:String){

}

class SubClass(name :String) extends  SuperClass(name){

	def updateName(newName:String)={
		name = newName //编译报错
	}
}

这个updateName方法,肯定是编译不过去。 在给name赋值那一行会报错:Reassigment  to val. 问题就这样出来了!


根据《快学scala》上的说的,“统一访问原则”,当直接调用一个属性时,会默认调用他的get方法,当给一个属性赋值是,会调用他的set方法,即_=()。可是这里为什么会报错?


反编译一下代码,如下:

public class SuperClass
{
  private String name;

  public String name()
  {
    return this.name; } 
  public void name_$eq(String x$1) { this.name = x$1; }


  public SuperClass(String name)
  {
  }
}


public class SubClass extends SuperClass
{
  public void updateName(String newName)
  {
  }

  public SubClass(String name)
  {
    super(name);
  }
}

其实这样的Java类,在子类我们肯定是访问不到父类的属性name,但是可以访问他的getter setter方法。但是你访问他的属性的时候,报的是:未定义的属性!这好像和我们scala中提示的错误不是一回事。


有一位朋友说:SubClass中的name重写了SuperClass中的name。那么就有以下几点疑虑:

1. 为何反编译出来的SubClass,没有看到重写迹象?

2.scala明确规定,只要出现override情况,一定要显式申明override,这里并没有

3.假如这种说法成立,那么SubClass中的name的作用域相当于变成了private[this] val,看上去好像是符合逻辑,只能取值,不能赋值。但是接着写了一个测试,发现这种假设也不成立:

object extendsTest extends App{

	val c = new SubClass("aaa")
	println(c.name)
}

看,我们仍然还是可以在外部对象中访问到SubClass的name属性! 那这种说法好像也不成立!


这时候,另外一位朋友,提出这么情况:将构造器的参数换个名字,就OK了,代码如下:

class SuperClass(var name:String){

}

class SubClass(name1 :String) extends  SuperClass(name1){

	def updateName(newName:String)={
		name = newName
	}
}

注意SubClass的参数名叫:name1。发现,现在居然可给name赋值,我们看一下反编译的源代码:

public class SubClass extends SuperClass
{
  public void updateName(String newName)
  {
    name_$eq(newName);
  }

  public SubClass(String name1)
  {
    super(name1);
  }
}

好像确实是符合我们预期了,确实是最能解释我们情况的问题了,事实上问题也这么解决。


----------------------------------------------------------------------------------分割线----------------------------------------------------------------------------------------------


但是,故事还没有就此结束,因为对于我们这些追根溯源的码农来说,怎么会就此罢手列,我们不光要知道How,还要知道Why。


那我们再来一点改造:在updateName里面,打印一下name1。

class SubClass(name1 :String) extends  SuperClass(name1){

	def updateName(newName:String)={
		name = newName
		println(name1)
	}
}

再反编译一下:

public class SubClass extends SuperClass
{
  private final String name1;

  public void updateName(String newName)
  {
    name_$eq(newName);
    Predef..MODULE$.println(this.name1);
  }

  public SubClass(String name1)
  {
    super(name1);
  }
}

这说明了一个情况:我们在构造器里面使用的参数,当有一个以上方法使用到时,将会提升为类成员属性。事实上scala,也确实是这么约定的。


那么想到这,我又跑出了一个想法:如果变量名,仍然还是name,我在update方法里面打印一下name,即println(name),也就是说只调get方法。反编译出来代码会是啥样的列?拭目以待。

public class SubClass extends SuperClass
{
  private final String name;

  public void updateName(String newName)
  {
    Predef..MODULE$.println(this.name);
  }

  public SubClass(String name)
  {
    super(name);
  }
}

果然,跟前面一样,只要参数被一个以上方法使用,就会提升为属性。 这时候的确实是SubClass的name覆盖了SuperClass的name属性。

那么我们是不是可以这样理解: 之所以报错,是因为我们试图在一个方法里面给SubClass赋值时,scala会把name属性提升为SubClass的成员属性。而name的访问域是 private[this] val的,所以赋值会编译不过去!而当我们把name更名为name1时,name便是从SuperClass继承过来的name.因为是var声明,所以赋值操作是没有问题的。罪魁祸首就是:当构造器的参数在一个以上方法中使用时,会自动提升为


所以最终的解决办法就是我们前面标红的:在发生继承时,子类构造器参数的名子不要与父类的属性名同名。


-------------------------------------------------------------------------------------------------再次分割-------------------------------------------------------------------------------------------------------------------------

故事还没有结束,我们还再举个容易出问题的例子来让你看到这个坑:

试着跑下下面这段scala代码:

class SuperClass(var name:String){
  def sayName()={
    println(name + " in SuperClass.")
  }
}

class SubClass(name:String) extends  SuperClass(name){
  def sayNameOther()={
    println(name + " in SubClass.")
  }
}

object TestRunner extends App{
  val s1 = new SubClass("aaa")
  s1.name = "bb"
  s1.sayName()
  s1.sayNameOther()
}

运行一下,看看是不是很容易掉进这个坑里面。 



  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值