01-面向对象(异常-finally)
还是以之前那个程序为例。我们回想起来,以前在进行异常处理的时候,除了try、catch,是不是还有一部分叫finally呀?
我们把finally也加进去。
finally中存放的是一定会被执行的代码。
测试了两次,一次是传错误的值,发生异常的情况;一次是传正确的值,没有异常的情况,发现finally都执行到啦:
那么,有一个问题,程序发生异常了,这两句话都会执行到:
那这两句话有什么区别吗?
我们来做一个小测试,在程序发生异常的时候,我们希望程序在这里结束,不要往下执行了, 加一句return语句:
运行结果是这样的:
我们发现,"over"没有执行,而“finally”依然执行了。return代表着结束这个主函数,而“over”是主函数中的语句,所以没有被执行。
既然finally始终都会被执行到,那么它存在的意义是什么呢?
我们来用一个实际需求来说明它存在的作用。
我们写程序,去连接数据库。数据库里存储了n多的数据,我们从主机发一个请求到服务器里面去,首先要做的是不是连接呀?
连接好之后,使用完服务器里的数据,我们要及时断开。因为服务器的连接数是有限的,就像CPU的处理能力是有限的一样,如果我们始终和它连接着不断开,会耗费服务器的资源,占用服务器的连接,害其他想使用服务器的宝宝不能及时连接上。
注意哦,不论是否成功取到服务器中的数据,我们结束后都要及时断开哦!
对数据库的使用需要经历这三个过程:
但是在第二步的时候,可能会出现一些异常,比如要删除的数据不存在啦、要添加的数据不符合规则啦这种奇奇怪怪的情况。出现异常之后,程序会结束掉,但是作为一个文明有爱的宝宝,我们关闭数据库的操作也要正常进行呢!
这个时候就需要用到finally啦,我们这样来写(只写了大意,省略了一些内容):
因此,finally灰常重要!
在写到关于异常的处理,如果没有做finally的处理,没有进行资源的关闭,这个程序就是有问题的,面试官会因此认为你缺乏编程经验。后果好严重呀。
finally代码块:定义一定执行的代码。
通常用于关闭资源。
资源一定要关闭掉,否则它的运行压力会非常大。
再说说这个异常,我们可以把它抛出去吗?
我们来思考一下:你在调用我的功能往数据库里面存数据,(也就是我在操作数据库,你只要调用我就行啦)。你把数据给我,我把结果给你就OK。这个时候数据库发生问题了,我把问题抛给你,你能解决吗?
你是调用我的人,你把数据给我了,我来操作数据库,现在数据库发生问题了,我把数据库异常抛给了你,这不合适呀。因为你只是拿数据的人,你根本不懂怎么操作数据库,我却把我的问题丢给了你,我很坏坏!所以这样不好的!
这样做的问题:
1,我打破了我这个程序的封装性,暴露出了它的内容到别人那里去。
2,我把我的问题丢给了你,而你不会解决。
所以,还是负责操作数据库的我,来对数据库异常进行处理,这才是最好的安排。
这样来写:
先处理数据库异常,将数据库异常处理完之后,再向外抛一个异常,并把这个异常标识到方法上:
而你在调用我的方法的时候,数据库异常你处理不了,但是这个异常就要交由你来处理啦。(数据库异常你处理不了是正常的,但是添加的数据不符合规则等等,这种问题,就是你可以处理的啦)
所以开发是要分层次的,模块化的开发,我就负责处理数据库的功能,你就负责数据的部分(数据的获取、返回之类的),将数据获取到,校验就是你的事,最终你是不是把数据要给我,我把它放到数据库里面去,或者说,你需要什么数据跟我说,我去数据库里面帮你找。
举个例子帮助理解哦:
仓库管理员和销售员。销售需要一批货,卖出去,难道需要销售亲自去仓库里面取货吗?如果这样做,过程就会很繁琐。所以将销售和仓库分开,销售来面向客户,需要什么货,让仓库管理员去取,仓库管理员是面向仓库的。
现在,销售需要10箱货,叫仓库管理员去取货。仓库管理员到仓库之后发现,昨天下大雨,仓库漏水了,把货全都淹了,货不能用了。这个时候,仓库管理员需要跟销售说“仓库漏水了,怎么办呀?”吗?不需要的。仓库内部的事情,由仓库管理员来处理。漏水的问题需要仓库管理员来处理,这不是销售可以处理的事,他只需要告诉销售“没有货哦。”就可以啦。
就像上面这两句话:我们先对仓库的异常问题进行处理,然后将问题报出去。但是不需要将本层的问题报出去,只报一个对方可以识别的问题就可以了。
这就叫做问题的封装。有些问题我们需要内部进行处理,处理完之后,我们告诉对方处理的结果就好了。至于原因,愿意说就说,不愿意说就算啦。
那这样可以吗:
仓库管理员把仓库修好了,但是没有告诉销售没有货。销售既没有拿到货,也不知道发生了什么,没有收到任何消息。
这个问题是有关联性的,仓库管理员却没有将关联性的信息提供出去。
所以不可以这样的,这两句话的内容都要做到哦。
02-面向对象(异常-处理语句其他格式)
异常处理语句的几种格式:
使用的时候择其一就好啦。
说一个小问题:
这样编译能通过吗?
不能哦。因为在函数内抛,函数上要标识呢。
那这样写编译可以通过吗:
能。因为问题在内部可以解决了,外部就不知道了。原则是:只要问题可以被解决,问题就可以不声明。问题没有被解决,就要声明在函数上。
什么叫问题可以解决了?有catch就叫问题可以解决了~(catch真好~)
再来,抛一个e,编译能通过吗?
不能。
因为抓住了一个问题,又抛了一个问题。
那这样呢,能通过吗?
能哦。因为它在catch中,只要被处理了就行。
接着再看,这样可以编译通过吗:
问题被处理了吗?没有catch就没有被处理。没处理的话,就要在函数上标识异常。这里并没有标识出去,所以不能编译通过。
那这里写finally有什么用呢?
注意,在这个功能内部,有可能会访问到一些其他资源,但是这个资源产生一些问题,并不在这个资源中处理。但是,我还是要关资源。产生不产生问题不管它,资源都要关掉。产生问题呢,丢给调用者去处理,这个功能不管,但是资源要先关掉,因为对方获取到这个问题之后,他有可能也不会关资源。所以,try和finally是可以在一起的,finally可以用于关闭资源(或者说一定会执行的代码)。
如果在一个功能当中,定义了一些必须要执行的代码。那么就可以用try-finally的形式,将一定要执行的代码放在finally当中。
记住一点:catch是用于处理异常。如果没有catch就代表异常没有被处理过,如果该异常是检测时异常,那么必须声明。
03-面向对象(异常-覆盖时的异常特点)
注意哦,异常声明的时候是不是声明在函数上呀?而函数有一个特性就叫做覆盖。
那么,异常在子父类覆盖中的体现是什么样呢?
异常在子父类中覆盖中的体现:
1,子类在覆盖父类时,如果父类的方法抛出异常,那么子类的覆盖方法,只能抛出父类的异常或者该异常的子类。
举例:
B异常是A异常的子类。
这是为什么呢?父类已经有问题了,子类在继承父类的时候,不能比父类还有问题,只能说是和父类一样的问题,或者是父类问题的子问题。
我们用一个例子来说明:
我们定义了一个Test类,其中有一个function方法,它的function方法接收了Fu类的引用,调用了Fu类的show方法。
function调用的是一个抛出异常的方法,是不是要么抛要么try呀?
我们先try一下:
然后在主函数中创建Test对象,调用function方法就好啦。
过了一段时间,来了一个Zi类,写了一个show方法把父类Fu的这个方法覆盖掉了,他抛出的是C异常。
我们现在调用function的时候给里面传new Zi()(属于多态)。
传了new Zi()之后会发生什么现象呢?
Test类中的f.show()运行的将是Zi类的show方法,而Zi类的show方法抛出的是C异常,它能够处理吗?
不能处理。这就叫做,早期的程序,不能处理后续产生的新异常。
所以,不可以让子类抛新异常,只能够抛可以处理的异常。
如果,Zi类在覆盖Fu类的show方法之后,真的发生了C异常,就必须在内部处理,不能抛哦!
2,如果父类方法抛出多个异常,子类在覆盖该方法时,只能抛出父类异常的子集。
3,如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时,也不可以抛出异常。如果子类方法发生了异常,就必须要进行try处理,绝对不能抛。
04-面向对象(异常-练习)
问题描述:
先要对这个程序进行基本设计。
这个面积功能我们该如何定义呢?我们发现,可以把面积功能先提取出来,这个面积呢,我们可以视为图形当中的拓展功能。(图形可以画出来,但不一定需要求面积,所以是拓展功能)
那么,我们可以把它定义成接口。
接口Shape:
长方形Rec类:
创建Rec对象,并调用getArea方法。
我们传入了负数值,这显然不合常理:
该怎么解决呢?
以前还比较青葱的我们会这样干:
这么做很笨笨哒~
为什么说这么做很笨笨,编译运行:
这样有意义吗?
没有,而且它是错的。按理说,传了-3和4,这个长方形就不应该存在,更别说调用面积了,所以r.getArea()方法是不应该运算的,这才是对的。
刚刚那种笨笨方法,叫做正常流程代码和问题处理代码结合得非常紧密,阅读性比较差。而且,当发生错误之后,处理方法不仅仅是一个简单的输出语句,而是一大堆处理方法,也就是会有一大堆代码来处理这些问题。当这些代码都集中在一起的时候,阅读起来会非常痛苦。
那么怎么办呢?
我们可以只保留正常流程的代码,当异常发生的时候,我们到异常发生的位置,再去看处理代码。这是不是就方便多啦?
所以,异常的产生可以让正常流程代码和异常处理代码相分离。
我们现在定义一个非法值的异常:
然后将异常在这里抛出:
主函数中用try-catch语句来写:
编译运行:
我们再来分析一下:
如果真的出现了问题,传入了-3和4,长方形就没有建立成功,求面积也是没有意义的,后面再用catch来处理,有意义吗?没有。
try-catch后面还有很多关于这个长方形的代码,下面的处理是不是全没有意义呀?那怎么办呢?
让异常继承它,RuntimeException:
一旦发生这个问题,我不告诉你,我就让你的程序停掉,因为数据非法了。数值都非法了,我还如何运算呢?没办法运算呀。而这个问题是你造成的,你造成的问题已经使得我的运算无法继续,那我就不运算辣!我也是有脾气的,哼!
这个时候,程序就被停掉,代码需要被修正。
这就是继承RuntimeException的原因。
这里也不需要标识啦:
不标识的话,主函数中调用的时候就不需要处理了,因为它根本就不知道会发生问题:
此时编译运行,结果就靠谱啦:
程序直接结束,由虚拟机来处理。处理完告诉你,你出现非法值啦,在29行和51行,你赶紧把这个值改掉辣!
所以就要修改代码了。
用户在使用Rec对象的时候,就需要判断一下,他往里面传的值必须保证是符合的值才能往里面传。
再写一个圆形Circle类:
在主函数中调用:
编译运行:
我们又有一个问题,那这样写的话,我们还写这个自定义异常NoValueException做什么呢?
需要写哒,因为自定义异常中会有我们自己的特有内容。而且,如果直接是RumtimeException异常,我们不能具体的看出是哪一个异常。如果我们能够起一个更有意义的名字,比如这里的NoValueException,是不是就更加方便阅读啦?
所以,我们通常会起一个特有的名称,就知道发生了什么问题啦。
这叫做对问题名称的描述。
所以,在这里,我们还是建议,写成NoValueException:
这样运行的时候抛出的就是NoValueException啦。这样名称会更直观一些:
这种是RuntimeException,当然,如果其他可以处理的对象,就需要在方法上声明,并在调用的时候写try-catch语句来处理。注意,将正常流程写在try中,将处理过程写在catch中,这样正常流程和异常处理的代码就成功分离啦,阅读性会更好。
以后注意啦,写代码的时候会经常碰到问题,碰到问题怎么办呢?用异常来描述,封装成对象。问题也是个对象呢。
05-面向对象(异常-总结)
异常:
是什么?是对问题的描述。将问题进行对象的封装。
异常体系:
Throwable
|--Error
|--Exception
|--RuntimeException
异常体系的特点:异常体系中的所有类以及建立的对象都具备可抛性,也就是说可以被throw和throws关键字所操作。注意,只有异常体系具备这个特点。
throw和throws的用法:
throw定义在函数内,用于抛出异常对象。
throws定义在函数上,用于抛出异常类,可以抛出多个用逗号隔开。
当函数内容有throw抛出异常对象,并未进行try处理,那么必须要在函数上声明,否则都会编译失败。
注意:RuntimeException异常除外。也就说,函数内如果抛出的是RuntimeException异常,函数上可以不用声明。
如果函数声明了异常,调用者需要进行处理,处理方法可以throws可以try。
异常有两种:
1,编译时被检测异常
该异常在编译时如果没有处理(没有抛也没有try),编译失败。
该异常被标识,代表这可以被处理。
2,运行时异常(编译时不检测)
在编译时,不需要处理,编译器不检查。
该异常的发生,建议不处理,让程序停止。需要对代码进行修正。
异常处理语句:
有三种结合方式:
1,try和catch:
2,try和finally:
3,try、catch、finally:
注意:1.finally中定义的通常是关闭资源代码,因为资源必须释放。
另外,注意在下面这个例子中这两种语句的不同:
2,finally只有一种情况不会执行。当执行到System.exit(0);的时候,finally不会执行。
自定义异常:
定义类继承Exception或者RuntimeException
1,为了让该自定义类具备可抛性
2,让该类具备操作异常的共性方法
当要定义自定义异常的信息时,可以使用父类已经定义好的功能。
比如异常信息会传递给父类的构造函数:
自定义异常的好处:
自定义异常是按照Java的面相对象思想,将程序中出现的特有问题进行封装。
异常的好处:
1,将问题进行封装。
2,将正常流程代码和问题处理代码相分离,方便于阅读。
异常的处理原则:
1,处理方式有两种:try或者throws
2,调用到抛出异常的功能时,抛出几个,就处理几个。
这时会出现一个try对应多个catch的情况。
3,多个catch,父类的catch放到最下面。
4,catch内,需要定义针对性的处理方式。不要简单的定义printStackTrace,或者输出语句。也不要不写。(因为但凡出现问题,将不会有任何的提示和日志文件保留,会导致这个问题就过去了)
当捕获到的异常,本功能处理不了时,可以继续在catch中抛出。
如果该异常处理不了,但并不属于该功能出现的异常。
可以将异常转换后,再抛出和该功能相关的异常。
或者异常可以处理,但需要将异常产生的和本功能相关的问题提供出去。
当调用者知道,并处理,也可以将捕获异常处理后,转换新的异常。
异常的注意事项:
在子父类覆盖时:
1,子类抛出的异常必须是父类的异常的子类或者子集。
2,如果父类或者接口没有异常抛出时,子类覆盖出现异常,只能try不能抛。
06-面向对象(练习四)
这一节将有一大串截屏,因为不想在这些小练习题上耗费太多时间,就先截下来,以后闲暇时间可以慢慢做着玩儿~
不想看题之间把这段划过去就OK啦。
编译失败。没有再func方法上声明该异常。应改成:
如果func方法上声明了该异常,结果是:B C D
考的是子类的实例化过程。
结果:Test Demo Test
编译失败:因为A接口中并未定义func方法,这个方法是子类特有的。
A B
编译失败,因为A接口中没有定义test方法。
B C 5
编译失败,非静态内部类中不可以定义静态成员。
内部类中如果定义了静态成员,该内部类必须被静态修饰。
4 5 showzi showzi
B C D
编译失败。因为傅雷中缺少空参数的构造函数。
或者子类应该通过super语句指定要调用的父类中的构造函数。
编译失败。因为子类父类中的get方法没有覆盖。但是子类调用的时候不能明确返回的值是什么类型。所以这样的函数可以存在子父类中。
编译失败。因为打印“A”的输出语句执行不到。
记住:throw单独存在,下面不要定义语句,因为执行不到。
编译失败。多个catch时,父类的catch要放在下面。
134 13423
4
网友评论