拥有一个以自身为场的对象是否合理?

Is it ever justified to have an object which has itself as a field?

是否有理由拥有一个将自身作为字段的对象,如下所示:

class Thing {

    Thing field;

    public Thing() {
        this.field = this;
    }
}

我不是在谈论具有相同类型字段的 class,而是 class 使得 class 的每个实例都有自己作为字段。 我刚刚在一些遗留代码中看到了这个(这个字段从未使用过)所以我很好奇。有任何合法用途吗?

不,在我看来这是不合理的,因为每个对象隐含地已经有这样一个字段:this。当然,一个对象有一个有时但不总是引用自身的字段是合理的(例如,它可能出现在循环链表中),但问题是关于一个总是引用对象的字段本身。

我看到过代码,其中使用这样的字段能够从内部 类 引用来自(匿名)的包含对象,但在那种情况下也没有必要。您可以使用 ContainingClass.this。例如:

class A {
    class B {
        A getParent() {
            return A.this;
        }
    }
}

否,因为当它具有关键字 this

时,将其自身作为字段是多余的

Within an instance method or a constructor, this is a reference to the current object — the object whose method or constructor is being called. You can refer to any member of the current object from within an instance method or a constructor by using this." - Javadocs Keyword "this"

我至少能想到一个有道理的例子。例如

我有一个证书链,链中的每个 link 都有一个对其父级的引用。在链的顶部,最后一个证书是自签名的,所以它的父级就是它自己。

简而言之,这实际上取决于您建模的问题space。任何断言不应该这样做的断言都是缺乏想象力。

public class Cert {
    public Cert parent;
}

public class SelfSignedCert extends Cert {
    public SelfSignedCert() {
        this.parent = this;
    }
}

我能想到的唯一情况是某些类型的重构。举个例子,一个对象可能已经开始作为不是非常面向对象的静态方法的集合:

public static void doItToIt(Foo it) {
  if (it.getType().equals("bar")) {
    it.setSomeVariable(value);
    it.manipulate();
  } else {
    // do nothing
  }
}

...所以我们决定把getType()去掉,做成子类关系,重构的时候复制粘贴这个东西,让代码更干净更面向对象,需要:

public class Bar extends Foo {
    private Bar it = this;

    private String getType() {
      return "bar";
    }

    public void doItToIt() {
      if (it.getType().equals("bar")) {
        it.setSomeVariable(value);
        it.manipulate();
      } else {
        // do nothing
      }
    }
 }

如果只有一个这样的方法,最好使用局部变量:

public void doItToIt() {
  final Bar it = this;
  ...
}

但如果有多个,使用实例变量会使代码运行得更快。

现在最好将 it 的所有实例替换为 this(并删除 getType(),并删除 if,等等第四)但作为一个中间步骤,它仍然是一个改进,如果有(或被认为是)其他更重要的事情要做,它可能会留在那个状态。

另一种可能性是从规范之类的东西中复制伪代码,其中 this 在伪代码中有一个名称,并且您故意尝试匹配伪代码结构以使其易于将实现与规范进行比较。 (同样,如果您有多个方法,那么使用局部变量会导致重复。)

但一般来说,不,拥有一个 总是 引用 this 的实例变量是多余的,因此妨碍了清晰度。

是的,虽然这种情况很少见。这在 JDK 中用于字段可能是 this 但可能不是的情况。

来自实现 Collections.synchronizedCollection(c)

的 class
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
    private static final long serialVersionUID = 3053995032091335093L;

    final Collection<E> c;  // Backing Collection
    final Object mutex;     // Object on which to synchronize

    SynchronizedCollection(Collection<E> c) {
        this.c = Objects.requireNonNull(c);
        mutex = this;
    }

    SynchronizedCollection(Collection<E> c, Object mutex) {
        this.c = Objects.requireNonNull(c);
        this.mutex = Objects.requireNonNull(mutex);
    }

在这种情况下,mutex 可能是当前的 class,但是如果此集合是从现有的同步集合中获取的,互斥量可能会有所不同。例如如果您调用 Map.values() 并且 MapsynchronizedMap,则 mutex 将是地图而不是集合。


另一个例子是 Throwable,它默认指向自己作为原因。

/**
 * The throwable that caused this throwable to get thrown, or null if this
 * throwable was not caused by another throwable, or if the causative
 * throwable is unknown.  If this field is equal to this throwable itself,
 * it indicates that the cause of this throwable has not yet been
 * initialized.
 *
 * @serial
 * @since 1.4
 */
private Throwable cause = this;

如果引用的对象可以在之后改变,那是合理的,但是初始值是this(虽然这样的设计也应该重新考虑,因为它可能表明Thing已经更多的责任)。

但是,如果引用的对象总是this,那就没必要了,容易混淆。

下面的声明不仅令人困惑,而且很有趣:)

private final Thing thing = this;

JDK 中的另一个示例 - java.lang.Throwable

private Throwable cause = this;

cause 字段可以处于 3 种状态 - 未设置;设置为空;设置为另一个 Throwable。

实施者使用this表示unset状态。

一个更具可读性的策略可能是为unset

定义一个标记值
static Throwable UNSET = new Throwable();

private Throwable cause = UNSET;

当然,还有一个递归依赖 - UNSET.cause=? - 这是另一个有趣的话题。

在 iOS 中,对象通常有委托 - 通常是另一个对象,它可以提供数据,或者可以做使对象正常工作所需的其他事情,作为子类化的替代方法。如果对象本身实现了委托应实现的所有功能,则该对象可以是它自己的委托。不完全常见,但也不罕见。

假设您有一个 "boss",他有一个 "secretary"、一个 "coffee maker" 和一个 "driver"。老板显然可以自己写信,自己煮咖啡,自己开车,但不是每个老板都这样做。所以有时这些字段中的一个或多个会被设置为"this"。

库 UI 控件的反射案例。

它在某些使用模式下是完全合法的。在未公开的企业框架中,我看到 XML 像这个简化的例子:

<form>
    <comboBox listItems="order.LineItems" displayMember="description" valueMember="selfRef"/>
</form>

通过 Reflection 将控件绑定到值。如果您对 Integer IDvalueMember 感到满意,则不需要引用对象本身的 class 成员。但是如果你想引用对象本身并且通过设计,空值 ("") 没有作为特殊情况实现,意思是 this,那么你需要将它保存在一个成员变量中 (selfRef 在上面的例子中)。