三.升级打怪

1.面向对象

面向对象是事物的特点与事物的功能的集合,在java中用类来表示面向对象
对于这个类进行实例化出来的东西就叫做对象。
那么,类与对象到底有什么区别?
类是对某一类事物的抽象描述,而对象用于表示现实中该类事物的个体
比如我定义了一个汽车类,那么特斯拉和宝马是不是这个汽车类实例化出来的对象?
一个完整的类一般是静态属性,构造方法,动态特征三部分组成
初次遇见JAVA(二) - 图1
静态属性:类所拥有的属性,用变量的形式展现
构造方法:调用类的时候需要经历的初始化方法,类似于Python的init初始化方法
动态特征:类所拥有的特征,用方法的形式展现
其中重点介绍一下构造方法,只用public修饰,方法名与类名字一致,它的作用是:
每次实例化这个类成为一个新的对象的时候,需要执行构造方法里面的功能。
初次遇见JAVA(二) - 图2
利用前面我们所学的方法重载,我们能够很好地实现两种实例化对象的方式。
值得一提的是,一个严格规范的类中的属性,应该都被private修饰成私有变量
这样实例化的对象不能直接访问属性,而是通过方法去取值和赋值
初次遇见JAVA(二) - 图3

2.封装,继承

我们知道,面向对象有三大特征:封装,继承,多态
封装我应该不需要多言,就是将一大堆方法封装起来放到类里面,成为类的动态特征
现在我们来重点讲一下继承,因为继承可以拓展出两个重要的概念:抽象类与接口

心急吃不了热豆腐,我先放出继承重要的关键字。
继承的使用方法:子类 extends 父类,extends为继承的关键字。
继承的实际应用就是我创建了个员工类,这样就够了吗?显然还需要分成研发部员工和运维部员工
但是这两个类又要有员工类的属性,于是继承员工类,这就是继承的作用。
继承的好处是提高了代码的复用性,提高开发效率。
先来看看一个父类:
初次遇见JAVA(二) - 图4
子类:
初次遇见JAVA(二) - 图5
这个类继承了父类Employee里面的company属性,所以可以直接使用company变量
当然了,还继承了父类的work()方法,也是可以直接使用的
初次遇见JAVA(二) - 图6
实例化子类成一个对象,这个对象直接使用父类的属性与方法,从输出结果看没问题
初次遇见JAVA(二) - 图7
你已经弄懂了继承是怎么样的一回事,现在我要告诉你一些注意事项
①一个子类只能继承一个父类,但是不同的子类可以继承一个父类
②多层继承是允许的,比如C继承B继承A,这样的效果就是C是B的子类,也是A的子类
③父类和子类是相对的,一个类可以是别人的子类,同时 也可以是另外一个人的父类
④父类和子类如果出现同名的成员变量是允许的,但是子类对象使用的时候以子类成员变量为准
⑤根据第4条的规则有感而发,如果要同时使用父类子类的同名变量,应该怎么办呢?
⑥第5条规则的答案是: 父类使用super,子类使用this,就可以区别开来

3.方法重写

我们在上面学习了继承的概念,知道了子类继承父类的详细过程,还知道了同名变量的特性
那么变量也可以同名,方法也可以重名啊,这个过程叫做方法重写
有人就提出问题了,和前面的方法重载有区别吗?

我就直接放干货了
①方法重载一般发生在一个类中,对于同名方法参数类型和个数不相同的情况
②方法重写一般存在于继承的时候,子类需要修改或覆盖父类的一些方法
所以,方法重写发生于继承的时候,父类和子类存在同名方法的情况。
但是,方法重写有两个硬核要求:
①设置的方法名和方法参数必须一模一样,不然就无法启动方法重写
②启动方法重写的时候,子类的同名方法访问权限必须要大于等于父类的同名方法
第2条是什么意思呢,权限就是修饰符的使用
父类方法public void print() {},子类也必须是public void print() {},少了public就报错
知道了方法重写,可能还不知道实际的应用是什么:
比如一个手机父类,定义的是4G属性,但是旗下的某个手机属性却是5G属性,这个时候就需要同名方法进行方法重写,让5G给覆盖掉父类的4G属性,当然了,如果想保留父类的4G属性,加一句super修饰的方法名就兼容了4G与5G。

现在到了最关键的时候,方法重写有个特殊的地方,就是构造方法
构造方法是个特殊的方法,请问方法重写会对父类和子类的构造方法生效吗?
废话,这两个构造方法肯定不重名啊,方法重写无法生效的
但是我们在调用子类对象的时候,会产生一个这样的过程:
实例化子类对象的时候,会先调用父类的构造方法,再调用子类的构造方法
所以如果构造方法有参数设置,父类子类一定要统一,如果不是,请使用super去接收。

4.抽象类

我说过,继承有个重要的应用就是抽象类。
在面对两个或两个以上类似数据属性类似方法的类的时候,可以取一个类来概括这一类相似的类
这个类,就叫做抽象类,抽象类所使用的修饰符是abstract
先来一张图,感受一下抽象类的整体构成:
初次遇见JAVA(二) - 图8
首先,类名的class需要abstract修饰,然后可以给抽象类定义一些静态属性给子类使用
抽象类里面不定义private static final等等修饰符,只有最纯粹的变量和最纯粹的方法
如果有特殊要求,请对子类进行修饰处理
然后规定子类所使用的方法就叫做抽象方法,也用abstract修饰,子类必须进行方法重写
至于非抽象方法,是个特殊的方法,专门给每个子类对象调用。
所以,我们可以总结出来通过抽象类继承的子类具有以下重要的特征:
①拥有抽象类的属性
②必须方法重写抽象类里的抽象方法
③能调用非抽象方法

可能有人读到这儿还不明白,抽象类到底有啥用?请结合上面三个重要特征读我的故事:
一个公司,肯定不止研发部员工,运维部员工,还有营销部员工,后勤部员工啊
这些员工都有名字,年龄等属性,又都有上班下班共同的特征,难道我们一个个创建类吗?
那样是不是太麻烦了?还不如将这些共同的属性和共同的方法弄到一个抽象类中,
这样一来,通过抽象类继承的这些子类,强制有了名字,年龄等属性和上班,下班等方法
不存在特殊的子类没有继承到上班方法,这就是抽象类的作用。

现在来看看抽象类的一个超级案例,复习上面的private,super,方法重写,继承等知识点
我会定义三层继承关系:Staff(员工类) -> Develoption(开发员工类) -> Python(Python开发员工类)
层层传递,来验证一下继承的思维
Staff.java
初次遇见JAVA(二) - 图9
Develoption.java
初次遇见JAVA(二) - 图10
Python.java
初次遇见JAVA(二) - 图11
这样一来,我们肯定是创建Python类的对象,先用方法赋值id和name,最后调用work方法
这个work方法就包含了super导航到上一层的work方法,一起使用
初次遇见JAVA(二) - 图12

5.接口

接口实际上是一种特殊的继承方法,可以说,它并不是继承,但是原理是类似的
有时候,我们已经定义好了类与对象,突然有一天,一大批类拥有了一种功能
难道我们需要每个类去添加上这个功能吗?那样是不是太麻烦了?
于是有一种不添加方法代码的方法诞生了,也就是接口。接口是什么?

接口就是功能的集合,比抽象类更为抽象的类,只描述所应具备的方法,并没有具体实现
接口的定义不用class,只用interface修饰,相当于public static final修饰
接口里面的方法用void修饰就可以了,相当于public abstract修饰
变量用普通的定义就行了,哦不,接口并没有变量,里面全都是public static final修饰的常量
初次遇见JAVA(二) - 图13
接口该怎么使用呢?针对于我在上面讲到的案例,定义好接口之后
这些需要被添加的功能的类,只需要使用implements引用接口就行了
然后就是方法重写,将接口里面的方法进行具体化。
所以,抽象类和接口到底有什么区别呢?可以来看看下图的故事:
初次遇见JAVA(二) - 图14
这说明,抽象类和接口也是可以同时使用的,在这儿,接口像是附加功能一样。
抽象类与接口的终极总结:
初次遇见JAVA(二) - 图15

6.权限修饰符

在讲权限修饰符之前我需要讲一下跨包的数据通信
假设我们有demo01包和demo02包,分别有Java1类和Java2类,这两个包里面的类怎么互相通信呢?
第一个方法就是在Java2中import进去demo01.Java1,就可以new Java1了
第二个方法就是直接demo01.Java1 j = new demo01.Java();使用。
所以要么就import导入或者完整路径new。
现在我们可以讲讲四个权限:public,protected,default,private
其中default就是无修饰符的意思,其他三个均为修饰符。
我们分别设置四个权限情况如下:
初次遇见JAVA(二) - 图16
我们会假设四种情况,分析这四种权限修饰符下的变量是否能访问
表格如下:

通信情况 public protected default private
同一个类 能访问 能访问 能访问 能访问
同一个包不同类 能访问 能访问 能访问 不能访问
不同包不同类,但父子类 能访问 能访问 不能访问 不能访问
不同包不同类,无关系 能访问 不能访问 不能访问 不能访问

这个表格很好理解,就拿第三个例子来说,不同的包不同的文件,只有设置成父子类的关系
四种权限修饰符修饰的变量,只有public和protected修饰的变量才能在子类访问
初次遇见JAVA(二) - 图17
记住这个表格就万事大吉,记住开头和结尾两端:
任何情况下,public修饰的变量都能被访问!
只要是private修饰的变量,不能在不同的文件访问!

7.多态

现在我们讲面向对象三大特征最后一个特征:多态
多态体现在子类对象的多态性,什么意思呢?
就是我们会通过父类类型的变量指向子类对象:
设置Father类和Son类,存在继承关系,Son是子类
多态的表现就是 Father p = new Son();
为什么要这么写呢?我要举例一个粗暴的例子了

假如一个电脑可以插入很多设备,比如鼠标类,键盘类,网线类等等
如何在一个方法里面随意传入这些类的参数呢?
难道我要为鼠标类,键盘类,网线类等等分别写一个函数来调用吗?这是不是太麻烦了?
所以我们需要使用多态性完成这个步骤。
这些类都属于USB接口插入使用,所以我们定义一个USB接口
初次遇见JAVA(二) - 图18
然后我就通过这些接口发展成子类:鼠标类,键盘类,网线类等等
然后我们在笔记本类中写一个方法,这个方法可以任意接入这些子类:
初次遇见JAVA(二) - 图19
假设我是插入鼠标类,这个函数的参数不就等于:USB u = new Mouse();
这个不就是上面的多态例子吗?
如果是键盘类呢?不就是USB u = new Keyboard(); ?

多态是不是很简单啊,一个父类变量对应多种子类对象
问题就来了,下面的u.open()怎么办呢?接口只是抽象方法啊
这个时候我就需要介绍老概念了,方法重写
当Father p = new Son();发生的时候,如果两个类都有同名的方法的时候,p去调用子类的方法
其实我在前面也说过,当定义抽象类和接口的子类的时候,子类是必须要重写同名方法
所以并不用担心其他的情况出现。
所以上面的u.open()已经在Mouse子类里面被方法重写了
初次遇见JAVA(二) - 图20
所以传入鼠标类,实现多态的过程就是:
初次遇见JAVA(二) - 图21
最终的结果就是
初次遇见JAVA(二) - 图22
所以,多态在JAVA中是非常重要的一个概念,要知道,如果要处理一大类相似的参数,直接使用多态
直接设置这些相似类的父类变量,作为方法的函数,然后这些子类进去当参数并方法重写就完事了。

多态性还有一个终极应用:快速创建子类对象
比如我定义一个交通工具抽类:MotoVehiel
这个抽象类我会发展出两个子类,汽车Car和客车Bus,然后完成相应的方法重写
这个时候,我该怎么快速创建4个详细汽车对象和4个详细客车对象呢?
当然是无脑多态性!
初次遇见JAVA(二) - 图23
以上的过程实际上就是MotoVehiel moto = new Car/Bus();

8.关键字

关键字无非是分为final 和 static
final应该很好解释,就是修饰一个变量变成常量。
有一点需要注意的是,final修饰变量的时候,必须完成赋值,不能只声明:
final int money = 200; // 正确姿势
final int money; // 直接报错,不允许只声明,必须赋值。
当然了,final可不是只能修饰变量,它还可以修饰方法甚至类,我就概括成两点吧
①final修饰方法的时候,不允许被方法重写
②final修饰类的时候,不允许被继承。

static我就想也要好好讲一遍了,因为在前面也接触了static,但不明所以
我要说一下,如果在一个类里面使用static修饰了变量和方法
那么它们将会脱离出这个类,成为单独的存在,调用的时候实例化对象无法访问
只能通过类直接访问
初次遇见JAVA(二) - 图24
但实际上,如果一个类里面,虽然说static修饰的都单独出去了
但是static修饰的变量和方法都可以互相通信
初次遇见JAVA(二) - 图25
如果非静态变量进入静态方法,就会报错,只允许静态变量进去。

总结下来的就是三点:
①final关键字可以修饰变量,方法,类等等,会限制重写,继承等行为
②static修饰的东西成静态以后,和实例化对象无关,只经过类直接调用
③final与static一起使用,就会成为只能通过类访问的静态常量或方法

9.内部类

内部类是什么?就是一个类写在类的内部,类中有类
内部类分成三个部分:成员内部类/局部内部类/匿名内部类

①成员内部类
常规的类中有类。
成员内部类有个重要的特性,里面的类可以访问到外面的成员变量
初次遇见JAVA(二) - 图26
调用的时候应该怎么做呢?
初次遇见JAVA(二) - 图27
直接类里访问类,进行创建对象访问show方法

②局部内部类
就是一个类里面的一个方法,在里面定义类,也就是类的一个方法中含有类
这种局部内部类的使用特性就是需要经过自调用放在方法里面
初次遇见JAVA(二) - 图28
调用的时候,就很简单,因为这个里面的类的方法,已经在外面自调用了
所以我们直接使用外部类的对象就可以了
初次遇见JAVA(二) - 图29

③匿名内部类
最常用的内部类,属于局部内部类的特使情况,最节省内存的写法
使用的特性就是不创建指定类的子类对象,而是直接调用指定类进行方法重写
初次遇见JAVA(二) - 图30
这个可能不好理解,我们来看看People,实际上是一个抽象类而已
初次遇见JAVA(二) - 图31
但是在上面创建抽象类对象的时候,直接将方法重写了然后再下面完成pp.eat()自调用
于是我们调用方法的时候,通过不开辟新内存存储子类对象的方式,直接完成了功能
初次遇见JAVA(二) - 图32
这是最常用的内部类用法,如果你认为一个抽象类没必要创建子类,你可以通过这个方式直接方法重写
针对于这个抽象类的对象本身,而不需要抽象类的子类对象出场,省去内存。
也就是说省去了继承的麻烦,直接进行调用并方法重写。

总结是个良好的习惯:
①讲了三大内部类:成员内部类,局部内部类,匿名内部类②成员内部类: 在类里面定义一个类,调用时累加访问调用③局部内部类: 在类的方法里面定义一个类,调用时只需要外面的类对象④匿名内部类: 在类里面,直接调用其他类并方法重写,调用直接用方法完成

10.代码块

代码块应该不属于学习的侧重点,有一个了解非常重要,所以我粗暴总结如下:
代码块分为:局部代码块,构造代码块,静态代码块

①局部代码块
就是一对花括号,单独存在的一个域
初次遇见JAVA(二) - 图33

②构造代码块
构造代码块一般放在构造方法前面,这样实例化对象的时候,是先运行构造代码块再运行构造方法
初次遇见JAVA(二) - 图34
所以我们实例化对象的时候,会发生这个情况
初次遇见JAVA(二) - 图35

③静态代码块
属于强制的代码块,是一定会运行的,无需放在main入口
这个了解一下即可,现在我来重点讲静态代码块在类中的应用:
对于同一个类的不同对象调用中,只运行一次
使用方法是加个static修饰
初次遇见JAVA(二) - 图36
我把上面三种代码块结合到一起,我们来调用两次这个类的对象
初次遇见JAVA(二) - 图37
最后的结果会是什么?
初次遇见JAVA(二) - 图38
所以静态代码块是特殊的,只要是同一个类,不管调用了多少次,只运行一次。

总结还是不能忘:
①讲了三大代码块:局部代码块,构造代码块,静态代码块
②局部代码块:在函数的内部,一般作为单独的作用区域存在
③构造代码块:在类的内部,放在构造方法上面,调用对象是先运行这个再运行构造方法
④静态代码块:1)在main入口上面,运行这个文件先运行静态代码块再进入main
⑤静态代码块:2)在类的内部,不管类的对象调用多少次,只运行一次

11.异常

异常处理是JAVA程序能够流畅执行的重要指标之一,如果没有异常处理
项目随时会因为一点小错误而直接中断,非常影响总体运行的流畅性。
异常,就是Throwable,这是一个异常总类,分为两部分:
Error(错误,不能处理的错误,只能改代码) 和 Exception(异常,可以处理的异常)

实际上我们对Error是无能为力的,只能优化代码结构来解决
所以我们设计JAVA程序的时候,需要关注Exception层次结构。

我们常见的错误就是数组索引不合法,空指针异常等等
数组索引不合法就是ArrayIndexOutOfBoundsException类,空指针异常就是NullPointerException类
我们来举一个例子,学会解决异常之前,我们需要学会抛出异常
初次遇见JAVA(二) - 图39
这个简单的例子,很好地展现了抛出异常的完整过程,这些都是JAVA底层中已有的异常类。
学会了抛出异常,我们该了解到如何自定义异常,因为JAVA程序是读给人看的,团队合作开发
你的代码必须要让他人能够看得懂,所以需要自定义异常,取个更加合理的名字。

我们知道,Exception是一个异常总类,它的父类是Throwable,抛出类(错误就是要用来抛出的)
所以我们创建自定义异常类,肯定是:
定义一个继承于Exception的类或者继承于Exception子类的类
然后抛出错误信息的时候,使用super去调用父类的报错信息
这相当于是套着自定义异常类的壳,使用的还是Exception类的报错,这一切只是为了命名方便
初次遇见JAVA(二) - 图40
这个图片就展示了自定义异常的过程,其中ex就是需要输出的报错信息,自己自定义。
那么我们该如何使用这新的异常类呢?
我们知道,JAVA程序是严格的,抛出异常类取决于底层,我们能够直接使用新的自定义异常吗?
显然不能,我们需要告诉JAVA程序即将会抛出这个异常,才能正常使用
初次遇见JAVA(二) - 图41
必须提前使用throws要告诉这个构造方法,然后里面的代码才能使用这个异常
这段程序,讲述了性别如果不是男或女,就会抛出这个自定义的性别异常
我们该如何在测试中使用?
我说过,JAVA程序依旧是异常的,如果在main总入口创建这个对象,也要提前告诉main
初次遇见JAVA(二) - 图42
看到了吗,这就是使用自定义异常的过程,一定不要忘了使用throws提前告知程序。

现在,我们学会了抛出异常,自定义异常,该轮到解决异常了
直接放大招,解决异常三件套:try-catch-finally
老规矩,先来一段代码混个脸熟
初次遇见JAVA(二) - 图43
所以我们能够知道这三件套的用法了,就是:
try{ } catch(){ } finally{ }
首先说说finally代码块,这是一定执行的,不管前面的执行结果如何,通常用来收尾工作
然后重点讲try-catch之间的配合
try代码块,里面就放需要判断是否有异常的代码
如果有,就进入catch代码块,注意:
如果try存在多个错误,执行时碰到第一个错误的时候跳过try余下的代码,直接来到catch
所以我们判断到第一个异常就不代表没有第二个异常,只不过是跳过了而已
那么catch()到底是什么呢,需要两个参数,可能会发生的异常类,存储错误信息的变量名
假如try里面会发生数组索引不合法的异常,那么catch应该怎么写?
初次遇见JAVA(二) - 图44
如果是空指针异常呢?
初次遇见JAVA(二) - 图45
如果两种异常都有可能发生呢,那就写多个catch
初次遇见JAVA(二) - 图46
事实上,我们并没有那么多心思来判断和记住可能会出现的异常类
所以我们正确的用法就是用一个总类作为参数,依据ex来判断具体是什么报错
初次遇见JAVA(二) - 图47
我们来运行一下,这一次是空指针异常,来看看输出回事怎么样的
初次遇见JAVA(二) - 图48
看,通过这个异常处理三件套,这个JAVA程序最终输出并没有被打断,而是输出了三行
初次遇见JAVA(二) - 图49
相比较上面没有异常处理的结果,是不是显然顺利多了?肯定是前者更加运行流畅。

在前面,我们知道,能使用ex这个变量来打印具体的异常类
实际上这个ex变量自带方法,能够以更好的方式输出,进行直观展现
已经知道的方法:System.out.println(ex);
更好的方法:
①ex.printStackTrace();
②System.out.println(ex.toString());
初次遇见JAVA(二) - 图50
毫无疑问,第一种方法显然更好,展现也更加逼真。
现在,我们需要一个案例,来强化记忆一下上面学到的东西
将自定义异常,抛出异常,解决异常结合在一起,做一个完整的案例
这个过程我们会在NoAgeExcetion.java,Student.java和MyTest.java一起展示
①NoAgeExcetion.java自定义异常
初次遇见JAVA(二) - 图51
②Student.java设置抛出异常
初次遇见JAVA(二) - 图52
③MyTest.java,解决异常
初次遇见JAVA(二) - 图53
我们可以看到,我给的是-20年龄,肯定是要抛出异常的,这段代码会进入catch
初次遇见JAVA(二) - 图54

现在,你会用异常了吗?
实际上就是try语句发现了异常,然后正好也符合catch的参数设置,于是这个异常被catch抓到了
就进入了catch语句,进一步对这个异常的处理,一般是回溯。
如果try语句没有异常,跳过catch代码块,继续往下执行。

12.一些感悟

我们在上面所有的笔记中,学习了大部分JAVA入门基础
当然了,以我学习Python和JavaScript的经验,学习一门语言应该是:
学习语法—学习变量—学习数组—学习if/for/while—学习面向对象—学习相关库—学习异常
这些步骤差不多是所有语言的入门步骤,只不过JAVA表现地更加丰富
所以你在上面看到了许许多多稍微复杂的JAVA基础知识,这些是正常的。
我有必要做一个入门的一个总结,但是我又疲于写那么多总结
大不了重新翻阅一下上面的笔记,再看一遍就完事了,我主要想表达我对JAVA的一些感悟
JAVA语言毫无疑问是美丽的,我已经深深被JAVA吸引了
因为我学习的Python/JavaScript都是脚本语言,而JAVA是编译语言
我相信,JAVA还是能够带来更大的舞台的,一个语言的特性决定上限,脚本语言终究存在局限性
为此,我买了《Java 核心卷 Ⅰ》,学习的过程中,尤其是异常,给我带来了一个很大的帮助。
不知道什么时候我可以读完这本伟大的书籍,但是我的思想肯定是经过了实实在在的转变
今后绝对不会再局限于某种语言或者是某种方向,这个走下去就是一个死胡同
曾经我想过,我就把Python死里学,肯定能靠这个混碗饭吃,现在,我一笑而过
成为真正的程序员只有一条出路,全面发展,所以,我已经准备好了。
我还会继续往下学习,学习集合和框架,接触到JAVA高级部分。
但是学完之后,不会立即投向于JAVA WEB开发,我说过,我需要全面发展
所以我会回去复习Python,深入Python特性,搞清楚那些比较复杂的Python特性
然后我会投入数据结构与算法的学习,使用Python和Java反复实现,彻底巩固我的JAVA基础
以后,我就不知道了,谁也不知道会发生什么,但是我很清楚我上面的选择,我知道我在做什么
我这样做,我的路只会越来越宽,不再局限于某一个固定的方向,至于未来会怎么样
who cares?学习就完事了呗!

初次遇见JAVA(二) - 图55