目录
一、异常是什么
1.异常的概念
2.异常的分类
二、异常的处理
1.防御式编程
2.(异常的抛出)throw的使用
3.异常的声明-throws
4.捕获异常-try-catch捕获并处理异常
5.finally关键字起到
三、异常的处理流程
四、总结
一、异常是什么
1.异常的概念
异常是指在程序执行过程中发生的意外或错误情况,这些情况会中断正常的程序流程。异常通常由程序无法控制的内部或外部因素引发,例如无效输入,资源不足或逻辑错误。
2.异常的分类
2.1异常分为:
(1)编译时异常(除了RuntimeException):在代码编译阶段被检测到的错误,通常由语法错误或类型不匹配引起。这类异常必须修复后才能成功编译。
public class Person { private String name; private String gender; int age; // 想要让该类⽀持深拷⻉,覆写Object类的clone⽅法即可 @Override public Person clone() { return (Person)super.clone();//这里会报错,你要声明这里会报CloneNotSupportedException这个异常 } } 编译时报错: Error:(17, 35) java: 未报告的异常错误java.lang.CloneNotSupportedException; 必须对其进⾏捕获或声明以便抛出(2)运行时异常(RuntimeException):在程序运行过程中发生的错误,例如空指针引用、数组越界或算术溢出。这类异常可能不会立即导致程序崩溃,但会破坏正常逻辑。(运行后窗口报错Exception)
(3)逻辑异常:由程序逻辑错误引发的异常,例如无限循环或错误的业务规则。这类异常需要通过代码审查和测试来发现。
2.2 异常就是一个类
Error类:这是java虚拟机无法解决的严重问题,比如:jvm的内部错误、资源耗尽,典型代表:StackOverflowError和OutOfMemoryError,一旦发生回力乏术。
Exception类:异常产生后程序员可以通过代码进行处理,使程序继续执⾏。⽐如:感冒、发烧。我 们平时所说的异常就是Exception。
二、异常的处理
1.防御式编程
错误在代码中是客观纯在的。因此我们要让程序出现问题的时候及时通知程序员。
1.1LBYL:Look Before You Leap.在操作之前就做充分的检查.即:事前防御型
boolean ret = false; ret = 登陆游戏(); if (!ret) { 处理登陆游戏错误; return; } ret = 开始匹配(); if (!ret) { 处理匹配错误; return; } ret = 游戏确认(); if (!ret) { 处理游戏确认错误; return; } ret = 选择英雄(); if (!ret) { 处理选择英雄错误; return; } ret = 载⼊游戏画⾯(); if (!ret) { 处理载⼊游戏错误; return; } ......(缺陷:正常流程和错误处理流程代码混在⼀起,代码整体显的⽐较混乱)
2.EAFP:It's Easier to Ask Forgiveness than Permission.先正常运行,遇到问题再解决(事后认错)
try { 登陆游戏(); 开始匹配(); 游戏确认(); 选择英雄(); 载⼊游戏画⾯(); ... } catch (登陆游戏异常) { 处理登陆游戏异常; } catch (开始匹配异常) { 处理开始匹配异常; } catch (游戏确认异常) { 处理游戏确认异常 } catch (选择英雄异常) { 处理选择英雄异常; } catch (载⼊游戏画⾯异常) { 处理载⼊游戏画⾯异常; } ......(优势:正常流程和错误流程是分离开的,程序员更关注正常流程,代码更清晰,容易理解代码 异常处理的核⼼思想就是EAFP)
针对eafp型解决方案我们有五方面的学习使用(throw、try、catch、finally、throws)
2.(异常的抛出)throw的使用
在java中我们通过throw来抛出一个指定的异常对象,将错误信息告知给调用者具体语法如下:
throw new XXXException("异常产生的原因")注意事项:
1.throw必须在方法体内部
2.抛出的对象必须是Exception 或者 Exception的子类对象
3.如果抛出的是RunTimeException或者RunTimeException的子类,则可以直接交给jvm来处理
4.如果抛出的是编译时异常,用户必须处理,否则无法通过编译
5.异常一旦抛出,其后的代码就不会执行
3.异常的声明-throws
throws 关键字⽤于在⽅法声明中列出该⽅法可能抛出的异常,它告诉调⽤者这个⽅法可能会抛出某 些异常,调⽤者需要处理这些异常。使⽤throws 实际上是将异常的处理责任转移给了调⽤该⽅法 的代码
public static void funcction(int a) throws CloneNotSupportedException{ if(a == 10){ throw new CloneNotSupportedException(); } }注意:
1.throws必须跟在方法的参数列表之后
2.声明的异常必须是Exception 或者Exception的子类
3.方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可。
public class Config { File file; // 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("配置⽂件名字不对"); }4.调用声明抛出异常的方法时,如果该异常是编译时异常时,调用者必须处理,或者继续使用throws抛出
public static void main(String[] args) throws IOException { Config config = new Config(); config.openConfig("config.ini"); }将光标放在抛出异常方法上,alt+Insert快速处理:
4.捕获异常-try-catch捕获并处理异常
throws对异常并没有真正处理,⽽是将异常报告给抛出异常⽅法的调⽤者,由调⽤者处理。如果真正 要对异常进⾏处理,就需要try-catch。
语法格式:
try{ //将可能出现异常的代码 }catch(要捕获的异常类型 e){ //对异常的正常处理,处理完成跳出try-catch }finally{ //一定会执行的代码 }//finally可添加可不添加看自己需求实例:
public class Config { File file; public void openConfig(String filename) throws FileNotFoundException{ if(!filename.equals("config.ini")){ throw new FileNotFoundException("配置⽂件名字不对"); } // 打开⽂件 } public void readConfig(){ } public static void main(String[] args) { Config config = new Config(); try { config.openConfig("config.txt"); System.out.println("⽂件打开成功"); } catch (IOException e) { // 异常的处理⽅式 //System.out.println(e.getMessage()); // 只打印异常信息 //System.out.println(e); // 打印异常类型:异常信息 e.printStackTrace(); // 打印信息最全⾯ } // ⼀旦异常被捕获处理了,此处的代码会执⾏ System.out.println("异常如果被处理了,这⾥的代码也可以执⾏"); } }(IOException是编译时异常的父类哦)
注意事项:
1.try代码块内抛出异常位置后的代码将不会执行
2.如果抛出异常类型与catch里的异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往抛,直到jvm收到后中断程序
3.try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获
public static void main(String[] args) { int[] array = {1, 2, 3}; try { System.out.println("before"); // array = null; System.out.println(array [100]); System.out.println("after"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("这是个数组下标越界异常"); e.printStackTrace(); } catch (NullPointerException e) { System.out.println("这是个空指针异常"); e.printStackTrace(); } System.out.println("after try catch"); }如果多个异常的处理⽅式是完全相同,也可以写成这样:
catch (ArrayIndexOutOfBoundsException | NullPointerException e) { ... }如果异常之间具有⽗⼦关系,⼀定是⼦类异常在前catch,⽗类异常在后(否则子类异常不会运行)
4. 可以通过⼀个catch捕获所有的异常,即多个异常,⼀次捕获
public static void main(String[] args) { int[] arr = {1, 2, 3}; try { System.out.println("before"); arr = null; System.out.println(arr[100]); System.out.println("after"); } catch (Exception e) { e.printStackTrace(); } System.out.println("after try catch"); }不推荐,因为这样写不知道自己怎么解决对应的异常(乱喝药的感觉)
5.finally关键字起到
5.1在写程序时,有些特定的代码,不论程序是否发⽣异常,都需要执⾏,⽐如程序中打开的资源:⽹络 连接、数据库连接、IO流等,在程序正常或者异常退出时,必须要对资源进进⾏回收。另外,因为异常会引发程序的跳转,可能导致有些语句执⾏不到,finally就是⽤来解决这个问题的
问题:既然finally和try-catch-finally后的代码都会执⾏,那为什么还要有finally呢?
看下面的代码:
public class TestFinally { public static int getData(){ Scanner sc = null; try{ sc = new Scanner(System.in); int data = sc.nextInt(); return data; }catch (InputMismatchException e){ e.printStackTrace(); }finally { System.out.println("finally中代码"); } System.out.println("try-catch-finally之后代码"); if(null != sc){ sc.close(); } return 0; } public static void main(String[] args) { int data = getData(); System.out.println(data); } } // 正常输⼊时程序运⾏结果: 100 finally中代码 100(上述程序,如果正常输⼊,成功接收输⼊后程序就返回了,try-catch-finally之后的代码根本就没有执 ⾏,即输⼊流就没有被释放,造成资源泄漏。存在即合理)
注意:finally中的代码一定会执行,一般用来做资源清理的扫尾工作。在finally中尽量不要用return(编译器警告)
三、异常的处理流程
关于"调⽤栈":⽅法之间是存在相互调⽤关系的,这种调⽤关系我们可以⽤"调⽤栈"来描述.在JVM中有⼀块内存空 间称为"虚拟机栈"专⻔存储⽅法之间的调⽤关系.当代码中出现异常的时候,我们就可以使⽤ e.printStackTrace(); 的⽅式查看出现异常代码的调⽤栈.
看下面的例子(如果本⽅法中没有合适的处理异常的⽅式,就会沿着调⽤栈向上传递):
public static void main(String[] args) { try { func(); } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); } System.out.println("after try catch"); } public static void func() { int[] array = {1, 2, 3}; System.out.println(array[100]); }、运行结果(at后的错误就是通过e.printStackTrace()这个显示的):
如果向上⼀直传递都没有合适的⽅法处理异常,最终就会交给JVM处理,程序就会异常终⽌(和我们最开 始未使⽤try-catch时是⼀样的).
public static void main(String[] args) { func(); System.out.println("after try catch"); } public static void func() { int[] arr = {1, 2, 3}; System.out.println(arr[100]); }运行结果:
(明显after try catch没有打印)也就是说异常没有处理一直向上传递最后到jvm。
异常处理流程总结:
1.程序先执行try中的代码
2.如果try中的代码出现异常,就会结束try中的代码,看和catch中的异常类型是否匹配。
3.如果找到匹配的异常类型,就会执行catch代码
4.如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者。
5.无论是否找到匹配的异常类型,finally中的代码都会被执行到(在该方法结束之前执行)。
6.如果上层调用者也没有处理了异常,就会继续向上传递。
7.一直到main方法也没有合适的代码处理异常,就会交给jvm来进行处理,此时程序就会终止。
四、总结
看到这里,想必你对java中异常有了一定的了解,还有问题评论区留言。最后可以写一个用户登录功能(用异常)让自己更加熟练