Java继承
类 超类 子类
已存在的类称为超类( superclass )、基类 ( base class ) 或父类 ( parent class ) ; 新类称为子类 ( subclass )、派生类( derived class ) 或孩子类 ( child class )
Java通过extends关键字来实现继承
基本语法:
访问修饰符 class SubClass extends SuperClass
{
}
下面展示水果和苹果之间的继承关系
public class Fruit {
public double weight;
public void info()
{
System.out.println("我是一个水果!重"
+ weight + "g!");
}
}
public class Apple extends Fruit{
public static void main(String[] args)
{
// 创建Apple对象
Apple a = new Apple();
// Apple对象本身没有weight成员变量
// 因为Apple的父类有weight成员变量,也可以访问Apple对象的weight成员变量
a.weight = 56;
// 调用Apple对象的info()方法
a.info();
}
}
重写父类的方法
比如:
public class Bird {
// Bird类的fly()方法
public void fly()
{
System.out.println("天空飞翔...");
}
}
public class Ostrich extends Bird{
// 重写Bird类的fly()方法
public void fly()
{
System.out.println("地上奔跑...");
}
public void callOverridedMethod()
{
// 在子类方法中通过super来显式调用父类被覆盖的方法。
super.fly();
}
public static void main(String[] args)
{
// 创建Ostrich对象
Ostrich os = new Ostrich();
// 执行Ostrich对象的fly()方法,将输出"我只能在地上奔跑..."
os.fly();
}
}
子类包含与父类同名方法的现象就是方法重写,也叫作方法覆盖
方法重写应该遵守的原则:
-
方法名相同,形参列表相同
-
子类方法返回值类型应该比父类返回值类型更小或者一样,子类方法声明抛出的异常类应该比父类方法声明抛出的异常类更小或者一样
-
子类方法的访问权限应该比父类方法的访问权限更大或者一样
-
覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法一个是实例方法
当子类覆盖了父类方法之后,子类对象无法访问父类中被覆盖的方法,但可以在子类方法中调用父类中被覆盖的方法。
如果父类的某个方法使用private修饰,那么该方法子类无法重写。
也就是说如果在子类中定义了一个与父类priva方法具有相同方法名,相同形参列表,相同的返回值类型的方法,这就不是重写了。只是在子类中定义了一个新的方法
如:
public class A {
private void f(String s){
System.out.println(s);
}
}
public class Sub_A extends A{
// 此处就不是方法重写了
public static void f(String s){
System.out.println(s);
}
}
Super关键字
要在子类方法调用父类被覆盖的实例方法,就可以用super关键字
super用于限定对象调用从父类继承得到的实例变量或者方法,所以super不能出现在static修饰的方法中
如果你在子类定义了和父类同名字的实例变量,那么子类的实例变量会隐藏父类中的实例变量
也就是说你在子类中使用该实例变量默认使用的是子类中定义的,而不是父类中定义的实例变量
所以可以用super解决这个问题
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
}
}
如果在某个方法中访问名为a的成员变量,但没有显式指定调用者,则查找顺序为:
-
查找该方法中是否有名为a的局部变量
-
查找当前类中是否包含名为a的成员变量
-
查找a的直接父类中是否包含名为a的成员变量,不断往上回溯,直到Object类,如果最终没有找到,编译器报错
创建一个子类对象是,系统除了为该类中定义的实例变量分配内存,也会为从父类继承得到的所有实例变量分配内存,即使同名
特殊情况:
class Parent
{
public String tag = "parent"; //①
}
class Derived extends Parent
{
// 定义一个私有的tag实例变量来隐藏父类的tag实例变量
private String tag = "Derived"; //②
}
public class HideTest
{
public static void main(String[] args)
{
Derived d = new Derived();
// 程序不可访问d的私有变量tag,所以下面语句将引起编译错误
// System.out.println(d.tag); //③
// 将d变量显式地向上转型为Parent后,即可访问tag实例变量
System.out.println(((Parent)d).tag); //④
}
}
调用父类构造函数
一个构造函数中调用另一个重载的构造函数使用this,在子类构造函数中调用父类构造函数使用super
class Base
{
public double size;
public String name;
public Base(double size , String name)
{
this.size = size;
this.name = name;
}
}
public class Sub extends Base
{
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(5.6 , "World" , "红色");
// 输出Sub对象的三个实例变量
System.out.println(s.size + "--" + s.name
+ "--" + s.color);
}
}
super调用的是父类的构造函数,this调用的是同一个类中的重载的构造函数
使用super调用父类构造函数也必须出现在子类构造函数中的第一行
如果子类的构造器没有显式地调用超类的构造器 , 则将自动地调用超类默认 ( 没有参数 )的构造器。如果超类没有不带参数的构造器 , 并且在子类的构造器中又没有显式地调用超类的其他构造器 ’ 则 Java 编译器将报告错误
class Creature
{
public Creature()
{
System.out.println("Creature无参数的构造器");
}
}
class Animal extends Creature
{
public Animal(String name)
{
System.out.println("Animal带一个参数的构造器,"
+ "该动物的name为" + name);
}
public Animal(String name , int age)
{
// 使用this调用同一个重载的构造器
// this(name);
System.out.println("Animal带两个参数的构造器,"
+ "其age为" + age);
}
}
public class Wolf extends Animal
{
public Wolf()
{
// 显式调用父类有两个参数的构造器
super("灰太狼", 3);
System.out.println("Wolf无参数的构造器");
}
public static void main(String[] args)
{
new Wolf();
}
}
可以看出,创建任何对象总是从该类所在继承树最顶层类的构造函数开始,依次向下执行,最后才执行本类的构造函数