0729(020天 异常处理1.0)
每日一狗(露娜是只嘤嘤怪)
异常处理
1. 异常处理
异常:以对象形式封装了各种各样的错误信息(错误类型、报错行数等等)
程序员真理:程序会出错。
- 出错不是问题,关键是出错之后,错误如何处理?谁处理?
- 程序员可以从错误中恢复吗?恢复不了就崩溃吗?
- 不能够呀!
目的:在程序报错的时候会抛出异常,从而结束进程,而异常处理可以接收这个程序抛出的异常,并根据异常的类型做出不同的处理,从而使程序继续正常执行。
为了防止一些由于报错所导致程序中断,是程序更具鲁棒性,保证用户体验。
1.1 基础语法
// 程序内部对异常进行了消化处理,当然如果在处理时也报错了就没辙了
try{
try代码段中包含可能产生异常的代码,有人称为陷阱代码,
在执行过程中如果出现了异常,则异常之后的java语句不会
执行。转而执行catch部分的代码
} catch(SomeException e){ 可以写多个
try后可以跟一个多个catch代码段,针对不同异常执行不同
的处理逻辑。当异常发生时,程序会中止当前的流程,根据获
取异常的类型去执行响应的代码段。注意异常类型判定时是从
上向下逐个判断的。
}finally{
finally代码是无论是否发生异常都会执行的代码
}
// 负责告诉调用者,我这段代码可能会报错,而且我也没做处理,你自己看着办吧
public boid pp() throws Exception{
try{
// 可能的异常代码块
}finally{
// 一定会执行部分
}
}
案例:键盘录入整数
package com.yang1;
import java.util.Scanner;
public class T01 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int input = -1;
while (true) {
try {
String sinput = sc.next();
input = Integer.parseInt(sinput);
break;
} catch (Exception e) {
System.out.println("输入了非法字符");
}
}
System.out.println(input);
}
}
1.2 finally一定要被执行的代码块
一些细节
- finally代码块在return之前执行
- 通常在finally语句中可以进行资源的清楚工作,例如关闭打开的文件,删除临时文件等。注意Java的垃圾回收机制执行回收在堆内存中对象所占用的内存,而不会回收任何物理资源(如数据库连接、网络连接和磁盘文件等)
中断执行的方法
- system.exit(0)中断正在运行虚拟机,并将参数返回;一般为-数时则是有错
- 程序所在线程死亡或者cpu关闭
- 如果finally代码块中的操作有产生异常,则该finally代码块不能完全执行结束。
1.3 异常的分类
异常:
-
受检型异常:明知道存在这种异常的可能,但是没有办法杜绝,所以必须进行编码异常处理(必须进行异常处理)
-
非受检型异常(运行时异常)属于Exception但是不是RuntimeException:这种一场不需要进行处理,发现的越早越好(可以处理也可以不管)
异常的大类:
-
Error及其子类:
- 一般是有虚拟机导致的异常,没办法处理故不处理(内存溢出)
-
RuntimeException及其子类(运行时异常):
- 一般是由于程序不严谨导致(除零操作),越早发现越好
- 这个一般是完善代码,一般不用异常处理
-
Exception及其子类中除了RuntimeException及其子类之外的其它异常:
- 明知道存在但是没有办法杜绝的异常(断网啦,断电啦),需要做断点续传,需要编码将程序从异常状态中恢复过来,保证程序能够继续执行【鲁棒性】
- 这类异常一般采用try/catch或者throws声明抛出的方式进行异常处理,当程序出了非正常情况,尽量保证程序正常结果,而不是立即中断、
1.4 常见的运行时异常
索引下标越界(ArrayIndexOutOfBoundsException)
数组越界、字符串索引越界
解决方案:在访问指定索引位置信息时进行下标范围判定
数学异常(ArithmeticException)
除零
解决方案:判定除数
空指针异常(NullPointerException)
针对空指针对象调用方法
解决方案:保证引用类型在调用其方法前判定不为null
引用类型转换时异常(ClassCastException)
将变量强转为不兼容的类型。
解决方案:进行强转前先用 instanceofy 进行类型判定
数据格式异常(NumberFormatException)
字符串强转为整形时如果字符串含有非数字字符及
解决方案:可以使用正则表达式进行格式验证,或者判定字符是否全都是字符
package com.yang1;
public class T02 {
/*
* 123a:123
*
* 123:-1
*
*/
public static void main(String[] args) {
int strInt1 = -1;
int strInt2 = -1;
String str1 = "123a";
String str2 = "123";
if (isInt(str1)) {
strInt1 = Integer.parseInt(str1);
}
if (isInt(str2)) {
strInt1 = Integer.parseInt(str2);
}
System.out.println(str1 + ":" + strInt1 + "\n" + str2 + ":" + strInt2);
}
public static boolean isInt(String str) {
boolean res = true;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) < '0' || str.charAt(i) > '9') {
res = false;
}
}
return res;
}
}
1.5 人为抛出异常
- throw:人为抛出异常
new RuntimeException(); //
new RuntimeException("异常的提示信息");// 这个是有说明性质的提示信息的异常
- throws:
用在方法签名中,用于声明该方法可能抛出的异常,要求谁调用谁处理这个异常。
主方法上也可以使用throws抛出。如果在主方法上使用了throws抛出,就表示在主方法里面可以不用强制性进行异常处理,如果出现了异常,就交给JVM进行默认处理,则此时会导致程序中断执行。
public void pp() throws Exception{}
1.6 catch结构中的e对象
是用于指代陷阱代码中的异常对象
-
getMessage()
:String用于获取有关异常事件的信息,一般是异常信息的描述,
例如【For input string: “123.456”】 -
toString():
String,输出格式为【java.lang.NumberFormatException:
For input string: “123.456”】 -
printStackTrace()
:void用来跟踪异常事件发生时执行堆栈的内容,注意:这
里的输出默认采用错误流System.err进行输出,如果还使用了System.out进行输
出,则不能保证显示顺序
1.7 考点
到底返回的是啥,这里返回的是88,finally代码块在return之前执行😂
1.8 OOM重要面试问题
当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error OutOfMemoryError
两种情况:
-
分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少
-
应用用的太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。
内存异常的两种方向
-
内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。(不用了我占着就是玩)
-
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。(太贪了)
java中有自己的垃圾回收机制,所以一般不用去主动释放不用的对象所占用的内存,也就是说从理论上来讲,Java应该不会存在内存泄露的(当内存不足时java会自动将垃圾回收进程的优先级向上提的,没内存的时候,java会自动进行垃圾回收,这时候就又有内存了)。但是如果是全局的东西,搞了个变长数组,一直往其中搞数据,这是谁也顶不住呀!这不同于c++中的忘了做资源回收,这是纯纯作死呀。
常见的OOM三种溢出:
- 堆内存溢出(内存溢出)
- 永久带溢出(要读取的的类太多了、另外过多的常量尤其是字符串也会导致方法区溢出)
- 栈溢出(递归自己)
1、java.lang.OutOfMemoryError: Java heap space
------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
2、java.lang.OutOfMemoryError: PermGen space
------>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m
-XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。 3、java.lang.StackOverflowError ------> 不会抛OOM
error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。
1.9 注意事项
-
异常从上到下、从小到大(可以同级别异常、只要不把子写在父后边导致不可达语句产生就行了)
-
也可以不处理,光接受
-
cath是if_esle的匹配
-
允许在catch中声明多个同级别异常,用 | 分隔(父子类情况只写子类)
-
异常捕获后允许继续向上抛出
-
异常e不允许修改类型,可以修改为另一个自己或者自己的子类(仅支持单个异常时)
-
catch结构中的e指代陷阱代码中的异常对象
- 输出错误流System.err进行输出
最佳异常相关编程实践
- 不要在try_eatch_finanlly中使用return
- 不要在finall中抛出异常。
- 减轻finally的任务,不要在finally中做一些其他的事情,finally快仅仅用来释放资源是最适合的。
- 将尽量将所有的return写在函数的最后面,而不是try_catch_finally中
- 不要过度使用异常,不要把异常当作不同的逻辑分支对待
- 不要使用过于庞大的try块
- 避免使用catch all语句 try{}catch(Throwable t){}
- 坚决不要忽略捕获的异常,坚决不允许catch块中内容为空
2. 一些问题的解决方案
2.1 依赖 finally 执行资源回收操作(关闭文件)
- 原始写法(套娃)
一定要执行的关闭代码部分也有可能会报错,在套一个try_eatch
public class Test2 {
public static void main(String[] args) {
Reader r = null;
try {
r = new FileReader("d:/aaa.txt");
int temp = -1;
while ((temp = r.read()) != -1) {
System.out.print((char) temp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (r != null)
r.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 简写
这里引入了Closeable和AutoCloseable接口,可以自动关闭资源。不需要使用finally编程关闭
public class Test3 {
public static void main(String[] args) throws Exception {
try (Reader r = new FileReader("d:/aaa.txt");) {
int temp = -1;
while ((temp = r.read()) != -1) {
System.out.print((char) temp);
}
}
}
}
2.2 容易出现的一个编码问题:隐藏异常
不要坑自己,可以坑客户
try{陷阱代码;}catch(Exception e){}
3. 自定义异常
不能diy的东西就不是好东西
3.1 基本定义
自定义类继承于Exception就行,剩下的全都IDE工具生成。
public class AccountException extends Exception {
public AccountException() {
super("账户余额不足!");
}
}
组成部分
-
自己写的
- 一个无参构造函数
-
其余的方法可以使用IDE工具自动生成
-
一个带有String参数的构造函数,并传递给父类的构造函数。
-
一个带有String参数和Throwable参数,并都传递给父类构造函数
-
一个带有Throwable 参数的构造函数,并传递给父类的构造函数。
-
3.3 使用
2、在方法适当的位置生成自定义异常的实例,并使用throw语句抛出
public void zhuan(Account source, Account target, long mount) throws AccountException {//告知调用方,这个方法可能会出现这个问题,要求调用方法进行处理
// 因为定义AccountException的父类为Exception,
// 所以这个异常属于受检型异常,所以必须进行处理,try/catch或者throws
if (source.getBalance() < mount)
throw new AccountException();
... ...
}
3、在方法的声明部分用throws语句声明该方法可能会抛出的异常或者使用try/catch结构直接进行处理或者自定义异常为运行时异常
// 使用运行时异常是可以简化调用方编程,
// 但是也可能导致调用方根本不知道这里可能会出现异常
// 毕竟用户的脑回路岂是区区几个测试人员就能猜透的
public int pp(String str){ //可以使用throws声明抛出,也可以不做声明
return Integer.parseInt(str);
}
4、注意:方法重写时需要抛出比原来方法一致或者更少的异常
扩展小芝士
- 输出空指针
Object a = null;
System.out.println(a);
/PrintStream.class//
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
String.class///
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
-
在tasks面板可以找到
TODO 工作日志
-
输出空指针
Object a = null;
System.out.println(a);
/PrintStream.class//
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
String.class///
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
- 在tasks面板可以找到
TODO 工作日志