假设我使用的是具有泛型类型参数的接口
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
中保存一些状态。在这种情况下,我似乎需要将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
...
}
答案 0 :(得分: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();
}
}
答案 1 :(得分:4)
您的问题类似于"capture helper",解决的问题,但我不确定它是否可以应用于您使用两个单独方法的第二个示例。您的第一个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);
}
}
}
答案 2 :(得分:1)
为什么不具有三层层次结构:
abstract class Foo
abstract class FooImplBase<T> extends Foo
class Bar extends FooImplBase<String>
客户只知道Foo
,它不包含任何通用方法。在FooImplBase<T>
中引入您需要的任何通用方法,然后具体类派生自它。
因此,在您的示例中startStuff()
和endStuff()
在Foo
中是抽象的,并在FooImplBase<T>
中实现。听起来它可能适用于您的实际情况吗?我同意这有点麻烦。
答案 3 :(得分:1)
您正在定义Bar类。有两件事是真的......
1)Bar中没有涉及参数类型。也就是说,foo和t成员只有一个类型,比如U,它是为定义修复的。如果你传递了一个你期望分配给foo的Foo,它必须是Foo&lt; U&gt;。如果这一切都是真的,那么是的,它不是公共接口的一部分 - 一切都有特定的类型。然后,我不确定你的意思是“量化”,因为没有自由类型变量。如果你的意思是普遍量化,你如何调和Bar没有参数化类型的事实,那么每个成员都必须给出具体的类型吗?
2)Bar中涉及参数类型。它可能不是显而易见的公共接口,但也许您传入Foo&lt; T&gt;,因此您希望使用多种类型实例化Bar类。然后,如上所述,这是在公共界面中,您需要在T中使用泛型进行参数化。这为定义提供了某种形式的通用量化,“对于所有类型T,这个定义都是正确的。”
答案 4 :(得分:0)
某处必须决定,你想在Bar类中使用什么类型的'T'。所以你要么必须在Bar的定义中选择它(在类定义中用Foo替换Foo),要么将它留给Bar类的客户端:在这种情况下,Bar必须是通用的。
如果你想让Bar的接口不依赖于T 和能够为T选择不同的类型,你应该使用非泛型接口或抽象基类,如:
interface Bar {
void startStuff();
// ...
}
class BarImplement<T> {
private Foo<T> foo;
// ...
}
答案 5 :(得分:0)
如果你真的想绘制一些愤怒,你可以将Bar类放在Foo界面中,并以这种方式吸取T.有关接口内类的更多信息,请参阅this article。也许这是一个有意义的案例?
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);
}
}
}
答案 6 :(得分:-2)
就Bar而言,在这种情况下参数T变得无用,因为它将在编译时被擦除为Object。所以你也可以“免除麻烦”,并尽早进行删除:
public class Bar {
private Foo<Object> foo;
private Object t;
...
}