一、异常机制
1.异常机制的引入
先入为主的一个概念:在以往的一些学习过程中,我们往往把一些错误用if-else语句进行处理。比如:如果一个字符串的输入并不是我们需要的,我们就用if-else对其进行控制(如果不是满足某种格式的话),以达到我们想要的结果。但是有些问题并不能解决的如此顺利,甚至它会影响到程序的运行。此时,异常机制就显得尤为重要了。
在程序当中,错误可能产生于程序员根本没有预料到的情况,或者是超出了程序员可控范围之内,比如:输入数据非法、试图访问根本不出存在的地址等。异常往往是程序在执行期间发生的事情,它中断了正在执行的程序正常指令流。
String str = null;
System.out.println(str.toString());
//Exception in thread "main" java.lang.NullPointerException
//空指针异常
//上述代码异常时,下面的代码将不会执行
System.out.println(30/0);
//Exception in thread "main" java.lang.ArithmeticException: / by zero
//算术异常
对于这些非正常情况(发生的时候回中断程序运行),一般都分两种情况:
①.Error一般是JVM相关的不可修复的错误,比如:内存溢出、系统崩溃等。此时由JVM抛出,我们无法处理。常见的有:StackOverflowError(程序递归过深发生内存溢出)
②.Exception表示异常。程序出现了不正常情况,并且可以修复。常见的有空指针异常和数组越界异常等。
当异常出现时,必须处理异常!!!
2.异常捕捉try/catch
如果异常出现了,我们通常会使用try/catch语句来处理异常。其语法如下:
try{
//可能出现异常的代码块
}catch(异常类型 自定义的异常名e)
{
//处理异常的代码
//记录日志/打印异常信息/抛出异常
}
需要注意的是:try/catch都不能单独使用,必须连用!
那么,如何获取异常信息呢?我们往往使用Throwable类的方法,如getMessage()等。代码示例如下:
public class Demo{
public static void main(String[] args) {
System.out.println("Begin!");
try {
int num = 10/0;
System.out.println(num);//出现异常,此句不执行。转而执行Catch代码块
}catch(ArithmeticException e) {
System.out.println("About Exception:" + e.getMessage());
//String getMessage()我们一般用来提示用户。输出错误性质
System.out.println("Exception : " + e.toString());
//String toString()一般我们不用。输出异常的类型和性质
e.printStackTrace();
//void printStackTrace() 我们在开发中常用,多用,方便调试和修改。
}
System.out.println("End!");
}
}
但是这仅仅是发生了一个算术异常, 在实际开发中也有可能会遇到多个异常的情况。此时的代码就应该是这样的:
try{
//可能出现异常的代码块
}catch(异常类型A 自定义的异常名e1)
{
//处理异常的代码
//记录日志/打印异常信息/抛出异常
}catch(异常类型B 自定义的异常名e2)
{
//处理异常的代码
//记录日志/打印异常信息/抛出异常
}
需要注意的是:①在一个catch语句中,只能捕获中类型的异常,如果需要捕获多种异常,就需要使用catch语句。
②代码在一瞬间只能出现一种类型的异常,只需要一个catch捕捉,但是绝对不会同时出现多个异常。
当然,一个完整的异常处理也是需要finally语句。它的作用是:无论程序有无异常发生,无论try/catch语句是否顺利执行,它总会执行finally语句。当然也会有4种特殊情况:
①.finally代码块中发生了异常。
②.前面的代码使用了System.exit()退出程序
③.程序所在的线程死亡
④.关闭CPU
public class Demo_01 {
public static void main(String[] args) {
System.out.println("Begin!");
try {
int num = 10/0;
System.out.println(num);//出现异常,此句以下不执行。转而执行Catch代码块
}catch(ArithmeticException e) {
System.out.println("About Exception:" + e.getMessage());
//String getMessage()我们一般用来提示用户。
System.out.println("Exception : " + e.toString());
//String toString()一般我们不用。
e.printStackTrace();
//void printStackTrace() 我们在开发中常用,多用,方便调试和修改。
System.exit(0);
//加入此句finally将无法执行
}finally {
System.out.println("Shut Down!");
}
System.out.println("End!");
}
}
那么,此时又对于finally有个小小的问题:下列代码输出什么值呢?
**public class Demo_01 {
public static void main(String[] args) {
int num = testMethod();
System.out.print(num);
}
private static int testMethod()
{
try {
return 1;
}finally {
return 1001;
}
}
}**
通过Debug后,可以看到的是,当进入testMethod()后,首先执行的try语句,但是并没有执行return 1而是直接跳入到了finally中。可以得到:finally是先于return执行的。
3.异常与Throwable
异常的分类:
①.编译时期异常:checked异常。在编译时期就是检查,如果没有处理异常将会编译失败。比如上面提到的算术异常。处理方案有二:1.使用throw抛出;2.使用try/catch。
②.运行时期异常:在运行时期检查的异常,不会影响编译器检测(不报错)。运行异常:在编译时期可处理可不处理。
但是我们多数还是使用Runtime异常,受检异常真的很难处理。
4.抛出异常
有两种方法抛出异常:throw和throws。
①.用throw抛出异常的时候,是运用于方法内部的,抛出一个具体的异常对象(是对象)。语法格式为:
throw new 异常类("异常信息");//后终止方法。
具体应用:
public class Demo_01 {
public static void main(String[] args) {
try {
int ret = divide(12,0);
System.out.println(ret);
}catch(ArithmeticException e) {
System.out.println(e.getMessage());
}
}
private static int divide(int num1,int num2)
{
System.out.println("Begin!");
if(num2 == 0){
throw new ArithmeticException("除数不能为0");
//执行完此句后,后面的句子均不能执行了
//return是返回一个值,它就相当于返回一个错误给调用者
}
try {
int ret = num1/num2;
System.out.println("结果 = " + ret);
return ret;
}catch(ArithmeticException e) {
e.printStackTrace();
}
System.out.println("End!");
return 0;
}
同时,对于Java的源代码中,也有类似。比如:String类中的charAt()方法,如果参数为-1的话,会有StringIndexOutOfBoundsException异常。源代码为:
②.throws用于方法声明之上,表示该方法不处理异常而是提醒方法的调用者来处理异常(抛出异常)。语法格式:
修饰符 返回类型 方法名 (参数列表) throws Exception{}
代码示例如下:
public class Demo_01 {
public static void main(String[] args) throws Exception{
divide(10,0);
}
//不处理这类型的异常,提醒调用者处理
private static int divide (int num1,int num2) throws Exception
{
System.out.println("Begin!");
if(num2 == 0){
new Exception("除数不能为0");
}
try {
int ret = num1/num2;
System.out.println("结果 = " + ret);
return ret;
}catch(ArithmeticException e) {
e.printStackTrace();
}
System.out.println("End!");
return 0;
}
}
如果都不处理异常,连主方法也不处理异常,那么将继续抛出给JVM,底层的处理机制就是打印异常的跟踪栈信息。runtime异常默认的就是这种处理方式。
以上代码的异常结果:
5.自定义异常结果
在开发中,根据需要自定义异常。异常类如何定义?
方法一:自定义一个受检查的异常类:自定义类 并继承于java.lang.Exception
方法二:自定义一个运行时期的异常类:自定义类 并继承于java.lang.RuntimeException
(此刻再次提倡我们使用RuntimeException)。
例如已经定义好的了ArithmeticException:
代码示例(自定义一个LogicException异常,模拟一个注册用户名判断机制):
//自定义的异常
public class LogicException extends RuntimeException {
private static final long serialVersionUID = 1L;
public LogicException() {
super();
}
public LogicException(String message, Throwable cause) {
super(message, cause);
/*
* @param message 表示当前异常的原因/信息
* @param cause 表示异常的根本原因
* */
}
public LogicException(String message) {
super(message);
}
}
//用户名判重机制代码
public class RegisterDemo {
private static String[] names = {"Lucy","Jack"};//模拟数据库提供的信息
public static void main(String[] args) {
try {
checkUsername("Lucy");
System.out.println("Congratulations!");
}catch(LogicException e) {
//处理异常
String errorMsg = e.getMessage();
System.out.println(errorMsg);
}
}
public static boolean checkUsername(String username)
{
for(String name : names) {
if(name.equals(username)) {
throw new LogicException("Registered!");
}
}
return true;
}
}
最后,总结一下异常机制的使用原则:
①.异常只能用于非正常情况,毕竟try-catch会影响性能。
②.需要为异常提供说明文档,比如Java doc,如果自定义了一个异常或者某一个方法抛出了异常。我们应该记录在文档注释中。
③.尽可能避免一些没有必要的异常,比如算术异常和空指针异常。
④.异常的粒度很重要。应该为一个基本操作提供try-catch代码块,而不是对几百行代码使用。
⑤.不建议在循环中使用异常处理。
⑥.自定义异常尽量使用RuntimeException,它真的很好用。