隐藏(hide):子类的某个字段、静态方法、成员内部类与其父类的具有相同名字(对于静态方法还需要相同的参数列表),此时父类对应的字段、静态方法、成员内部类就被隐藏了。
举个例子,天鹅(Swan)是会飞的,而丑小鸭(UglyDuck)小时候是不会飞的,看看下面的代码,看看能够打印出什么。
class Swan {
public static void fly() {
System.out.println("swan can fly ...");
}
}
class UglyDuck extends Swan {
public static void fly() {
System.out.println("ugly duck can't fly ...");
}
}
public class TestFly {
public static void main(String [] args) {
Swan swan = new Swan();
SwanuglyDuck = new UglyDuck();
swan.fly();
uglyDuck.fly();
}
}
按道理的话,我们认为应该是输出两句不同的结果,因为我们可能认为 UglyDuck 继承了 Swan 并且“重写”了 fly()
方法,而且在 main() 方法中 Swan uglyDuck = new UglyDuck(); 也表明了 uglyduck 实际上是 UglyDuck 类型的,因此构成了多态行为。
其实,运行结果是两句“swan can fly ...”,为什么会这样子?原因有下:
1、父类 Swan 中的 static 静态方法 fly() 是不能被重写的,上一段我对重写二字用了双引号;
2、尽管子类 UglyDuck 中的 fly() 方法与父类中的有一致的参数列表,但是对于 static
方法来说,这叫隐藏(hide),而不是重写(override);
3、对于 static 方法,根本不存在像多态那样的动态分派机制,JVM
不会根据对象引用的实际类型来调用对应的重写方法。这一点在个例子中是最重要的。
对于 static 方法,我们称之为类方法,不是实例方法,对
static 方法的调用直接用所属类名加个点就行,如 UglyDuck.fly()
。而实例方法就不得不使用对象引用来获得其可访问方法的调用权。在上面的例子 main() 中的 uglyDuck.fly()
语句,JVM 根本据不会去判断 uglyDuck 引用的究竟是什么类型,既然调用的是 fly() 方法,那么 JVM 只会根据
uglyDuck 的声明类型(即 Swan 类)去获得该 static 方法的调用。根本就谈不上多态…
这就说明,最好避免用对象引用的方式来访问一个
static 方法。此外,别以为在继承关系上的父类、子类只要方法名、参数列表一致就是重写(override)而构成多态,其实还得看看父类中的方法有没有被什么修饰符声明(在这个例子中是
static 修饰的)。再如 final
修饰符的方法则表明不可被子类重写,即方法名、参数列表不能和父类完全一致。在我看来,这一类修饰符就表明了方法、变量、字段等特有的性质,或者是身份。
对于隐藏(hide),实际上是为了使得父类中的该方法、字段、内部类等不允许再被下一级继承树的子子类所继承。说起隐藏,我想起《代码大全
2》当中刚好看过的内容,作者认为把握住信息隐藏的原则来思考软件构建要优于面向对象原则。有点抽象难懂,书中还讲到封装、模块化和抽象等几个概念,建议看看,我也要回过头去多啃啃这些抽象概念。
要修改上面代码,只需要去掉两个 static 则可,那就构成多态了。《Java
解惑》中其他谜题还讲到多种该注意的地方,可以看看。
小结:
1、注意掌握重写(override)与隐藏(hide)的异同点:相同点就是两者都是相对于继承树中父类、子类来说,而不同点就是其目的以及所造成的效果。别把重写和隐藏混淆在一起了;
2、对于 static 方法,要避免用具体的对象引用来调用,而应该简单的用其所属类名进行调用即可。
遮蔽(shadow):其实就是平时我们可能遇到的窄作用域的变量名、方法名、类名等将其他相同名字的变量、方法、类屏蔽掉的现象。
例如,最常见的就是局部变量将类实例变量屏蔽了。其实,遮蔽这个词我之前好像也没什么印象,不过作用域屏蔽这种情况我们大多应该会避免的了,因为课堂上、教材上对于变量作用域的内容已经讲解过了,尽管没有这么一个术语。此时如果想要获得被遮蔽实体的引用、调用,则只能通过完整的限定名去实现了。不过有一些情况可能是根本就引用不到的,被屏蔽得太严密了。
遮掩(obscure):一个变量可以遮掩具有相同名字的一个类,只要它们都在同一个范围内:如果这个名字被用于变量与类型都被许可的范围,那么它将引用到变量上。相似地,一个变量名或一个类名可以遮掩一个包。遮掩是唯一一种两个名字位于不同的名字空间的名字重用形式,这些名字空间包括:变量、包、方法或类。如果一个类型或一个包被遮掩了,那么你不能通过其简单名引用到它,除非是在这样一个上下文环境中,即语法只允许在其名字空间中出现一种名字。遵守命名习惯就可以极大地消除产生遮掩的可能性。
其实,遮掩这个术语我更是完全没听过了,上面这一段是从《Java
解惑》中引用过来的。我觉得,如果代码是一个人所写,或者团队中大家都遵守了一定的命名规范,而且也各自分配了一定职责,那么遮掩这种情况应该是可以避免的。同样,需要使用完全限定名来引用被遮掩掉的实体,如下:
用前面例子的代码大概就是这种情况:
public class TestFly {
// 如此变量名
static String System= "system";
public static void main(String [] args) {
// String System = "hao";
// 编译不通过
// System.out.println("No");
// 编译通过
java.lang.System.out.println("OK");
}
}
小结:
1、我觉得,在文章标题当中的五个名词当中,尤为前面三个最为重要,陷阱炸弹也多多,而且文中所讲仅仅是那么一丁点儿相关的,大量更细节的还得慢慢发现;
2、就算后面两个名词,也就是这两种情况不常见,但了解一下记在脑里还是不错的,毕竟为自己增加了专业词汇量;
3、最后,就是建议各位也看看《Java 解惑》然后也告诉我一些炸弹型陷阱之类的,呵呵...学习快乐!加油!