Java——继承的细节
继承的细节1、构造方法1.1、父类无默认构造1.2、父类构造调用可被重载的方法2、重名与静态绑定2.1、重名3、重载和重写4、父子类型转换5、继承访问权限protected6、可见性重写7、防止继承final1、构造方法1.1、父类无默认构造子类可以通过super调用父类的构造方法如果子类没有通过super调用则会自动调动父类的默认构造方法那如果父类没有默认构造方法呢如下所示publicclassBase{privateStringmember;publicBase(Stringmember){this.membermember;}}这个类只有一个带参数的构造方法没有默认构造方法。这个时候它的任何子类都必须在构造方法中通过super调用Base的带参数构造方法如下所示否则Java会提示编译错误。publicclassChildextendsBase{publicChild(Stringmember){super(member);}}1.2、父类构造调用可被重载的方法另外需要注意的是如果在父类构造方法中调用了可被重写的方法则可能会出现意想不到的结果。我们来看个例子下面是基类代码publicclassBase{publicBase(){test();}publicvoidtest(){}}构造方法调用了test()方法。这是子类代码publicclassChildextendsBase{privateinta123;publicChild(){}publicvoidtest(){System.out.println(a);}}子类有一个实例变量a初始赋值为123重写了test()方法输出a的值。看下使用的代码publicstaticvoidmain(String[]args){ChildcnewChild();c.test();}输出结果是0 123第一次输出为0第二次输出为123。第一行为什么是0呢第一次输出是在new过程中输出的在new过程中首先是初始化父类父类构造方法调用test()方法test()方法被子类重写了就会调用子类的test()方法子类方法访问子类实例变量a而这个时候子类的实例变量的赋值语句和构造方法还没有执行所以输出的是其默认值0。像这样在父类构造方法中调用可被子类重写的方法是一种不好的实践容易引起混淆应该只调用private的方法。2、重名与静态绑定2.1、重名子类可以重写父类非private的方法当调用的时候会动态绑定执行子类的方法。那实例变量、静态方法和静态变量呢它们可以重名吗如果重名访问的是哪一个呢重名是可以的重名后实际上有两个变量或方法。private变量和方法只能在类内访问访问的也永远是当前类的即在子类中访问的是子类的在父类中访问的是父类的它们只是碰巧名字一样而已没有任何关系。public变量和方法则要看如何访问它。在类内访问的是当前类的但子类可以通过super明确指定访问父类的。在类外则要看访问变量的静态类型静态类型是父类则访问父类的变量和方法静态类型是子类则访问的是子类的变量和方法。我们来看个例子这是基类代码publicclassBase{publicstaticStringsstatic_base;publicStringmbase;publicstaticvoidstaticTest(){System.out.println(base static: s);}}定义了一个public静态变量s一个public实例变量m一个静态方法staticTest。这是子类代码publicclassChildextendsBase{publicstaticStringschild_base;publicStringmchild;publicstaticvoidstaticTest(){System.out.println(child static: s);}}子类定义了和父类重名的变量和方法。对于一个子类对象它就有了两份变量和方法在子类内部访问的时候访问的是子类的或者说子类变量和方法隐藏了父类对应的变量和方法下面看一下外部访问的代码publicstaticvoidmain(String[]args){ChildcnewChild();Basebc;System.out.println(b.s);System.out.println(b.m);b.staticTest();System.out.println(c.s);System.out.println(c.m);c.staticTest();}以上代码创建了一个子类对象然后将对象分别赋值给了子类引用变量c和父类引用变量b然后通过b和c分别引用变量和方法。这里需要说明的是静态变量和静态方法一般通过类名直接访问但也可以通过类的对象访问。程序输出为static_base base basestatic:static_base child_base child childstatic:child_base当通过b静态类型Base访问时访问的是Base的变量和方法当通过c静态类型Child访问时访问的是Child的变量和方法这称之为静态绑定即访问绑定到变量的静态类型。静态绑定在程序编译阶段即可决定而动态绑定则要等到程序运行时。*实例变量、静态变量、静态方法、private方法都是静态绑定的。3、重载和重写重载是指方法名称相同但参数签名不同参数个数、类型或顺序不同重写是指子类重写与父类相同参数签名的方法。对一个函数调用而言可能有多个匹配的方法有时候选择哪一个并不是那么明显。我们来看个例子这是基类代码publicclassBase{publicintsum(inta,intb){System.out.println(base_int_int);returnab;}}它定义了方法sum下面是子类代码publicclassChildextendsBase{publiclongsum(longa,longb){System.out.println(child_long_long);returnab;}}以下是调用的代码publicstaticvoidmain(String[]args){ChildcnewChild();inta2;intb3;c.sum(a,b);}Child和Base都定义了sum方法这里调用的是哪个sum方法呢子类的sum方法参数类型虽然不完全匹配但是是兼容的父类的sum方法参数类型是完全匹配的。程序输出为base_int_int父类类型完全匹配的方法被调用了。如果父类代码改成下面这样呢publicclassBase{publiclongsum(inta,longb){System.out.println(base_int_long);returnab;}}父类方法类型也不完全匹配了。程序输出为base_int_long调用的还是父类的方法。父类和子类的两个方法的类型都不完全匹配为什么调用父类的呢因为父类的更匹配一些。现在修改一下子类代码更改为publicclassChildextendsBase{publiclongsum(inta,longb){System.out.println(child_int_long);returnab;}}程序输出变为了child_int_long终于调用了子类的方法。可以看出当有多个重名函数的时候在决定要调用哪个函数的过程中首先是按照参数类型进行匹配的换句话说寻找在所有重载版本中最匹配的然后才看变量的动态类型进行动态绑定。4、父子类型转换子类型的对象可以赋值给父类型的引用变量这叫向上转型那父类型的变量可以赋值给子类型的变量吗或者说可以向下转型吗语法上可以进行强制类型转换但不一定能转换成功。我们以前面的例子来看BasebnewChild();Childc(Child)b;Child c (Child)b就是将变量b的类型强制转换为Child并赋值为c这是没有问题的因为b的动态类型就是Child但下面的代码是不行的BasebnewBase();Childc(Child)b;语法上Java不会报错但运行时会抛出错误错误为类型转换异常。一个父类的变量能不能转换为一个子类的变量取决于这个父类变量的动态类型即引用的对象类型是不是这个子类或这个子类的子类。给定一个父类的变量能不能知道它到底是不是某个子类的对象从而安全地进行类型转换呢答案是可以通过instanceof关键字看下面代码publicbooleancanCast(Baseb){returnbinstanceofChild;}这个函数返回Base类型变量是否可以转换为Child类型instanceof前面是变量后面是类返回值是boolean值表示变量引用的对象是不是该类或其子类的对象。5、继承访问权限protected变量和函数有public/private修饰符public表示外部可以访问private表示只能内部使用还有一种可见性介于中间的修饰符protected表示虽然不能被外部任意访问但可被子类访问。另外protected还表示可被同一个包中的其他类访问不管其他类是不是该类的子类。我们来看个例子这是基类代码publicclassBase{protectedintcurrentStep;protectedvoidstep1(){}protectedvoidstep2(){}publicvoidaction(){this.currentStep1;step1();this.currentStep2;step2();}}action表示对外提供的行为内部有两个步骤step1()和step2()使用currentStep变量表示当前进行到了哪个步骤step1()、step2()和currentStep是protected的子类一般不重写action而只重写step1和step2同时子类可以直接访问currentStep查看进行到了哪一步。子类的代码是publicclassChildextendsBase{protectedvoidstep1(){System.out.println(child step this.currentStep);}protectedvoidstep2(){System.out.println(child step this.currentStep);}}使用Child的代码是publicstaticvoidmain(String[]args){ChildcnewChild();c.action();}输出为child step1child step2基类定义了表示对外行为的方法action并定义了可以被子类重写的两个步骤step1()和step2()以及被子类查看的变量currentStep子类通过重写protected方法step1()和step2()来修改对外的行为。这种思路和设计是一种设计模式称之为模板方法。action方法就是一个模板方法它定义了实现的模板而具体实现则由子类提供。模板方法在很多框架中有广泛的应用这是使用protected的一种常见场景。6、可见性重写重写方法时一般并不会修改方法的可见性。但我们还是要说明一点重写时子类方法不能降低父类方法的可见性。不能降低是指父类如果是public则子类也必须是public父类如果是protected子类可以是protected也可以是public即子类可以升级父类方法的可见性但不能降低。看个例子基类代码为publicclassBase{protectedvoidprotect(){}publicvoidopen(){}}子类代码为publicclassChildextendsBase{//以下是不允许的会有编译错误//private void protect(){//}//以下是不允许的会有编译错误//protected void open(){//}publicvoidprotect(){}}为什么要这样规定呢继承反映的是“is-a”的关系即子类对象也属于父类子类必须支持父类所有对外的行为将可见性降低就会减少子类对外的行为从而破坏“is-a”的关系但子类可以增加父类的行为所以提升可见性是没有问题的。7、防止继承final继承是把双刃剑带来的影响就是有的时候我们不希望父类方法被子类重写有的时候甚至不希望类被继承可以通过final关键字实现。final关键字可以修饰变量而这是final的另一种用法。一个Java类默认情况下都是可以被继承的但加了final关键字之后就不能被继承了如下所示publicfinalclassBase{//主体代码}一个非final的类其中的public/protected实例方法默认情况下都是可以被重写的但加了final关键字后就不能被重写了如下所示publicclassBase{publicfinalvoidtest(){System.out.println(不能被重写);}}