第十二章 通过异常处理错误

java 的基本理念是“结构不佳的代码不能运行”。

1、基本异常

异常说明使用了附加的关键字 throws ,后面接一个所有潜在异常类型的列表,方便客户端程序员查看.

1.1 异常参数

所有表准异常类都有两个构造器:一个是默认构造器,另一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器:  throw new NullPointerException("t = null");

2、捕获异常

 在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器,处理异常的地点叫异常处理程序。 

  • - 运行时系统从发生异常的方法开始,依次回查调用栈中的方法,当异 常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。 
  • - 当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止,异常处理有两种基本模型:终止模型和恢复模型。 
  • - 对于运行时异常、错误或可查异常,Java技术所要求的异常处理方式有所不同。总体来说,Java规定:对于可查异常必须捕捉、或者声明抛出。允许忽略不可查的RuntimeException和Error。 
  • - 简单地说,异常总是先被抛出,后被捕捉的。

异常处理通过5个关键字来实现:try、catch、 finally、throw、throws

 

 3、创建自定义异常

要自己定义异常,必须从已有的异常类继承,最好选择意思相近的异常类继承;

3.1 异常与记录日志

使用基本日志功能输出异常信息:

//: exceptions/LoggingExceptions.java
// An exception that reports through a Logger.
import java.util.logging.*;
import java.io.*;

class LoggingException extends Exception {
  private static Logger logger =
    Logger.getLogger("LoggingException");
  public LoggingException() {
    StringWriter trace = new StringWriter();
    printStackTrace(new PrintWriter(trace));
    logger.severe(trace.toString());
  }
}

public class LoggingExceptions {
  public static void main(String[] args) {
    try {
      throw new LoggingException();
    } catch(LoggingException e) {
      System.err.println("Caught " + e);
    }
    try {
      throw new LoggingException();
    } catch(LoggingException e) {
      System.err.println("Caught " + e);
    }
  }
} /* Output: (85% match)
Aug 30, 2005 4:02:31 PM LoggingException <init>
SEVERE: LoggingException
        at LoggingExceptions.main(LoggingExceptions.java:19)

Caught LoggingException
Aug 30, 2005 4:02:31 PM LoggingException <init>
SEVERE: LoggingException
        at LoggingExceptions.main(LoggingExceptions.java:24)

Caught LoggingException
*///:~

 常见的情形是捕获并记录他人编写的异常,因此我们必须在异常处理程序中生成日志消息:

//: exceptions/LoggingExceptions2.java
// Logging caught exceptions.
import java.util.logging.*;
import java.io.*;

public class LoggingExceptions2 {
  private static Logger logger =
    Logger.getLogger("LoggingExceptions2");
  static void logException(Exception e) {
    StringWriter trace = new StringWriter();
    e.printStackTrace(new PrintWriter(trace));
    logger.severe(trace.toString());
  }
  public static void main(String[] args) {
    try {
      throw new NullPointerException();
    } catch(NullPointerException e) {
      logException(e);
    }
  }
} /* Output: (90% match)
Aug 30, 2005 4:07:54 PM LoggingExceptions2 logException
SEVERE: java.lang.NullPointerException
        at LoggingExceptions2.main(LoggingExceptions2.java:16)
*///:~

 throw和throws有什么区别?

  • throws是异常声明但不做处理,而是将异常向上抛,用于方法声明;
  • throw则是抛出一个具体的异常类型;

system.out和system.err有什么区别?

https://blog.csdn.net/captainCZY/article/details/79496959

如果想通过system.err将错误发送给标准错误流,通常这比把错误信心输出到system.out要好,因为system.out也许会被重新定向,如果把结果送到system.err,她不会 随system.out 重新定向;

4、 捕获所有异常

  1. 通过捕获异常类型的基类Exception就可以处理所有类型的异常.
  2. Exception从基类Throwable继承的方法有
    • String getMessage(): 
    • String getLocalizedMessage():           用来获取详细信息或本地语言描述详细信息
    • void printStackTrace():              调用栈显示了"把你带到异常抛出地点"的方法调用序列,输出到标准输出.
    • void printStackTrace(PrintStream):            调用栈显示了"把你带到异常抛出地点"的方法调用序列,输出到要输出的流.
    • void printStackTrace(java.io.PrintWriter):           调用栈显示了"把你带到异常抛出地点"的方法调用序列,输出到要输出的流.
    • Throwable fillInStackTrace():           用于在Throwable对象的内部记录栈帧的当前状态.

4.1 栈轨迹

  1. printStackTrace()方法所提供的信息可以通过getStackTrace()方法直接访问.
  2. getStackTrace()方法返回一个由根轨迹中的元素所构成的数组,每一个元素都表示栈中的一帧.
  3. //: exceptions/WhoCalled.java
    // Programmatic access to stack trace information.
    
    public class WhoCalled {
      static void f() {
        // Generate an exception to fill in the stack trace
        try {
          throw new Exception();
        } catch (Exception e) {
          for(StackTraceElement ste : e.getStackTrace())
            System.out.println(ste.getMethodName());
        }
      }
      static void g() { f(); }
      static void h() { g(); }
      public static void main(String[] args) {
        f();
        System.out.println("--------------------------------");
        g();
        System.out.println("--------------------------------");
        h();
      }
    } /* Output:
    f
    main
    --------------------------------
    f
    g
    main
    --------------------------------
    f
    g
    h
    main
    *///:~
    

4.2 重新抛出异常

  1. 重新抛出异常会把异常抛给上一级环境中的异常处理程序,同一个try块的后续catch字句将忽略.
  2. 异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有消息.
  3. 如果只是把当前异常对象重新抛出,那么printStackTrace()方法显示的将是原来异常抛出点的调用栈信息,而非重新抛出点的信息.
  4. 想要更新这个信息,可以调用fillInStackTrace()方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的.
try {
      f();
    } catch(Exception e) {
      System.out.println("Inside g(),e.printStackTrace()");
      e.printStackTrace(System.out);
      throw e;
    }

4.3 异常链

  1. 捕获一个异常后抛出另一个异常,并且希望把原始异常的信息保存下来,被称为异常链.
  2. Throwable子类在构造器中可以接受一个cause(因由)对象作为参数.这个cause就是用来表示原始异常,这样通过原是异常传递给新的异常,使

得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追中岛异常最初发生的位置.

  1. 在Throwable子类中,只有三种基本异常类提供了带cause参数的构造器
    • Error(用于Java虚拟机报告系统错误)
    • Exception
    • RuntimeException
  2. 如果要其他类型的异常链接起来,应该使用initCause()方法而不是构造器.

5 、Java标准异常

  1. Throwable这个Java类被用来表示任何可以作为异常被抛出的类.
  2. Throwable可分为两种类型(从Throwable继承而得到的类型):
    • Error用来表示编译时和系统错误
    • Exception是可以被抛出的基本类型

5.1 RuntimeException

  1. 属于运行时异常的类型有很多,它们会自动被Java虚拟机抛出,这些异常都是从RuntimeException中继承而来的.
  2. RuntimeException是 "不受检查异常" ,这种异常属于错误,将被自动捕获.
  3. 只能在代码中忽略RuntimeException(及其子类)类型的异常,其他类型的异常处理都是由编译器强制实施的.
  4. RuntimeException代表的是编程错误:
    • 无法预料的错误.比如你从控制范围之外传递进来的null引用.
    • 作为程序员,应该在代码中进行检查的错误.

6 、使用finallly进行清理

6.1 finally 用来做什么

  1. Java中使用finally一般把除内存之外的资源恢复到它们的初始状态时.
  2. 异常处理机制会在跳到更高一层的异常处理程序之前,执行finally子句.
//: exceptions/Switch.java
import static net.mindview.util.Print.*;

public class Switch {
  private boolean state = false;
  public boolean read() { return state; }
  public void on() { state = true; print(this); }
  public void off() { state = false; print(this); }
  public String toString() { return state ? "on" : "off"; }
} ///:~

6.2 在return中使用finally

  1. 因为finally子句总是会执行,所以在一个方法中,可以从多个点返回,并且可以保证重要清理工作仍旧会执行.
  2. return语句返回之前会执行finally子句的代码块.
//: exceptions/MultipleReturns.java
import static net.mindview.util.Print.*;

public class MultipleReturns {
  public static void f(int i) {
    print("Initialization that requires cleanup");
    try {
      print("Point 1");
      if(i == 1) return;
      print("Point 2");
      if(i == 2) return;
      print("Point 3");
      if(i == 3) return;
      print("End");
      return;
    } finally {
      print("Performing cleanup");
    }
  }
  public static void main(String[] args) {
    for(int i = 1; i <= 4; i++)
      f(i);
  }
} /* Output:
Initialization that requires cleanup
Point 1
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Point 3
Performing cleanup
Initialization that requires cleanup
Point 1
Point 2
Point 3
End
Performing cleanup
*///:~

6.3 异常丢失的情况

  1. 前一个异常还没处理就抛出下一个异常,没有catch捕获异常,使用finally抛出下一个异常.
  2. 在finally中加入return语句,没有用catch语句捕获异常.

7、异常的限制

  1. 不能基于异常说明来重载方法.
  2. 当覆盖方法时,只能抛出在基类方法的异常说明里列出的那些异常.
  3. 子类抛出的异常要小于父类.

8、构造器

  1. 构造器抛出异常要格外注意清理方法是否有必要调用.
  2. 设计异常时,直接向上层抛出的却能简化编程.
  3. 对于在构造阶段可能会抛出异常,并且要求清理的类,最安全的使用方式是使用嵌套的try语句.
  4. 这种通用的清理惯用法在构造器不抛出任何异常时也应该使用,基本规则是:在创建需要清理的对象之后,立即进入一个 try-finally 语句块.
  5. 派生类的构造器不能捕获它的基类构造器的异常.

9、异常匹配

  1. 抛出异常的时候,异常处理系统会安装代码书写顺序找出"最近的"处理程序.

10、其他可选的方式

  1. "被检查异常"强制你在还没准备好处理错误的时候被迫加上catch子句,这就导致了吞食则有害的问题.异常被吞食了.
  2. 异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离 .

10.1 把异常传递给控制台

  1. 最简答而又不用写多少代码就能保护异常信息的方法,就是把它们从main()传递到控制台.
//: exceptions/MainException.java
import java.io.*;

public class MainException {
  // Pass all exceptions to the console:
  public static void main(String[] args) throws Exception {
    // Open the file:
    FileInputStream file =
      new FileInputStream("MainException.java");
    // Use the file ...
    // Close the file:
    file.close();
  }
} ///:~

10.2 把"被检查的异常"转换为"不检查的异常"

  1. 把"被检查的异常"包装进RuntimeException里面,避免异常被"吞".
  2. 可以不写try-catch,直接忽略一行,让它沿着调用栈往上”冒泡“,还可以用getCause()捕获并处理特定异常.
//: exceptions/TurnOffChecking.java
// "Turning off" Checked exceptions.
import java.io.*;
import static net.mindview.util.Print.*;

class WrapCheckedException {
  void throwRuntimeException(int type) {
    try {
      switch(type) {
        case 0: throw new FileNotFoundException();
        case 1: throw new IOException();
        case 2: throw new RuntimeException("Where am I?");
        default: return;
      }
    } catch(Exception e) { // Adapt to unchecked:
      throw new RuntimeException(e);
    }
  }
}

class SomeOtherException extends Exception {}

public class TurnOffChecking {
  public static void main(String[] args) {
    WrapCheckedException wce = new WrapCheckedException();
    // You can call throwRuntimeException() without a try
    // block, and let RuntimeExceptions leave the method:
    wce.throwRuntimeException(3);
    // Or you can choose to catch exceptions:
    for(int i = 0; i < 4; i++)
      try {
        if(i < 3)
          wce.throwRuntimeException(i);
        else
          throw new SomeOtherException();
      } catch(SomeOtherException e) {
          print("SomeOtherException: " + e);
      } catch(RuntimeException re) {
        try {
          throw re.getCause();
        } catch(FileNotFoundException e) {
          print("FileNotFoundException: " + e);
        } catch(IOException e) {
          print("IOException: " + e);
        } catch(Throwable e) {
          print("Throwable: " + e);
        }
      }
  }
} /* Output:
FileNotFoundException: java.io.FileNotFoundException
IOException: java.io.IOException
Throwable: java.lang.RuntimeException: Where am I?
SomeOtherException: SomeOtherException
*///:~

11、异常使用指南

  1. 在恰当的级别处理问题.(在知道该如何处理的情况下才捕获异常)
  2. 解决问题并且重新调用产生异常的方法.
  3. 进行少许修补,然后绕过异常发生的地方继续执行.
  4. 用别的数据进行计算,以代替方法预计会返回的值.
  5. 把当前环境下能做的事情尽量做完,然后把相同的异常重抛到更高层.
  6. 把当前环境下能做的事情尽量做完,然后把不同的异常抛到更高层.
  7. 终止程序.
  8. 进行简化.
  9. 让类库和程序更加安全.

 参考博客:https://blog.csdn.net/geekmubai/article/details/82016589

                   https://www.cnblogs.com/devinkin/p/9889949.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值