注:本文参考《疯狂Java讲义》一书
子类扩展了父类,子类是一个特殊的父类。大部分时候,子类总是以父类为基础,额外增加新的成员变量和方法。但有一种情况例外:子类需要重写父类的方法。例如鸟类都包含了飞翔方法,其中有一种鸟是特殊鸟类——鸵鸟,因此它也会从鸟类获得飞翔方法,但这个飞翔方法明显不适合鸵鸟,所以,鸵鸟需要重写鸟类的方法。
我们先看下面的程序
public class Bird {
//Bird 类的fly()方法
public void fly()
{
System.out.println("我在天空可劲的飞啊");
}
}
public class Ostrich {
// 重写Bird类的fly()方法
public void fly() {
System.out.println("我能在地上可劲跑");
}
public static void main(String[] args) {
// 创建Ostrich对象
Ostrich os = new Ostrich();
// 执行Ostrich对象的fly()方法,将输出“我在地上可劲的跑”
os.fly();
}
}
我能在地上可劲跑
可见上面的程序执行之后,执行os.fly()时执行的不再是Bird类的fly()方法,而是执行Ostrich类的fly()方法。
这种子类包含与父类同名方法的现象被称为方法重写(Override),也被成为方法覆盖。可以说子类重写了弗雷德方法,也可以说子类覆盖了父类的方法。
这里有个需要注意的地方,方法的重写要遵循“两同两小一大”规则:
另外还有个需要和注意的地方就是:覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法(static),一个是实例方法。
super 是 Java 提供的一个关键字, super 用于限定该对象调用它从父类继承得到的实例变量或方法。
super不能出现在static修饰的方法中:
如果子类定义了和父类同名的实例变量,则会发生子类实例变量隐藏父类实例变量的情形。在正常情况下,子类里定义的方法直接访问该实例变量默认会访问到子类中定义的实力变量,无法访问到父类中被隐藏的实例变量。在子类定义的实例方法中可以通过super来访问父类中被隐藏的实例变量,如上段代码。
我们对上稍作修改,看一下super怎么用
Bird类不变,所以在此不再贴代码了。
public class Ostrich extends Bird {
// 重写Bird类的fly()方法
public void fly() {
super.fly(); //①
System.out.println("我能在地上可劲跑");
}
public static void main(String[] args) {
// 创建Ostrich对象
Ostrich os = new Ostrich();
// 执行Ostrich对象的fly()方法,将输出“我在地上可劲的跑”
os.fly();
// super.fly(); //②
}
}
在删除1 而加上2时 会报错,而加上1 删除2 程序会正确运行
我在天空可劲的飞啊
我能在地上可劲跑
这是为什么?
This和super都不能在main()方法中使。
为了更加进一步了解它,我们看看下面的代码:
public class BaseClass {
public int a = 5;
}
public class SubClass extends BaseClass {
public int a = 7;
public void accessOwner() {
System.out.println(a);
}
public void accessBase() {
// 通过super来限定访问从父类继承得到的a实例变量
System.out.println(super.a);
}
public static void main(String[] args) {
SubClass sc = new SubClass();
sc.accessOwner();// 输出7
sc.accessBase();// 输出5
}
}
如果子类里没有包含和父类同名的成员变量,那么在子类实例方法中访问该成员变量时,则无需显示使用super或父类名作为调用者。如果在某个方法中访问名为a的成员变量,但没有显示指定调用者,则系统查找a的顺序为:
1 .查找该方法中是否有名为a的局部变量。
2. 查找当前类中是否包含名为a的成员变量。
3. 查找a的直接父类中是否包含名为a的成员变量,依次上溯a的所有父类,直到 java.lang.Object 类,如果最终不能找到名为a的成员变量,则系统出现编译错误。
注意:
如果在子类里定义了与父类中已有变量同名的变量,那么子类中定义的变量会隐藏父类中定义的变量。注意不是完全覆盖,因此系统在创建子类对象时,依然会为父类中定义的、被隐藏的变量分配内存空间。
由之前的文章我们知道了,子类不会获得父类的构造器,但是子类构造器可以调用父类构造器的初始化代码,类似于前面介绍的一个构造器调用另一个重载的构造器(用this)
class Test
{
public double size;
public String name;
public Test(double size , String name)
{
this.size = size;
this.name = name;
}
}
public class Sub extends Test {
public String color;
public Sub(double size, String name, String color) {
// 通过super调用来调用父类构造器的初始化过程
super(size, name);
this.color = color;
}
public static void main(String[] args) {
Sub s = new Sub(8.1, "测试", "棕色");
// 输出Sub对象的三个实例变量
System.out.println(s.size + "--" + s.name + "--" + s.color);
}
}
8.1–测试–棕色
从上面的代码我们可以看出来,使用super调用恶化使用this调用也很像,区别就在于super调用的是其父类的构造器,而this嗲用的是同一个类中重载的构造器。因此 ,使用super嗲用父类构造器也是必须出现在子类构造器执行体的第一行,所以this调用和super调用不会同时出现。
子类构造器调用父类构造器有如下几种情况。