Java 异常
1. 异常概念
异常:指的是在程序执行过程中,出现的非正常情况,最终会导致JVM的非正常停止。
在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出一个异常对象。Java处理异常的方式是中断处理。
注意:异常指的并不是语法错误。语法错了,编译不通过,不会产生字节码文件,根本不能运行。
2. 异常体系
异常的根类是java.lang.Throwable,其下有两个子类:
java.lang.Error与java.lang.Exception,平常说的异常就是java.lang.Exception。
3. 异常分类
- java.lang.Throwable类是java语言中所有错误或异常的超类。
- Exception:编译器异常,进行编译(写代码)java程序出现的问题。
- public class RuntimeException extends Exception
- RuntimeException:运行期异常,java程序运行出现的问题。
- 示例:
public class DemoException {
public static void main(String[] args) /*throws ParseException*/ {
/*SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = sdf.parse("19991013");
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(date);*/
int[] arr = {1, 2, 3};
try {
System.out.println(arr[3]);
}catch (Exception e){
System.out.println(e);
}
System.out.println("================");
// int[] arr2 = new int[1024*1024*1024]; // OutOfMemoryError
}
}
4. 异常的产生过程解析
以下面这段代码示例:
public class DemoExceptionParse{
public static void main(String [] args){
// 创建int数组,并赋值
int[] arr = {1, 2, 3};
int e = getElement(arr, 3);
System.out.println(e);
}
/*
定义一个方法返回数组指定索引处的元素
参数:
int[] arr
int index
*/
public static int getElement(int[] arr, int index){
return arr[index];
}
}
运行结果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at com.my.package7.DemoExceptionParse.getElement(DemoExceptionParse.java:10)
at com.my.package7.DemoExceptionParse.main(DemoExceptionParse.java:6)
分析:
一、访问了数组中的3索引,而数组是没有3索引的,JVM就会检测出程序出现异常。
JVM会做两件事:
- JVM会根据异常产生的原因,创建一个异常对象,这个异常对象包含了异常产生的(内容,原因,位置)
new ArrayIndexOutOfBoundsException("3");
- 在getElement方法中,没有异常的处理逻辑(try catch),那么JVM就会把异常对象抛给方法的调用者,也就是main方法来处理这个异常。
二、main方法接受到了这个异常对象,如果main方法也没有处理这个异常的逻辑,如果没有会继续把这个异常对象抛给main方法的调用者JVM处理。
三、JVM接受到这个异常对象,做了两件事:
- 把异常对象(内容,原因,位置)以红色字体打印在控制台。
- JVM会终止当前正在执行的java程序 --> 中断处理
简而言之:
- 方法把异常对象抛给main方法
- main方法再把异常对象抛给JVM
- JVM将异常对象的内容,原因,位置打印在控制台,并中断当前程序的运行。
5. 异常的处理
java异常处理的五个关键字:try、catch、finally、throw、throws
throw
- 作用:可以使用throw关键字在指定的方法中抛出指定的异常。
- 格式:`throw new xxxException("异常产生的原因");
注意事项:
- throw关键字必须写在方法的内部。
- throw关键字后边new的对象必须是Exception或者Exception的子类对象。
- throw关键字抛出指定的异常对象,我们就必须处理这个异常对象。
- throw关键字后边创建的是RuntimeException或者RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
- throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws,要么try...catch。
- 示例
public class Demo03Throw{
public static void main(String [] args){
int[] arr = new int[3];
int e = getElement(arr, 3);
System.out.println(e);
}
public static int getElement(int[]arr, int index){
if (arr == null){
throw new NullPointerException("传递的数组的值为null");
}
if (index < 0 || index >= arr.length){
throw new ArrayIndexOutOfBoundsException("传递的索引超出数组的使用范围");
}
return arr[index];
}
}
Objects非空判断 requireNonNull方法
-
public static
T requireNonNull(T Obj):查看指定对象不是null -
源码:
public static <T> T requireNonNull(T obj){
if (obj == null){
throw new NullPointerException();
}
return obj;
}
- 示例:
import java.util.Objects;
public class Demo04Objects{
public static void main(String [] args){
method(null);
}
public static Object method(Object obj){
/* if (obj == null){
throw new NullPointerException("传递的对象值为null");
}*/
Objects.requireNonNull(obj,"传递的对象值为null");
return obj;
}
}
声明异常throws
-
throws关键字:异常(编译)处理的第一种方式,交给别人处理。
-
作用:当方法内部抛出异常对象时,我们就必须处理这个异常对象,可以使用 throws关键字处理异常对象,会把异常对象声明抛出给方法的调用者(自己不处理,交给别人),最终交给JVM处理 --> 中断处理。
-
格式:(在方法声明时使用)
修饰符 返回值类型 方法名称 (参数列表) throws AAAException, BBBException ... {
throw new AAAException("产生原因");
throw new BBBException("产生原因");
...
}
注意事项:
- throws关键字必须写在方法的声明处。
- throws关键字后边声明的异常必须是 Exception或者Exception的子类。
- 方法内部如果抛出了多个异常对象,那么 throws后边必须也声明多个异常。
如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可,但是对使用的子类异常还是要导包的。
- 调用了一个声明抛出异常的方法,我们就必须处理声明的异常.
要么继续使用 throws声明抛出,交给方法的调用者,最终交给 JVM处理.
要么 try ... catch 自己处理.
- 示例:
/*FileNotFoundException extends IOException extends Exception*/
import java.io.IOException;
import java.io.FileNotFoundException;
public class Demo05Throws {
public static void main(String [] args) throws Exception{
readFile("c:\a.docx");
}
public static void readFile(String fileName) throws Exception{
/*if (!fileName.equals("c:\a.txt")){
throw new FileNotFoundException("文件路径c:\a.txt没有找到");
}*/
if (!fileName.endsWith(".txt")){
throw new IOException("文件名后缀不是.txt");
}
System.out.println("读取文件成功");
}
}
捕获异常 try ... catch
-
try ... catch:异常处理的第二种方式,自己处理.
-
格式:
try {
可能产生异常的代码
} catch (异常类型 变量名称) {
异常的处理逻辑
} catch (异常类型 变量名称) {
异常的处理逻辑
}
...
注意事项:
- try 中可能会抛出多个异常对象,那么就可以使用多个 catch来捕获这些异常对象.
- 如果 try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕后继续执行 try...catch后面的代码.
- 如果 try中没有产生异常,那么就不会执行catch中的异常处理逻辑,而是执行 try...catch后面的代码.
- 示例:
import java.io.IOException;
public class Demo06TryCatch{
public static void main(String [] args){
try{
readFile("c:\a.docx");
}catch (IOException/*Exception*/ e){
System.out.println("传递的文件名后缀不是.txt");
}
System.out.println("后续代码");
}
public static void readFile(String fileName) throws IOException/*Exception*/{
if (!fileName.endsWith(".txt")){
throw new IOException("文件名后缀不是.txt");
}
System.out.println("读取文件成功");
}
}
Throwable类中3个异常处理的方法
- String getMessage():返回此 Throwable的简短描述.
- String toString():返回此 Throwable的详细消息字符串.
- void printStackTrace():JVM打印异常对象,默认此方法,打印的异常信息是最全面的.
finally 代码块
finally:有一些特定的代码,无论异常是否发生,都需要执行,另外,因为异常会引发程序跳转,导致有些语句执行不到.而finally语句就是为了解决这个问题的,在finally代码块中存放的代码是一定会被执行到的.
- 格式:
try {
可能产生异常的代码
} catch (异常类型 变量名称) {
异常的处理逻辑
}
...
catch (异常类型 变量名称) {
异常的处理逻辑
} finally{
无论是否出现异常都会执行
}
注意事项:
- finally 不能单独使用,必须和 try一起使用。
- finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放(IO)。
6. 异常注意事项
多异常的捕获处理
- 多个异常使用捕获又该如何处理?
- 多个异常分别处理。
- 多个异常一次捕获,多次处理。
- 多个异常一次捕获,一次处理。
一般我们使用一次捕获,多次处理的方式。
- 示例:
// 1. 多个异常分别处理
try{
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
} catch (ArrayIndexOutOfBoundsException e){
System.out.println(e);
}
try {
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3);
System.out.println(list.get(3));
} catch (IndexOutOfBoundsException e) {
System.out.println(e);
}
// 2. 多个异常,一次捕获,多次处理
try {
int [] arr = {1, 2, 3};
// System.out.println(arr[3]);
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3);
System.out.println(list.get(3));
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e);
} catch (IndexOutOfBoundsException e){
System.out.println(e);
}
// 3. 多个异常,一次捕获,一次处理
try {
int [] arr = {1, 2, 3};
//System.out.println(arr[3]);
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3);
System.out.println(list.get(3));
} catch (Exception /*IndexOutOfBoundsException*/ e) {
System.out.println(e);
}
/* java.lang.ArrayIndexOutOfBoundsException
List<Integer> list = List.of(1, 2, 3);
System.out.println(list.get(3)); */
- 一个 try 多个 catch注意事项:
catch里边定义的异常变量,如果有子父类关系,那么子类的异常变量必须写在父类异常变量的上边,否则就会报错。
ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException extends Exception
- 运行时异常被抛出可以不处理,即不捕获处理也不声明抛出。默认给虚拟据 JVM处理,终止程序
finally中有return语句
如果 finally中有 return语句,永远返回finally中的结果,避免该情况。
子父类异常
父类异常时什么样,子类异常就什么样。
- 如果父类抛出了多了异常,子类覆盖父类方法时,只能抛出相同的异常或者它的子集。
- 父类方法没有抛出异常,子类覆盖父类该方法时也不可抛出异常,此时子类产生该异常,只能捕获处理,不能声明抛出。
7. 自定义异常
- 格式:
修饰符 class 类名称Exception extends Exception / RuntimeException {
添加一个空参的构造方法
添加一个带异常信息的构造方法
}
注意事项:
- 自定义异常类一般都以 Exception结尾,说明该类是一个异常类。
- 自定义异常类必须继承 Exception 或者 RuntimeException
Exception:编译期异常,必须处理异常,要么 throws,要么 try ... catch。
RuntimeException:运行期异常,无需处理,交给 JVM虚拟机处理即可。
- 练习
/*
模拟注册操作,如果用户名已经存在,则抛出异常并提示:亲,您的用户名已经被注册了
分析:
1, 创建一个String类型数组用来存储已经注册的用户名(数据库)
2. 使用Scanner类获取用户输入的用户名(前端,页面)
3. 使用一个方法,遍历已经存在的存储用户名的数组,判断是否存在
*/
import java.util.Scanner;
public class Demo01ExceptionPractise {
static String[] userNames = {"聂离", "杜泽", "周元"};
public static void main(String[] args) /*throws RegisterException*/ {
Scanner sc = new Scanner(System.in);
System.out.println("请输入您要注册的用户名:");
String username = sc.next();
checkUseName(username);
}
/*
用来判断输入的用户名是否已经被注册
true:抛出RegisterException, 输出:“亲,该用户名已经被注册了”
false:输出:“恭喜您,注册成功”
*/
public static void checkUseName(String username) /*throws RegisterException*/ {
for (String name : userNames) {
if (name.equals(username)){
try {
throw new RegisterException("亲,该用户名已经被注册了");
} catch (RegisterException e) {
e.printStackTrace();
return;
}
}
}
System.out.println("恭喜您,注册成功”");
}
}