目录
🎈什么是异常
🚩算术异常
🚩数组越界异常
🚩空指针异常
🚩输入不匹配异常
🎈异常的体系结构
🎈异常的分类
🚩运行时异常(非受查异常)
🚩编译时异常(受查异常)
🎈异常的处理
🚩防御式编程
🚩事后认错型
🌈printStackTrace
🎈异常的捕获
🚩try-catch捕获并处理
🎈异常的抛出
🚩异常声明throws
🎈finally
🎈异常的处理流程
🎈自定义异常类
🚩使用throw抛出自定义异常
🚩使用try..catch抛出自定义异常
🎈什么是异常
在Java中,将程序执行过程中发生的不正常行为称为异常。
🚩算术异常
ArithmeticException
public class Abnormal {public static void main(String[] args) {int a=10;int b=0;System.out.println(a/b);}
}Exception in thread "main" java.lang.ArithmeticException: / by zeroat Abnormal.main(Abnormal.java:5)
// 实际上JVM在执行到此处的时候,会new异常对象:new ArithmeticException("/ by zero");// 并且JVM将new的异常对象抛出,打印输出信息到控制台了。
🚩数组越界异常
ArrayIndexOutOfBoundsException
public static void main(String[] args) {//数组越界异常int[] arr={0,1,2};System.out.println(arr[100]);}
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100at Abnormal.main(Abnormal.java:5)
🚩空指针异常
NullPointerException
public static void main(String[] args) {int[] arr=null;System.out.println(arr.length);}Exception in thread "main" java.lang.NullPointerExceptionat Abnormal.main(Abnormal.java:4)
🚩输入不匹配异常
InputMismatchException
- ArithmeticException 算术异常
- ArrayIndexOutOfBoundsException 越界异常
- NullPointerException 空指针异常
- InputMismatchException 输入不匹配异常
🎈异常的体系结构
第一个图展示的只是部分的子类异常,第二个图是所有包含的。
从上图中可以看到:
- 1. Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception
- 2. Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表: StackOverflowError和OutOfMemoryError,一旦发生回力乏术。
- 3. Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。我们平时所说的异常就是Exception。
🎈异常的分类
异常可能在编译时发生,也有可能在程序运行时发生,根据发生时机不同,可以分为:
🚩运行时异常(非受查异常)
- RuntimeException:运行时异常。(非受查异常)(在编写程序阶段程序员可以预先处理,也可以不管,都行。)
运行时异常都是 RuntimeException 类及其子类异常,如 NullPointerException、IndexOutOfBoundsException 等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般由程序逻辑错误引起,程序应该从逻辑角度尽可能避免这类异常的发生。
🚩编译时异常(受查异常)
- Exception的直接子类:编译时异常,(受查异常)(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)。
编译时异常是指 RuntimeException 以外的异常,类型上都属于 Exception 类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、ClassNotFoundException 等以及用户自定义的 Exception 异常,一般情况下不自定义检查异常。
class Person implements Cloneable{@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
public class Abnormal {public static void main(String[] args) {Person person=new Person();Person person1=(Person) person.clone();}
}
我们按住alt加ctrl就显示了。
class Person implements Cloneable{@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
public class Abnormal {public static void main(String[] args) throws CloneNotSupportedException {Person person=new Person();Person person1=(Person) person.clone();}
}
注意:编译时出现的语法性错误,不能称之为异常。例如将 System.out.println 拼写错了 , 写成了system.out.println. 此时编译过程中就会出错 , 这是 "编译期" 出错。而运行时指的是程序已经编译通过得到 class 文件了 , 再由 JVM 执行过程中出现的错误 .
🎈异常的处理
🚩防御式编程
错误在代码中是客观存在的. 所以要让程序出现问题的时候快速通知程序猿.
(1)LBYL 在操作之前就做充分的检查
private static int divide() {int a = 0, b = 0;Scanner scanner = new Scanner(System.in);a = scanner.nextInt();b = scanner.nextInt();if (b == 0) {System.out.println("除数为0");return 0;} else {return a / b;}}
缺点:正常流程和错误处理流程代码混在一起, 代码整体条理不清晰。
🚩事后认错型
(2)EAFP 先操作遇到问题再处理
- try…catch捕捉异常之后,后续代码可以执行(但是哪里出错误了那么跳到catch捕捉那里错误后面的代码就不执行了)。
处理异常的核心思想就是EAFP 。
public static void main(String[] args) {try {System.out.println(10/0);}catch (NullPointerException e){System.out.println("你这里出现算术异常了");}System.out.println("执行代码");}
异常处理的核心思想就是 EAFP 。在 Java 中, 异常处理主要的 5 个关键字: throw 、 try 、 catch 、 final 、 throws
🌈printStackTrace
如果多个异常的处理方式是完全相同, 也可以写成这样:
public static void main7(String[] args) {try{int[] arr={1,2,3};// arr=null;// System.out.println(arr.length);System.out.println(arr[10]);}catch (NullPointerException|IndexOutOfBoundsException|ArithmeticException e){e.printStackTrace();}System.out.println("执行后续代码");
}
我们可以 三者用|来拼接,然后调用printStackTrace方法进行检查哪一行出错误。
🎈异常的捕获
🚩try-catch捕获并处理
我们看到上述俩者报错的方式,第一种我们try catch后续代码执行,第二种直接交给JVM处理直接终止程序。
有些同学问:一次抛出俩个或者多个异常怎么办?
—————这个是错误的,不会同时抛出俩个及以上的异常。
try…catch捕捉异常之后,后续代码可以执行(但是哪里出错误了那么跳到catch捕捉那里错误后面的代码就不执行了)。
1.try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获
public static void main7(String[] args) {try{int[] arr={1,2,3};System.out.println(10/0);//算法异常arr=null;//空指针异常System.out.println(arr[10]);//越界异常} catch (NullPointerException e){e.printStackTrace();System.out.println("空指针异常");}catch (IndexOutOfBoundsException e){e.printStackTrace();System.out.println("栈溢出异常");}catch (ArithmeticException e){e.printStackTrace();System.out.println("你这里出现算法异常");}System.out.println("执行后续代码"); }
2. try块内抛出异常位置之后的代码将不会被执行
我们可以看到执行19行之后直接跳到catch()算术异常那去了。所以20行的空指针异常和21行的下标越界异常都没有执行直接跳过到catch捕捉了,所以我们不会出现俩个或者多个异常。
public static void main(String[] args) {
try {
int[] array = {1,2,3};
System.out.println(array[3]); // 此处会抛出数组越界异常
}catch (NullPointerException e){ // 捕获时候捕获的是空指针异常--真正的异常无法被捕获到
e.printStackTrace();
}
System.out.println("后序代码");
}
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
at day20210917.ArrayOperator.main(ArrayOperator.java:24)
5.一定是子类异常在前catch,父类异常在后catch,否则语法错误:
此时Exception是所有类的父类,那么 现在都交给他了,后面子类就没有意义了。所以我们需要让子类放在前面,如果子类没有,后面有个父类保底的。
🎈异常的抛出
throw new XXXException("异常产生的原因");
public class Abnormal {public static void test(int a){if(a==10){throw new NullPointerException();}}public static void main(String[] args) {test(10);}
}
还有一种是try..catch捕获
public class Abnormal {public static void test(int a){if(a==10){throw new NullPointerException();}}public static void main(String[] args) {try{test(10);}catch (NullPointerException e){e.printStackTrace();System.out.println("你这里出现异常了");}}
}
- 1. throw必须写在方法体内部
- 2. 抛出的对象必须是Exception 或者 Exception 的子类对象
受查异常就是编译异常,默认是编译异常。
- 3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理,直接交给JVM来处理
- 4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
🚩异常声明throws
处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常。
语法格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{
}public static void test3(int a) throws CloneNotSupportedException,OutOfMemoryError,StackOverflowError
public class Config {
File file;
// public void OpenConfig(String filename) throws IOException,FileNotFoundException{
// FileNotFoundException 继承自 IOException
public void OpenConfig(String filename) throws IOException{if(filename.endsWith(".ini")){throw new IOException("文件不是.ini文件");}if(filename.equals("config.ini")){throw new FileNotFoundException("配置文件名字不对");}// 打开文件
}public void readConfig(){}
}
4. 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出
🎈finally
语法格式:
try{
// 可能会发生异常的代码
}catch(异常类型 e){
// 对捕获到的异常进行处理
}finally{
// 此处的语句无论是否发生异常,都会被执行到
}
// 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行
就拿scanner来做比较吧,我们System.in进行输入,然后scanner需要一个scanner.close()进行关闭输入。
public static void main(String[] args) {Scanner scanner=new Scanner(System.in);try {int n=scanner.nextInt();}catch (InputMismatchException e){e.printStackTrace();System.out.println("输入的内容和类型不匹配");}finally {System.out.println("执行了finally代码,finally一般是用来关于资源的");scanner.close();}System.out.println("如果没有抛出异常,或者异常被处理了,try-catch后的代码也会执行");}
如果不想写scanner.close()我们需要再try里面声明。
public static void main(String[] args) {try( Scanner scanner=new Scanner(System.in)) {int n=scanner.nextInt();}catch (InputMismatchException e){e.printStackTrace();System.out.println("输入的内容和类型不匹配");}finally {System.out.println("执行了finally代码,finally一般是用来关于资源的");// scanner.close();}System.out.println("如果没有抛出异常,或者异常被处理了,try-catch后的代码也会执行");}
问题:既然 finally 和 try-catch-finally 后的代码都会执行,那为什么还要有finally呢?
public static int getDate(){Scanner sc=null;try {sc=new Scanner(System.in);int n=sc.nextInt();return n;}catch (InputMismatchException e){e.printStackTrace();}finally {System.out.println("执行了finally");}System.out.println("try-catch-finally后面的代码");if(sc!=null){sc.close();}return 0;}public static void main(String[] args) {int date=getDate();System.out.println(date);}
此时我们可以证明肯定是会执行finally的代码。所以我们给sc.close()放到finally。上述程序,如果正常输入,成功接收输入后程序就返回了,try-catch-finally之后的代码根本就没有执行,即输入流就没有被释放,造成资源泄漏。所以给sc.close()写到finally中。
注意: finally 中的代码一定会执行的,一般在 finally 中进行一些资源清理的扫尾工作 。// 下面程序输出什么? public static void main(String[] args) {System.out.println(func()); } public static int func() { try {return 10; } finally {return 20;} } A: 10 B: 20 C: 30 D: 编译失败
我们上面知道肯定会执行finally,所以结果是20.
finally 执行的时机是在方法返回之前 (try 或者 catch 中如果有 return 会在这个 return 之前执行 finally). 但是如果finally 中也存在 return 语句 , 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return.一般我们不建议在 finally 中写 return ( 被编译器当做一个警告 )
🎈异常的处理流程
public static void func1() {int[] arr = {1, 2, 3};System.out.println(arr[100]);}public static void main(String[] args) {try {func1();} catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace();}System.out.println("after try catch");}
public static void func2() {int[] arr = {1, 2, 3};System.out.println(arr[100]);}public static void main(String[] args) {func();System.out.println("after try catch");}
- 程序先执行 try 中的代码
- 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
- 如果找到匹配的异常类型, 就会执行 catch 中的代码
- 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
- 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
- 如果上层调用者也没有处理的了异常, 就继续向上传递.
- 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.
🎈自定义异常类
class Login{private String name="chenle";private int age=20;public void loginInfo(String name, int age){if(!this.name.equals(name)){System.out.println("用户名错误");return;}if(this.age!=age){System.out.println("年龄错误");return;}}
}
public class Abnormal {public static void main(String[] args) {Login login=new Login();login.loginInfo("chenle1",201);}
}
我们代码输出“用户名错误”。我们如果用异常来写呢?因为再异常中没有密码错误异常和年龄错误异常,所以这时候我们需要自定义异常。
我们对照空指针异常的源代码继承的是RuntimeException,里面有一段定义和构造。
1. 自定义异常类,然后继承自Exception 或者 RunTimeException2. 实现一个带有String类型参数的构造方法,参数含义:出现异常的原因
我们创建一个类叫 UserNameErrorException和ageErrorException继承运行异常类,然后写上俩个构成重载的构造方法。
public class UserNameErrorException extends RuntimeException{public UserNameErrorException() {super();}public UserNameErrorException(String s){super(s);}
}public class ageErrorException extends RuntimeException{public ageErrorException() {super();}public ageErrorException(String s){super(s);}
}
🚩使用throw抛出自定义异常
class Login{private String name="chenle";private int age=20;public void loginInfo(String name, int age){if(!this.name.equals(name)){throw new UserNameErrorException("用户名错误");}if(this.age!=age){throw new ageErrorException("年龄错误");}}
}
public class Abnormal {public static void main(String[] args) {Login login=new Login();login.loginInfo("chenle1",201);}
}
由于上述俩个是个单独的执行,再实际的开发中,我们需要将俩者都放在一起,这时候用到的是try..catch。
🚩使用try..catch抛出自定义异常
class Login{private String name="chenle";private int age=20;public void loginInfo(String name, int age){try{if(!this.name.equals(name)){throw new UserNameErrorException("用户名错误");}if(this.age!=age){throw new ageErrorException("年龄错误");}System.out.println("登录成功");}catch (UserNameErrorException e){e.printStackTrace();}catch (ageErrorException e){e.printStackTrace();}}
}
public class Abnormal {public static void main(String[] args) {Login login=new Login();login.loginInfo("chenle1",201);}
}
注意事项
- 自定义异常通常会继承自 Exception 或者 RuntimeException
- 继承自 Exception 的异常默认是受查异常
- 继承自 RuntimeException 的异常默认是非受查异常
新的开始了。