异常在Java中还是比较重要的,在工作中经常需要用到。今天整理一波来复习异常。
参考:《疯狂Java讲义》
常见的异常处理方式
try、catch:
try{}中用于包裹可能引起异常的代码块。catch通常会和try进行配对使用,通常用来处理和try对应的异常的处理。
try{
//这里放可能出现异常的代码块,出现异常编译会进行捕获
} catch (这个放置捕获的异常的类型对象)
{
// 这里进行相关的提示信息操作,自行定义
}
注意:try块里声明的变量是代码块内局部变量,它只在try块内有效,在catch块中不能访问该变量。这是说声明的变量,你别把所有的变量都当成局部变量。
finally:
这个关键字一般放到最后。表示一定会执行。他通常会在多个catch块的最后面进行搭配使用。放到最后。当然不限制catch的个数,一个也行。catch放多个的目的是让出现异常之后,能够进行针对不同的异常类提供不同的异常处理方式。系统发生不同的意外情况时,系统会生成不同的异常对象,java运行时就会根据该异常对象所属的异常类来决定使用哪个catch块来处理该异常。
通过在try块后提供多个catch块可以无须在异常处理块中使用if、switch判断异常类型,但依然可以针对不同的异常类型提供相应的处理逻辑,从而提供更细致、更有条理的异常处理逻辑。
在通常情况下,如果try块被执行一次,则try块后只有一个catch块会被执行,绝不可能有多个catch块被执行。除非在循环中使用了continue开始下一次循环,下一次循环又重新运行了try块,这才可能导致多个catch块被执行。
try{
//这里放可能出现异常的代码块,出现异常编译会进行捕获
} catch (异常的类型1)
{
// 这里进行相关的提示信息操作,自行定义
}
catch (异常的类型1本身 或者 异常的类型1的父类)
{
// 这里进行相关的提示信息操作,自行定义
}
catch (上一个捕获的异常的类型的本身,或者 上一个异常类的父类)
{
// 这里进行相关的提示信息操作,自行定义
}
finally{
//一定会被执行的代码
}
这里有个要特别注意的,从上到下,catch块中的异常对象都是其本身或者其异常类型父类。可以想象下,如果你在第一个catch块中就写了最大的异常类,你只要发生了异常,就会被他捕获。那你写后面异常子类是不是都不会被捕获。那写这么多catch块的意义在哪?写这么多catch块异常就是为了获取更加仔细的异常。通常父类异常在后,子类异常在前,比如(Exception 在后,NulPointerException在前),如果发生异常,只会匹配一个catch。
一般的处理如下图所示:
还有个注意点:当Java程序执行try块、catch块时遇到了return或throw语句,这两个语句都会导致该方法立即结束,但是系统执行这两个语句并不会结束该方法,而是去寻找该异常处理流程中是否包含finally块,如果没有finally块,程序立即执行return或throw语句,方法终止:如果有finaly块,系统立即开始执行finally块--只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句;如果finally块里也使用了return或throw等导致方法终止的语句,finally块已经终止了方法,系统将不会跳回去执行try块、catch块里的任何代码。
因此要尽量避免在finally块里使用return或throw等导致方法终止的语句,否则可能出现一些很奇怪的情况.
throws:
使用throws声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由上一级调用者处理;
如果main方法也不知道如何处理这种类型的异常,也可以使用throws声明抛出异常,该异常将交给JVM处理。JVM对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。
throws声明抛出只能在方法签名中使用,throws可以声明抛出多个异常类,多个异常类之间以逗号隔开。throws声明抛出的语法格式如下:
throws ExceptionClassl,ExceptionClass2..
上面throws声明抛出的语法格式仅跟在方法签名之后,如下例子程序使用了throws来声明抛出I0Exception异常,一旦使用throws语句声明抛出该异常,程序就无须使用try...catch块来捕获该异常了。
import java.io.FileInputStream; // 导入 FileInputStream 类
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest {
public static void main(String[] args) throws FileNotFoundException {
var fis = new FileInputStream("a.txt");
}
}
在疯狂java讲义中,作者建议:在大部分时候推荐使用Runtime异常,而不使用Checked异常。尤其当程序需要自行抛出异常时(如何自行抛出异常请看下一节),使用Runtime异常将更加简洁。
当使用Runtime异常时,程序无须在方法中声明抛出Checked异常,一旦发生了自定义错误,程序只管抛出Runtime异常即可。
如果程序需要在合适的地方捕获异常并对异常进行处理,则一样可以使用try...catch块来捕获Runtime异常。
使用Runtime异常是比较省事的方式,使用这种方式既可以享受“正常代码和错误处理代码分离”,“保证程序具有较好的健壮性”的优势,又可以避免因为使用Checked异常带来的编程烦琐性。因此,C#、Ruby、Python等语言没有所谓的Checked异常,所有的异常都是Runtime异常。
但Checked异常也有其优势--Checked异常能在编译时提醒程序员代码可能存在的问题,提醒程序员必须注意处理该异常,或者声明该异常由该方法调用者来处理,从而可以避免程序员因为粗心而忘记处理该异常的错误。
throw:
格式如下:
throw ExceptionInstance;
如果需要在程序中自行抛出异常,则应使用throw语句,throw语句可以单独使用,throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。
当Java运行时接收到开发者自行抛出的异常时,同样会中止当前的执行流,跳到该异常对应catch块,由该catch块来处理该异常。也就是说,不管是系统自动抛出的异常,还是程序员手动抛出的异常,Java运行时环境对异常的处理没有任何差别。
如果throw语句抛出的异常是Checked异常,则该throw语句要么处于try块里,显式捕获该异常,要么放在一个带throws声明抛出的方法中,即把该异常交给该方法的调用者处理:如果throw语句抛出的异常是Runtime异常,则该语句无须放在try块里,也无须放在带throws声明抛出的方法中:程序既可以显式使用try...catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给该方法调用者处理。
异常类型的分类
那么异常之间关系是怎么样的呢?java把所有的非正常情况分成两种:异常(Exception)和错误(Error),它们都继承Throwable父类。
Error错误:一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断。通常应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象。在定义该方法时,也无须在其throws子句中声明该方法可能抛出Error及其任何子类。一般要处理的异常是运行时的异常。
多异常的捕获
从java 7开始,一个catch块可以捕获多种类型的异常。使用一个catch块捕获多种类型的异常时需要注意:
- 捕获多种类型的异常时,多种异常类型之间用竖线 | 隔开。
- 捕获多种类型的异常时,异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值。
异常信息访问
这里一般是在自定义异常的时候才用到,用来描述异常的信息。如果程序需要在catch块中访问异常对象的相关信息,则可以通过访问catch块的后异常形参来获得。当java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋给catch块后的常参数,程序即可通过该参数来获得异常的相关信息。
所有的异常对象都包含了如下几个常用方法。
- getMessage():返回该异常的详细描述字符串。
- printStackTrace():将该异常的跟踪栈信息输出到标准错误输出。
- printStackTrace(PrintStream s):将该异常的跟踪栈信息输出到指定输出流。
- getStackTrace():返回该异常的跟踪栈信息。
import java.io.FileInputStream; // 导入 FileInputStream 类
import java.io.IOException;
public class ExceptionTest {
public static void main(String[] args) {
try {
var fis = new FileInputStream("a.txt");
} catch (IOException ioe) {
System.out.println(ioe.getMessage());
ioe.printStackTrace();
}
}
}
运行结果如下所示:
完整运行: