9

假设我正在使用带有泛型类型参数的接口

interface Foo<T> {
  T getOne();
  void useOne(T t);
}

目的是该类型T是抽象的:它对 的实现实施类型约束Foo,但客户端代码并不关心究竟是什么T

这在泛型方法的上下文中没有问题:

public <T> void doStuff(Foo<T> foo) {
  T t = foo.getOne();
  /* do stuff */
  foo.useOne(t);
}

但是假设我想分解工作doStuff,在一个类中保存一些状态Bar。在这种情况下,我似乎需要添加 to 的类型Foo参数Bar

public class Bar<T> {
  private Foo<T> foo;
  private T t;

  /* ... */

  public void startStuff() {
    t = foo.getOne();
  }

  public void finishStuff() {
    foo.useOne(t);
  }
}

这有点奇怪,因为类型参数T没有出现在的公共接口中Bar(即它不包含在任何方法参数或返回类型中)。有没有办法“量化T”?即,我是否可以将参数T隐藏在 的界面中Bar,如下所示?

public class Bar {
  <T> { // foo and t have to use the same T
    private Foo<T> foo;
    private T t;
  } // T is out of scope
  ... 
}
4

7 回答 7

6

为了有用,在某些时候您将设置该foo字段。那时你应该知道(或能够捕捉到)T。我建议在构造函数中这样做,然后Bar有一个通用参数是有意义的。您甚至可以使用接口,这样客户端代码就不必查看类型。但是,我假设您不会接受我的建议,并且真的想要一个setFoo. 因此,只需为可切换实现添加一点:

/* pp */ class final BarImpl<T> {
    private final Foo<T> foo;
    private T t;

    BarImpl(Foo<T> foo) {
        this.foo = foo;
    }

    public void startStuff() {
        t = foo.getOne();
    }

    public void finishStuff() {
        foo.useOne(t);
    }
}

public final class Bar {
    private BarImpl<?> impl;

    /* ... */

    // Need to capture this wildcard, because constructors suck (pre-JDK7?).
    public void setFoo(Foo<?> foo) {
        setFooImpl(foo);
    }
    private <T> void setFooImpl(Foo<T> foo) {
        impl = new BarImpl<T>(foo);
    }

    public void startStuff() {
        impl.startStuff();
    }

    public void finishStuff() {
        impl.finishStuff();
    }
}
于 2009-03-30T23:43:46.557 回答
4

您的问题类似于“捕获助手”解决的问题,但我不确定它是否可以应用于使用两种单独方法的第二个示例。您的第一个doStuff方法肯定可以更好地写为,因为它与类型参数public void doStuff(Foo<?> foo)无关。Foo然后,“捕获助手”模式将很有用。


更新:经过一番修改,扩展了 Goetz 的捕获助手的想法,我想出了这个。里面,看起来有点乱;从外面看,你不会怀疑任何事情。

public class Bar {
  private final Helper<?> helper;
  public Bar(Foo<?> foo) {
    this.helper = Helper.create(foo);
  }
  public void startStuff() {
    helper.startStuff();
  }
  public void finishStuff() {
    helper.finishStuff();
  }
  private static class Helper<T> {
    private final Foo<T> foo;
    private T t;
    private Helper(Foo<T> foo) {
      this.foo = foo;
    }
    static <T> Helper<T> create(Foo<T> foo) {
      return new Helper<T>(foo);
    }
    void startStuff() {
      t = foo.getOne();
    }
    void finishStuff() {
      foo.useOne(t);
    }
  }
}
于 2009-03-30T23:19:24.993 回答
1

为什么不采用三层结构:

abstract class Foo

abstract class FooImplBase<T> extends Foo

class Bar extends FooImplBase<String>

客户只知道Foo,它不包含任何通用方法。引入您需要的任何通用方法FooImplBase<T>,然后从它派生具体类。

因此,在您的示例中startStuff(),并且endStuff()FooFooImplBase<T>. 这听起来像是在您的实际情况下可行吗?我同意这有点麻烦。

于 2009-03-30T23:15:36.390 回答
1

您正在定义 Bar 类。有两件事是真的...

1) Bar 中不涉及参数类型。也就是说, foo 和 t 成员只有一个类型,比如 U,它对于定义是固定的。如果你传递了一个你希望分配给 foo 的 Foo,它必须是一个 Foo<U>。如果这一切都是真的,那么是的,它不是公共接口的一部分 - 一切都有特定的类型。然后,我不确定您所说的“量化”是什么意思,因为没有自由类型变量。如果您的意思是普遍量化,您如何协调 Bar 没有参数类型的事实,因此必须为它的每个成员指定一个具体类型?

2) Bar 中涉及参数类型。它可能在公共接口中并不明显,但也许您传入了一个 Foo<T>,因此您希望使用多个单一类型实例化一个 Bar 类。然后,如前所述,这是在公共接口中,您需要使用泛型在 T 中使 Bar 参数化。这为您提供了某种形式的通用量化定义,“对于所有类型 T,此定义都是正确的”。

于 2009-03-30T23:16:55.670 回答
0

必须在某个地方决定,你想在 Bar 类中为“T”使用什么类型。因此,要么您必须在 Bar 的定义中选择它(在类定义中将 Foo 替换为 Foo),要么将其留给 Bar 类的客户端:在这种情况下, Bar 必须是通用的。

如果你想要一个不依赖于 T 的 Bar 接口并且能够为 T 选择不同的类型,你应该使用非泛型接口或抽象基类,如下所示:

interface Bar {
  void startStuff();
  // ...
}

class BarImplement<T> {
  private Foo<T> foo;
  // ...
}
于 2009-03-30T23:55:46.170 回答
0

如果你真的想惹人生气,你可以把 Bar 类放在 Foo 接口中,然后吸那个 T。有关接口内部类的更多信息,请参阅本文。也许这是有意义的一种情况?

interface Foo<T> {
  T getOne();
  void useOne(T t);

  public class Bar<T> {
    private Foo<T> foo;
    private T t;
    public void startStuff() {
      t = foo.getOne();
    }
    public void finishStuff() {
      foo.useOne(t);
    }
  }
}
于 2009-03-31T04:11:56.927 回答
-2

在这种情况下,参数 T 就 Bar 而言变得无用,因为它将在编译时被擦除为 Object。因此,您也可以“省去麻烦”,并尽早进行擦除:

public class Bar {

    private Foo<Object> foo;
    private Object t;

  ... 
}
于 2009-03-30T23:18:58.193 回答