简介

异样是java程序员无奈防止的一个话题,咱们会有JVM本人的异样也有应用程序的异样,对于不同的异样,咱们的解决准则是不是一样的呢?

一起来看看吧。

异样简介

先上个图,看一下常见的几个异样类型。

所有的异样都来自于Throwable。Throwable有两个子类,Error和Exception。

Error通常示意的是严重错误,这些谬误是不倡议被catch的。

留神这里有一个例外,比方ThreadDeath也是继承自Error,然而它示意的是线程的死亡,尽管不是重大的异样,然而因为应用程序通常不会对这种异样进行catch,所以也归类到Error中。

Exception示意的是应用程序心愿catch住的异样。

在Exception中有一个很特地的异样叫做RuntimeException。RuntimeException叫做运行时异样,是不须要被显示catch住的,所以也叫做unchecked Exception。而其余非RuntimeException的Exception则须要显示try catch,所以也叫做checked Exception。

不要疏忽checked exceptions

咱们晓得checked exceptions是肯定要被捕捉的异样,咱们在捕捉异样之后通常有两种解决形式。

第一种就是依照业务逻辑解决异样,第二种就是自身并不解决异样,然而将异样再次抛出,由下层代码来解决。

如果捕捉了,然而不解决,那么就是疏忽checked exceptions。

接下来咱们来考虑一下java中线程的中断异样。

java中有三个十分类似的办法interrupt,interrupted和isInterrupted。

isInterrupted()只会判断是否被中断,而不会革除中断状态。

interrupted()是一个类办法,调用isInterrupted(true)判断的是以后线程是否被中断。并且会革除中断状态。

后面两个是判断是否中断的办法,而interrupt()就是真正触发中断的办法。

它的工作要点有上面4点:

  1. 如果以后线程实例在调用Object类的wait(),wait(long)或wait(long,int)办法或join(),join(long),join(long,int)办法,或者在该实例中调用了Thread.sleep(long)或Thread.sleep(long,int)办法,并且正在阻塞状态中时,则其中断状态将被革除,并将收到InterruptedException。
  2. 如果此线程在InterruptibleChannel上的I / O操作中处于被阻塞状态,则该channel将被敞开,该线程的中断状态将被设置为true,并且该线程将收到java.nio.channels.ClosedByInterruptException异样。
  3. 如果此线程在java.nio.channels.Selector中处于被被阻塞状态,则将设置该线程的中断状态为true,并且它将立刻从select操作中返回。
  4. 如果下面的状况都不成立,则设置中断状态为true。

看上面的例子:

    public void wrongInterrupted(){        try{            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }

下面代码中咱们捕捉了一个InterruptedException,然而咱们仅仅是打印出了异样信息,并没有做任何操作。这样程序的体现和没有发送一异样一样,很显著是有问题的。

依据下面的介绍,咱们晓得,interrupted()办法会革除中断状态,所以,如果咱们本身解决不了异样的状况下,须要从新调用Thread.currentThread().interrupt()从新抛出中断,由下层代码负责解决,如下所示。

    public void correctInterrupted(){        try{            Thread.sleep(1000);        } catch (InterruptedException e) {            Thread.currentThread().interrupt();        }    }

不要在异样中裸露敏感信息

遇到异样的时候,通常咱们须要进行肯定水平的日志输入,从而来定位异样。然而咱们在做日志输入的时候,肯定要留神不要裸露敏感信息。

下表能够看到异样信息可能会裸露的敏感信息:

除了敏感信息之外,咱们还要做好日志信息的平安爱护。

在解决捕捉的异样时,须要复原对象的初始状态

如果咱们在解决异样的时候,批改了对象中某些字段的状态,在捕捉异样的时候须要怎么解决呢?

    private int age=30;    public void wrongRestore(){        try{            age=20;            throw new IllegalStateException("custom exception!");        }catch (IllegalStateException e){            System.out.println("we do nothing");        }    }

下面的例子中,咱们将age重置为20,而后抛出了异样。尽管抛出了异样,然而咱们并没有重置age,最初导致age最终被批改了。

整个restore的逻辑没有处理完毕,然而咱们局部批改了对象的数据,这是很危险的。

实际上,咱们须要一个重置:

    public void rightRestore(){        try{            age=20;            throw new IllegalStateException("custom exception!");        }catch (IllegalStateException e){            System.out.println("we do nothing");            age=30;        }    }

不要手动实现finally block

咱们在应用try-finally和try-catch-finally语句时,肯定不要在finally block中应用return, break, continue或者throw语句。

为什么呢?

依据Java Language Specification(JLS)的阐明,finally block肯定会被执行,不论try语句中是否抛出异样。

在try-finally和try-catch-finally语句中,如果try语句中抛出了异样R,而后finally block被执行,这时候有两种状况:

  • 如果finally block失常执行,那么try语句被终止的起因是异样R。
  • 如果在finally block中抛出了异样S,那么try语句被终止的起因将会变成S。

咱们举个例子:

public class FinallyUsage {    public boolean wrongFinally(){        try{            throw new IllegalStateException("my exception!");        }finally {            System.out.println("Code comes to here!");            return true;        }    }    public boolean rightFinally(){        try{            throw new IllegalStateException("my exception!");        }finally {            System.out.println("Code comes to here!");        }    }    public static void main(String[] args) {        FinallyUsage finallyUsage=new FinallyUsage();        finallyUsage.wrongFinally();        finallyUsage.rightFinally();    }}

下面的例子中,咱们定义了两个办法,一个办法中咱们在finally中间接return,另一办法中,咱们让finally失常执行结束。

最终,咱们能够看到wrongFinally将异样暗藏了,而rightFinally保留了try的异样。

同样的,如果咱们在finally block中抛出了异样,咱们肯定要记得对其进行捕捉,否则将会暗藏try block中的异样信息。

不要捕捉NullPointerException和它的父类异样

通常来说NullPointerException示意程序代码有逻辑谬误,是须要程序员来进行代码逻辑批改,从而进行修复的。

比如说加上一个null check。

不捕捉NullPointerException的起因有三个。

  1. 应用null check的开销要远远小于异样捕捉的开销。
  2. 如果在try block中有多个可能抛出NullPointerException的语句,咱们很难定位到具体的谬误语句。
  3. 最初,如果产生了NullPointerException,程序基本上不可能失常运行或者复原,所以咱们须要提前进行null check的判断。

同样的,程序也不要对NullPointerException的父类RuntimeException, Exception, or Throwable进行捕获。

不要throw RuntimeException, Exception, or Throwable

咱们抛出异样次要是为了可能找到精确的解决异样的办法,如果间接抛出RuntimeException, Exception, 或者 Throwable就会导致程序无奈精确解决特定的异样。

通常来说咱们须要自定义RuntimeException, Exception, 或者 Throwable的子类,通过具体的子类来辨别具体的异样类型。

不要抛出未声明的checked Exception

一般来说checked Exception是须要显示catch住,或者在调用办法上应用throws做申明的。

然而咱们能够通过某些伎俩来绕过这种限度,从而在应用checked Exception的时候不须要恪守上述规定。

当然这样做是须要防止的。咱们看一个例子:

    private static Throwable throwable;    private ThrowException() throws Throwable {        throw throwable;    }    public static synchronized void undeclaredThrow(Throwable throwable) {        ThrowException.throwable = throwable;        try {                ThrowException.class.newInstance();            } catch (InstantiationException e) {            } catch (IllegalAccessException e) {        } finally {            ThrowException.throwable = null;        }    }

下面的例子中,咱们定义了一个ThrowException的private构造函数,这个构造函数会throw一个throwable,这个throwable是从办法传入的。

在undeclaredThrow办法中,咱们调用了ThrowException.class.newInstance()实例化一个ThrowException实例,因为须要调用构造函数,所以会抛出传入的throwable。

因为Exception是throwable的子类,如果咱们在调用的时候传入一个checked Exception,很显著,咱们的代码并没有对其进行捕捉:

    public static void main(String[] args) {        ThrowException.undeclaredThrow(                new Exception("Any checked exception"));    }

怎么解决这个问题呢?换个思路,咱们能够应用Constructor.newInstance()来代替class.newInstance()。

try {        Constructor constructor =                    ThrowException.class.getConstructor(new Class<?>[0]);            constructor.newInstance();        } catch (InstantiationException e) {        } catch (InvocationTargetException e) {            System.out.println("catch exception!");        } catch (NoSuchMethodException e) {        } catch (IllegalAccessException e) {        } finally {            ThrowException.throwable = null;        }

下面的例子,咱们应用Constructor的newInstance办法来创建对象的实例。和class.newInstance不同的是,这个办法会抛出InvocationTargetException异样,并且把所有的异样都封装进去。

所以,这次咱们取得了一个checked Exception。

本文的代码:

learn-java-base-9-to-20/tree/master/security

本文已收录于 http://www.flydean.com/java-security-code-line-exception/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」,懂技术,更懂你!