黑马程序员----面向对象三大特征之继承

来源:互联网 发布:网站漏洞检测软件 编辑:程序博客网 时间:2024/05/21 14:44

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


继承

一、什么叫继承

不知道大家第一次看到这个词会想到什么,反正我是想到了一部韩剧叫“继承者们”,在电视剧中,年轻的孩子们会因为他们的父亲特别有钱而也变得非常富裕(心里好不平衡啊),而且我发现基本那些年轻人都是他们父亲的加强版,会很多他们父亲不会的,同时还拥有他们父亲的财富。

在这里我们就可以说年轻人继承于他们的父亲,为什么他们有那么多东西(属性),会做那么多事(方法)呢?因为他们的父亲会,而他又继承自他的父亲,因此他也会,同时年轻人在继承的基础上,又可以有自己的功能,比如年轻人不仅会经商,还会飙车,而飙车是他父亲不具有的,因此这个飙车的能力就是年轻人自己特有的。

那么继承有什么好处呢?

我们发现,对于年轻人来说,省去好多事啊对吧,由于他的父亲具有的东西都不用他自己去努力获得了,只要继承就可以了,因此他只需关注那些父亲没有的,而自己又想要有的能力就行了,也就是自己的特有功能。
这就是继承最大的好处:代码复用,大量的代码被复用,而不用再重复定义一次,同时由于子类可以扩展自己的功能和特有属性等,因此也有很高的扩展性

其实还有一个好处,那就是让类之间有了关系,继承关系,这主要是为了以后实现多态的前提。


继承的种类:

在java中,有三种继承方式:单继承,多继承,多层继承。
单继承:指的是一个类只继承于另外一个类,在Java中,类之间的继承必须是单继承
多继承:指的是一个接口可以继承于多个接口,在Java中,只有接口可以多继承,类是不允许的
多层继承:这应该不能算是一种继承方式,这表示我们可以进行一下继承方式:A继承B,B继承C.....,多层继承形成了体系

使用继承语法:

关键字:extends
class Fu{}class Zi extends Fu{}



为什么类间不允许多继承,而接口可以?

为什么类间不允许多继承呢?这是因为会有二义性的可能,例如假如可以多继承,观察一下代码:
class C{void show(){s.o.p("C");}}class B{void show(){s.o.p("B");}}class A extends B,C{}main(){A a=new A();a.show();}
那么此时是调用哪个类中的show方法呢?这就是二义性

那么又为什么接口间可以多继承呢?
通过分析我们发现,之所以类间不允许多继承本质原因是子类的两个父类中有同名的方法实现,导致jvm在调用该方法时不知道该执行哪个方法体,这里的二义性。而接口却没有这个问题(下一节详细讲接口,这里先提一下),我们简单的了解一下,接口里面就是一堆方法声明,但是都没有实现,这就很好理解了,观察以下代码:
interface C{public abstract void show();}interface B{public abstract void show();}interface A extends B,C{}class Demo implements A{public void show(){s.o.p("Demo");}}main(){Demo d=new Demo();d.show();}<span style="white-space:pre"></span>
我们发现,虽然接口B,C中也有同名的方法,但是当A继承于B,C时,实际上对于这样的方法声明只会保存一个,因为他们一模一样,这就导致了使用B的或者C的都一样的结果,最本质的原因就是这两个方法声明没有方法体,因此不存在二义性问题

对继承体系的使用:

简单来说就是:查阅父类,使用子类
原因1:首先父类可能是抽象的,也就无法使用
原因2:子类具有更多的功能
原因3:父类中都是共性的部分,查阅这些部分更有助于理解体系的整体功能

使用继承的前提:

如果想让一个类继承于另一个类,那么必须这两个类之间存在着继承关系,不能单纯的为了使用某个类的功能而去继承它。

二、super关键字和继承中的属性,方法,构造函数

super关键字:

这个关键字只有在继承体系的子类中才能使用,它代表的是那些子类从父类中继承下来的内容的引用。

子父类中的同名变量:

三种情况:
a.子父类中有同名变量(且父类中不为private)子类访问自己的加this,访问父类的加super
b.只有父类有var使用this,super,或者什么都不加都能访问到父类中的var
c.只有子类有var使用this,什么都不加可以取到var,但是super会报错,因为super只能取到父类的属性方法。
原因:本质上只有子类对象,this,super都能指向子类对象,是多态的体现。
若对象名为p,继承于k,则this.var相当于p.var,不管var是子类还是父类,都能访问super.var相当于k.var,所以只能访问到父类k中的成员

子父类中的一致的方法:

子父类中一致(一致表示一模一样,包括返回值和参数列表)的函数:子类中的函数覆盖父类的函数
用处:子类继承父类,有跟父类一样的功能函数但是内容不同,可以使用覆盖,增强代码的扩展性
注意:子类覆盖父类方法,必须子类方法的访问权限大于等于父类,静态只能覆盖静态
小知识点:若父类中方法为private时,不能称为覆盖,因为压根子类就没继承父类的该方法,何来覆盖 。

子父类中的构造函数:

子类构造函数第一行有隐式语句super(),进行父类变量初始化(显式,构造函数,构造代码块都执行)
如果子类需要访问父类中其他构造函数(带参数的),那么需要手动指定,例如super(12)表示访问父类中带一个int参数的构造函数。
因为super()与this()都要放到第一行,如果写了this(),那么就不在此构造函数中访问父类构造函数了,而是到this()中访问。也就是说肯定会访问一次父类构造函数

为什么子类中的构造函数一定要访问父类中的构造函数

因为子类继承了父类中的变量,那么这些变量的初始化工作应该交给父类构造函数,子类肯定要先super()进行初始化,否则数据可能无意义继承中初始化顺序:super()-->显式初始化,构造代码块,构造函数

本节小练习:

class Person{//String name="baba";int age;}class Student extends Person{String name="erzi";public void show(){System.out.println("I am "+this.name+", my father is "+super.name);}}class Worker extends Person{String name="worke";public void show(){System.out.println("my name is "+name);System.out.println("my name is "+this.name);System.out.println("my name is "+super.name);//不管是直接访问name,还是this,还是super,最终都是父类的name}}class ExtendsDemo{public static void main(String[] args){Student s=new Student();s.show();Worker w = new Worker();w.show();Person p = new Student();((Student)p).show();}}
运行图


三、继承破坏封装性

学到继承这里可能大家会有这样一个疑惑,不是说类是一种封装机制吗?那继承又能获得类的这些方法和属性,而且还能复写,这难道不是破坏了封装性吗?答案是:YES,确实破坏了封装性,但是我们说一种机制我们是否要使用,是要看在特定的环境下是利大于弊,还是弊大于利,如果是利大于弊,那么我们就要去使用它。
对于面向对象来说,继承是非常重要的,它大大的提高了程序代码的复用性,成倍的减少了开发时间,同时又具有很强的扩展性,因此我们还是要使用它
这时我们就发现了一个问题,假如我们写了一个工具类,对外提供接口供别人使用,但是如果那个人用一个类把我的工具类给继承了,那么就可以篡改我的工具类的方法了,这是我们不希望看到的,也就是说有时候一些类和方法等我们不想让他们被继承或者复写,这时怎么办呢?Java提供了一个关键字来解决这类问题。

final:

最终,该关键字可以用来修饰类,方法和变量。
修饰类:类变成最终类,不能被继承,这就保护了这个类的封装性
修饰方法:方法不能被复写
修饰变量:变量变成常量,只能被赋值一次,通常被final修饰的变量命名方式为全大写,多个单词间用_连接,例如MAX_EXELEMENT。


四、抽象类

有一天我们遇到了一个需求:要求我们设计一下几个类:学生类,老师类,工人类,然后我们开始设计了:
class Student{String name;int age;String num;void eat(){.....}void walk(){......}void study(){....}}class Teacher{String name;int age;String tell;void eat(){....}void walk(){....}void teach(){....}}class Worker{String name;int age;int workYear;void eat(){....}void walk(){....}void work(){....}}
我们看到,就是简单的三个类,而且也没有多少属性和方法,但是已经非常繁琐了,而且我们发现有大量的重复代码,这不符合我们的代码复用的思想,因此我们就要考虑该怎么设计类使得代码的复用能够提高呢?
这时我们发现虽然这是三个不同的类,但是综合来看他们都属于一类事物,人这个事物。也就是说他们都可以继承于人,那我们能不能先设计人,然后让这三个类继承于人呢?
class Person{String name;int age;void eat(){....}void walk(){....}}class Student extends Person{String num;void study(){....}}class Teacher extends Person{String tell;void teach(){....}}class Worker extends Person{int workYear;void work(){....}}
我们发现,重复的代码大大减少了,基本已经没有重复代码了,这就是继承的威力,大大的提高了代码的复用性。当然了使用继承也有一点一定要注意的,观察一下问题:假如要我们设计两个类:学生类(name,age,weight,study()),电脑类(name,age,weight,run())。我们发现这两个类也有很多一样的属性,那我们能不能设计一个父类呢,例如:学生电脑类(name,age,weigth),然后让学生类和电脑类继承于该类呢?答案是:NO,从第一节我们知道,如果想让一个类继承于另一个类,那么必须是这两个类本身存在着继承关系,不能单纯的为了复用代码而继承
回到上面的需求,我们定义了一个父类人类,来达到代码复用的效果,我们发现人这个类也是可以创建对象的,这似乎不是很妥当,因为首先我们不需要它创建对象,其次它创建的对象也没有意义,这时怎么处理呢?Java提供了抽象类来解决这个问题。
抽象类:当多个类中有方法功能相同,但是内容不同时,可以向上抽取为抽象方法,存入抽象类中

使用抽象类注意事项:

子类必须覆盖所有抽象方法,如果只覆盖了部分,那么该子类也为抽象类,因为继承了抽象方法


五、模板方法模式

当类中功能一部分确定,一部分不确定时,将不确定的封装成单独方法,既可以将此方法设为抽象(则要使用该功能,必须由子类继承并实现该单独方法),也可以将此方法有默认实现,如果有特殊要求,那么子类覆盖即可
例如做蛋糕,月饼:模板类相当于模子,形状可以确定,但是放什么材料不确定,于是将材料单独封装起来,可以默认放面粉,也可以什么都不放,等着具体要求来了再放

模板方法模式练习:

/**测试某段代码运行时间System.currentTimeMillis()返回当前时间与协调世界时之差的毫秒数同时:GetTime类就应该是用于给测试运行时间的类所继承,它的getTime方法不能被复写,用final修饰同样的:此类也可以不讲code()方法抽象,也可以给它默认实现方式,如果子类有其他需求,再复写它即可,也不是一定要getTime方法为final,只是此处需求而已,关键是模板方法的思想,而不是具体代码实现。*/abstract class GetTime{public final void getTime(){long start=System.currentTimeMillis();code();long end=System.currentTimeMillis();System.out.println("运行时间:"+(end-start)+"毫秒");}public abstract void code();}class GetCodeTime extends GetTime{public void code(){for(int x=0;x<200;x++)System.out.println(x);}}class templateDemo{public static void main(String[] args){GetCodeTime gct=new GetCodeTime();gct.getTime();}}
运行图



六、接口

什么是接口?

接口的表现形式看起来就像是一个全是抽象方法的抽象类一样,它作为类的一个功能扩展。对外提高的一套标准的方法供外界调用。也可以说是一种标准,只要某个类实现这个接口,那么就具有这个接口的标准功能

使用接口语法:

关键字:implements,interface
interface Demo1{}interface Demo2{}class A implements Demo1,Demo2{}
我们可以看到一个类是可以实现多个接口的,这在某些方面也大大提高了代码的扩展性
同时,由于可以多实现接口,因为类在实现接口时,不像在继承类时存在着局限性

接口中成员特点:

接口成员是由自己的固定格式:
常量:public static final int NUM=12;
抽象方法:public abstract void function();

接口的用处:

例如我们有一个父类:人,然后由很多的子类继承于人类,我们发现其中一部分子类有一个功能:编程,但是另外一部分子类没有这个功能,因此该功能不能定义在父类人中,但是如果我们要在每一个由此功能的子类中定义,又违反了代码复用的原则,这时,就是使用接口的时候了,我们可以定义一个接口,里面有编程方法,让有该功能的子类实现这个接口,实现编程方法。
也就是说,接口中的应该是那些一部分子类有的功能,而不是全部子类都有的
简单说就是:基本功能放到父类中,特有功能放到接口中

继承中实现接口的特点:

若类B继承于类A,实现了接口C,那么如果类D继承于类B时,类D可以不用实现父类和接口中那些被类B实现了的抽象方法






七、综合练习

1.抽象类综合练习

/*员工类:姓名,工号,工资经理类:继承自员工类,多个奖金普通员工类:继承自员工类*//*乍一看好像Employee是一个普通员工类,其实不是,因为经理跟普通员工之间是同层次的,不可能有继承的关系(否则就是为了简化代码,实现功能而继承了,大忌),所以员工类应该是经理类和真正的普通员工类共性抽取出来的抽象类,其实应该有经理类和普通员工类都有的属性,和功能(抽象方法),而且每个继承自员工类的类都必须实现。假如说继承自员工类的有老师类,但是老师类的工作方法不确定,因为要看是教什么的老师,那么老师只需实现能实现的方法,然后再作为父类被语文老师,数学老师等继承。*/abstract class Employee{private String name;public void setName(String name){this.name=name;}public String getName(){return name;}private String num;public void setNum(String num){this.num=num;}public String getNum(){return num;}private double salary;public void setSalary(double salary){this.salary=salary;}public double getSalary(){return salary;}Employee(String name,String num,double salary){this.name=name;this.num=num;this.salary=salary;}public abstract void work();public abstract void show();}class  Manager extends Employee{private double bonus;public void setBonus(double bonus){this.bonus=bonus;}public double getBonus(){return bonus;}Manager(String name,String num,double salary,double bonus){super(name,num,salary);this.bonus=bonus;}public void work(){System.out.println("manager work");}public void show(){System.out.println(getName()+","+getNum()+","+getSalary()+","+bonus);}}class Pro extends Employee{Pro(String name,String num,double salary){super(name,num,salary);}public void work(){System.out.println("pro work");}public void show(){System.out.println(getName()+","+getNum()+","+getSalary());}}abstract class Teacher extends Employee{Teacher(String name,String num,double salary){super(name,num,salary);}public void show(){System.out.println(getName()+","+getNum()+","+getSalary());}}class EnTeacher extends Teacher{EnTeacher(String name,String num,double salary){super(name,num,salary);}public void work(){System.out.println("teach En");}}class CnTeacher extends Teacher{CnTeacher(String name,String num,double salary){super(name,num,salary);}public void work(){System.out.println("teach Cn");}}class abstractDemo{public static void main(String[] args){Manager m=new Manager("helong","JS01",1234,234);m.work();m.show();Pro p=new Pro("ldy","CS01",1234);p.work();p.show();EnTeacher et=new EnTeacher("haha","YY01",234.2);et.work();et.show();CnTeacher ct=new CnTeacher("heihei","YW01",345.3);ct.work();ct.show();}}
运行图



2.接口综合练习

/**1.模版方法设计模式:GetTime类,使用abstract和不用两种实现,final看情况用2.接口:单继承(接口与接口,类与类),多继承(接口与接口),单实现,多实现(接口与类)3.不支持多继承(类与类)原因,支持多实现,多继承(接口与接口)的原因4.接口特点,用处(什么情况用接口)5.哪种功能定义在类中,哪种又在接口中*//*单继承:类单继承与类,接口单继承与接口多继承:接口多继承与接口实现:类实现接口,可多个不支持多继承:多个父类可能存在同名且参数列表相同的方法例如父类A:int f(int x)父类B:void f(int x)此时用子类调用时zi.f(x);JVM根本不能确定调用的是哪个方法支持多实现:即便多个接口出现了同名,同参的方法也没有问题,因为本来都是抽象方法,没有方法体,不存在二义性问题,实现哪个都一样接口特点:1.程序对外暴露的规则。2.提供程序的扩展性。3.降低了各部分的耦合性用处:当部分子类存在某功能,但是部分不存在,那么该功能不能放到父类中让子类继承,而是因为作为接口,成为该继承体系的扩展功能。子类都具有的基本功能放到父类中定义,而特有功能放到接口中作为部分子类的扩展。*///模板方法设计模式,提高代码复用性,扩展性class GetTime{public final void getTime(){long start=System.currentTimeMillis();code();long end=System.currentTimeMillis();System.out.println("程序用时:"+(end-start));}public void code(){//默认实现for(int i=0;i<5000;i++){System.out.println(i);}}}class My_GetTime extends GetTime{public void code(){int x=0;for(int i=0;i<10000;i++){x+=5;System.out.println(x);}}}//接口的使用,提高代码扩展性abstract class Student{public String name;public int age;public abstract void study();public void sleep(){System.out.println("I am sleeping");}}interface Smoke{public static final int COUNT=10;public abstract void smoke();}interface Drike{public abstract void drike();}class JavaStudent extends Student{public void study(){System.out.println("学习java知识");}}class RubyStudent extends Student{public void study(){System.out.println("学习ruby知识");}}class SmokeJavaStudent extends JavaStudent implements Smoke{public void smoke(){System.out.println("I am smoke "+COUNT+" package");}}class DrikeSmokeJavaStudent extends SmokeJavaStudent implements Drike{public void drike(){System.out.println("I am driking");}//public void smoke()//{//System.out.println("自己抽烟,不用父类的");//}}class DSJStudent extends Student implements Smoke,Drike{public void study(){System.out.println("自己实现学习java");}public void smoke(){System.out.println("自己实现抽烟方法");}public void drike(){System.out.println("自己实现喝酒方法");}}class interfaceDemo{public static void main(String[] args){My_GetTime mgt=new My_GetTime();//mgt.getTime();//接口测试JavaStudent js = new JavaStudent();js.study();js.sleep();RubyStudent rs = new RubyStudent();rs.study();rs.sleep();SmokeJavaStudent sjs = new SmokeJavaStudent();sjs.smoke();sjs.study();sjs.sleep();DrikeSmokeJavaStudent dsjs = new DrikeSmokeJavaStudent();dsjs.smoke();dsjs.drike();dsjs.study();dsjs.sleep();DSJStudent d=new DSJStudent();d.study();d.smoke();d.drike();}}
运行图




八、总结

通过这一部分的学习,基本能够掌握继承的使用,我发现继承确实是一个非常强大的机制,合理的使用继承可以达到代码复用率极高的效果。而接口则更像是对继承机制的一种补充和增强。很多时候一些功能不能定义在父类中,让子类使用,那么这时候使用接口是最适合不过的了,而且学到后面会发现,接口同样是实现多态的重要手段。


------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

0 0
原创粉丝点击