【JAVA入门】Day36 - 异常
文章目录
- 【JAVA入门】Day36 - 异常
- 一、异常结构体系综述
- 1.1 错误(Error)
- 1.2 异常(Exception)
- 1.3 运行时异常(RuntimeException)
- 1.4 其他异常
- 二、编译时异常和运行时异常
- 三、异常的作用
- 3.1 查询 bug 的关键参考信息
- 3.2 作为方法内部一种特殊的返回值
- 四、异常的处理方式
- 4.1 JVM 默认的处理方式
- 4.2 自己处理(捕获异常)
- 4.3 如果 try 中没有遇到问题,怎么执行?
- 4.4 如果 try 中可能会遇到多个问题,怎么执行?
- 4.5 如果 try 中碰到的问题没有被捕获?
- 4.6 如果 try 中遇到了问题,那么 try 下面的其他代码还会执行吗?
- 五、Throwable 类
- 六、抛出处理
- 七、自定义异常
异常就是代表程序出现的问题。Java 的异常机制可以告知 JVM,程序出现异常之后,应该如何处理。
比如下面两种异常情况:
int[] arr = {10, 20, 30};
System.out.println(arr[3]);
ArrayIndexOutOfBoundsException 数组越界异常
int a = 10;
int b = 0;
System.out.println(a / b);
ArithmeticException 算术异常
Java 中有太多异常类型了,它们组成了异常结构体系。
一、异常结构体系综述
所有的错误和异常都继承自 Java.lang.Throwable 类,然后分为两个分支,一个叫“错误”(Error),一个叫“异常”(Exception),其中异常中最重要的一个总类就是“运行时异常”(RuntimeException),它下面还有很多很多异常类。
1.1 错误(Error)
Error 代表的是系统级别的错误(属于严重问题),它们是 JVM 无法解决的问题,如:栈溢出(StackOverflowError)、内存溢出(OOM)等,系统一旦出现问题,sun 公司会把这些错误封装成 Error 对象给公司内部研究,我们开发人员无需关注。
1.2 异常(Exception)
Exception 代表程序可能出现的问题,我们通常会用 Exception 及它的子类来封装程序出现的问题。
1.3 运行时异常(RuntimeException)
RuntimeException 及其子类,编译阶段不会出现异常提醒,在运行时才会出现异常(如:数组索引越界异常)。
1.4 其他异常
指的是编译阶段就会出现提醒的异常(如:日期解析异常)。
二、编译时异常和运行时异常
以下代码明确了编译时异常和运行时异常的区别:
package Exceptions;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;public class ExceptionDemo1 {public static void main(String[] args) throws ParseException {//编译时异常(在编译阶段,必须要手动处理,否则代码报错)String time = "2030年1月1日";SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");Date date = sdf.parse(time);System.out.println(date);//运行时异常(编译阶段不会出现报错,在运行时才会出现)int[] arr = {1, 2, 3, 4, 5};System.out.println(arr[10]); //ArrayIndexOutOfBoundsException}
}
Java 为什么设计这两种异常?这正是因为 Java 在把 java 文件编译成字节码 class 文件时,不会运行代码,只会检查语法是否错误,或者做一些性能的优化。而 JVM 才是真正运行代码的。编译时异常更多地是提醒程序员检查本地信息,而运行时异常并不在于提醒,它是真的代码出错导致程序运行时出现了问题。
三、异常的作用
异常的两个重要作用就是:
- 作用一:异常是用来查询 bug 的关键参考信息。
- 作用二:异常可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况。
3.1 查询 bug 的关键参考信息
开发环境运行代码后的异常往往会在下方提示,根据异常提示的名字、信息,我们可以快速找到 bug 产生的原因。
package Exceptions;public class ExceptionDemo2 {public static void main(String[] args) {/*- 作用一:异常是用来查询 bug 的关键参考信息。- 作用二:异常可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况。*/Student[] arr = new Student[3];String name = arr[0].getName();System.out.println(name);/*Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Exceptions.Student.getName()" because "arr[0]" is nullat Exceptions.ExceptionDemo2.main(ExceptionDemo2.java:11)*/}
}
3.2 作为方法内部一种特殊的返回值
异常可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况。
例如下面的代码,在同学类的setAge()方法中,当年龄小于18岁或超过40岁时,抛出运行时异常。
public void setAge(int age) {if(age < 18 || age > 40){//年龄超出范围throw new RuntimeException();} else {this.age = age;}}
在测试类中如果存入异常数据,会出现异常提醒。
package Exceptions;public class ExceptionDemo3 {public static void main(String[] args) {//1.创建学生对象Student s1 = new Student();//年龄:(同学)18~40岁//返回异常获知赋值失败s1.setAge(50);/*Exception in thread "main" java.lang.RuntimeExceptionat Exceptions.Student.setAge(Student.java:39)at Exceptions.ExceptionDemo3.main(ExceptionDemo3.java:9)*/}
}
四、异常的处理方式
在 Java 中有三种异常的处理方式:
① JVM 默认的处理方式。
② 自己处理。
③ 抛出异常(交给调用者处理)。
4.1 JVM 默认的处理方式
- 虚拟机把异常的名称,异常原因及异常出现的位置等信息输出在控制台。
- 程序停止执行。
4.2 自己处理(捕获异常)
格式:
try {可能出现异常的代码;
} catch(异常类名 变量名) {异常处理的代码;
}
例子:
package Exceptions;public class ExceptionDemo4 {public static void main(String[] args) {int[] arr = {1, 2, 3, 4, 5, 6};try {System.out.println(arr[10]);} catch(ArrayIndexOutOfBoundsException e){System.out.println("索引越界了");}System.out.println("看看我执行了吗?");}
}
如果 try{ } 中的语句出现了异常,就会创建一个异常对象,创建什么对象取决于异常的类型,上面代码的异常类型是 ArrayIndexOutOfBoundsException (数组索引越界异常),然后会把这个对象和 catch() 中的异常变量进行对比,看这个变量是否可以接收这种对象,如果异常种类相同就可以接收,表示该异常被捕获,然后执行 catch() { } 中的代码,当这些代码执行完毕,会继续执行 try…catch 体系之后的其他代码,并不会导致程序的中断。
4.3 如果 try 中没有遇到问题,怎么执行?
如果 try 中没有遇到问题,会直接执行 try{ } 中的代码,而不会去执行 catch() { } 中的代码了,然后执行 try…catch 体系后面的代码。
package Exceptions;public class ExceptionDemo5 {public static void main(String[] args) {int[] arr = {1, 2, 3, 4, 5, 6};try {System.out.println(arr[0]);} catch(ArrayIndexOutOfBoundsException e){System.out.println("索引越界了");}System.out.println("看看我执行了吗?");}
}
4.4 如果 try 中可能会遇到多个问题,怎么执行?
如果 try{ } 中可能出现多种问题,那么我们就需要写多个 catch(){ } 与之对应。
package Exceptions;public class ExceptionDemo6 {public static void main(String[] args) {/*细节:如果我们要捕获多个异常,这些异常中如果存在父子关系,那么父类一定要写在下面*/int[] arr = {1, 2, 3, 4, 5, 6};try {System.out.println(arr[10]); //ArrayIndexOutOfBoundsExceptionSystem.out.println(2 / 0); //ArithmeticExceptionString s = null;System.out.println(s.equals("abc")); //NullPointerException}catch(ArrayIndexOutOfBoundsException e){System.out.println("索引越界了");}catch(ArithmeticException e){System.out.println("除零错误");}catch(NullPointerException e){System.out.println("空指针异常");}catch(Exception e){System.out.println("存在异常");}//在 JDK7 以前,异常的一个捕获书写一个异常//如果有多个异常,处理方式是一样的,可以一起写,中间用 |try {System.out.println(2 / 0);}catch(ArrayIndexOutOfBoundsException | ArithmeticException e) {System.out.println("索引越界或除数为0");}System.out.println("看看我执行了吗?");}
}
4.5 如果 try 中碰到的问题没有被捕获?
如果 try{ } 中的代码发生的异常没有被 catch() { } 捕获到(catch 中写的异常类型和你发生的异常类型不同,所以没捕获到),虚拟机会直接报错(相当于 catch(){ } 没捕获到异常,直接交给了虚拟机处理)。
package Exceptions;public class ExceptionDemo7 {public static void main(String[] args) {int[] arr = {1, 2, 3, 4, 5, 6};try {System.out.println(arr[10]);} catch(NullPointerException e){System.out.println("索引越界了");}System.out.println("看看我执行了吗?"); //没执行/*Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 6at Exceptions.ExceptionDemo7.main(ExceptionDemo7.java:9)*/}
}
4.6 如果 try 中遇到了问题,那么 try 下面的其他代码还会执行吗?
try{ } 中某一行代码遇到问题,它会直接跳转到 catch() { } 结构中,并不会继续执行此行代码之后的代码。
package Exceptions;public class ExceptionDemo8 {public static void main(String[] args) {int[] arr = {1, 2, 3, 4, 5, 6};try{System.out.println(arr[10]);System.out.println("看看我执行了吗?... try");}catch(ArrayIndexOutOfBoundsException e){System.out.println("索引越界了");}System.out.println("看看我执行了吗?... 其他代码");}
}
五、Throwable 类
Throwable 类是异常的最高层类,它有三个常用方法。
三个方法的使用特征如下:
package Exceptions;public class ExceptionDemo9 {public static void main(String[] args) {int[] arr = {1, 2, 3, 4, 5, 6};try {System.out.println(arr[10]);} catch (ArrayIndexOutOfBoundsException e) {String message = e.getMessage();System.out.println(message); //Index 10 out of bounds for length 6String str = e.toString();System.out.println(str); //java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 6e.printStackTrace(); //java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 6// at Exceptions.ExceptionDemo9.main(ExceptionDemo9.java:8)//printStackTrace方法会把异常信息以红色字体打印在虚拟机控制台//但是这样做不会结束代码的执行,后面的代码仍旧可以执行}System.out.println("看看我执行了吗");}
}
printStackTrace() 方法所包含的信息已经包含了前两个方法的信息,我们在使用时一般都使用这个方法,且这个方法是 try…catch 结构的默认处理方法。
这个方法在底层是利用 System.err.println() 方法进行输出,因此可以把字体转为红色打印在控制台。
六、抛出处理
throws 和 throw 的区别:
- throws:写在方法定义处,表示声明一个异常,告诉调用者,使用本方法可能会有哪些异常。
public void 方法() throws 异常类名1,异常类名2... {...
}
注意:如果异常是编译时异常,那就必须要写;如果是运行时异常,可以不写。
- throw:写在方法内部,手动抛出异常对象,交给调用者,方法中下面的代码就不再执行了。
public void 方法(){throw new NullPointerException();
}
异常抛出的用例:
package Exceptions;public class ExceptionDemo10 {public static void main(String[] args) {/*调用方法求一个数组的最大值*/int[] arr = null;try {int max = getMax(arr);} catch (NullPointerException e) {System.err.println("空指针异常");} catch (ArrayIndexOutOfBoundsException e) {System.err.println("索引越界异常");}}public static int getMax(int[] arr){if(arr == null) {throw new NullPointerException(); //手动创建一个异常对象,并把这个异常交给方法调用者处理//之后的代码就不会再执行}if(arr.length == 0) {throw new ArrayIndexOutOfBoundsException();}System.out.println("看看我执行了吗?");int max = arr[0];for (int i = 1; i < arr.length; i++) {if(arr[i] > max) {max = arr[i];}}return max;}
}
【练习】使用异常完成需求。
需求:
键盘录入自己心仪的女朋友姓名和年龄。姓名的长度在3~10之间,
年龄的范围为18– 40岁,
超出这个范围是异常数据不能赋值,需要重新录入,一直录到正确为止。
提示:
需要考虑用户在键盘录入时的所有情况。
比如:录入年龄时超出范围,录入年龄时录入了abc等情况。
package Exceptions;import java.util.Scanner;public class ExceptionDemo11 {public static void main(String[] args) {/*姓名长度为 3-10年龄范围为 18-40否则不赋值,重新录入改写set方法*///1.键盘录入Scanner sc = new Scanner(System.in);//2.女朋友对象GirlFriend gf = new GirlFriend();while (true) {//3.接收女朋友的姓名try {System.out.println("请输入女朋友名字:");String name = sc.nextLine();gf.setName(name);//4.接收女朋友的年龄System.out.println("请输入女朋友的年龄:");String ageStr = sc.nextLine();int age = Integer.parseInt(ageStr);gf.setAge(age);//如果所有的输入数据都是正确的,跳出循环break;} catch (NumberFormatException e) {System.out.println("年龄的格式有误,请输入数字。");} catch (RuntimeException e) { //父类一定要写在下面System.out.println("姓名的长度或年龄的范围有误。");}}//5.打印System.out.println(gf.toString());}
}
要注意,这里的抛出异常,需要自己设定,在 GirlFriend 类中写具体的抛出语句,因为 Java 不会知道你对姓名和年龄的特殊要求,这里的异常需要手动抛出。
public void setName(String name) {int len = name.length();if(len < 3 || len > 10){throw new RuntimeException();}this.name = name;}
public void setAge(int age) {if(age < 18 || age > 40){throw new RuntimeException();}this.age = age;}
七、自定义异常
通过上面的例子我们意识到,Java 给我们准备的异常不能满足我们平时的需求,需要我们自定义想要的异常类型。定义一个异常类的目的,就是为了让控制台的报错信息更加见名知意。
定义一个完整的异常类,我么要按照以下步骤:
① 定义异常类
② 写继承关系
③ 空参构造
④ 带参构造
将上面的方法改为抛出自定义异常,可以分别告知“姓名”和“年龄”的异常,并且可以直接传递异常信息作为参数。
先定义一个姓名格式异常,继承运行时异常。注意见名知意,然后带一个空参构造,一个有参构造,参数是错误信息。
package Exceptions;public class NameFormatException extends RuntimeException {//NameFormat:当前异常名字,表示姓名格式化问题//Exception:当前类是一个异常类//运行时:继承RuntimeException//编译时:继承Exceptionpublic NameFormatException() {}public NameFormatException(String message) {super(message);}
}
再定义一个年龄超范围异常。
package Exceptions;public class AgeOutOfBoundsException extends RuntimeException{public AgeOutOfBoundsException() {}public AgeOutOfBoundsException(String message) {super(message);}
}
改造 GirlFriend 类代码,抛出这两个异常,可以加提示信息作为参数。
public void setName(String name) {int len = name.length();if(len < 3 || len > 10){throw new NameFormatException(name + "格式有误,长度应该为:3~10");}this.name = name;}
public void setAge(int age) {if(age < 18 || age > 40){throw new AgeOutOfBoundsException(age + "年龄超出了范围");}this.age = age;}
在测试类中捕获异常,可以分别捕获,分别处理,可用 printStackTrace() 方法直接打印异常信息。
package Exceptions;import java.util.Scanner;public class ExceptionDemo11 {public static void main(String[] args) {/*姓名长度为 3-10年龄范围为 18-40否则不赋值,重新录入改写set方法*///1.键盘录入Scanner sc = new Scanner(System.in);//2.女朋友对象GirlFriend gf = new GirlFriend();while (true) {//3.接收女朋友的姓名try {System.out.println("请输入女朋友名字:");String name = sc.nextLine();gf.setName(name);//4.接收女朋友的年龄System.out.println("请输入女朋友的年龄:");String ageStr = sc.nextLine();int age = Integer.parseInt(ageStr);gf.setAge(age);//如果所有的输入数据都是正确的,跳出循环break;} catch (NumberFormatException e) {System.out.println("年龄的格式有误,请输入数字。");} catch (NameFormatException e) { //父类一定要写在下面e.printStackTrace();} catch (AgeOutOfBoundsException e) {e.printStackTrace();}}//5.打印System.out.println(gf.toString());}
}