Java的异常处理机制
异常
异常指的是,程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
由图可知,异常的根类是throwable.其下有两个子类
Error:严重错误Error,无法通过处理的错误,只能事先避免。
Exception:由于使用不当导致,是可以避免的,异常产生后程序员可以通过代码的方式去纠正,使程序继续运行。
而我们平常所说的异常就是指Exception,而它分为;也分为两类:
• 编译时期异常:在编译时期就会检查,如果没有处理异常,则编译失败。(如日期格式化类异常)
•运行时期异常:在运行期间,去检查异常,在编译时期,运行异常不会编译器检测(不报错,如数学异常)
(一)异常产生过程的解析
下面的代码是一个简单的数组索引越界案例(运行时期异常,编译器不会提示错误)
public static int getElement(int []arr,int index){ int ele = arr[index]; return ele; }
已知传来的数组使{1,2,3},索引是3,这是时候JVM就会检测程序出现异常
JVM会做两件事:
1.JVM会根据异常产生的原因创建一个异常的对象,这个异常的对象包含了异常产生的(内容,原因,位置)。
会 new ArrayIndexOutBoundsException(“3”);
2.在getElement方法中没有异常处理逻辑(try..catch),那么JVM就会把异常对象抛出给方法的调用者mian方法去处理这个异常。
public static void main(String[] args) { int [] arr ={1,2,3}; getElement(arr,3) }
main方法接收到这个异常对象 new ArrayIndexOutBoundsException(“3”),main 方法也没有异常的处理逻辑,就会继续把对象抛出给main方法的调用者JVM来处理。
JVM接受到这个异常对象,会做两件事:
1.把异常对象(内容,原因,位置)以红色的字体打印在控制台。
2.JVM会终止正在执行的Java程序—中断处理。
(二)异常的处理
Java异常处理的五个关键字:try,catch,finally,throw,throws
throw关键字:可以使用throw关键字在指定的方法中抛出特定的异常。throw new xxxException(“异常产生的原因”);
注意:1.throw关键字必须写在方法的内部。
2.throw关键字抛出指定的异常对象,我们必须去处理这个异常对象。(如果是运行时期的异常,则默认交给JVM去处理,如果是编译期异常,我们要么继续throws往上抛异常,要么try..catch自行处理异常)。
throws关键字:当方法内部抛出异常对象时,那么我们必须去处理这个异常对象。throws关键字会把异常对象声明抛出给方法的调用者去处理。在方法声明时使用。
注意:1.throws关键字必须在方法声明时去使用
2.方法内部如果抛出了多个异常对象,那么throws后面也必须声明多个异常。
3.调用了一个声明抛出异常的方法,那么我们必须去处理声明的异常。
try..catch关键字 :try{ 可能产生异常的代码
}catch(定义一个异常的变量,用来接收try中抛出来的异常对象){异常的处理逻辑,怎么处理异常对象,一般在工作中会把异常的信息记录在一个日志当中}
注意:1.try中可能会抛出多个异常,那么可以用多个catch去接受这些异常对象。
2.处理完try..catch程序会继续执行之后代码。
Throwable中定义了一些查看异常信息的方法:
public Stirng getMessage():获取简单的错误原因。
public String toString():获取异常的类型及原因 相当于对象本身。
public void printStackTrace():打印异常的跟踪栈信息并输出到控制台。
try..catch..fianlly{无论是否出现异常都会执行,一般用来释放资源。}
import java.io.FileNotFoundException; import java.io.IOException; public class DemoException { public static void main(String[] args) { try { readFile("C:\\a.tx"); } catch (IOException e) { //System.out.println(e.getMessage());//异常信息简短 //System.out.println(e.toString());//等于e,重写Object.toString方法 e.printStackTrace(); } System.out.println("你好"); } public static void readFile(String file) throws IOException{ if (!file.equals("C:\\a.txt")){ throw new FileNotFoundException("传递的文件路径不是C:\\a.txt"); } } }
如上述代码,仔细观察,有一个重点我们需要知道!!!
在以后工作中,我们必须对方法传递过来的参数进行合法性校验,如果参数不合法,那么我们必须用抛出异常的方式,去告知方法的调用者,传递的参数有问题。
如果参数传的是数组,假设数组为空,那么我们就抛出空指针异常,告知方法的调用者“传递的数组是空”。
在这里介绍一个使用的方法,即Object非空判断
•public static <T> T requireNonNull(T obj):查看指定的对象不是null
直接在定义的方法中去调用Object.requireNonNull(T obj),来完成非空判断,并抛出异常。
特别注意的是方法的子父类异常之间的关系,总结为一句话是:父类什么样,子类什么样。
(三)自定义异常类
为什么会有自定义异常类呢?因为Java提供的异常类不够我们使用,需要自己定义一些异常类来使用。
格式:
public class xxxException extends Exception | RuntimeException{
1.添加一个空参构造方法,并且内部使用super()方法调用父类的无参构造方法。
2.添加一个带异常信息的构造方法,并调用内部使用super()方法调用父类的有参构造方法,并传递异常参数。
}
下面是一个一个案例,模拟注册过过程,如果用户名已存在,则抛出异常并提示:亲,该用户名已被注册。
分析:
1.使用数组保存已经注册过的用户名(数据库)。
2.使用Scanner获取用户输入的注册的用户名(前端/页面)。
3.定义一个方法,对用户输入的中注册的用户名进行判断
遍历已经注册过的用户名的数组,获取每一个用户名
使用获取到的用户名和用户输入的输入的用户名进行比较
true:用户名已经存在,抛出RegisterException异常。该用户名已经被注册
false:继续遍历比较。
循环结束给用户一个提示:恭喜你注册成功。
代码如下:
自定义异常类:
public class RegisterException extends Exception { RegisterException(){ super(); } RegisterException(String message){ super(message); } }
注册用户代码:
import java.util.ArrayList; import java.util.Collections; import java.util.Scanner; public class Register { public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); Collections.addAll(list,"张三","李四","王五"); Scanner sc = new Scanner(System.in); System.out.println("欢迎注册,请输入用户名:"); String user = sc.next(); try { checkUser(user,list); } catch (RegisterException e) { System.out.println(e.getMessage()); } } public static void checkUser(String user,ArrayList<String> list) throws RegisterException{ for (String name:list){ while(user.equals(name)){ throw new RegisterException("亲,用户名已经存在");//自定义异常类继承Exception异常类属于编译期异常 } } list.add(user); System.out.println("亲,注册成功"); System.out.println("已经注册的用户:"+list); } }
那么Java异常就学习到这了~~~