异常
概述: Java针对代码中的错误或者异常的一种描述的对象体现 【错误、异常信息的描述 -->(封装成) 对象】
文章目录
1.代码中异常的分类
- Throwable
- Error【严重错误】 我们无法解决,比如内存溢出
- Exception 【异常】
- 编译时异常、受检异常: 我们如果不解决,会编译报错,Exception的子类但是不是RuntimeException都是编译时异常
- 运行时异常: 我们也可以解决,一般程序不够严谨, RuntimeException或者RuntimeException的子类都是运行时异常
2.为什么要处理异常?
处理异常的本质–> 在程序出现异常的情况下,能够保证程序继续执行,最好打印错误信息
2.1默认虚拟机处理异常的方式
JVM处理异常的方式不能够满足我们的需求,所以需要自行处理异常
- 打印错误信息
- 错误的类名: java.lang.ArithmeticException
- 错误的描述: / by zero
- 错误的位置 main ExceptionDemo02 java:11
- 直接杀死程序,导致程序终止不能完整执行
3.try catch finally
程序必定转入finally代码块执行,除非虚拟机退出,一般用来释放资源,IO,数据库连接都需要释放连接
虚拟机退出:(e.g.: System.exit(0); // 退出JVM
-
try catch
//格式 try{ //放置程序可能出现问题的代码 } catch(异常类 异常名){ //这里放置异常处理的代码 }
-
try catch finally
//格式 try{ //放置程序可能出现问题的代码 } catch(异常类 异常名){ //这里放置异常处理的代码 } finally { //IO,数据库连接释放连接 }
-
try finally
//格式 try{ //放置程序可能出现问题的代码 } finally { //IO,数据库连接释放连接 }
-
try catch…catch…finally
//格式 try{ //放置程序可能出现问题的代码 } catch(异常类 异常名){ //这里放置异常处理的代码 } catch(异常类 异常名){ //这里放置异常处理的代码 } ... finally { //IO,数据库连接释放连接 }
3.1注意
- try代码块中,异常的代码后的代码全部不执行
- 系统抛出的异常对象和异常类进行匹配,可以使用多态
- try块中的代码越少越好
- 无论多少个catch块,只会匹配成功一个,或者都不匹配
- 异常处理的顺序是先子类后父类
- 在catch的最后使用Exception作为父类处理
- 一般不在类中进行try catch,因为实际开发并不一定能看到原码
3.2finally final finalize的区别
final:
- 可以修饰类、方法、变量
- 修饰的变量为自定义常量
- 不可以继承
- 不可以重写
finally:
- 保证重点代码一定要被执行的一种机制
- 进行类似资源释放、保证解锁等动作
finalize:
- 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。用于垃圾回收,但是什么时候回收不确定。
4.如何模仿JVM处理异常
4.1源码解析
Throwable子类都没有方法实现,以下是关于异常的源码方法
class Throwable {
"空指针异常啦!!!"
private String detailMessage;
"空指针异常啦!!!"
public Throwable(String message) {
fillInStackTrace();
detailMessage = message;
}
public String toString() {
String s = this.getClass().getName(); // java.lang.NullPointExcetion
String message = this.getLocalizedMessage(); "空指针异常啦!!!"
return (message != null) ? (s + ": " + message) : s;
java.lang.NullPointExcetion: 空指针异常啦!!!
}
public String getLocalizedMessage() {
return this.getMessage(); "空指针异常啦!!!"
}
public String getMessage() {
return detailMessage;"空指针异常啦!!!"
}
}
4.2从Throwble类中提取错误信息
说明 | |
---|---|
fillInStackTrace() | 向堆栈中填充错误信息 |
toString() | 返回错误信息的描述 类路径:错误信息 |
getLocalizedMessage | 返回错误描述 |
getMessage | 返回错误描述 |
StackTraceElement[] getStackTrace() | 获取堆栈中的错误信息 |
void printStackTrace() | 输出堆栈错误信息 |
void printStackTrace(PrintWriter s) | 日志文件 log |
// 日志文件 log生成(PrintWriter & FileWriter)
try {
System.out.println(a / b);
} catch (Exception e) {
e.printStackTrace(new PrintWriter(new FileWriter("exceptionLog.txt"), true));
System.exit(0);
}
4.3StackTraceElement中相关方法
说明 | 例子 | |
---|---|---|
String getClassName() | 返回类的完全限定名,该类包含由该堆栈跟踪元素所表示的执行点。 | com.exceptiondemo.ExceptionDemo |
String getFileName() | 返回源文件名,该文件包含由该堆栈跟踪元素所表示的执行点。 | ExceptionDemo.java |
int getLineNumber() | 返回源行的行号,该行包含由该堆栈该跟踪元素所表示的执行点。 | 55 |
String getMethodName() | 返回方法名,此方法包含由该堆栈跟踪元素所表示的执行点。 | main |
int hashCode() | 返回此堆栈跟踪元素的哈希码值。 | |
boolean isNativeMethod() | 如果包含由该堆栈跟踪元素所表示的执行点的方法是一个本机方法,则返回 true。 |
-
异常的相关方法Demo
try { method(); } catch (Exception e) { StackTraceElement[] sTraceElements = e.getStackTrace(); for (StackTraceElement ste : sTraceElements) { String className = ste.getClassName(); String fileName = ste.getFileName(); String methodName = ste.getMethodName(); int lineNumber = ste.getLineNumber(); boolean isNative = ste.isNativeMethod(); System.out.println(fileName + "|" + className + "|" + methodName + "|" + isNative + "|" + lineNumber); } } //ExceptionDemo.java|com.exceptiondemo.ExceptionDemo|show|false|82 //ExceptionDemo.java|com.exceptiondemo.ExceptionDemo|demo|false|75 //ExceptionDemo.java|com.exceptiondemo.ExceptionDemo|method|false|71 //ExceptionDemo.java|com.exceptiondemo.ExceptionDemo|main|false|55
-
为什么相关方法是数组形式?
- 因为异常很多时候不止一个,出现多个方法中,数组就能把不同信息存储在不同的StackTraceElement相关方法中
try {
method();
} catch (Exception e) {
StackTraceElement[] sTraceElements = e.getStackTrace();
for (StackTraceElement ste : sTraceElements) {
String className = ste.getClassName();
String fileName = ste.getFileName();
String methodName = ste.getMethodName();
int lineNumber = ste.getLineNumber();
boolean isNative = ste.isNativeMethod();
System.out.println(fileName + "|" + className
+ "|" + methodName + "|" + isNative + "|" + lineNumber);
}
}
//ExceptionDemo.java|com.exceptiondemo.ExceptionDemo|show|false|82
//ExceptionDemo.java|com.exceptiondemo.ExceptionDemo|demo|false|75
//ExceptionDemo.java|com.exceptiondemo.ExceptionDemo|method|false|71
//ExceptionDemo.java|com.exceptiondemo.ExceptionDemo|main|false|55
如何看异常:从下往上看,先看自己写的代码,再看别人或者源码的
4.4模拟虚拟机处理异常的方式
为什么要模拟JVM的异常处理?因为自己写的太简略了,模拟虚拟机,即可以解决虚拟机因为异常而终止程序,也可以获得详尽的异常信息
try {
System.out.println(a / b);
} catch (Exception e) {
StackTraceElement[] stackTrace = e.getStackTrace();
String className = stackTrace[0].getClassName();
String fileName = stackTrace[0].getFileName();
String methodName = stackTrace[0].getMethodName();
int lineNumber = stackTrace[0].getLineNumber();
System.err.format("Exception in thread \"%s\" %s\r\n" + " at %s.%s(%s:%d)", methodName, e.toString(),
className, methodName, fileName, lineNumber);
//printStackTrace()可以打印出上述的所有信息
e.printStackTrace();
}
5.异常链
即在catch处理一个异常的时候,因为这个异常导致catch代码块也出现异常,这时可以cause,把新异常缘由标记为旧异常,这样就可以清晰知道相关关系,在处理好旧异常后,不进入catch,新异常就解决了
-
常常会再捕获一个异常后抛出另外一个异常,并且希望把异常原始信息保存下来,这被称为异常链。
-
在JDK1.4以前,程序员必须自己编写代码来保存原始异常信息
-
现在所有Throwable的子类子构造器中都可以接受一个cause对象作为参数,这个cause就异常原由,代表着原始异常
- 即使在当前位置创建并抛出新的异常,也可以通过这个cause追踪到异常最初发生的位置。
-
只有Error,Exception,RunimeException提供了带cause参数的构造器,其他的所有异常就只有通过initCause()来设置cause了。
try { test2(); } catch (NullPointerException ex) { ex.printStackTrace(); //这是没有带cause参数的构造器的异常对象【Xxx(异常信息, cause)】 ArithmeticException ae = new ArithmeticException("算数异常!"); ae.initCause(ex); //Error,Exception,RunimeException提供了带cause参数的构造器 Exception e = new Exception("出问题啦!大家快来看!", ex); throw e; } }
6.throws
有时处理不了,不想处理,没有权限处理的异常可以用throws把异常抛给调用者
//格式
[访问权修饰符] 返回值类型 方法名(参数列表) [throws 异常类名] {
方法体;
}
特点
-
这种处理方式是一种不负责任的处理方式,也是一种提醒调用者的方式,谁调用谁处理
-
如果是主方法,我们必须处理,因为我们不处理JVM会处理,JVM会将程序结束,不满足异常处理的本质
-
如果一个方法抛出一个异常,那么子类不能抛出的异常不能够被放大(异常不能大于父类)
-
我们可以在一个方法上声明多个异常,中间使用逗号隔开,表示异常出现的可能性(可能出现,不是一定异常)
-
当一个方法抛出的是一个编译时异常的时候,必须处理,不处理就会报错
7.throw
throw是主动的说明异常位置和问题原因,这便于之后问题异常的处理
//格式
throw 异常对象;
7.1throw和throws的区别
- throws是在方法的声明上,throw是在方法体内
- throws声明的是异常类,并且可以声明多个,throw抛出的是异常对象,只能够抛出一个
- throws表示异常出现的可能性,throw表示异常出现的必然性
//throw是主动的,把问题标识出来,防止错误因为规避而导致别的地方出错
//Demo中的“int b = 0;”即使这里可以规避错误,但也可能因为= 0,导致别的地方出错,所以抛出更优
public static void method1() throws ArithmeticException {
int a = 10;
int b = 0;
// System.out.println(a / b);
// 方式一: 规避错误
if (b != 0) {
System.out.println(a / b);
}
// 方式二: 提示错误
if (b == 0) {
throw new ArithmeticException("除数不能为0!");
}
}
8.自定义异常
继承自Exception,当Throwable中的异常不满足我们需求时,可以自定义满足自己需求的异常
public static boolean checkScore(double score) throws ScoreException {
if (score < 0 || score > 100) {
//有参构造才能输出说明
throw new ScoreException("兄弟啊!分数要在0~100分之间,不能是" + score + "这个分数");
}
return true;
}
//直接建一个继承Exception类的类就完成自定义异常了
class ScoreException extends Exception {}
//但要想可以输出说明,就需要有参构造
class ScoreException extends Exception {
private static final long serialVersionUID = -1989349702073875392L;
public ScoreException() {
super();
}
//有参构造
public ScoreException(String s) {
super(s);
}
}