异常是指程序在运行过程中出现的非正常情况,如用户输入错误,除数为零,文件不存在,数组下标越界等。
Error:表示程序无法恢复的严重错误或者恢复起来比较麻烦的错误,例如内存溢出、动态链接失败、虚拟机错误等。应用程序不应该主动抛出这种类型的错误,通常由虚拟机自动抛出。如果出现这种错误,最好的处理方式是让程序安全退出。在进行程序设计时,我们更应关注Exception类。
Exception 类:由Java应用程序抛出和处理的非严重错误,例如文件未找到、网络连接问题、算术错误(如除以零)、数组越界、加载不存在的类、对空对象进行操作、类型转换异常等。Exception类的不同子类对应不同类型的异常。Exception类又可分为两大类异常:
不受检异常(非检查异常)
也称为unchecked异常,包括RuntimeException及其所有子类。对这类异常并不要求强制进行处理,例如算术异常ArithmeticException等。
受检异常(检查异常)
也称为checked异常,指除了不受检异常外,其他继承自Exception类的异常。对这类异常要求在代码中进行显式处理。
不受检异常又称运行时异常,指编码在运行时出现异常(不需要强制处理)
受检异常又称编译时异常,指在编译过程中出现的异常,通常需要处理
受检异常需要在方法声明中显式声明,并且需要调用方处理
非受检异常不需要显式声明,也不需要强制处理。
受检异常通常表示程序可能遇到的可预测的错误情况,需要在代码中进行处理
非受检异常通常表示程序中的错误或逻辑异常,由程序员编码错误或错误的使用API导致。
调用一个声明了非受检异常的方法时,可以选择捕获并处理,也可以将其传播到调用堆栈的更高层。
Exception
类,非受检异常继承自RuntimeException
类或其子类。异常类名 | 异常分类 | 说明 |
---|---|---|
Exception | 设计时异常 | 异常层次结构的根类。 |
IOException | 设计时异常 | IO异常的根类,属于非运行时异常。 |
FileNotFoundException | 设计时异常 | 文件操作时,找不到文件。属于非运行时异常。 |
RuntimeException | 运行时异常 | 运行时异常的根类,RuntimeException及其子类,不要求必须处理。 |
ArithmeticException | 运行时异常 | 算术运算异常,比如:除数为零,属于运行时异常。 |
IllegalArgumentException | 运行时异常 | 方法接收到非法参数,属于运行时异常。 |
ArrayIndexOutOfBoundsException | 运行时异常 | 数组越界访问异常,属于运行时异常。 |
NullPointerException | 运行时异常 | 尝试访问null对象的成员时发生的空指针异常,属于运行时异常 。 |
ArrayStoreException | 运行时异常 | 数据存储异常,写数组操作时,对象或数据类型不兼容 |
ClassCastException | 运行时异常 | 类型转换异常 |
IllegalThreadStateException | 运行时异常 | 试图非法改变线程状态,例如试图启动一已经运行的线程 |
NumberFormatException | 运行时异常 | 数据格式异常,试图把一字符串非法转换成数值 |
Checked异常:编译时异常,运行之前就报错了
UnChecked异常:运行时异常,运行之后才可能能抛出的异常
Java的异常处理机制类似于人们对可能发生的意外情况进行预先处理的方式。在程序执行过程中,如果发生了异常,程序会按照预定的处理方式对异常进行处理。处理完异常之后,程序会继续执行。如果异常没有被处理,程序将终止运行。
Java的异常处理机制依靠以下5个关键字:try、catch、finally、throw、throws。这些关键字提供了两种异常处理方式。
public class Main {
public static void main(String[] args) {
try {
int i = 1, j = 0, res;
System.out.println("begin");
res = i / j;
System.out.println("end");
} catch (ArithmeticException e) {
System.out.println("caught");
e.printStackTrace();
} finally {
System.out.println("finally");
}
System.out.println("over");
}
}
输出结果输出了“finally”。如果将j的值改为不是0,再次运行该程序,也会输出“finally”
try-catch-finally语句块执行流程大致分为如下两种情况。
1.如果try语句块中所有语句正常执⾏完毕,程序不会进⼊catch语句块执⾏,但是finally语句块会被执⾏。
2.如果try语句块在执⾏过程中发⽣异常,程序会进⼊到catch语句块捕获异常, finally语句块也会被执⾏。
3.try-catch-finally结构中try语句块是必须存在的,catch、finally语句块为可选,但两者⾄少出现其中之⼀。
即使在catch语句块中存在return语句,finally语句块中的语句也会执行。发生异常时的执行顺序是,先执行catch语句块中return之前的语句,再执行finally语句块中的语句,最后执行catch语句块中的return语句退出。
异常一旦被捕获,就相当于没有异常了,因此程序可以继续向后运行
用于确保释放资源,就是调用close()方法(当try块执行结束后就会调用try()括号中对象的close()方法来确保关闭资源)
try with resource代码示例
示例
public static void main(String[] args) {
// try块执行结束后,自动调用scanner对象的close()方法,确保关闭scanner对象,释放资源,也就是释放内存
try(Scanner scanner = new Scanner(System.in)){
String name = scanner.next();
System.out.println(name);
}
}
Java中提供了try-catch结构进行异常捕获和处理,把可能出现异常的代码放在try语句块中,并使用catch语句块
捕获异常。
try-catch语句块首先执行try语句块中的语句,这时可能会出现以下3种情况:
1.如果try语句块中的所有语句正常执⾏完毕,没有发⽣异常,那么catch语句块中的所有语句都将被忽略。
2.如果try语句块在执⾏过程中发⽣异常,并且这个异常与catch语句块中声明的异常类型匹配,那么try语句块中剩下的代码都将被忽略,⽽相应的catch语句块将会被执⾏。匹配是指catch所处理的异常类型与try块所⽣成的异常类型完全⼀致或是它的⽗类。
3.如果try语句块在执⾏过程中发⽣异常,⽽抛出的异常在catch语句块中没有被声明,那么程序⽴即终⽌运⾏,
程序被强迫退出。
4.catch语句块中可以加⼊⽤⼾⾃定义处理信息,也可以调⽤异常对象的⽅法输出异常信息,常⽤的⽅法如下
void prinStackTrace() :输出异常的堆栈信息。堆栈信息包括程序运行到当前类的执行流程,它将输出从方法
调用处到异常抛出处的方法调用的栈序列。
String getMessage() :返回异常信息描述字符串,该字符串描述了异常产生的原因,是 printStackTrace() 输
出信息的一部分。
try-catch-finally语句块组合使用时,无论try块中是否发生异常,finally语句块中的代码总能被执行。
使用try-catch-finally捕获并处理异常。
1.如果try语句块中所有语句正常执⾏完毕,程序不会进⼊catch语句块执⾏,但是finally语句块会被执⾏。
2.如果try语句块在执⾏过程中发⽣异常,程序会进⼊到catch语句块捕获异常, finally语句块也会被执⾏。
3.try-catch-finally结构中try语句块是必须存在的,catch、finally语句块为可选,但两者⾄少出现其中之⼀。
即使在catch语句块中存在return语句,finally语句块中的语句也会执行。发生异常时的执行顺序是,先执
行catch语句块中return之前的语句,再执行finally语句块中的语句,最后执行catch语句块中的return语
句退出。
finally语句块中语句不执行的唯一情况是在异常处理代码中执行了 System.exit(1) ,退出Java虚拟机
当一段代码可能引发多种类型的异常时,可以在一个try语句块后面跟随多个catch语句块,分别处理不同类型的
异常。
catch语句块的排列顺序必须是从子类到父类,最后一个一般是Exception类。这是因为在异常处理中,
catch语句块会按照从上到下的顺序进行匹配,系统会检测每个catch语句块处理的异常类型,并执行第一个与异常类型匹配的catch语句块。如果将父类异常放在前面,子类异常的catch语句块将永远不会被执行,因为父类异常的catch语句块已经处理了异常。
一旦系统执行了与异常类型匹配的catch语句块,并执行其中的一条catch语句后,其后的catch语句块将被忽略,
程序将继续执行紧随catch语句块的代码。
总结起来,异常处理的顺序和匹配原则非常重要。子类异常应该放在前面,父类异常应该放在后面,以确保正确
的异常处理顺序。
异常处理有三种方式(第二种和第三种本质上是同一种)
try-catch-finally处理的是在一个方法内部发生的异常,在方法内部直接捕获并处理。如果在一个方法体内抛出了
异常,并希望调用者能够及时地捕获异常,Java语言中通过关键字throws声明某个方法可能抛出的各种异常,以
通知调用者。throws可以同时声明多个异常,之间用逗号隔开。
Java语言中,可以使用throw关键字来自行抛出异常。
如果 throw 语句抛出的异常是 Checked 异常,则该 throw 语句要么处于 try 块⾥,显式捕获该异常,要么放在⼀个带 throws 声明抛出的⽅法中,即把该异常交给该⽅法的调⽤者处理;
如果 throw 语句抛出的异常是 Runtime 异常,则该语句⽆须放在 try 块⾥,也⽆须放在带 throws 声明抛出的⽅法中;程序既可以显式使⽤ try…catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给该⽅法调⽤者处理
自行抛出Runtime 异常比自行抛出Checked 异常的灵活性更好。同样,抛出 Checked 异常则可以让编译器提醒
程序员必须处理该异常。
1.作⽤不同:throw⽤于程序员⾃⾏产⽣并抛出异常,throws⽤于声明该⽅法内抛出了异常。
2.使⽤位置不同:throw位于⽅法体内部,可以作为单独的语句使⽤;throws必须跟在⽅法参数列表的后⾯,不能单独使⽤。
3.内容不同:throw抛出⼀个异常对象,只能是⼀个;throws后⾯跟异常类,可以跟多个。
⾃定义异常
⾃定义异常可能是编译时异常,也可以是运⾏时异常
1.如果自定义异常类继承Excpetion,则是编译时异常。
特点:方法中抛出的是编译时异常,必须在方法上使用throws声明,强制调用者处理。
2.如果自定义异常类继承RuntimeException,则运行时异常。
特点:方法中抛出的是运行时异常,不需要在方法上用throws声明。
异常链:有时候我们会捕获一个异常后再抛出另一个异常
顾名思义就是将异常发生的原因一个传一个串起来,即把底层的异常信息传给上层,这样逐层抛出。
// 异常处理
// Throwable
// Error Exception
// RuntimeException 其他
// RuntimeException 运行时异常 (unchecked) 剩余其他编译时异常(checked)
// catch 不一定被执行 finally 不管有没有异常一定会被执行,
// try-catch try -finally try-catch-finally
// finally 不被执行的唯一情况是 执行了System.exit(1) 退出虚拟机
// 运行时异常可以不用,编译时异常必须处理
// try-catch 方法内部处理异常
// try {可能出现异常的代码块} catch(异常类 变量名) {}
// try-catch-finally 方法内部处理异常
// try-catch-catch...-finally 方法内部处理异常
// catch里面的类别是先子后父,一定要注意书写顺序
// 访问修饰符 返回值类型 方法名(参数列表) throws 异常类型{方法体}
// throws 在方法声明处抛出异常 代码自动抛出的异常 让方法调用者去处理 运行时异常可以不用,编译时异常必须处理
// throw 在方法体抛出 可以抛出自己设定的条件的异常 可以自定义异常信息 运行时异常本方法不用处理,编译时异常本方法要处理,要么在try-catch 要么 throws
}
构造方法可以声明并抛出异常,就像其他方法一样。当构造方法可能会遇到异常情况,且需要调用方处理时,可以使用throws
关键字在构造方法的声明中指定异常。
对于子类,当子类继承了一个声明了异常的父类构造方法时,子类的构造方法可以选择继续声明相同的异常,也可以声明父类异常的子类异常,或者不声明任何异常。如果子类构造方法声明了异常,那么在子类实例化时,必须处理这些异常或将其继续向上抛出。
构造方法可能会抛出异常的情况包括但不限于:
依赖外部资源的构造方法,例如读取文件、连接数据库等,可能会抛出I/O异常或数据库异常。
构造方法中进行的操作可能会导致逻辑错误,例如传入的参数无效或不符合预期,可能会抛出自定义的业务异常。
构造方法中调用其他方法或外部API,这些方法或API可能会抛出异常,构造方法可以选择捕获并处理这些异常,或者将其继续向上抛出。
在使用构造方法时,如果构造方法可能会抛出异常,需要在代码中适当处理这些异常,以确保程序的正常运行,并提供必要的错误处理和容错机制。
throw
和throws
是Java中处理异常的关键字,它们有什么区别,区别:
throw
关键字用于抛出一个异常对象。它通常在代码块内部使用,用于手动触发异常的发生。
通过throw
关键字,我们可以将特定的异常对象抛出,并将其传递给调用方或者被捕获并处理。
throws
关键字用于在方法声明中指定可能抛出的异常。它通常在方法签名的尾部使用,用于告诉调用方该方法可能会抛出指定的异常。在方法内部,如果有代码可能会引发被throws
声明的异常,可以选择捕获并处理这些异常,或者将其继续向上抛出。
throws
一个Error
,是可以的。Error
是Throwable
类的子类,它表示严重的错误情况,通常是由系统或虚拟机引起的,例如内存溢出、栈溢出等。与受检异常不同,Error
和它的子类是非受检异常,不需要在方法声明中显式声明,也不需要强制处理。通常情况下,我们不会故意throws
一个Error
,因为它表示系统或虚拟机的错误,我们很少能够有效地处理它们。
try,catch,finally里都有return,为什么最后返回finally里return的值
finally
块中的代码在try
块或catch
块中的代码执行完毕后,无论是否发生异常,都会执行。
当finally
块中存在return
语句时,它会覆盖try
块或catch
块中的return
语句,最终返回finally
块中的return
语句的返回值。
这是因为在执行return
语句时,首先会将返回值保存在一个临时位置,然后执行finally
块中的代码。最后,无论finally
块中是否存在return
语句,都会将之前保存的返回值返回给调用方。
需要注意的是,在finally
块中使用return
语句可能会导致一些逻辑上的问题,因为finally
块中的return
语句可能会改变原本的逻辑流程。因此,在使用finally
块时,需要仔细考虑逻辑的正确性和一致性。
自定义异常是为了更好地满足程序的需求和业务逻辑,提供更加清晰和有意义的异常信息,以及更好的异常处理和容错机制。
示例
// 自定义异常
class MyException extends Exception{
public MyException(String message){
// 通过构造函数将异常描述信息传递给异常父类
super(message);
}
}
public class UserController {
public static void main(String[] args) throws MyException
{
int i=1 ,j = 0;
if(j ==0){
// 将自定义异常抛给虚拟机,让虚拟机去处理
throw new MyException("除数不能为0");
}
System.out.println("i / j = "+ i/j);
}
}
运行结果:
atic void main(String[] args) throws MyException
{
int i=1 ,j = 0;
if(j ==0){
// 将自定义异常抛给虚拟机,让虚拟机去处理
throw new MyException(“除数不能为0”);
}
System.out.println("i / j = "+ i/j);
}
}
运行结果:
[外链图片转存中...(img-9FDt5miB-1695129631742)]
## **方法结束运行的三种情况**
- 方法中的代码执行完毕时,方法结束运行
- 方法中执行return语句后,方法结束运行
- 方法中抛出异常后,方法结束运行
[外链图片转存中...(img-2XI3rnem-1695129631743)]