在Java中隐藏“本地”类型参数

时间:2009-03-30 23:07:02

标签: java generics adts

假设我使用的是具有泛型类型参数的接口

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
  ... 
}

7 个答案:

答案 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;

  ... 
}