【JavaSE基础语法】07-继承与多态
1. 继承
面向对象思想提出了继承的概念,专门用来进行共性抽取,实现代码复用
1.1继承的概念
继承机制:是面向对象程序设计使代码可以复用的最重要手段,允许程序员在保持原有类特性的基础上进行扩展,新增功能,这样产生的类称为派生类。
- 继承最大的作用就是:共性抽取、实现代码复用以及是实现多态
1.2 继承的语法
在Java中如果要表示类之间的继承关系,需要借助extends关键字
修饰符class子类extends父类{//...}【注意】:
- 子类会将父类中的成员变量或成员方法继承到子类中
1.3 父类成员访问
1.3.1 子类访问父类中的成员变量
- 子类和父类不存在同名成员变量
publicclassBase{inta;intb;}publicclassDerivedextendsBase{intc;publicvoidmethod(){a=10;//访问从父类中继承下来的ab=20;//访问从父类中继承下来的bc=30;//访问子类自己的c}}- 父类和子类成员变量同名
publicclassBase{inta;intb;intc;}publicclassDerivedextendsBase{inta;// 与父类中成员 a 同名,且类型相同charb;// 与父类中成员 b 同名,但类型不同publicvoidmethod(){a=100;//是子类自己的成员ab=101;//是子类自己的成员bc=102;//是父类的成员c// d = 103; // 编译失败,因为父类和子类都没有定义成员变量d}}在子类方法中 或者 通过子类对象访问成员时:
- 如果访问的成员变量子类中有,优先访问自己的成员变量
- 如果访问的成员变量子类中没有,则访问父类继承下来的,如果父类也没有,则报错
- 如果访问的成员变量与父类中成员变量同名,则优先访问自己的
1.3.2 子类中访问父类的成员方法
- 成员方法不同名
publicclassBase{publicvoidmethodA(){System.out.println("Base中的methodA()");}}publicclassDerivedextendsBase{publicvoidmethodB(){System.out.println("Derived中的methodB()方法");}publicvoidmethodC(){methodB();//访问子类自己的 methodB()methodA();//访问父类继承的 methodA()// methodD(); //编译失败,在整个继承体系中没有发现方法 methodD()}}【总结】:成员方法没有同名时,在子类方法或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类也没有则报错
- 成员方法名字相同
publicclassBase{publicvoidmethodA(){System.out.println("Base中的methodA()");}publicvoidmethodB(){System.out.println("Base中的methodB()");}}publicclassDerivedextendsBase{publicvoidmethodA(inta){System.out.println("Derived中的method(int)方法");}publicvoidmethodB(){System.out.println("Derived中的methodB()方法");}publicvoidmethodC(){methodA();//没有传参,访问父类中的 methodA()methodA(20);//传递 int 参数,访问子类中的 methodA(int)methodB();//直接访问,则永远访问到的都是子类中的 methodB(),基类的无法访问到}}【说明】:
- 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
- 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法时传递的参数选择合适的方法访问,如果没有则报错
1.4 super关键字
Java提供了super关键字,主要作用是在子类方法中访问父类的成员
publicclassBase{inta;intb;publicvoidmethodA(){System.out.println("Base中的methodA()");}publicvoidmethodB(){System.out.println("Base中的methodB()");}}publicclassDerivedextendsBase{inta;// 与父类中成员变量同名且类型相同charb;// 与父类中成员变量同名但类型不同// 与父类中 methodA() 构成重载publicvoidmethodA(inta){System.out.println("Derived中的method()方法");}// 与基类中 methodB() 构成重写(即原型一致,重写后序详细介绍)publicvoidmethodB(){System.out.println("Derived中的methodB()方法");}publicvoidmethodC(){// 对于同名的成员变量,直接访问时,访问的都是子类的a=100;// 等价于:this.a = 100;b=101;// 等价于:this.b = 101;// 注意:this 是当前对象的引用// 访问父类的成员变量时,需要借助 super 关键字// super 可以在子类中访问父类的成员变量、成员方法或构造方法super.a=200;super.b=201;// 父类和子类中构成重载的方法,直接可以通过参数列表区分访问父类还是子类方法methodA();//没有传参,访问父类中的 methodA()methodA(20);//传递 int 参数,访问子类中的 methodA(int)//如果在子类中要访问重写的基类方法,则需要借助 super 关键字methodB();//直接访问,则永远访问到的都是子类中的 methodB(),基类的无法访问到super.methodB();//访问基类的 methodB()}}【注意事项】
- 只能在非静态方法中使用
1.5 子类构造方法
子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分。
在构造子类对象的时候,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增的成员初始化完整。
publicclassBase{publicBase(){System.out.println("Base()");}}publicclassDerivedextendsBase{publicDerived(){//super(); //注意子类构造方法中默认会调用基类的无参构造方法:super()//用户没有写时,编译器会自动添加,而且 super() 必须是子类构造方法中第一条语句//并且只能出现一次System.out.println("Derived()");}}publicclassTest{publicstaticvoidmain(String[]args){Derivedd=newDerived();}}// 结果打印:// Base()// Derived()【注意】:
- 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的
super()调用,即调用基类构造方法。 - 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
- 在子类构造方法中,
super(...)调用父类构造时,必须是子类构造函数中第一条语句。 super(...)只能在子类构造方法中出现一次,并且不能和this(...)同时出现。
1.6 super和this
【相同点】
- 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
- 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
【不同点】
this是对当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象从父类继承下来的部分成员的引用- 非静态成员方法中,
this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性 - 在构造方法中:
this(...)用于调用本类的构造方法,super(...)用于调用父类的构造方法,两种构造方法不能同时在构造方法中出现
每个构造方法的第一条语句必须是this(...)或super(...)。如果没有显式写,编译器会默认补上super()。如果写了this(...),则当前构造方法不会直接调用super(...),但最终会通过其他构造方法间接调用父类构造方法。
1.7 执行顺序
- 父类静态代码块优先子类静态代码块执行,且是最早执行
- 父类实例代码块和父类构造方法紧接着执行
- 子类实例代码块和子类构造方法紧接着执行
- 第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
1.8 继承方式
【注意】:Java 类不支持多继承,即一个类只能extends一个父类;但 Java 支持一个类实现多个接口。
1.9 final关键字
final关键字可以用来修饰变量、成员方法以及类
- 修饰变量或字段,表示常量(即不能修改)
finalinta=10;a=20;//编译出错- 修饰类,表示此类不能被继承
我们平时使用的String字符串类就是用final修饰的,不能被继承
finalpublicclassAnimal{...}publicclassBirdextendsAnimal{...}//编译出错- 修饰方法,表示该方法不能被重写
1.10 继承与组合
和继承类似,组合也是一种表达类之间关系的方式,也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如extends这样的关键字),仅仅是将一个类的实例作为另外一个类的字段。
// 轮胎类classTire{// ...}// 发动机类classEngine{// ...}// 车载系统类classVehicleSystem{// ...}classCar{privateTiretire;//可以复用轮胎中的属性和方法privateEngineengine;//可以复用发动机中的属性和方法privateVehicleSystemvs;//可以复用车载系统中的属性和方法// ...}// 奔驰是汽车classBenzextendsCar{//将汽车中包含的:轮胎、发动机、车载系统全部继承下来}组合和继承都可以实现代码的复用,一般建议:能使用组合尽量使用组合
2. 多态
概念:多态就是去完成某个行为,当不同的对象去完成时就会产生出不同的状态
2.1 多态的实现条件
- 必须在继承体系下
- 子类必须要对父类中方法进行重写
- 通过父类的引用调用重写的方法
多态的体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法
publicclassAnimal{Stringname;intage;publicAnimal(Stringname,intage){this.name=name;this.age=age;}publicvoideat(){System.out.println(name+" eat");}}publicclassCatextendsAnimal{publicCat(Stringname,intage){super(name,age);}@Overridepublicvoideat(){System.out.println(name+" eat fish");}}publicclassDogextendsAnimal{publicDog(Stringname,intage){super(name,age);}@Overridepublicvoideat(){System.out.println(name+" eat bone");}}publicclassTestAnimal{publicstaticvoideat(Animala){a.eat();}publicstaticvoidmain(String[]args){Catcat=newCat("哈基米",2);Dogdog=newDog("大黄",1);eat(cat);eat(dog);}}//输出结果//哈基米 eat fish//大黄 eat bone2.2 重写
重写(override):也称为覆盖。重写是子类对父类中非静态、非private、非final、非构造方法 等方法的实现过程进行重新编写,返回值和形参都不能改变。即外壳不变,核心重写。
重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说,子类能够根据需要实现父类的方法。
【方法重写的规则】:
- 子类在重写父类的方法时,一般必须与父类方法原型一致:返回值类型方法名(参数列表)要完全一致
- 被重写的方法返回值类型可以不同,但是必须是具有父子关系的
- 访问权限不能比父类中被重写的方法的访问权限更低
static方法不能被重写,只能被隐藏;private方法对子类不可见,不能被重写;final方法不能被重写;构造方法不能被继承,因此也不能被重写- 重写的方法,可以使用
@Override注解来显式指定.有了这个注解能帮我们进行一些合法性校验
【重写和重载的区别】:
| 区别点 | 重写(override) | 重载(overload) |
|---|---|---|
| 参数列表 | 一定不能修改 | 必须修改 |
| 返回类型 | 一定不能修改(除非可以构成父子类关系) | 可以修改 |
| 访问限定符 | 一定不能做更严格的限制(可以降低限制) | 可以修改 |
【重写的设计原则】:
对于已经投入使用的类,尽量不要进行修改。最好的方法是:重新定义一个新的类,类重复利用其中共性的内容,并添加或者改动新的内容
2.3 向上转型和向下转型
2.3.1 向上转型
就是创建一个子类对象,将其当成父类对象来使用
父类对象 对象名=new子类类型();【使用场景】
publicclassTestAnimal{// 2. 方法传参:形参为父类型引用,可以接收任意子类的对象publicstaticvoideatFood(Animala){a.eat();}// 3. 作返回值:返回任意子类对象publicstaticAnimalbuyAnimal(Stringvar){if("狗".equals(var)){returnnewDog("狗狗",1);}elseif("猫".equals(var)){returnnewCat("猫猫",1);}else{returnnull;}}publicstaticvoidmain(String[]args){Animalcat=newCat("元宝",2);// 1. 直接赋值:子类对象赋值给父类对象Dogdog=newDog("小七",1);eatFood(cat);eatFood(dog);Animalanimal=buyAnimal("狗");animal.eat();animal=buyAnimal("猫");animal.eat();}}【优点】:让代码实现更灵活
【缺点】:不能调用到子类特有的方法
2.3.2 向下转型
- 将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时将父类引用再还原为子类对象即可,即向下转型
- 向下转型用的比较少,而且不安全,万一转型失败,运行时就会抛异常。
- Java中为了提高向下转型的安全性,引入了
instanceof,如果该表达式为true,则可以安全转移
publicclassTestAnimal{publicstaticvoidmain(String[]args){Catcat=newCat("元宝",2);Dogdog=newDog("小七",1);// 向上转型Animalanimal=cat;animal.eat();animal=dog;animal.eat();if(animalinstanceofCat){cat=(Cat)animal;cat.mew();}if(animalinstanceofDog){dog=(Dog)animal;dog.bark();}}}2.4 多态的优缺点
【优点】
- 能够降低代码的“圈复杂度”,避免大量使用
if-else - 可扩展性强
【缺点】
- 通过父类引用只能访问父类中声明的成员,不能直接访问子类特有方法;如果需要访问子类特有方法,可能需要向下转型,存在类型转换风险。
