技术开发 频道

诊断 Java 代码: 悬挂复合错误类型

  事实上有一个简单的办法来避免悬挂复合错误:给每个数据类型的基本例定义一个自己的类。我建议执行有着 LinkedList 类的链表,该类包含一个有 Empty 类或 Cons 类的域,而不是象我们前面做的那样,单个执行链接链表。这些类执行一个公共接口,如图 1 所示。

        图 1. Empty 和 Cons UML 示意图
 

  为了执行可变的方法,新的 LinkedList 类作为一个内部不可变的链表的容器,如清单 5 所示。这个步骤是必须的,因为真正的空链表没有域可变,所以它们是不可变的。

  清单 5. 每个基本例获得自己的类

1 import java.util.NoSuchElementException;
2 public class LinkedList {
3   private List value;
4   /**
5    * Constructs an empty LinkedList.
6    */
7   public LinkedList() { this.value = new Empty(); }
8   /**
9    * Constructs a LinkedList containing only the given element.
10    */
11   public LinkedList(Object _first) { this.value = new Cons(_first); }
12   /**
13    * Constructs a LinkedList consisting of the given Object followed by
14    * all the elements in the given LinkedList.
15    */
16   public LinkedList(Object _first, LinkedList _rest) {
17    this.value = new Cons(_first, _rest.value);
18   }
19   private LinkedList(List _value) { this.value = _value; }
20   public Object getFirst() { return this.value.getFirst();  }
21   public LinkedList getRest() { return new LinkedList(this.value.getRest()); }
22   public void addFirst(Object o) { this.value = new Cons(o, this.value); }
23   public boolean isEmpty() { return this.value instanceof Empty; }
24   public boolean equals(Object that) {
25   
26     if (this.getClass() == that.getClass()) {
27       // The above test guarantees that the cast to LinkedList will always
28       // succeed.
29       return this.value.equals(((LinkedList)that).value);
30     }
31     else {
32       return false;
33     }
34   }
35 }
36

  那时,执行一个不可变的链表是直截了当的,如清单 6 所示。

  清单 6. 对节点作加法和乘法的方法

1 interface List {
2   public Object getFirst();
3   public List getRest();
4 }
5 class Empty implements List {
6   public Object getFirst() { throw new NoSuchElementException(); }
7   public List getRest() { throw new NoSuchElementException(); }
8   public boolean equals(Object that) {
9     return this.getClass() == that.getClass(); }
10 }
11 class Cons implements List {
12   
13   Object first;
14   List rest;
15   
16   Cons(Object _first) {
17     this.first = _first;
18     this.rest = new Empty();
19   }
20   Cons(Object _first, List _rest) {
21     this.first = _first;
22     this.rest = _rest;
23   }
24   public Object getFirst() { return this.first; }
25   public List getRest() { return this.rest; }
26   public boolean equals(Object that) {
27     if (this.getClass() == that.getClass()) {
28       // The above test guarantees that the cast to Cons will always succeed.
29       Cons _that = (Cons)that;
30       boolean firstEltsMatch  =  this.getFirst().equals(_that.getFirst());
31       boolean restEltsMatch = this.getRest().equals(_that.getRest());
32       return firstEltsMatch && restEltsMatch;
33     }
34     else {
35       return false;
36     }
37   }
38 }  
39

  每一个方法的逻辑现在相当简单了。也请注意,虽然就如以前在单参数 Cons 构造器中一样,它仍旧可能引入同样的错误,但我们已经构造了一个显式 Empty 类的事实使这种可能性大大减少。另外,任何阻断我们的链表以及忽略检查空例的代码将返回一个 NoSuchElementException ,而不是那些没什么用的空指针异常。

  这段代码的一个简单优化是对 Empty 类应用同一个设计类型,因为每一个 Empty 的实例都是同样的。我省去了这个优化,因为它不能相应地消除空指针异常,并且使得代码更复杂了一些。

  总结

  下面是这个星期的错误类型的分析:

  类型:悬挂复合

  症状:使用递规定义的数据类型的代码报告一个空指针异常。

  原因:定义的某些基本例没有给出自己的类,然后以这种方法定义了递归数据类型。相反,空指针被插入到不同的复合数据类型。客户端代码对基本例处理不一致。

  解决方法和预防措施:确保基本例的表示和检查的一致性。为每个基本例给出一个自己的类。

  我们这时还不能结束对空指针问题的讨论。在下一个部分,我们将还要注视另外一个非常普遍的,也被证明是一个空指针异常的错误类型,以及怎样识别它和避免它。

0
相关文章