我的Java学习笔记(20)—java的异常机制、异常分类以及对应异常的处理方式

软件运行过程常有异常的问题,就是Exception,就是例外。

异常怎么处理?

1.自己考虑:

面对程序里的异常,我们如果要处理,那么开发者自己可以考虑很多种异常情况,然后用if-else语句来解决异常问题。

比如我们来看这个伪代码:

if(“d:/a.txt”这个文件存在){
        if(e盘的空间大于a.txt文件长度){
             if(文件复制一半IO流断掉){
                 停止copy,输出:IO流出问题!
             }else{
             copyFile("d:/a.txt","e:/a.txt");
             }
         }else{
             输出:e盘空间不够存放a.txt!
         }
     }else{
         输出:a.txt不存在!
     }

其中真正有用的代码只有一行,那么这段代码就非常臃肿,并且业务逻辑的代码就重点消失了,并且异常的情况实在太多,开发者自己能想到的又有多少呢?

所以我们有了第二种异常处理的方法:

2.java提供的异常处理机制

开发者不需要自己通过if-else考虑这么多问题,java来提供异常处理机制,它将异常处理代码和业务代码分离,使程序更优雅,更好的容错性和高健壮性。

上面的一个拷贝文件操作的伪代码我们可以修改成这样:

       try {
            copyFile("d:/a.txt","e:/a.txt");
       } catch (Exception e) {
            e.printStackTrace();
       }
 }

异常机制的本质是当程序出现错误,程序安全退出的机制

3.java处理异常的过程:

1).抛出异常:

在执行一个方法的时候,如果发生异常,则这个方法生成代表该异常的一个对象,停止当前执行路径,并把异常对象交给JRE(运行时环境)

2).捕获异常:

JRE得到该异常之后,寻找相应的代码来处理该异常,JRE在方法的调用栈中查找,从生成异常的方法开始回溯,直到找到相应的异常处理代码为止。

具体的一个异常处理结果长什么样呢,我们先来看一个很简单的例子:

public static void main(String[] args) {
       int a=1/0;
       System.out.println(a);
 }

在这里插入图片描述
可以看到,抛出了一个叫算术异常的,异常,0不能做分母。那这里执行代码到那一句的时候,发生了异常,就会封装成一个对象,交给jre,然后这个算术异常的包里的代码就捕获了异常,并且抛出了。

4.异常分类

JDK中定义了很多异常类,这些类对应了各种各样可能出现的异常事件,所有异常对象都是派生于Throwable类的一个实例。如果内置的异常类不能满足需要,还可以创建自己的异常类。

Java对异常进行了分类,不同类型的异常类分别用不同的Java类表示,所有异常类的根类为Java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception。java异常类的层次结构如下图:
在这里插入图片描述
这两个派生的子类中,Error的意思是错误,表示运行中应用程序性出现较严重的错误,与开发人员的代码编写和执行操作无关,是JVM出现了问题(例如当JVM不再有继续执行操作需要的内存资源时,会出现OutOfMemoryError,这个发生时,JVM一般会选择线程终止)。对于Error来说,是不用我们开发人员管的,也不是我们管的了的,一般来说如果遇到Error的话,重启虚拟机就可以。

而Exception是我们需要关注的,也就是异常,是开发人员需要关注的。

5.处理异常

5.1 RuntimeException运行时异常

派生于RuntimeException的异常,如被0除,数组下标越界、空指针等,产生比较繁琐,处理麻烦,如果显式的声明或捕获将会对程序可读性和运行效率影响很大。因此由系统自动检测并将他们交给缺省的异常处理程序(用户可不必对其处理)

这类异常通常是由于编程错误导致的,所以在编写程序的时候,并不要求必须使用异常处理机制来处理,经常要通过增加逻辑处理来避免这些异常。

比如上面举的用0做分母的例子就是一个逻辑上出了问题的异常,出现的是**(ArithmeticException)**。

我们需要修改的就是代码的逻辑,加上判断,当分母不是0的时候才进行除法操作。

       int a=1;
       int b=0;
       if (b!=0) {
            System.out.println(a/b);
       }

又比如空指针异常**(NullPointerException)**:

       String string=null;
       string.length();//对象为空,没有方法

处理的方法就是加上指针不为空的判断:

       String string=null;
       if (string!=null) {
            System.out.println(string.length());
       }

还有ClassCastException异常,一般出现在引用数据类型的时候出现强制转型的错误:

比如我定义一个Cat和Dog类型都是继承了同一个父类Animal类,然后在声明一个新对象的时候将声明的Cat对象强制转换成Dog对象:

class Animal{
     
}
class Dog extends Animal{
     
}
class Cat extends Animal{
     
}

然后:

   Animal animal=new Dog();
   Cat cat=(Cat)animal;

就会告诉你不能这样强制转型:

在这里插入图片描述
下一个是数组越界:ArrayIndexOutOfBoundsException异常

比如长度是n的数组,后面进行操作的时候进行对Array[n]进行了操作,就会跑出这样的异常,处理方式一样是加上一个index和array.length的大小比较判断。

如果使用包装类将字符串转换成基本数据类型时候,字符串的格式不正确,就会出现数字格式异常**(NumberFormatException)**。

比如下面的代码:

           String string2="123456abc";
           System.out.println(Integer.parseInt(string2));

其中static int parseInt(String s) 将字符串参数解析为带符号的十进制整数。这个string2的格式后有abc因此会抛出并不是数字类型字符串的异常。

数字格式化异常的解决,可以引入正则表达式判断是否为数字。(具体做法不说了)

5.2 Checked Exception 已检查异常(注意中间的空格)

所有不是上一类RuntimeException的都称为Checked Exception,已检查异常(也可以称作编译器异常,注意!是统称为这个,但是并没有这个异常类存在),比如IOException、SQLException以及用户自己定义的Exception等等。这类异常在编译的时候就必须作出处理,不然会无法通过编译。

这种要通过编译器处理,前面的运行时异常是在编译的时候可以通过,但是运行之后会抛出异常,然后要返回去修改逻辑代码;但是这一类是在写代码的时候编译器就会提示的异常,比如我们写下这一段代码:

 public static void main(String[] args) {
       InputStream iStream=new FileInputStream("D:\\a.txt");
 }

在写下的时候编译器就会提示错误:

在这里插入图片描述
可以看到解决方案就是提示的两个操作:

    1. )使用“try/catch”来捕获异常
    1. )使用“throws”声明异常

下面我们仍然用IOException来距离,看一看这两种解决方案的具体做法。

  • 1.)使用“try/catch”来捕获异常

捕获异常是通过三个关键词来实现的,try-catch-finally:

用try来执行一段程序,如果出现异常,系统则抛出一个异常,可以通过它的类型来捕捉(catch)并处理它,最后一步是通过finally语句为异常处理提供一个统一的出口,finally所指定的代码都要被执行(catch语句可以有多条,finally语句最多只能有一条,根据个性需要可有可无)。

在这里插入图片描述
IO目前笔记还没有进行,因此这里的代码示例只用看到结果:

比如:

       try {
            FileReader fileReader=new  FileReader("D:/a.txt");
       } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
       }

这里FileReader的源码里就是抛出了异常:

在这里插入图片描述
因此我们在写的时候的选项可以继续选择抛出,也可以选择用try-catch来打印出异常的信息。

这里编译器会自动帮我们加上选项,可以自动生成try-catch语句。

如果我们想要再读文件之后去一个一个字符读取并打印:

            char c1=(char) fileReader.read();
            System.out.println(c1);

加入这两行代码之后又会提示错误,然后会提示你再用try-catch语句包围,并且根据逻辑顺序,catch的顺序是从子类在前到父类,为了保证每一个catch都有可能执行。改变之后就会变成:

 public static void main(String[] args) {
       try {
            FileReader fileReader=new  FileReader("D:/a.txt");
            
            char c1=(char) fileReader.read();
            System.out.println(c1);
       } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
       } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
       }
 }

按照最规范的写法,还需要加入finally语句的部分,由于这里我们的**fileReader对象是需要关闭的,它的操作涉及到了操作系统底层的文件系统,执行完操作之后这些资源应该要关闭掉,因此我们要用finally来关闭这个对象。**因为try包含了这个对象的定义,到后面语句可能会找不到这个对象,因此我们把new对象的语句放到try外面,先把对象定义为null。

然后在finally语句里又会提示要判断异常,并且这里的逻辑上,关闭这个对象,按照我们说过的一些运行时异常,关闭的时候需要考虑到空指针异常,所以自己再加上一个if判断,最后的代码就会变成这样:

 public static void main(String[] args) {
       FileReader fileReader=null;
       try {
            fileReader=new FileReader("D:/a.txt");
            char c1=(char) fileReader.read();
            System.out.println(c1);
       } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
       } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
       }finally {
            try {
                 if (fileReader!=null) {
                       fileReader.close();
                 }
            } catch (IOException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
            }
       }
 }

就是一个比较完善的异常处理了。

  • 2.)使用“throws”声明异常

当Checked Exception产生,我们不一定要用try-catch来处理,可以依然把异常throws出去。因为在有些情况下,这个方法里不一定要把异常处理掉,而时向上传递给调用他的方法来处理。(就好像我们前面try-catch处理的类里的方法,他们本身不处理,而在我们调用那些方法的时候需要处理)

“我是有异常的,但是可能出现的异常我不处理,谁调我谁处理”

我们把上面try-catch处理读取文件的代码封装起来,把所有的try-catch都改成throws,然后去调用:

 public static void readMyfile() throws IOException {
       FileReader fileReader=null;
            fileReader=new FileReader("D:/a.txt");
            char c1=(char) fileReader.read();
            System.out.println(c1);
                 if (fileReader!=null) {
                       fileReader.close();
                 }
       }

封装成这个方法之后,在main方法里去调用这个方法:

       readMyfile();

会看到依然报错,需要处理,这时候要么用try-catch进行处理,就是调用的时候处理了;要么就继续throws,那么就是又向上一层丢给了main方法, 这个时候main方法也throws了,那就是JRE帮助处理了。

  • 3.)上面还说到了自定义异常:

有些时候JDK里面定义的异常都无法充分描述清楚我们想要表达的问题,那么这种情况下就可以自己创建自己的异常类,也就是自定义异常类。

自定义异常类只需要从Exception类或者它的子类派生一个子类即可。

需要注意的是,上面我们说了Exception下面的异常分类有两个,一个是RuntimeException一个是Checked Exception,如果自定义异常的时候继承的是Exception类,默认是Checked Exception,那就必须对其进行处理了,如果不想处理想要继续throws,那就在自定义异常类的时候继承RuntimeException。

习惯上,自定义异常类应该包含两个构造器,一个是默认的构造器,一个是带有详细信息的构造器:

class IllegalAgeException extends RuntimeException{
     public IllegalAgeException() {
           // TODO Auto-generated constructor stub
     }
 
 public IllegalAgeException(String message){
       super(message);
 }

}

第一个构造器是默认构造器,第二个构造器有参数,一般里面的详细信息可以用父类构造器的方法。像上面例子这样写就完事了。

然后我们就可以在声明一个类的时候定义这一类异常:

class Person{
     private int age;
     public int getAge() {
           return age;
     }
     public void setAge(int age) {
           if (age<0) {
                throw new IllegalAgeException("年龄不能为负数");
           }
           this.age = age;
     }
}

然后我们在main方法里定义一个新Person类对象,传入异常的参数:

 public static void main(String[] args) {
       Person person=new Person();
       person.setAge(-10);
 }

运行起来就会发现:

在这里插入图片描述
抛出的异常是自定义的异常类,信息是自定义的异常信息。

可以注意到这里的那句throw new IllegalException写的有些牵强,其实是没必要那样写的,因为自定义的异常类是继承于RuntimeException,这一类的异常前面说了 ,并不需要用异常处理,而是用业务逻辑来处理,就是那句语句写成输出就行了,而不是写一个throw new IllegalException。

因此我们修改让他直接继承于Exception,这样编译器自己也就会提示需要对他进行处理了,这样Person类就会变成这样:

class Person{
     private int age;
     public int getAge() {
           return age;
     }
     public void setAge(int age) {
           if (age<0) {
                try {
                     throw new IllegalAgeException("age is not  allowed to be under 0");
                } catch (IllegalAgeException e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                }
           }
           this.age = age;
     }
}
5.3遇到问题和异常的处理习惯
  • 1.)读懂,英文也要读懂,总会有一行信息提示到是自己的代码
  • 2.)从大范围到小范围取查找,一样的问题肯定别人也遇到过
  • 3.)尽量独立思考,不然不会增长经验
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值