继承2

151 阅读15分钟

继承2 -- Inherit

上一讲的关键

1、什么是继承? 2、什么时候做继承? is-a关系的时候 3、怎么做继承? extends 单继承 4、父类中的属性和行为的表现? 全部被子类继承,但能否访问受父类定义它的访问修饰符的影响 5、构造方法的表现? 不被继承 但参与实现 内存叠加 6、特殊情况:子类中定义与父类同名的属性和行为有啥效果? 同名属性 子类会产生一个新的,很少出现这种情况 同名方法 方法的重写 经常出现的一种设计,能够体现类的丰富度 7、访问修饰符4种情况 8、重载与重写的区别

作业点评

拿到一个场景如何进行简单快速的OO分析: 1、找名词 游戏,盗贼,猎人,医生,农民,士兵 ,角色,名称、等级,体力,攻击力,防御力,金币,角色加权

2、在名词当中分析出哪些是我们要自定义的类型 游戏:在这个场景中,游戏不是一个具体的类型,而是整个场景的描述,我们可以看成是这个软件的整体名词。应该是整个项目叫做“游戏”,而不是这个项目中的某一个具体的类。

盗贼,猎人,医生,农民,士兵 :这些是在Java语言中没有的数据类型,需要我们自定义,包括它们有哪些属性,哪些行为。

角色:盗贼,猎人,医生,农民,士兵 都是角色。如果它们有共有属性和行为,我们是能够定义在角色身上的,所以角色如果要需要定义,那么它就是盗贼,猎人,医生,农民,士兵 的父类。

名称、等级,体力,攻击力,防御力,金币,角色加权:我们发现在Java语言中都有现成的数据类型表示,够用。

3、设计类与属性 角色:名称、等级,体力,攻击力,防御力,金币,三种角色加权 盗贼,猎人,医生,农民,士兵 : 没有特殊属性

4、找动词 自己介绍自己,攻击,防御,偷盗,打猎,采矿,治疗,狂暴

5、把行为关联到第三步设计的类当中去 角色:自己介绍自己,攻击,防御 盗贼:偷盗 猎人:打猎 农民:采矿 医生:治疗 士兵:狂暴 6、找类与类之间的关联关系 盗贼,猎人,医生,农民,士兵 is-a 角色 医生 在治疗行为中 use-a 士兵

继承中的类加载 -- 理论掌握

类加载动作是发生在我们程序代码执行之前,但是在代码中有一个东西是和类加载息息相关的 --- 类当中staic修饰的内容。 其中特别是静态代码块,里面的代码是在加载期被执行的,所以我们需要知道静态代码块在继承关系下,所体现出来的特性。

需要大家掌握的结论: 1、当程序当中需要用到哪个类的时候,就会加载哪个类,也就会执行它的静态代码块; 用谁就加载谁,而不是写了谁就加载谁;

2、如果加载一个子类,那么会自动加载它的父类。因为子类代码中明确告知了有一部分共有属性和行为没有定义在子类本身身上,而是继承于父类。如果不加载父类,这部分共有的子类信息就会丢失掉。 加载的顺序:先父类,再子类。

3、一个类只加载一次! 如果父类已经被加载过了,那么加载子类的时候不会再去加载它。

4、现在的Java虚拟机在加载的时候采用的是JIT(just in time) --- 即时加载 其含义就是,它不是预先把要用到的类进行加载,全部完成以后才开始执行;而是边执行边加载,用到谁就加载谁。

顺口提的你们需要知道缩写: JDK JRE JVM GC JIT IDE(不是IDEA) OO OOA OOD OOP

this 和 super -- 开发中比较常用 面试也常出现

this 关键字

this有两种用法: “this.” 和 “this()”
1、当作为"this."的时候,this的含义指代的是当前对象,口语化就是指"我";
   我们用它主要是用来访问当前对象的属性和行为。这其中既包括的有定义在本类当中的属性和行为,
   也包括从父类继承的属性和行为。
   用“this.”访问本类定义的内容,没有任何问题,都可以看;
           访问父类定义的内容,要受父类中该内容声明的访问修饰符的限制。
           
2this()的作用是在本类的一个构造方法当中,调用本类的另一个构造方法,达到构造方法当中代码
   能够共享。
   特点:
   1this()只能写在构造方法里面,且只能写在第一句;
   2this()是根据实参与形参匹配去寻找到底调用的是本类中的哪一个其他构造;
   3、不能够使用this()在构造方法当中形成“递归”
   4this() 只负责共享被调用构造方法里面的代码,而不会重新创建新对象。           

super关键字

super也有两种用法:"super." 和 “super()”
1、当作为“super.”的时候,super的含义指代的是当前对象的父类对象部分,口语化指的是“我身上来自于父亲的那部分”
用"super."访问本类定义的内容,全部看不到;因为这些内容都是存在于子类特有部分的,而super看的从父类继承过来的那部分。   
         访问父类定义的内容,能看到但同样要受父类中该内容声明的访问修饰符的限制。

总结:“this.”能看到的,"super."不一定能看到;"super."能看到的,"this."一定能看到。因此,我们更多的时候使用的是"this."。

有一种情况,我们会使用"super." 如果子类重写了父类的某个方法,然后我们在子类中又想去调用这个方法在父类里面的实现,那么这个时候要用"super."。如果用"this.",你就在调用重写后的实现了。

2、super()的作用是在子类的构造方法里面,指定调用父类的某一个构造方法。 特点: 1、子类的构造方法里面就算一句代码都没有写,编译器也会自动加一个super(),用来默认调用父类的公共无参构造; 2、super()也是根据实参与形参的匹配去寻找到底调用的是父类的哪一个构造; 3、super()只能写在构造方法里面,也只能写在构造方法的第一句; 这说明一个构造方法里面this() 与 super() 不能共存,只能写一个。

根类:Object -- 非常重要,这是唯一一个里面所有方法都要求Java程序员掌握的类

在Java当中,有一个非常特殊的类 -- Object,它的地位很特殊。它是所有类的根类(包括数组),它是祖宗!!! 任何一个类,如果我们没有主动写继承,那么就会让它默认继承Object这个类。作为一个祖宗类,根据类的继承层次结构,它里面定义的属性和方法,会被子子孙孙无条件的继承下去。

Object 既然是所有类的 根类,那么它的方法一定会被所有类拥有。在这个层面上讲,Object里面方法一定都是共用性最强的方法,是Java认为只要你是一个对象你就该有的方法。 这些方法有: equals --- 判断两个对象是否相等 toString --- 返回对象的字符串描述 hashcode --- 返回哈西值 --- 留到学习集合框架的时候讲 getClass --- 获取类模版对象 --- 留到反射的时候讲 clone --- 克隆 --- 留到设计模式,“原型模式”的时候讲 notify --- 唤醒线程 --- 留到线程的时候讲 notifyAll --- 唤醒所有线程 --- 留到线程的时候讲 wait --- 让线程等待 --- 留到线程的时候讲 finalize --- 对象的销毁方法

equals 方法

当我们在比较两个对象是否相等的时候,我们一直以来都有一个运算符可以做这件事情。这个运算符就是所谓的"=="。 这个“==”,我们长期使用,前面也讲过。基本数据类型的变量可以用来比较两个变量里面的值是否相等; 而引用数据类型的变量,用它比较的时候,是比较两个引用是否相等。什么是引用是否相等?说白了就是这两个变量里面的引用有没有指向同一个对象,指向同一个得到true,没有指向同一个得到false。

但是还有一种更常见的情况,那就是我们要判断是两个对象里面的内容是否相等。这种情况下,"=="是无能为力的了。所以,这就是equals方法要做的事情:判断两个对象内容是否相等。

equals()方法设计出来就是让程序员去重写的。因为,在当年写Object的时候,并不知道后续的程序员会书写什么样的子类(也就是说对子类的内容一无所知),所以他们只能规范: 1、所有的对象都有可能会有相互比较内容的情况发生,为了不让后续程序员各自发挥起各种各样的方法名,他们定义了一个专用的方法去规范; 2、由于不知道要具体后续程序员要如何比?这个比较规则是由具体业务确定的,所以他们暂时先写一个用"=="号比较的实现,以后交给子类的程序员去重写

equals方法的使用: 1、什么时候用?如果我们需要判断本类的两个对象是否相等(内容相等或是业务相等),而不是指向同一个对象。那么我们不用自己去创建方法,而是重写来自于Object的equals方法。

2、怎么用?当然就是在这个类中重写这个方法,然后把需要实现的逻辑在它的方法体里面实现。

3、如果没有重写equals,那么Object当中的实现是"=="号。

toString方法

先解除一个误会,很多初学者会误以为这个方法的作用是:把一个对象转换成字符串。其实不是,而是返回这个对象的字符串描述。

toString方法的使用: 1、它是做什么的? 当我们对一个类的对象进行字符串操作的时候(直接打印一个对象,或直接拼接一个对象),那么JVM会自动调用该对象的toString(),把返回的字符串作为这个对象的描述信息进行使用。

2、咋用? 重写!把你需要看到的信息在该方法的实现内部进行拼接就可以了。

3、如果没有重写该方法,那么它的效果是: 类的限定名@对象的hash值

这里解释一下hash值:hash值是JVM根据hash算法,得到的每个对象在内存存放位置的唯一标识。 它不是内存地址,Java为了程序安全不会把内存地址公布给我们,你们可以想象成用了hash函数对这个值做了一次加密。这个hash值往往是用16进制的方式展示出来的。

final关键字 -- 知识点补全,final不仅仅是用来声明常量的

final关键字 -- final的英文含义就是“最终的”,"不变的“。所以用final修饰的内容都具有最终不变的特点。 1、final修饰的变量(包括属性 和 局部变量,甚至是形参) 就是成为常量

     int a = 10; //a是变量,值可以变
     final int b = 100;//b是常量,值不能变

2、final可以修饰方法,由final修饰的方法不能被“重写”。 记住:是不能重写,不是不能“重载”。重写才是对原方法进行实现部分的改变,重载是添加了一个(或多个)新的重名方法。

final修饰的方法 由于不能被重写,所以我们叫它“最终方法”/“终态方法”。

3、final可以修饰类, 这种类不能被继承。 我们可以把子类看成是对父类的改变,用了父类的属性和行为,还可以自己加新的属性和行为,也能把父类的行为进行重写改变。所以如果一个类被定义为final,不变的,那么它就不能够再生成新的子类。也就是说它不能当爸爸了,成为继承结构树上的某个分支的最终节点。这样的类,称为“终态类”或“最终类”。 4、final不能修饰构造方法。因为构造方法本身就不能被继承(前面讲过的),当然也就不能被重写,所以也不存在被改变的情况。

abstract 关键字

abstract 关键字 是一个修饰符,意思就是“抽象”。

我们发现在很多场景当中,都会出现这么一种情况,所有子类都拥有某一个行为,但是各自的实现不一样。根据我们学到的面向对象的分析,这种情况肯定要把这个共有行为设计到父类当中去。但尴尬的事情发生了,那就是父类虽然有这个行为,但是父类不知道该咋实现。 那么在前面的操作过程中,我们采用了给这个方法打上一对"{}",但是里面不写任何代码,让它做一个空白实现。

这里其实存在一个问题,一个方法是由方法的声明和方法的实现共同组成的。方法的实现就是那对“{}”及其里面的内容。从编程语言的语法结构上来说,只要打上"{}"就叫做这个方法已经实现了,只不过你的实现是什么指令都不发出。

而我们从“宠物”这例子当中,可以看到父类"Pet"只能确定它有这个“叫”的行为,不能确定这个行为的具体实现。因此在宠物这个类当中的叫的行为不应该实现,也就不应该打上这对“{}”。

Java语言在设计的时候考虑到了这种情况,允许一个方法在声明的后面不接"{}",而是用一个";"直接告诉它,这个方法的书写到此为止。形如这样的方法,我们把它称之为“抽象方法”。而抽象方法必须用"abstract"关键字修饰。

这个时候我们看到“Pet”类又再报错,通过查看报错信息,我们发现Java要求“有抽象方法的类必须是一个抽象类”。也就是说“Pet”类的声明处也要加上“abstract”关键字。

什么又是抽象类呢? 抽象类指的的是一种特殊的类,最大的特点是不能产生对象,也就是不能 new。除此之外,抽象类在内容的定义上和普通类没有任何区别,能够定义:属性、构造、方法(抽象方法和非抽象方法)。

抽象类既然不能产生对象,为什么还需要构造方法呢? 我们目前定义类只有两种用法:1、产生对象;2、充当父类。 抽象类不能产生对象了,那它就只能当父类了。也就是说抽象类的任务就是专门为子类规范,你们有哪些共有属性和共有行为的。虽然它不能产生对象,但是它的子类是需要产生对象的,而new一个子类对象必然要求调用父类的构造方法,因为子类是不知道有哪些共有属性和行为的,这些代码都是写在父类身上的。所以,抽象类的构造方法不是自己用的,而是产生子类对象的时候去调用的。

抽象设计的好处是什么呢? 主要好处是把上层父类的设计意图强制的推广给下层类,要求下层子类必须重写哪些方法,省却程序员自己去记忆这个环节。 如果子类不重写父类的抽象方法,那么这个子类必须设计为抽象类,不然编译通不过。

总结一下: 语法上: 1、abstract关键字是用来修饰类 或 方法的;修饰类,这个类就叫“抽象类”;修饰方法,就叫“抽象方法”、 2、有抽象方法的类一定是抽象类; 抽象类在语法上可以没有抽象方法; --- 通常我们不会这么设计; 3、抽象类不能产生对象,只能充当父类; --- 这一点抽象类 与 最终类 刚好相反 4、子类必须重写抽象类当中的所有抽象方法,否则它也是抽象类;---- 不遵守就编译不过,这就是父类的设计意图强制子类去实现。 5、除了以上4点,抽象类在书写的时候与普通类没有不同:属性、构造、实现的方法、静态代码块都可以有。

场景当中: 1、抽象类是自然而然设计出来,就是当我们在提取共有行为到父类中的时候,发现这个行为在父类这个等级“只能确定有这个行为,不能确定这个行为的实现”,那么我们就把这个方法设计为”抽象方法“,这类设计为”抽象类“。 2、抽象类也是类,也可以声明变量,但是不能产生对象。那么它的赋值咋办呢?可以赋值为子类的对象 比如:Pet p = new Dog(); p = new Cat(); 这里将体现出“多态”的特征。