一.继承的概念
前文我们学过:面向对象的三大特征:封装性、继承性、多态性。
继承是多态的前提,如果没有继承,就没有多态。
继承主要解决的问题就是:共性抽取。
有一些类写的时候有很多变量或者方法都是相同的,这样写会使代码很冗余,因此可以将这些共性的变量和方法抽取出来放在一个类里,然后让这些类继承它,这样这些继承的类就不用再写这些变量和方法,可以直接使用,且这些继承的类还可以有各自独有的变量和方法。
被继承的类叫做父类,也叫基类、超类。
继承的类叫做子类,也叫派生类。
继承关系的特点:
1.子类可以拥有父类的可继承的内容。
2.子类还可以拥有自己转有的新内容。
注意:在继承的关系中,“子类就是一个父类”,也就是说,子类可以被当作父类看待。
例如父类是员工,子类是讲师、主教。那么“讲师就是一个员工”等价于说“子类就是一个父类”。其实就是不要冲突了一个观念,在定义上子类都属于父类,而在内容上子类包括父类--相反的。
定义父类的格式:(一个普通的类定义)
定义子类的格式:
public class 子类名称 extends 父类名称{}
举例:父类:
public class Employee { public void method(){ System.out.println("方法执行!"); } }
子类:
public class Teacher extends Employee{ }
调用子类的类:
public class Demo04Extends { public static void main(String[] args) { // 没写构造方法的话,编译器送一个 Teacher teacher = new Teacher(); teacher.method(); } }
执行结果:
即虽然实例化Teacher类的对象可以直接调用父类的方法。
二.在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:
1.直接通过子类对象访问成员变量:
等号左边是谁,就优先用谁,没有则向上找。
public class Fu { int num = 10; }
public class Zi extends Fu{ int num = 20; }
Fu fu = new Fu(); Zi zi = new Zi(); System.out.println(fu.num); // 10 System.out.println(zi.num); // 20
2.间接通过成员方法访问成员变量:
方法属于谁,就优先用谁,没有则向上找。
public class Fu { int num = 10; public void methodFu(){ // 使用的是本类的,不会向下找 System.out.println(num); } }
public class Zi extends Fu{ int num = 20; public void methodZi(){ // 记住,优先用自己的,如果本类没有该变量,才往父类找 // 即即使注释掉定义num那一行,下面这个输出也不会报错 // 会去父类找到同名的 System.out.println(num); } }
public static void main(String[] args) { Fu fu = new Fu(); Zi zi = new Zi(); System.out.println(fu.num); // 10 System.out.println(zi.num); // 20 zi.methodZi(); // 20 // 这个方法是在父类里定义的,所以这个num是父类的,不会向下找 zi.methodFu(); // 10 }
三.区分子类方法中重名的三种变量
局部变量: 直接写
本类的成员变量:this.成员变量名
父类的成员变量:super.成员变量名
public class Fu { int num = 10; public void methodFu(){ // 使用的是本类的,不会向下找 System.out.println(num); } }
public class Zi extends Fu{ int num = 20; public void method(){ // 局部变量重名,直接写,出栈即毁 int num = 30; System.out.println(num); // 30,局部变量,优先离得近的 System.out.println(this.num); // 20,成员变量 System.out.println(super.num); // 10,父类的成员变量 } }
public static void main(String[] args) { Zi zi = new Zi(); zi.method(); }
五.继承中成员方法的访问特点(重点注意父子类的成员方法重名)
在父子类的继承关系中,创建子类对象,访问成员方法的规则:
创建的对象是谁,就优先用水,如果没有则向上找。
注意事项:
无论是成员变量和成员方法,如果没有都是向上找父类,绝对不会向下找子类的。
public class Fu2 { public void methodFu(){ System.out.println("父类方法执行!"); } public void method(){ System.out.println("父类重名方法执行!"); } }
public class Zi2 extends Fu2{ public void methodZi(){ System.out.println("子类方法执行!"); } public void method(){ System.out.println("父类重名方法执行!"); } }
public static void main(String[] args) { Zi2 zi = new Zi2(); zi.methodFu(); zi.methodZi(); zi.method(); }
运行截图:
六.继承中方法的覆盖重写(Override)
概念:在继承关系中,方法的名称一样,参数列表也一样,这种时候就叫做重写,注意和方法的重载进行区分。
重写Override:方法的名称一样,参数列表【也一样】,也叫覆盖、覆写。
重载Overload:方法的名称一样,参数列表【不一样】
方法的覆盖重写特点:创建的是子类对象,则优先用子类方法。
方法的覆盖重写的注意事项:
1.必须保证父子类之间的方法的名称相同,参数列表也相同。
为了保证我们进行一个有效的覆盖重写,我们介绍一个@Override(注解),写在方法前面,用来检测是不是有效的正确覆盖重写,没有报错即有效覆盖重写。
这个注解就算不写,只要哦满足要求,也是有效的正确覆盖重写,只是一个安全的检测手段。
2.子类方法的返回值必须小于等于父类方法的返回值范围。
我们回忆一个小前提,String类有一个父类Object,是所有类的公共父类(基类)。
举例:父子类返回类型一样,正确格式:
public class OverrideFu { public Object method(){ Object object = new Object(); return object; } }
public class OverrideZi extends OverrideFu { // 没有报错,即有效重写 @Override public Object method() { Object object = new Object(); return object; } }
父子类返回类型不一样,子类类型比父类范围小,正确格式,注意这个要判断范围大小的类型局限于有继承关系的引用类型:
public class OverrideZi extends OverrideFu { // 没有报错,即有效重写 @Override public String method() { String str = new String(); return str; } }
3.子类方法的权限必须大于等于父类方法的权限修饰符。
小扩展提示:public > protected > (default) > private
备注:(default)不是关键字default,而是什么都不写,留空。
七.继承种方法的覆盖重写的应用场景。
设计原则:对于已经投入使用的类,尽量不要进行修改。推荐定义一个新的类,来重读利用其中共性内容,并且添加改动新内容。
八.继承中构造方法的访问特点
1.子类构造方法当中有一个默认隐含的“super()”调用(不写,编译器就送),所以一定是先调用的父类构造,后执行的子类构造。
public class Fu3 { public Fu3() { System.out.println("父类构造方法"); } }
public class Zi3 extends Fu3{ public Zi3() { // Super(); //调用父类无参构造方法 System.out.println("子类构造方法"); } }
public class Demo04Constructor { public static void main(String[] args) { // 不仅会用到子类的构造方法,也会用到父类的构造方法 // 且父类的构造方法先调用。 Zi3 zi = new Zi3(); } }
运行截图:
2.子类构造通过关键字来调用父类重载构造。
此处注意:任何类如果重载了有参数的构造方法没有写无参的构造方法,编译器也不会再赠送无参的构造方法!
那么如果我们将父类的构造方法改成有参的,子类不写super();的话,就会报错,因为没有父类的无参构造方法给它调用。
此时我们就可以用super.()方法按照父类重载的有参构造方法格式来调用父类重载的有参构造方法。(分颜色看好理解这句话)
举例:
public class Fu3 { public Fu3(int num) { System.out.println("父类构造方法"); } }
public class Zi3 extends Fu3{ public Zi3() { super(3); // 按照格式,写上int参数 // super(); //调用父类无参构造方法 System.out.println("子类构造方法"); } }
3.【重点】super()的父类构造调用,必须是子类构造方法的第一个语句,不能一个子类构造调用多次super构造!!!
这句话注意两个点:super()必须写在子类的构造方法里;必须是第一句。
总结:
子类必须调用父类构造方法,不写则赠送一个super();写了则用写的指定的super调用,super只能有一个,还必须是第一个。
补充:
1.Java语言是单继承的。
单继承:一个类的直接父类只能有一个,父亲是唯一的。
因为如果继承两个父类,两个父类有同名方法时,子类对象调用这个方法,编译器无法决定选择哪个,即会编译出错。
2.Java语言可以多级继承。
子类(子)有一个父类(父),该父类又有一个父类(爷),等等。
3.一个子类的直接父类是唯一的,但是一个父类可以拥有很多个子类。