>Java >java지도 시간 >Java 예외 처리에 대한 몇 가지 제안 공유(컬렉션)

Java 예외 처리에 대한 몇 가지 제안 공유(컬렉션)

黄舟
黄舟원래의
2017-06-18 09:47:401546검색

Java는 예외 발생, 예외 포착 및 프로그램 예외 처리를 위한 finally 문 사용을 제공합니다. Java예외 처리

제1조: 비정상적인 경우에만 예외를 처리해야 합니다. 상황에 사용

권장 사항: 예외는 비정상적인 조건에만 사용해야 하며 정상적인 제어 흐름에는 절대 사용하면 안 됩니다.

아래 두 코드를 비교하여 설명해보세요.

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;
}

두 코드의 기능은 arr 배열을 순회하고 배열의 각 요소 값을 0으로 설정하는 것입니다. 코드 1은 이해하기 매우 어려운 예외로 인해 종료됩니다. 코드 2는 배열 경계로 종료됩니다. 세 가지 주요 이유 때문에 코드 1의 방법을 사용하지 않아야 합니다.

  1. 예외 메커니즘은 원래 비정상적인 상황에서 사용하도록 설계되었으므로 성능을 최적화하려고 시도하는 JVM 구현은 거의 없습니다. 따라서 예외를 생성하고, 던지고, 포착하는 데 비용이 많이 듭니다.

  2. try-catch 반환에 코드를 넣으면 JVM이 수행할 수 있는 특정 최적화를 구현하지 못하게 됩니다.

  3. 배열 탐색의 표준 패턴은 중복 검사로 이어지지 않으며 일부 최신 JVM 구현에서는 이를 최적화합니다.

실제로 예외 기반 모드는 표준 모드보다 훨씬 느립니다. 테스트 코드는 다음과 같습니다.


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: 복구 가능한 조건에는 확인된 예외를 사용하고 프로그램 오류에는 런타임 예외를 사용하세요.

  1. 런타임 예외 -- RuntimeException 클래스와 그 하위 클래스를 런타임 비정상이라고 합니다.

  2. 검사 예외 - 예외 클래스 자체와 "런타임 예외"를 제외한 예외 하위 클래스의 다른 하위 클래스는 모두 검사 예외입니다.

두 가지의 차이점은 다음과 같습니다.

Java 컴파일러는 "확인된 예외"를 확인하지만 "런타임 예외"는 확인하지 않습니다 . 즉, 확인된 예외의 경우 throw를 통해 선언 및 throw되거나 try-catch를 통해 캡처 및 처리되어야 합니다. 그렇지 않으면 컴파일을 통과할 수 없습니다. 런타임 예외의 경우, throws 문을 통해 발생하지도 않고 try-catch 문으로 포착되지도 않으면 컴파일러는 계속 통과합니다. 물론 Java 컴파일러는 런타임 예외를 확인하지 않지만 throw를 통해 예외를 설명하거나 try-catch를 통해 캡처할 수도 있습니다.

ArithmeticException(예: 제수가 0임), IndexOutOfBoundsException(예: 배열이 범위를 벗어남) 등은 모두 런타임 예외입니다. 이런 종류의 예외에 대해서는 코드를 수정하여 이를 방지해야 합니다. 확인된 예외의 경우 처리를 통해 프로그램을 재개할 수 있습니다. 예를 들어, 사용자가 충분한 수의 접두사를 저장하지 않아 공중전화로 전화를 걸려는 시도가 실패했다고 가정해 보겠습니다.


항목 3: 불필요한 확인 예외 사용 방지

"확인 예외"는 Java 언어의 좋은 기능입니다. 반환 코드와 달리 "확인된 예외"는 프로그래머가 예외 조건을 처리하도록 하여 프로그램의 신뢰성을 크게 향상시킵니다.


그러나 확인된 예외를 과도하게 사용하면 API 사용이 매우 불편해집니다. 메서드가 하나 이상의 확인된 예외를 발생시키는 경우 메서드를 호출하는 코드는 하나 이상의 catch 블록에서 이러한 예외를 처리하거나 throws 선언을 통해 이러한 예외를 발생시켜야 합니다. catch를 통해 처리하든 throws 문을 통해 throw하든 관계없이 프로그래머에게는 무시할 수 없는 부담이 추가됩니다.


"확인된 예외"에 적용하려면 두 가지 조건을 동시에 충족해야 합니다. 첫째,

API를 올바르게 사용해도 예외 조건이 발생하는 것을 방지할 수는 없습니다. 둘째, 예외가 발생하면 API를 사용하는 프로그래머는 프로그램을 처리하기 위해 유용한 조치를 취할 수 있습니다.

4조: 표준 예외를 사용해 보세요

코드 재사용은 권장할만한 가치가 있으며 이는 일반적인 규칙이며 예외도 예외는 아닙니다. 기존 예외를 재사용하면 여러 가지 이점이 있습니다.


첫째, API가 프로그래머에게 이미 익숙한 관용어와 일관되기 때문에 API를 더 쉽게 배우고 사용할 수 있습니다.


둘째, 이러한 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.