异常
异常体系继承结构
Throwable类是 Java 语言中所有错误或异常的超类,只有当对象是此类(或其子类之一)的实例时,才能通过 Java 虚拟机或者 Java throw 语句抛出。
异常是对象,而对象都采用类来定义。异常的根类是 java.lang.Throwable。关系图如下:
注意:类名 Error、Exception 和 RuntimeException 有时候容易引起混淆。这三种类都是异常,这里讨论的错误都发生在运行时。
Throwable 类是所有异常类的根。所有的 Java 异常类都直接或者间接地继承自Throwable。可以通过继承 Exception 或者 Exception 的子类来创建自己的异常类。
这些异常类可以分为三种主要类型:系统错误、编译时异常和运行时异常。
Exception
:程序本身可以处理的异常,可以通过catch
来进行捕获。Exception
又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。Error
:Error
属于程序无法处理的错误 ,我们没办法通过不建议通过catch
来进行捕获catch
捕获 。例如 Java 虚拟机运行错误(Virtual MachineError
)、虚拟机内存不够错误(OutOfMemoryError
)、类定义错误(NoClassDefFoundError
)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止
1.系统错误(Error)
- 系统錯误(system error) 是由 Java 虚拟机抛出的,用 Error 类表示。Error 类描述的是内部系统错误。这样的错误很少发生。如果发生,除了通知用户以及尽量稳妥地终止程序外,几乎什么也不能做。
OutOfMemoryError :内存耗尽 ;
NoClassDefFoundError :无法加载某个Class ;
StackOverflowError :栈溢出。
2.编译时异常(Exception)
- 编译时异常:Exception及其子类(除了RuntimeException),在编译时期抛出的异常,在编译期间检查程序是否可能会出现问题,如果可能会有,则预先防范:捕获 声明。
Exception 则是编译时异常,它可以被捕获并处理。
某些异常是应用程序逻辑处理的一部分,应该捕获并处理。例如:NumberFormatException :数值类型的格式错误;
FileNotFoundException :未找到文件;
SocketException :读取网络失败。
还有一些异常是程序逻辑编写不对造成的,应该修复程序本身。例如:NullPointerException :对某个 null 的对象调用方法或字段;
IndexOutOfBoundsException :数组索引越界。
3.运行时异常(RuntimeException)
运行时异常(runtime exception) 是用 RuntimeException 类表示的,它描述的是程序设计错误,例如,错误的类型转换、访问一个越界数组或数值错误。运行时异常通常是由 Java 虚拟机抛出的。
RuntimeException是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类,可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明,指的就是这些问题不需要提前被预防(本质上也可以的,只不过没必要),因为只有在真正运行的时候才能发现是否发生问题,一旦在运行期间发生了问题,则一般不会修正,程序直接中断
Checked Exception 和 Unchecked Exception 有什么区别?
Checked Exception 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 catch
或者throws
关键字处理的话,就没办法通过编译。
比如下面这段 IO 操作的代码:
除了RuntimeException
及其子类以外,其他的Exception
类及其子类都属于受检查异常 。常见的受检查异常有:IO 相关的异常、ClassNotFoundException
、SQLException
...。
Unchecked Exception 即 不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
RuntimeException
及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):
NullPointerException
(空指针错误)IllegalArgumentException
(参数错误比如方法入参类型错误)NumberFormatException
(字符串转换为数字格式错误,IllegalArgumentException
的子类)ArrayIndexOutOfBoundsException
(数组越界错误)ClassCastException
(类型转换错误)ArithmeticException
(算术错误)SecurityException
(安全错误比如权限不够)UnsupportedOperationException
(不支持的操作错误比如重复创建同一用户)
什么异常可以不处理,什么异常一定要处理
RuntimeException、Error 以及它们的子类都称为免检异(unchecked exception )。所有其他异常(编译时异常)都称为必检异常(checked exception), 意思是指编译器会强制程序员检査并通过 try- catch 块处理它们,或者在方法头进行声明。
在大多数情况下,免检异常都会反映出程序设计上不可恢复的逻辑错误。例如,如果通过一个引用变量访问一个对象之前并未将一个对象陚值给它,就会抛出 NullPointerException异常;如果访问一个数组的越界元素,就会抛出IndexOutOfBoundsException 异常。这些都是程序中必须纠正的逻辑错误。免检异常可能在程序的任何一个地方出现。为避免过多地使用 try-catch 块,Java 语言不强制要求编写代码捕获或声明免检异常。
Java规定:
必须捕获的异常,包括 Exception 及其子类,但不包括 RuntimeException 及其子类,这种类型的异常称为Checked Exception。
不需要捕获的异常,包括 Error 及其子类, RuntimeException 及其子类。
处理异常方式
2.1 JVM默认处理异常的方式
这种方式就是我们直接一直向上抛出之后最后就是jvm进行处理。
如果程序出现了问题,我们没有做任何处理,最终JVM 会做默认的处理,处理方式有如下两个步骤:
把异常的名称,错误原因及异常出现的位置等信息输出在了控制台
程序停止执行
2.2 try - catch方式处理异常
- 定义格式
try {
可能出现异常的代码;
} catch(异常类名 变量名) {
异常的处理代码;
}
- 执行流程
- 程序从 try 里面的代码开始执行
- 出现异常,就会跳转到对应的 catch 里面去执行
- 执行完毕之后,程序还可以继续往下执行
- 示例代码
public class ExceptionDemo01 {
public static void main(String[] args) {
System.out.println("开始"); method();
System.out.println("结束");
}
public static void method() {
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
System.out.println("这里能够访问到吗");
} catch (ArrayIndexOutOfBoundsException e) {
// System.out.println("你访问的数组索引不存在,请回去修改为正确的索引");
e.printStackTrace();
}
}
}
这种方式处理异常就是直接就在本方法中进行处理,不交给其他调用者进行处理。
2.3 throws方式处理异常
- 格式
public void 方法() throws 异常类名 {
}
这种方式处理异常方式就是我自己不处理,交给调用者进行处理,调用者如何处理呢,还是两种方式try catch 或者继续向上抛出交给其他调用者处理,如果一直抛的话,最后就是jvm最后进行处理(默认处理)。
- 示例代码
/*
throws 异常类名;
这个格式是跟在方法的括号后面的
*/
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("开始");
// method();
try {
method2();
}catch (ParseException e) {
e.printStackTrace();
}
System.out.println("结束");
}//编译时异常
public static void method2() throws ParseException {
String s = "2048-08-09";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date d = sdf.parse(s);
System.out.println(d);
}//运行时异常
public static void method() throws ArrayIndexOutOfBoundsException {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
}}
为什么要抛出异常?什么时候要抛出异常?
为什么要捕获异常?
编译时程序有可能会出现异常,如果出现异常,程序就会中断,但是你不想程序中断的话,就可以选择
捕获异常,做出处理,让程序可以运行下去。
例子:比如程序中有3行代码,第2行有异常。如果不处理第1行代码的异常,那么第3行代码就执行不了;
如果处理的第2行代码的异常后,程序就可以继续执行第3行代码。
抛出异常和捕获异常的区别?
抛出异常:如果程序出现了异常,没有办法将具体的异常打印出来,不做任何处理;程序中断。捕获异常:如果程序出现了异常,就能够详细的打印是什么原因导致了异常并且能够做出相应的处理,能够显示详细的日志,程序不中断。
编译异常和运行异常的区别?什么时候要抛出异常?
编译时异常必须要进行处理,两种处理方案:try…catch …或者 throws,如果采用throws 这种方案, 将来谁调用谁处理 ,处理用到try…catch…捕获或者抛出一层层往上抛,最终抛给虚拟机
运行时异常可以不处理,出现问题后,需要我们回来修改代码
throw和throws的区别
throws
- 用在方法声明后面,跟的是异常类名
- 表示抛出异常,由该方法的调用者来处理
- 表示出现异常的一种可能性,并不一定会发生这些异常
throw
- 用在方法体内,跟的是异常对象名
- 表示抛出异常,由方法体内的语句处理
- 执行 throw 一定抛出了某种异常
throw就是主动抛出一个异常 , 要很确信这个代码一定会发生异常,我们才会直接主动抛出去,用在方法体中。对于throws用在方法上 这是这个方法可能会抛出异常,也可能不会抛出异常,如果抛出了异常则交给我们调用者进行处理。
总结:
- throw 是语句抛出一个异常;throws 是方法抛出一个异常;
- throw语法:throw <异常对象>
- throws语法:[<修饰符>]<返回值类型><方法名>([<参数列表>])[throws<异常类>]
public class ThrowTest {
public static void main(String[] args) {
try {
// 调用声明抛出 Checked 异常的方法,要么显式捕获该异常
// 要么在 main 方法中再次声明抛出
throwChecked(-3);
} catch (Exception e) {
System.out.println(e.getMessage());
}
// 调用声明抛出 Runtime 异常的方法既可以显式捕获该异常
// 也可不理会该异常
throwRuntime(3);
}
public static void throwChecked(int a) throws Exception{
if (a > 0){
//自行抛出 Exception 异常
//该代码必须处于 try 块里,或处于带 throws 声明的方法中
throw new Exception("a 的值大于 0,不符合要求");
}
}
public static void throwRuntime(int a){
if( a > 0) {
// 自行抛出 RuntimeException 异常,既可以显式捕获该异常
// 也可完全不理会该异常,把该异常交给该方法调用者处理
throw new RuntimeException("a 的值大于 0,不符合要求");
}
}
}
throw使用限制
try 块里,或处于带 throws 声明的方法中
throw 必须是直接实现或者间接继承hrowable类的子类的对象
实现自定义异常类
1、自定义异常类继承Exception类
/**
* 自定义异常类
*/
public class MyException extends Exception {
//异常信息
private String message;//构造函数
public MyException(String message){
super(message);
this.message = message;
}//获取异常信息,由于构造函数调用了super(message),不用重写此方法
//public String getMessage(){
// return message;
//}
}
2、在要抛出异常的函数使用throws关键字
/**
* 在需要抛出异常的地方使用异常类
*/
public class UseMyException {
private String name;
private String password;public UseMyException(String name,String password){
this.name = name;
this.password = password;
}public void throwException(String password) throws MyException{
if (!this.password.equals(password)){
throw new MyException("密码不正确!");
}
}
}
3、测试,使用try-catch处理异常
/**
* 测试异常
*/
public class TestException {@org.junit.Test
public void test(){
UseMyException ex = new UseMyException("admin","123");
try{
ex.throwException("1234");
}catch (MyException me){
System.out.println("MyException:"+me.getMessage());
}
}
}