一、异常
如:
package com.exception.demo01;
public class Demo01 {
public static void main(String[] args) {
// new Demo01().a(); // StackOverflowError: 堆栈溢出错误
System.out.println(11 / 0); // ArithmeticException: 算术异常
}
public void a (){
b();
}
public void b (){
a();
}
}
二、异常体系结构
Error(错误):AWTError(GUI组件错误)、VirtulMachineError(JVM虚拟机错误)。
Exception(异常):IOException(输入输出异常,属于Checked Exception检查型异常)、RuntimeException(运行时异常,属于Unchecked Exception非检查型异常)。
常见错误有:OutOfMemoryError(内存耗尽)、StackOverflowError(栈溢出)、NoClassDefFoundError(类定义未找到)。
Exception(异常)分为两类:RuntimeException( 运行时异常 / 非检查型异常)、其他Exception(检查型异常(Checked Exception) / 非运行时异常)。
检查型异常(Checked Exception):也叫非运行时异常。是指在Java编程中,必须在编译时显式处理的异常。编译器会强制要求开发者对这类异常进行处理,否则编译无法通过。如:IOException(文件未找到、读写错误等)、SQLException(数据库操作错误)、ClassNotFoundException(类加载失败)等。
非检查型异常(Unchecked Exception):包括运行时异常(RuntimeException)和错误(Error)。运行时异常如:NullPointerException(空指针)、ArrayIndexOutOfBoundsException(数组越界)、IllegalArgumentException(非法参数)、ArithmeticException(算术异常,如除零)。
三、异常处理机制
异常处理关键字:
package com.exception.demo01;
/*
try --> 可能抛出异常的代码。未被捕获的异常最终会导致线程/程序终止。
throws --> 用在方法声明上,表示该方法可能抛出哪些异常。
throw --> 用在方法内部,立即中止当前代码块的执行,主动抛出一个异常对象。
catch --> 捕获并处理异常
finally --> 最终执行的清理代码
*/
/* 抛出的异常会抛到哪里:
抛出的异常会沿着调用栈向上传播:
1. 如果当前方法有catch块处理该异常,就在那里被捕获。
2. 如果没有,异常会抛给调用该方法的上层方法。
3. 如果一直传播到main方法还没有被捕获,最终会传到JVM。导致线程(或主线程,程序)终止,打印异常堆栈信息到控制台。
*/
public class Test {
public static void main(String[] args) {
// try {
// new Test().test(1 , 0);
// } catch (ArithmeticException e) { // 捕获 ArithmeticException异常
// throw new RuntimeException(e); // 包装后重新抛出 RuntimeException异常
// }
int a = 1;
int b = 0;
try { // try监控区域
if (b == 0 ){
throw new ArithmeticException(); // 程序运行终止,抛出异常,跳转到匹配的捕获catch代码块。
}
System.out.println(a / b); // 不会执行
}catch (ArithmeticException e){ // catch捕获异常。ArithmeticException算数异常。
System.out.println("程序出现异常,变量b不能为0");
}finally { // finally(最后),可选语句块。finally代码块无论是否发生异常都会执行,主要用于资源释放和清理工作。finally的优先级:即使在catch中又抛出了新异常,finally也会先执行。
System.out.println("finally");
}
try {
new Test().a();
}catch (Error e){ // catch(想要捕获的异常类型)。假设要捕获多个异常: 应从上往下,范围从小到大依次捕获,只会执行其中的一个catch代码块。
System.out.println("程序出现Error异常");
}catch (Exception e){
System.out.println("程序出现Exception异常");
}catch (Throwable e){ // 参数为想要捕获异常的类型,Throwable为最高超类。
System.out.println("程序出现Throwable异常");
}finally {
}
try {
new Test().test(1,0);
} catch (ArithmeticException e) {
// e.printStackTrace(); // 打印异常堆栈信息的方法,通常用于调试阶段。
}
}
public void a(){
b();
}
public void b(){
a();
}
/* throws:
声明方法可能抛出的异常。对于Checked Exception(检查型异常),编译器会强制调用者处理(捕获或继续声明抛出)。
throws声明实际上是为异常提供传播路径。当方法内部:使用throw抛出异常或调用的其他方法抛出异常时,这些异常会通过throws声明的通道向上传递。
*/
// 检查型异常(Checked Exception)是指在Java编程中,必须在编译时显式处理的异常。编译器会强制要求开发者对这类异常进行处理,否则编译无法通过。
// 检查型异常可以通过try-catch块捕获,或者通过throws声明抛出给上级方法处理。
public void test(int a, int b) throws ArithmeticException{ // throws: 用于方法声明,表示该方法可能抛出哪些异常。语法: method() throws ExceptionType1,ExceptionType2{}
if (b == 0){
throw new ArithmeticException(); // 主动抛出算术异常。throw: 用于方法内部,立即中止当前代码块的执行,主动抛出一个异常对象。throw。语法: throw new ExceptionType()。
}
System.out.println(a / b);
}
}
扩展:
package com.exception.demo01;
public class Test2 {
public static void main(String[] args) {
int a = 1;
int b = 0;
// 选中需运行代码,快捷键: Ctrl + Alt + T。选择创造语句块类型
try {
System.out.println(a / b);
} catch (Exception e) {
e.printStackTrace(); // 打印异常堆栈信息的方法,通常用于调试阶段。生产环境更推荐用日志框架(如log.error("异常", e))。
// System.exit(1); // 程序结束,不会执行后面的语句。status的参数是程序的退出状态码。0表示程序正常退出。1或2等表示程序异常退出的不同错误类型。1一般表示通用错误。
throw new RuntimeException(e); // 将捕获的异常e包装后重新抛出RuntimeException异常。如果外层没有捕获这个异常,线程/程序会终止并打印堆栈信息,并不会往下运行。
} finally {
}
}
}
代码块创建快捷键:
四、自定义异常
MyException(自定义异常类):
package com.exception.demo02;
// 自定义的异常类
public class MyException extends Exception{
// 传递数字 > 10;
private int detail;
public MyException(int a) { // 自定义异常类的构造器。创建一个完整的异常对象
this.detail = a;
}
// toString: 异常的打印信息
@Override
public String toString() {
return "MyException{" + detail + '}';
}
}
主动抛出并捕获自定义异常:【包含字符串连接,底层运行讲解】
package com.exception.demo02;
public class Test {
static void test(int a) throws MyException{ // 2. 声明可能抛出MyException异常。异常沿着调用栈向上传播。
System.out.println("传递的参数为:" + a);
if (a > 10 ){
throw new MyException(a); // 1. 创建并抛出异常对象。调用MyException的构造器,创建一个完整的异常对象。
}
System.out.println("OK"); // 不会执行
}
public static void main(String[] args) {
try {
test(11);
} catch (MyException e) { // 3. 最后main方法中的try-catch会捕获MyException异常。
// 可在此增加一些处理异常的代码
System.out.println("MyException=>" + e); // 输出: MyException=>MyException{11}。调用e.toString()
}
/* 解释: System.out.println("MyException=>" + e); 为什么会调用自定义异常类MyException的toString方法。
'+' : Java处理字符串连接时,编译器会将其进行转换。(Java的隐式方法调用机制)
原始代码: String s = "MyException=>" + e;
编译后的等效代码:
String s = new StringBuilder()
.append("MyException=>")
.append(e)
.toString();
=========================================================
相关源码: public AbstractStringBuilder append(String str) { // JDK8和JDK9+略有不同
if (str == null) return appendNull();
int len = str.length();
ensureCapacityInternal(count + len); // 检查扩容
str.getChars(0, len, value, count); // 关键复制操作
count += len;
return this;
}
public StringBuilder append(Object obj) { // 该方法直接继承AbstractStringBuilder
return append(String.valueOf(obj));
}
public static String valueOf(Object obj) { // 【重点: Java处理对象转字符串的核心方法】
return (obj == null) ? "null" : obj.toString(); // 重写toString()方法自定义
}
public String toString() { // JDK8和JDK9+略有不同
return new String(value, 0, count);
}
逐句解释:
new StringBuilder()这里会创建一个初始容量为16的字符数组。【这样的好处是节省不必要的空间】
.append("MyException=>")这里会调用StringBuilder.append(String str),际执行其父类AbstractStringBuilder。
【作用是检查其中的字符串是否>16,是否需要扩容,然后将字符串复制到内部数组中。"MyException=>".getChars(0,13,value,0);计数count=13;】
.append(e)这里会调用StringBuilder.append(Object obj),触发String.valueOf(e),调用重写的toString()方法。
.append(e)后面返回为append("MyException{11}"),再调用AbstractStringBuilder append(String str)方法。
【依然将字符串复制到内部数组中,结合上面一个字符串长度13+新字符串12=25,大于16,随后扩容。新容量计算:(16+1)*2=34。将原数组内容复制到新数组里面,并追加新字符内容。计数count=25;】
.toString()创建新的String对象。将StringBuilder内部数组中实际使用的部分(前25个字符)复制过去。不直接暴露内部数组(防御性拷贝)。
最终结果:
new String(["M","y","E","x",...,"1","1"], 0, 25)
即"MyException=>MyException{11}"
*/
}
}
快捷键:ALT + ENTER —— 快速生成代码、修正代码错误、各种自动化操作。