Java は、例外のスロー、例外のキャッチ、およびプログラム例外を処理するためのfinallyステートメントの使用を提供します例外処理についてのいくつかの提案を詳しく見てみましょう
第1条: 例外は異常に対してのみ発生するべきです。状況で使用されます
推奨事項: 例外は異常な状況にのみ使用されるべきであり、通常の制御フローには決して使用されるべきではありません。
以下の 2 つのコードを比較して説明してください。
try { int i=0; while (true) { arr[i]=0; i++; } } catch (IndexOutOfBoundsException e) { }
for (int i=0; i<arr.length; i++) { arr[i]=0; }両方のコードの機能は、arr 配列を走査し、配列内の各要素の値を 0 に設定することです。コード 1 は例外によって終了しますが、コード 2 は配列境界によって終了することが非常にわかりにくいようです。以下の 3 つの主な理由により、コード 1 の方法の使用は避けるべきです:
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]); } } }
endByRange time:8ms endByException time:16ms結果の説明: 例外を通過する速度は、通常の方法で配列を走査するよりもはるかに遅いです!
項目 2: 回復可能な状態にはチェック例外を使用し、プログラム エラーには実行時例外を使用します。
実行時例外 -- RuntimeException クラスとそのサブクラスは実行時異常と呼ばれます。
チェック例外 -- Exception クラス自体、および「実行時例外」を除く Exception サブクラスの他のサブクラスはすべてチェック例外です。
それらの違いは次のとおりです: Java コンパイラーは「チェック例外」をチェックしますが、「実行時例外」はチェックしません 。つまり、チェック例外の場合は、宣言して throws によってスローするか、try-catch によってキャプチャして処理する必要があります。そうでないと、コンパイルに合格できません。実行時例外に関しては、throws ステートメントでスローされず、try-catch ステートメントでキャッチされない場合でも、コンパイラは通過します。もちろん、Java コンパイラは実行時例外をチェックしませんが、スローを通じて例外を説明したり、try-catch を通じて例外をキャプチャしたりすることもできます。
ArithmeticException (たとえば、除数が 0 である)、IndexOutOfBoundsException (たとえば、配列が範囲外である) などはすべて実行時例外です。この種の例外については、コードを変更して回避する必要があります。チェックされた例外の場合、処理を通じてプログラムを再開できます。たとえば、ユーザーが公衆電話に電話をかけようとして、十分な数のプレフィックスを保存していないために失敗したとします。チェックされた例外がスローされます。
項目 3: チェック例外の不要な使用を避ける
「チェック例外」は Java 言語の優れた機能です。リターン コードとは異なり、「チェック例外」によりプログラマは例外条件を処理する必要があり、プログラムの信頼性が大幅に向上します。
ただし、チェック例外を過度に使用すると、API が非常に使いにくくなります。メソッドが 1 つ以上のチェック例外をスローする場合、メソッドを呼び出すコードは 1 つ以上の catch ブロックでこれらの例外を処理するか、throws 宣言を通じてこれらの例外をスローする必要があります。 catch によって処理されるか、throws ステートメントによってスローされるかに関係なく、プログラマには無視できない負担がかかります。
「チェック例外」に適用するには、次の 2 つの条件を同時に満たす必要があります: まず、API を正しく使用しても、例外条件の発生は妨げられません。 2 番目に、例外が発生すると、API を使用するプログラマーはプログラムを処理するために役立つアクションを実行できます。
第 4 条: 標準例外の使用を試みる
これは一般的なルールであり、例外は例外ではありません。既存の例外を再利用すると、いくつかの利点があります:
まず、プログラマーがすでに慣れ親しんでいる慣用句と一致しているため、API の学習と使用が容易になります。
第 2 に、これらの API を使用するプログラムは、プログラマが馴染みのない例外が含まれていないため、より読みやすくなります。
第三,异常类越少,意味着内存占用越小,并且转载这些类的时间开销也越小。
虽然它们是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块应该包含一条说明,用来解释为什么忽略这个异常是合适的。
以上がJava 例外処理に関するいくつかの提案を共有する (コレクション)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。