Java 详解异常 Exception,入门到实践(一)

最近发现很多人连异常 Exception 都“不会处理”,不是说不会try catch,而是不知道try catch、throw、throws的作用,以及在实际项目中主流的处理方案是怎样,兼容业务异常、返回格式、异常码、兜底异常处理等等。

我准备做个总结,希望帮到一部分人,还有未来可能要面试,到时可以翻出来看看

Java异常

简单的我就直接贴图片了,我们普遍说的异常是Exception,它的父类是ThrowableError代表错误,和异常不一样,不管是图片中提到的内存溢出还是栈溢出都代表一时半会无法处理,需要优化程序。而Exception可以被处理,分成两类:

RuntimeException,称作运行时异常,这类异常可以被编译,但代表着代码的“漏洞”,我们编写代码时就应该考虑到,程序运行时,会不会因为某种可能没判断,导致异常,程序中止。

NullPointerException就是没对值判断null,导致被调用。

	String s = null;			
        s.substring(0,1);

Checked Exception,它也被叫做非RuntimeException异常,这种异常不处理,编译没法通过,程序不能运行,处理方案有两个,要么 try catch 捕获异常,要么声明出去

这种异常你肯定也碰到过,因为 IDE 能识别,提醒我们处理,这就是编译异常。

捕捉、处理异常

就用上个例子来看,它会提醒我们用以下两种方案来处理

第一种处理:

 private static void testException2(String s) throws FileNotFoundException {
        FileInputStream file = new FileInputStream(s);
    }

碰到异常,我们可以throws将异常声明出去,也就是自己不处理,向外声明,声明该方法可能存在异常,上级调用后,就会收到异常提醒(Checked Exception会收到提醒)。上面提到的编译异常Checked Exception就是这么来的。为啥只要将代码编写出来就会收到编辑器的提醒呢?

点进去就会发现,这个方法内部就向外声明了异常。

 public FileInputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null);
    }

第二种处理:

private static void testException2(String s)  {
        try {
            FileInputStream file = new FileInputStream(s);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

这种方案采取两步处理,首先 try catch 捕捉异常,接着e.printStackTrace()打印异常的堆栈信息。这种默认处理方式,它的作用是啥呢?

写个简单小代码做测试,相信很容易看懂

System.out.println("1111");
testException2("");
System.out.println("2222");

运行程序后,控制台输出如下:

很明显,处理是有效的,首先程序不会因为异常中止运行,接着还打印出堆栈信息,这样排查时,可以定位原因。

那这样做是不是就解决问题了呢?不,这只是初级方案。

很多人喜欢把try catch看作捕捉异常,但其实只要捕捉异常,就算处理异常了,因为异常一旦被try catch,就不会再使程序中止崩溃。所以,处理异常的关键是try catch的处理方案。

先来看最蠢的方案:

private static void testException2(String s)  {
        try {
            FileInputStream file = new FileInputStream(s);
        } catch (FileNotFoundException e) {
            
        }
    }

捕捉到异常后,不做任何处理。虽然使程序能成功运行,并且保证触发异常后不会中止程序,但却没做任何处理,像个黑洞,没给出任何有效信息,异常就一直在那。所以,还不如让程序中止崩溃,我们能发现问题。

稍微好点的方案刚才提过,就是打印堆栈信息。

还可以再优化,比如在里面再抛出异常。不一样的是,非运行时异常(编译异常)不能try catch后再抛出异常。

所以,我这替换成运行时异常(RuntimeException

private static void testException(String s){
        try {
            Integer.parseInt(s);
        }catch (NumberFormatException exception){
            throw new NumberFormatException();
        }
    }

这种做法是不是很有意思,捕捉到异常后做了啥呢?再构造异常对象抛出去。

我们来看看,代码运行,当某个条件触发异常后,会发生啥呢?

        System.out.println("1111");
        testException("ss");
        System.out.println("2222");

控制台输出如下:

程序中止运行,打印堆栈信息。这个结果,是不是挺合理的,虽然try catch处理了异常,但处理方式是再抛出去。而上级调用者又没再处理,那触发异常,程序崩溃是符合逻辑的。

现在我们可以对比 throwthrows的区别了。

同样是“抛出去”,但两者结果很不一样。

throw是抛出异常对象,而throws是将【异常声明】抛出去。

严格意义上,它两都没真正处理异常,但throws做了声明,声明存在异常的可能性,上级如果要调用此方法,记得处理异常,处理方式同样可以再throws声明出去,也可以try catch

throw抛出异常对象,上级调用者也可以处理try catch这个异常,这样程序就不会报错了。

比如上面代码,我重新修改如下

        System.out.println("1111");
        try {
            testException("ss");
        }catch (NumberFormatException e){
            e.printStackTrace();
        }
        System.out.println("2222");

得到结果:

所以,throwthrows单独使用效果差不多,但一般两者结合一起使用,因为多人开发时,这样能告诉调用者,里面可能存在异常,这叫做好人好事。

private static void testException(String s) throws NumberFormatException{
        try{
            Integer.parseInt(s);
        }catch(NumberFormatException e){
            throw new NumberFormatException();
        }
    }

Java 详解异常 Exception,入门到实践(二)

本文由老郭种树原创,转载请注明:https://guozh.net/java-exception-1/

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注