一、异常
- 程序在运行过程中,由于意外情况导致程序发生异常事件,默认情况下发生的异常会中断程序的运行。
- 在Java中,把常见的异常情况,都抽象成了对应的异常类型,那么每种异常类型都代表了一种特定的异常情况。
- 当程序中出现一种异常情况时,也会创建[默认情况是JVM自动创建]并抛出一个异常类型对象,这个对象就表示当前程序所出现的问题。
1.异常体系
- 异常体系中的根类是:
java.lang.Throwable
Throwable
表示可以被抛出的
- 根类下面有俩个子类型:
java.lang.Error
Error
,表示错误情况,一般是程序中出现了比较严重的问题,并且程序自身并无法进行处理。
java.lang.Exception
Exception
,表示异常情况,程序中出了这种异常,大多是可以通过特定的方式进行处理和纠正的,并且处理完了之后,程序还可以继续往下正常运行。Exception
中并没有定义方法,它的方法都是从Throwable
中继承过来的,
Exception
常用的方式有:
printStackTrace()
,打印输出当前发送异常的详细信息(重要)getMessage()
,返回异常对象被抛出的时候,所携带的信息,一般是异常的发生原因(重要)printStackTrace(PrintWriter s)
,方法重载,可以指定字符输出流,对异常信息进行输出printStackTrace(PrintStream s)
,方法重载,可以指定字节输出流,对异常信息进行输出
2. 异常的种类和传播
(1)异常的种类
我们平时使用的异常类型,都是 Exception 类的子类型,它们把异常划分成了俩种:
- 编译时异常(受检异常),继承自
Exception
类,也称为checked exception,编译器在编译期间,会主动检查这种异常,发现后会报错,并提示我们要对这种异常进行处理。 - 运行时异常(非受检异常),继承自
RuntimeException
类,也称为unchecked exception,编译器在编译期间,不会检查这种异常,也不要求我们去处理,但是在运行期间,代码中可能会抛出这种类型的异常。
(2)异常传播
- 如果一个方法中抛出了异常,并且一直没有进行处理,那么这个异常将会抛给当前方法的调用者,并一直向上抛出,直到抛给JVM,最后JVM将这个异常信息打印输出,同时程序运行的停止。
- 如果,在异常传播的过程中,任何一个地方对异常进行了处理,那么JVM不会停止,程序还会正常往下运行!
二、异常处理——抛出
1.自动抛出异常
当前java代码中,出现了提前指定好的异常情况的时候,代码会自动创建异常对象,并且将该异常对象抛出。
- 算术异常
- 当代码中执行
int a = 1/0;
的时候,代码会自动创建并抛出ArithmeticException
类型的异常对象,来表示当前的这种异常情况。
- 当代码中执行
- 空指针异常
- 当前代码中执行
String str = null; str.toString();
的时候,代码会自动创建并抛出NullPointerException
类型的异常对象,来表示当前这种异常情况。
- 当前代码中执行
2.手动抛出异常
(1)运行时异常
方法中抛出的是一个运行时异常,编译器不会做出检查,所以代码可以正常的编译运行,但是运行的时候,name的值不是tom的时候,代码会报错,这个错误信息是我们自己抛出的
public void test(String name){
if(!"tom".equals(name)){
throw new RuntimeException("用户名和预期不符!");
}
}
(2)编译异常
- 方法中抛出的异常是编译异常,编译器会做检查,所以代码编译会报错,提示我们需要在test方法上声明出方法内抛出异常的类型,或者在方法内对这个异常进行处理!
public void test(String name){
if(!"tom".equals(name)){
throw new Exception("用户名和预期不符!");
}
}
ClassNotFoundException
属于编译异常,所以我们调用 forName 方法时候就要处理这个异常,或者将异常继续抛出!
public final class Class{
public static Class<?> forName(String className)throws ClassNotFoundException{ //... }
}
(3)使用throws
关键字,声明方法所抛出的异常类型
这个声明的目的,就是告诉test方法的调用者,你调用我的这个test方法的时候要小心啦,方法在运行的时候可能会抛出Exception类型的异常.。
方法内抛出异常对象使用关键字throw,方法上声明异常使用的是throws
public void test(String name)throws Exception{
if(!"tom".equals(name)){
throw new Exception("用户名和预期不符!");
}
}
三、异常处理——捕获
1.概述
当一个方法内,抛出了编译异常的时候,编译器在编译期间检查到,就会报错,提示我们有俩种修改方案:
- 把这个异常在方法上进行声明抛出
public void test(String className)throws ClassNotFoundException{
Class.forName(className);
}
- 把这个异常在方法内进行捕获处理
public void test(String className){
try {
Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
2. try-catch
- try-catch 语句块,就是用来对指定代码,进行异常捕获处理,并且处理完成后,JVM不会停止运行,代码还可以正常的往下运行!
- try:该代码块中编写可能产生异常的代码。
- catch:用来进行某种异常的捕获,并对捕获到的异常进行处理。
try{
编写可能会出现异常的代码
}catch(异常类型 e){
//处理异常的代码,可以是简单的输出异常信息,也可以使用日志进行了记录,也可以对数据进行修改纠正等操作
}
3.捕获多种异常
如果try语句块中的多句代码,都会抛出异常,并且是不同类型的异常,那么catch语句块就有不同的写法,来处理这几个不同类型的异常。
(1)使用一个catch语句,里面使用|
来表示捕获多种不同的异常类型
public static void main(String[] args) {
String className = "com.briup.demo.Student";
String methodName = "sayHello";
try {
//forName声明抛出ClassNotFoundException
Class c = Class.forName(className);
//getMethod方法声明抛出NoSuchMethodException
Method m = c.getMethod(methodName);
//invoke方法声明抛出IllegalAccessException和InvocationTargetException
m.invoke(null);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
(2)使用了四个catch语句,分别对四种不同的异常类型进行捕获处理
- 这种异常处理方式,要求多个catch中的异常不能相同,并且如果catch中的多个异常之间有子父类异常的关系的话,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。
- 因为如果父类型异常再最上面的话,下面catch语句代码,永远不会被执行
public static void main(String[] args) {
String className = "com.briup.demo.Student";
String methodName = "sayHello";
try {
//forName声明抛出ClassNotFoundException
Class c = Class.forName(className);
//getMethod方法声明抛出NoSuchMethodException
Method m = c.getMethod(methodName);
//invoke方法声明抛出IllegalAccessException和InvocationTargetException
m.invoke(null);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
(3)使用了一个catch语句,但是捕获的异常类型是Exception
,它最大的异常类型,由于多态的原因,Exception类型的引用e,可以捕获接收到任意类型的异常对象
public static void main(String[] args) {
String className = "com.briup.demo.Student";
String methodName = "sayHello";
try {
//forName声明抛出ClassNotFoundException
Class c = Class.forName(className);
//getMethod方法声明抛出NoSuchMethodException
Method m = c.getMethod(methodName);
//invoke方法声明抛出IllegalAccessException和InvocationTargetException
m.invoke(null);
} catch (Exception e) {
e.printStackTrace();
}
}
4.finally语句
(1)try-catch语句块,虽然可以捕获并处理异常情况,但是它也会改变代码的执行流程
public class Test {
public static void main(String[] args) {
System.out.println("hello");
Test t = new Test();
try {
t.test("zs");
System.out.println("briup"); //注意观察,这句代码是否执行
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("world");
}
public void test(String name)throws Exception{
if(!"tom".equals(name)){
throw new Exception("用户名不正确");
}
}
}
//运行结果:
hello
world
java.lang.Exception: 用户名不正确
at com.briup.demo.Test.test(Test.java:20)
at com.briup.demo.Test.main(Test.java:9)
(2)只要使用 finally 关键,就可以保证指定代码一定会执行,无论是否发生异常!
public class Test {
public static void main(String[] args) {
System.out.println("hello");
Test t = new Test();
try {
t.test("zs");
} catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("briup");
//这句代码一定会被执行
}
System.out.println("world");
}
public void test(String name)throws Exception{
if(!"tom".equals(name)){
throw new Exception("用户名不正确");
}
}
}
//执行结果:
hello
briup
world
java.lang.Exception: 用户名不正确
at com.briup.demo.Test.test(Test.java:21)
at com.briup.demo.Test.main(Test.java:6)