Maison  >  Article  >  Java  >  Partagez quelques suggestions sur la gestion des exceptions Java (Collection)

Partagez quelques suggestions sur la gestion des exceptions Java (Collection)

黄舟
黄舟original
2017-06-18 09:47:401500parcourir

Java fournit le lancement d'exceptions, la capture d'exceptions et l'utilisation d'instructions final pour gérer les exceptions du programme. Examinons de plus près quelques suggestions sur JavaGestion des exceptions

Article 1 : Utiliser les exceptions uniquement pour les conditions anormales

Recommandation : Les exceptions ne doivent être utilisées que pour des conditions anormales, elles ne doivent jamais être utilisées pour un contrôle normal couler.


Illustration en comparant les deux codes ci-dessous.


Code 1


try {
  int i=0;
  while (true) {
    arr[i]=0;
    i++;
  }
} catch (IndexOutOfBoundsException e) {
}
Code 2



for (int i=0; i<arr.length; i++) {
  arr[i]=0;
}
La fonction des deux codes est de parcourir le tableau arr et de définir la valeur de chaque élément du tableau sur 0. Le code 1 se termine par une exception, ce qui semble très difficile à comprendre. Le code 2 se termine par une limite de tableau. Nous devrions éviter d'utiliser la méthode du code 1 pour trois raisons principales :

  1. Le mécanisme d'exception est à l'origine conçu pour être utilisé dans des situations anormales, donc peu d'implémentations JVM tentent de les gérer pour optimiser les performances. Par conséquent, créer, lancer et intercepter des exceptions coûte cher.

  2. Mettre du code dans un retour try-catch empêche la JVM d'implémenter certaines optimisations qu'elle pourrait autrement effectuer.

  3. Le modèle standard de parcours de tableau n'entraîne pas de vérifications redondantes, et certaines implémentations JVM modernes les optimiseront.

Le mode basé sur les exceptions est en réalité beaucoup plus lent que le mode standard. Le code du test est le suivant :


public class Advice1 {

  private static int[] arr = new int[]{1,2,3,4,5};
  private static int SIZE = 10000;

  public static void main(String[] args) {

    long s1 = System.currentTimeMillis();
    for (int i=0; i<SIZE; i++)
      endByRange(arr);
    long e1 = System.currentTimeMillis();
    System.out.println("endByRange time:"+(e1-s1)+"ms" );

    long s2 = System.currentTimeMillis();
    for (int i=0; i<SIZE; i++)
      endByException(arr);
    long e2 = System.currentTimeMillis();
    System.out.println("endByException time:"+(e2-s2)+"ms" );
  }

  // 遍历arr数组: 通过异常的方式
  private static void endByException(int[] arr) {
    try {
      int i=0;
      while (true) {
        arr[i]=0;
        i++;
        //System.out.println("endByRange: arr["+i+"]="+arr[i]);
      }
    } catch (IndexOutOfBoundsException e) {
    }
  }

  // 遍历arr数组: 通过边界的方式
  private static void endByRange(int[] arr) {
    for (int i=0; i<arr.length; i++) {
      arr[i]=0;
      //System.out.println("endByException: arr["+i+"]="+arr[i]);
    }
  }
}
Résultat en cours :



endByRange time:8ms
endByException time:16ms
Résultat description : Réussi La vitesse de parcours des exceptions est beaucoup plus lente que la méthode normale

traversant le tableau  !

Point 2 : Utiliser les exceptions vérifiées pour les conditions récupérables, utiliser les exceptions d'exécution pour les erreurs de programme

  1. Runtime Exceptions -- La classe RuntimeException et ses sous-classes sont appelées exceptions d'exécution.

  2. Exceptions vérifiées -- la classe Exception elle-même et les autres sous-classes des sous-classes Exception, à l'exception des "exceptions d'exécution", sont toutes des exceptions vérifiées.

La différence entre eux est la suivante :

Le compilateur Java vérifiera les "exceptions vérifiées", mais les "exceptions d'exécution" ne seront pas vérifiées. C'est-à-dire que pour les exceptions vérifiées, elles doivent soit être déclarées et lancées via des lancers, soit capturées et traitées via try-catch, sinon elles ne peuvent pas passer la compilation. En ce qui concerne les exceptions d'exécution, si elles ne sont ni lancées via l'instruction throws ni interceptées avec une instruction try-catch, le compilateur passera quand même. Bien sûr, bien que le compilateur Java ne vérifie pas les exceptions d'exécution, nous pouvons également expliquer l'exception via des lancers ou la capturer via try-catch.

ArithmeticException (par exemple, le diviseur est 0), IndexOutOfBoundsException (par exemple, le tableau est hors limites), etc. sont toutes des exceptions d'exécution. Pour ce genre d'exception, il faut l'éviter en modifiant le code. Pour les exceptions vérifiées, le programme peut être repris via le traitement. Par exemple, supposons que la tentative d'un utilisateur de passer un appel sur un téléphone public échoue parce qu'il ne stocke pas un nombre suffisant de préfixes ; une exception vérifiée est levée ;


Point 3 : Éviter l'utilisation inutile des exceptions vérifiées

Les "exceptions vérifiées" sont une fonctionnalité intéressante de Java langue. Contrairement aux codes retour, les « exceptions vérifiées » obligent les programmeurs à gérer les conditions d'exception, améliorant ainsi considérablement la fiabilité du programme.


Cependant, une utilisation excessive des exceptions vérifiées rendra l'API très peu pratique à utiliser. Si une méthode lève une ou plusieurs exceptions vérifiées, le code qui appelle la méthode doit gérer ces exceptions dans un ou plusieurs blocs catch, ou il doit lever ces exceptions via la déclaration throws. Qu'il soit géré via catch ou lancé via l'instruction throws, cela ajoute une charge non négligeable au programmeur.


Pour que les « exceptions vérifiées » s'appliquent, deux conditions doivent être remplies en même temps : Premièrement,

même une utilisation correcte de l'API n'empêche pas la condition d'exception de se produire. Deuxièmement, une fois qu'une exception se produit, les programmeurs utilisant l'API peuvent prendre des mesures utiles pour gérer le programme .

Article 4 : Essayez d'utiliser les exceptions standards

La réutilisation du code mérite d'être encouragée, c'est une règle générale Règles , les exceptions ne font pas exception. La réutilisation des exceptions existantes présente plusieurs avantages :


Premièrement, cela rend votre API plus facile à apprendre et à utiliser car elle est cohérente avec les idiomes que les programmeurs connaissent déjà.


Deuxièmement, pour les programmes qui utilisent ces API, elles sont plus lisibles car elles ne sont pas remplies d'exceptions peu familières aux programmeurs.


第三,异常类越少,意味着内存占用越小,并且转载这些类的时间开销也越小。

虽然它们是Java平台库迄今为止最常被重用的异常,但是,在许可的条件下,其它的异常也可以被重用。例如,如果你要实现诸如复数或者矩阵之类的算术对象,那么重用ArithmeticException和NumberFormatException将是非常合适的。如果一个异常满足你的需要,则不要犹豫,使用就可以,不过你一定要确保抛出异常的条件与该异常的文档中描述的条件一致。这种重用必须建立在语义的基础上,而不是名字的基础上!

最后,一定要清楚,选择重用哪一种异常并没有必须遵循的规则。例如,考虑纸牌对象的情形,假设有一个用于发牌操作的方法,它的参数(handSize)是发一手牌的纸牌张数。假设调用者在这个参数中传递的值大于整副牌的剩余张数。那么这种情形既可以被解释为IllegalArgumentException(handSize的值太大),也可以被解释为IllegalStateException(相对客户的请求而言,纸牌对象的纸牌太少)。

第5条: 抛出的异常要适合于相应的抽象

如果一个方法抛出的异常与它执行的任务没有明显的关联关系,这种情形会让人不知所措。当一个方法传递一个由低层抽象抛出的异常时,往往会发生这种情况。这种情况发生时,不仅让人困惑,而且也"污染"了高层API。

为了避免这个问题,高层实现应该捕获低层的异常,同时抛出一个可以按照高层抽象进行介绍的异常。这种做法被称为"异常转译(exception translation)"。

例如,在Java的集合框架AbstractSequentialList的get()方法如下(基于JDK1.7.0_40):


public E get(int index) {
  try {
    return listIterator(index).next();
  } catch (NoSuchElementException exc) {
    throw new IndexOutOfBoundsException("Index: "+index);
  }
}

listIterator(index)会返回ListIterator对象,调用该对象的next()方法可能会抛出NoSuchElementException异常。而在get()方法中,抛出NoSuchElementException异常会让人感到困惑。所以,get()对NoSuchElementException进行了捕获,并抛出了IndexOutOfBoundsException异常。即,相当于将NoSuchElementException转译成了IndexOutOfBoundsException异常。

第6条: 每个方法抛出的异常都要有文档

要单独的声明被检查的异常,并且利用Javadoc的@throws标记,准确地记录下每个异常被抛出的条件。

如果一个类中的许多方法处于同样的原因而抛出同一个异常,那么在该类的文档注释中对这个异常做文档,而不是为每个方法单独做文档,这是可以接受的。

第7条: 在细节消息中包含失败 -- 捕获消息

简而言之,当我们自定义异常或者抛出异常时,应该包含失败相关的信息。

当一个程序由于一个未被捕获的异常而失败的时候,系统会自动打印出该异常的栈轨迹。在栈轨迹中包含该异常的字符串表示。典型情况下它包含该异常类的类名,以及紧随其后的细节消息。

第8条: 努力使失败保持原子性

当一个对象抛出一个异常之后,我们总期望这个对象仍然保持在一种定义良好的可用状态之中。对于被检查的异常而言,这尤为重要,因为调用者通常期望从被检查的异常中恢复过来。

一般而言,一个失败的方法调用应该保持使对象保持在"它在被调用之前的状态"。具有这种属性的方法被称为具有"失败原子性(failure atomic)"。可以理解为,失败了还保持着原子性。对象保持"失败原子性"的方式有几种:

(01) 设计一个非可变对象。

(02) 对于在可变对象上执行操作的方法,获得"失败原子性"的最常见方法是,在执行操作之前检查参数的有效性。如下(Stack.java中的pop方法):


public Object pop() {
  if (size==0)
    throw new EmptyStackException();
  Object result = elements[--size];
  elements[size] = null;
  return result;
}

(03) 与上一种方法类似,可以对计算处理过程调整顺序,使得任何可能会失败的计算部分都发生在对象状态被修改之前。 

(04) 编写一段恢复代码,由它来解释操作过程中发生的失败,以及使对象回滚到操作开始之前的状态上。

(05) 在对象的一份临时拷贝上执行操作,当操作完成之后再把临时拷贝中的结果复制给原来的对象。
虽然"保持对象的失败原子性"是期望目标,但它并不总是可以做得到。例如,如果多个线程企图在没有适当的同步机制的情况下,并发的访问一个对象,那么该对象就有可能被留在不一致的状态中。

即使在可以实现"失败原子性"的场合,它也不是总被期望的。对于某些操作,它会显著的增加开销或者复杂性。

总的规则是:作为方法规范的一部分,任何一个异常都不应该改变对象调用该方法之前的状态,如果这条规则被违反,则API文档中应该清楚的指明对象将会处于什么样的状态

第9条: 不要忽略异常

当一个API的设计者声明一个方法会抛出某个异常的时候,他们正在试图说明某些事情。所以,请不要忽略它!忽略异常的代码如下:


try {
  ...
} catch (SomeException e) {
}

空的catch块会使异常达不到应有的目的,异常的目的是强迫你处理不正常的条件。忽略一个异常,就如同忽略一个火警信号一样 -- 若把火警信号器关闭了,那么当真正的火灾发生时,就没有人看到火警信号了。所以,至少catch块应该包含一条说明,用来解释为什么忽略这个异常是合适的。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn