异常机制(重点)
-
基本概念
-
异常就是"不正常"的含义,在Java语言中主要指程序执行中发生的不正常情况。
-
java.lang.Throwable类是Java语言中==错误(Error)和异常(Exception)==的超类。
-
其中Error类主要用于描述Java虚拟机无法解决的严重错误,通常无法编码解决,如:JVM挂掉了 等。
-
其中Exception类主要用于描述因编程错误或偶然外在因素导致的轻微错误,通常可以编码解决, 如:0作为除数等。
-
-
异常的分类
-
java.lang.Exception类是所有异常的超类,主要分为以下两种:
- RuntimeException - 运行时异常,也叫作非检测性异常,编译ok,运行报错。
- IOException和其它异常 - 其它异常,也叫作检测性异常,所谓检测性异常就是指在编译阶段都能 被编译器检测出来的异常。在Idea中写完代码直接爆红 ,或者在dos窗口中使用javac编译生成字节码文件的时候直接报错。
-
其中RuntimeException类的主要子类:笔试题中可能会考简答题:简述你所熟知的异常并且举例说明
- ArithmeticException类 - 算术异常
- ArrayIndexOutOfBoundsException类 - 数组下标越界异常
- NullPointerException - 空指针异常
- ClassCastException - 类型转换异常
- NumberFormatException - 数字格式异常
- 上面五个异常必须记住
-
注意:
当程序执行过程中发生异常但又没有手动处理时,则由Java虚拟机采用默认方式处理异常,而默认 处理方式就是:打印异常的名称、异常发生的原因、异常发生的位置以及终止程序,异常发生后的代码不再执行。
package com.lagou.module05.task01; public class ExceptionTest { public static void main(String[] args) { // 发生异常之后的代码不会执行 // 1.见识一下非检测性异常 运行时异常 编译器没有检测出来,在运行阶段发生异常 System.out.println(5 / 0); // 编译ok,运行阶段会发生算术异常 下面的代码不会执行 // 当程序在执行中发生了异常时,控制台就会打印一堆的信息:异常的名称、异常的原因、异常发生的位置,然后程序结束,代码不再执行 // 2.检测性异常 在编译阶段就被编译器检测出来了 // Unhandled exception: java.lang.InterruptedException在执行这段代码时有可能发生中断异常,但是并没有做处理 // Thread.sleep(1000); // 编译错误 不处理就无法到运行阶段 System.out.println("程序正常结束了!"); } }
-
-
异常的避免
-
在以后的开发中尽量使用 if 条件判断来避免异常的发生。 if 判断是个好东西,可以规避很多异常。
-
但是过多的 if 条件判断会导致程序的代码加长、臃肿,可读性差。如果异常多起来了,几乎要在每一个异常的上面加上 if 条件判断。
-
查看字符串格式是否与自己想要的格式匹配:str2.matches(String regex); 传一个正则表达式。
package com.lagou.module05.task01; import java.io.IOException; // 异常说到底就是不正常的代码,我们要学会去处理这种不正常的代码 public class ExceptionPreventTest { public static void main(String[] args) { // 会发生算术异常 int ia = 10; int ib = 0; if (0 != ib) { System.out.println(ia / ib); } // 数组下标越界异常 int[] arr = new int[5]; int pos = 5; if (pos >= 0 && pos < 5) { System.out.println(arr[pos]); } // 发生空指针异常 String str = null; // 没有明确指向的对象,一调用方法就会报异常。 if (null != str) { System.out.println(str.length()); } // 类型转换异常 Exception ex = new Exception(); if (ex instanceof IOException) { IOException ie = (IOException) ex; } // 数字格式异常 String str2 = "123a"; if (str2.matches("\\d+")) { System.out.println(Integer.parseInt(str2)); } System.out.println("程序总算正常结束了!"); } }
-
-
异常的捕获
-
在以后的开发当中首先要避免异常的发生,要是实在避免不了了,这个时候就得对异常进行一个捕获(治疗)。
-
语法格式
try { 编写可能发生异常的代码; } catch(异常类型 引用变量名) { 编写针对该类异常的处理代码; } ... finally { 编写无论是否发生异常都要执行的代码;必定执行 }
-
注意事项
a.当需要编写多个catch分支时,切记小类型应该放在大类型的前面;根据业务需求,实际情况来调节异常的使用方式 b.懒人的写法: catch(Exception e) {} c.finally通常用于进行 善后 处理,如:关闭已经打开的文件等。最终的最后的。
-
执行流程
try { a; b; - 可能发生异常的语句 c; }catch(Exception ex) { d; }finally { e; } 当没有发生异常时的执行流程:a b c e; 当发生异常时的执行流程:a b d e; 无论是否发生异常,finally都会执行,这也就是finally的功能或者说是价值。
-
异常捕获的简单应用
- try - catch 的使用
package com.lagou.module05.task01; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; /** * finally的善后功能: * 假设在两个try-catch中间发生了一个别的异常导致程序终止了,此时第一个try-catch所打开的文件就没有机会关闭了。 * 或者说文件关闭的代码就没有机会执行了,这个时候就会浪费一些资源。因为不关闭的话,底层实际上会有一些更复杂的结构。 * 比如磁盘结构、表结构等等,如果因为异常导致文件没有关闭,这些资源就会被浪费掉。 *此时,就可以把文件关闭的代码写到finally中,因为中间无论是否发生异常,代码都一定会执行,打开的文件一定会关闭,就相当于做善后了,然后我们的资源也就不会被浪费。 */ public class ExceptionCatchTest { public static void main(String[] args) { // 这是IO流那一块的知识,使用这一块更好说明问题 // lang完之后就去IO包 // 创建一个FileInputStream类型的对象与d:/a.txt文件关联,打开文件 // FileInputStream fi = new FileInputStream("file"); Unhandled exception: java.io.FileNotFoundException 可能存在文件不存在的情况 // 检测性异常如果不处理(捕获异常)就无法到运行阶段 FileInputStream fis = null; // 加打印语句只是为了测试代码的执行流程到底是什么样的? try { // try 尝试 尝试执行块中的所有代码 System.out.println("1"); // 当程序执行过程中发生了异常后直奔catch分支进行处理 fis = new FileInputStream("d:/a.txt"); // 创建一个FileInputStream类型的对象与d:/a.txt文件关联,打开文件 System.out.println("2"); } catch (FileNotFoundException e) { // catch 抓 尝试执行的同时随时等着 抓文件找不到的异常 System.out.println("3"); e.printStackTrace(); // 打印栈区的一些工作信息,也就是打印异常信息 System.out.println("4"); } // 关闭文件 try { System.out.println("5"); fis.close(); // 关闭文件 System.out.println("6"); } /*catch (Exception e) { e.printStackTrace(); }*/ catch (IOException e) { System.out.println("7"); e.printStackTrace(); System.out.println("8"); } catch (NullPointerException e) { // 此处是处理异常的代码此处只是打印异常信息,如果是正式的商业项目的话,可以弹一个小窗口可以给用户各种提示 e.printStackTrace(); // 相当于程序员手动处理了空指针异常 } catch (Exception e) { // 多态的理念:无论是什么异常都可以使用Exception来抓 // 在学习阶段可以采用懒人写法来处理:直接抓取Exception来统一处理所有的异常 // 在开发中更推荐将所以可能发生的异常列出来捕获,这样加强了代码的可读性,让人们知道在哪里错了,因为什么原因导致的异常 // 多个catch分支的时候要把大catch放在最后面,不然会拦截其它异常 e.printStackTrace(); } System.out.println("世界上最真情的相依就是你在try我在catch,无论你发神马脾气我都默默承受并静静的处理,到那时再来期待我们的finally!"); // 当程序执行过程中没有发生异常时的执行流程:1 2 5 6 世界上... // 当程序执行过程中发生异常又没有手动处理空指针异常时的执行流程:1 3 4 5 fis.close()发生空指针异常导致程序终止 // 前面new对象的时候发生文件找不到异常导致对象没有new出来,拿着null调用方法引发空指针异常 出现了以外异常 // 当程序执行过程中发生异常并且手动处理空指针异常时的执行流程: 1 3 4 5 世界上... 希望代码往下执行我们只要对空指针异常进行手动处理即可 // 手动处理异常和没有处理的区别:代码是否可以继续向下执行 } }
- try - catch - finally 的用法以及一个笔试考点
package com.lagou.module05.task01; public class ExceptionFinallyTest { // 笔试考点 public static int test() { try { int[] arr = new int[5]; System.out.println(arr[5]); return 0; } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); return 1; // 返回数据并结束方法,但是还没来得及结束的时候finally在喊:兄弟,等一下,我这里还没执行完呢,你得等我执行完再执行方法。然后finally就返回数据并结束方法了 } finally { // 还没结束方法就要求先执行这里面的,在那边还在等的时候,这边就提前结束方法并返回数据了,return 1就没有机会再返回了 return 2; // 提交结束方法并返回数据 } } public static void main(String[] args) { try { int ia = 10; int ib = 0; System.out.println(ia / ib); // 有可能发生异常,也有可能不发生异常,取决于ib的值,只要是有可能发生异常的时候,我们都把代码使用try-catch代码块括起来 // 小细节:对于运行时异常写代码的时候写不写try-catch异常处理都可以,都不会报错 // 对于检测性异常在写代码的时候必须写处理代码,虽然写完try-catch代码之后不会报错,但是运行的时候依旧会报错 // try-catch异常处理对于检测型异常而言只是把异常的发生往后挪了挪而已,本来是在编译阶段报错,但是我们把它挪到了运行阶段 // 对于运行时异常捕获不捕获都无所谓,因为反正是在运行阶段发生的 } catch (ArithmeticException e) { e.printStackTrace(); // 打印栈轨迹 String str1 = null; // str1.length(); // 会发生空指针异常 } finally { // 无论是否发生异常都必定会执行finally代码块。 System.out.println("无论是否发生异常都记得来执行我哦!"); // 依然是执行 // 如果catch块中发生了异常而并没有手动处理时(默认方式处理处理完之后结束程序),finally代码块依旧会执行执行完后结束程序,但是之后的代码不再执行。这也就是有无finally的区别 } System.out.println("Over!"); // 不执行了 System.out.println("----------------------------------------"); // 笔试考点:这里最终打印的结果到底是0?1?2? 2 int test = test(); System.out.println("test = " + test); // 2 } }
笔试考点
package com.lagou.module05.task01; /** * @author hhc19 * @date 2022/1/12 21:35 * @description */ public class MyTest { public static void main(String[] args) { int test = test(3,5); System.out.println(test); } public static int test(int x, int y){ int result = x; try{ if(x<0 || y<0){ return 0; } result = x + y; return x + y; } finally { x = 0; y = 0; result = x - y; } } } // 此处是我已经将return执行完了,然后我发现finally中的代码还没执行,然后我再去执行finally中的代码,但是我已经将结果return出去了,所以这里的结果是8
-
-
异常的抛出
-
首先,我们避免异常;然后,我们捕获异常;实在捕获不了了我们就抛出异常。
-
基本概念:捕获是就地处理了,抛出是转移出去了
在某些特殊情况下有些异常不能处理或者不便于处理时,就可以将该异常转移给该方法的调用者, 这种方法就叫异常的抛出。当方法执行时出现异常,则底层生成一个异常类对象抛出,此时异常代 码后续的代码就不再执行。
底层原理:throw new NullPointerException(); 生成一个异常类对象抛出。
-
语法格式
访问权限 返回值类型 方法名称(形参列表) throws 异常类型1,异常类型2,…{ 方法体; }
有父子类关系,自动就合了,没有父子类关系就使用逗号隔开
如:
public void show() throws IOException{}
-
方法重写的原则
a.要求方法名相同、参数列表相同以及返回值类型相同,从jdk1.5开始支持返回子类类型;
b.要求方法的访问权限不能变小,可以相同或者变大;
c.要求方法不能抛出更大的异常;(历史遗留问题)
-
注意:
子类重写的方法不能抛出更大的异常(孩子不能比爹更坏)、不能抛出平级不一样的异常,但可以抛出一样的异常、更小 的异常以及不抛出异常。
package com.lagou.module05.task01; import java.io.IOException; public class ExceptionMethod { public void show() throws IOException {} }
package com.lagou.module05.task01; import com.sun.jdi.ClassNotLoadedException; import java.io.FileNotFoundException; import java.io.IOException; public class SubExceptionMethod extends ExceptionMethod { @Override public void show() throws IOException {} // 子类重写的方法可以抛出和父类中方法一样的异常 //public void show() throws FileNotFoundException {} // 子类重写的方法可以抛出更小的异常,子类异常 //public void show() {} // 子类可以不抛出异常 //public void show() throws ClassNotLoadedException {} // 不可以抛出平级不一样的异常 //public void show() throws Exception {} // 不可以抛出更大的异常 }
-
经验分享
-
若父类中被重写的方法没有抛出异常时,则子类中重写的方法只能进行异常的捕获处理。
-
若一个方法内部又以递进方式分别调用了好几个其它方法(a 调 b,b 调 c,c 调 d),则建议这些方法内可以使用抛出的方法处理到最后一层进行捕获方式处理。一路抛抛到最后一层捕获处理。
-
早抛出晚捕获
package com.lagou.module05.task01; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class ExceptionThrowsTest { // FileNotFoundException 是 IOException的子异常,我们抛出大的即可,多态的理念 public static void show() throws IOException { FileInputStream fis = new FileInputStream("d:/a.txt"); // 打开文件 System.out.println("我想看看你抛出异常后是否继续向下执行???"); fis.close(); // 关闭文件 } public static void test1() throws IOException { show(); } public static void test2() throws IOException { test1(); } public static void test3() throws IOException { test2(); } // 不建议在main方法中抛出异常 JVM负担很重 public static void main(String[] args) /*throws IOException*/ { try { // 如果在main方法中抛异常的话就直接抛给了Java虚拟机。一般我们在main方法中不建议抛 show(); // show方法是static修饰的,Main方法也是static修饰的,故而可以直接调用 } catch (IOException e) { e.printStackTrace(); } System.out.println("------------------------------------"); try { test3(); } catch (IOException e) { e.printStackTrace(); } } }
-
-
-
自定义异常
-
基本概念
当需要在程序中表达年龄不合理的情况时,而Java官方又没有提供这种针对性的异常,此时就需要 程序员自定义异常加以描述。如果以后遇到独特的有针对性的异常时,就自己写一个。
-
实现流程
a.自定义xxxException异常类继承Exception类或者其子类(原因:遇到我们自定义的异常和遇到Java官方定义的异常输出的异常信息的格式一致)。 编程规范:异常类类名必须以Exception结尾。
b.提供两个版本的构造方法,一个是无参构造方法,另外一个是字符串作为参数的构造方法。字符串是用来描述异常信息的。
-
异常的产生:自定义异常的使用和Java官方的是一样的。
throw new 异常类型(实参);
如:
throw new AgeException(“年龄不合理!!!”);
-
Java采用的异常处理机制是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序 简洁、优雅,并易于维护。
package com.lagou.module05.task01; public class AgeException extends Exception { static final long serialVersionUID = 7818375828146090155L; // 序列化的版本号 与序列化操作有关系,具体IO流再说,Java官方异常类中都有这个 // 依赖于父类中的构造方法调用有参无参构造方法 public AgeException() { } public AgeException(String message) { super(message); } }
package com.lagou.module05.task01; public class Person { private String name; private int age; public Person() { } public Person(String name, int age) /*throws AgeException */{ setName(name); setAge(age); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) /*throws AgeException */{ // throws 方法之中异常抛出的一个声明 if (age > 0 && age < 150) { this.age = age; } else { // System.out.println("年龄不合理哦!!!"); try { throw new AgeException("年龄不合理哦!!!"); // 创建一个异常对象把它丢出去 // 此处把异常处理了,后续代码继续正常执行。 } catch (AgeException e) { e.printStackTrace(); } } } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
package com.lagou.module05.task01; public class PersonTest { public static void main(String[] args) { /*Person p1 = null; try { // 建议使用这种方式,总不能前面都错了,这里还正常new出对象了,new出来的对象的值是0,还不如使用这种方法让它new不出来对象 p1 = new Person("zhangfei", -30); // 这个对象不能创建了,发生异常导致p1的没有new完,p1就为null } catch (AgeException e) { e.printStackTrace(); } System.out.println("p1 = " + p1); // zhangfei 0*/ Person p1 = new Person("zhangfei", -30); // 创建出对象了 System.out.println("p1 = " + p1); // zhangfei 0 null } }
-