Java基础之异常机制学习&分析

Java异常机制学习&分析
处理错误
Java异常层次简要类图
何时声明受查异常

    调用一个抛出受查异常的方法,例如, FileInputStream构造器
    程序运行过程中发现错误,并且利用throw语句抛出一个受查异常
    程序出现错误,例如,a[-1]=0会抛出一个ArrayIndexOutOfBoundsException这样的非受查异常。
    Java 虚拟机和运行时库出现的内部错误。

如果出现前两种情况之一,则必须告诉调用这个放啊的程序员有可能抛出异常。为什么?因为任何一个抛出异常的方法都可能是一个死亡陷阱。
如果没有处理器捕获这个异常,当前执行的线程就会结束。
如下所示:

  public Image loadImage(String s) throws IOException {
        return null;
    }

    public Image loadImage2(String s) throws FileNotFoundException,EOFException {
        return null;
    }



如果在子类中覆盖了超类的一个方法,子类方法中声明的受查异常不能比超类方法中声明的异常更通用(也就是说,子类方法中可以
抛出更特定的异常,或者根本不抛出任何异常)。特别需要说明的是,如果超类方法没有抛出任何受查异常,子类也不能抛出任何受查异常。
自定义异常类

public class FileFormatException extends IOException {
    public FileFormatException() {

    }

    public FileFormatException(String msg) {
        super(msg);
    }

}


捕获异常

以下代码:

 FileInputStream in = new FileInputStream("test.txt");
        try {
            //1
//        code that might throw exception
            //2
        } catch (IOException e) {
           //3
            //show error message
            //4
        }
        finally {
            //5
            //in.close();
        }
        //6


有下列3中情况会执行finally子句
1. 代码没有抛出异常,在这种情况下,程序首先执行try语句块中的全部代码,然后执行finally子句
中的代码,随后,继续执行try语句块之后的第一条语句。也就是说,执行顺序:1,2,5,6
2. 抛出一个在catch子句中捕获的异常。在上面的实例中就是IOException异常。在这种情况下,程序将
执行try语句块中的所有代码,知道发生异常为止。此时,将跳过try语句块中的剩余代码,转去
执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。

如果catch子句没有抛出异常,程序将执行try语句块之后的第一条语句,在这里,执行顺序:1,3,4,5,6

如果catch子句抛出了一个异常,异常江北抛回这个方法的调用者。在这里,执行顺序是:1,3,5
3. 代码抛出了一个异常,但这个异常不是由catch子句捕获的。在这种情况下,程序将执行try语句块中的所有
语句,知道有异常被抛出位置。此时将跳过try语句块中的剩余代码,然后执行finally子句中的语句,并将异常抛给
这个方法的调用者,在这里,执行顺序:1,5
强烈建议

强烈建议解耦合try/catch和try/finally语句块。这样可以提高代码的清晰度。例如:

   FileInputStream in = new FileInputStream("test.txt");
        try {
            try {
//        code that might throw exception
            } finally {
                in.close();
            }
        } catch (IOException e) {
            //show error message
        }


内层的try语句块只有一个职责,就是确保关闭输入流。外层的try语句块也只有一个职责,就是确保
报告出现的错误。这种设计方式不仅清楚,而且还具有一个功能,就是将会报告finally子句中出现的错误。
finally子句中也有返回的情况

public static void main(String[] args) {
        System.out.println("n=1时方法mult返回结果:" + mult(1));

        System.out.println("n=2时方法mult返回结果:" + mult(2));
    }

    public static int mult(int n) {
        try {
            int r = n * n;
            return r;
        } finally {
            if (n == 2) {
                return 0;
            }
        }
    }



得到结果是:n=1时方法mult返回结果:1
n=2时方法mult返回结果:0
如结果所示,当n=2时,try语句块的计算结果为r=4,并执行return语句,然而,在方法真正返回值之前,还要执行finally子句。finally子句将使得方法的返回值为0,这个返回值会覆盖原始的返回值4。

### 带资源的try语句
对于以下代码模式:

 open a resource
  try{
    work with resource
  }
  finally{
     close the resource
  }



假设资源属于一个实现了AutoCloseable接口的类。Java SE 7为这种代码提高了一个很有用的快捷方式,
AutoCloseable 接口有一个方法。
我们可以简写成

  try(Resource res=...){
    work with res
  }

  例如:
  try(InputStream inputStream = request.getInputStream()){
              reqJsonStr = StreamUtils.copyToString(inputStream , Charset.defaultCharset());
          }

### 分析堆栈轨迹元素
堆栈轨迹(stack trace)是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置。
前面已经看到过这种列表,当Java程序正常终止,而没有捕获异常时,这个列表就会显示出来。
e.printStackTrace(); 打印堆栈信息。
例如:以下打印递归阶乘函数的堆栈情况

  public static void main(String[] args) {
         Scanner scanner = new Scanner(System.in);
         System.out.println("Enter=" + scanner);
         int n = scanner.nextInt();
         factorial(n);
     }

     public static int factorial(int n) {

         System.out.println("factorial(" + n + ")");
         Throwable throwable = new Throwable();
         StackTraceElement[] stackTrace = throwable.getStackTrace();
         for (StackTraceElement stackTraceElement : stackTrace) {
             System.out.println(stackTraceElement);
         }
         int result;
         if (n == 1) {
             result = 1;
         } else {
             result = n * factorial(n - 1);
         }
         System.out.println("result=" + result);
         return result;
     }



结果是:

  factorial(3)
    com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22)
    com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16)
    factorial(2)
    com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22)
    com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31)
    com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16)
    factorial(1)
    com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:22)
    com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31)
    com.jay.exceptions.StackTraceTest.factorial(StackTraceTest.java:31)
    com.jay.exceptions.StackTraceTest.main(StackTraceTest.java:16)
    result=1
    result=2
    result=6



使用异常机制的技巧

    异常处理不能代替简单的测试
    不要过分地细化异常

    利用异常层次结构

    不要只抛出RuntimeException异常。应该寻找更加适当的子类或者创建自己的异常类

    不要只捕获Throwable异常,否则,会使程序代码更难读,更难维护。

    不要压制异常

    在检测错误时。”苛刻” 要比放任更好

    当用无效参数滴啊用一个方法时,返回一个虚拟的数值,还是抛出一个异常,哪种处理方式更好呢?
    例如:当栈为空时,Stack.pop是返回一个null,还是抛出一个异常?我们认为:在出错的地方抛出一个EmptyStackExceptin
    异常要比后面抛出一个NullPointExceptin异常更好。

    不要羞于传递异常

    很多程序员都感觉应该捕获抛出的全部异常。如果调用了一个抛出异常的方法,例如FileInputStream构造器或
    readLine方法,这些方法就会本能地捕获这些可能产生的异常。其实,传递异常要比捕获这些异常更好:

      public void readStuff(String filenam) throws IOException{
            InputStream in=new InputStream(filenam);
            ...
      }  


    让高层次的方法通知用户发生了错误,或者放弃不成功的命令更加适宜。

    PS: 规则5,6可以归纳为”早抛出,晚捕获”,抛出不能处理的异常,捕获可以处理的异常
    使用断言
    断言的概念

    假设确信某个属性符合要求,并且代码的执行依赖于这个属性。
    断言的关键字是assert,这个关键字有两周形式:

    assert 条件:和assert 条件:表达式;

    这两种形式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常,在第二种形式中,
    表达式将传入AssertError的构造器,并转换成一个消息字符串
    启动和禁用断言

    在默认情况下,断言被禁用。可以在运行程序时用-enableassertions或者-ea选项启用。
    命令是 java -enableassertions MyApp

    需要注意的是,在启用或禁用断言时不必重新编译程序。启用或禁用断言时 类加载器的功能,当断言被禁用时,
    类加载器将跳过断言代码,因此不会降低程序的运行速度。
    使用断言完成参数检查

    什么时候应该选择使用断言呢?请记住下面几点:
    断言失败是致命的,不可恢复的错误。

    断言检查只用于开发和测试阶段
    记录日志

    基本日志

    可以使用全局日志记录器(global logger) 并调用其info方法。
    Logger.getGlobal().info("日志测试");
    设置日志级别:
    Logger.getGlobal().setLevel(Level.INFO);

    高级日志

    可以自定义日志记录器,可以调用getLogger方法创建或获取记录器:

    private static final Logger myLogger=Logger.getLogger("com.jay.exception.LoggerTest");

    未被任何变量引用的日志记录器可能会被垃圾回收机制回收。为了防止这种情况发生,要像
    上面的例子中一样,用一个静态变量存储日志记录的一个引用。

    通常,有以下7个日志记录器级别:
        SERVER
        WARNING
        INFO
        CONFIG
        FINE
        FINER
        FINEST
        默认情况下,只记录前三个级别。也可以设置其他的级别。例如:
        logger.setLevel(Level.FINE)
    修改日志管理器配置
    在默认情况下,配置文件存在于 jre/lib/logging.propertiest,要想使用另一个配置文件,
    就要将 java.util.logging.config.file 特性设置为配置文件的存储位置。并用下列命令启动应用程序
    java -Djava.util.logging.config.file=configFile MainClass





作者:码农飞哥
微信公众号:码农飞哥