谈谈我对Exception和Error的理解(未完)

谈谈我对Exception和Error的理解(未完)

NoClassDefFoundError/ClassNotFoundException

NoClassDefFoundError发生在jvm运行时,执行某个方法或者静态成员时,如果jvm加载不到该类时报错的错误。本地编译没报错,但运行时报错,有可能该类对类加载器而言是不可见的。

ClassNotFoundException发生与编译时根据classpath找不到对应类时报的错误。

由于User类为"private"修饰,Test.java里的main无权访问,编译时不会报错到运行时因找到User类NotClassDefFoundError:

import java.util.ArrayList;
import java.util.List;

/**
 * Created by fujianbo on 2018/5/12.
 *
 * @author fujianbo
 * @date 2018/05/12
 */
public class Test {
    public static void main(String args[]){
        List<User> users = new ArrayList<>(2);
        try {
            users.add(new User("1234"));
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

class User{
    private static String USER_ID = getUserId();

    public User(String id){
        this.USER_ID = id;
    }
    private static String getUserId() {
        throw new RuntimeException("UserId Not found");
    }
}

不良案例

捕获时异常要明确异常类型,提高代码可读性

public class Test {
    public static void main(String args[]){
        try {
            Thread thread = new Thread();
            // do something
            // ...
            thread.wait();
        } catch (InterruptedException e) {
            
        }
    }
}

捕获异常范围不宜过大

RuntimeException更应该扩散而不是捕获(很多时候我们并不能处理这类异常),如Throwable或则Error也是如此。

不要使用标准输出打印异常

try {
        Thread thread = new Thread();
        // do something
        // ...
        thread.wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
}

使用e.printStackTrace将导致异常堆栈输出到标准异常流,这很难判断日志最终被输出都哪里而丧失监控的意义。

throw early & catch late原则

public static void main(String args[]){
        loadFile(null);
    }

    private static void loadFile(String fileName) {
        try {
            FileInputStream fileInputStream = new FileInputStream(fileName);
            func1(fileInputStream);
            func2(fileInputStream);
        } catch (FileNotFoundException e) {
            // take logs here
        }

    }
    
    private static void func1(FileInputStream fileInputStream) {
        if (fileInputStream == null) {
            return;
        }
        // do something
    }
    
    private static void func2(FileInputStream fileInputStream) {
        // do something
    }

throw early可节省查看堆栈排查问题的时间,无论使用断言还是if判断都可以简单做到。

自定义异常

  • Checked Exception的初衷为从异常中恢复,哪些要恢复以及怎么恢复依赖于精细化的异常分类
  • 异常信息不要包含敏感信息,这是ConnectionException不打印ip和端口信息的原因

Checked Exception Or Unchecked Exception?

  • [引用自某博客,出处后续补上]虽然反对使用Checked Exception的声音不少,但Checked Exception在提示不要忘记处理异常,虽然调用该方法的方法体需要申明外抛异常,但至少可以指导底层会抛哪些异常,在自定义异常时提供了有效信息(异常种类划分&处理)
  • Unchecked Exception使代码更简洁,但容易丢失详细堆栈信息。如调用链路横跨多个流程,每个流程涉及到的方法调用都有上下文,哪一步出错可以依赖不同流程的上下文打印信息,快速定位问题。
  • try/catch代码块产生额外的性能开销,影响jvm优化(尽量缩小try/catch块的大小)
  • 实例化一个Exception会保留执行时的堆栈快照,该过程为相对较重的操作。

思考

Reactive Stream(异步、基于事件机制),出现异常不能简单外抛。由于代码堆栈不在是同步调用时的垂直结构,我们看到的是特定executor的堆栈。那如何处理这类异常呢?(未完待续...)

上一篇:并发编程系列之CLH锁


下一篇:HBase学习&实践笔记之HBase初探(to be continued...)