论坛首页 Java版

小心重写方法,正确实现多态

浏览 4478 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
时间:2005-04-25
这是我今天在工作中碰到的问题,是关于继承和多态的。同事对项目中的一项基础功能进行了重构,可是当我们从CVS服务器上更新了项目源代码并编译了之后,发现这项功能已不能正常工作了。先撇开这个同事所犯的错误(对代码进行重构后没有测试他的新代码就上传到了CVS服务器上)不说,在这里我就说说这个问题所带出来的JAVA konwhow.

由于我们的项目比较复杂,我在这里采取比较简单的例子来讲解这个问题。首先,我们有2个类:supper.TestSupper.java 和sub.TestSub.java。他们的代码是这样的:

package supper;

public class TestSupper {
String getString()
{
return "This is supper class.";
}
}
-----------------------------------
package sub;

import supper.TestSupper;

public class TestSub extends TestSupper {
public String getString(){
return "This is sub class.";
}
}

十分简单,第一眼看上去,你会觉得TestSub继承了TestSupper并且重写getString()方法。
现在我们写一个测试程序:
package supper;

import sub.TestSub;

public class Test {
public static void main(String[] args) {
TestSupper test = new TestSub();
System.out.println(test.getString());
}
}

请注意啊!测试程序和TestSupper在同一个包里。从理论上说,这个测试程序应该输出“This is sub class”,因为test的实体是一个TestSub对象而不是TestSupper。所以,当我们调用test.getString()时,真正被调用的应该是TestSub里的getString()。可是事实如何呢?输出是"This is supper class."!为什么会这样的?

原因很简单,因为在TestSupper里方法getString()的标签(signature)是"default",是默认的不用写出来。这导致这个方法只能在这个包里面可见。TestSub虽然继承了TestSupper,却没有办法“看见”getString()方法,因为TestSub在另外一个包里。所以当我们在测试程序里调用test.getString(),程序首先会寻找TestSub中是否重写了这个方法,在这里请一定要注意标签是default的,当然是没有发现。结果程序就会调用父类的相应方法,故父类中的结果就被输出了。

解决的办法很简单,但凡是要被重写的方法一定不能定义成“default”,最少要定义成"protected”.

如果你是在使用Eclipse的话,你可以在eclipse中进行设置,把这种情况视为Error就可以避免这种错误的产生。
方法是:window ->preferences -> style ->methods overridden but not pachage visible这项选为Error。我用的是eclipse3.0。

后话
别看这是个很小的问题,而且很简单,一看就明白,可是当系统出现了问题,而你要在几百个类中寻找到问题所在的时候,这种不易察觉的错误绝对是致命的。寻找这个错误花了我们2个人天!可怕吧!

请关注我的blog
http://blog.csdn.net/schnell/ ;
   
时间:2005-04-25
问题虽小,不过确实值得注意.一般都把是否能重写某些方法的注意力集中在方法的参数上了,还真没怎么想这个signature的.也可能是一般都不声明用default的方法吧
   
0 请登录后投票
时间:2005-04-25
仔细一看就知道了。。

不过还是要注意。
   
0 请登录后投票
时间:2005-04-26
的确值得注意。

不过我觉得这个错误出现可能是因为你们的项目中有滥用继承的嫌疑。

许多know how都指出,继承是不该被滥用的,不应该随意地对一个普通concrete class进行继承。如果一个类允许被继承,那么它应该对继承作出承诺,包括:
1.设定允许改写的方法为protected
2.设定允许访问的域为protected(不过一般不鼓励这么做,都是设定一个final protected的getter方法)
3.设定不允许修改的骨干方法为final

总之,继承应该是受限而作好准备的,绝不允许随意继承。尤其要禁止的是子类随意地涂改父类的行为。我觉得在你们的项目中,getString方法明显是为包内其他成员调用而提供的方法,而非为继承作准备。
   
0 请登录后投票
时间:2005-04-28
在重构方法中,最禁忌的就是随便更改接口定义(这里所说的接口是指被外界程序使用的API),一般来说,要更改接口API定义的时候,都必须首先在某几个版本范围内提供一定的时间,并注释为@deprecated,能够让外面调用者知道该方法已经作废(编译时会出现警告),会在一定时间后被替代(这也是所有API发布的一个标准)。
很多人喜欢重构,但是如果盲目放大重构,带来的就不是优势,而是问题了(因为版本控制、个人模块权限的原因,不是每个人都可以访问所有的程序或者说可以随意更改所有的程序。)
楼上的这个帖子,可以说既不应该是问题,却又是很多人容易碰到的问题
   
0 请登录后投票
时间:2005-04-28
受教~
   
0 请登录后投票
时间:2005-04-29
若你用的是eclipse3的话,只能说你没有充分利用ide提供的功能,在这种情况下,ide会有警告,而真正实现多态的方法在大纲视图旁会有一个绿色的三角箭头
   
0 请登录后投票
时间:2005-05-02
用任何IDE工具进行完全重构的前提是你拥有所有的代码并且拥有全部的权限.我想问楼上,如果你是做组件的,做中间件的,当你发布一个版本供业务软件系统使用后,难道你拥有全公司所有的业务软件系统的源代码,并且拥有全部自由修改的权限(如果你要改类名,连配置管理库的所有读写删除操作都必须赋予给你),好让你可以一次性重构你组件中的某一个接口的定义从而改变所有代码么? 如果你可以做到这些,那么自然是没有问题了!
如果你是超级管理员,全公司就你一个人可以这么做,那还好;如果你只是普通的程序员,全公司有N个像你这样拥有权限的人,那么公司就真的惨了.
   
0 请登录后投票
时间:2005-05-02
   
0 请登录后投票
时间:2005-05-03
gust 写道
若你用的是eclipse3的话,只能说你没有充分利用ide提供的功能,在这种情况下,ide会有警告,而真正实现多态的方法在大纲视图旁会有一个绿色的三角箭头


在大项目中,warnning只会在优化时才会被关注,平常开发时没有人有时间去注意他们的
   
0 请登录后投票
论坛首页 Java版

跳转论坛: