JavaSE学习总结(十五)Java异常处理/Throwable/Error/Exception/异常处理/try…catch/throw和throws的区别/finally/自定义异常
Java 异常处理
一、异常的概述和分类
(一)什么是异常?
异常就是Java程序在运行过程中出现的错误。
(二)Throwable、Error、Exception
- Throwable: Java中所有错误(Error)和异常(Exception)的父类
- Error:一般是指与虚拟机相关的问题,如系统崩溃,内存空间不足等。它表示不希望被程序捕获或者是程序无法处理的错误,我们不予处理,因为这类一般是很严重的问题,我们处理不了。
- Exception:表示程序可以处理的异常,可以捕获且可能恢复。分为编译时异常和运行时异常。
\1. 编译时异常: 非RuntimeException及其子类(必须显式处理,否则程序就会发生错误,无法运行)
\2. 运行时异常: RuntimeException及其子类(可以显式处理,也可以不处理,不处理则是虚拟机默认处理)
为了更好地理解Error、编译时异常和运行时异常,可以举个例子:
张三骑自行车去旅游
①骑车途中掉链子,但张三自己可以修好(运行时异常)
②骑车途中轮子爆胎了,张三自己解决不了(严重问题Error)
③出发前发现自行车刹闸松了(编译时异常)
(三)虚拟机JVM是如何默认处理异常的
当遇到运行时异常时,如不作处理则交给JVM处理,JVM有一个默认的异常处理机制,其将该异常进行处理,并将该异常的名称、异常的信息和异常出现的位置打印在了控制台上,同时将程序停止运行(后面的代码不执行)
案例演示
(1 除 0)
public class MyTest5 {
public static void main(String[] args) {
int a=1;
int b=0;
System.out.println(a/b);
System.out.println("这句话被执行了");
}
}
运行:
发生了运行时异常并且我们没有处理,默认交给了JVM处理,从运行结果来看,发生异常后,后面的代码也没有执行。
显然,这样不作处理的方式会导致异常后的代码无法执行,于是我们自己处理,下面就介绍处理异常的方法。
二、异常处理
(一)try…catch的方式处理异常
try {
可能出现问题的代码 ;
}catch(异常名1 变量名1){
对异常的处理方式 ;
}catch (异常名2 变量名2){
对异常的处理方式 ;
}....
可以用快捷键ctrl+alt+T生成
注意事项:
1. try中的代码越少越好,放有可能出现问题的代码。try里面的代码一旦遇到相应的异常,就会执行catch里面的代码。catch里面的异常类名,就是你要对何种异常进行捕获
2. catch中要做处理,不能做空处理(不能将异常信息隐藏),哪怕是一条输出语句也可以,比如e.printStackTrace();
(打印异常的堆栈信息)。
3. 如果捕获多个异常,平级关系的异常谁前谁后无所谓,如果出现了子父关系,父类异常捕获必须在后面,否则,子类异常的语句不被执行。
4. 能明确异常种类的尽量明确,不要直接用大的异常类(Exception)来处理。(如果实在无法明确,最后再来一段catch(Exception e){......})
public class MyTest4 {
public static void main(String[] args) {
int a = 1;
int b = 0;
int[] arr = {2, 4};
arr = null;
try {
System.out.println(a / b);
System.out.println(arr.length);
} catch (ArithmeticException e) {
System.out.println("除数为0了");
} catch (NullPointerException e) {
System.out.println("空指针异常");
} catch (Exception e) {
System.out.println("其他异常");
}
System.out.println("下面的代码执行");
System.out.println("下面的代码执行");
System.out.println("下面的代码执行");
}
}
运行
这里由于try语句段中先碰到了System.out.println("a/b");
所以只处理了这个算术异常,后面的异常没有处理。
JDK1.7以后处理多个异常的方式及注意事项
try {
可能出现问题的代码 ;
}catch(异常名1 | 异常名2 | .... 变量名){
对异常的处理方案 ;
}
- 优点: 就是简化了代码
- 弊端: 对多个异常的处理方式是一致的,不推荐
- 注意事项:
\1. 处理方式是一致的。(实际开发中,好多时候可能就是针对同类型的问题,给出同一个处理)
\2. 多个异常间必须是平级关系,不能出现子父类的继承关系。
案例演示1
public class MyTest5 {
public static void main(String[] args) {
int a = 1;
int b = 0;
int[] arr = {2, 4};
arr = null;
try {
System.out.println(arr.length);
System.out.println(a / b);
} catch (ArithmeticException | NullPointerException e) {
//在一个catch中写多个异常,处理方式一致。
e.printStackTrace();//打印异常堆栈信息。
//Throwable的几个常见方法:
//1:
//getMessage():获取异常信息,返回字符串。
//2:
//toString():获取异常类名和异常信息,返回字符串。
//3:
//printStackTrace():获取异常类名和异常信息,以及异常出现在程序中的位置,返回值void。
}
}
}
运行
案例演示2
可以通过try…catch的方式解决一些问题
需求:现在需要在控制台输入一个整数,如果输入的不是整数,则提示并继续要求输入,直至输入的是整数。
import java.util.InputMismatchException;
import java.util.Scanner;
public class MyTest {
public static void main(String[] args) {
//InputMismatchException
//方式1 用if else结构
//while (true){
// Scanner scanner = new Scanner(System.in);
// System.out.println("请输入一个整数");
// if (scanner.hasNextInt()) {
// int i = scanner.nextInt();
// System.out.println(i);
// break;
// } else {
// System.out.println("输入的类型不正确,请重新输入");
// }
//}
//方式2 也可以用try...catch来解决
while (true) {
Scanner scanner = new Scanner(System.in);
try {
System.out.println("请输入一个整数");
int i = scanner.nextInt();
break;
} catch (InputMismatchException e) {
System.out.println("输入的类型不正确,请重新输入");
}
}
}
}
(二)try…catch的方式处理异常中的finally
try {
可能出现问题的代码 ;
}catch(异常名 变量名){
针对问题的处理 ;
}catch(异常名 变量名){
针对问题的处理 ;
}finally{ //可有可无
释放资源;
}
123456789
1.作用
不管try里面有没有遇到异常,finally里面的代码都会执行(特殊情况:在执行到finally之前jvm退出了(比如System.exit(0));
,那么finally的语句不会执行。)一般会在finally里面做一些收尾工作,比如释放资源。如果没有释放资源之类的收尾工作,可以没有finally这一段。
案例演示
import java.util.Scanner;
public class MyTest {
public static void main(String[] args) {
int a = 1;
int b = 0;
Scanner scanner = null;
try {
scanner = new Scanner(System.in);
System.out.println("请输入一个整数");
String next = scanner.next();
System.out.println(a / b);
//scanner.close();如果将释放资源的语句放在这,则出现异常后无法执行到
} catch (ArithmeticException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
//一般我们会在finally里面做一些收尾工作,比如释放资源
System.out.println("不管你try里面有没有遇到异常,finally里面的代码都会执行");
if (scanner != null) {
scanner.close();//释放资源
}
}
}
}
2.final、finally和finalize的区别
- final: 是一个状态修饰符, 可以用来修饰类 、变量 、成员方法。
被修饰的类不能被子类继承;修饰的变量其实是一个常量,不能被再次赋值;被修饰的方法不能被子类重写。 - finally:用在try…catch…语句中 , 作用: 释放资源 . 特点: 始终被执行(前提JVM不能退出)。
- finalize: Obejct类中的一个方法,用来回收垃圾。
面试题: 如果catch里面有return语句,请问finally的代码还会执行吗?如果会,请问是在return前还是return后?
答:会执行, 在return前。
public class MyTest5 {
public static void main(String[] args) {
int a=1;
int b=0;
try {
System.out.println(a/b);
} catch (ArithmeticException e) {
System.out.println("除数为0!");
return ;
}finally{
System.out.println("这句话被执行了");
}
}
}
(三)throws的方式处理异常
定义功能方法时,需要把出现的问题暴露出来让调用者去处理(哪个函数调用就哪个处理,俗称“甩锅”),那么就通过throws 异常类名 在方法上标识。此时一旦方法体执行出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行!
案例演示
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class MyTest5 {
public static void main(String[] args) throws ParseException {
parseDate();
System.out.println("这段代码被执行了");
}
private static void parseDate() throws ParseException {
String dateStr = "2019-10-10";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy:MM-dd");
dateFormat.parse(dateStr);
}
}
- 一开始第12行代码产生编译时异常,如果用throws方式处理异常,则是在
parseDate()
方法上标识抛出异常,抛出给了调用parseDate()
方法的main()
方法,于是第6行代码产生编译时异常,于是在main()
方法上标识抛出异常,又将异常抛给虚拟机,这就是“甩锅”处理异常的方式。
运行
编译时异常处理方式有两种:
①throws抛出异常(如果main函数抛出就是给虚拟机处理,最好不要,这种情况还是选择捕获处理)
②捕获处理
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class MyTest5 {
public static void main(String[] args) {
try {
parseDate();
} catch (ParseException e) {
e.printStackTrace();
} finally {
System.out.println("释放资源");
}
}
private static void parseDate() throws ParseException {
String dateStr = "2019-10-10";
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy:MM-dd");
dateFormat.parse(dateStr);
}
}
(四)throw的概述以及和throws的区别
1.throw的概述:
在功能方法内部出现异常,程序不能继续运行,就用throw把异常对象抛出。
格式:throw new xxxException("异常产生的原因");
如果throw抛出的是运行时异常或其子类,不需要异常处理,如果不是,则还需要处理异常(try-catch或throws)。
2.throws和throw的区别
A. throws
1. 用在方法声明后面,跟的是异常类名
2. 可以跟多个异常类名,用逗号隔开
3. 表示抛出异常,由该方法的调用者来处理
4. throws表示出现异常的一种可能性,并不一定会发生这些异常
B. throw
1. 用在方法体内,跟的是异常对象名
**2.**只能抛出一个异常对象名
3. 这个异常对象可以是编译时异常对象,可以是运行时异常对象
4. 表示抛出异常,由方法体内的语句处理
5. 使用throw抛出异常代表一定会发生某种异常
案例演示
import java.util.Scanner;
public class MyTest5 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入被除数");
int one = scanner.nextInt();
System.out.println("请输入第二个数字除数");
int two = scanner.nextInt();
int result = divide(one, two);
System.out.println("结果是"+result);
}
private static int divide(int one, int two) {
int r = 0;
if (two == 0) {
throw new ArithmeticException("你傻啊!除数为0了!");
} else {
r = one / two;
}
return r;
}
}
(五)自定义异常的概述和基本使用
- 为什么需要自定义异常?
因为在开发过程中,我们可能会遇到各种问题,而jdk不可能针对每一种问题都给出具体的异常类与之对应。为了满足需求,我们就需要自定义异常。 - 自定义异常类时必须继承已有异常类,将我们自定义的异常类纳入到我们的异常体系中
案例演示
输入成绩,成绩必须在0-100之间,否则产生异常。
import java.util.Scanner;
class ScoreException extends RuntimeException{
//注意 自定义异常类 提供有参数构造
public ScoreException(String m){
super(m);
}
}
public class MyTest3 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入你的考试成绩");
int score = scanner.nextInt();
test(score);
}
private static void test(int score) {
// 判断成绩范围
if (score > 100 || score < 0) {
// 抛出一个异常对象
throw new ScoreException("成绩不在有效的范围内(0~100)....");
} else {
System.out.println("成绩合法");
}
}
}
运行
(六)异常的注意事项
针对编译期异常
a:子类重写父类方法时,如果父类有抛出异常,子类的方法必须抛出和父类所抛出的异常相同或父类所抛出异常的子类异常,或者子类不抛出异常也是可以的。但是子类不能抛出父类所抛出的异常的父类异常(父亲坏了,儿子不能比父亲更坏)
b:如果父类抛出了多个异常,子类重写父类时,只能抛出相同的异常或者是它们的子集,子类不能抛出父类没有的异常,或者子类不抛出异常也是可以的。
c:如果父类被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常,如果子类方法内有异常发生,那么子类只能用try…catch…方式处理,不能用throws方式处理异常。
public class MyTest5 {
public static void main(String[] args) {
}
}
class Fu{
public void show() throws NullPointerException,ArithmeticException{
}
}
class Zi extends Fu{
@Override
public void show() throws ArithmeticException {
}
}
(七)try-catch和throws如何选择
try-catch:真正的将异常给处理掉了。
throws:只是将异常抛给了方法的调用者,并没有真正将异常处理掉。
- 如果父类中被重写的方法没有throws方式处理异常,则子类重写此方法也不能使用throws,这时如果出现异常子类必须使用try-catch方式处理。
- 假设方法a中,调用了另外的几个方法,这几个方法又是递进关系执行的,我们建议这几个方法自己的方法体使用throws的方式处理异常,而方法a可以考虑使用try-catch方式处理异常。