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

异常的英文单词是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给我们提供的异常机制,它好似一个长者,时不时给我们指引道路,也让我们在编码的时候没有那么无聊:)

(0)

相关推荐

  • java中使用try-catch-finally一些值得注意的事(必看)

    我们知道,try负责圈定可能会出异常的代码:catch负责处理try中可能异常的处理,如记录错误日志,使业务能够正常运行:finally负责资源释放等善后工作,无论有无异常都必须要执行的代码,一般都是放在finally中的.如果catch和finally也会出现异常,那么会是什么效果呢? try { // java.lang.ArithmeticException int a = 1 / 0; } catch (Exception e) { System.out.println("catch&q

  • 谈谈Java中try-catch-finally中的return语句

    我们知道return语句用在某一个方法中,一是用于返回函数的执行结果,二是用于返回值为void类型的函数中,仅仅是一个return语句(return ;),此时用于结束方法的执行,也即此return后的语句将不会被执行,当然,这种情况下return语句后不能再有其它的语句了. 在try-catch-finally语句中使用return语句遇到了一些疑问 代码一: static int intc(){ int x =0; try{ x=1; return x; }finally { x = 3;

  • 关于Java中try finally return语句的执行顺序浅析

    问题分析 finally语句块一定会执行吗? 可能很多人第一反应是肯定要执行的,但仔细一想,如果一定会执行的话 也就不会这么SB的问了. Demo1 public class Test { public static void main(String[] args) { System.out.println("return value of test(): " + test()); } public static int test() { int i = 1; // if (i ==

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

    异常的英文单词是exception,字面翻译就是"意外.例外"的意思,也就是非正常情况.事实上,异常本质上是程序上的错误,包括程序逻辑错误和系统错误. 一 前言 java异常处理大家都不陌生,总的来说有下面两点: 1.抛出异常:throw exception class SimpleException{ public void a() throws Exception{ throw new Exception(); }; } 2.捕获异常: public class MyExcepti

  • 简单谈谈java自定义注解

    Java在1.5开始引入了注解,目前流行的框架都在用注解,可想而知注解的强大之处. 以下通过自定义注解来深入了解java注解. 一.创建自定义注解 package com.sam.annotation; import java.lang.annotation.*; /** * @author sam * @since 2017/7/13 */ @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.R

  • 简单谈谈Java中的方法和方法重载

    今天我们就讲一点内容,来说说Java中的方法和方法重载以及需要注意的一些地方: 方法: Java的方法类似与其他语言的函数,是一段用来完成特定功能的代码片段, 声明格式: [修饰符1 修饰符2 ....] ,返回值类型 方法名 (形式参数列表) { Java语句: - - -} 形式参数:在方法被调用时用于接受外界输入的数据: 实参: 调用方法时世界传给方法的数据: 返回值: 方法在执行完毕后返回给调用他的环境的数据: 返回值类型: 事先约定好的返回值的数据类型,如无返回值必须给出返回值类型vo

  • 简单谈谈java中final,finally,finalize的区别

    (1) final:修饰符(关键字),如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承.因此一个类不能既被声明为 abstract的,又被声明为final的.将变量或方法声明为final,可以保证它们在使用中不被改变.被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改.被声明为final的方法也同样只能使用,不能重载 (2) finally:在异常处理时提供 finally 块来执行任何清除操作.如果抛出一个异常,那么相匹配的 catc

  • 简单谈谈Java中String类型的参数传递问题

    提要:本文从实现原理的角度上阐述和剖析了:在Java语言中,以 String 作为类型的变量在作为方法参数时所表现出的"非对象"的特性. 一.最开始的示例 写代码最重要的就是实践,不经过反复试验而得出的说辞只能说是凭空遐想罢了.所以,在本文中首先以一个简单示例来抛出核心话题: public class StringAsParamOfMethodDemo { public static void main(String[] args) { StringAsParamOfMethodDem

  • 简单谈谈Java中的栈和堆

    人们常说堆栈堆栈,堆和栈是内存中两处不一样的地方,什么样的数据存在栈,又是什么样的数据存在堆中? 这里浅谈Java中的栈和堆 首先,将结论写在前面,后面再用例子加以验证. Java的栈中存储以下类型数据,栈对应的英文单词是Stack 基本类型 引用类型变量 方法 栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享.但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性. 栈中主要存放一些基本类型的变量(int, short, long, byte, float, double, b

  • 简单谈谈Java垃圾回收

    好久没看关于java的书了,最近,看了James Gosling的<<Java程序设计语言>>,做了一些读书笔记.这部分是关于垃圾回收的. 一. 垃圾回收 对象是使用new创建的,但是并没有与之相对应的delete操作来回收对象占用的内存.当我们完成对某个对象的使用时,只需停止该对象的引用: ->将引用改变为指向其他对象 ->将引用指向null ->从方法中返回, 使得该方法的局部变量不复存在 要点: ->当我们从任何可执行代码都无法到达某个对象时,它所占用

  • 简单谈谈Java 中的线程的几种状态

    Java 中的线程有以下状态: 新建状态(New):新创建的线程,还未执行. 就绪状态(Runnable):执行了 start() 方法,等待运行, 运行状态(Running):就绪状态的线程开始执行程序代码. 阻塞状态(Blocked) 同步堵塞:在运行过程中,需要拿到锁才能运行,而锁被其他资源占用,需要等待. 等待堵塞:执行了 wait() 方法,进入了等待. 其他堵塞:执行了 join().sleep() 方法,进入了等待. 终止状态(Terminated):运行完 run() 方法后结束

  • 简单谈谈Java类与类之间的关系

    类与类之间最常见的关系主要有三种:依赖(uses-a).聚合(has-a)和继承(is-a). 下面以在线书店订单系统为例,来详细的讲述这三种关系的概念. 在线书店订单系统的主要功能是:注册用户可以登录到网上书店选购图书,在线填写订单,并支付购书款.书店确认已经收到购书款时,按用户留下的地址邮寄图书.可以在这个系统中建立几个类,有图书(book).账户(account).订单(order).地址(address)等,如下图所示: 依赖(uses-a) 依赖关系是类中最常见的关系,例如订单类(or

  • 简单谈谈java中匿名内部类构造函数

    先看看下面的代码能不能编译通过: public static void main(String[] args) { List l1 = new ArrayList(); List l2 = new ArrayList(){}; List l3 = new ArrayList(){{}}; System.out.println(l1.getClass() == l2.getClass() ); System.out.println(l2.getClass() == l3.getClass() );

随机推荐