因为java是c\c++ 发展而来的, 首先咱们先看看c语言里的错误.java
咱们实现1个程序的过程包括, 代码编写, 编译代码成为程序, 执行程序.c++
其中大部分常见的语法错误都会被编译代码这样部过滤掉. 可是即便经过了编译. 执行程序这一步可能仍是会有错误.程序员
缘由不少, 例如常见的除数为0, 内存溢出(数组的index超出界限), 或者内存被其余程序修改等.面试
最简单的例子:编程
[java] view plain copy数组
上面的例子编译时是无错的, 可是一旦执行就会提示吐核错误了. 安全
c语言里对这种执行时出现的错误是无能为力的, 一旦出错就会整个程序崩溃, 就不会在继续执行下面的代码.jvm
并且不少时候出错信息不多, 让你没法判断出错的缘由和地方, 只能一步步当心debug...函数
因此不少用c写的程序有时会出现非法关闭的现象.spa
解决方法只能是在代码里对可能出错的地方添加if 判断.
例如f()函数里能够对b进行判断, 若是是0就不执行.
java里编译器对代码的规范性比c严格得多. 可是即便如此, 经过编译的java程序有时也很难避免执行时出错.
例如, 将上面的c程序改编成java程序:
[java] view plain copy
运行时同样会出错, 下面是出错信息:
[java] view plain copy
可是能够见到, java告诉你出错的类型: 运算错误(ArithmeticExcetion), 出错信息和出错的类与文件行数输出, 方便你调试. jvm虚拟机是会对错误做出必定的处理的.
因此能够简单地将java里的异常理解成java运行时出现的错误, 异常机制就是对这种错误进行处理的机制.
实际上, 当java程序执行时出现错误时, jvm会把执行时出错的信息(例如出错缘由, 类型, 位置) 收集,而后打包成为1个对象(object), 程序员能够对这种对象进行处理. 这种对象就是所谓的异常.
可能出现的异常的代码并非确定会出现异常, 取决于执行环境和数据.!
见下图:
Throwable
/ \
Error Exception
/ / \
xxxxxx xxxxxx RuntimeException
/ \
xxxxxx ArithmeticException
上图的全部对象都是类.
Throwable 表明是可抛出的.
Error 表明的是严重错误, 这种错误程序员没法进行处理, 例如操做系统崩溃, jvm出错, 动态连接库失败等. Error并非异常, 不是本文的重点.
Exception 表明的就是异常了. 它下面不少派生类, 其中它的派生类也分两种, 一种是RuntimeException(运行时异常), 其余的都是非运行时异常
RuntimeException 包括除数为0, 数组下标超界等. 运行时异常的派生类有不少, 其产生频率较高. 它的派生类能够由程序处理或者抛给(throw) 给jvm处理. 例如上面的例子就是抛给了jvm处理, jvm把程序中断执行, 并把错误信息输出到终端上.
非RuntimeExcption 这种异常属于Excepion的派生类(上面红色的xxx), 可是不是RuntimeException的派生类, 这种异常必须由程序员手动处理,不然不经过编译.
ArithmeticExcpetion 算术异常, 它是RuntimeException的派生类, 因此程序员不手动处理也经过编译, 只不过出错时会被jvm处理.
java里对异常的处理有三种.
例如咱们将上面的例子改动一下:
[java] view plain copy
在f()函数中对可能出现的异常的代码进行try catch处理后, 程序会执行catch里的代码. 并且不会中断整个程序, 继续执行try catch后面的代码.
程序执行输出:
[java] view plain copy
注意最终也执行了g()函数中的最后一条语句, 输出了i的值.
也就是说try catch处理后并不会终止程序, 令程序即便出现了错误, 也能够对错误进行必定的处理后继续执行. 这就是java异常机制比c语言安全的地方.
下面会详细讲解 try catch.
注:
getMessage() 方法: Exception类的方法之一, 返回异常的缘由, 上面的 / by zero 就是这个方法输出的.
printStackTrace(): Exception类的方法之一, 在屏幕输出函数栈信息, 也就是异常出现的地方.
例如我在f()函数中不想处理可能出现的异常, 想把它抛出上级函数处理:
下面是个例子:
[java] view plain copy
能够见到f() 加了个条件判断, 若是参数b = 0, 使用throw 直接手动抛出1个异常. 让调用它的函数处理.
g()调用f()函数, 预见到f()可能有异常, 可是也不想处理, 使用throws 关键字告诉调用它的函数本函数有可能抛出这种异常. // 注, 这里的throws对程序并无实质的影响.
h()调用g(), 简单g()定义的throws, 用try catch在本函数进行处理.
输出:
[java] view plain copy
注意这个程序没有执行g() 最后的代码.
throw 和 throws 后面也会详细讲解.
假如上面的例子h() 也不处理怎么办? 就如1.2 的例子, 会抛给jvm处理.
可是这种状况只适用于RuntimeExecption及其派生类.
jvm怎么处理呢, 就是中断整个程序, 并把异常信息输出到屏幕上.
实际上, 当java程序的1个函数抛出异常时,
首先会检查当前函数有没有try catch处理, 若是无检查上一级函数有无try..catch处理....
这样在函数栈里一级一级向上检查, 若是直至main函数都无try..catch, 则抛给jvm..
项目中强烈建议尽可能手动处理, 不要把异常交给jvm.
这里开始详解try catch finally了.
语法是这样的.
try{
可能出异常的若干行代码;
}
catch(ExceptionName1 e){
产生ExceptionName 1的处理代码;
}
catch(ExceptionName2 e){
产生ExceptionName 2的处理代码;
}
...
finally{
不管如何, 最终确定会执行的代码
}
下面用个例子来讲明:
[java] view plain copy
当f()抛出了异常, 那么ff()就不会执行了. 程序会尝试捕捉异常.
首先捕捉ArithmeticException, 捕捉失败.
接下来捕捉IOException, 捕捉成功, 执行gg();
一旦捕捉到一个异常, 不会再尝试捕捉其余异常, 直接执行finally里的h();
执行后面的函数k().
也就是说路线是:
f() -> gg() -> h() -> k()
有2点要注意的.
1. f()函数极有可能未完整执行, 由于它抛出了异常, 抛出异常的语句执行失败, 以后的语句放弃执行.
2. try{} 里面, f()以后的语句, 例如ff()放弃执行.
这种状况很简单, 就是try{}里面的代码被完整执行, 由于没有抛出任何异常, 就不会尝试执行catch里的部分, 直接到finally部分了.
路线是:
f() -> ff() -> h() -> k()
也许有人会问, 咱们怎么知道到底会抛出什么异常?
下面有3个解决方案.
1.看代码凭经验, 例如看到1段除法的代码, 则有可能抛出算术异常.
2.在catch的括号里写上Exception e, 毕竟Exception 是全部其余异常的超类, 这里涉及多态的知识, 至于什么是多态能够看看本人的另外一篇文章.
3. 观察被调用函数的函数定义, 若是有throws后缀, 则能够尝试捕捉throws 后缀抛出的异常
包括我在内不少人会以为finally语句简直多勾余, 既然是否捕捉到异常都会执行, 上面那个例子里的h()为何不跟下面的k() 写在一块儿呢.
上面的例子的确看不出区别.
但下面两种状况下就体现了finally独特的重要性.
例如try里面抛出了1个A异常, 可是只有后面只有捕捉B异常, 和C异常的子句.
这种状况下, 程序直接执行finally{}里的子句, 而后中断当前函数, 把异常抛给上一级函数, 因此当前函数finally后面的语句不会被执行.
例子:
[java] view plain copy
我所说的状况, 就在上面例子里的g()函数, g()函数里尝试捕捉两个异常, 可是抛出了第3个异常(ArithmeticException 算术异常).
因此这个异常会中断g()的执行, 由于没有被捕捉到, 而后抛给调用g()的 h()函数处理, 而在h()捕捉到了, 因此h()函数是能完整执行的.
也就是说g()里的
[java] view plain copy
执行失败
而h()里的
[java] view plain copy
执行成功
可是不管如何, g()里的finally{}部分仍是被执行了
执行结果以下:
[java] view plain copy
这种状况是1中编程的低级错误, 在项目中是不容许出现.
避免方法也十分简单, 在catch子句集的最后增长1个catch(Exception e)就ok, 由于Exception是全部异常的超类, 只要有异常抛出, 则确定会捕捉到.
下面例子:
[java] view plain copy
假如在f()函数抛出了IOExcepion 异常被捕捉到.
那么执行路线就是
f() -> gg() -> j() -> h() -> 上一级function
也就说, 这种状况下finally里的子句会在return回上一级function前执行. 然后面的k()就被放弃了.
能够看出, finally里的语句, 不管如何都会被执行.
至有两种状况除外, 一是断电, 二是exit函数.
在项目中, 咱们通常在finally编写一些释放资源的动做, 例如初始化公共变量. 关闭connections, 关闭文件等.
这个上面提到过了, 一旦捕捉到1个异常, 就不会尝试捕捉其余异常.
若是try里面的一段代码可能抛出3种异常A B C,
首先看它先抛出哪一个异常, 若是先抛出A, 若是捕捉到A, 那么就执行catch(A)里的代码. 而后finally.. B和C就没有机会再抛出了.
若是捕捉不到A, 就执行finally{}里的语句后中断当前函数, 抛给上一级函数...(应该避免)
两种状况, 1就是没有异常抛出, 另外一种就是抛出了异常可是没有捕捉不到(应该避免)
加入try 里面尝试捕捉两个异常, 1个是A, 1个是B, 可是A是B的父类.
这种状况下, 应该把catch(B)写在catch(A)前面.
缘由也很简单, 加入把catch(A)写在前面, 由于多态的存在, 即便抛出了B异常, 也会被catch(A)捕捉, 后面的catch(B)就没有意义了.
也就是说若是捕捉Exception这个异常基类, 应该放在最后的catch里, 项目中也强烈建议这么作, 能够避免上述4.3.1的状况出现.
这个没什么好说的. 语法规则
每1个异常对象只能由catch它的catch子句里访问.
跟if相似, 很少说了.
这个也不难理解..
下面开始详讲异常另外一种处理方法throw 和 throws了.
注意的是, 这两种用法都没有真正的处理异常, 真正处理的异常方法只有try catch, 这两种方法只是交给上一级方法处理.
就如一个组织里 , 有1个大佬, 1个党主, 1个小弟.
大佬叫党主干活, 堂主叫小弟干活, 而后小弟碰上麻烦了, 可是小弟不会处理这个麻烦, 只能中断工做抛给党主处理, 而后堂主发现这个麻烦只有大佬能处理, 而后抛给大佬处理..
道理是相通的..
throws的语法很简单.
语法:
throw new XException();
其中xException必须是Exception的派生类.
这里注意throw 出的是1个异常对象, 因此new不能省略
做用就是手动令程序抛出1个异常对象.
咱们看回上面3.2 的例子:
[java] view plain copy
当这个函数的if 判断了b=0时, 就利用throws手动抛出了1个异常. 这个异常会中断这个函数. 也就是说f()执行不完整, 是没有返回值的.
[java] view plain copy
例如上没的g()函数, 在调用f() 会收到1个异常.
这时g()函数有三种选择.
1. 不作任何处理
这时, g()收到f()里抛出的异常就会打断g()执行, 也就是说g()里面的k(); 被放弃了, 而后程序会继续把这个函数抛给调用g()函数.
而后一级一级寻求处理, 若是都不处理, 则抛给jvm处理. jvm会中断程序, 输出异常信息. 这个上没提到过了.
2. 使用try catch处理
若是catch成功, 则g()函数能完整执行, 并且这个异常不会继续向上抛.
若是catch失败(尽可能避免), 则跟状况1相同.
将上面的例子改一下:
[java] view plain copy
例如, 我不想抛出ArithmeticException, 我想抛出IOExcetpion.
注意 这里, IOException虽然逻辑上是错误的(彻底不是IO的问题嘛), 可是在程序中彻底可行, 由于程序猿能够根据须要控制程序指定抛出任何1个异常.
可是这段代码编译失败, 由于IOException 不是 RuntimeException的派生类.
java规定:
改为这样就正确了:
[java] view plain copy
注意在方法定义里加上了throws子句. 告诉调用它的函数我可能抛出这个异常.
例如抄回上面的例子, g()调用f()函数.
[java] view plain copy
可是编译失败.
由于f()利用throws 声明了会抛出1个非runtimeExcetpion. 这时g()必须作出处理.
处理方法有两种:
1. try catch本身处理:
[java] view plain copy
须要注意的是, catch里面要么写上throws对应的异常(这里是 IOException), 要么写上这个异常的超类, 不然仍是编译失败.
2.g()利用throws 往上一级方法抛
.
[java] view plain copy
这是调用g()的函数也要考虑上面的这两种处理方法了...
可是最终上级的方法(main 方法)仍是不处理的话, 就编译失败, 上面说过了, 非runtimeException没法抛给jvm处理.
虽然这两种处理方法都能经过编译, 可是运行效果是彻底不一样的.
第一种, g()能完整执行.
第二种, g()被中断, 也就是g()里面的k(); 执行失败.
throws稍微比throw难理解点:
语法是:
public void f() throws Exception1, Exception2...{
}
也就是讲, thorws能够加上多个异常, 注意这里抛出的不是对象, 不能加上new.
并且不是告诉别人这个函数有可能抛出这么多个异常. 而是告诉别人, 有可能抛出这些异常的其中一种.
若是为f()函数加上throws后续, 则告诉调用f()的方法, f()函数有可能抛出这些异常的一种.
若是f()throws 了1个或若干个非RuntimeException, 则调用f()的函数必须处理这些非RuntimeException, 如上面的g()函数同样.
若是f() throws的都是RuntimeException, 则调用f()的函数能够不处理, 也能经过编译, 可是实际上仍是强烈建议处理它们.
实际上, 若是1个方法f() throws A,B
那么它有可能不抛出任何异常.(程序运行状态良好)
也有能抛出C异常(应该避免, 最好在throws上加上C)
这个是强制, 告诉别人这个函数内有炸弹.
这个是非强制的, 可是若是你知道一个函数内的代码有可能抛出异常, 最好仍是写上throws 后缀
不管这个异常是否runtimeExcepion.
我的建议, 若是你调用1个函数throws A, B, C
那么你就在当前函数写上
try
catch(A)
catch(B)
catch(C)
catch(Exception)
这样能处理能保证你的函数能完整执行, 不会被收到的异常中断.
固然若是你容许你的函数能够被中断, 那么就能够在当前函数定义加上throws A, B 继续抛给上一级的函数.
例如你在一个派生类重写一个方法f(), 在超类里的f() throws A, B 你重写方法时就不throws出 A,,B,C 或者throws A和B的超类.
缘由也是因为多态的存在.
由于1个超类的引用能够指向1个派生类的对象并调用不一样的方法. 若是派生类throws的范围加大
那么利用多态写的代码的try catch就再也不适用.
面试问得多,单独拉出来写了:
应付面试官.
因此throw后面的通常加上new 和exception名字().
而throws后面不能加上new的
由于一旦一个函数throw出1个异常, 这个函数就会被中断执行, 后面的代码被放弃, 若是你尝试在函数内写两个throw, 编译失败.
而throws 是告诉别人这个函数有可能抛出这几种异常的一种. 可是最多只会抛出一种.
缘由上面讲过了.
咱们能够自定义异常, 只须要编写1个类, 继承1个异常类就ok
例子:
[java] view plain copy
上面的类User_Exception1 就是1个自定义异常, 并重写了printStackTrace()方法.
咱们要理解异常的优缺点, 首先看看没有异常的C语言是如何处理错误的.
下面是个例子:
[cpp] view plain copy
能够见到c语言处理错误有这些特色
1. 大部分精力都在错误处理.
2. 须要把各类可能出现的错误所有考虑到, 才能保证程序的稳定性.
3. 程序可读性差, 错误处理代码混杂在其余代码中.
4. 出错返回信息少, 一旦出错难以调试.
5. 一旦出现了未考虑到的错误, 资源释放代码没法执行.
[java] view plain copy