Java异常处理

处理错误

异常分类

在Java中,所有的异常对象都是派生于Throwable类的实例,不过后面还可以自定义异常类

Throwable 下层分为两个分支:Error和Exception

  • Error是Java运行时系统内部的错误,所以不用处理这种错误
  • Exception 才是需要处理的错误,它又分为两个分支:RuntimeException和 非RuntimeException

RuntimeException包括以下问题:

  • 数值越界
  • 错误的强制类型转换
  • 使用null指针

非RuntimeException包括:

  • 超越文件末尾读取数据
  • 打开一个不存在的文件
  • 根据字符串查找一个类时,这个类不存在

RuntimeException 一定是程序本身的问题,这种问题要通过修改代码来处理,也就是说写的代码出bug了

非RuntimeException 才是需要处理的问题,这种异常叫做检查型(checked)。比如说提供一个根据用户输入的名称打开对应的文件,如果用户输入的文件不存在,那么程序就会报错而停止运行,我们要做的就是捕获并且处理这些异常,使得即使用户输入了非法的数据程序也能够继续运行下去,当然还包括其他的异常也是一样。

声明检查型异常

定义一个方法时,假设这个方法是:说根据用户输入的名称打开对应的文件。那么这个方法很有可能出现异常,所以要在定义是声明 这个方法可能出现异常,来告诉调用者在调用的时候需要异常处理

public void openFile(String name) throws FileNotFoundException{}
//声明这个方法可能抛出 FileNotFoundException 异常(即文件找不到异常)

但是如果文件找到了,但是在文件操作的时候有可能出现其他错误,而我们却只考虑了文件找不到的异常,一旦出现其他异常,那么就会立即停止执行这个方法

所以可以声明多个检查型异常,用逗号分隔

public void openFile(String name) throw FileNotFoundException,IOException{}
//声明这个方法可能抛出上面两种异常

现在不用管FileNotFoundException这些类是什么,只要知道这是告诉调用者“这个方法可能抛出异常”就行,那些下面会解释

抛出异常

Java内置了许多异常类,但是如果出现符合语法却不符合我们自己的需求,那么就需要自己抛出一个异常

比如说一个文件已经承诺长度为1024,在读取的时候却发现只有678个字符,这种情况就要手动抛出一个异常,Java API中有 EOFException异常:输入过程遇到了意外的EOF,完美契合我们的需求。

public void openFile(String name,int len) throw EOFException{
    FileInputStream in;
    try{		//暂时忽略这里,知道这里是打开一个文件就行
        in=new FileInputStream(name);	//打开一个文件流
    } catch(IOException e){
        System.out.println(e.getMessage());
    }
    //正文:
    int x,flag=0;
    while((x=in.read())!=-1){
        flag++;
        System.out.print(x);
    }
    if(flag!=len){		//如果文件长度不够,则抛出异常
        throw new EOFException();
    }
}

代码中先声明需要抛出什么异常,然后在代码中根据情况抛出异常

创建异常类

如果遇到任何标准异常类都不能描述的异常,那么就需要自定义一个异常类。自定义异常类通常继承 Exception类或者Exception的子类,如 IOException类。

自定义的类通常有两个构造器:默认构造器、带详细参数的构造器;因为超类Throwable中有一个构造器,功能是根据提供的String 参数设置message 字段,就是getMessage() 方法输出的内容,所以在子类中应该调用超类的这个构造器并且定义错误信息。

class MyException extends Exception{
    //默认构造器
    public MyException(){}
    
    //带参数的构造器
    public MyException(String message){
        super(message+"My words...");
    }
}

现在就能够抛出自定义的异常类型了,定义的时候就要声明 throws MyException,提醒调用者这个方法可能抛出MyException 的异常。

class Employee{
    ...
    //提醒调用者我这个方法可能抛出MyException异常
	public void printInfo(String name) throws MyException{
        
    	//长度小于100 则抛出异常,并且设置异常信息为:Length isn't enough
        if(name.length()<100){
            throw new MyException("Length isn't enough!");            
        }
        System.out.println(name);
    }
}

捕获异常

捕获异常

如果某个地方抛出了异常但是没有处理,那么程序就会终止,并在控制台打印异常的类型和堆栈轨迹

为了能够继续运行下去,需要使用 try/catch 语句块,最简单的示例:

try{
    ...
}catch (ExceptionType e){
    what are you want to do when you catch a Exception?
}

假设写一段文件数据读取代码:

public openFile(String name){
    try{
        var in=new FileInputStream(name);
        int x;
        while((x=in.read())!=-1){
            ...
        }
    } catch(IOException e){
        e.printStackTrace();		//打印异常信息、堆栈轨迹
    }
}

上面的代码中打开了一个文件流,因为读取的时候可能抛出IOException (输入输出流异常),所以用try/catch语句处理。但是这个方法中处理异常的操作仅仅是打印堆栈轨迹,在方法内这些异常没有什么很好的解决方法。这个时候就要选择暂时不处理,将这个异常抛给调用者,让调用者来处理这个异常

一般来说

  • 捕获知道如何处理的异常
  • 传播不知道如何处理的异常

要注意的是,传播一个异常时必须在方法首部声明要传播的异常类型,throws ExceptionType ,提醒调用者这个方法可能抛出异常

特例:如果子类覆盖了超类的方法,而这个超类没有声明检查类型异常,那么子类的覆盖方法就不能声明检查类型异常,只能在方法内捕获处理。

class Employee{
    public void printStack(String name){}
}

class Employee2 extends Employee{
    public void printStack(String name) throws Exception {}		//ERROR!
}

如果try 语句中捕获了一个异常:

  1. 跳过try剩余的代码
  2. 执行catch 中的代码
  3. 继续执行方法中的其他语句
//往上数第二个代码块,先用它举个栗子try{
    new Employee.printInfo("ABC");		// 1    System.out.println("I am alive!");	// 2
    } catch(MyException e){
       System.out.println(e.getMessage());	// 3}System.out.println("Yes");  			//4

因为 1处会抛出错误,所以跳过try中剩余的语句,执行顺序为:1->3->4

如果try 语句没有捕获异常:

  1. 执行完try 所有语句
  2. 跳过 catch 中的语句
  3. 继续执行方法中的其他语句
try{
    new Employee.printInfo("ABC".repeat(40));		// 1
    System.out.println("I am alive!");				// 2
} catch(MyException e){
    System.out.println(e.getMessage());				// 3
}
System.out.println("Yes");							// 4

跳过catch语句,所以执行顺序为:1->2->4

捕获多个异常

一个语句块中可能出现多种异常,就需要捕获多个异常。

还是比如说根据传递的字符串打开一个文件,并且读取数据。在API文档中,FileInputStream() 方法可能抛出FileNotFoundException异常,read()方法可能抛出 IOException异常,所以要捕获两种异常

public void openFile(String name){
    try{
        var in=new FileInputStream(name);		//FileNotFoundException
        int x;
        while((x=in.read())!=-1){		//IOException
            System.out.println(x);
        }
    } catch(FileNotFoundException e){
        e.printStackTrace();
    } catch(IOException e){
        System.out.println(e.getMessage);
    }
}

如果处理两个异常的动作一样,那么可以合并catch语句:

try{
    ...
}catch(异常类型1 e){
    e.printStackTrace();
}catch(异常类型2 e){
    e.printStackTrace();
}

两条catch 执行相同的操作,那么可以合并,语法格式为:

try{
	...
}catch(异常类型1 | 异常类型2 e){
    e.printStackTrace();
}

在最前面就讲了,所有检查型的异常都派生于Exception 类,所以这些异常都能够被Exception捕获到,像上面的IOException、EOFExcpeiton 等等都能被Exception 捕获到。但是为了更好的处理,就不能够笼统的全都视为Exception 类处理。

再次抛出异常

可以在catch 中抛出异常,比如说我只想记录下异常,然后再抛给调用者继续处理

try{
    what are you doing?
} catch(Exception e){
    logger.log(level,message,e);
    throw e;		//记录后又抛出
}

finally 子句

无论有没有捕获到异常,finally子句中的代码都会执行,不管什么情况finally都会执行

try{
    ...
}catch(Exception e){
 	...   
}finally{
    ...			//这里的语句风雨无阻
}

finally 一般用在关闭资源上,比如说打开了一个文件流,在try中读到一半却抛出了异常,那么就需要finally来关闭流

open a resourse
try{
    ...
}catch(Exception e){
    ...
}finally{
    close the resourse
}

不过还有一个更好的办法解决关闭资源问题,下面就会讲到

注意:finally中不能够写更改程序执行流程的语句:return、throw、break、continue:

try{
    return 10;
}finally{
    return 0;
}

即使try中没有抛出异常,执行了return 10,也会在真正的return 10之前执行finally语句,最后返回值是0

try-with-Resourse

对于往上面数第一个代码块的语句,可以改为:

try(open a reourse){
    ...
}catch(Exception e){
    ...
}

不管这个try中发生了什么,最后这个资源一定会被关闭

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值