1-异常

2022-10-27 13:24:22 浏览数 (2)

异常的概念

异常是程序在“编译”或者“执行”的过程中可能出现的问题

异常应该尽量提前避免,但是无法做到绝对避免,异常的可能情况太多,开发中只能提前干预。

异常一旦出现,如果没有提前避免,程序就会退出JVM虚拟机而终止,开发中异常是需要提前处理的。研究异常,并且避免异常,然后提前处理异常,体现的是程序的安全性,健壮性

Java会为常见的代码异常都设计一个类来代表

Error

错误的意思,严重错误Error,无法通过处理的错误,一旦出现,程序员难以进行修改,一般只能重启系统,优化项目。例如:内存崩溃,JVM本身崩溃

Exception

异常类,是开发中代码在编译或者执行过程中可能出现的错误,它是需要提前处理的,以便程序更加健壮

  1. 编译时异常:继承自Exception的异常或者其子类,编译阶段就会报错,必须程序员进行处理,否则代码编译无法通过
  2. 运行时异常,继承自RuntimeException的异常或其子类,编译阶段不报错,运行阶段出现,运行时异常可处理也可不处理

运行时异常

常见的运行时异常

  • 数组索引越界异常:ArrayIndexOutOfBoundsException
  • 空指针异常:NullPointerException(直接输出没有问题,但是调用空指针变量的功能就会报错)
  • 类型转换异常:ClassCastException
  • 迭代器遍历没有此元素异常:NoSuchElementException
  • 数学操作异常:ArithmeticException
  • 数字转换异常:NumberFormatException
代码语言:javascript复制
public class test1 {


    public static void main(String[] args) {
        //数组索引越界异常:ArrayIndexOutOfBoundsException
        int[] array={10,20,30};
        System.out.println(array[3]);
        //空指针异常:NullPointerException
        String str1=null;
        System.out.println(str1); //直接输出没有问题
        System.out.println(str1.length());  //但是调用空指针变量的功能就会报错
        //类型转换异常:ClassCastException
        Object name="Leslie";
        Integer s=(Integer) name;
        //迭代器遍历没有此元素异常:NoSuchElementException
        //数学操作异常:ArithmeticException
        int c=10/0;
        //数字转换异常:NumberFormatException
        String num="23a";
        Integer n=Integer.valueOf(num);
    }
}

编译时异常

代码编译阶段就会报错
代码语言:javascript复制
package ExceptionTest;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionDemo {
    //这里如果不设置throws ParseException,就会抛出编译异常
    //原因就在于parse操作很容易不规范
    public static void main(String[] args) throws ParseException {
        String date="2015-01-12 10:23:21";
        SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d =sdf.parse(date);
        System.out.println(d);
    }
}

异常的默认处理机制

异常产生后的默认处理过程是自动处理过程

  1. 默认会在出现异常的代码处自动创建一个异常对象:ArithmeticException等
  2. 异常会从方法中出现的点这里先抛出给调用者,各层调用者不断抛出最终抛出给JVM虚拟机
  3. 虚拟机接收到异常对象后,现在控制台输出/打印异常栈信息数据
  4. 直接从当前执行的异常点终止当前程序
这种默认异常处理机制并不好(对于项目开发),一旦出现真的异常,会立即导致程序的死亡

编译时异常处理方式

方式一:直接抛出错误

代码语言:javascript复制
方法 throws 异常1,异常2,...{
    ...
}

//上述方式,需要对异常逐个抛出,当异常很多时并不方便
//一般建议采用throws Exception这种方式,直接抛出根类异常

方法 throws Exception{
    ...
}
代码语言:javascript复制
package ExceptionTest;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionDemo1 {
    public static void main(String[] args) throws ParseException {
        System.out.println("----程序开始----");
        parseDate("2020-05-15 08:54:05");  //如果程序正确可以输出结果
        parseDate("2020/05/15/08/54/05");  //如果程序错误依旧会报错并直接终止程序
        System.out.println("----程序结束----");
    }
    public static void parseDate(String time) throws ParseException {
        SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d=sdf.parse(time);
        System.out.println(d);
    }

}

这种异常处理方式并不好,整个过程中只是逐层把异常向上层抛出,本质与默认处理方式相同,只是避免了编译阶段的报错,让程序能够正常编译。但一旦出现错误,仍然会导致JVM虚拟机终止程序。这种方法适用于程序较小,报错原因较易排查的项目

方式二:在出现异常的地方自己处理,谁出现谁处理

采用监视捕获异常的方式,即try catch
代码语言:javascript复制
package ExceptionTest;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionDemo1 {
    public static void main(String[] args)  {
        System.out.println("----程序开始----");
        parseDate("2020-05-15 08:54:05");  //程序正确
        parseDate("2020/05/15/08/54/05");  //程序错误
        System.out.println("----程序结束----");
    }
    
    public static void parseDate(String time) {
        try{
            SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date d=sdf.parse(time);
            System.out.println(d);
        }
        catch (ParseException e){  //可以并列多个异常,或是直接用Exception监视所有可能异常
            e.printStackTrace();  //打印异常栈信息,不会引起程序死亡
        }
    }

}

(在企业开发中,一般直接采用监视Exception根类的方式,这样可以监视并打印所有可能的异常)

第二种处理异常的方式可以处理异常,并且出现异常后代码也不会死亡而是正常执行,但这种方式也存在不足之处,在没有返回值的情况下,下层独自监视处理异常,导致上层不了解下层的处理结果而是盲目的运行程序

方式三:在出现异常的地方把异常逐层抛出给最外层调用者,最外层调用者几种捕获处理(规范做法)

代码语言:javascript复制
package ExceptionTest;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionDemo1 {
    public static void main(String[] args)  {
        System.out.println("----程序开始----");
        try {
            parseDate("2020-05-15 08:54:05");  //程序正确
            parseDate("2020/05/15/08/54/05");  //程序错误
        } catch (ParseException e) {
            e.printStackTrace();
        }
        System.out.println("----程序结束----");
    }

    public static void parseDate(String time) throws ParseException {
        SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d=sdf.parse(time);
        System.out.println(d);
    }

}

这种方案最外层调用者可以知道底层执行的情况,同时程序在出现异常后也不会立即死亡,这是理论上最好的方案

运行时异常的处理机制

运行时异常的处理规范:直接在最外层捕获处理即可,底层会自动抛出

代码语言:javascript复制
package ExceptionTest;

public class ExceptionDemo2 {
    public static void MyDivision(int a,int b){
        System.out.println(a/b);
    }

    public static void main(String[] args) {
        System.out.println("----程序开始----");
        try{
            MyDivision(10,0);
            System.err.println("运行成功!");
        }catch (Exception e){
            e.printStackTrace();
            System.err.println("运行失败!");
        }
    }
}

异常捕获的本质就是直接将上层传输的异常捕获,然后输出它的异常栈信息,避免异常传输到JVM虚拟机,终止程序。从而保证了程序能够顺利执行完毕


finally关键字

用在捕获处理的异常格式中的,放在捕获异常的最后面
代码语言:javascript复制
try{
    //可能出现异常的代码
}catch(Exception e){
    e.printStackTrace();
}finally{
    //无论代码是否出现异常还是正常执行
    //最终一定要执行这里的代码
}

在捕获异常中,try固定出现一次,catch出现0~N次(当存在finally时,可以省略catch,其他情况下不可以),finally可以出现至多一次

finally作用

可以在代码执行完毕之后进行资源(资源都实现了Close able接口,自带close()方法)的释放操作

代码语言:javascript复制
package ExceptionTest;

public class ExceptionDemo2 {
    public static double MyDivision(double a,double b){
        try{
            return a/b;
        }
        catch (Exception e){
            return 0.0;
        }
        finally {
            System.out.println("====finally被执行====");
        }
    }

    public static void main(String[] args) {
        System.out.println(MyDivision(10,2));
        /* 这里就可以看出finally的强制执行效果
           程序正常执行,原本应该进入try,在try中会执行return语句
           return语句理应终止程序运行,但程序还是被强制执行了finally中的内容
           也就是在这里实际执行流程是在执行到return语句后被强制跳转到finally语句,先执行其中内容
           
           因此,在finally语句中加入return语句是十分危险的
           因为无论任何情况,只要finally中包含return语句,最后都只会执行finally中的return语句
           
         */
         
         
        //return 233; 
    }
}

自定义异常

自定义编译时异常

代码语言:javascript复制
package ExceptionTest;


/*
* 自定义编译时异常:
* 1. 继承Exception
* 2. 重写构造器
* */


public class MyException extends Exception{
    public MyException() {
    }

    public MyException(String message) {
        super(message);  //调用父类构造器
    }

    public MyException(String message, Throwable cause) {
        super(message, cause);
    }

    public MyException(Throwable cause) {
        super(cause);
    }

    public MyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

调用自定义异常

代码语言:javascript复制
package ExceptionTest;

public class ExceptionDemo3 {
    public static void main(String[] args) {
        try {
            ageControl(12);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    public static void ageControl(int age) throws MyException {
        if(age>60 || age<18){
            throw new MyException("/ age is illegal");
        }
        else{
            System.out.println("age is " age);
        }
    }
}

自定义的编译时异常,在调用时会直接报错,所以直接用throws抛出给上层即可

注意:

throws用在方法上,用于抛出方法中的异常给调用方

throw用在出现异常的地方,用于创建异常对象且立即从此处抛出

自定义运行时异常的方式跟自定义编译时异常基本一致,只是运行时异常需要继承的是RuntimeException,并且在编译阶段并不会报错


0 人点赞