1. 异常概念
异常:是程序在执行过程中,出现的非正常情况。不加以处理,最终会导致JVM的非正常停止。
程序如果出现语法错误和逻辑错误时,是编译不通过,不会产生字节码,也不会运行。
异常触发机制:在Java中把不同的异常用不同的类表示。当发生某种异常时,就创建该类的异常对象并抛出(throw),然后可以在程序中捕获(catch)并处理。如果程序中没有捕获并处理这个异常,那么程序将终止运行。
异常解决方法:
- 遇到错误终止程序的运行。
- 充分考虑各种可能发生的异常和错误,进行预防和避免。当无法避免时,应进行异常检测和处理,从而保证程序的健壮性。
2. 异常类继承体系
异常的基类:java.lang.Throwable
类,是Java程序执行过程中发生的异常事件对应类的根父类。
Throwable类常用方法:
public void printStackTrace()
:打印异常的详细信息。包含异常的类型、原因、出现位置。public String getMessage()
:获取异常的原因。
Throwable分类:Error和Exception,分别是java.lang.Error
和java.lang.Exception
。
- Error:表示Java虚拟机无法解决的严重问题,一般不针对其进行处理。
- Exception:表示因编程错误或偶然的外在因素导致的一般性问题,需要针对具体问题进行处理。否则程序出现异常时,将会停止运行。其从程序角度分为:编译时异常(受检异常)和运行时异常(非受检异常)。
常见异常:
异常名 | 分类 | 出现时机 | 含义 |
---|---|---|---|
java.lang.StackOverflowError | Error | 运行时异常 | 栈内存溢出 |
java.lang.OutOfMemoryError | Error | 运行时异常 | 堆内存溢出,OOM |
java.lang.ClassNotFoundException | Exception | 编译时异常 | 类不存在 |
java.lang.FileNotFoundException | Exception | 编译时异常 | 文件不存在 |
java.lang.IOException | Exception | 编译时异常 | 输入输出错误 |
java.lang.ArrayIndexOutOfBoundsException | Exception | 运行时异常 | 数组下标越界 |
java.lang.NullPointerException | Exception | 运行时异常 | 空指针 |
java.lang.ClassCastException | Exception | 运行时异常 | 类型转化失败 |
java.lang.NumberFormatException | Exception | 运行时异常 | 数值格式化错误 |
java.lang.InputMismatchException | Exception | 运行时异常 | 输入类型不匹配 |
java.lang.ArithmeticException | Exception | 运行时异常 | 算数错误 |
常见异常示例:
/**
* Exception in thread "main" java.lang.StackOverflowError
* */
public static void main(String[] args) {
main(args);
}
/**
* -Xms10 -Xmx10
* Error occurred during initialization of VM
* Too small maximum heap
* */
public static void main(String[] args) {
byte[] bytes = new byte[1024 * 1024 * 1000];
}
/**
* Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException:
* Index 10 out of bounds for length 10
* */
public static void main(String[] args) {
int[] arr = new int[10];
System.out.println(arr[10]);
}
/**
* Exception in thread "main" java.lang.NullPointerException:
* Cannot invoke "String.toString()" because "str" is null
* */
public static void main(String[] args) {
String str = "demo";
str = null;
System.out.println(str.toString());
}
/**
* Exception in thread "main" java.lang.ClassCastException:
* class java.lang.String cannot be cast to class java.util.Date
* */
public static void main(String[] args) {
Object object = new String();
Date date= (Date) object;
}
/**
* Exception in thread "main" java.lang.NumberFormatException:
* For input string: "abc"
*/
public static void main(String[] args) {
String str = "abc";
int number = Integer.parseInt(str);
System.out.println(number);
}
/**
* Exception in thread "main" java.util.InputMismatchException
* */
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int number = scanner.nextInt();
System.out.println(number);
}
/**
* Exception in thread "main" java.lang.ArithmeticException:
* / by zero
* */
public static void main(String[] args) {
System.out.println(10 / 0);
}
根据异常出现时机分为:编译时异常和运行时异常
- 编译时异常(Checked异常,受检异常):在代码编译阶段,编译器能明确警示当前代码可能存在的异常,并期望针对其做出处理。
- 运行时异常(Runtine异常,Unchecked异常,非受检异常):在代码运行后,根据实际情况发生的异常。通常这类异常是由代码编写不当引起的。
java.lang.RuntimeException
,运行异常标识。
3. 异常处理 try-catch-finally
try-catch-finally,也称抓抛模型。其过程分为两阶段:
- “抛”:在程序在执行过程中,当出现异常时,就会在出现异常的代码处,生成对应异常类的对象并抛出。在抛出后,程序就不在执行其后序代码。
- “抓”:针对“抛”中的异常,进行捕获并处理,此时就称为抓。当对异常进行处理后,程序可以正常运行。
try-catch-finally基本的代码段结构:
try {
// todo... 可能产生异常的代码
} catch (异常类型 e) {
// todo... 异常处理的代码
} catch (异常类型 e) {
// todo... 异常处理的代码
} finally {
// todo... 无论是否发生异常都会执行的代码
}
try-catch-finally的代码段执行流程:
- 当程序正常运行,执行到try代码块时,如果try代码块出现异常,那么会生成一个对应异常的对象并抛出。
- catch语句块进行逐一匹配抛出的异常,当匹配成功后,进入该语句块内处理,也称捕获成功。如果当前没有一个catch语句块匹配成功,即捕获失败,则会向其调用者抛出异常。
- 当try代码块、catch代码块执行完后或方法返回前,finally代码块则会执行。
try代码块注意事项:在try中声明的变量,作用域仅在try中,在其它地方无法使用。
catch代码块注意事项:如果多个异常类型满足父类的关系,则必须将子类声明在父类结构的上面,否则,编译报错。
对于异常处理方式:
- 编译时异常:在异常出现时就需要处理,否则程序将编译不通过。
- 运行时异常:在程序执行过程中,出现异常,根据异常提示调整。
finally代码块注意事项:当try代码块、catch代码块执行完后或方法返回前,finally代码块则会执行。catch代码块和finally代码块是可选的,但finally不能单独使用。
当一些资源(输入流、输出流、数据库连接、Socket连接等资源)在使用完以后,必须显式的进行关闭操作,否则,GC不会自动回收这些资源,进而导致内存泄漏。
为保证这些资源,不论是正常还是异常情况都能被关闭,必须将这些操作声明到finally中。
final、finally、finalize三者区别:
- final:用来修饰类、方法和变量,表示不可变或不可重写的特性。
- finally:是try-catch-finally语句的一部分,确保无论是否发生异常,某些代码都会执行。
- finalize:是一个在Object类中定义的方法,可以被子类重写,在对象被回收前执行清理操作,但垃圾收集器的调用时机是不确定的,所以finalize方法的执行时机也是不确定的,不推荐使用。
4. 异常处理 throws
使用格式:在方法处声明,throws 异常类型1, 异常类型2,....
throws基本的代码段结构:
方法属性 返回类型 方法名(参数列表) throws 异常类型1, 异常类型2,....{
// todo... 执行代码块
}
throws处理:
- 从编译角度看,throws通过向上抛出的方式,解决了自身的异常。
- 从异常处理角度看,throws是将自身异常抛给了调用者,并没有解决异常。
throws在父子类重写的注意事项:
- 子类重写方法throws的异常类型必须为父类方法throws异常类型的子类
- 如果父类方法没有throws异常类型,子类可以使用RuntimeException异常类型
throws 和 try-catch-finally 异常处理场景:
- 资源必须执行时,使用try-catch-finally处理
- 父子类重写时,子类可以throws父类处理过的异常类型和子异常类型;当出现父类没有处理的异常时,子类需要使用try-catch-finally自行处理。
- 当方法调用存在递进关系时,被调用的方法一般采用throws处理异常,当前方法则采用try-catch-finally处理异常。
5. 手动抛出异常 throw
在实际操作中,为了更加的符合业务场景,基于Java自有异常外,手动抛出(throw)一个指定类型的异常。
使用格式:在方法内部,throw 异常类的对象
在手动抛出异常throw的处理方式:
- 通过try-catch-finally解决异常。
- 通过throws向上层抛出异常,由调用者考虑处理异常。
throw异常,如果是运行时异常,一般可不处理;但如果是编译时异常,必须通过上述方式处理。
throw 和 throws的区别:
- throw使用在方法内,是手动的抛出一个异常,创建异常对象。
- throws使用在方法的声明处,表明向上层抛出异常,处理异常的一种方式。
6. 自定义异常
自定义异常类步骤:
- 继承当前异常类体系。一般继承于RuntimeException / Exception
- 提供对应重载的构造器。
- 声明一个全局常量,
static final long serivalVersionUID;
自定义异常类的示例:
public class BelowZeroException extends RuntimeException {
static final long serialVersionUID = 5766939L;
public BelowZeroException() {
super();
}
public BelowZeroException(String message) {
super(message);
}
}
使用自定义异常类注意事项:
- 在使用自定义异常时,是手动抛出的,
throw 自定义异常对象
。 - 如果该自定义异常是运行时异常,一般可选择不处理;但如果是编译时异常,必须要考虑如何处理此异常。
需要自定义异常类的原因:为了能够直接通过异常名称和原因定位问题,更加符合实际业务场景。
评论区