
继承和多态知识梳理继承与多态一、继承1.继承概念2.父类的成员访问3.super与this关键字3.构造方法与默认规则4.final关键字5.继承与组合二、多态1.重写2.向上转型4.向下转型**4.多态的优缺点**继承与多态一、继承1.继承概念机制面像对象程序设计使得代码可以复用的最重要手段它允许程序员保持原有类特性的基础上进行扩展增加新功能这样新产生的类称为派生类。主要解决的问题是共性的抽取实现代码的复用。语法修饰符class 子类 extends 父类{}注意1.子类会将父类中的成员变量或者成员方法继承到子类中了2.子类继承父类之后必须要新添加自己特有的成员体现出与基类的不同否则就没有必要继承了3.继承不能多继承,不能一个类继承多个类例如猫和狗都是动物我们可以将共性的内容抽取然后用继承的思想达到共用。// 动物父类classAnimal{privateStringname;privateintage;// 添加构造方法publicAnimal(Stringname,intage){this.namename;this.ageage;}// 添加name的getter方法publicStringgetName(){returnname;}publicvoideat(){System.out.println(name is eating.);}publicvoidsleep(){System.out.println(name is sleeping);}}// 猫子类classCatextendsAnimal{publicCat(Stringname,intage){super(name,age);}publicvoidmeow(){System.out.println(getName() says: Meow!);}}// 狗子类classDogextendsAnimal{publicDog(Stringname,intage){super(name,age);}publicvoidbark(){System.out.println(getName() says: Woof!);}}// 测试类publicclassMain{publicstaticvoidmain(String[]args){CatcatnewCat(Fluffy,2);DogdognewDog(Buddy,3);cat.eat();cat.sleep();cat.meow();dog.eat();dog.sleep();dog.bark();}}输出结果2.父类的成员访问在子类方法中或者通过子类对象访问成员时如果访问的成员变量子类中有优先访问自己的成员变量。如果访问的成员变量子类中无则访问父类继承下来的如果父类也没有定义则编译报错。如果访问的成员变量与父类中成员变量同名则优先访问自己的。成员变量访问遵循就近原则自己有优先自己的如果没有则向父类中找。如果父类和子类出现相同的成员时那如何在子类访问父类名字相同的成员super关键字:主要作用在子类中访问父类的成员变量和方法注意只能在非静态方法使用使用示例publicclassA{intn2;intt4;}publicclassBextendsA{intn3;ints5voidgetAn(){System.out.println(super.n);}}publicclassTest{publicstaticvoidmain(String[]args){BbnewB();System.out.println(b.n);b.getAn();}}输出结果:super和this所能调用成员关系图(具体作用后续详解)》当父类与子类两个成员变量重名时像下面n一样那么this调用的是父类的n,要调用父类的n就只能用super.3.super与this关键字作用的三个共同性super(调用父类的)this(调用当前类的 )成员变量super.datathis.data成员方法super.func()this.func()构造方法super()this()super指向直接父类用于调用父类构造方法必须位于子类构造方法第一行,不支持跨级访问父类的父类。this指向当前对象编译器不会自动生成。super()和this() 不能共存于同一构造方法且均需置于第一行。3.构造方法与默认规则若没有手动在父类写构造方法或者是显示定义无参构造方法编译器在子类会自动生成第一行默认有隐含的super()调用。如果父类构造方法带参数当子类继承父类后子类需要显示调用父类的构造方法要先帮父类初始化且super(…)必须是第一句在子类构造方法中只能出现一次否则编译失败。classParent{privateStringparentField;// 父类构造方法publicParent(Stringvalue){this.parentFieldvalue;System.out.println(Parent constructor: parentField);}}classChildextendsParent{privateStringchildField;// 子类构造方法显式调用super()publicChild(StringparentValue,StringchildValue){super(parentValue);// 必须放在第一行this.childFieldchildValue;System.out.println(Child constructor: childField);}}上一章类和对象中已经讨论了不同代码块的执行顺序,在继承关系上又有什么不同?继承关系上的代码块执行顺序:1、父类静态代码块优先于子类静态代码块执行且是最早执行2、父类实例代码块和父类构造方法紧接着执行3、子类的实例代码块和子类构造方法紧接着再执行4、第二次实例化子类对象时父类和子类的静态代码块都将不会再执行4.final关键字作用:1.修饰变量或字段,使得变量变为常量,不能再修改final int a1;a2//编译错误2.修饰类,表示类不能继承,称为封装类Final public class Animal{}public class Dog extends Animal{}//编译错误3.修饰方法,不能被重写(后续详说重写)5.继承与组合和继承类似组合也是一种表达类之间关系的方式也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如extends这样的关键字)仅仅是将一个类的实例作为另外一个类的字段。继承表示对象之间是is-a的关系比如狗是动物猫是动物组合表示对象之间是has-a的关系比如汽车// Engine类作为Car的组成部分classEngine{privateStringtype;publicEngine(Stringtype){this.typetype;}publicvoidstart(){System.out.println(type发动机启动);}}// Car类通过成员变量持有Engine实例classCar{privateStringmodel;privateEngineengine;// 组合关系关键点publicCar(Stringmodel,StringengineType){this.modelmodel;this.enginenewEngine(engineType);// 创建不可分割的部件}publicvoidstartCar(){System.out.println(model车启动...);engine.start();// 委托给Engine对象}}// 使用示例publicclassTest{publicstaticvoidmain(String[]args){CarmyCarnewCar(S,Electric);myCar.startCar();}}输出结果:二、多态多态体现“一种事物多种形态”具体就是完成某个行为当不同的对象去完成时会产生不同的状态。1.重写必须满足条件1.方法名一样2.参数列表一样顺序、个数、类型3.返回值一样注意静态方法不能重写用final修饰不能重写密封方法如果子类重写父类方法子类权限要大于等于父类权限被private修饰方法不能重写可以用Override注解防止方法名拼写错误返回类型可以不一样但必须构成父子类关系publicclassAnimal{Stringname;publicAnimaleat(){System.out.println(this.name正在吃);retrunnull;}}publicclassDogextendsAnimal{publicDogeat(){System.out.println(this.name正在吃狗粮);retrunnull;}}重写与重载重写重载方法名不能修改不能修改参数列表不能修改必须修改返回类型不能修改除非构成父子类关系能修改访问限定符不能降低权限可以更高能修改即方法重载是一个类的多态表现而方法重写是子类与父类的一种多态性表现。静态绑定也称为前期绑定早绑定即在编译时根据用户所传递实参类型就确定了具体调用那个方法典型代表函数重载。简单来说就是通过传几个值就知道调用哪个方法。动态绑定(后面有动态绑定示例也称为后期绑定晚绑定即在编译时不能确定方法的行为需要等到程序运行时才能够确定具体调用那个类的方法。2.向上转型方式:直接赋值Animal animal new Dog()。方法传参定义接收父类引用的方法public static void func(Animal animal){}func(dog)方法返回子类对象赋值给父类引用public static Animal func2(){Dog dognew Dog();return dog;}通过向上转型后只能调用父类自己的方法:publicclassAnimal{Stringname;publicAnimal(Stringname){this.namename;}publicvoideat(){System.out.println(this.name正在吃);}}publicclassDogextendsAnimal{publicDog(Stringname){super(name);}publicvoidbark(){System.out.println(this.name在叫);}}publicclassTest{publicstaticvoidmain(String[]args){AnimalanewDog(大黄);//通过向上转型后只能调用父类自己的方法a.bark;//编译错误a.eat;//编译成功}}动态绑定例如以下示例程序在编译的时候确实调用的是父类eat方法当运行代码的时候通过父类引用调用了父类和子类重写的那个方法结果实际调用了子类的方法所以这与通过向上转型后只能调用父类自己的方法并不矛盾。publicclassAnimal{Stringname;publicAnimal(Stringname){this.namename;}publicvoideat(){System.out.println(this.name正在吃);}}publicclassDogextendsAnimal{publicDog(Stringname){super(name);}publicvoideat(){System.out.println(this.name正在吃狗粮);}}publicclassTest{publicstaticvoidmain(String[]args){AnimalanewDog(大黄);a.eat();}}运行结果通过向上转型实现多态多态实现条件必须在继承关系下。子类必须对父类方法进行重写。通过父类的引用调用重写方法向上转型。//Animal父类publicclassAnimal{Stringname;publicAnimal(Stringname){this.namename;}publicvoideat(){System.out.println(this.name正在吃);}}//Dog类publicclassDogextendsAnimal{publicDog(Stringname){super(name);}//条件二 子类必须对父类方法进行重写publicvoideat(){System.out.println(this.name正在吃狗粮);}}//Bird类publicclassBirdextendsAnimal{publicBird(Stringname){super(name);}//条件二 子类必须对父类方法进行重写publicvoideat(){System.out.println(this.name吃鸟粮);}}//多态实现publicclassTest{publicstaticvoidmain(String[]args){AnimalanewDog(大黄);a.eat();//条件三通过父类的引用调用重写方法Animala2newBird(八哥);a2.eat();}}运行结果由上面的示例体现出animal引用的对象不同但是调用同一个方法eat此时表现的现象不一样这就是多态也体现了向上转型的优点让代码实现更加灵活。4.向下转型方式Animal animalnew Bird();Bird bird(Bird) aniaml; (强转bird.eat();不是所有向下转型都可以Animal animal2new Dog();Bird Bird2(Bird)animal2;Bird2.eat();//编译错误因为animal2实际指的是狗向下转型用的比较少而且不安全万一转换失败运行时就会抛异常java中为了提高向下转型的安全性引入了instanceof,如果该表达式为true,则可以安全转换向下转型多态的补充当需要通过父类引用调用子类特有方法时必须先进行向下转型——将父类引用“还原”为子类类型才能访问子类独有的属性或方法。这就补充了向上转型的缺点即无法调用子类特有的方法。//Animal父类publicclassAnimal{Stringname;publicAnimal(Stringname){this.namename;}publicvoideat(){System.out.println(this.name正在吃);}}//Dog类publicclassDogextendsAnimal{publicDog(Stringname){super(name);}publicvoideat(){System.out.println(this.name正在吃狗粮);}publicvoidbark(){System.out.println(this.name正在叫);}}// Bird类publicclassBirdextendsAnimal{publicBird(Stringname){super(name);}publicvoideat(){System.out.println(this.name正在吃鸟粮);}publicvoidfly(){System.out.println(this.name正在飞);}}publicclassTest{publicstaticvoidmain(String[]args){AnimalanewDog(大黄);if(ainstanceofDog){Dogdog1(Dog)a;dog1.bark();//子类特有}Animala2newBird(八哥);if(a2instanceofBird){BirdBird2(Bird)a2;Bird2.fly();//子类特有}}}输出结果4.多态的优缺点【使用多态的好处】1. 能够降低代码的 “圈复杂度”, 避免使用大量的 if - else什么叫 “圈复杂度” ?圈复杂度是一种描述一段代码复杂程度的方式. 一段代码如果平铺直叙, 那么就比较简单容易理解. 而如果有很多的条件分支或者循环语句, 就认为理解起来更复杂.因此我们可以简单粗暴的计算一段代码中条件语句和循环语句出现的个数, 这个个数就称为 “圈复杂度”.如果一个方法的圈复杂度太高, 就需要考虑重构.不同公司对于代码的圈复杂度的规范不一样. 一般不会超过 10 .例如我们现在需要打印的不是一个形状了, 而是多个形状. 如果不基于多态, 实现代码如下:publicstaticvoiddrawShapes(){RectrectnewRect();CyclecyclenewCycle();FlowerflowernewFlower();String[]shapes{cycle,rect,cycle,rect,flower};for(Stringshape:shapes){if(shape.equals(cycle)){cycle.draw();}elseif(shape.equals(rect)){rect.draw();}elseif(shape.equals(flower)){flower.draw();}}}如果使用使用多态,则不必写这么多的if-else分支语句,代码更简单.publicstaticvoiddrawShapes(){// 我们创建了一个 Shape 对象的数组.Shape[]shapes{newCycle(),newRect(),newCycle(),newRect(),newFlower()};for(Shapeshape:shapes){shape.draw();}}2.可扩展能力强:改动成本低【多态缺陷】代码的运行效率降低。1.属性没有多态性当父类和子类都有同名属性的时候通过父类引用只能引用父类自己的成员属性2.构造方法没有多态性