Android 性能优化(三)认识错误Error和异常Exception及栈轨迹StackTrace

目录

一、定义

throwable

exception

error

二、异常类型

三、异常处理5个关键字 

try

catch

throw

throws

finally

四、模拟面试

面试题1:try语句可以嵌套吗?

面试题2:下面代码有问题吗?

面试题3:try块中加退出语句return会怎样?

五、异常的栈轨迹(Stack Trace)

1、printStackTrace()

2、getStackTrace()方法

 3、fillInStackTrace()


 前言

Android 性能优化(一)闪退治理、卡顿优化、耗电优化、APK瘦身 ,这篇中我强调“运行稳定大于一切”,保证程序运行中不出现Crash,要比卡顿、耗电、安装包大小等方面更为重要。

当一个方法发生错误时,此方法会产生一个对象并将其交给运行时系统。 这个对象就叫异常对象,它包含了错误信息、异常类型以及程序的状态。创建一个异常对象并将其交给运行时系统称之为抛出异常。造成Crash的原因有很多,而在程序运行过程中Throwable抛出的异常或错误就是其中最普遍的成因。本篇将对Throwable下的Exception和error有一个比较清晰的认识。


一、定义

Android 性能优化(三)认识错误Error和异常Exception及栈轨迹StackTrace_第1张图片

  • throwable

可抛出的意思,是根基类,子类有异常exception和错误error。在java中只有Throwable类型的实例才能被抛出(throw)或者捕获(catch),它是异常处理机制的基本类型。

  • exception

表示程序运行中出现的非正常状态,并告诉我们程序发生了什么问题,且程序自身可以进行拦截或处理的异常。

  • error

是指程序无法处理的错误,其中包括程序运行时 JVM出现的问题。


二、异常类型

Throwable分为检查型异常(Checked Exception)和非检查型异常(Unchecked Exception)。

  • 检查型异常必须在源码中进行try-catch捕获处理,这是编译检查的一部分。
  • 类似NullPointerException,ArrayIndexOfBoundException就是非检查型异常,通常是可以通过编码避免的逻辑错误。
  • 编译期不检查,如果抛出了非检查型异常,那就是编码逻辑有问题,需要开发者解决。

Android 性能优化(三)认识错误Error和异常Exception及栈轨迹StackTrace_第2张图片


三、异常处理5个关键字 

异常处理过程:一般情况下是用try来执行一段程序,如果系统会抛出(throw\throws)一个异常对象,可以通过它的类型来捕获(catch)它,或通过总是执行代码块(finally)来处理。

需要注意:try-catch代码段会产生额外的性能开销,Java每实例化一个Exception,都会对当时的栈进行快照。

try

  • 用来指定一段可能产生异常的代码块;是一个“监控区域”的概念。

catch

  • 紧跟在try块后面,用来指定你想要捕获的异常的类型;

throw

  • 语句用来明确地抛出一个异常;

throws

  • 用来声明一个方法可能抛出的各种异常;

finally

  • 为确保一段代码不管发生什么异常状况都要被执行;

四、模拟面试

 

面试题1:try语句可以嵌套吗?

答:可以的。每当遇到一个try语句,指定的一段可能产生异常的代码块就会被放入异常栈中,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理(catch),异常栈就会执行出栈操作,交给上一层处理,直到遇到有处理这种异常的try语句或者最终将异常抛给JVM。

 

面试题2:下面代码有问题吗?

try {
     Thread.sleep(500L);
} catch (Exception e) {
}

答:这段代码违反了异常处理的两个基本原则。  第一,尽量不要捕获类似Exception这样的通用异常,而是应该捕获特定异常。例如,这里Thread.sleep() 应当捕获 InterruptedException。第二,在异常处理(catch)中不要生吞(swallow)异常。这是特别注意的事情,因为很可能会导致非常难以诊断的诡异情况。

 

面试题3:try块中加退出语句return会怎样?

  try块中有return语句时,仍然会执行finally块中的语句,然后方法再返回。

 


五、异常的栈轨迹(Stack Trace)

参考:Java异常的栈轨迹(Stack Trace) - wawlian - 博客园

1、printStackTrace()

  Exception类本身除了定义了几个构造器之外,所有的方法都是从其父类Throwable继承过来的,而且和异常相关的方法都是从父类继承过来的。其中就有一个 printStackTrace() 方法。

这个方法会将Throwable对象的栈轨迹信息打印到标准错误输出流上。输出的大体样子如下:

java.lang.NullPointerException
         at MyClass.mash(MyClass.java:9)
         at MyClass.crunch(MyClass.java:6)
         at MyClass.main(MyClass.java:3)

输出的第一行是toString()方法的输出,后面几行的内容都是之前通过fillInStackTrace()方法保存的内容。

 

2、getStackTrace()方法

    对printStackTrace()方法所打印信息进行访问。它会返回一个栈轨迹元素的数组 StackTraceElement[]。

    下面是一个使用getStackTrace()访问这些轨迹栈元素并打印输出的例子:

public class TestPrintStackTrace {
    public static void f() throws Exception{
        throw new Exception("出问题啦!");
    }
    public static void g() throws Exception{
        f();
    }
    public static void main(String[] args) {
        try {
            g();
        }catch(Exception e) {
            System.out.println("e.printStackTrace:");
            e.printStackTrace();
       
            System.out.println("e.getStackTrace:");
            for(StackTraceElement elem : e.getStackTrace()) {
                System.out.println(elem);
            }
        }
    }
}

getStackTrace()的输出和printStackTrace()的输出基本上是一样的,如下: 

e.printStackTrace:
java.lang.Exception: 出问题啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:6)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:10)

e.getStackTrace:
TestPrintStackTrace.f(TestPrintStackTrace.java:3)
TestPrintStackTrace.g(TestPrintStackTrace.java:6)
TestPrintStackTrace.main(TestPrintStackTrace.java:10)

 

 3、fillInStackTrace()

我们在前面也提到了这个方法。要说清楚这个方法,首先要讲一下捕获异常之后重新抛出的问题。

首先,在catch代码块中捕获到异常,调用printStackTrace()打印栈轨迹,又重新throw出去。在上一级的方法调用中,再捕获这个异常并且打印出栈轨迹信息,这两个栈轨迹信息会一样吗?我们看一下代码:

public class TestPrintStackTrace {
    public static void f() throws Exception{
        throw new Exception("出问题啦!");
    }
    public static void g() throws Exception{
        try {
            f();
        }catch(Exception e) {
            e.printStackTrace();
            throw e;
        }
         
    }
    public static void main(String[] args) {
        try {
            g();
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
}

在main方法中调用g()方法,并且捕获抛出的异常。此时 f()方法抛出异常“出问题啦!”,按理说两次打印栈轨迹的信息应该不同,第二次打印的信息应该没有关于f()的信息。输出结果如下:事实上,两次打印栈轨迹信息是一样的!

java.lang.Exception: 出问题啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:16)
java.lang.Exception: 出问题啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:16)

因此,当捕获到异常立即抛出,在上级方法调用中再次捕获这个异常,两次打印的栈轨迹信息是一样的。

因为,没有将当前线程当前状态下的轨迹栈的状态保存进Throwabe中。而fillInStackTrace()方法刚好做的就是这样的保存工作。

public Throwable fillInStackTrace()

可见,fillInStackTrace()方法的返回值是保存了当前栈轨迹信息的Throwable对象。g()方法代码改动如下:

  public static void g() throws Exception{
        try {
            f();
        }catch(Exception e) {
            e.printStackTrace();
            throw (Exception)e.fillInStackTrace();
        }
    }

输出结果:

java.lang.Exception: 出问题啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:17)
java.lang.Exception: 出问题啦!
    at TestPrintStackTrace.g(TestPrintStackTrace.java:11)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:17)

 我们看到,在main方法中打印栈轨迹已经没有了f()相关的信息了。

你可能感兴趣的:(Android,Java)