想必你已经阅读了一两本这样的Java书籍,它们在开头都指出了面向对象编程的3个主要概念:封装、继续和多态。理解这3个概念对于领会Java 语言来说至关重要,而搞懂方法的覆盖又是理解继续概念的要害部分。
这个例子摘自 Java 语言规范
01: class Super
02: {
03: static String greeting()
04: {
05: return "Goodnight";
06: }
07:
08: String name()
09: {
10: return "Richard";
11: }
12: }
01: class Sub extends Super
02: {
03: static String greeting()
04: {
05: return "Hello";
06: }
07:
08: String name()
09: {
10: return "Dick";
11: }
12: }
01: class Test
02: {
03: public static void main(String[] args)
04: {
05: Super s = new Sub();
06: System.out.println(s.greeting() + ", " + s.name());
07: }
08: }
运行 Test 类的结果如下
Goodnight, Dick
要是你得出了同样的输出结果,那么你或许对方法的覆盖有了较好的理解,假如你的结果和答案不一致,那就让我们一起找出原因,我们先分析一下各个类:Super类由方法 greeting和name组成,Sub 类继续了 Super 类,而且同样含有 greeting 和 name方法。Test 类只有一个 main方法。在 Test 类的第5 行中,我们创建了一个 Sub 类的实例。在这里,你必须明白的是:虽然变量 s的数据类型为 Super 类,但是它仍然是 Sub 类的一个实例,假如你对此有些迷惑,那么可以这样理解: 变量s 是一个被强制转换为 Super 型的Sub 类的实例。
下一行(第 6 行)显示了s.greeting()返回的值,加上一个字符串,紧随其后的是 s.name()的返回值。要害问题就在这里,我们调用的到底是Super类的方法还是Sub类的方法,让我们首先判定调用的是哪个类的name()方法,两个类中的name()方法都不是静态方法,而是实例方法,因为Sub类继续了Super类,而且有一个和它父类同样标识的name()方法,所以Sub类中的name()
方法覆盖了Super类中的name()方法,那么前面提到的变量s又是Sub 类的一个实例,这样一来 s.name()的返回值就是“Dick”了。
至此,我们解决了问题的一半,现在我们需要判定被调用的greeting()方法究竟是Super类的还是Sub类的。需要注重的是,两个类中的greeting()方法都是静态方法,也称为类方法。尽管事实上Sub类的greeting()方法具有相同的返回类型、相同的方法名以及相同的方法参数。然而它并不覆盖Super类的greeting()方法,由于变量s被强制转换为Super型并且Sub类的greeting()方法没有覆盖Super类的greeting()方法,因此 s.greeting()的返回值为Goodnight。
还是很迷惑?请记住这条规则:“实例方法被覆盖,静态方法被隐藏”。
现在你可能会问“隐藏和覆盖有什么区别”你也许还未理解这点。然而实际上我们刚刚在这个Super/Sub 类的例子中已经解释了两者的不同。使用类的全局名可以访问被隐藏的方法,即使变量s是Sub类的一个实例,而且Sub类的greeting()方法隐藏了Super 类的同名方法,我们仍然能够将s强制转换为Super型以便访问被隐藏的greeting()方法,与被隐藏的方法不同,对被覆盖的方法而言,除了覆盖它们的类之外,其他任何类都无法访问它们。这就是为何变量s调用的是Sub类的name(),而非Super类的name()方法。
也许对你来说 理解隐藏静态方法和覆盖实例方法的区别的最佳方式,就是自己创建几个类似于Sub/Super的类,再重复一次规则,实例方法被覆盖而静态方法被隐藏,被覆盖的方法只有覆盖它们的类才能访问它们,而访问被隐藏的方法的途径是提供该方法的全局名。现在你终于明白标题里问题的答案了吧。什么时候“被覆盖的”方法并非真地被覆盖了呢?答案就是“永远不会”。另外,还有几个要点,请谨记:
--试图用子类的静态方法隐藏父类中同样标识的实例方法是不合法的,编译器将会报错
--试图用子类的实例方法覆盖父类中同样标识的静态方法也是不合法的,编译器会报错
--静态方法和最终方法(带要害字final的方法)不能被覆盖
--实例方法能够被覆盖
--抽象方法必须在具体类中被覆盖
现在我们来看继续时变量覆盖和隐藏的问题,假如你认为你已经理解了上面的方法继续时的覆盖和隐藏问题,继而认为变量也如此的话,那么请继续往下看:
Java共有6种变量类型:类变量、实例变量、方法参数、构造函数参数、异常处理参数和局部变量。类变量包括在类中定义的静态数据成员以及在接口中声明的静态或非静态的数据成员。实例变量是在类体中声明的非静态变量,术语“变量成员”指的是类变量和实例变量。方法参数是用来传入方法体的。构造函数参数是用来传入构造函数的。异常处理参数用来传入一个try语句中的catch块的。最后,局部变量是在一个代码块或一个for语句中声明的变量。
class Base {
int x = 1;
static int y=2;
int z=3;
int method() {
return x;
}
}
class Subclass extends Base {
int x = 4;
int y=5;
static int z=6;
int method() {
return x;
}
}
public class Test {
public static void main(String[] args) {
Subclass s=new Subclass();
System.out.println(s.x + " " + s.y +" "+ s.z);
System.out.println(s.method());
Base b = (Subclass)s;
System.out.println(b.x + " " + b.y +" "+ b.z);
System.out.println(b.method());
}
}
运行可以得到输出:
4 5 6
4
1 2 3
4
由此我们可以得出:
实例变量和类变量能被隐藏,被子类的同名变量成员隐藏。局部变量和各种参数永远不会被隐藏(参见下例)。
class Hidden
{
public static void main(String args[])
{
int args=0; //compile error
String s="abc";
int s=10; //compile error
}
}
如何访问被隐藏的变量呢? 使用“this”要害字可以访问被局部变量隐藏的本类中的实例变量,要害字“super”可以访问父类中被隐藏的实例变量,类变量可以用类加“.”来访问。强制转换为父类型。
变量和方法覆盖和隐藏的不同:一个类的实例无法通过使用全局名或者强制自己转换为父类型,以访问父类中被隐藏的方法,然而强制转换子类为父类型之后,可以访问父类中被隐藏的变量。另外静态方法不能覆盖父类的实例方法,而静态变量却可以隐藏父类的一个同名实例变量,同样,实例方法不能覆盖父类的同名静态方法,而变量却可以隐藏父类的同名变量成员,不论父类的这个变量成员是类变量或者是实例变量。