Java中的异常
1.异常的分类
在Java中,所有的异常(Exception)和所有的错误(Error)都有一个父类:Throwable类。位于java.lang包中
1.1.Error类
java.lang.Error类是一个程序无法处理的错误,严重性比较大,一般发生此错误,就表示了程序运行出现了大麻烦大错误,例如:JVM系统内部错误或资源耗尽等严重情况,JVM需要负担的责任,这一类异常事件无法恢复或不可能捕获,将导致应用程序中断,交由系统处理。
1.2.Exception类
java.lang.Exception类是由于程序编程错误或偶然的外在因素导致的一般性问题。这类异常得到恰当的处理时,程序有机会恢复至正常运行状况。分为运行时异常和受检异常(编译异常)。
2.常见的运行时异常和编译异常(受检异常)
2.1.什么是运行时异常(RunTimeException)
- 就是运行时才给你报错的异常。编译器不要求强制处置的异常。一般是指编程时的逻辑错误,所以能避免就尽量避免,否则找起来就要把F6按坏。
2.2.常见的运行时异常
-
ArithmeticException 算术异常:最常见的就是做除法运算时,除数为0的情况了。
-
NullPointerException 空指针异常:这也是很常见的一种异常了。在我之前的文章中就有一篇主要针对NullPointerException异常的处理逻辑。
-
ArrayIndexOutOfBoundsException 数组下标越界异常。这个异常在数组中就比较常见。
-
IndexOutOfBoundsException 这也是一种下标越界的异常,是ArrayIndexOutOfBoundsException这个异常的父类,在集合中就经常见了,比如在ArrayList中获取超过它的个数的下标时就会报这个错误
-
NumberFormatException 数字格式异常。有时尝试将String类型转换为数字类型是,该字符又不能满足数字格式时后就会报错。这个在算术运算时有时也会遇到,例如传入的是一串abc的String类型进行Integer.parseInt()。
-
inputMismatchException 输入类型不匹配异常。 这个在前期学习中一直在控制台输入打印最经常出现的错误了。就是你设置接收的类型跟你输入的类型不一致就会
-
NegativeArraySizeException 数组大小负数异常。这个错误应该说一般不会出现吧,谁会吃饱了撑着去创建应该数组然后指定大写为负数的。
。。。此处省略一堆其他类型的RunTimeException类型异常
2.4.什么是受检异常
受检异常也叫编译异常。就是在你写出来的时候,编译器就会给你划红线。编译器要求必须处置的异 常。指的是程序在运行时由于外界因素造成的一般性异常。
2.5.常见的受检异常
-
ClassNotFoundException 没有找到指定名称的类
-
FileNotFoundException 文件可能找不到
-
IOException 操作文件可能发生的异常
-
SQLException 操作数据库可能发生的异常
。。。此处省略一堆受检异常。
其实发现这些异常的产生原因都很相似,就是可能要你去操作一个外界的的东西的时候,就是不确定因素约束嘛。
既然了解了,那就来看一下代码**:常见的运行时异常**
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
//RuntimException:运行时异常(非检查异常):编译时没有提示出错,运行时报错
public class TestRunTimeException {
public static void main(String[] args) {
// test1();
// test2();
// test3();
// test4();
// test5();
// test6();
// test7();
test8();
}
//空指针异常 NullPointerException
public static void test1() {
String name=null;
//Exception in thread "main" java.lang.NullPointerException
System.out.println(name.getClass());
}
//算术异常 ArithmeticException
public static void test2() {
int i=0;
int n=10/i;
//Exception in thread "main" java.lang.ArithmeticException: / by zero
System.out.println(n);
}
//类型转换异常
public static void test3() {
Object obj="110";
Integer i=(Integer) obj;
//Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
System.out.println(i);
}
//数组下标越界 IndexOutOfBoundsException
public static void test4() {
List list=new ArrayList();
//Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
System.out.println(list.get(0));
}
//数组下标越界 ArrayIndexOutOfBoundsException
public static void test5() {
int[] nums=new int[0];
// Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException:
System.out.println(nums[1]);
}
//
//输入类型不匹配异常
public static void test6() {
Scanner sc=new Scanner(System.in);
System.out.print("输入一个整数:");
int n=sc.nextInt();
//Exception in thread "main" java.util.InputMismatchException
System.out.println(n);
}
//数字格式异常
public static void test7() {
String str="abc";
int s=Integer.parseInt(str);
int n=10;
//Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
System.out.println(s/n);
}
//数值大小负数异常
public static void test8() {
int[] nums=new int[-10];
}
}
受检异常
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
//CheckedException:受检异常,检测异常,还没运行就提示错误,编译器强制你去处理的异常
public class TestCheckedException {
public static void main(String[] args){
test1();
}
//受检异常,强制你必须去处理
public static void test1() {
FileOutputStream out=new FileOutputStream("g:\\test.txt");
}
}
错误Error
//1、错误(Error):JVM系统内部错误或资源耗尽等严重情况-属于JVM需要负担的责任
//这一类异常事件无法恢复或不可能捕获,将导致应用程序中断。
//Exception in thread "main" java.lang.StackOverflowError
public class TestError {
Person p=new Person();
public static void main(String[] args) {
TestError err=new TestError();
System.out.println("error演示");
}
}
class Person{
TestError test=new TestError();
}
//Exception in thread "main" java.lang.StackOverflowError
//栈满了。递归的时候如果没有设定好出口也会报错
3.异常的处理机制
3.1.try{}catch{}
就是将在try里面可能出现的错误把它在catch里面咔嚓掉,程序继续运行。
注意:catch后面跟着的括号,放的就是你try里面可能发生错误的类型,如NullPointerException。如果你明确知道会报什么类型的错误,那就直接指明该类型。如果你不明确会报什么类型的错误,那么你可以声明为Exception类型,这是所有异常的父类,根据以前学过的多态可以明白,如果声明为这个类型,那么它可以捕获任何类型的异常。
try{
//可能会发生异常的代码
}
catch(Exception e){
//咔嚓掉
//e.printStackTrace() 打印错误信息
}
public class TestTryCatch {
public static void main(String[] args) {
System.out.println("异常发生前,正常运行");
//将有可能发生异常的代码用try包围起来
try {
System.out.println("算术异常测试");
int i=0;
int j=100/i;//除数不能为0,会有错误
System.out.println("发生了错误了");
}
//如果try里面真的发生了异常,那就让catch处理
//最好将知道要发生的异常类型传进去,不然可以传所有异常的父类Exception
catch(ArithmeticException e) {
//e.printStackTrace();//打印异常信息
System.out.println("发现异常,把你捕获");
}
//经过异常处理之后,后面的程序将继续运行
System.out.println("异常已经被捕获了,我可以继续正常运行");
}
}
如果不进行异常捕获,那么运行到异常处,程序就会直接停止,相当于return了。但是如果你想要运行异常之后的代码,那你就用try…catch捕获。后面的代码继续执行
3.2.多重 try{} catch{} catch{}
如果你想要根据不同类型的异常抛出不同的声明,那么你就可以使用多重try…catch来捕获。
import java.util.Random;
//多重catch进行不同类型错误的捕获
//注意:在最后要再添加一个Exception异常,把想不到的异常捕获.而且这个Exception异常
// 处理不能反正最前面,因为他是所有异常父类,会直接捕获所有异常类型
public class TestTryCatch2 {
public static void main(String[] args) {
String[] arrs= {"3","0","A","3"};
Random r=new Random();
System.out.println("准备进行运算");
try {
int x=Integer.parseInt(arrs[r.nextInt(5)]);
int y=Integer.parseInt(arrs[r.nextInt(5)]);
int result=x/y;
System.out.println("计算结果:"+result);
}
catch(ArithmeticException e) {
System.out.println("算术异常,捕捉");
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("数组下标越界,没有输入参数,捕捉");
}
catch(NumberFormatException e) {
System.out.println("数字类型异常,不是数字,捕捉");
}
catch(Exception e) {
System.out.println("不知道还会发生什么异常,全部捕捉吧");
}
System.out.println("计算结果出来了,有异常都已经被捕捉了");
}
}
**注意:**在多重catch的最后,要catch(Exception e)来捕获你可能漏掉的异常类型。但是不能把这个放在最前面,不然你什么类型的异常都直接给它抛出了。
3.3.在JDK1.7之后,多重try…catch的不同写法
import java.util.Random;
import java.util.Scanner;
//在jdk1.7后,对多重catch异常处理进行了一些使用优化
public class TestTryCatchJDK7 {
public static void main(String[] args) {
String[] arrs= {"3","3","0","A"};
Random r=new Random();
System.out.println("准备进行运算");
try {
int x=Integer.parseInt(arrs[r.nextInt(5)]);
int y=Integer.parseInt(arrs[r.nextInt(5)]);
int result=x/y;
System.out.println("计算结果:"+result);
}
catch(ArithmeticException | NumberFormatException | ArrayIndexOutOfBoundsException e) {
if(e instanceof ArithmeticException) {
System.err.println("算术异常");
}
if(e instanceof NumberFormatException) {
System.err.println("数字格式异常");
}
if(e instanceof ArrayIndexOutOfBoundsException) {
System.err.println("数组越界");
}
}
catch(Exception e) {
System.out.println("不知道还有什么奇奇怪怪的异常,全部捕获");
}
System.out.println("计算结果出来了,有异常都已经被捕捉了");
}
}
3.4.try{} catch{} finally
上面已经说了try的作用就是进行异常捕获,catch的作用就是进行异常处理。现在加多了个finally{},那么它的作用是什么。
只要记住,finally{}代码块里面的代码是一定会执行的。那么上面说过,在catch{}中进行了异常处理了,后面的代码就可以继续正常运行而不会被异常终止,那么既然这样,为什么还要finally{}干嘛,想要运行直接写在catch{}代码块后面不就好了吗。说的对,但是上面说的例子呢方法是一直运行下去,没有中间跳出方法来,但是我们可以有return关键字呀,return关键字的作用是什么,就是跳出该方法嘛,那么如果在catch{}代码块中进行异常处理之后,选择return跳出该方法,那么catch{}后面的代码不就没有运行到吗。那么如果想要运行后面的代码要怎么办,那么finally{}的作用就体现出来了。只要在finally中的代码,就一定会执行。
看一下这个图
这就像是尽管你这个方法要return退出了,但是我的finally{}代码块就直接跑到你return语句之前,你给我执行完了你再退出。
那么既然说finally{}里面的代码一定会执行,那么我们看一下下面这个
执行结果是什么,是不是就是1234了。
额,不是说finally{}里面的代码一定会执行吗?为什么没有执行呢?因为你都直接退出程序了你还怎么执行呀,System.exit(0)就是直接退出程序了,它不像return一样退出方法之前还给你执行完finally{}里面的内容。
看一下完整的代码
//finally{}代码块:这个代码块里面的代码一定会执行.(除非整个程序退出)
public class TestTryCatchFinally {
public static void main(String[] args) {
test1();
System.out.println("===========================");
test2();
System.out.println("===========================");
test3();
}
//将异常捕获并处理:依次执行12345
public static void test1() {
System.out.println("test1测试");
System.out.println("1.代码准备执行");
try {
System.out.println("2.将可能要发生异常的代码进行捕获");
int i=0;
int j=10/i;
System.out.println(j);//这个肯定要出现算术异常
}
catch(ArithmeticException e) {
System.out.println("3.将算术异常ArithmeticException进行处理了");
}
finally {
System.out.println("4.这里面的代码一定会执行的");
}
System.out.println("5.finally执行之后,我也能执行");
}
//在处理异常的时候添加返回值return,也就是退出当前方法,那么finally之后的代码无法执行
//执行了1235
public static int test2() {
System.out.println("1.代码准备执行");
try {
System.out.println("2.将可能要发生异常的代码进行捕获");
int i=0;
int j=10/i;
System.out.println(j);//这个肯定要出现算术异常
}
catch(ArithmeticException e) {
System.out.println("3.将算术异常ArithmeticException进行处理了,并return返回值");
return -1;
}
catch(Exception e) {
System.out.println("4.看看我有没有执行,我是不会执行的,因为上一个catch中已经return了");
}
finally {
//finally里面的代码块一定会执行,相当于在return之前执行
System.out.println("5.finally里面的代码是我一定一定要执行的");
}
// i++;try里面的局部变量不能在外面使用
//这一段,是不会运行的,因为在上面catch中进行异常处理的时候return了,相当于退出了方法
//但是finally中的代码是一定会执行的
System.out.println("6.异常捕获了,但是在处理的时候return了,我执行不了");
return 0;
}
//直接退出了,finally{}里面的代码也没法运行。执行了123
public static void test3() {
System.out.println("1.开始执行");
try {
System.out.println("2.进行异常捕获");
String name=null;
name.getClass();
}
catch(NullPointerException e) {
System.out.println("3.进行异常处理");
System.exit(0);
}
finally {
System.out.println("4.finally里面的代码是一定会执行的");
}
}
}
3.5.try{}finally{}
既然有try{} catch{},那么是不是也有 try{} finally{}。没错有的,try{}是可以单独和catch{}一起存在的。
那么这样结合的代码是什么意思呢?就是只有异常捕获没有异常处理,然后在异常终止前把finally{}里面的代码执行完再结束。
那能不能 try{}单独存在?不行。try{}要么就与catch{}一起,要么就跟finally{}一起,要么这3就一起来,而且顺序就只能是try{} catch{} finally{}.
4.异常的另一种处理机制—throws 和throw
4.1.throws
4.1.1.throws的作用:
throws关键字时在方法的声明时使用的,表示此方法不对异常进行处理,一旦产生异常直接交给方法的调用者进行处理,当然方法的调用者也可以继续甩锅。
throws也叫做自动抛出异常,就是当程序再运行时遇到不符合规范的代码或结果时,会产生异常。
4.1.2.throws与try{}catch{}的区别
try{}catch{} : 发现异常自己直接在catch{}中处理掉。任劳任怨
throws : 发现异常不自己处理,直接交给调用者处理。甩锅侠
4.1.3.throws抛出非受检异常类型(运行时异常)与抛出受异常类型的区别
a.当throws抛出运行时异常时,因为运行时异常是可处理可不处理,所以调用它的方法可以不对异常进行处理。
b.当throws抛出的是受检异常是,因为受检异常是必须进行异常处理的,所以调用它的方法也必须进行异常处理。那么既可以用try{}catch{}进行异常处理掉,也可以继续throws继续抛给调用者处理。最终可以抛到 JVM进行处理。
注意:Exception同时也是一个受检异常类型
调用的运行时异常:
//try{}catch{}:这是一种异常处理方式.发现异常自己直接处理掉,任劳任怨
//throws:也是一种异常处理方式,但是它不自己进行处理,直接甩锅给调用者处理
//throws:表现处异常的一种可能性,不一定会抛出异常
//throw:主动抛出一个异常,一定会抛出
//如果是 非受检异常,也就是运行时异常,因为它是可处理可不处理,所以调用它的方法可以不进行处理
public class TestThrows {
public static void main(String[] args) throws NullPointerException {
System.out.println("1.开始");
//调用了test()方法,可以不进行处理
test();
System.out.println("2.结束");
}
//非受检异常抛出throws,调用者可以不进行处理
//throws必须在方法名后面声明
public static void test() throws NullPointerException{
String name=null;
name.getClass();//肯定会报NullPointerException,在方法抛出
}
}
调用的受检异常:
//如果方法是存在检查异常,那么一定要进行处理,同时,调用该方法的,也要进行处理
//处理方式:1.try{}catch{}自己处理
// 2.继续throws甩锅给别人处理
public class TestThrows2 {
//main也不自己处理,直接甩给JVM处理
public static void main(String[] args) throws InterruptedException {
//调用了test(),该方法存在受检异常,我必须要处理
//1.try{}catch{}自己处理
//2.直接throws甩锅给JVM处理
/*System.out.println("1.开始");
try {
test1();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2.结束");*/
System.out.println("1.开始");
test1();
System.out.println("2.结束");
}
//检查异常,一定要处理
//1.直接甩锅,throws给调用者
public static void test1() throws InterruptedException {
//这是一个受检异常,一定要进行处理,不然不给你运行
Thread.sleep(1000);// InterruptedException
}
}
4.2.throw
4.2.1.throw作用
throw:手动抛出异常。上面说的throws是一种自动抛出异常。那么什么是自动什么是手动呢?
a. throws自动抛出异常:表现处异常的一种可能性,不一定会抛出异常。如果你不明确这个代码会不会抛出异常,那你就可以使用throws,如果有错误就抛,没有就当作不存在。
b.throw手动抛出异常:就是你认为这个地方有异常,你就使用throw手动抛出,一定会抛出。直接写在方法里面你想要抛出错误的地方。
4.2.2.throw抛出非受检异常类型(运行时异常)与抛出受异常类型的区别
其实跟throws的区别是一样的。
**a.**当throw自动抛出运行时异常时,因为运行时异常是可处理可不处理,所以调用它的方法可以不对异常进行处理。
**b.**当throw自动抛出的是受检异常时,因为受检异常是必须进行异常处理的,所以调用它的方法也必须进行异常处理。那么既可以用try{}catch{}进行异常处理掉,也可以用throws抛给调用者处理。最终可以抛到 JVM进行处理。
throw一个运行时异常
//throw:主动抛出一个异常,写在方法里面
//throw new NullPointerException
//throw抛出一个运行时异常(非受检异常)
public class TestThrow {
public static void main(String[] args) {
test(null);
}
//抛出一个运行时异常(非受查异常)
public static void test(String str) {
System.out.println("1.开始执行:");
if(str==null) {
System.out.println("2.你居然传了一个null给我,那我主动抛出一个NullPointerException异常");
//作用在方法里面,主动抛出异常
//NullPointerException有一个有参构造方法,可以传入参数
throw new NullPointerException("空指针异常了吧");
//System.out.println("3.上面抛出异常了,相当于return了,我不能在throw后面执行了");
}
}
}
throw抛出一个受检异常
//throw 主动抛出一个 受检异常
//当前方法和调用者都 必须抛出
public class TestThrow2 {
public static void main(String[] args) throws Exception {
//必须进行异常处理。2种方式
/*try {
test(null);
} catch (Exception e) {
// TODO Auto-generated catch block
//e.printStackTrace();
}*/
test(null);
}
public static void test(String str) throws Exception {
System.out.println("1.开始执行");
if(str==null) {
System.out.println("2.你居然传了一个null给我,那我主动抛出一个NullPointerException异常");
//作用在方法里面,主动抛出异常
//有参构造器
throw new Exception("空指针异常了吧");
//System.out.println("3.上面抛出异常了,相当于return了,我不能在throw后面执行了");
}
//抛出异常就相当于结束方法了,当然没有执行到这里
System.out.println("3.看看我有没有执行");
}
}
我认为虽然throws和throw可以抛出异常,但是为了程序的稳定性,最好最后还是要使用try{}catch{}进行异常处理最好。
5.自定义异常
5.1.在java中,根据实际情况可以自己自定义一个异常类,但是必须继承Exception类或其子类。
5.2.格式
class MyExceptionDIY extends Exception{}
5.3.用法
其实用法就跟JDK自带的那些异常类一样,只不过现在你抛出的异常类换成你自己定义的罢了。不过要注意的是,如果你自定义的异常类继承的父类是一个运行时异常类(RuntimeException)时,那么你自定义的类也是一个运行时异常类。如果你继承的是一个受检异常类,那么你自定义的类也是一个受检异常类。用法都跟上面讲到的一样。
自定义运行时异常类:
//自己动手创建一个异常类
//所有的异常类都继承自Exception类,所以自定义的异常类也需要继承Exception或它的子类如(RunTimeException)
//继承的是一个运行时的异常,那么该自定义异常也是一个运行时异常
public class MyRunExceptionDIY extends RuntimeException{
//定义无参构造方法
public MyRunExceptionDIY() {
}
//定义有参构造方法
public MyRunExceptionDIY(String str) {
super(str);
}
}
import java.util.Random;
//自定义运行时异常:不需要显式处理
//自定义检查异常: 必须显示处理
public class TestRunExceptionDIY {
public static void main(String[] args) {
test1();
}
public static void test1() {
Random r=new Random();
int n=r.nextInt(100);
if(n%2==0) {
//抛出自定义无参数异常对象
throw new MyRunExceptionDIY();
}
else {
//抛出自定义有参异常对象
throw new MyRunExceptionDIY("自定义有参数的异常类型");
}
}
}
自定义一个受检异常类
//Exception也是一个受检异常
public class MyCheckedExceptionDIY extends Exception{
//无参构造器
public MyCheckedExceptionDIY() {
}
//有参构造器
public MyCheckedExceptionDIY(String str) {
super(str);
}
}
import java.util.Random;
public class TestCheckedExceptionDIY {
public static void main(String[] args){
//必须进行处理 2种方式
try {
test();
} catch (MyCheckedExceptionDIY e) {
//e.printStackTrace();
System.out.println("对自定义异常进行处理");
}
}
//测试自己定义的检查异常类
public static void test() throws MyCheckedExceptionDIY {
Random r=new Random();
int n=r.nextInt(100);
if(n%2==0) {
//抛出自定义无参数异常对象
//因为是受检异常,无论是自己定义的还是jdk定义的,都需要进行处理
throw new MyCheckedExceptionDIY();
}
else {
//抛出自定义有参异常对象
throw new MyCheckedExceptionDIY("自定义有参数的异常类型");
}
}
}
6.异常在继承关系中的体现
6.1.子类重写父类的带有异常声明的方法的时候
**1.运行时异常的声明:**子类的重写的方法可以随便抛出异常类型,无论异常类型范围比父类方法的范围大还是小
**2.编译异常(受检异常、检查异常):**子类的重写的方法的异常类型声明的范围一定不能比父类方法的异常类型范围广。就是 重写方法的异常类型是父类方法异常类型的子类或是同级
3.如果父类方法没有抛出任何类型的异常,那么子类重写该方法时只能抛出运行时异常类型。
总结一句话就是:运行时异常在继承关系中可以乱来随便抛;检查异常在继承关系中子类的异常类型必须比父类的异常类型更小或相等
父类方法抛出运行时异常:
import javax.naming.directory.AttributeInUseException;
//继承关系中异常类型的特点:
/*
* 1.如果父类没有抛出任何类型异常,子类重写方法,只能声明为运行时异常类型,不能声明编译类型异常
* 2.如果父类方法中声明异常类型为 运行时异常类型 时,那么子类重写该方法可以抛出任何运行时异常类型,不考虑范围的大小
* 3.如果父类方法中声明异常类型为 检查异常类型 时,那么子类重写该方法声明的异常类型范围一定不能比父类方法的异常类型范围广,
* 就是 重写方法的异常类型是父类方法异常类型的子类或是同级
*/
public class TestRunTimeExceptionExtends {
public static void main(String[] args) throws Exception {
Animal an = new Dog();
an.sleep("day");
}
}
// 创建一个普通动物类
class Animal {
public void sleep(String str) throws NullPointerException {
// 如果是白天,就报异常
if (str.equals("day")) {
System.out.println("父类抛出运行时类型异常");
throw new NullPointerException();
}
}
}
// 创建一个子类dog类
class Dog extends Animal {
// 重写父类sleep方法
// 如果父类没有抛出任何类型异常,子类重写方法,只能声明为运行时异常类型,不能声明编译类型异常
public void sleep(String str) throws ArithmeticException{
if (str.equals("day")) {
System.out.println("子类能抛出任何类型的运行时类型异常");
throw new ArithmeticException();
}
}
}
父类方法抛出受检异常
import java.io.FileNotFoundException;
import java.io.IOException;
public class TestRunTimeExceptionExtends2 {
public static void main(String[] args) {
}
}
//创建一个普通动物类
class Animal2 {
public void sleep(String str) throws IOException {
// 如果是白天,就报异常
if (str.equals("day")) {
System.out.println("父类抛出Exception类型异常");
throw new IOException();//不要忘记Exception异常也是一种检查异常,必须抛出
}
}
}
//创建一个子类dog类
class Dog2 extends Animal2 {
// 重写父类sleep方法
// 如果父类没有抛出任何类型异常,子类重写方法,只能声明为运行时异常类型,不能声明编译类型异常
//抛出的检查异常类型必须比父类的异常类型范围小或相等
//这里子类不能抛出Exception异常,因为Exception异常是IOException异常类型的父类
public void sleep(String str) throws IOException{
if (str.equals("day")) {
System.out.println("子类只能声明为范围更小的异常类型");
throw new IOException();
}
}
}
常见的异常类型图
异常处理机制流程图
额画的有点乱。