Error Handling with Exceptions【3】

import com.bruceeckel.simpletest.*;
  
  

  
  
   
    
  
  
class DynamicFieldsException extends Exception {}
  
  

  
  
   
    
  
  
public class DynamicFields {
  
  
  private static Test monitor = new Test();
  
  
  private Object[][] fields;
  
  
  public DynamicFields(int initialSize) {
  
  
    fields = new Object[initialSize][2];
  
  
    for(int i = 0; i < initialSize; i++)
  
  
      fields[i] = new Object[] { null, null };
  
  
  }
  
  
  public String toString() {
  
  
    StringBuffer result = new StringBuffer();
  
  
    for(int i = 0; i < fields.length; i++) {
  
  
      result.append(fields[i][0]);
  
  
      result.append(": ");
  
  
      result.append(fields[i][1]);
  
  
      result.append("/n");
  
  
    }
  
  
    return result.toString();
  
  
  }
  
  
  private int hasField(String id) {
  
  
    for(int i = 0; i < fields.length; i++)
  
  
      if(id.equals(fields[i][0]))
  
  
        return i;
  
  
    return -1;
  
  
  }
  
  
  private int  getFieldNumber(String id) throws NoSuchFieldException {
  
  
    int fieldNum = hasField(id);
  
  
    if(fieldNum == -1)
  
  
      throw new NoSuchFieldException();
  
  
    return fieldNum;
  
  
  }
  
  
  private int makeField(String id) {
  
  
    for(int i = 0; i < fields.length; i++)
  
  
      if(fields[i][0] == null) {
  
  
        fields[i][0] = id;
  
  
        return i;
  
  
      }
  
  
    // No empty fields. Add one:
  
  
    Object[][]tmp = new Object[fields.length + 1][2];
  
  
    for(int i = 0; i < fields.length; i++)
  
  
      tmp[i] = fields[i];
  
  
    for(int i = fields.length; i < tmp.length; i++)
  
  
      tmp[i] = new Object[] { null, null };
  
  
    fields = tmp;
  
  
    // Reursive call with expanded fields:
  
  
    return makeField(id);
  
  
  }
  
  
  public Object  getField(String id) throws NoSuchFieldException {
  
  
    return fields[getFieldNumber(id)][1];
  
  
  }
  
  
  public Object setField(String id, Object value)
  
  
  throws DynamicFieldsException {
  
  
    if(value == null) {
  
  
      // Most exceptions don't have a "cause" constructor.
  
  
      // In these cases you must use initCause(),
  
  
      // available in all Throwable subclasses.
  
  
      DynamicFieldsException dfe =
  
  
        new DynamicFieldsException();
  
  
      dfe.initCause(new NullPointerException());
  
  
      throw dfe;
  
  
    }
  
  
    int fieldNumber = hasField(id);
  
  
    if(fieldNumber == -1)
  
  
      fieldNumber = makeField(id);
  
  
    Object result = null;
  
  
    try {
  
  
      result = getField(id); // Get old value
  
  
    } catch(NoSuchFieldException e) {
  
  
      // Use constructor that takes "cause":
  
  
      throw new RuntimeException(e);
  
  
    }
  
  
    fields[fieldNumber][1] = value;
  
  
    return result;
  
  
  }
  
  
  public static void main(String[] args) {
  
  
    DynamicFields df = new DynamicFields(3);
  
  
    System.out.println(df);
  
  
    try {
  
  
      df.setField("d", "A value for d");
  
  
      df.setField("number", new Integer(47));
  
  
      df.setField("number2", new Integer(48));
  
  
      System.out.println(df);
  
  
      df.setField("d", "A new value for d");
  
  
      df.setField("number3", new Integer(11));
  
  
      System.out.println(df);
  
  
      System.out.println(df.getField("d"));
  
  
      Object field = df.getField("a3"); // Exception
  
  
    } catch(NoSuchFieldException e) {
  
  
      throw new RuntimeException(e);
  
  
    } catch(DynamicFieldsException e) {
  
  
      throw new RuntimeException(e);
  
  
    }
  
  
    monitor.expect(new String[] {
  
  
      "null: null",
  
  
      "null: null",
  
  
      "null: null",
  
  
      "",
  
  
      "d: A value for d",
  
  
      "number: 47",
  
  
      "number2: 48",
  
  
      "",
  
  
      "d: A new value for d",
  
  
      "number: 47",
  
  
      "number2: 48",
  
  
      "number3: 11",
  
  
      "",
  
  
      "A value for d",
  
  
      "Exception in thread /"main/" " +
  
  
      "java.lang.RuntimeException: " +
  
  
      "java.lang.NoSuchFieldException",
  
  
      "/tat DynamicFields.main(DynamicFields.java:98)",
  
  
      "Caused by: java.lang.NoSuchFieldException",
  
  
      "/tat DynamicFields.getFieldNumber(" +
  
  
      "DynamicFields.java:37)",
  
  
      "/tat DynamicFields.getField(DynamicFields.java:58)",
  
  
      "/tat DynamicFields.main(DynamicFields.java:96)"
  
  
    });
  
  
  }
  
  
}
  
  

Each DynamicFields object contains an array of Object-Object pairs. The first object is the field identifier (a String), and the second is the field value, which can be any type except an unwrapped primitive. When you create the object, you make an educated guess about how many fields you need. When you call setField( ), it either finds the existing field by that name or creates a new one, and puts in your value. If it runs out of space, it adds new space by creating an array of length one longer and copying the old elements in. If you try to put in a null value, then it throws a DynamicFieldsException by creating one and using initCause( ) to insert a NullPointerException as the cause.

每个DynamicFields对象都包含一个ObjectObject的数组对,第一个对象是标识符为字符串类型,第二个对象是数据项,可以是除了基本类型之外的所有类型。当你创建一个对象的时候,你估计一下需要多少数据成员,你可以调用setField(),这个方法可以通过标识符查找数据项是否存在或者创建一个新的数据项并且给value赋值。如果控件用完了的话,它会创建一个比原来的数组多一个元素的数组,并且把原来的数据拷贝到新的数组去,如果你试图赋一个为null的值,它会创建一个DynamicFieldsException对象并抛出该异常,并且使用NullPointerException来作为cause插进去。

As a return value, setField( ) also fetches out the old value at that field location using getField( ), which could throw a NoSuchFieldException. If the client programmer calls getField( ), then they are responsible for handling NoSuchFieldException, but if this exception is thrown inside setField( ), it’s a programming error, so the NoSuchFieldException is converted to a RuntimeException using the constructor that takes a cause argument.

作为返回值,setField()会调用getField()提取数据成员值并返回,但是可能会抛出一个NoSuchFieldException异常信息,如果客户端程序员调用getField()方法,那么他们必须要处理NoSuchFieldException异常信息,但是如果这个异常信息是setField()抛出来的话,这就是一个变成错误了,因为NoSuchFieldException异常会拿cause当作参数的构造方法转化成RunTimeException异常

Standard Java exceptions

The Java class Throwable describes anything that can be thrown as an exception. There are two general types of Throwable objects (“types of” = “inherited from”). Error represents compile-time and system errors that you don’t worry about catching (except in special cases). Exception is the basic type that can be thrown from any of the standard Java library class methods and from your methods and run-time accidents. So the Java programmer’s base type of interest is usually Exception.

JavaThrowable类描述了作为异常可以抛出的所有信息,这里有两个自Throwable继承下来的类。一个是Error,它是用来描述编译期间系统错误的,不需要捕获(特殊情况除外)。还有一个就是Exception,这是一个可以被任何一个Java标准类库中的方法抛出来,或者在你自己的类中运行期间的错误也可以抛出该异常。所以Java的程序员选择异常基类的时候更倾向于Exception

The best way to get an overview of the exceptions is to browse the HTML Java documentation that you can download from java.sun.com. It’s worth doing this once just to get a feel for the various exceptions, but you’ll soon see that there isn’t anything special between one exception and the next except for the name. Also, the number of exceptions in Java keeps expanding; basically, it’s pointless to print them in a book. Any new library you get from a third-party vendor will probably have its own exceptions as well. The important thing to understand is the concept and what you should do with the exceptions.

了解Java的异常最好的办法就是浏览HTML版本的Java帮助文档,你可以在java.sun.com下载,看一遍还是值得的,这样可以对各种异常信息有一个感性的认识,但是你很快就会发现两个异常之间除了名字之外几乎没有什么不同之处,此外Java异常的数量还在不断的增加;但是在本书中罗列这些也没有什么意义,而且你拿到的第三方的类库一般额都会提供一套自己的异常机制,其实最重要的是理解异常的概念和知道应该怎样操作异常。

The basic idea is that the name of the exception represents the problem that occurred, and the exception name is intended to be relatively self-explanatory. The exceptions are not all defined in java.lang; some are created to support other libraries such as util, net, and io, which you can see from their full class names or what they are inherited from. For example, all I/O exceptions are inherited from java.io.IOException.

基本的原则就是使用异常的名字来标识遇到了什么问题,异常的名字尽量是自解释的。并不是所有的异常都定义在java.lang;有一些是为了支持其它的类库而创建的例如utilnetio,你可用从类的全名或者所继承的基类可以看出一些端倪,所有I/O的异常都是继承自java.io.IOException

The special case of RuntimeException

The first example in this chapter was

if(t == null)
  
  
  throw new NullPointerException();
  
  

It can be a bit horrifying to think that you must check for null on every reference that is passed into a method (since you can’t know if the caller has passed you a valid reference). Fortunately, you don’t—this is part of the standard run-time checking that Java performs for you, and if any call is made to a null reference, Java will automatically throw a NullPointerException. So the above bit of code is always superfluous.

如果需要校验传递给你的每个方法的参数是否为null确实是比较恶心,因为你不知道调用者传递给你的是否是一个有效的reference,幸运的是其实不需要,因为这是标准的运行时检查的一部分,Java会替你做,如果某个调用者传入了nullJava会自动的抛出一个NullPointerExcepion。所以上面这部分代码就是多余的了。

There’s a whole group of exception types that are in this category. They’re always thrown automatically by Java and you don’t need to include them in your exception specifications. Conveniently enough, they’re all grouped together by putting them under a single base class called RuntimeException, which is a perfect example of inheritance; It establishes a family of types that have some characteristics and behaviors in common. Also, you never need to write an exception specification saying that a method might throw a RuntimeException (or any type inherited from RuntimeException), because they are unchecked exceptions. Because they indicate bugs, you don’t usually catch a RuntimeException—it’s dealt with automatically. If you were forced to check for RuntimeExceptions, your code could get too messy. Even though you don’t typically catch RuntimeExceptions, in your own packages you might choose to throw some of the RuntimeExceptions.

NullPointerException异常类中包含了很多的异常类型,他们通常都是Java自动抛出的不需要在异常处理程序中捕获它们。它们都是继承的RuntimeException基类,所以使用起来方便了很多,而且这也是一个很好的展现继承的例子。它建立了一个具有相同特征和行为的类系。当然你也不需要去为这里面的方法作一个异常处理程序,怀疑它会抛出一个RuntimeException异常,因为它们都是Unchecked Exceptions。由于它们表示Bugs,所以你不需要去捕获它们,它会自动的处理。如果你非要去检查RuntimeException那么你的代码会很凌乱,虽然一般你不回去捕捉这些RuntimeException,但是在你自己创建package的时候可能会抛出RuntimeException异常信息。

What happens when you don’t catch such exceptions? Since the compiler doesn’t enforce exception specifications for these, it’s quite plausible that a RuntimeException could percolate all the way out to your main( ) method without being caught. To see what happens in this case, try the following example:

你不捕捉这些异常又会发生些什么呢?因为编译器不会强制你为这些作异常处理程序,所以RuntimeException应会不受到任何阻挡直接到达main()方法,如果想试试这样的话会发生什么试试下面的代码:

import com.bruceeckel.simpletest.*;
  
  

  
  
   
    
  
  
public class NeverCaught {
  
  
  private static Test monitor = new Test();
  
  
  static void f() {
  
  
    throw new RuntimeException("From f()");
  
  
  }
  
  
  static void g() {
  
  
    f();
  
  
  }
  
  
  public static void main(String[] args) {
  
  
    g();
  
  
    monitor.expect(new String[] {
  
  
      "Exception in thread /"main/" " +
  
  
      "java.lang.RuntimeException: From f()",
  
  
      "        at NeverCaught.f(NeverCaught.java:7)",
  
  
      "        at NeverCaught.g(NeverCaught.java:10)",
  
  
      "        at NeverCaught.main(NeverCaught.java:13)"
  
  
    });
  
  
  }
  
  
} 
  
  

You can already see that a RuntimeException (or anything inherited from it) is a special case, since the compiler doesn’t require an exception specification for these types.

你可以看到RuntimeException或者它的派生类是个特例,因为编译器不需要为它们作异常处理。

So the answer is: If a RuntimeException gets all the way out to main( ) without being caught, printStackTrace( ) is called for that exception as the program exits.

所以答案就是:如果RuntimeException异常没有被捕获而是一路到达了main()方法,那么在程序退出的时候会为这个异常信息调用printStackTrace()方法。

Keep in mind that you can only ignore exceptions of type RuntimeException (and subclasses) in your coding, since all other handling is carefully enforced by the compiler. The reasoning is that a RuntimeException represents a programming error:

记在心里,你可以在代码中忽略RuntimeException(以及它的子类),因为编译器强制要求你处理其它的异常。原因就是RuntimeException描述的是程序错误。

  1. An error you cannot anticipate. For example, a null reference that is outside of your control.  
  2. An error that you, as a programmer, should have checked for in your code (such as ArrayIndexOutOfBoundsException where you should have paid attention to the size of the array). An exception that happens from point #1 often becomes an issue for point #2.

1. 错误是不可预料的,例如一个nullreference就不在你的控制范围内;

2. 针对错误,作为一个程序员你应该检查你的代码(诸如ArrayIndexOutOfBoundsException你应当把精力集中到array的长度上)。第一种情况发生的异常经常演化成第二种情况发生的异常。

You can see what a tremendous benefit it is to have exceptions in this case, since they help in the debugging process.

It’s interesting to notice that you cannot classify Java exception handling as a single-purpose tool. Yes, it is designed to handle those pesky run-time errors that will occur because of forces outside your code’s control, but it’s also essential for certain types of programming bugs that the compiler cannot detect.

你可用看到这种情况下有异常处理是有很大好处的,因为在调试当中会给你很大的帮助。值得一提的是你不要仅仅的把Java的异常处理程序作为一种单一的工具。它设计的目的是为了那些不在你控制之下的因素,会引起的运行时的错误,它们对于那些编译器无法发现的一些Bug的类型也是很有必要的。

Performing cleanup with finally

There’s often some piece of code that you want to execute whether or not an exception is thrown within a try block. This usually pertains to some operation other than memory recovery (since that’s taken care of by the garbage collector). To achieve this effect, you use a finally clause[41] at the end of all the exception handlers. The full picture of an exception handling section is thus:

我们经常遇到这样的情况,不管异常在try区是否抛出了,我们都希望一部分代码都要执行。这经常被用来作除了恢复内存之外的一些操作(因为内存回收是由垃圾回收器作的),要实现这种效果你可以在所有的异常处理程序之后使用finally句子。这样一个完整的异常处理程序就是这样的了:

try {
  
  
  // The guarded region: Dangerous activities
  
  
  // that might throw A, B, or C 
  
  
} catch(A a1) {
  
  
  // Handler for situation A
  
  
} catch(B b1) {
  
  
  // Handler for situation B
  
  
} catch(C c1) {
  
  
  // Handler for situation C
  
  
} finally {
  
  
  // Activities that happen every time
  
  
}
  
  

To demonstrate that the finally clause always runs, try this program:

为了证明finally句子使用会执行,试试下面的程序:

import com.bruceeckel.simpletest.*;
  
  

  
  
   
    
  
  
class ThreeException extends Exception {}
  
  

  
  
   
    
  
  
public class FinallyWorks {
  
  
  private static Test monitor = new Test();
  
  
  static int count = 0;
  
  
  public static void main(String[] args) {
  
  
    while(true) {
  
  
      try {
  
  
        if(count++ == 0)
  
  
          throw new ThreeException();
  
  
        System.out.println("No exception");
  
  
      } catch(ThreeException e) {
  
  
        System.err.println("ThreeException");
  
  
      } finally {
  
  
        System.err.println("In finally clause");
  
  
        if(count == 2) break;
  
  
      }
  
  
    }
  
  
    monitor.expect(new String[] {
  
  
      "ThreeException",
  
  
      "In finally clause",
  
  
      "No exception",
  
  
      "In finally clause"
  
  
    });
  
  
  }
  
  
}
  
  

From the output, you can see that whether or not an exception is thrown, the finally clause is always executed.

通过输出结果你可以看到不管是否抛出了异常信息finally句子总是会被执行。

This program also gives a hint for how you can deal with the fact that exceptions in Java (like exceptions in C++) do not allow you to resume back to where the exception was thrown, as discussed earlier. If you place your try block in a loop, you can establish a condition that must be met before you continue the program. You can also add a static counter or some other device to allow the loop to try several different approaches before giving up. This way you can build a greater level of robustness into your programs.

这个程序也给了我们一些提示:我们如何应对在Java中发生了异常后会阻止我们回到异常的抛出地点,如果你在try区放入一个循环,你可以建立一个条件必须成立后才可以继续执行后面的程序,你也可以增加一个计数器或者其它的方式让循环多以几种不同的方式去试,知道放弃为止。这样你就可以让你的程序的健壮性更上一个台阶。

What’s finally for?

In a language without garbage collection and without automatic destructor calls,[42] finally is important because it allows the programmer to guarantee the release of memory regardless of what happens in the try block. But Java has garbage collection, so releasing memory is virtually never a problem. Also, it has no destructors to call. So when do you need to use finally in Java?

在没有垃圾回收器和自动析构方法的语言中,finally是非常重要的,因为它允许程序员来保证不管在try区发生了什么都可以去释放内存控件。但是Java提供了垃圾回收器,所以释放内存就不是问题了,当然它没有析构方法去供调用。所以在Java中你什么时候才需要finally呢?

The finally clause is necessary when you need to set something other than memory back to its original state. This is some kind of cleanup like an open file or network connection, something you’ve drawn on the screen, or even a switch in the outside world, as modeled in the following example:

当你需要把一些除了内存之外的一些东西回退到原始的状态时,这finally句子还是很有用的。可能是一个打开的文件或者网络连接,或者你画在屏幕上的一些东西,甚至是现实世界中的开关,就像下面的例子中示范的:

public class Switch {
  
  
  private boolean state = false;
  
  
  public boolean read() { return state; }
  
  
  public void on() { state = true; }
  
  
  public void off() { state = false; }
  
  
} 
  
  

  
  
   
    
  
  
public class OnOffException1 extends Exception {}
  
  

  
  
   
    
  
  
public class OnOffException2 extends Exception {}
  
  

  
  
   
    
  
  
public class OnOffSwitch {
  
  
  private static Switch sw = new Switch();
  
  
  public static void f()
  
  
  throws OnOffException1,OnOffException2 {}
  
  
  public static void main(String[] args) {
  
  
    try {
  
  
      sw.on();
  
  
      f();
  
  
      sw.off();
  
  
    } catch(OnOffException1 e) {
  
  
      System.err.println("OnOffException1");
  
  
      sw.off();
  
  
    } catch(OnOffException2 e) {
  
  
      System.err.println("OnOffException2");
  
  
      sw.off();
  
  
    }
  
  
  }
  
  
} 
  
  

The goal here is to make sure that the switch is off when main( ) is completed, so sw.off( ) is placed at the end of the try block and at the end of each exception handler. But it’s possible that an exception could be thrown that isn’t caught here, so sw.off( ) would be missed. However, with finally you can place the cleanup code from a try block in just one place:

这里的目的就是确保在main()方法执行完毕之后switch是关闭的,所以sw.off()被写在了try区的后面也写在了每个异常处理程序中。但是很有可能抛出了一个你捕获不到的异常,所以sw.off()可能会执行不到。然而你如果使用finally的话你只需要在try的一个位置写就可以了。

public class WithFinally {
  
  
  static Switch sw = new Switch();
  
  
  public static void main(String[] args) {
  
  
    try {
  
  
      sw.on();
  
  
      OnOffSwitch.f();
  
  
    } catch(OnOffException1 e) {
  
  
      System.err.println("OnOffException1");
  
  
    } catch(OnOffException2 e) {
  
  
      System.err.println("OnOffException2");
  
  
    } finally {
  
  
      sw.off();
  
  
    }
  
  
  }
  
  
}
  
  

Here the sw.off( ) has been moved to just one place, where it’s guaranteed to run no matter what happens.

这里的sw.off()被移动到了一个位置,这里能够确保不管发生什么都会被执行。

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值