目录
一、前言:
大家好,这篇博客是对java异常基础篇的一个内容延申和补充,主要和大家分享一下多异常的三种处理方式,自定义异常介绍,自定义异常的定义和使用等等,感谢阅读。
二、多异常的处理:
Δ前言:
多异常,即代码中出现了多个异常。
多异常的处理方式:
1.分别捕获,并分别处理
2.一次捕获,但多次处理
3.一次捕获,且一次处理
1.处理方式一:
1.分别捕获,并分别处理
我们首先来看一下以下代码段:
int[] array= {1, 2, 11, 985, 5}; System.out.println("我想访问数组索引为5的元素:"+array[5]);
显然,直接运行上述代码,会报异常ArrayIndexOutOfBoundsException(数组下标越界异常)。我们再来看看另一代码段:
inti=10/0; System.out.println("The value of i is : "+i);
显然,直接运行这段代码会报异常ArithmeticException(运算中的异常)。
so,up就以这两个异常为栗,给大家演示以下多异常的处理。
代码演示:
packageknowledge.exception.advance; publicclassMultipleAnomaly { publicstaticvoidmain(String[] args) { /*利用try...catch捕获*///1.分别捕获,并分别处理try { int[] array= {1, 2, 11, 985, 5}; System.out.println("我想访问数组索引为5的元素:"+array[5]); } catch (ArrayIndexOutOfBoundsExceptionarrayIndexE) { System.out.println(arrayIndexE); } try { inti=10/0; System.out.println("The value of i is :"+i); } catch (ArithmeticExceptionariE) { System.out.println(ariE); } } }
运行结果:
2.处理方式二:
2.一次捕获,但多次处理
实现方式:一个try,多个catch
Δ注意事项 :
catch语句里面定义的异常变量,如果存在子父类关系,那么子类的异常变量必须定义在父类异常变量的上面,否则就会报错。 eg : 下面代码段:
try { inti=10/0; int[] array= {1, 2, 11, 985, 5}; System.out.println("我想访问数组索引为5的元素:"+array[5]); } catch (Exceptione) { System.out.println(e); } catch (ArrayIndexOutOfBoundsExceptionarrayIndexE) { System.out.println(arrayIndexE); }
报错情况如下图所示:
正如IDEA所说的,该异常已经被捕获了。
报错原因:
try中如果出现了异常对象,会把异常对象抛出给catch处理,抛出的异常对象, 会从上至下依次赋值给catch中定义的变量。 如果是子类变量在前,父类变量在后(即正常情况下),因为父类异常对象本身就无法赋值给子类异常对象, 因此try会自动跳开,最终子类异常对象赋值给子类异常变量,父类异常对象赋值给父类异常变量;
但是,
如果是父类变量在前,子类变量在后,因为子类异常对象本身是可以赋值给父类异常对象的, 就是多态嘛。因此try不会跳开,而是将子类异常对象也赋值给了父类异常变量,这就导致 下面定义子类异常变量的catch语句是多余的语句,因此报错。
代码演示:
packageknowledge.exception.advance; publicclassMultipleAnomaly { publicstaticvoidmain(String[] args) { //2.一次捕获,但多次处理try { inti=10/0; //直接运行这段代码会报异常ArithmeticExceptionint[] array= {1, 2, 11, 985, 5}; System.out.println("我想访问数组索引为5的元素:"+array[5]); } catch (ArithmeticExceptionariE) { System.out.println(ariE); } catch (ArrayIndexOutOfBoundsExceptionarrayIndexE) { System.out.println(arrayIndexE); } System.out.println("---------------------------------"); /*try中若出现异常,会立刻跳转至catch,此处当执行到int i = 10 / 0; 时,就会跳转至catch语句,因此控制台只会打印出ArithmeticException*/ } }
运行结果:
3.处理方式三:
3.一次捕获,且一次处理
刚刚我们讲到了为什么多个catch语句中子类异常变量必须定义在父类异常变量的前面。其实,一次捕获一次处理,也是应用了这个原理。怎么做到一次处理?直接给它定义一个父类异常的异常变量不就完了么。
直接定义一个父类异常变量,那么try无论是把父类本身的异常对象抛给catch,还是它的子类对象抛给catch ,父类异常变量都可以做接收,一劳永逸!
代码演示:
packageknowledge.exception.advance; publicclassMultipleAnomaly { publicstaticvoidmain(String[] args) { //3.一次捕获,且一次处理try { int[] array= {1, 2, 11, 985, 5}; System.out.println("我想访问数组索引为5的元素:"+array[5]); inti=10/0; } catch (Exceptione) { System.out.println(e); } /*直接定义异常类父类Exception,可以接收绝大多数的异常对象。*/ } }
运行结果:
注意,这里我们先产生了数组下标越界异常(ArrayIndexOutOfBoundsException),立刻便跳转到catch语句,因此控制台只打印出了ArrayIndexOutOfBoundsException。
三、PS:finally代码块中存在return的问题:
如果finally中含有return语句,会永远只返回finally中return的结果 因此要避免此情况发生。
PS : 注意!
①若finally代码块之前的catch语句中也有return语句,那么该return语句中的内容也会执行!(eg : return ++i; )。只不过执行后并不会由当前return语句返回,而是在底层用一个临时变量temp保存了该返回值,最终的返回值仍是以finally代码块中的return语句为准。若finally代码块中无return语句,才会返回去执行之前的return语句,最终返回temp变量保存的值。
②有时会出现try-finally的搭配,这种用法相当于没有捕获异常,因此若出现异常程序会直接崩掉。适用于“执行一段代码,不管有没有出现异常都要执行某个业务逻辑”的场景。
代码演示:
packageknowledge.exception.advance; /*如果finally中含有return语句,会永远返回finally中的结果因此要避免此情况发生。*/publicclassFinallyReturn { publicstaticvoidmain(String[] args) { intnumber=getNumber(); System.out.println("The value of number is : "+number); } publicstaticintgetNumber() { inti=11; try { returni; } catch (Exceptione) { System.out.println(e); } finally { i=5; returni; } } } //最终的返回值是5
运行结果:
四、子父类异常:
1.子父类异常的关系:
关于子父类的异常:
①如果父类中抛出多个异常,子类重写父类方法时, 抛出和父类相同的异常,或者是父类异常的子类,或者不抛出。
②如果父类中没有抛出异常,子类重写父类该方法时, 也不可抛出异常。此时子类若产生了异常,只能做捕获处理,不能声明抛出
人话:
只要子类和父类保持一致,就不会出事儿。
2.代码演示①:
由于代码中的方法涉及到了读取文本文件,up先给大家看一下文本文件1.txt中内容,如下:
我们以Father1类作为第一个演示类,Father1类代码如下:
packageknowledge.exception.advance; importjava.io.*; publicclassFather { publicvoidreadText() throwsIOException { Readerreader=newFileReader("D:\\JAVA\\IDEA\\file\\1.txt"); intdata; while ((data=reader.read()) !=-1) { System.out.println((char)data); } reader.close(); } publicvoidreadFile() throwsIOException { thrownewIOException("😋抛出一个IO父类异常对象"); } publicvoidgetElement() throwsIndexOutOfBoundsException { System.out.println("这是父类的方法"); } } classSonextendsFather{ //1.父类抛出异常时//1_①抛出和父类相同的异常publicvoidreadText() throwsIOException { Readerreader=newFileReader("D:\\JAVA\\IDEA\\file\\1.txt"); char[] charArray=newchar[1024]; //用于读写数据的字符数组的长度最好是1024的整数倍intdata; while ((data=reader.read(charArray)) !=-1) { System.out.println(charArray); } reader.close(); } //1_②抛出父类异常的子类publicvoidreadFile() throwsFileNotFoundException { thrownewFileNotFoundException(); } //1_③不抛出异常publicvoidgetElement() { System.out.println("这是子类的方法"); } } classTest1 { publicstaticvoidmain(String[] args) throwsIOException { Fatherf=newFather(); Sons=newSon(); f.readText(); System.out.println("-----------------------------------"); s.readText(); f.readFile(); f.getElement(); s.readFile(); s.getElement(); } }
运行效果(GIF图):
3.代码演示②:
up以Father2类作为第二个演示类,来演示如果父类没有抛出异常的情况下,子类的处理情况。Father2代码如下:
packageknowledge.exception.advance; publicclassFather2 { publicvoidf() { System.out.println("父类没有抛出任何异常。"); } } classSon2extendsFather2 { //2.父类没有抛出异常时// public void f() throws Exception{ //如果此时子类声明异常,编辑器会报错。// System.out.println("此时子类也不能抛出任何异常");// }publicvoidf() { System.out.println("此时子类也不能抛出任何异常"); System.out.println("假设子类重写方法时产生了异常,也只能捕获处理,不可以抛出异常对象!"); try { thrownewException("无聊抛出一个异常对象玩玩儿😁"); } catch (Exceptione) { e.printStackTrace(); //这个方法在异常基础篇分享过 } } } classTest2 { publicstaticvoidmain(String[] args) { Father2ff=newFather2(); Son2ss=newSon2(); ff.f(); System.out.println("--------------------------------------"); ss.f(); } }
运行效果:
五、自定义异常:
1.介绍:
java中本身已经定义了许多的异常类,但是某些情况下仍然不够我们霍霍的,所以需要我们自己去定义一些异常类。
2.格式:
public class ___Exception extends Exception / RuntimeException {
//自定义的异常类内部,需要定义两个构造方法。
//一个空参构造
//一个带参构造(说明异常信息)
}
3.注意事项:
①自定义异常类的类名一般都是以"Exception"作为结尾,以达到见名知意的效果,表示该类是一个异常类。
②自定义的异常类,必须得继承Exception类或者RuntimeException类,
继承Exception类,表示自定义的异常类是一个编译期异常;
继承RuntimeException类,表示自定义的异常类是一个运行期异常;
关于编译期异常和运行期异常的区别以及处理方式,我们在java异常基础篇已经讲过,此处不再赘述。
4.代码演示:
①自定义异常类:
我们创建一个RegisterException类的自定义异常类,当我们模拟注册用户时,如果用户输入的用户名异常,则可以抛出RegisterException类的异常对象。
RegisterException类代码如下:
packageknowledge.exception.advance; publicclassRegisterExceptionextendsException { //1.添加一个空参构造publicRegisterException() { super(); //默认调用父类的空参构造 } //2.添加一个带参构造(说明异常信息)/*查看源码发现(IDEA快捷键Ctrl + b可查看类的源码),所有的异常类都会有一个带异常信息的构造方法,让父类来处理这个异常信息。*/publicRegisterException(Stringmessage) { super(message); } }
②测试自定义异常:
有了自定义的异常类,接着我们就可以创建一个测试类来测试自定义异常类,这里我们以TestRegisterException类作为演示类,需求 :模拟注册用户的操作,如果用户名已经存在,则抛出RegisterException异常并提示用户: 这个用户名已经注册过了哈。
TestRegisterException类代码如下:
packageknowledge.exception.advance; importjava.util.Scanner; /*requirement : 模拟注册用户的操作,如果用户名已经存在,则抛出RegisterException异常并提示用户:这个用户名已经注册过了哈。mentality(思路) :1.首先,可以使用一个数组来保存已注册的用户名,模拟数据库。2.其次,可以使用Scanner类获取用户想注册的用户名,模拟前端页面。3.定义一个方法,对用户输入的用户名进行判断。若已存在该用户名,抛出RegisterException异常并提示用户:这个用户名已经注册过了哈。若不存在该用户名,提示用户:注册成功🌶!*/publicclassTestRegisterException { publicstaticvoidmain(String[] args) throwsRegisterException { //1.首先,可以使用一个数组来保存已注册的用户名,模拟数据库。String[] names= {"大伟哥", "赵鑫鑫", "杜兰特", "Cyan", "王五"}; //2.其次,可以使用Scanner类获取用户想注册的用户名,模拟前端页面。Scannersc=newScanner(System.in); System.out.println("请输入你想注册的用户名:"); StringuserName=sc.nextLine(); ifExist(names, userName); sc.close(); } //3.定义一个方法,对用户输入的用户名进行判断。publicstaticvoidifExist(String[] names, StringuserName) throwsRegisterException { inttemp=0; for (Stringname : names) { if (name.equals(userName)) { temp=-1; thrownewRegisterException("这个用户名已经注册过了哈。"); } } if (temp==0) { System.out.println("注册成功🌶!"); } /*加入这个判断条件更严谨,以不会导致在抛出异常对象后仍然打印出"注册成功🌶!"的提示信息。其实还有多种处理方式,比如在抛出异常对象后执行return语句,直接结束方法体,都可以。*/ } }
运行效果如下GIF图:
六、完结撒❀:
恭喜你看到这里,感谢阅读。
System.out.println("------------------------------------------------------------------------------------");