Java基础day07笔记:继承|子父类中变量、函数、构造函数的

2018-12-03  本文已影响0人  楠楠喜欢泡枸杞

    01-面向对象(继承-概述)

        接下来讲面向对象的另一个特征:继承。

学生类和工人类

        我们发现,学生和工人都有姓名和年龄属性。

        思考之后,将学生和工人的共性描述提取出来,单独进行描述,只要让学生和工人与单独描述的这个类有关系,就可以了。

继承关系

        引入一个新的关键词:extends(继承)

        Person是基类,学生和工人是Person的子类。

        继承的作用:

        1,提高了代码的复用性。

        2,让类与类之间产生了关系。有了这个关系,才有了多态的特性。

        注意:

        千万不要为了获取其他类的功能,简化代码而继承。

        必须是类与类之间有所属关系才可以继承。所属关系:* is a *

        有个例子,哈哈哈哈,比如别人有个iphoneXS,你想玩,但不能为了玩它管别人叫爸爸,哈哈哈。这个时候就不需要继承。

    02-面向对象(继承-概述2)

        怎么判断是否有所属关系呢?

        如果父类中的内容,子类都有,则有所属关系。如果父类中有的内容子类没有,则没有所属关系。

        比如:

        在这个例子中,B继承A之后,B拿到demo1的同时,也拿到demo2,而它不需要demo2,所以它们没有所属关系。

        虽然A和B之间没有直接继承关系,但是它们之间有共性。

        将共性抽取出来,封装进类C中:

        A和B继承C即可。

        在Java语言中:java只支持单继承,不支持多继承。

        因为多继承容易带来隐患。

        假设,C继承了A和B:

        这时c.show()该打印a还是b?

        多继承带来的安全隐患:

        当多个父类中定义了相同功能,当功能内容不同时,子类对象不确定要运行哪一个。

        而C++支持多继承,所以Java语言在这一方面相对比C++做了优化。

        但是java保留了这种机制。并用另一种体现形式来完成表示,叫做多实现,也就是对多继承的一个改良,这个知识点后面会说。

        java支持多层继承。

        C继承B,B继承A这样。

多继承

        那么,如果创建c对象,能不能使用a中内容呢?

        可以。

        A,B,C就相当于爷爷、父亲、孙子的关系,父亲继承了爷爷的东西,孙子当然可以用爷爷的东西。

        java支持多层继承。也就是一个继承体系。

        如何使用一个继承体系中的功能呢?

        想要使用体系,先查阅体系父类的描述,因为父类中定义的是该体系中的共性功能。

        通过了解共性功能,就可以知道该体系的基本功能。

        那么这个体系已经可以基本使用了。

        那么在具体调用时,要创建最子类的对象,为什么呢?一是因为有可能父类不能创建对象,二是创建子类对象可以使用更多的功能,包括基本的也包括特有的。

        简单一句话:查阅父类功能,创建子类对象使用功能。

        实际开发的时候就按这个规律来找,否则一个一个查是查不完的。

    03-面向对象(聚集关系)   

       Java中类与类之间的关系不只继承这一种,还有其他关系。

        聚集关系(has a),分成两种,组合和聚合,它们俩的紧密联系程度会稍有不同。

        组合:手是人身体的一部分。

        聚合:球员是球队中的一部分。

        手是人身体不可或缺的一部分,人没有手是不行的,但球队离开某一个球员就没有这么严重的后果。

    04-面向对象(子父类中变量的特点)

         类中成员:

        1,变量。

        2,函数。

        3,构造函数。

        子父类出现后,类成员的特点:

        1.变量

        当父类与子类中出现同名变量时,打印的是子类:

       但是又想在子类中访问父类的num,为了区分这里引入一个新的关键词:super

        如果子类中出现非私有的同名成员变量时,子类要访问本类中的变量,用this;子类要访问父类中的同名变量,用super。

        super的使用和this的使用几乎一致。

        this代表的是本类对象的引用,super代表的是父类对象的引用。

        一个例子:

        这种情况下,super.num和this.num打印出来的都是4。

        可以有两种理解方式:

        第一种,子类继承了父类,那么子类中相当于有自己的num,那么this就是用自己的,没有问题。

        第二种,super是引用没有错,this是引用没有错,这个时候this和super指向的都是同一个对象:new zi();

        而super代表的是父类引用,现在没有父类对象,只有子类对象,那么super和this指向的是同一个对象。

        其实挺不好理解的,this指向子类的对象可以理解,因为是本类的,super为什么也指向子类的对象呢?

        这个就是面向对象的第三个特征:多态,父类引用指向子类对象。

        比如过来一只猫,我们说,诶~这动物好可爱,这种说法也没错~

        再比如我想要一只动物,你给我一只猫,也没毛病~

        这个后面还会仔细讲。

    05-面向对象(子父类中函数的特点-覆盖)

        刚刚所说的子父类中出现同名变量的例子其实很少见,因为父类已经有这个变量了,子类可以直接获取这个变量,就没有自己再定义的必要了。

        接下来说另一部分,函数。

        2.函数

        当子类出现和父类一模一样的函数时,子类对象调用该函数,会运行子类函数的内容,如同父类的函数被覆盖一样。

        这种情况是函数的另一个特性:重写(覆盖)

        父类函数其实还在内存当中,只是没有运行而已,不要真的以为它被覆盖掉了~

        一个例子,框出的两个函数功能是相同的:

        当子类沿袭了父类的功能到子类中,但是子类已经具备该功能,只是功能的内容和父类稍有区别。这时,没有必要定义新功能,而是使用覆盖特性,保留父类的功能定义,并重写功能内容。

        注意:修改源码绝对是灾难。

        因此,不用重新定义speak2函数,只需要重写父类speak函数即可:

        手机的例子,出了新款手机,加了新的功能,我们不需要在源码中修改,只需要继承、复写它里面的功能,就可以啦:

        复写还可以用于扩展。

        这个升级我们发现如上那种方式写也可以,但还会有点麻烦,比如来电号码,父类已经完成这个了,子类只需要在原有功能上加个姓名和照片就完事了。

        注意一定不要写成this.show();,否则会变成递归,自己调用自己,陷入死循环。

        覆盖的注意事项:

        1,子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败。

        2,静态只能覆盖静态。(如果覆盖非静态,想想先加载后加载的问题,就知道不可以)

        我们之前用的权限修饰符有private,public,但是也有时候没有用修饰符,这个时候是什么权限呢?默认权限。默认权限介于私有和公有之间。

        像这种就不能叫覆盖,子类都不知道父类有这个方法。

       这样也不行哦,子类权限并不比父类权限大:

        记住:

        重载:只看同名函数的参数列表。

        重写:子父类方法要一模一样,包括返回值类型。(当然,在多态中,父子类的返回值类型是可以有不同的)

        比如,像下面这种情况,调show()谁运行呢?

        这样会报错的,因为虚拟机也不知道该运行哪个。

    06-面向对象(子父类中构造函数的特点-子类实例化过程)

        3.子父类中的构造函数。

         我们new一个Zi,运行结果是这样滴:

        这是为什么捏?

        这个原因是因为,子类的构造函数前面其实有一条隐式的语句,这句话你不写,系统会自动默默加在前面:

        在对子类对象进行初始化时,父类的构造函数也会运行,那是因为子类的构造函数默认第一行有一条隐式的语句 super();

        super():会访问父类中空参数的构造函数。而且子类中所有的构造函数默认第一行都是super();

        再写一个带参数的构造函数试试:

        这个构造函数也会运行父类构造函数哦:

        即使我们再写一个父类的构造函数,也不会影响最后的结果,因为和新写的构造函数并没有关系,只和父类空参数的构造函数有关哦。

       试着改一下父类的空参数的构造函数,改成有参的,这个时候程序挂掉了:

        这个时候我们的解决方案是,在子类构造函数中手动指定父类的构造函数,像酱紫:

        程序就没有问题啦。

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

        因为父类中的数据子类可以直接获取。所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。

        所以子类在对象初始化时,要先访问一下父类中的构造函数。

        如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。

        注意:

        super语句一定定义在子类构造函数的第一行。

        这里有个例子:

        第二个构造函数第一行是this(),调用了本类的空参数构造函数,这个时候就不调用父类的空参数构造函数super()啦。

        可是不是说子类在对象初始化时,要先访问一下父类中的构造函数吗?

        没关系,第二个构造函数不是调用了本类的空参数的构造函数嘛,本类的空参数的构造函数的第一行就是super()呀,逃不过哒。

        结论:

        子类的所有的构造函数,默认都会访问父类中空参数的构造函数。

        因为子类每一个构造函数内的第一行都有一句隐式super();

        当父类中没有空参数的构造函数时,子类必须手动通过super();语句形式来指定要访问的父类中的构造函数。

        当然,子类的构造函数第一行也可以手动指定this();语句来访问本类中的构造函数。子类中至少会有一个构造函数会访问父类中的构造函数。

        其实,这个过程就叫做子类的实例化过程

        那父类中有木有super();语句呢?

        有的。

        它找的是谁呢?

        它找的是父类的父类。

        而Java当中有一个类特别牛,叫做所有类的父类。就是Object。

        还有个问题,为什么this();和super();不能在同一行?

        因为它们都只能写第一行。

        为什么必须要写第一行?

        因为初始化动作要先做。

    07-面向对象(final关键字)

        final关键字:       

        final关键字是什么意思呢?

        最终。

        final关键字有什么特点呢?

        1,final可以修饰类,方法,变量。

        2,final修饰的类不可以被继承。

        继承的出现很有好处呀,复用性提高了,而且也有多态了。

        但是继承有一个弊端,就是它打破了封装性。

        什么意思呢?

        本来写一个类写的好好的,定义了一些功能,子类不好好用这些功能,还要把它复写,输出一个哈哈!哼!好过分!

        所以集成有利有弊,它的出现对封装性是一个挑战,里面一些东西反倒不是隐藏了,还能复写。

        比如说,搞了一个类,里面有几个功能,有些功能调用的直接是虚拟机底层的内容。换句话说,调用了系统内容了。而你把它一复写,把功能改掉了,这个功能还能遇到底层吗?

        为了进一步保证封装性,还能有继承的特性,叫做:有些类我不让你继承,这里面的功能你不允许复写,它是固定的,要么用要么不用。

        所以,为了避免被继承,被子类复写,定义类的时候加上修饰符final,这个类就叫最终类,不能被子类所继承。

        如下例,再继承就会出错:

        3,final修饰的方法不可以被覆盖。

        比如现在有个类,里面有十个功能,里面4个是调用系统功能不能被复写,另外6个是可以被复写的,这个时候就不能把这个类都定义为final,否则都不能复写了。

        这个时候,只对不允许被复写的方法修饰final就好了。

        如下例,覆盖了final修饰的方法会报错:

        4,final修饰的变量是一个常量,只能被赋值一次。既可以修饰成员变量,又可以修饰局部变量。

         加了final修饰符后,x就终身为3,y就终身为4了:

        把y重新赋值会怎样呢?

        程序会挂掉的:   

        当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字,方便于阅读。

        而这个值不需要改变,所以加上final修饰。作为常量:常量的书写规范是所有字母都大写,如果由多个单词组成,单词通过_连接。

        比如圆周率π:

        像这样的数据以后还会碰到很多,都是把一些特定的数据用一些名称来标识。

        注意:

        以后写程序的过程中,但凡出现了一些固定数据,即使只出现一次,也建议把这个数据起个名字,人们阅读这个名字,绝对要比阅读数字来得快来得方便。当然前提是这个数据不能变。

        其实final的作用已经体现出来了,就相当于一个锁变量罐,对于一个变量,把final一加,这个变量空间就锁住了,值就固定在里面了,再往里面进值就进不去了。

        而成员变量在不变化的情况下,往往还会跟上一个修饰符:static。如果再加上public,就变成全局常量了,类名就可以直接访问。 

        5,内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量。

    08-面向对象(抽象类)

        当多个类中出现相同功能,但是功能主体不同,这时可以进行向上抽取。

        这时,只抽取功能定义,而不抽取功能主体。

        引入修饰符:abstract(抽象),被它修饰的方法意思就是看不懂的方法。

        抽象方法必须定义在抽象类中。

        这样有个好处,别人都知道它是抽象类,就不会创建它的对象。否则创建了它的对象,调用其中的抽象方法,并没有意义。

        例:

        抽象类的特点:

        1,抽象方法一定在抽象类中。

        2,抽象方法和抽象类都必须被abstract关键字修饰。

        3,抽象类不可以用new创建对象,因为调用抽象方法没意义。    

        我们试着new一下,会报错:

        (就算我们把类和方法前面的abstract修饰符撤掉,再建立该类的对象并调用那个空方法,而且运行也不会报错,可是它又有什么意义呢?并没有。)

        4,抽象类中的方法要被使用,必须由子类复写其所有的抽象方法后,建立子类对象调用。

            如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。

    09-面向对象(抽象类2)

        抽象方法有一个好处,就是必须被复写,强迫子类去完成复写这件事情。

        如果没有抽象,那子类可以偷懒直接拿去用。但抽象了,子类就必须复写,完成自己的特定功能。也算是功能分工的明确。

        抽象类中可以有非抽象方法。

        抽象类和一般类没有太大的不同。

        该如何描述事物,就如何描述事物,只不过,该事物出现了一些看不懂的东西。

        这些不确定的部分,也是该事物的功能,需要明确出现,但是无法定义主体。这些通过抽象方法来表示。凡是功能不确定的,就沿袭到子类去做,但是它也属于父类的一部分。

        抽象类和一般类的一丢丢小小不同:

        1,抽象类比一般类多了抽象函数。就是在类中可以定义抽象方法。

        抽象类中可以不定义抽象方法吗?

        这是可以的。

        那它岂不是没用嘛?

        有用的。

        有一个特殊作用,但不多见:抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象。

        2,抽象类不可以实例化。

        PS:abstract可以修饰类、方法,但不可以修饰变量哦,没有抽象变量这一说。

    10-面向对象(抽象类练习)

        接下来做一个练习:

        分析:

        定义员工类:

        工作内容是这个方法是抽象的,因为每个人干的活都不一样。

        经理类继承员工类:

        经理干经理的活,复写工作内容的抽象方法。

        再来一个普通员工,普通员工也复写工作内容这个抽象方法,普通员工干普通员工的活:

    11-面向对象(模版方法模式)

        一个例子:

        需求:获取一段程序运行的时间。

        原理:获取程序开始和结束的时间并相减即可。    

        获取时间用到的方法:System.currentTimeMillis();

        定义一个类,在其中写一个获取时间方法,建立对象并运行获取时间方法:

        
        成功获取到时间啦。

        接下来有一个小问题,如果想把中间那段代码换成其他代码该怎么办呢?

        一次一次在代码中间手动改很麻烦。

        可不可以把这部分可以修改的代码单独封装一下呢?

        这样可以吗:

        想用到这个方法的时候,只需继承这个类,复写runcode()方法就好啦。

        虽然调用的依然是父类的getTime()方法,但由于子类已经将runcode()覆盖掉了,所以父类的getTime()方法运行的是子类的runcode()方法。

        这样就很方便程序扩展。

        那么再思考一下,我们定义了一个获取时间的功能,功能中获取的是哪段代码的时间,这段代码不明确,不确定则必然是抽象的。

        此时这个类也变成了抽象类:

        因为我们这个类就是提供了获取时间的功能,getTime()方法提供的功能就是获取时间,所以不允许复写,用final修饰: 

        到目前为止,这个类就是能够获得某段代码运行时间的一个类了:

       当代码完成优化后,就可以解决这类问题。

        这种方式,我们取名为模板方法设计模式

        什么是模板方法呢?

        在定义功能时,功能的一部分是确定的,但是有一部分是不确定的,而确定的部分在使用不确定的部分,那么这时就将不确定的部分暴露出去,由该类的子类去完成。

        啥叫模板呢?

        就跟做月饼那个模子一样,做出来的形状是确定的,但是馅儿是不确定的,我们可以豆沙、放蛋黄、放芝士~流口水ing~~~

        但是注意一点,不确定的那段代码不一定是抽象的哦,有可能这个方法里面有默认的内容,你不爽你可以复写~确定的那段代码也不一定是固定的要用final修饰,这里我们不愿意让它被复写所以用final修饰了,如果不是很介意的话也可以不用final修饰。

    12-面向对象(接口)

        我们发现,抽象类中可以定义抽象方法,也可以定义非抽象方法,那么如果抽象类中的方法全都是抽象的,这个时候我们可以把它转换成另外一种体现形式,叫做接口

        当然接口产生的原因并不是上段所说的,哈哈,只是正好说到这里很形象的带到这里了。

        接口:初期理解,可以认为是一个特殊的抽象类。

                当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。

        class用于定义类。

        interface用于定义接口。

        接口定义时,格式特点:

        1,接口中常见定义:常量,抽象方法。

        2,接口中的成员都有固定修饰符。

                常量:public static final

                方法:public abstract

         记住:接口中的成员都是public的。

         比如:

        当然,在写的过程中我们也可以将常量和方法前面的固定修饰符省略掉,也不影响,因为只要前面使用interface修饰的,里面的成员都是默认有固定修饰符的。

        但是这样省略写法的话阅读性就会很差,我们就不知道它是公有的,也不知道它是可以被名称访问的。所以写的时候还是写全最好。

        而且直接写void show();会引起误解。

        比如正常写void() show();编译的时候会出现错误提示: 

        这个时候要么补一个方法主体,要么声明抽象。

        接口能创建对象吗?

        肯定不能。

        这个时候就搞一个类去实现它。

        类与类之间是继承关系,而类与接口之间就变成了实现关系:implements。

        类为什么不是实现类呢?这个不一定哦。不过叫继承是因为类里面一般会有一些非抽象方法可以直接拿来用,这个子类不用太清楚。而接口里面全都是抽象方法,子类拿过来全部要复写。所以这个时候就需要更确切的表达方式,子类要将接口中的方法全都实现后才能实例。否则,子类就是一个抽象类。

        还用刚才那个例子:

        其中NUM的调用方式有三种:

        分别是:对象调用、类名调用、接口调用。

    13-面向对象(接口2)

        Java不支持多继承,但它有另一个方式:多实现

        接口可以被类多实现。

        比如这就是多实现:

        不支持多继承的原因是担心方法会有重复,担心子类调用的问题。

        那么为什么接口就没这个问题呢?

        其实关键就是一点:多继承里的方法有方法体,多实现里的方法没有方法体,你爱怎么弄怎么弄~

        假如真的有两个接口的方法都叫show():

        接口中的show()方法都没有主体,可以由子类来任意定义。

        搞定~

        一个类在继承一个类的同时,还可以实现多个接口。

        先继承,再实现,可以扩展这个类的功能。

        接口与接口之间其实也有关系:继承。

        例:

        还有更厉害的呢。接口之间可以多继承,像酱紫:

        注意:别这么写哦,这么设计是有问题的:

        编译器会报错的。

    14-面向对象(接口的特点)

        定义接口可以有什么好处捏?

        举个例子。

        以前想换CPU了直接换一个主板,但是我们在主板上弄一个槽来插CPU,下次想换CPU直接把CPU拔出来,换一个新的就好了。CPU更新换代快,这样做可以在更新CPU的前提下提高主板的复用性、功能的扩展性。

        这个槽就相当于接口。

        接口的特点:

        1,接口是对外暴露的规则。

        2,接口是程序的功能扩展。

        3,接口可以用来多实现。

        卡槽的出现降低了CPU和主板的耦合性,接口的出现也降低了这两个设备的耦合性。

        Intel的CPU既可以插在华硕的板子上,也可以插在别的板子上。只要它们定义的是同一个插槽就都能用。

        什么开发最难受?

        就是依赖性太强。你没做,我也不能做。你做不出来,我也动不了。

        降低了耦合性,你做你的,我做我的,就很好了,模块化的开发。

        再举个例子。

        笔记本的USB接口。想要升级硬盘,这个时候只要能对得上这个口,不用拆电脑也可以实现升级。买个移动硬盘咔一插,就扩展了。

        4,类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口。

        5,接口与接口之间可以有继承关系。

    15-面向对象(接口举例体现)

        接下来通过一个实例来说明接口这个事情。

        这个实例可能稍微有点不恰当,但能说清楚问题。

        定义了一个学生类,其中有学习和睡觉的行为。

        张三继承了学生类,具有所有学生具有的行为。张三还是一个烟民,有抽烟的行为。这时定义了一个抽烟的接口,张三继承学生类的同时实现了抽烟接口。

        而李四不抽烟,所以他继承学生类就好了,不用实现抽烟的接口。

        因此,接口可以用于类功能的扩展。

        再来一个运动员的例子。

        运动员都有运动的行为,有的运动员还学Java。基本功能定义在(父)类中,扩展功能定义在接口中。

        大概酱紫:

        

        

上一篇下一篇

猜你喜欢

热点阅读