简单谈谈java的异常处理(Try Catch Finally)

作者:hebedich 时间:2021-08-01 12:40:02 

异常的英文单词是exception,字面翻译就是“意外、例外”的意思,也就是非正常情况。事实上,异常本质上是程序上的错误,包括程序逻辑错误和系统错误。

一 前言

java异常处理大家都不陌生,总的来说有下面两点:

1.抛出异常:throw exception


class SimpleException{
 public void a() throws Exception{
   throw new Exception();
 };
}

2.捕获异常:


public class MyException {
 public static void main(String[] args){
   MyException e = new MyException();
   SimpleException se = new SimpleException();
   try {
     se.a();
   } catch (Exception e1) {
     e1.printStackTrace();
   }
 }
}

class SimpleException{
 public void a() throws Exception{
   throw new Exception();
 };


本文将在此基础上,更加深入的谈一些细节问题。

二 自定义异常类

java语言为我们提供了很多异常类,但是有时候我们为了写代码的方便还是要自定义的去创造异常类:

class SimpleException extends Exception {};
创建好之后我们可以使用try catch捕获它:


public class MyException {
 public static void main(String[] args){
   MyException e = new MyException();
   try {
     e.a();
   } catch (SimpleException e1) {
     e1.printStackTrace();
   }
 }

public void a() throws SimpleException{
   throw new SimpleException();
 }
}

class SimpleException extends Exception {};

我们在MyException中定义了一个方法a(),让它抛出SimpleException异常,然后我们在main()中调用这个方法,并使用try catch捕获了这个异常:


SimpleException
 at MyException.a(MyException.java:15)
 at MyException.main(MyException.java:8)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:606)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

Process finished with exit code 0

编译执行后的结果,主要看前三行就行了。这里着重说明几点:
1.抛出异常类型的指定:(exception specification)
当我们需要在一个方法中抛出一个异常时,我们使用throw后加某异常类的实例,程序会在此向客户端程序(调用这段代码的程序)抛出对应异常并在此退出(相当于return)。另外需要注意的是,我们必须在定义该方法的时候指明异常类型,比如下面这段代码会抛出SimpleException异常

public void a() throws SimpleException

2.抛出多个异常:


 public void a() throws SimpleException,AException,BException{
   throw new SimpleException();

}

不同的异常类之间用逗号隔开即可,在这种情况下我们不必须throw每个异常类的实例(),但是客户端代码必须要catch到每个异常类:


public class MyException {
 public static void main(String[] args){
   MyException e = new MyException();
   try {
     e.a();
   } catch (SimpleException e1) {
     e1.printStackTrace();
   } catch (BException e1) {
     e1.printStackTrace();
   } catch (AException e1) {
     e1.printStackTrace();
   }
 }

public void a() throws SimpleException,AException,BException{
   throw new SimpleException();

}
}

class SimpleException extends Exception {};
class AException extends Exception{}
class BException extends Exception{}

三 stack trace

无论是抛出异常,或者是捕获处理异常,我们的目的是为了写出更健壮的程序,这很大程度上依赖于java异常机制给我们提供的异常信息,而它的载体就是stack trace。
前面的代码中我们直接使用printStackTrace()打印出异常信息,其实我们还可以使用getStackTrace()方法来获取StackTraceElement型的集合,如果你手头有IDEA的话,你可以先搜索出StackTraceElement类,可以发现它实现了接口Serializable ,再看看它的类描述:


/**
* An element in a stack trace, as returned by {@link
* Throwable#getStackTrace()}. Each element represents a single stack frame.
* All stack frames except for the one at the top of the stack represent
* a method invocation. The frame at the top of the stack represents the
* execution point at which the stack trace was generated. Typically,
* this is the point at which the throwable corresponding to the stack trace
* was created.
*
* @since 1.4
* @author Josh Bloch
*/

讲的很清楚,这个类的每个实例都是stack trace的一个元素,代表着一个stack frame,stack trace是由getStackTrace()方法返回的。后边的我试着翻译了几遍,都觉得不好,还是直接上代码才能说清楚:


public class MyException {
 public static void main(String[] args){
   MyException e = new MyException();
   e.a();

public void a(){
   try {
     throw new Exception();
   } catch (Exception e) {
     StackTraceElement[] ste = e.getStackTrace();
     System.out.println(ste.length);

}
 }
}

我们定义了方法a,让它抛出Exception异常的同时捕获它,然后我们通过getStackTrace()方法得到一个StackTraceElement型的数组,并打印出数组的长度:

7

Process finished with exit code 0
我们把代码稍微改一下,不在a中捕获异常了,我们重新定义一个方法b,让它在调用a的同时将异常捕获:


public class MyException {
 public static void main(String[] args){
   MyException e = new MyException();
   e.b();
 }

public void b(){
   try {
     a();
   } catch (Exception e) {
     StackTraceElement[] ste = e.getStackTrace();
     System.out.println(ste.length);
   }
 }

public void a() throws Exception{
   throw new Exception();
 }
}

结果如下:

8

Process finished with exit code 0
别急,我们再来看点有趣的:


public class MyException {
 public static void main(String[] args){
   MyException exception = new MyException();
   try {
     exception.c();
   } catch (Exception e) {
     StackTraceElement[] ste = e.getStackTrace();
     System.out.println(ste.length);
     System.out.println("---------------------------------------------------------------");
     for (StackTraceElement s : e.getStackTrace()){
       System.out.println(s.getClassName()+":method "+s.getMethodName()+" at line"+s.getLineNumber());
     }
     System.out.println("---------------------------------------------------------------");

}

}

public void c() throws Exception{
   try {
     a();
   }catch (Exception e){
     throw e;
   }
 }

public void a() throws Exception{
   throw new Exception();
 }
}

下面是结果:


8
---------------------------------------------------------------
MyException:method a at line43
MyException:method c at line39
MyException:method main at line9
sun.reflect.NativeMethodAccessorImpl:method invoke0 at line-2
sun.reflect.NativeMethodAccessorImpl:method invoke at line57
sun.reflect.DelegatingMethodAccessorImpl:method invoke at line43
java.lang.reflect.Method:method invoke at line606
com.intellij.rt.execution.application.AppMain:method main at line144
---------------------------------------------------------------

Process finished with exit code 0

也就是说,getStackTrace()返回一个栈,它包含从调用者(main())到初始抛出异常者(a())的一些基本信息 ,在上面的代码中,我们在c方法中调用a方法时捕获异常并通过throws将其再次抛出(rethrow),调用c方法的方法可以捕获并处理异常,也可以选择继续抛出让更高层次的调用者(靠近栈底)处理。rethrow虽然很方便,但存在着一些问题,我们看下面这段代码:


public class MyException {
 public static void main(String[] args){
   MyException exception = new MyException();
   try {
     exception.c();
   } catch (Exception e) {
     e.printStackTrace(System.out);
   }

}

public void c() throws Exception{
   try {
     a();
   }catch (Exception e){
     throw e;
   }
 }

public void a() throws Exception{

throw new Exception("Exception from a()");
 }
}
java.lang.Exception: Exception from a()
 at MyException.a(MyException.java:40)
 at MyException.c(MyException.java:30)
 at MyException.main(MyException.java:21)

我们在c中重新抛出e,在main中使用 e.printStackTrace()打印出来,可以看到打印出来stack trace还是属于a的,如果我们想把stack trace变成c的可以这么写:


public class MyException {
 public static void main(String[] args){
   MyException exception = new MyException();

try {
     exception.c();
   } catch (Exception e) {
     e.printStackTrace(System.out);
   }

}

public void c() throws Exception{
   try {
     a();
   }catch (Exception e){
//      throw e;
     throw (Exception)e.fillInStackTrace();
   }
 }

public void a() throws Exception{

throw new Exception("Exception from a()");
 }
}
java.lang.Exception: Exception from a()
 at MyException.c(MyException.java:22)
 at MyException.main(MyException.java:10)

四 异常链 Exception chaining

先来看一个场景:


public class TestException {
 public static void main(String[] args){
   TestException testException = new TestException();
   try {
     testException.c();
   } catch (CException e) {
     e.printStackTrace();
   }
 }

public void a() throws AException{
   AException aException = new AException("this is a exception");
   throw aException;
 }

public void b() throws BException{
   try {
     a();
   } catch (AException e) {
     throw new BException("this is b exception");
   }
 }

public void c() throws CException{
   try {
     b();
   } catch (BException e) {
     throw new CException("this is c exception");
   }
 }
}

class AException extends Exception{
 public AException(String msg){
   super(msg);
 }
}

class BException extends Exception{
 public BException(String msg){
   super(msg);
 }
}

class CException extends Exception{
 public CException(String msg){
   super(msg);
 }
}

创建了三个异常类AException、BException、CException,然后在a()中抛出AException,在b()中捕获AException并抛出BException,最后在c()中捕获BException并抛出CException,结果打印如下:


CException: this is c exception
 at TestException.c(TestException.java:31)
 at TestException.main(TestException.java:8)

好,我们只看到了CException的信息,AException,BException的异常信息已丢失,这时候异常链的作用就出来了,看代码:


public class TestException {
 public static void main(String[] args){
   TestException testException = new TestException();
   try {
     testException.c();
   } catch (CException e) {
     e.printStackTrace();
   }
 }

public void a() throws AException{
   AException aException = new AException("this is a exception");
   throw aException;
 }

public void b() throws BException{
   try {
     a();
   } catch (AException e) {
//      throw new BException("this is b exception");
     BException bException = new BException("this is b exception");
     bException.initCause(e);
     throw bException;
   }
 }

public void c() throws CException{
   try {
     b();
   } catch (BException e) {
//      throw new CException("this is c exception");
     CException cException = new CException("this is c exception");
     cException.initCause(e);
     throw cException;
   }
 }
}

class AException extends Exception{
 public AException(String msg){
   super(msg);
 }
}

class BException extends Exception{
 public BException(String msg){
   super(msg);
 }
}

class CException extends Exception{
 public CException(String msg){
   super(msg);
 }
}

我们用initCause()方法将异常信息给串联了起来,结果如下:


CException: this is c exception
 at TestException.c(TestException.java:35)
 at TestException.main(TestException.java:8)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:606)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: BException: this is b exception
 at TestException.b(TestException.java:24)
 at TestException.c(TestException.java:32)
 ... 6 more
Caused by: AException: this is a exception
 at TestException.a(TestException.java:15)
 at TestException.b(TestException.java:21)
 ... 7 more

Process finished with exit code 0

五 后记

其实关于java异常处理还有很多需要探讨的地方,但是由于我经验有限,还不能体会的太深刻,最常用的也就是


try {
     ...
   }catch (Exception e){
     ...
   }finally {
    //不管异常会不会被捕捉或者处理都会执行的代码,如关闭IO操作
   }

但是无论如何我们还是要感谢java给我们提供的异常机制,它好似一个长者,时不时给我们指引道路,也让我们在编码的时候没有那么无聊:)

标签:java,异常
0
投稿

猜你喜欢

  • Android生成带圆角的Bitmap图片

    2022-09-08 11:18:19
  • java生成随机数的方法

    2023-12-12 12:49:51
  • IDEA安装阿里巴巴编码规范插件的两种方式详解(在线安装和离线安装)

    2022-07-23 19:18:54
  • Android中的应用认领总结

    2022-03-12 02:54:05
  • Android Jetpack中Room的使用

    2021-11-11 08:43:36
  • java实现静默加载Class示例代码

    2023-12-18 22:06:52
  • Android运用onTouchEvent自定义滑动布局

    2021-09-24 04:39:06
  • Android自定义view Path 的高级用法之搜索按钮动画

    2023-12-23 06:53:28
  • java贪吃蛇游戏编写代码

    2023-06-16 02:41:10
  • Servlet注解之@WebInitParam多个InitParam的使用

    2023-08-04 13:43:01
  • java封装前端查询条件通用版

    2023-06-24 12:06:15
  • java文件操作练习代码 读取某个盘符下的文件

    2023-11-12 06:34:07
  • 一文彻底搞懂Kotlin中的协程

    2021-09-19 19:16:01
  • Android自定义控件实现带数值和动画的圆形进度条

    2021-09-09 22:02:46
  • Java序列化和反序列化示例介绍

    2023-11-25 04:24:26
  • SpringMVC4 + MyBatis3 + SQL Server 2014整合教程(含增删改查分页)

    2023-09-27 01:47:32
  • Maven如何修改打包文件名称

    2022-09-02 21:29:52
  • Android开发之绘制平面上的多边形功能分析

    2023-12-13 13:31:57
  • 使用SpringBoot 工厂模式自动注入到Map

    2021-12-22 10:02:42
  • Android开发新手常见的10个误区

    2021-07-30 02:14:10
  • asp之家 软件编程 m.aspxhome.com