类的继承
1) 继承的特点 :
java的继承通过关键字extends关键字来实现,实现继承的类被称为子类,被继承的类被称为父类,也被称为超类或基类。子类是一种特殊的父类,因为父类包含的范围比子类大,因此可以认为父类是大类,子类是小类。
extend 表示扩展,代表子类是父类的扩展,子类拥有父类的全部成员变量,方法和内部类,但不能获得父类的构造器和初始化块
public class Fruit
{
public int weight;
public void info()
{
System.out.println("我是一个重量为"+weight+"克的水果");
}
}
public class apple extends Fruit
{
public static void main(String[] args)
{
var a = new Fruit();
//因为是子类,因此可以直接访问Apple对象里的Weight成员变量和方法
a.weight = 12;
a.info();
}
}
- 因此程序在创建Apple对象之后可以直接访问该Apple对象中的weight成员变量和info方法.
- 在Java中,Java类只能有一个直接父类,可以有多个间接父类.
2 ) 重写父类的方法 :
虽然父类的范围比子类的范围更广,但子类也可能与父类的特性不同,比如鸟类都包含了飞行的特点,但鸵鸟却不会飞,因此需要重新父类方法.
public class Bird
{
public void fly()
{
System.out.println("我们都会飞。。。");
}
}
public class Ostrich extends Bird
{
public void fly()//重写Ostrich对象中fly方法
{
System.out.println("我只会跑。。。");
}
public static void main(String[] args)
{
var s = new Ostrich();//创建对象
s.fly();
}
}
- 方法重写要遵循 两同两小一大 原则
- 两同-----形参列表相同, 方法名相同
- 两小-----子类返回值类型应该比父类返回值类型更小 , 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等
- 一大-----子类访问权限应该比父类更大或相等
当然子类覆盖了父类方法之后, 其实例对象将无法访问 被覆盖的父类方法,但可以在子类方法中调用父类方法.
调用被覆盖的父类方法有以下两种情况:- 父类方法为类方法-----直接 类名+.+方法(…)
- 父类方法为实例方法-----使用super+.+方法(…)
3 ) super 限定
如果需要在子类方法中调用父类被覆盖的实例方法,则可使用super限定来调用父类中被覆盖的实例方法.
注意 :super不能出现在static修饰的方法中,因为它和 this一样需要相应的对象,而该方法的调用者可能是一个类,而不是对象
class BaseClass
{
public int a = 5;
}
public class SubClass extends BaseClass
{
public int a = 7;
public void ss()
{
System.out.println(a);
}
public void ff()
{
System.out.println(super.a);//直接访问父类中的a成员变量
}
public static void main(String[] args)
{
var f = SubClass();
f.ss();//访问子类中成员变量//输出7
f.ff();//访问父类中成员变量//输出5
}
}
- 如果在子类里定义了与父类中已有变量同名的变量,那么子类中定义的变量会隐藏父类中定义的变量。注意不是完全掩盖,因此系统在创建子类对象时,依然会为父类中定义的,被隐藏的变量分配内存空间。(只是简单的隐藏了父类中的实例变量)
class Parent
{
public String tag = "小学生";
}
class Derived extends Parent
{
private String tag = "中学生";
}
public class HideTest
{
public static void main(String[] args)
{
var d = new Derived();
//System.out.println(d.tag);//错误
System.out.println(((parent)d).tag);
}
}
解释:程序的入口main()方法先创建了一个Derived对象。这个对象将会保存两个tag实例变量,一个是在Parant类中定义实例变量tag, 一个是在Derived类中定义实例变量tag,此时程序中包括一个d变量,它引用一个Derived对象,接着,程序将Derived对象赋给d变量,并判断实例变量是否可以访问。
- 如上代码中,d无法访问具有Private修饰的tag变量(Derived类中),但是由于强制转化为Parent类,所以可以访问到具有Public修饰的tag变量(Parent中)
4 ) 调用父类构造器
在一个构造器中调用另一个构造器用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(size, name);
this.color = color;
}
public static void main(String[] args)
{
var s = new Sub(5.6, "对象", "绿色");
System.out.println(s.size + "--" + s.name + "--" + s.color);
}
}
- 注意:使用super调用父类构造器必须出现在子类构造器执行体第一行,所以this调用不会与super调用不会同时出现。
父类的构造器总是在子类构造器之前执行,如下图所示,如果创建ClassB的对象,那么系统将从java.lang.Object开始执行,然后执行ClassA,最后才执行ClassB。
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(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();
}
}
运行结果:
Creature无参数的构造器
Animal带一个参数构造器,其name为灰太狼
Animal带两个参数构造器,其age为3
wolf无参数的构造器
- 因此,创建任何对象总是从该类所在继承树最顶层类的构造器开始执行的,然后依次向下执行,直到执行本类构造器.