:Java异常概念处理和自定义异常类

Posted 快乐江湖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了:Java异常概念处理和自定义异常类相关的知识,希望对你有一定的参考价值。

文章目录

一:什么是异常

异常:在Java中,将程序执行过程中发生的不正常行为称之为异常,此时编译器将会报出错误,也即抛出异常。在编写程序时,我们会经常遇到以下异常

①:算术异常

public static void main(String[] args) 
    System.out.println(1 / 0);

②:数组越界异常

public static void main(String[] args) 
    int[] arr = 1, 2, 3;
    System.out.println(arr[3]);

③:空指针异常

 public static void main(String[] args) 
     int[] arr = null;
     System.out.println(arr.length);
 

二:异常体系结构和分类

(1)体系结构

异常体系结构:在Java中,异常就是一个类,名字叫做Throwable

其体系结构如下图所示(部分),其中

  • Throwable:是异常体系顶层类,派生出了ErrorException

  • Error:指的是JVM无法解决的严重问题,例如JVM内部错误、资源耗尽等等。StackOverflowOutOfMemoryError是其典型代表

  • Exception:也即异常;异常产生后程序员可以通过代码进行出来使其正常运行

(2)异常分类

异常分类:根据异常发生时机的不同,可以将异常分为如下两类

  • 编译时异常
  • 运行时异常

A:编译时异常

编译时异常:发生于程序编译期间,也称之为受查异常(Checked Exception)

如下,在IDEA中编写代码时,如果出现了红色波浪线且提示你需要抛出或捕获异常,那么表示此时是编译时异常

B:运行时异常

运行时异常:发生于程序执行期间,也称之为非受查异常(Unchecked Exception)

  • 上图所示的“RunTimeException”及其子类对应的异常都是运行时异常
  • 注意编译时出现的语法错误并不能称之为异常
  • 常见的运行时异常前面已举例

三:异常处理

(1)防御式编程

防御式编程:只要从写第一行代码的那一刻开始,错误就已经潜在了。所以我们需要做到的就是当程序出现问题时能及时通知程序员解决。主要有以下两种方式

①:事前防御:在编写代码时就预想到一切可能发生的错误

  • 缺点:把处理错误的代码逻辑搅合到正常代码逻辑中,显得混乱;而且你也无法将所有错误情况都考虑到
boolean ret = false;
ret = 登录游戏();
if(!ret)
	处理登录游戏错误;
	return;

ret = 匹配对局();
if(!ret)
	处理匹配对局错误;
	return;

ret = 购买物品();
if(!ret)
	处理购买物品错误;
	return;

...

②:事后认错(异常处理的核心思想):不管怎样“先运行再说”,遇到问题了再解决

  • 优点:正常流程和错误流程是分离开的,程序员更关注正常流程,代码更清晰,容易理解代码
try
	登录游戏();
	匹配对局();
	购买物品();
	...
catch(登录游戏异常)
	处理登录游戏异常;
catch(匹配对局异常)
	处理匹配对局异常;
catch(购买物品异常)
	处理购买物品异常;

(2)Java异常处理五大关键

在Java中,关于异常的处理无外乎就是以下五点

  • throw
  • try
  • catch
  • final
  • throws

A:异常抛出

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

  • throw必须写在方法体内部
  • 抛出对象必须为Exception或其子类
  • 如果抛出的是RunTimeException或其子类,则可以不用处理,JVM会自动处理
  • 如果抛出的是编译时异常,则用户必须处理,否则无法通过编译
  • 异常一旦抛出成功,后面代码便不再执行
throw new XXXEXception("异常产生的原因")

例如下面是一个获取数组中任意位置元素的例子

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[] arr = 61, 45, 93, 26, 88;
    int b = getElement(arr, 10);
    int c = getElement(null, 2);

B:异常捕获

异常捕获:也即异常的具体处理方式,有如下两种

  • 异常声明throws
  • try-catch捕获处理

①:异常声明throws

异常声明throws:适用于当前方法不处理异常,题型方法调用者去处理异常;异常声明位于方法声明时的参数列表之后。语法格式如下

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

例如下面的例子中,需求是加载指定的配置文件config.ini,这里会有一个隐含错误就是文件名字错误,所以我们需要抛出一个“FileNotFoundException”异常,但是对于这种异常是无法处理的,只有调用者才可以检查并修改文件名字

import java.io.File;
import java.io.FileNotFoundException;

public class Config 
    File file;
    public void OpenConfig(String filename) throws FileNotFoundException
        if(filename.equals("config.ini"))
            throw  new FileNotFoundException("配置文件名字错误,请检查");
        
    
    public void readConfig()
        ;
    

注意以下几点

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

②:try-catch捕获并处理(常用)

try-catch捕获并处理throws并未对异常进行处理,如果不作处理那么就会交由JVM处理,而且结果大概率就是程序终止,所以很多时候我们需要对异常进行捕获并作出相应处理,这里就需要用到try-catch语句。语法格式如下

try
	这里放置可能出现异常的代码
catch(要捕获的异常类型 e)
	如果[try中的代码抛出异常了,且和catch捕获的异常类型一致时(或其基类)]就会被捕获到
	此时就会对异常进行处理,处理完成之后,try-catch跳出,继续执行代码
catch(要捕获的异常类型 e)
	...
catch(要捕获的异常类型 e)
	...
...
	...
finally
	此处的代码一定会被执行到



//后续代码
 当异常被捕获到时,异常就被处理了,后续代码会被执行
 如果没有捕获到或者是捕获到了但是没有其对应的类型,这里的代码不会执行(此时会交由JVM处理,一般来说程序会终止)

例如下面的例子中,需求是加载指定的配置文件config.ini

public class Config 
    File file;
    public void OpenConfig(String filename) throws FileNotFoundException, FileSystemException 
        if(filename.equals("config.ini"))
            throw  new FileNotFoundException("配置文件名字错误,请检查");
        
    
    //打开文件
    public void readConfig()
        ;
    

    public static void main(String[] args)
        Config config = new Config();
        try
            //打开文件时有可能错误,然后抛出IOException异常
            config.OpenConfig("config.txt");
            System.out.println("文件打开成功");
        catch (IOException e)
            //异常处理方式
            System.out.println(e.getMessage()); //可以只打印异常信息
            System.out.println(e); //可以打印异常类型:异常信息
            e.printStackTrace(); //可以把信息打印全
        
        System.out.println("异常一旦被捕获且被处理了,此处代码便会执行");
    

注意以下几点

  • try块内抛出异常后,该位置之后的代码不再会被执行

  • 如果抛出异常类型与catch块内异常类型不匹配时,也即异常不会被成功捕获,也就不会处理。接着,它会继续向上抛,知道JVM收到后中断程序

  • 如果异常具有父子关系,一定保证子类异常在前,父类异常在后,否则会出现语法错误

C:finally

finally:在写程序时,有些代码,无论程序是否发生异常,都必须要执行,例如网络连接资源、数据库连接资源、I/O流等等,这些资源在程序正常或异常退出时,必须予以回收。所以finally可以解决这个问题,语法格式如下

try
	//可能会发生异常的代码
catch(异常类型 e)
	//异常处理
finally
	//无论是否发生异常或者能够捕捉到,这里一定会被执行

如下是一个数组越界异常的例子

public class TestDemo 
    public static void main(String[] args) 
        try 
            int[] arr = 1, 2, 3;
            System.out.println(arr[100]);
        catch (ArrayIndexOutOfBoundsException e)
            System.out.println(e);
        finally 
            System.out.println("finally中代码执行");
        

        System.out.println("后续代码");
    

问题:如下,既然finallytry-catch-finally后面的代码都会执行,那么finally的存在是否显得多余?

如下是一个获取输入数字的例子,main方法调用getData()方法获取一个整数。如果正常输入,那么这个方法将会直接结束在try块内,后面的sc.close()也不会执行,所以资源未能回收

public class TestDemo
    public static int getDate()
        Scanner sc = null;
        try 
            sc = new Scanner(System.in);
            int data = sc.nextInt();
            return data;
        catch (InputMismatchException e)
            e.printStackTrace();
        finally 
            System.out.println("finally中的代码");
        
        System.out.println("try-catch-finally后的代码");
        if(sc != null)
            System.out.println("关闭资源");
            sc.close();
        
        return 0;
    

    public static void main(String[] args) 
        int data = getDate();
        System.out.println(data);
    

但是如果把资源的回收工作写在finally内部,那么即便方法返回,它也是会自动执行的

try 
     sc = new Scanner(System.in);
     int data = sc.nextInt();
     return data;
 catch (InputMismatchException e)
     e.printStackTrace();
 finally 
     System.out.println("finally中的代码");
     if(sc != null)
         System.out.println("关闭资源");
         sc.close();
     
 

四:异常的处理流程

异常的处理流程:总结如下

  • 程序先执行try中的代码
  • 如果try中的代码出现异常,就会结束try中的代码,看和catch中的异常类型是否匹配
  • 如果找到匹配的异常类型,就会执行catch中的代码
  • 如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者
  • 无论是否找到匹配的异常类型,finally中的代码都会被执行到(在该方法结束之前执行)
  • 如果上层调用者也没有处理的了异常,就继续向上传递
  • 一直到 main方法也没有合适的代码处理异常,就会交给JVM来进行处理,此时程序就会异常终止

以上是关于:Java异常概念处理和自定义异常类的主要内容,如果未能解决你的问题,请参考以下文章

[JAVA]异常

异常概念和处理机制,try-catch-finally,throw和throws,自定义异常

异常概念和处理机制,try-catch-finally,throw和throws,自定义异常

JAVA——异常Throwable抛出异常Throws异常处理try-catch制造异常Throw自定义异常类

异常处理

异常处理