Heim  >  Artikel  >  Java  >  Teilen Sie einige Vorschläge zur Java-Ausnahmebehandlung (Sammlung)

Teilen Sie einige Vorschläge zur Java-Ausnahmebehandlung (Sammlung)

黄舟
黄舟Original
2017-06-18 09:47:401500Durchsuche

Java bietet Auslösen von Ausnahmen, das Abfangen von Ausnahmen und die Verwendung von „finally“-Anweisungen zur Behandlung von Programmausnahmen. Schauen wir uns einige Vorschläge zur Ausnahmebehandlung von Java genauer an

Artikel 1: Ausnahmen nur für anormale Zustände verwenden

Empfehlung: Ausnahmen sollten nur für anormale Zustände verwendet werden, sie sollten niemals für die normale Kontrolle verwendet werden fließen.


Veranschaulichung durch Vergleich der beiden Codes unten.


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;
}
Die Funktion beider Codes besteht darin, das arr-Array zu durchlaufen und den Wert jedes Elements im Array auf 0 zu setzen. Code 1 wird durch eine Ausnahme beendet, die sehr schwer zu verstehen scheint. Code 2 wird durch eine Array-Grenze beendet. Wir sollten die Verwendung der Methode von Code 1 aus drei Hauptgründen vermeiden:

  1. Der Ausnahmemechanismus ist ursprünglich für die Verwendung in abnormalen Situationen konzipiert, daher versuchen nur wenige JVM-Implementierungen, diese durch Leistungsoptimierung zu bewältigen. Daher ist das Erstellen, Auslösen und Abfangen von Ausnahmen teuer.

  2. Das Einfügen von Code in eine Try-Catch-Rückgabe verhindert, dass die JVM bestimmte Optimierungen implementiert, die sie andernfalls durchführen könnte.

  3. Das Standardmuster der Array-Traversierung führt nicht zu redundanten Prüfungen, und einige moderne JVM-Implementierungen werden diese wegoptimieren.

Der ausnahmebasierte Modus ist tatsächlich viel langsamer als der Standardmodus. Der Testcode lautet wie folgt:


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]);
    }
  }
}
Laufergebnis:



endByRange time:8ms
endByException time:16ms
Ergebnis Beschreibung: Bestanden Die Geschwindigkeit der Ausnahmedurchquerung ist viel langsamer als die normale Methode

beim Durchlaufen des Arrays !

Punkt 2: Verwenden Sie geprüfte Ausnahmen für wiederherstellbare Bedingungen, verwenden Sie Laufzeitausnahmen für Programmfehler

  1. Laufzeit Ausnahmen – Die RuntimeException-Klasse und ihre Unterklassen werden Laufzeitausnahmen genannt.

  2. Geprüfte Ausnahmen – die Exception-Klasse selbst und andere Unterklassen von Exception-Unterklassen mit Ausnahme von „Laufzeitausnahmen“ sind alle geprüfte Ausnahmen.

Der Unterschied zwischen ihnen ist:

Der Java-Compiler prüft „geprüfte Ausnahmen“, aber „Laufzeitausnahmen“ werden nicht geprüft. Das heißt, geprüfte Ausnahmen müssen entweder durch Throws deklariert und ausgelöst oder durch Try-Catch erfasst und verarbeitet werden, andernfalls können sie die Kompilierung nicht bestehen. Was Laufzeitausnahmen betrifft, so wird der Compiler sie trotzdem bestehen, wenn sie weder durch die throws-Anweisung ausgelöst noch durch eine try-catch-Anweisung abgefangen werden. Obwohl der Java-Compiler keine Laufzeitausnahmen prüft, können wir die Ausnahme natürlich auch durch Würfe erklären oder sie durch Try-Catch erfassen.

ArithmeticException (z. B. ist der Divisor 0), IndexOutOfBoundsException (z. B. liegt das Array außerhalb der Grenzen) usw. sind allesamt Laufzeitausnahmen. Diese Art von Ausnahme sollten wir durch eine Änderung des Codes vermeiden. Bei geprüften Ausnahmen kann das Programm durch Verarbeitung fortgesetzt werden. Angenommen, der Versuch eines Benutzers, einen Anruf auf einem Münztelefon zu tätigen, schlägt fehl, weil er nicht genügend Präfixe speichert.


Punkt 3: Vermeiden Sie unnötige Verwendung von geprüften Ausnahmen

„Geprüfte Ausnahmen“ sind eine großartige Funktion von Java Sprache. Im Gegensatz zu Rückkehrcodes zwingen „geprüfte Ausnahmen“ Programmierer dazu, Ausnahmebedingungen zu behandeln, was die Zuverlässigkeit des Programms erheblich verbessert.


Allerdings macht die übermäßige Verwendung von geprüften Ausnahmen die Verwendung der API sehr unpraktisch. Wenn eine Methode eine oder mehrere geprüfte Ausnahmen auslöst, muss der Code, der die Methode aufruft, diese Ausnahmen in einem oder mehreren Catch-Blöcken behandeln oder diese Ausnahmen über die throws-Deklaration auslösen. Unabhängig davon, ob es über „catch“ gehandhabt oder über die „throws“-Anweisung ausgelöst wird, stellt es für den Programmierer eine nicht zu vernachlässigende Belastung dar.


Damit „geprüfte Ausnahmen“ gelten, müssen zwei Bedingungen gleichzeitig erfüllt sein: Erstens

auch die korrekte Verwendung der API verhindert nicht das Eintreten der Ausnahmebedingung. Zweitens sobald eine Ausnahme auftritt, können Programmierer, die die API verwenden, nützliche Maßnahmen ergreifen, um das Programm zu behandeln .

Artikel 4: Versuchen Sie, Standardausnahmen zu verwenden

Die Wiederverwendung von Code ist förderungswürdig, dies ist eine allgemeine Regel Ausnahmen sind keine Ausnahmen. Die Wiederverwendung vorhandener Ausnahmen hat mehrere Vorteile:


Erstens erleichtert es das Erlernen und Verwenden Ihrer API, da sie mit Redewendungen konsistent ist, mit denen Programmierer bereits vertraut sind.


Zweitens sind sie für Programme, die diese APIs verwenden, besser lesbar, da sie nicht mit Ausnahmen gefüllt sind, die Programmierern unbekannt sind.


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

虽然它们是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块应该包含一条说明,用来解释为什么忽略这个异常是合适的。

Das obige ist der detaillierte Inhalt vonTeilen Sie einige Vorschläge zur Java-Ausnahmebehandlung (Sammlung). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn