Java09笔记——面对对象编程

Java09笔记——面对对象编程,第1张

目录

1:包

1.1 包的概念:

1.2 包中类的导入

2 继承

2.2 引入继承

protected 关键字

小结:

super 关键字

super 修饰方法 (构造方法 和 普通方法)

final 关键字

组合

多态

方法重写

向下转型

instanceof

抽象类

接口


先复习下修饰符的知识点:

 

原文链接: https://blog.csdn.net/pengbo6665631/article/details/80238220

1:包 1.1 包的概念:

包 (package) 是组织类的一种方式. 使用包的主要目的是保证类的唯一性.

Java中的包就是 *** 作系统中的文件夹,声明一个包使用 package关机键。

若存在多个文件夹的嵌套,使用 “ . ” 点 分隔

输入:

 注释:如果包的展现是并列的,解决办法:

如何查看 包 在本地文件夹的位置:

包的存在就是为了解决类同名的问题
 

一个类的全名称:包名.类名

例如我在刚刚创建的 com 包下创建一个类

 那么此时这个类的全名称为: www.csdn.com.Test

然后我们在 csdn 包下创建一个 同名的 Test 类

发现:两个类的名字一样,却没有发生保存,这就是包的原因。

一个类名是  www.csdn.com.Test

一个类名是  www.csdn.Test

不一样,所以不会报错。

1.2 包中类的导入

如何导入某个包中的某的类呢?

两种办法:

1. 使用类的全名称(太麻烦)

2. 使用 import 导入类

import语句只能导入相关包中的某个具体的类

注释:再次强调:import 是导入 类, 不是导入一个 包,这个概念要区分

举例:我在 www 的包下创建一个 Animal 这个类,现在我们想在com 包下的Test类里面调用这个类创建对象

 这就是 导入 www 包下的 Animal 这个类

重点!!!导入了这个类,就可以根据这个类生成对象,根据对象就可以调用这个类里面相对于的功能了

结论:就是类的名字相同,但在不同包中就可以,只需要明确告诉编译器这个是类是哪个包里面的

提问:如果一个包下面有很多个类,那么一行行导入就显得太麻烦了。

比如java自带的 java.util 这个包下就有很多的类

解决办法:可以这么写: java.util.*

这么写的意思是:将整个 util 包下的所以类按需加载,注意此时导入的还是类,而不是把 util 文件夹导入!!!(import只能导入类,不能导入包)

当我们真正在程序中使用这个包中的某个类的时候,才会被自动加载进来,而不是一开始一股脑的全部加载。

下面这里此时因为用到的util包中的类,才会加载。(目前这里只加载了两个类) 

 提问:如果此时用 * 导入两个包,而这个两个包中却存在相同名字的类,那当我们引用这个类的时候会怎么样?

 在util包和 sql包中都存在 Date这个类,但此时编译器根本就不知道你到底想要的是哪个类,所以会报错!

强行运行结果:

所以:一般不推荐直接使用 import.* 这样的 *** 作,会存在歧义问题!

解决办法:明确的告诉编译器我们要哪个包中的类

1.  import java.util.* 修改成 import java.util.Data;

 2. 使用类的全名称!

 这样也能成功的调用

包的导入还有最后一个概念:静态导入

比如我们平常写的 System.out.println

我们 按住 ctrl + 左键 点击 out这里查看

发现这个out就是被static静态所修饰的。

它就是使用了 System这个类名 . 属性名

我们现在可以这么写:import static java.lang.System.*;

 这句话表示了导入 System 这个类下的所以静态方法和属性

这两个句话产生的效果是一样的。 

注释:被static修饰过的属性不需要通过对象,类名.属性就可以调用。

          被static修饰过的方法不需要通过对象,类名.方法名就可以调用。

但是!由  包的静态导入后 import static 后,可以直接使用 属性名,或者是方法名,让人不清楚这个属性还是方法到底是自己定义的还是由包类导入的。

举例:定义了两个被static修饰的

 然后在别的包下 静态导入

在这个主方法中,我只是导入了静态包,什么都没有定义,就可以直接调用animal这个类里面的static成员或方法了。

 举例:

out.println 和 max 看起来像是我们自己写的方法一样,其实不是,是我们引入的静态方法 

但是!不建议这样直接导入静态方法,会让看代码的人分不清这个是你导入的静态方法,还是自己写的方法。

还是尽量使用 常规的 类名称.方法名字(属性名字)。而不是直接导入静态直接使用方法名字。

补充知识:java中常见的几个包。

常见的系统包

1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。

2. java.lang.reflect:java 反射编程包;

3. java.net:进行网络编程开发包。

4. java.sql:进行数据库开发的支持包。

5. java.util:是java提供的工具程序包。(集合类等) 非常重要

6. java.io:I/O编程开发包。

将类放到包中

基本规则

1. 在文件的最上方加上一个 package 语句指定该代码在哪个包中.

2. 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式(例如 com.baidu.www ).

3. 包名要和代码路径相匹配. 例如创建 com.baidu.www 的包, 那么会存在一个对应的路径 com/baidu/www 来存 储代码.

4. 如果一个类没有 package 语句, 则该类被放到一个默认包中.

2 继承

面对对象编程:封装 、 继承 、 多态

之前我们学了 封装,用private将属性进行封装,它拥有保护性易用性等。

复习一下:

什么是方法重载?

同一个类中,定义了若干个方法名称相同参数列表不同与返回值无关的一组方法。这样的一组方法称之为方法重载。

继承作为面向对象三大特此之一,这次来说继承

概述:

代码中创建的类, 主要是为了抽象现实中的一些事物(包含属性和方法). 有的时候客观事物之间就存在一些关联关系, 那么在表示成类和对象的时候也会存在一定的关联.

比如举例:

我们先创建一个 animal 包,在包下创建 两个类

 

再弄个Test类来测试。 

根据类,产生对象,再引用里面的方法等 *** 作都是我们之前学的。 

但是如果你细心点就会发现,我们写的这些类中的代码完全是一模一样

代码里面存在了大量的冗余代码

再举例:

这三个类都具备一个相同的 eat 方法, 而且行为是完全一样的.

这三个类都具备一个相同的 name 属性, 而且意义是完全一样的.

从逻辑上讲, Cat 和 Bird 都是一种 Animal (is - a 语义).

现在的一些公司:

 注释:虽然说代码的复用度高不会影响程序正常跑起来,但是这样的代码往往代表其质量不高,也是一个程序员水平的体现。

此时我们就可以让 Cat 和 Dod 分别继承 Animal 类, 来达到代码重用的效果.

此时, Animal 这样被继承的类, 我们称为 父类 , 基类 或 超类, 对于像 Cat 和 Bird 这样的类, 我们称为 子类, 派生类 和现实中的儿子继承父亲的财产类似, 子类也会继承父类的字段和方法, 以达到代码重用的效果.

2.2 引入继承

当类和类之间满足 —个类 is a 另外—个类 —定是存在继承关系,天然继承。

例如:

此时Dog,Cat,Bird 都应该是Animal的子类

当一个类继承了另一个类,另一个类中所有的属性和方法子类就天然具备了。
Javaz中使用 extends 表示类的继承

基本语法

class 子类 extends 父类 {

}

 这时我把 God类里面写的代码全部删除,但却让 God extends Animal 继承。

运行结果还是跟原来一样

 此时说明 God 是 Animal的子类,God天然继承 父类里面的内容。

规则:

1. 使用 extends 指定父类.

2. Java 中一个子类只能继承一个父类 (单继承)(而C++/Python等语言支持多继承).

3. 子类会继承父类的所有 public 的字段和方法.——显示继承

4. 对于父类的 private 的字段和方法, 子类中是无法访问的.——隐式继承

5. 子类的实例中, 也包含着父类的实例.

6. 可以使用 super 关键字得到父类实例的引用.

注释:

 
分析:

如果我现在又创建了一个 Taidi 类(泰迪),我们发现这个类既可以 继承 狗 这个类,同时也可以继承 动物 这个类。

 允许 多层继承。 也叫  继承"树"

满足继承关系的类之间一定是逻辑上垂直的关系
 

显示继承隐式继承 

我们之前继承的内容都是显示继承,就是可以直接使用的。

但问题在于什么是隐式继承?

比如我在 Animal 这个类一个写一个private封装的变量。

然后我们在Test测试里面使用这个私有的变量,发现报错了。 

 

 God类明明跟Animal类是继承关系,却不能使用里面的成员变量。

解释:其实God类里面已经继承了 age这个变量,那如何证明God类真的继承了age呢?

办法:不能直接去引用,要通过父类给我们的方法去 *** 作这个属性。

在Animal里面写

 在去测试里面试一下就发现可以了。

 

注释:God类里面什么都没有写,只是继承了Animal这个类,但却可以使用Animal里面的set和get方法去修改和显示 private修饰的 age。 

 这就说明age这个变量是真的已经被God这个子类继承了,只是区别在于不能直接使用。这样的继承关系,叫做 隐式继承

举个现实生活中的例子:

 你和你儿子之间,你儿子跟你肯定是继承关系。如他的血型,肤色,性格等等方面。但是虽然你儿子跟你是继承关系,但你儿子并没有全部继承你的所有。

比如你的钱。难道你儿子可以自由支配家里的财产支出吗?

钱就是被private修饰的私有属性,什么样的情况下你儿子可以花你的钱?

你允许的情况下(这其实就是get和set方法),你儿子无法自由的去调用这个属性,必须通过父类提供的方法来 *** 作这个属性。比如说你限制它每个月只能花1000元。

有句话这么说:我给你的才是你的

注释:而且为什么你可以拿你爸的钱话,甚至拿到房子,我却不行呢?就是因为你们是继承关系

提问:静态的属性和方法能直接被继承吗?

肯定是可以的。静态的属性和方法是归于某个类所有,当一个类继承了另一个类,肯定所有静态属性和方法都继承了。

但是到底能否正常的使用,还是要看权限

静态区别:就在于到底是通过子类对象使用还是直接通过类名称来使用
 

protected 关键字

刚才我们发现, 如果把字段设为 private, 子类不能访问. 但是设成 public, 又违背了我们 "封装" 的初衷. 两全其美的办法就是 protected 关键字.

protected:范围是  不同包下有继承关系的子类 和 同包下的其他类

对于类的调用者来说, protected 修饰的字段和方法是不能访问的

对于类的 子类同一个包的其他类 来说, protected 修饰的字段和方法是可以访问

区分 导入类 、 继承 、 关键字protected  之间的联系(不同包下

我们上面讲了用 import导入包中的类,这样我们就可以在别的类中,产生这个类的对象,再根据对象调用里面的属性,方法等等。

但是你会发现,只能是public修饰的方法和属性才能被我们的对象调用,因为public的范围是整个项目。 protected修饰的方法和属性就不能通过对象来调用。

当一个类使用 extends 继承另一个类的时候,是可以使用这个类里面的方法和属性。(也是需要import导入这个类,告诉编译器这个类在哪)

但是你会发现就不只能用 public修饰的了,用 protected修饰的方法和属性也是可以被我们继承的子类所调用的

 

 案例:

注释:protected只允许在同包和子类里面出现,但如果你在别的类里面,这个类跟Animal这个父类没有任何关系,这是你在这个类里面导入子类。因为子类是继承了Animal这个父类的,所以在这个子类里面可以出现 protected修饰的变量或者方法,但你在没有任何关系的类中new这个子类,任然是看不见 protected修饰的。

跟private差不多的意思,只在他允许的范围内可见,出了这个类的范围不管你是导入还是new对象都是不可见的。

 

 注释:四个修饰符

 他们之间的关系类似于我们高中学的集合,大于就一定是包含了。

 

 注释:我们创建的这些包实质就是电脑里面的那些文件夹,可见范围都是指同目录下的,包中的子包肯定是不可见的。

小结:

Java 中对于字段和方法共有四种访问权限

private: 类内部能访问, 类外部不能访问

默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问. protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.

public : 类内部和类的调用者都能访问

 注意事项:

什么时候下用哪一种呢?

我们希望类要尽量做到 "封装", 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.

因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限.

例如如果一个方法能用 private, 就尽量不要用 public.

另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public.

不过这种方式属于是对 访问权限的滥用, 还是更希望大家能写代码的时候认真思考, 该类提供的字段方法到底给 "谁" 使用(是类内部 自己用, 还是类的调用者使用, 还是子类使用).

super 关键字

复习下我们之前学的 this 关机键:

 而 super 关机键跟继承的父类有关:

实验案例:创建了三个包,World包、Chinese包、测试包

 

 Chinese是World的子类,继承关系

 然后我们在测试包中进行测试

我们只创建了 Chinese类的对象,按理说只会运行该类中的构造方法等 

 运行结果:

 发现:父类的构造方法也被执行了,而且执行顺序是高于子类的构造方法

当调用new chinese 无参构造产生子类对象之前,先默认调用父类的构造方法产生父类对象然后才会执行子类的构造方法.

这就是所谓的,没有你爸爸,哪来的你

思考题:

一个主类 B

一个子类 D

问题:它们的执行顺序是怎么样的? 

 运行结果:

 解析:执行主方法先加载主方法所在的主类

 1.加载类时就会加载里面的被静态修饰的,这里就是加载静态块到方法区中

2. 而D是继承了B的,要加载D就先要加载父类。然后才会去加载主类(子类)。

所以先执行的是

3. B的静态块 然后执行子类的静态块

6. D的静态块 类加载结束后,才进入主方法 .

注释:构造块 和 构造函数这些只有在产生对象的时候才会去执行,构造方法的目的在于给值 初始化。

7. main开始。   (构造块的执行顺序高于构造方法)

2 1 5 4  这是产生一个对象执行的顺序,new了两个D,所以执行两边。

2 1 5 4  注释:静态块从始至终只执行一遍,不管产生了多少次对象。

8. main结束   最后才是 8 结束、

super 关机键

现在在同包下创建三个类 person 是主类,其他两个是继承关系

 之前说了继承,那么在两个子类中都会有 public修饰的 name 属性,但不巧的是在它们各自的类中也有个 同名的 name属性。

提问:现在我们写一个fun方法去打印name属性,如果我在主方法里面调用这个方法,到底会打印哪个name值?

运行的结果:

 原因:还是程序的 就近原则

这就跟你在fun方法里面用 this一样,就算你不写,程序还是会默认帮你添上,

 在访问成员变量的时候,推荐大家还是主动加上 this关机键,尤其是在有继承关系的时候。

这样就会非常的明确,如果当前的子类中有name属性,我就会优先的调研当前类中的属性。

如果你此时把子类中的 name 属性注释掉,那么就会打印父类的 name属性

 当有继承关系时
this关键字默认先在当前类中寻找同名属性,若没找到,继续向上寻找父类中是否有同名属性
直接使用name。编译器默认都是this.name,所以说我吗写或不写都一样,但最好是写。

提问:如果我把父类的name修饰符变成 private,子类的name属性注释。然后用fun方法去找name,运行起来会怎么样?

回归正题:如果一切都正常的情况下,我就是想调用 父类的属性该怎么办?

这时候就是使用 super 关机键 访问,直接去寻找父类的同名属性。

 当然了,如果此时的父类name被private修饰,就算用super进行访问,还是找不到的,因为只在它修饰的当前类可见,出了就不可能看的见了。

此刻我们再创建一个类 Animal 类,让  person 类 继承它,而 Animal里面也存在一个同名属性name,那么此刻再让 Chinese 里面的 fun方法用 super访问的到底是直接父类,还是最高等级的父类呢?

他们之间的关系图:

 运行结果: 还是 人类,还是直接父类的name值。

 但如果此时直接父类不存在同名的name会怎么样?

运行结果:发现它就会开始继续向上一级的父类开始寻找是否有同名的。

 如果此时只有子类存在 name属性,多层上级的父类都没有name这个同名变量,然后用super去寻找会怎么样?

 如果此时都存在name,只是直接父类的修饰符变成了 private,那么会怎么样?还是用super关机键。

运行结果: 发现变成了 不可范访问的状态,并不会继续向上找。

因为在直接父类中 其实是存在 name的,只是我们的权限不够罢了。

 结论:

 

super 修饰方法 (构造方法 和 普通方法)

super 修饰 构造方法(重点)

我们在 Animal  、 Person 、Chinese 中都定义一个无参的构造方法。然后去运行。

 

发现:我们只是产生了子类 Chiness的对象,它的所有父类的构造方法都被调用了,而且还是等级最高的父类最先执行。 

结论:

当产生子类对象时,默认先产生父类对象
若父类对象还有基础,继续向上先产生祖类对象

另一种场景。现在只有 person 类 和 Chine 类 这两个类,它们是继承关系,但现在在person类中定义的是 有参构造 , 而它的子类是 无参构造

这时候就会报错!

原因:

1. 当person类中定义有参构造时,默认的无参构造就会消失。

2. 都说调用子类构造方法时,会先调用父类的构造方法。这其实是因为在子类的构造方法中默认第一行有一个 super( )  去调用无参的父类。此时父类的无参没了,变成了有参的,当然会报错。

 解决办法:显示使用super调用父类的有参构造。自己写一个有参的调用父类构造方法的super

 运行结果:

结论:当父类的构造方法只有 有参 时,在 new子类对象调用子类构造方法时就不能省略了。必须要第一行写一个super关键字显示调用下父类的有参构造。 

 我们之前学过调用当前类里别的构造方法用 this(),而且这玩意也是要求放在第一行的。

 会报错! 就算父类既有 无参 和 有参构造方法,只是它们两个同时出现就是会报错

结论:

在一个构造方法中无法显式使用this()和super()同时出现

解决办法:一个隐式,一个显示就可以。

因为在有继承关系的子类的构造方法中,首行会默认存在一个super()父类的无参调用。

这时在父类加上无参构造,那么在子类有参构造中不显式写super也就没事了,这时可以在第一行写this()无参的调用

这样既调用了父类构造方法还在当前类中调用了别的构造方法 

super 修饰普通方法

如果父类和子类都有相同名字的方法时:跟上面调用属性是一样的,都会遵循 最近匹配原则

但你想直接调用父类的方法时, super.方法名   就行了

细节提醒:super不能指代当前父类的对象引用

我们之前学 this时,this表示当前对象的引用,我们调用方法打印this时,this会根据是哪个对象里的方法调用了它,this就打印该对象的地址,但super却不行!

this没问题,super就不行。 

final 关键字

final也叫终结器(到此为止)

曾经我们学习过 final 关键字, 修饰一个变量或者字段的时候, 表示 常量 (不能修改).

final 关键字也能修饰类, 此时表示被修饰的类就不能被继承.

final 关键字的功能是 限制 类被继承

"限制" 这件事情意味着 "不灵活".

在编程中, 灵活往往不见得是一件好事. 灵活可能意味着更容易出错.

是用 final 修饰的类被继承的时候, 就会编译报错, 此时就可以提示我们这样的继承是有悖这个类设计的初衷的.

我们平时是用的 String 字符串类, 就是用 final 修饰的, 不能被继承

组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果.

例如表示一个学校:

public class Student { 
 ... 
} 
public class Teacher { 
 ... 
} 
public class School { 
 public Student[] students; 
 public Teacher[] teachers; 
} 

组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段. 这是我们设计类的一种常用方式之一

组合表示 has - a 语义

在刚才的例子中, 我们可以理解成一个学校中 "包含" 若干学生和教师.

继承表示 is - a 语义

在上面的 "动物和猫" 的例子中, 我们可以理解成一只猫也 "是" 一种动物.

大家要注意体会两种语义的区别

多态

什么是多态?

多态:一个引用可以表现出多种 行为 / 特性  ==>多态性

这是我们之前最常写的: 

类名称  类引用  =  new  该类对象();
而多态里面有个东西叫: 向上转型

之前我们写的是 god类型局部变量里面 保存 god类型产生的对象地址

现在我们写的是 Animal类型局部变量里面 保存 god类型产生的对象地址。

可以这么理解:狗是狗类,这没任何问题,那我现在说 狗 是动物类,这也是没问题的。

比如现实生活中:每个人都是自己专属的名字,别人叫这个名字你就知道他是在叫你。但并不是任何时候所以人都只会叫你的名字,有时候也会叫你的 外号, 小明、张三、李四等等之类的,当别人叫这个名字的时候,你也会知道他们是在叫你,虽然它们叫法有不同,但本质还是你自己

所以:我们把  父类名称 父类引用 = new 子类对象(); 这样的 *** 作叫做 向上转型

注释:向上转型不止是 只 new子类对象,这个父类向下的所有继承关系都可以,孙子类,曾孙类都是可以的。

 向上转型发生在有继承关系的类之间

 为啥叫 "向上转型"?(随便了解下就行)

在面向对象程序设计中, 针对一些复杂的场景(很多类, 很复杂的继承关系), 程序猿会画一种 UML 图的方式来表 示类之间的关系.

此时父类通常画在子类的上方. 所以我们就称为 "向上转型" , 表示往父类的方向转.

向上转型最大的意义就是 参数统一化,降低使用者的使用难度。

我们之前写方法都是不同的类写一个方法。现在想写一个方法,接受的参数是一个 类。比如说我写的方法想接受 Animal类就要写个该类的方法,我又写了一个 Dog类,就需要再写一个方法去接受Dog类型的,方法的重载,只是的参数的类型不同嘛,这是我们之前所学的。但如果现在有1000类呢?我们继续写一千个方法是不是很麻烦?有没有什么一劳永逸的办法?那就是向上转型

普通平常的写法:三个类,写三个不同接收参数的方法。

如果现在存在向上转型这个概念后,对使用者没有什么区别,对调用者可大有帮助。就变成了这样:

只要我们写的类之间是继承关系的话,那么就只需要写一个 最顶端 父类 的方法就行了,往里面传 子类的形参都是可以接收的。

 这时候就算我们再创建一个新的类,只要是继承关系,就可以直接用以前写的方法,往里面传参没事。

 向上转型其实就是为了方便扩展,方便使用吗,参数的统一化!!!

提问:现在在每个类中,父类,子类里面都写一个同名同样的 eat方法,然后通过主方法去调用,请问会输出哪个类里面的eat方法?

调用三次fun方法,里面传入的都是不同的类,每个类里面都有相同名字的eat方法。会打印什么?

运行结果:

 解析:

fun中animal局部变量的引用调用eat方法时,当传入不同的对象时,表现出来了不同的eat方法行为称为:多态性
 

看起来都相同,结果却不同。 

 

 这个现象的本质,就是 方法重写

方法重写

对比区别: 

方法重载(overload):发生在同一个类中,定义了若千个方法名称相同,参数列表不同的一组方法。
 

方法重写(override):发生在有继承关系的类之间,子类定义了和父类除了权限不同其他全都相同的方法,这样的一组方法称之为方法重写
 

 这种情况就叫做 重写方法

那我们调用的时候,到底调用的是谁呢?

规则:

 

运行结果: 

 提问:如果此时子类都并没有重写这个eat方法,只有主类有,他们去方法里面调用时会怎么样呢?

 如果子类没有重写eat方法,则向上搜索碰到第一个父类重写的eat就调用最"接近"的eat方法

一层层向上找~  都是遵循 : 就近匹配规则,碰到最接近的调用

刚才说了,重写的定义就只有权限的不同,这里就有一个要求:

子类权限必须  =》 父类权限 才可以重写

private除外,是特例。

举例:主类重写方法用 protected  修饰

 子类:

 

上面两个 大于等于权限的情况下到没事。 

default权限:小于的 protected 就会报错!无法覆写

但是,这里有一个特殊案例,就是当主类的修饰符是 private 的时候,就算你的子类权限比它大,但还是会报错。因为privatr的可见范围太小了,出了自己这个类,你连见都见不到,在外界都不知道尤其的存在,这又怎么可能存在 方法的 重写呢~~~

如果主类的重写方法是用 private 修饰符修饰的,就算你的子类是public还是会报错

我们现在把主方法就放在 Animal这个主类里面,因为Animal的重写是private修饰的,对外不可见,看不见我们理想的效果。

这种情况下运行的结果会是什么?

 我们之前说了,想看方法重写的结果,只看它 new的对象是谁就行了,根据对象的不同就会打印不同类中的eat的重写方法,但现在Animal里面的重写方法变成了私有方法,那么对结果会有影响吗?

这是情况之前 子类重写权限 =》 父类重写权限,且不包括 private的情况打印结果:

但现在的主类是private修饰,为了看到结果,把主方法放在了Animal这个主类里面。 

运行结果:根本就不会再去找各自类里面的重写方法了。

 因为出了主类根本就找不到这个eat方法了,主类都访问不到了,还谈什么重写,就是普普通通的方法了。

 

小技巧:判断自己写的重写权限是否合理: @Override

使用场景:比如我需要判断我子类重写的方法权限给的是否合理的时候,就再这个方法的头上一行写: @Override  会帮我们自动校验

它会在运行前就告诉你是否错了

 private私有权限也可以这么理解:他就像你的父亲藏想私房钱,你想拿出来花花?那是绝对不可能的。

提问:我们写的重写方法能否被 static修饰呢?

答案肯定是不能的!

现在回想一个多态的本质是什么?

多态的本质就是因为调用了不同的子类“对象”,根据这些不同子类对象所属的类覆写相应的方法,才能表现出不同的行为。

而被static修饰过的静态方法改变就不需要对象就能直接调用了,这不是跟多态的定义起了冲突嘛。

 

  

向上转型发生的时机:

直接赋值

方法传参(这个是使用场景最多见的)

方法返回

直接赋值:

 方法传参:

思考题: 现在有两个类 B 和 D为继承关系

运行结果是什么?

你的选择是哪个? 

 

答案:D

解析: 主方法在D里面,所以加载D这个类,执行里面的静态方法,要执行D类要先执行它的父类,但这个类里面什么都没有写,就什么都没打印。

注释:构造块和构造方法这些是在产生对象的时候就会调用的,几个对象就调用几次。

然后就进入了主方法里面,执行 D d = new D();产生了一个对象,就会去执行这个D类里面的无参构造方法,但执行子类之前还是要先执行父类才行。

执行了父类的无参构造方法,里面有个 fun(); 那就去调用这个方法,但编译器突然发现这个方法居然在父类和子类里面都出现了,是个重写方法。那重新方法到底要执行哪个fun呢?

规则:使用父类引用调用普通方法时,若子类重写了该方法,则调用该对象所在子类覆写后的方法

看是哪个对象调用它的,如果里面没有该重写方法就一层层的像上找,此时发现刚好子类里面就有,所以就去执行了子类里面的fun。

注释:这时执行的是父类里面无参构造,所以子类里面成员变量都还是默认值。

一个卡在super()这行呢~

所以最后执行了 

 

向下转型

我们上面说了向上转型就是:使用父类引用调用普通方法时,若子类重写了该方法,则调用该对象所在子类覆写后的方法

那么假设此时  Dog类(子类)中有一个扩展方法play()

这个方法Animal类中不具备,此时还能通过 animal.play()?

运行结果:


 

解析:

 我们都是这么定义的:

 平常我们都是怎么调用对象的呢?

  引用名称.方法名称();

 关键在 .方法名称

能通过 “  .   ” 访问的方法 谁说了算?:  类名称 说了算

能访问的这些方法必须在 ‘ 最前面的 ’ 类中定义过。

编译器会先去类中查找是否包含指定的方法。

至于这个方法到底表现出来是哪个类的样子,实例所在的方法说了算。

总的关系图:

 举例: 最开始到底能不能进行调用看还得看最前面的 Animal

 在能调用eat方法的的基础上,那么到底是 “啥样子”的eat,现在就要看 new的实例是通过哪个子类new的,看该子类中是否重写了 eat方法。

总的关系图:

 小总结:

到底能 . 哪些方法 前面 说了算,到底 . 之后这方法 长啥样 ,后面new的说了算

回到刚才的问题:如果此时就偏偏就是想强行使用play这个方法,该怎么办呢?

 其实animal这个引用是披着“动物皮”的狗,本质上是个dog,批了个Animal的外衣,所以此时才只能调用Animal里面定义的方法。

那么此时我想调用子类拓展的方法该咋办?

就是脱掉这层外衣,还原为子类引用。 ——》 这个举动就是 向下转型

格式:

子类名称 子类引用 = ( 子类名称 ) 父类引用

 变成为:

注释: 大转换小,是天然的,自动的。小转换大,需要强制类型转换。

 

具体实例:

 如果想把强制类型转换的对象变为原来:直接赋值就好了.

 

提问:这五行代码,一共产生了多少个对象?

 答案:很简单,对象的个数,看 new了几次。从始至终都只有一个。

只是它一会叫 animal一会叫 dog,一会叫 animal罢了。来回变的只是引用的名字。

 

现实生活的案例:

比如说现在这么写:

 假设现在 动物这个范围里面包括 人类。那么Animal此时就是父类。new就是它的子类。

一个披着 动物 外皮的 张三。

现在我们只是把 张三 当做动物类看待,所以此刻的张三具备的行为是Animal类里面定义好的方法。如:吃、睡。

 写代码这就行为(方法)不应该属性 Animal 这个类所具备的能力。应该是人类中的程序员子类所独有的技能。

虽然说张三这个对象是实实在在存在的个体。主要看你把我当做哪个类来看待。

但如果说你现在把张三中当做动物来看待,那么它就只会吃饭睡觉,但如果你把张三当做程序员来看待,那么他就会写代码。

假设此时我们想让张三写代码了,就需要帮助张三脱下动物之间皮衣,还原为程序员类的引用。

注释:我们来来回回变动是都只是张三这一个对象,这个对象的本质一直是张三这个类。

           区别在于你想把他当做什么类来看待。(是动物,还是程序员,还是什么别的取决于你的 *** 作)

现在来看另一个特点:

这是没有发生向上转型的,正常的引用。类名称和类实例一致。

 但此时我想把 animal 这个引用 用我们刚刚学的向下转型强制转换为 Dog类,这样可以吗?

 

运行结果:直接就报错了。

 原因:

1. 要发生向下转型,首先要发生向上转型。
2. 毫无关系的两个类之间没法强转(int boolean)

 之前我们的 animal 引用虽然是 Animal这个类型的引用,但却是通过 Dog类new出来的。所以才叫披着 动物皮的 狗。本质还是一个 God类的对象。我们的强制类型转换只是帮它“洗心革面,脱下外衣”的过程。而上面这个animal这个引用从始至终就是 动物类,跟 狗这个类没有产生任何的联系。(西瓜就算再怎么强制转换也变不成人人喜爱的 黄金,两者没有丝毫的继承关系)

 可以把向上转型理解为一个本质为人的对象,穿上了各种各样的衣服。

 可以把向下转型理解为一个本质为人的对象,他穿上了各种衣服而忘记了自己的本质是什么,这时候就需要我们帮他脱下他的衣服,露出他本来的面貌。

但注意了,你再怎么脱衣服也不会把一个本质为 人 的对象 说成是 石头、树、二氧化碳这些八竿子打不着的东西。

注释:向下转型会存在着一定的风险,类型转换异常。

使用 instanceof 关键字。 

instanceof

使用方法:

第一个打印语句的意思是: animal1 这个类的本质是 Dog:

然后返回是 false,说明这句话是错的。 

第二个打印语句的意思是: animal2 这个类的本质是 Dog:

然后返回是 true,说明这句话是对的。 

 运行结果:

 使用 instanceof 关键字的场景:只在是这个类的时候进行转换,不是就发出警告。

 发生向下转型的情况,举个例子。下班时的你就是一个程序员披了件动物的外皮,只会吃吃,喝喝。但每当上班的时候,你需要巧代码时,这时你就需要使用子类拓展的方法,也就是你原本的技能,编程。用向下转型还原子类引用。

 

复习题:方法重写。

下面这代码运行的结果是什么?

 答案:1.Person的test方法

我们创建的是一个子类对象,看他new的是子类 new Student().fun(); 说明是先从子类里面找 fun方法,可是发现子类里面并没有存在,所以就往上去父类找,在父类找到了就开始执行。

this.test(); 此时的this表示当前的父类引用。然而test方法是私有方法,子类根本就不知道其存在,也就无法进行覆写。构不成重写那也就是个普通的方法,所以调用的是父类里面自己的test方法。这两个test现在只能算是平常写的普通方法,没什么特殊的了。

 那怎么判断自己写的方法是否为重写方法呢?

Idea很智能,有提示。现在先把父类里面的私有属性改成 protected

这里idea左边就又有箭头图片显示。这样子就算的重写方法了。

是重写方法后再运行代码结果:变成了子类里面覆写的方法了。

  注释:第一个箭头向下表示:被覆写了。第二个箭头向上表示覆写了父类的某某方法。

如果现在把下面的 new子类对象,变成new 父类对象,在权限正常的情况下对结果有影响吗?

发现:他又变回了指向 父类里面的方法了。因为之前说过,重写发生在继续关系里面。我现在做的是new父类多谢,正常调用父类里面的方法,this就表示的是当前这个类里面的引用。之前new子类对象他是有爸爸的人,也就是有继承关系。我们现在new的父类没有爸爸,就这个意思。

补充知识:学了向上转型之后,我们之前说方法重写的返回值必须完全相同其实不严谨。

应该是:返回值完全相同或者至少是向上转型类返回值

毫无关系的两种类型不能作为方法重写的返回值。

返回值不一样的情况下:

比如说:我们把父类的返回类型换成 int,此时把子类的返回值换成 boolean、byte、double、char等等都不可以,都是会报错的,因为它们之间根本就不存在向上转型的这个概念。


 

但是如何换成有向上转型的类型:这样虽然它们返回值不一样,但却还是可以。

 注释:父类的返回类型在向上转型中必须是 大于等于 子类类型的。

如:我把这两个重写方法的返回类型换一下。

此时是个向下转型,不可以,不算方法重写。我们可以的前提是向上转型才允许。

一个的 小可以向上转成 大, 但不能是把 大 变成 小。

比如说:学生肯定是人,但人却不一定指的是学生。


浅浅先回顾下上面写的知识点:

以后大多数都是使用的是向上转型:

 一个子类继承了父类,直接使用父类里面的属性是完全没问题的,但如果此时是在子类里面 new了父类对象,再根据这个对象去调用父类里面的属性就不行了。

这都是正常的调用,没问题。

 运行结果:

 但如果此时就算是在子类的内容,new一个父类对象去引用父类里面 protected修饰的属性,却还是不可以。

protected修饰的属性,只出现在同包和继承子类中,虽然我们此时是在子类里面new的对象,但编译器还是会认为这个不在protected的可见范围之内,所以引用会报错

我们创建的 base 对象算的父类的引用,但就是不允许 通过父类引用使用,在父类外部的情况下。

但是,我们new子类对象就可以正常引用了。(有点绕,这个概念情况知道就行了

这些案例其实没有什么实际意义,只是说确实存在这么个现象,了解就行,正常人谁会这么写代码

 正常情况下还是使用我们之前学的 直接引用、tihs、super这些关机键。(知道这句话就够了)

    用多态特性打印图形:

 


抽象类

这是多态遇到的麻烦:

 解决办法:

若需要强制要求子类覆写方法,用到抽象类


其实现实生活中有很多的抽象类,这些类都是概念化的,没法具体到某个实例,描述这一类对象共同的属性和行为
 

比如我跟别人说:我有个朋友是 人类!人类是什么鬼?谁知道你是男是女,是那里人。

比如说,我让你给你画个 形状 !什么形状?是三角形、正方形、圆形还是什么。

这些描述的特别抽象概念,就是抽象类。

 抽象类概念:

 抽象类是普通类的"超集",只是比普通类多了一些抽象方法而已

 个数是:[0.….N] 可以是0个也可以是 N个。

 抽象方法所在的类必须是抽象类,子类若继承了抽象类,必须覆写所有抽象方法(子类是普通类)

Java中定义抽象类或者抽象方法使用  abstract  关键字

1.抽象方法所在的类必须使用abstract声明为抽象类

抽象方法指的是使用abstract关键字声明,只有函数声明,没有函数实现的方法,称为抽象方法。

注释:抽象方法是只有方法申明,没有 方法体(大括号)

抽象方法没有方法体(没有 { }, 不能执行具体 代码)

格式:

abstract class Shape {

        ......................

        abstract public void draw();

}

注释:对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类

提问:下面这句话对吗,或者是错哪了?

 答案:肯定是错的。举例。

 如: native

若一个类使用abstract声明为抽象类,则无法直接通过该类实例化对象,哪怕该类中一个抽象方法都没有。当一个类是抽象类,不管他有没有抽象方法,这个类本身就是一个抽象的概念,没法具体到某个特定的实例。

什么叫实例化对象?

如: Shape shp = new shape(); 

抽象类不能自己实例化对象,只能通过子类向上转型变为抽象父类的引用

 总的来说:就是不能 new 抽象类的对象,但可以正常的new继承抽象类的普通类。

                   抽象类不能产生对象,普通类可以产生对象。

 

 3.

       子类继承了抽象类,就必须强制子类覆写抽象类中的所有抽象方法(子类是普通类),也满足单继承局限,一个子类只能 extends      —个抽象类。

模拟场景:现在有三个类A、B、C。A的抽象类主类,B也是抽象类,但继承了A。

C只是一个普通的类,继承B。

下面C类中需要写几个抽象类。

两个。

概念一:如果一个抽象类的子类也是抽象类,那么可以选择性的覆写父类的抽象方法。

上图中的B应该要覆写A类的抽象方法的,但它选择了不写,可这并不代表不需要写了,这相似一个债务关系,只会累积。(包括父类继承来的方法,如果父类没写的话,也需要帮它覆写)

所以现在C类需要覆写父类及以上级别使用定义了抽象方法,没有被覆写过的抽象方法。

正确的结果:

 注释:可以理解为 “父债子偿”,出来混,早晚要还的。

如果B类里面覆写了A类里面的抽象方法,那么C类里面就可以不用写了

4. 抽象类也可以存着 构造方法。

下面代码运行的结果是什么? 

 

解析:虽然是抽象类,但规则还是跟之前学类是一样。

我们new的虽然是 Fun,但这个类继承了BaseTest父类,所以调用子类的构造方法先调用父类的。然后父类构造里面的this调用print,因为这个类是被覆写的,所以调用的是子类里面的方法。

这些 *** 作是在子类构造方法赋值之前完成的,所以num还是 0;

答案:

 

 

 再次强调:

 

所以现在引入接口的概念。 

接口

我们说抽象类是:

抽象类虽然没法直接实例化对象,子类仍然满足is a原则,子类和抽象父类之间仍然是满足"继承树"的关系。(可以理解为是一种垂直关系) 

而我们说的接口,可以理解为是一种 水平关系

一般来说,接口的使用表示两种场景

1.接口表示具备某种能力/行为。子类实现接口时不是is a,而是具备这种行为或者能力


"游泳"是一种 能力或者行为,Person满足游泳接口,Dog也能满足游泳接口,Duck也能满足游泳接口。(水平关系

接口表示一种规范或者标准。"USB接口",5G标准

接口中只有全局常量和抽象方法-→>更加纯粹的抽象概念。

其他东西统统没有(构造方法,普通方法都没有)

接口使用关键字 interface 声明接口,子类使用  implements  实现接口
 

注释:接口中只有全局常量和抽象方法这是JDK8之前,之后的JDK接口里面也有普通方法。

 

创建一个接口:平常创建java源文件时,我们选择的是calss,现在选择第二个:Interface 

 接口使用 interface 关键字定义,只有全局常量(1%接口)和抽象方法(99%全都是方法)

 USB接口表示一种规范。

子类使用 implements 实现接口,必须覆写所有的抽象方法

子类的实现:

 再写一个子类

 鼠标,键盘外设都属于USB接口的子类。

那么问题来了,电脑这个类算不算USB接口的子类。

所有带USB线插入到电脑的设备都应该满足USB规范。

电脑叫做USB规范的使用者!! ! !

电脑算一个USB的一个载体。

那么此时就创建一个电脑类:

 为何此时的方法的参数用的是USB接口的引用?

答:很简单,多态特性。

对于电脑的使用者生产者来说,我根本不关心到底哪个具体设备插入到我的电脑上,只要这个设备满足了USB接口,都能被电脑识别。

这不就跟我们上面说的向上转型一样嘛,不同的对象传入返回不同的行为。

 如果此时我们把fun里面的USB类型换成某个具体的实例,

如:fun(Mouse mouse)那么它此时这个插口就只能插鼠标,键盘耳机别的都无法识别,因为对Mouse来说他们是两个毫无关系的类。(鼠标和键盘是一种水平关系,不是垂直关系。)

现实生活中,如果没有接口的规范化,那么一个对应的外设就需要一个接口,电脑岂不是要N多个接口才行。但现在有了接口,只要你的设备满足我的规范要求,都可以被我使用,这就是多态的美丽。(参数统一化,兼容

接口测试:

注释:假设现在多了个子类Camera,对于电脑这个类来说,一点影响都没有,多一行代码都不会写。

 开闭原则,所有设计模式的核心思想:程序应当对扩展开放,对修改关闭。
 方便扩展,不能影响已经写好的程序

接口表示能力

接口允许多实现,一个类可能具备多个能力,同时实现多个父接口,若实现多个父接口,子类普通类,需要覆写所有的抽象方法

注释:继承是只能单继承,而接口却允许多个父接口。

现在写三个接口:

 现在来看实效子类:

假设老鼠只具备跑的能力,不会游泳,不会飞。

现在来复习下快速修复这个快捷键,光标放在有问题的代码里,Ait + 回车

选择你要覆写的方法。        

就自动覆写好了。 

 

 可以在里面写一句话,表示等会我真的调用了这个子类里面的方法。

 现在再假设God类它会跑和会游泳。

用逗号来分隔:IRur和ISWim都是自己写的两个接口。一个是跑功能,一个是游泳功能。

 说明这个Dog类具备了两个接口的功能。表示子类实现多个父接口

现在需要把这个子类所用的父类方法都要覆写。

 在假设一个动物具备三种接口能力的。比如说:鸭子

现在写个Test测试一下刚刚写的。 

注释:刚刚说过,抽象类是不能直接实例化对象的,需要向上转型。那么比抽象类更加纯粹的接口,更是不可以的。

 这里的狗类和鸭子都不止继承了一个接口,所以也可以new。

 

 由于接口中只有全局常量和抽象方法,因此接口中

 

 在接口声明中,这些关键字都不用写,只要保留最核心的方法返回值,方法参数列表,名称列表即可。

注释:只要接口里面才能省略,别的地方不行。

接口知识点小总结:

 

接口第三特点:接口和类之间的关系。

接口和接口之间也存在继承关系。接口坚决不能继承—个类。

此时IC接口同时继承多个父接口,继承了所有的抽象方法子类在实现IC接口时,必须覆写所有的抽象方法

如果一个类需要继承—个类,同时实现多个接口时,该怎么写?

先使用extends继承—个类,而后使用implements实现多个父接口。

 这时这里需要把接口里面定义的方法覆写一遍。

关于接口的命名规范:

为了区分接口和类,命名接口使用 l 开头,如:IRun,ISwim
 

子类实现一个接口时,命名以相应的接口开头,以impl结尾


eg:如果是IRun的子类,就这么写:RunImpl(不是强制要求,尽量能做到以此命名)

如果子类实现多个父接口,不需要使用此规范来命名。如我们之前写的God类就不用。

 注释:如果下次看到别人写的类名是 I 开头, 或者是以 impl结尾的,大概率就是接口类了。能做到看懂就行了。


拓展知识点:

java万物之母——Object类

全名称:包名.类名 :java.lang.Object

1.  Object类是Java中所有类的默认父类,无须使用extends来定义。

    只要是class声明的类都有一个父类,那就是一个共同的父类——Object类。

这么定义的好处:因为Object类是所有类的父类,使用Object引用来接收所有的类型参数最高统一化


 

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/871806.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-13
下一篇 2022-05-13

发表评论

登录后才能评论

评论列表(0条)

保存