Java复习(五)----异常处理

在计算机程序运行的时候,总会出现各总各样的错误,有一些是人为造成的,有一些是随机出现的。现在我们就来说说Java的异常处理,有啥问题请指出,谢谢。

什么异常

Java 中的异常(Exception)又称为例外,是一个在程序执行期间发生的事件,它中断正在执行程序的正常指令流。Java的异常是class,并且从Throwable继承。

在一个程序中,有些错误是用户造成的,比如,我们希望用户输入一个int类型的身高,但是用户输入的是字符:

//假设用户输入了abc
String s = "abc";
int n = Integer.parseInt(s);
//NumberFormatException

还有一些则是随机出现的,并且是不可能避免的。比如:

  • 断网了,连接不到服务器
  • 内存不足,程序崩溃了

所以一个程序必须处理各种各样的错误。Java内置了一套异常处理机制,总是使用异常来表示错误。

Throwable

Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。异常和错误的区别是:异常能被程序本身可以处理,错误是无法处理。

Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。

Exception(异常):是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。

Java规定:

  • 必须捕获的异常,包括Exception及其子类,但不包括RuntimeException及其子类,这种类型的异常成为Checked Exception。
  • 不需要捕获的异常,包括error及其子类,RuntimeException及其子类。

注意:编译器对RuntimeException及其子类不做强制捕获要求,不是指应用程序本身不应该捕获并处理RuntimeException。是否需要捕获,具体问题具体分析。

捕获异常

try…catch

在java中,凡是可能抛出异常的语句都可以使用try…catch语句,把可能发成异常的代码块放到try{…}中,然后使用catch捕获对应的Exception及其子类。

try {
    // 可能会发生异常的程序代码
} catch (Type1 one){
    // 捕获并处置try抛出的异常类型Type1
}
catch (Type2 two){
     //捕获并处置try抛出的异常类型Type2
}
public class Main {

     public static void main(String[] args) {

           String a = "12";

           String b = "x9";

           // 捕获异常并处理

           int c = stringToInt(a);

           int d = stringToInt(b);

           System.out.println(c * d);

     }

     static int stringToInt(String s) {

           try {

                return Integer.parseInt(s);

           } catch (NumberFormatException e) {

                System.out.println("Bad input");

                return 0;

           }

     }

}

结果:Bad input
0

多catch语句

在异常处理中,可以使用多个catch语句,每个catch分别捕获对于的Exception及其子类。JVM在捕获到异常后,会从上到下匹配catch语句,匹配到某个catch后,执行catch代码块,然后不再继续匹配。简单的说就是:多个catch语句中只能有一个能被执行。如:

public static void main(String[] args) {
  try{
    one();
    two();
    three();
  }catch(IOException e){
    System.out.println(e);
  }catch(NumberFormatException e){
    System.out.println(e);
  }
}

存在多个catch的时候,catch的顺序非常重要:**子类必须写在前面。**否则将永远捕获不到子类。

finally语句

不管有没有异常,finally中的代码都会执行。这里就说一下try…catch…finally语句

        try {
			// 可能会发生异常的程序代码
		} catch (Type1 one) {
			// 捕获并处理try抛出的异常类型Type1
		} catch (Type2 two) {
			// 捕获并处理try抛出的异常类型Type2
		} finally {
			// 无论是否发生异常,都将执行的语句块
		}
public static void main(String[] args) {
  try{
    one();
    two();
    three();
  }catch(ArrayIndexOutOfBoundsException e){
    System.out.println("数组下标越界异常");
  }catch(IOException e){
    System.out.println("IO error");
  }finally{
    System.out.println("End");
  }
}

以下是我在牛客刷题的时候看到一位大佬的总结

结论

  1. 不管有没有异常,finally中的代码都会执行
  2. finally语句不是必须的,可写可不写
  3. 当try、catch中有return时,finally中的代码依然会继续执行
  4. finally是在return后面的表达式运算之后执行的,此时并没有返回运算之后的值,而是把值保存起来,不管finally对该值做任何的改变,返回的值都不会改变,依然返回保存起来的值。也就是说方法的返回值是在finally运算之前就确定了的。
  5. finally代码中最好不要包含return,程序会提前退出,也就是说返回的值不是try或catch中的值

举例:

  • 情况1try{}catch{}finally{} return;
    程序正常进行

  • 情况2try{return;}catch{}finally{} return;
    a、先执行try中的语句,包括return后面的表达式,
    b、然后执行finally中的语句,
    c、最后执行try中的return
    ps:返回值是try中的return后面的表达式的值,finally后面的return语句不会被执行

  • 情况3try{} catch{return;} finally{} return;
    先执行try中的代码块
    有异常
    a、先执行catch中的语句,包括return后面的表达式;
    b、然后执行finally中的语句;
    c、最后执行catch中的return,finally后面的return不会被执行
    无异常:执行finally中的代码块,然后执行return语句

  • 情况4try{return;} catch{} finally{return;}
    a、先执行try中的语句,包括return后面的表达式;
    b、然后执行finally中的语句;
    c、最后执行finally中的return 。
    ps:返回的值是finally中return后面的表达式的值,因为finally中有return语句,所以会提前退出

  • 情况5try{} catch{return} finally{return};
    先执行try中的代码块
    有异常
    a、先执行catch中的语句,包括return后面的表达式;
    b、然后执行finally中的语句;
    c、最后执行finally中的return,因为finally中有return语句,所以会提前退出
    无异常:执行finally中的代码块,然后执行finally中的return

  • 情况6try{return;} catch{return;} finally{return;}
    先执行try中的代码块,包括return后面的表达式
    有异常
    a、先执行catch中的语句,包括return后面的表达式;
    b、然后执行finally中的语句;
    c、最后执行finally中的return,因为finally中有return语句,所以会提前退出
    无异常:执行finally中的代码块,然后执行finally中的return

最总结论:在执行try、catch中的return之前一定会执行finally中的代码(如果finally存在),如果finally中有return语句,就会直接执行finally中的return方法,所以finally中的return语句一定会被执行的。编译器把finally中的return语句标识为一个warning.

抛出异常

当发生错误时,任何Java代码都可以抛出异常。抛出异常分两步:

  1. 创建某个Exception的实例
  2. throwthrows语句抛出

以下两种方法是引用一位博主的看法(其实是自己懒得写了,这位博主写的很好,结尾我会放上那篇博客的链接)

throws抛出异常

如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。例如汽车在运行时可能会出现故障,汽车本身没办法处理这个故障,那就让开车的人来处理。

throws语句用在方法定义时声明该方法要抛出的异常类型,如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。多个异常可使用逗号分割。throws语句的语法格式为:

methodname throws Exception1,Exception2,..,ExceptionN
{
}

使用throws关键字将异常抛给调用者后,如果调用者不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的调用者。

pop方法没有处理异常NegativeArraySizeException,而是由main函数来处理。

Throws抛出异常的规则:

  1. 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
  2. 必须声明方法可抛出的任何可查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误
  3. 仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。
  4. 调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。
void method1() throws IOException{}  //合法  
 
//编译错误,必须捕获或声明抛出IOException  
void method2(){  
  method1();  
}  
 
//合法,声明抛出IOException  
void method3()throws IOException {  
  method1();  
}  
 
//合法,声明抛出Exception,IOException是Exception的子类  
void method4()throws Exception {  
  method1();  
}  
 
//合法,捕获IOException  
void method5(){  
 try{  
    method1();  
 }catch(IOException e){}  
}  
 
//编译错误,必须捕获或声明抛出Exception  
void method6(){  
  try{  
    method1();  
  }catch(IOException e){throw new Exception();}  
}  
 
//合法,声明抛出Exception  
void method7()throws Exception{  
 try{  
  method1();  
 }catch(IOException e){throw new Exception();}  
} 

使用throw抛出异常

throw总是出现在函数体中,用来抛出一个Throwable类型的异常。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。
  我们知道,异常是异常类的实例对象,我们可以创建异常类的实例对象通过throw语句抛出。该语句的语法格式为:
throw new exceptionname;
例如抛出一个IOException类的异常对象:
throw new IOException;
要注意的是,throw 抛出的只能够是可抛出类Throwable 或者其子类的实例对象。下面的操作是错误的:
throw new String(“exception”);

这是因为String 不是Throwable 类的子类。

如果抛出了检查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。

如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出的是Error或RuntimeException,则该方法的调用者可选择处理该异常。

package Test;
import java.lang.Exception;
public class TestException {
	static int quotient(int x, int y) throws MyException { // 定义方法抛出异常
		if (y < 0) { // 判断参数是否小于0
			throw new MyException("除数不能是负数"); // 异常信息
		}
		return x/y; // 返回值
	}
	public static void main(String args[]) { // 主方法
		int  a =3;
		int  b =0; 
		try { // try语句包含可能发生异常的语句
			int result = quotient(a, b); // 调用方法quotient()
		} catch (MyException e) { // 处理自定义异常
			System.out.println(e.getMessage()); // 输出异常信息
		} catch (ArithmeticException e) { // 处理ArithmeticException异常
			System.out.println("除数不能为0"); // 输出提示信息
		} catch (Exception e) { // 处理其他异常
			System.out.println("程序发生了其他的异常"); // 输出提示信息
		}
	}
 
}
class MyException extends Exception { // 创建自定义异常类
	String message; // 定义String类型变量
	public MyException(String ErrorMessagr) { // 父类方法
		message = ErrorMessagr;
	}
 
	public String getMessage() { // 覆盖getMessage()方法
		return message;
	}
}

Java常见异常

序号异常名称异常描述
1java.lang.ArrayIndexOutOfBoundsException数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
2java.lang.ArithmeticException算术条件异常。如:整数除零等。
3java.lang.SecurityException安全性异常
4java.lang.IllegalArgumentException非法参数异常
5java.lang.ArrayStoreException数组中包含不兼容的值抛出的异常
6java.lang.NegativeArraySizeException数组长度为负异常
7java.lang.NullPointerException空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
8IOException操作输入流和输出流时可能出现的异常
9EOFException文件已结束异常
10FileNotFoundException文件未找到异常
11ClassCastException类型转换异常类
12ArrayStoreException数组中包含不兼容的值抛出的异常
13SQLException操作数据库异常类
14NoSuchFieldException字段未找到异常
15NoSuchMethodException方法未找到抛出的异常
16NumberFormatException字符串转换为数字抛出的异常
17StringIndexOutOfBoundsException字符串索引超出范围抛出的异常
18IllegalAccessException不允许访问某类异常
19InstantiationException当应用程序试图使用Class类中的newInstance()方法创建;一个类的实例,而指定的类对象无法被实例化时,抛出该异常
20java.lang.ClassNotFoundException找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。

自定义异常

我们在一个大型项目中,可以自定义新的异常类型,但是,保持一个合理的异常继承体系是非常重要的。

一个常见的的做法是自定义一个BaseException作为“根异常”,然后派生出各种类型的异常。

BaseException需要从一个合适的Exception派生,通常建议从RuntimeException派生:

public class BaseException extends RuntimeException {
}

其他类型异常就可以从BaseException派生:

public class BaseException extends RuntimeException {
}

public class LoginFailedException extends BaseException{
}

自定义的BaseException应该提供多个构造方法:

public class BaseException extends RuntimeException {
	public BaseException() {
		super();
	}
	
	public BaseException(String message, Throwable cause) {
		super(message,cause);
	}
	
	public BaseException(String message) {
		super(message);
	}
	
	public BaseException(Throwable cause) {
		super(cause);
	}
}

引用链接:https://blog.csdn.net/hguisu/article/details/6155636?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522160232221319724839245050%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=160232221319724839245050&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v3~pc_rank_v2-2-6155636.first_rank_ecpm_v3_pc_rank_v2&utm_term=java%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86&spm=1018.2118.3001.4187

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值