代码之家  ›  专栏  ›  技术社区  ›  Charles Duffy

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

  •  8
  • Charles Duffy  · 技术社区  · 15 年前

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

    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 . 在这种情况下,我似乎需要添加 酒吧 .

    public class Bar<T> {
      private Foo<T> foo;
      private T t;
    
      /* ... */
    
      public void startStuff() {
        t = foo.getOne();
      }
    
      public void finishStuff() {
        foo.useOne(t);
      }
    }
    

    这有点奇怪,因为类型参数 T 不出现在的公共接口中 酒吧 (即,它不包含在任何方法参数或返回类型中)。有没有办法“量化” T “?”也就是说,我能安排参数吗 T 隐藏在 酒吧 ,如下所示?

    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 回复  |  直到 15 年前
        1
  •  4
  •   erickson    15 年前

    你的问题和 "capture helper", 但我不确定它是否可以应用到第二个例子中,其中使用了两个独立的方法。你第一 doStuff 方法可以更好地写成 public void doStuff(Foo<?> foo) ,因为不管 Foo 类型参数。然后,“捕获助手”模式会很有用。


    更新:在做了一点修改,扩展了戈茨的捕获助手的概念之后,我想到了这个。里面看起来有点乱;从外面看,你不会怀疑什么。

    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
  •  6
  •   Tom Hawtin - tackline    15 年前

    为了有用,在某个时刻你要设置 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();
        }
    }
    
        3
  •  1
  •   Jon Skeet    15 年前

    为什么不分三层:

    abstract class Foo
    
    abstract class FooImplBase<T> extends Foo
    
    class Bar extends FooImplBase<String>
    

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

    所以在你的例子中 startStuff() endStuff() 将是抽象的 并在 FooimplBase<t> . 这听起来像是在你的真实情况下工作吗?我同意这有点麻烦。

        4
  •  1
  •   Adam Wright    15 年前

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

    1)钢筋中不涉及参数类型。也就是说,foo和t成员只有一个类型,比如u,它是为定义而固定的。如果您通过了要分配给foo的foo,则它必须是foo<u>。如果这都是真的,那么是的,它不是公共接口的一部分——所有东西都有一个特定的类型。那么,我不确定你所说的“量化”是什么意思,因为没有自由类型的变量。如果你的意思是“普遍量化”,你如何调和这样一个事实:条形图没有参数类型,那么它的每个成员都必须有一个具体的类型?

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

        5
  •  0
  •   MartinStettner    15 年前

    必须在某个地方确定,您希望在bar类中使用什么类型的“t”。因此,要么您必须在BAR定义中选择它(在类定义中用foo替换foo),要么您将其留给BAR类的客户机:在这种情况下,必须将BAR设为泛型。

    如果你想有一个接口来禁止不依赖T 为了能够为T选择不同的类型,应该使用非泛型接口或抽象基类,如:

    interface Bar {
      void startStuff();
      // ...
    }
    
    class BarImplement<T> {
      private Foo<T> foo;
      // ...
    }
    
        6
  •  0
  •   Myer    15 年前

    如果你真的想画一些IRE,你可以把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);
        }
      }
    }
    
        7
  •  -2
  •   Varkhan    15 年前

    在这种情况下,对于bar来说,参数t是无用的,因为它将在编译时被擦除为对象。所以你也可以“省去麻烦”,尽早删除:

    public class Bar {
    
        private Foo<Object> foo;
        private Object t;
    
      ... 
    }