异常
- 异常就是代表程序出现的问题
- 误区:不是让我们以后不出现异常,而是程序出了异常后,该如何处理
- Error:代表系统级别错误(属于严重问题)
- 系统一旦出现问题,sun公司会把这些错误封装成Error对象,Error是给sun公司自己用的,不是给我们程序员用的,因此我们开发人员不用管它
- Exception:叫做异常,代表程序可能出现的问题,我们通常会用Exception以及它的子类来封装程序出现的问题
- 运行时异常:RuntimeException及其子类,编译阶段不会出现异常提醒,运行时出现的异常(如:数组索引越界异常)
- 编译时异常:编译阶段就会出现异常提醒(如:日期解析异常)
- 为什么不将其全部归为编译时异常呢?
- 编译阶段:Java不会运行代码,只会检查语法错误,或者做一些性能的优化
- 为什么不将其全部归为运行时异常呢?
- 因为编译时产生的异常更多是在于提醒程序员检查本地信息
- 而运行时异常主要是代码出错而导致程序出现的问题,一般是由于参数传递错误带来的问题
异常的作用
- 异常是用来查询bug的关键参考信息
- 异常可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况
异常的处理方式
JVM默认的处理方式
- 把异常的名称,异常原因及异常的位置等信息输出在了控制台
- 程序停止运行,下面的代码不会再执行了
自己处理(捕获异常)
- 格式
try {
可能出现异常的代码;
} catch(异常类名 变量名) {
异常的处理代码;
}
- 目的:当异常出现异常时,可以让程序继续往下执行
int[] arr = {1, 2, 3, 4, 5, 6};
try {
//可能出现异常的代码
System.out.println(arr[10]);
} catch(ArrayIndexOutOfBoundsException e) {
//如果出现了ArrayIndexOutOfBoundsException异常,我该如何处理
System.out.println("索引越界了");
}
System.out.println("看看我执行了吗");
-
代码逻辑
- 此处出现了异常,程序就会在这里创建一个ArrayIndexOutOfBoundsException对象
- new ArrayIndexOutOfBoundsException();
- 拿着这个对象到catch的小括号中对比,看括号中的变量是否可以接收这个对象
- 如果能被接收,就表示该异常被捕获,执行catch里面对应的代码
- 当catch里面所有的代码执行完毕,继续执行try...catch体系下面的其他代码
-
如果try中没有遇到问题,怎么执行
- 会将try里面所有的代码全部执行完毕,不会执行catch里面的代码
- 只有当出现了异常,才会执行catch里面的代码
-
如果try中可能会遇到多个问题,怎么执行
- 会写多个catch与之对应
- 如果我们要捕获多个异常,这些异常中存在父子关系的话,那么父类一定要写在下面
int arr = {1, 2, 3, 4, 5, 6};
try {
System.out.println(arr[10]);
System.out.println(2 / 0);
String s = null;
System.out.println(s.equals("abc"));
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("索引越界");
} catch (ArithmeticException e) {
System.out.println("除数不能为0");
} catch (NullPointerException e) {
System.out.println("空指针异常");
} catch (Exception e) {
System.out.println("Exception");
}
)
- JDK7的新的书写方式,当多个异常的处理方式一样时,我们可以在catch里面捕获多个异常,中间用 | 进行隔开,表示如果出现了A异常或者B异常的话,采取同一种处理方案
try {
System.out.println(arr[10]);
System.out.println(2 / 0);
} catch (ArrayIndexOutOfBoundsException | ArithmeticException e) {
System.out.println("Exception");
}
- 如果try中遇到的问题没有被捕获,怎么执行
- 相当于try...catch的代码白写了,最终还是会交给虚拟机进行默认处理
- 如果try中遇到了问题,那么try下面的代码还会执行吗
- 下面的代码就不会执行了,直接跳转到对应的catch当中,执行catch里面的语句体
- 但是如果没有对应的catch与之对应,那么还是会交给虚拟机进行处理
抛出处理
throws
- 写在方法定义处,表示声明一个异常,告诉调用者,使用本方法可能会有那些异常
public void 方法()throws 异常类名1, 异常类名2...{
...
}
- 编译时异常:必须要写
- 运行时异常:可以不写
throw
- 写在方法内,结束方法,手动抛出异常对象,交给调用者,方法中下面的代码不再执行了
public void 方法(){
throw new NullPointerException();
}
//定义一个方法求数组的最大值
int[] arr = {};
int max = 0;
//在这里如果真的出现异常了,还什么都没有写,那么异常会有虚拟机自行处理,后面的代码不会运行,为了让后面的代码能够正常运行,建议还是手动捕获异常
try {
max = getMax(arr);
} catch (NullPointerException e) {
System.out.println("空指针异常");
} catch (ArrayIndexOutOfException e) {
System.out.println("索引越界异常");
}
System.out.println(max);
//这里的两个异常都是运行时异常,可以不写
public static int getMax(int[] arr) throws NullPointerException, ArrayIndexOutOfException{
if (arr == null) {
//手动创建一个异常对象,并把这个对象交给方法的调用者处理
//此时方法就会结束,方法下面的代码不会再执行了
throw new NullPointerException();
}
if (arr.length == 0) {
throw new ArrayIndexOutOfException();
}
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
总结
| 处理方式 | 特点 |
|---|---|
| 虚拟机默认处理异常的方式 | 把异常信息一红色字体打印再控制台,并结束程序 |
| 捕获:try...catch | 一般用在调用处,能让代码继续往下运行 |
| 抛出:throws throw | 在方法中,出现异常了 方法就没有继续运行下去的意义了,采取抛出处理 让该方法结束运行并告诉调用者出现了问题 |
异常中的常见方法
Throwable的成员方法
| 方法名称 | 说明 |
|---|---|
| public String getMessage() | 返回此throwable的详细消息字符串(异常的信息) |
| public String toString() | 返回此可抛出的简短描述(异常的名字和异常的信息) |
| public void printStackTrace() | 把异常的错误信息输出在控制台(异常的名字、信息和位置),仅仅是打印信息,不会停止程序的运行 |
- 为什么是输出是红字
- 正常的输出语句
System.out.println(123);
- 错误的输出语句(用来打印错误信息)
System.err.println(123);
- 如果正常和错误的语句在一起输出时,顺序可能会不一样,和多线程的知识有关
- 正常的输出语句
自定义异常
- 意义:就是为了让控制台的报错信息更加见名知意
步骤
- 定义异常类
- 见名知意
- 写继承关系
- 运行时:继承RuntimeException
- 编译时:继承Exception
- 空参构造
- alt + insert 第一个
- 带参构造
- alt + insert 第二个
练习
- 键盘录入自己心仪的女朋友姓名和年龄,姓名的长度在3-10之间,年龄的范围在18-40之间,超出这个范围是异常数据不能赋值,需要重新录入,一直录到正确为止,需要考虑用户在键盘录入时的所有情况,比如:录入年龄时超出范围,录入年龄时录入了abc等情况
//javabean类
public class GirlFriend {
private int age;
private String name;
public GirlFriend() {
}
public GirlFriend(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
//运行时异常,可写可不写
public void setAge(int age) throws AgeOutOfAgeException{
if (age < 18 || age > 40) {
throw new AgeOutOfAgeException(age + "年龄超出范围");
}
this.age = age;
}
public String getName() {
return name;
}
//运行时异常,可写可不写
public void setName(String name) throws NameFormatException{
if (name.length() < 3 || name.length() > 10) {
throw new NameFormatException(name + "长度超出范围");
}
this.name = name;
}
public String toString() {
return "GirlFriend{age = " + age + ", name = " + name + "}";
}
}
//两个自定义异常
public class AgeOutOfAgeException extends RuntimeException{
public AgeOutOfAgeException() {
}
public AgeOutOfAgeException(String message) {
super(message);
}
}
public class NameFormatException extends RuntimeException{
public NameFormatException() {
}
public NameFormatException(String message) {
super(message);
}
}
while (true) {
try {
System.out.println("请输入姓名");
String nameStr = sc.nextLine();
gf.setName(nameStr);
System.out.println("请输入年龄");
String ageStr = sc.nextLine();
int age = Integer.parseInt(ageStr);
gf.setAge(age);
break;
} catch (NumberFormatException e) {
e.printStackTrace();
continue; //可写可不写
} catch (AgeOutOfAgeException e) {
e.printStackTrace();
continue; //可写可不写
} catch (NameFormatException e) {
e.printStackTrace();
continue; //可写可不写
}
}
System.out.println(gf);