目录
引言
继承是面向对象的三大特征之一. 也是实现软件复用的重要手段.
java中的子类继承父类的语法如下:
修饰符 class SonClass extends ParentClass
{
//类定义部分
}
一,继承的特点
- Java的继承通过extends关键字实现.
- 实现继承的类被称为子类.
- 被继承的类被称为父类.
- 父类和子类的关系, 是一种一般和特殊的关系.
- 子类继承了父类, 也将获得父类的全部成员变量和方法.
- 但是, Java的子类不能继承父类的构造器.
- 每个子类只有一个直接父类,但是一个父类可以有很多个子类
例如水果和苹果的关系, 苹果继承了水果, 苹果是水果的子类, 水果是苹果的父类.
示例:
我们写一个父类水果
public class Fruit
{
public double weight;
public void info()
{
System.out.println("我是一个水果! 重:" + weight);
}
}
再写一个子类苹果
public class Apple extends Fruit
{
public static void main(String[] args)
{
//创建Apple对象
Apple a = new Apple();
//Apple 对象本身没有 weight 成员变量
//因为Apple 父类有 weight 成员变量, 所以也可以访问 Apple 对象的 weight 成员变量.
a.weight = 100;
//调用 Apple 对象的 info() 方法
a.info();
}
}
输出的结果如下:
二,重写
子类继承了父类, 所以说子类是一个特殊的父类.
大部分时候, 子类总是以父类为基础. 额外增加新的成员变量和方法. 但有一种情况例外: 子类需要重写父类的方法.
例如鸟类都包含了飞翔的方法, 但其中的鸵鸟并不会飞, 因为鸵鸟是鸟的子类, 因此它将从鸟类中获得飞翔的方法, 但这个飞翔的方法显然不适合鸵鸟, 所以鸵鸟这个子类需要重写鸟类(父类)的方法.
示例:
先定义一个 Bird 类
public class Bird
{
//Bird 类的 fly() 方法
public void fly()
{
System.out.println("我在飞翔");
}
}
下面定义一个 Ostrich 类, 这个类继承了 Bird 类, 同时重写 Bird 类的 fly() 方法
public class Ostrich
{
//重写 Bird 类的 fly() 方法
public void fly()
{
System.out.println("NND, 我可飞不了, 虽然我有双翅膀, 啦啦啦");
}
public static void main(String[] args)
{
//创建 Ostrich 对象
Ostrich os = new Ostrich();
//执行 Ostrich 对象的 fly() 方法, 将会输出 "...飞不了..."
os.fly();
}
}
输出结果如下:
执行上面的程序, 将看到执行 os.fly() 时执行的不是 Bird 类的 fly() 方法.
而是执行 Ostrich 类的 fly() 方法.
这种子类包含与父类同名方法的现象称为方法重写(Override). 也被称为方法覆盖.
可以说子类重写了父类的方法, 也可以说子类覆盖了父类的方法, 都行.
方法的重写要遵循两同两小一大规则:
- 两同: 方法名相同 / 形参列表相同
- 两小: 子类方法返回值类型应比父类方法返回值类型小或相等. / 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等.
- 一大: 子类方法的访问权限应比父类方法的访问权限大或相等.
注意:
- 覆盖方法和被覆盖方法要么都是类(被static修饰的)方法, 要么都是实例方法.
- 当子类覆盖了父类方法后, 子类的对象将无法访问父类中被覆盖的方法.
- 但可以在子类方法中调用父类中被覆盖的方法.
- 如果需要在子类方法中调用父类中被覆盖的方法, 则可以使用super(被覆盖的是实例方法) 或者 父类类名(被覆盖的是类方法) 来作为调用者, 调用父类中被覆盖的方法.
如果父类方法具有 private 访问权限, 则该方法对其子类是隐藏的. 因此子类无法访问该方法, 也就无法重写该方法.
如果子类中定义了一个与父类 private 方法具有相同的方法名 / 相同的形参列表 / 相同的返回值类型的方法, 依然不是重写. 这只是在子类中重新定义了一个新的方法.
示例:
class BaseClass
{
//test() 方法是 private 访问权限, 子类不可访问该方法
private void test(){...}
}
class SubClass extends BaseClass
{
//此处并不是方法重写, 所以可以增加 static 关键字
public static void test(){...}
}
三,super
如果需要在子类方法中调用父类被覆盖的实例方法. 则可以使用 super 限定来调用父类被覆盖的实例方法.
super 是 Java提供的一个关键字, super 用于限定该对象调用它从父类继承得到的实例变量或方法.
正如 this 不能出现在 static 修饰的方法中一样, super 也不能出现在 static 修饰的方法中.
static 修饰的方法是属于类的.
该方法的调用者可能是一个类, 而不是对象, 因而 super 限定也就失去了意义.
如果在构造器中使用 super 则 super 用于限定该构造器初始化的是该对象从父类继承得到的实例变量, 而不是该类自己定义的实例变量.
如果子类定义了和父类同名的实例变量. 则会发生子类实例变量隐藏父类实例变量的情形.
在正常情况下, 子类里定义的方法直接访问该实例变量默认会访问到子类中定义的实例变量.
无法访问到父类中被隐藏的实例变量.
在子类定义的实例方法中可以通过 super 来访问父类中被隐藏的实例变量.
示例:
写一个父类:
class ParentClass{
public int a = 5;
}
写一个子类继承父类
public class SonClass extends ParentClass{
public int a = 7;
public void son()
{
System.out.println(a);
}
public void parent()
{
//通过使用 super 来限定访问从父类继承得到的 a 的实例变量
System.out.println(super.a);
}
public static void main(String[] args)
{
SonClass sc = new SonClass();
sc.son(); //输出 7
sc.parent(); //输出 5
}
}
输出结果如下:
上面程序的 ParentClass 和 SonClass 中都定义了名为 a 的实例变量. 则 SonClass 的 a 实例变量将会隐藏ParentClass 的 a 实例变量.
当系统创建了 SonClass 对象时, 实际上会为 SonClass 对象分配两块内存:
- 一块用于存储在 SubClass 类中定义的 a 实例变量.
- 一块用于存储从 BaseClass 类继承得到的 a 实例变量.
如果子类里没有包含和父类同名的成员变量.那么在子类实例方法中访问该成员变量时, 则无需显式使用 super 或 父类名作为调用者.
如果在某个方法中访问名为 a 的成员变量, 但没有显式指定调用者, 则系统查找 a 的顺序为:
- 查找该方法中是否有名为 a 的局部变量.
- 查找当前类中是否包含名为 a 的成员变量.
- 查找 a 的直接父类中是否包含名为 a 的成员变量, 依次上溯 a 的所有父类. 直到 java.lang.Object 类.
- 如果最终不能找到名为 a 的成员变量, 则系统出现编译错误.