Java必备知识之异常处理

262 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情

一、异常的概述和分类

1. 异常的概述

异常就是Java程序在运行过程中出现的错误。

未命名绘图.png

2. 异常的分类

a. 严重问题: Error

不予处理,因为这种问题一般是很严重的问题,如:栈溢出(StackOverflowError)和内存溢出(OOM)。

b. 非严重问题(异常): Exception

可以针对性进行处理,分为编译时异常和运行时异常,如:空指针、类型转换错误。

二、编译期异常和运行期异常的区别

Java中的异常被分为两大类:编译时异常(非RuntimeException)和运行时异常(RuntimeException)。 所有的RuntimeException类及其子类的实例被称为运行时异常,其他的异常就是编译时异常。

1. 编译时异常:

Java程序必须显示处理,否则程序就会发生错误,无法通过编译。

2. 运行时异常

Java程序无需显示处理,也可以和编译时异常一样处理。

三、Error和Exception代码示例

1. Error

a. java.lang.StackOverflowError(栈溢出)

public class Test {
 public static void main(String[] args) {
     //栈溢出:java.lang.StackOverflowError
     main(args);
 }
}

b. java.lang.OutOfMemoryError(堆溢出)

public class Test {
 public static void main(String[] args) {
     //堆溢出:java.lang.OutOfMemoryError
     Long[] arr = new Long[1024*1024*1024];
 }
}

2. Exception

a. 运行时异常

 //NullPointerException
 @Test
 public void test1(){
     int[] arr = null;
     System.out.println(arr[1]);
 }
 
 //ClassCastException
 @Test
 public void test2(){
     Object obj = new Boolean(true);
     String str = (String)obj;
 }

b. 编译时异常

 @Test
 public void test3(){
     File file = new File("a.txt");
     //java.io.FileNotFoundException
     FileInputStream fis = new FileInputStream(file);
     //java.io.IOException
     int date = fis.read();
     while (date != -1){
          System.out.println((char)date);
          date = fis.read();
     }
 fis.close();
 }

三、异常处理方式

1. 异常处理的两种方式

a. try…catch…finally

b. throws

2. try...catch和try-catch-finally处理异常的基本格式

a. try...catch

//try...catch
//处理一个异常
try {
    //可能出现问题的代码 
}catch(异常类型 变量名){
    //针对问题的处理 
}

//处理多个异常
try{
    //可能出现异常的代码
}catch(异常类型1 变量名1){
    //处理异常的方式1
}catch(异常类型2 变量名2){
    //处理异常的方式2
}catch(异常类型3 变量名3){
    //处理异常的方式3
}

注意事项:

a. try中的代码越少越好

b. catch中要做处理,哪怕是一条输出语句也可以(不能将异常信息隐藏)。

b. try-catch-finally

try{
    //可能出现异常的代码
}catch(异常类型 变量名){
    //针对问题的处理
}finally{
    //一定会执行的代码
}

注意事项:

a. 能明确的尽量明确,不要用大的来处理。

b. 平级关系的异常谁前谁后无所谓,如果出现了子父关系,父必须在后面。

3. throws处理异常

定义功能方法时,需要把出现的问题暴露出来让调用者去处理,那么就通过throws在方法上标识。

public class Test {
    public static void main(String[] args) {
        try{
            test();
        // 数组下标越界异常
        }catch(IndexOutOfBoundsException e){
            e.printStackTrace();
        }
    }
    public static void test() throws IndexOutOfBoundsException{
        Byte[] bytes = new Byte[3];
        System.out.println(bytes[4]);
    }
}

四、throw的概述以及和throws的区别

1. throw的概述

在功能方法内部出现某种情况,程序不能继续运行,需要进 行跳转时,就用throw把异常对象抛出。

代码示例:

public class Test {
	public static void main(String[] args) {
	    try{
	        test();
	    // 数组下标越界异常
	    }catch(IndexOutOfBoundsException e){
	        e.printStackTrace();
	    }
	}
	 public static void test(){
		 try {
		    Byte[] bytes = new Byte[3];
		    System.out.println(bytes[4]);
		 // 数组下标越界异常
		 }catch(IndexOutOfBoundsException e){
		    throw new IndexOutOfBoundsException();
		 }
	 }
}

2. throws和throw的区别

a. throws

  • 用在方法声明后面,跟的是异常类名
  • 可以跟多个异常类名,用逗号隔开
  • 表示抛出异常,由该方法的调用者来处理
  • throws表示出现异常的一种可能性,并不一定会发生这些异常

b. throw

  • 用在方法体内,跟的是异常对象名
  • 只能抛出一个异常对象名
  • 表示抛出异常,由方法体内的语句处理
  • throw则是抛出了异常,执行throw则一定抛出了某种异常

五、自定义异常

1. 自定义异常类步骤

  • 继承于现有的异常结构:RuntimeException 、Exception
  • 提供全局常量:serialVersionUID
  • 提供重载的构造器

2. 代码示例

自定义异常类

public class MyException extends Exception {

	static final long serialVersionUID = -5641210210148784L;

	public MyException() {
	}

	public MyException(String message) {
		super(message);
	}
}

手动抛出自定义异常

public class Test {
	public static void main(String[] args) {
		try {
			testException();
		} catch (MyException myException) {
			myException.printStackTrace();
		}
	}
	
	public static void testException() throws MyException {
		Scanner scanner = new Scanner(System.in);
		System.out.println("请输入大于0的数据:");
		double next = scanner.nextDouble();
		if (next > 0) {
			System.out.println("您输入的数据为:" + next);
		} else {
			throw new MyException("您输入的数据不满足要求!");
		}
	}
}

六、异常处理执行顺序

1. 除以下4种情况外,都会执行finally语句

  • 在try 或catch语句中执行了System.exit(0)
  • 在执行finally之前jvm崩溃
  • try语句中执行死循环
  • 电脑宕机

2. finally语句执行原则

a. 不论有没有出现异常,finally块中代码都会执行

//无异常
public class Test {
	public static void main(String[] args) {
		try {
			System.out.println("try...");
		} catch (Exception e) {
			System.out.println(e.getMessage());
		} finally {
			System.out.println("finally...");
		}
	}
}

执行结果: image.png 如果未出现异常,顺序执行try、finally代码块。

//有异常
public class Test {
	public static void main(String[] args) {
		try {
			System.out.println("try...");
			Byte[] bytes = new Byte[3];
			System.out.println(bytes[4]);
		} catch (Exception e) {
			System.out.println("exception...");
		} finally {
			System.out.println("finally...");
		}
	}
}

执行结果: image.png 如果有异常,则try中异常后的代码将不执行,再顺序执行catch、finally代码块。

b. 当try和catch中有return时,finally仍然会执行

public class Test {
	public static void main(String[] args) {
		int i = test();
		System.out.println("retuen:"+i);
	}
	
	public static int test() {
		try {
			System.out.println("try...");
			return 0;
		} catch (Exception e) {
			System.out.println("exception...");
			return 1;
		} finally {
			System.out.println("finally...");
		}
	}
}

执行结果: image.png 当finally里面没有return语句时,执行try、finally语句之后最后再执行return。

c. finally是在return后面的代码运算后执行的

public class Test {
	public static void main(String[] args) {
		int i = test();
		System.out.println("retuen:"+i);
	}

	public static int test() {
		int i = 0;
		try {
			i = 2;
			System.out.println("try...");
			return i;
		} catch (Exception e) {
			System.out.println("exception...");
			return i;
		} finally {
			i = 12;
			System.out.println("finally...");
		}
	}
}

执行结果: image.png

d. finally中最好不要包含return,否则程序会提前退出,不会再返回try或catch中保存的返回值

public class Test {
	public static void main(String[] args) {
		int i = test();
		System.out.println("retuen:" + i);
	}

	public static int test() {
		int i = 0;
		try {
			System.out.println("try...");
			return i;
		}catch (Exception e) {
			System.out.println("exception...");
			return i;
		} finally {
			i = 2;
			System.out.println("finally...");
			return i;
		}
	}
}

执行结果: image.png

在程序还未执行try中的return语句时就先执行了finally里面的return语句

七、JVM默认如何处理异常

main函数收到这个问题时,有两种处理方式:一是自己将该问题处理,然后继续运行; 二是自己没有针对的处理方式,只有交给调用main的jvm来处理。

jvm有一个默认的异常处理机制,就将该异常进行处理,并将该异常的名称、异常的信息、异常出现的位置打印在了控制台上,同时将程序停止运行。