Java异常

简介: Java异常

异常概念


就像人会生病一样,人生病就会产生异常行为,比如感冒发烧等,同样的,当程序出错的时候,程序执行过程中也会发生的不正常行为。

在Java中,将程序执行过程中发生的不正常行为称为异常。


举几个我们常见的例子:


  1. 算术异常:
System.out.println(10 / 0);  //在我们数学中,0是不能做被除数数的,这里也一样

当我们输入这段代码并执行时,会产生如下结果:

13045311a29843c09e826bfe85caa3f4.png


2. 数组越界异常:

就是数组越界访问抛出的异常:

int[] arr = {1, 2, 3, 4};
    System.out.println(arr[4]);  //数组元素下标最大为3,没有4

结果:

27034c73879243c29f5a972b7a042810.png


3. 空指针异常

int[] arr = null;
    System.out.println(arr.length);

b9a0a7c405e548de8e4d6d40e94fab38.png

我们以后见到最多的应该就是空指针异常和数组越界异常了。

我们可以看出,对于不同类型的异常,都有与其对应的类来进描述。


异常的体系结构


大致如下:

ffb00ad47962485993d7de140670a7ae.png


  1. Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception
  2. Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表:StackOverflowError和OutOfMemoryError,一旦发生回天乏术。
  3. Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。我们平时所说的异常就是Exception。


异常的分类


也就是图中Exception派生出的子类,图中的受查异常与非受查异常是什么意思呢?

异常可能在编译时发生,也可能在程序运行时发生,根据发生的时机不同,可以将异常分为:受查异常与非受查异常。


在程序编译期间发生的异常,称为编译时异常,也称为受查异常。

编译时异常是表示必须在编写程序的时候预先对这种异常进行处理,如果不处理编译器就报错。编译时异常发生概率较高。

cd78191d40874c8ea87781eed7a79a99.png


这里我们调用Object的clone方法报错了,是因为没有预先处理异常,如下即可:


cbdc5d337ca1414b894af83d10db0c8a.png


在程序执行期间发生的异常,称为运行时异常,也称为非受查异常

RunTimeException以及其子类对应的异常,都称为运行时异常。比如:NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException。


注:编译时出现的语法性错误,不能称之为异常,这是 “编译期” 出错。而运行时指的是程序已经编译通过得到 class 文件了, 再由 JVM 执行过程中出现的错误。


异常的抛出 throw


Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。具体语法如下:


throw new XXXException("异常产生的原因");


举个简单例子:已知数组下标,求该下标的元素。


public class Test {
    public static int getElement(int[] array, int index){
        if(null == array) {
            throw new NullPointerException("传递的数组为null");
        } if(index < 0 || index >= array.length){
            throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
        }
        return array[index];
    }
    public static void main(String[] args) {
        int[] array = {1,2,3};
        getElement(array, 3);
    }
}


注意几点:


  1. throw必须写在方法体内部
  2. 抛出的对象必须是Exception 或者 Exception 的子类对象
  3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,也就是非受查异常,则可以不用处理,直接交给JVM来处理
  4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
  5. 异常一旦抛出,其后的代码就不会执行


异常的处理


异常的出理方式主要有两种:异常声明throws 以及 try-catch捕获处理。


格式:


修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{
}

我们可以看到,一个方法可以声明多个异常,那为什么要声明异常呢?

当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常。


public class Test {
    public static void func() throws ClassNotFoundException {
    //throws ClassNotFoundException 该声明表示该方法在执行过程中可能会出现该异常
    //属于编译时异常
        System.out.println("func!");  
    }
    public static void main(String[] args) {
        func();    //这里报红,这里func方法将它的异常抛给了main方法,
                  //所以main方法必须对其进行处理,不处理编译器就报错
    }             
}


怎么解决呢?我们有两种方法:


方法一:throws抛给给调用者(谁调用了,谁处理,调用者知道是什么异常)


public class Test {   
    public static void func() throws ClassNotFoundException {
        System.out.println("func!");  
    }
    public static void main(String[] args) throws ClassNotFoundException {
     //这里将异常抛给了main函数的调用者JVM,让JVM来处理异常
        func();  
    }
}


JVM处理结果就是程序中断,抛出该异常对象,本质上并没有对异常进行处理,而下面的方法就是对其进行处理。


方法二:try…catch 捕捉处理(调用者不知道是什么异常)


public class Test {
    public static void main(String[] args) {
        try {
            func();    //可能出现异常的代码
        } catch (ClassNotFoundException e) {   //对该异常进行捕捉
            e.printStackTrace();      //处理该异常
        }
    }
    public static void func() throws ClassNotFoundException {
        System.out.println("func!");
    }
}


使用throws注意事项


  1. throws必须跟在方法的参数列表之后
  2. 声明的异常必须是 Exception 或者 Exception 的子类
  3. 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可。
  4. 调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出


try … catch 捕获并处理


举个栗子:


public static void main(String[] args) {
        try {
            func(null);  //如果这里发生异常,那么就看下面的catch是否能捕获异常,
            //如果未发生异常,则跳过catch
        } catch (CloneNotSupportedException e) {
        //如果catch里捕获的异常类型与try里抛出的异常类型相同,则该异常被捕获,然后程序进入该catch里 
            System.out.println("捕获到了 CloneNotSupportedException 异常");
        } //catch(异常类型 e){  
          //如果可能程序出现多个异常,则多加几个catch语句捕获异常
    // 对异常进行处理
  //}finally{
    // 此处代码一定会被执行到,无论是否发生异常
  //}
        //程序处理完异常后就执行正常代码
        System.out.println("正常逻辑");
    }
    public static void func(int[] array) throws CloneNotSupportedException {
        if(array == null) {
            throw new CloneNotSupportedException();
        }
    }


输出:


67b89bd474664fcc93461624e3331790.png


总结:


  1. try块内抛出异常位置之后的代码将不会被执行
  2. 如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序----异常是按照类型来捕获的。
  3. try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获。如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch
  4. 可以通过一个catch捕获所有的异常,即多个异常,一次捕获(不推荐)

由于 Exception 类是所有异常类的父类. 因此可以用这个类型表示捕捉所有异常.


获取异常信息


在catch 语句里我们有三种处理方法,以上述代码为例:


public static void main(String[] args) {
        try {
            func(null);
        } catch (CloneNotSupportedException e) { 
          // 异常的处理方式
    //System.out.println(e.getMessage()); // 只打印异常信息
    //System.out.println(e); // 打印异常类型,异常信息
    e.printStackTrace(); // 打印信息最全面
        }
        System.out.println("正常逻辑");
    }
    public static void func(int[] array) throws CloneNotSupportedException {
        if(array == null) {
            throw new CloneNotSupportedException();
        }
    }


关于finally


有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。另外,因为异常会引发程序的跳转,可能导致有些语句执行不到,finally就是用来解决这个问题的。


try{
  // 可能会发生异常的代码
  }catch(异常类型 e){
  // 对捕获到的异常进行处理
  }finally{
  // 此处的语句无论是否发生异常,都会被执行到
  }


自定义异常


系统定义的异常主要是用来处理一些常见的运行错误,对于某端代码自己的特有错误,需要程序员自己来创建自己的异常类,来处理。


实现方式:


  1. 自定义异常类,然后继承自Exception 或者 RunTimeException
  2. 实现一个带有String类型参数的构造方法,参数含义:出现异常的原因


注意:

自定义异常通常会继承自 Exception 或者 RuntimeException

继承自 Exception 的异常默认是受查异常

继承自 RuntimeException 的异常默认是非受查异常


举个例子,实现一个用户登录功能:


public class Test {
    public static void fun(String name2, String code2) throws PasswordException, UsernameException {
        String name = "张三";
        String code = "666666";
        if(!name2.equals(name)) { //如果输入的姓名或密码错误,则抛出异常
            throw new PasswordException("用户名错误!");
        }
        if(!code2.equals(code)) {
            throw new UsernameException("密码错误!");
        }
        System.out.println("输入成功!!!");
    }
    public static void main(String[] args) throws PasswordException, UsernameException {
        Scanner scanner = new Scanner(System.in);
        String name = scanner.nextLine();
        String code = scanner.nextLine();
        fun(name,code);
    }
}


public class PasswordException extends Exception{
    public PasswordException(String message) {
        super(message);
    }
}


public class UsernameException extends Exception{
    public UsernameException(String message) {
        super(message);
    }
}

相关文章
|
11月前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
312 1
|
4月前
|
Java 程序员 数据库连接
我们详细地讲解一下 Java 异常及要如何处理
我是小假 期待与你的下一次相遇 ~
|
11月前
|
Java API 调度
如何避免 Java 中的 TimeoutException 异常
在Java中,`TimeoutException`通常发生在执行操作超过预设时间时。要避免此异常,可以优化代码逻辑,减少不必要的等待;合理设置超时时间,确保其足够完成正常操作;使用异步处理或线程池管理任务,提高程序响应性。
498 13
|
11月前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
178 1
|
7月前
|
SQL Java 中间件
【YashanDB知识库】yasdb jdbc驱动集成BeetISQL中间件,业务(java)报autoAssignKey failure异常
在BeetISQL 2.13.8版本中,客户使用batch insert向yashandb表插入数据并尝试获取自动生成的sequence id时,出现类型转换异常。原因是beetlsql在prepareStatement时未指定返回列,导致yashan JDBC驱动返回rowid(字符串),与Java Bean中的数字类型tid不匹配。此问题影响业务流程,使无法正确获取sequence id。解决方法包括:1) 在batchInsert时不返回自动生成的sequence id;2) 升级至BeetISQL 3,其已修正该问题。
【YashanDB知识库】yasdb jdbc驱动集成BeetISQL中间件,业务(java)报autoAssignKey failure异常
|
7月前
|
SQL druid Oracle
【YashanDB知识库】yasdb jdbc驱动集成druid连接池,业务(java)日志中有token IDENTIFIER start异常
客户Java日志中出现异常,影响Druid的merge SQL功能(将SQL字面量替换为绑定变量以统计性能),但不影响正常业务流程。原因是Druid在merge SQL时传入null作为dbType,导致无法解析递归查询中的`start`关键字。
|
8月前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
479 14
|
8月前
|
缓存 Java 应用服务中间件
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
1325 5
|
11月前
|
IDE 前端开发 Java
怎样避免 Java 中的 NoSuchFieldError 异常
在Java中避免NoSuchFieldError异常的关键在于确保类路径下没有不同版本的类文件冲突,避免反射时使用不存在的字段,以及确保所有依赖库版本兼容。编译和运行时使用的类版本应保持一致。
340 8
|
11月前
|
Java 编译器
如何避免在 Java 中出现 NoSuchElementException 异常
在Java中,`NoSuchElementException`通常发生在使用迭代器、枚举或流等遍历集合时,尝试访问不存在的元素。为了避免该异常,可以在访问前检查是否有下一个元素(如使用`hasNext()`方法),或者使用`Optional`类处理可能为空的情况。正确管理集合边界和条件判断是关键。
293 6