一篇搞定Java异常处理

158 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情


异常:在Java中,将程序执行中发生的不正常情况称为“异常”。(但不包含语法错误和逻辑错误)

异常可以分为两类:

  • Error:Java虚拟机无法解决的严重问题。比如:StackOverflowError和OOM。 这种情况一般编写针对性代码进行处理
  • Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:空指针访问、试图读取不存在的文件、网络连接中断、数组角标越界。
public static void main(String[] args) {
        main(args);
}

比如上面的代码就是栈溢出:java.long.StackOverflowError

public static void main(String[] args) {
        Integer[]arr = new Integer[1024*1024*1024];
}

堆溢出:java.lang.OutOfMemoryError


对于这些错误,一般采用两种解决办法:①遇到错误就终止程序的运行②在编写程序时就考虑到错误的检测、错误消息的提示,以及错误的处理。

①比如你工作时要出去干事情,把干事情理解成一个“异常”,工作理解成当前正在执行的“程序”,那么此时你工作就会先终止。

②比如,你要出去旅游,你要先考虑到会不会期间生病,然后先准备好一些药品,这就相当于第二中解决办法。

✨异常可以是编译时异常运行时异常

捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等


异常的体系结构

所有异常的父类叫:java.long.Throwable

Java API中除了RuntimeException是运行时异常,其他都是编译时异常,当然RuntimeException里面细分了很多运行时异常。

常见异常举例:

  • 编译时异常(checked):IOException---FileNotFonundException 、ClassNotfoundException
  • 运行时异常(unchecked):NullPointerException(空指针)、ArrayIndexOutOfBoundsException(数组下标越界)、ClassCastException(类型转换异常)、NumberFormatException(数值类型转换异常)、InputMismatchException(传参类型不匹配)、ArithmaticException(算术异常)

运行时异常代码举例:

	//NullPointerException
	@Test
	public void test1() {
		int [] arr = null;
		System.out.println(arr[3]);
	}
	
	//ArrayIndexOutOfBoundsException
	@Test
	public void test2() {
		int []arr = new int[10];
		System.out.println(arr[10]);
	}
	
	//ClassCastException
	@Test
	public void test3() {
		Object obj = new Date();
		String str = (String)obj;
	}
	
	//NumberFormatException
	@Test
	public void test4() {
		String str = "123";
		str = "abc";//本身不是数值类型
		int num = Integer.parseInt(str); //不能转换成int型
	}
	
	//InputMismatchException
	@Test
	public void test5() {
		Scanner scan = new Scanner(System.in);
		int score = scan.nextInt(); //如果输入的不是数字,比如输入abc
		System.out.println(score); //就会报InputMismatchException
	}
	
	//ArithmaticException
	@Test
	public void test6() {
		int a = 10;
		int b = 0;
		System.out.println(a/b);
	}

异常处理机制

第一种:try-catch-finally ;程序可以自己处理

第二种:throws+异常类型 ;把异常抛给别人

✨异常的处理又叫做:抓抛模型,可以理解成两个过程

过程一:抛:程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象。并将此对象抛出,其后的代码也不再执行。

过程二:抓:可理解为异常的处理方式:①try-catch-finally②throws

try-catch-finally

代码格式如下:

        try {
            //可能出现异常的代码
    } catch (异常类型1 变量名1) {
            //处理异常的方式1
    } catch (异常类型2 变量名2) {
            //处理异常的方式2
    } catch (异常类型3 变量名3) {
            //处理异常的方式3
    }
    ...
    finally {
            //一定会执行的代码
    }
  1. 其中finally是可写可不写的
  2. 使用try将可能出现的异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配
  3. 一旦try中的异常对象匹配到某一个catch时,就进入catch中进行异常的处理。一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况下)。继续执行其后的代码。
  4. catch中的异常欸写如果没有字符类关系,则谁声明在上或下无所谓。但如果满足子父类关系,则要求子类一定声明在父类的上面。否则报错
  5. 在try结构中声明的变量,出了try结构以后就不能再被调用。
  6. try-catch-finally可以相互嵌套

try-catch-finally处理编译时异常,则编译时不再报错,相当于把编译时异常延迟到运行时出现。


比如在上面运行时异常举例中test4()类型转换异常,给它进行异常处理

@Test
public void test1() {
        String str = "123";
        str = "abc";//本身不是数值类型
        try {
                int num = Integer.parseInt(str); //不能转换成int型
        } catch(NumberFormatException e) {
                System.out.println("出现数值转换异常");
        }
}

注意点:

①可以把程序用try全包上

②catch的()中异常类型必须对应上,比如会出现NumberFormatException数值转换异常,你不能写NullPointerException空指针异常,这就和没处理是一回事。但可以写父类(Exception),不过还是具体到某一种异常好一点。

常用的异常对象处理的方式:①String getMessage()②printStackTrace()

@Test
public void test1() {
        String str = "123";
        str = "abc";//本身不是数值类型
        try {
                int num = Integer.parseInt(str); //不能转换成int型
        } catch(NumberFormatException e) {
                e.getMessage();
                e.printStackTrace();
        }
}

finally

finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中有return语句,catch中有return语句等情况。

举例:①即使报错也会执行

@Test
public void test1() {
        try {
                int a = 10;
                int b = 0;
                System.out.println(a / b);
        } catch (ArithmeticException e) {
                //e.printStackTrace();
                int []arr = new int[10];
                System.out.println(arr[10]);
        }  catch(Exception e) {
                e.printStackTrace();
        } finally {
                System.out.println("真帅~");
        }

}

try中出现异常会执行ArithmeticException e的catch语句,但这个catch语句里面也有异常,这样会报错,但finally仍会执行打印出“真帅~”

②有返回值

@Test
public void testMethod() {
        int num  = method();
        System.out.println(num);
}

@Test
public int method() {
        try {			
                int []arr = new int[10];
                System.out.println(arr[10]);
                return 1;
        } catch (ArrayIndexOutOfBoundsException e) {
                e.printStackTrace();
                return 2;
        } finally {
                System.out.println("我一定会被执行!");
        }
}

执行testMethod输出:

我一定会被执行!

2

什么时候使用finally?

像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动回收的,我们需要手动地进行资源释放,此时的资源释放,就需要声明再finally中


throws

throws + 异常类型

  1. 写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。一旦方法体执行时,出现异常,仍会再异常代码处生成一个异常类的对象,此对象毛南族throws后异常类型时,就会被抛出。
  2. 异常代码后的代码不再执行

try-catch-finally:真正的将异常给处理掉了

throws的方式知识将异常抛给了方法的调用者。并没有将异常处理掉


重写方法异常处理规则

子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。

class SuperClass{
	public void method() throws IOException {
		
	}
}

class SubClass extends SuperClass{
	public void method() throws FileNotFoundException{
		
	}
}

SubClass子类所抛出的异常就要比SuperClass父类所抛出的异常小或者相同,再者就不写。但一定不能大。

父类没有抛,子类就更不能抛!


处理机制的选择

✨如何选择try-catch-finally或者throws?

  1. 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法中有异常,就必须使用try-catch-finally去处理掉异常。
  2. 执行的方法a中,先后又调用了另外几个方法,这几个方法时递进关系执行的。建议这几个方法使用throws的方式进行处理。而执行的方法a可以考虑使用try-catch-finally方式处理。

手动抛出异常

关于异常对象的产生:①系统自动生成的异常对象②手动生成一个异常对象,并抛出(throw)而不是throws

比如我给一个学生的属性赋值不能赋值给这个属性为小于等于0的数,而且就算写一个提示语句,该输出还是会输出(toString接收负数输出0)。这是就会用到手动抛出异常

class Student{
	private int id;
	public void regist(int id) throws Exception{
		if(id > 0) {
			this.id = id;
		} else {
//			System.out.println("输入数据非法");
			//手动抛出异常
			throw new Exception("输入数据非法");
		}
	}
	@Override
	public String toString() {
		return "Student [id=" + id + "]";
	}	
}

main方法可以这么写

public static void main(String[] args) {
        try {
                Student s = new Student();
                s.regist(-1001);
                System.out.println(s);
        } catch (Exception e) {
                System.out.println(e.getMessage());
        }
}

用户自定义异常类

  1. 定义的类先继承现有的异常结构:RuntimeException(运行时)、Exception
  2. 提供全局常量serialVersionUID
  3. 提供重载的构造器

调用:格式为 throw new 类名

举例:

public class MyException extends RuntimeException{
	static final long serialVersionUID = -7034897190745766939L;//异常类源码中的一个全局常量
	public MyException() {
		
	}
	
	public MyException(String msg) {
		super(msg);
	}
}