之前已经问过我遇到的问题:How to implement an interface with an enum, where the interface extends Comparable?

但是,没有一种解决方案可以解决我的确切问题,这是:

我有一个值对象,类似于BigDecimal。有时,此值将不会用实际对象设置,因为尚不知道该值。因此,我想使用Null Object Pattern表示未定义此对象的时间。直到我尝试使我的Null对象实现Comparable接口之前,这都不是问题。这是一个SSCCE来说明:

public class ComparableEnumHarness {
  public static interface Foo extends Comparable<Foo> {
    int getValue();
  }

  public static class VerySimpleFoo implements Foo {
    private final int value;

    public VerySimpleFoo(int value) {
      this.value = value;
    }

    @Override
    public int compareTo(Foo f) {
      return Integer.valueOf(value).compareTo(f.getValue());
    }

    @Override
    public int getValue() {
      return value;
    }
  }

  // Error is in the following line:
  // The interface Comparable cannot be implemented more than once with different arguments:
  // Comparable<ComparableEnumHarness.NullFoo> and Comparable<ComparableEnumHarness.Foo>
  public static enum NullFoo implements Foo {
    INSTANCE;

    @Override
    public int compareTo(Foo f) {
      return f == this ? 0 : -1; // NullFoo is less than everything except itself
    }

    @Override
    public int getValue() {
      return Integer.MIN_VALUE;
    }
  }
}


其他问题:


在真实的示例中,这里有我称为Foo的多个子类。
我可以通过使NullFoo不是enum来解决此问题,但是我不能保证永远只有一个实例,即Effective Java Item 3, pg. 17-18

最佳答案

我不建议使用NullObject模式,因为我总是处于以下两种情况之一:


像对象一样使用NullObject是没有意义的,应该保留null
NullObject具有太多的含义,不能仅仅是一个NullObject,而应该本身就是一个真实的对象(例如,当它充当功能齐全的默认值时)


根据我们在评论中的讨论,在我看来,您的NullObject的行为与正常对象的0值非常相似。

我要做的实际上是使用0(或更有意义的默认值),如果您确实需要知道它是否已初始化,请放置一个标志。这样,您将需要考虑两件事:


所有未初始化的值都不会与我的解决方案共享同一实例
由于同样的原因,您现在可以稍后在不创建新实例的情况下初始化对象


这是我想到的那种代码:

public static class VerySimpleFoo implements Foo {
    private int value;
    private boolean initialized;

    public VerySimpleFoo() {
      this.value = 0; // whatever default value makes more sense
      this.initialized = false;
    }

    public VerySimpleFoo(int value) {
      this.value = value;
      this.initialized = true;
    }

    @Override
    public int compareTo(Foo f) {
      // possibly need some distinction here, depending on your default value
      // and the behavior you expect
      return Integer.valueOf(value).compareTo(f.getValue());
    }

    @Override
    public int getValue() {
      return value;
    }

    public void setValue(int value) {
      this.value = value;
      this.initialized = true;
    }

    public boolean isInitialized() {
      return initialized;
    }
}

10-04 10:32