【问题标题】:Capturing method argument type and ask for corresponding class object捕获方法参数类型并请求相应的类对象
【发布时间】:2013-06-25 11:37:56
【问题描述】:

鉴于以下情况

class Base { }
class Extended extends Base {}

我可以很容易地在泛型类的构造函数中请求一个类对象,它是泛型类型的某个子类

class Test<T extends Base> {
  Test(Class<? extends T> test) { }
}

这样我就可以完成以下所有操作

new Test<Base>(Base.class);
new Test<Base>(Extended.class);
new Test<Extended>(Base.class); // This is not allowed by the compiler
new Test<Extended>(Extended.class);

这正是我想要的。但是,当使用这样的通用方法时,我无法弄清楚如何实现相同的约束:

<T extends Base> void test(T x, Class<? extends T> test) { }

使用此定义,编译器允许以下所有内容:

test(new Base(), Base.class);
test(new Base(), Extended.class);
test(new Extended(), Base.class); // even though Base is not a subclass of Extended
test(new Extended, Extended.class);

我认为这是因为类型推断和 Java 确定

<Base>test(new Extended(), Base.class)

而不是

<Extended>test(new Extended(), Base.class)

但是我怎样才能强制执行后一种推理方法呢?

感谢您的帮助!

给未来的读者:在这个问题的第一个版本中,我调用了BaseAExtendedB。我后来澄清了这个符号。但是,答案中使用了此表示法。

【问题讨论】:

  • 把第二个参数变成Class&lt;T&gt;?
  • 这将禁止test(a, B.class) 而不是test(b, A.class)
  • &lt;T extends A, U extends T&gt; void test(U x, Class&lt;? T&gt; test) { }?
  • 我不明白,我以为你想禁止 Class 参数是与第一个参数不同的类?
  • 为了澄清,请标出以上4种情况中的哪一种不应该编译。您需要让我们知道什么应该允许,什么不应该。

标签: java generics type-inference


【解决方案1】:

这是计算机科学中的一个问题,因此,can be solved by another level of indirection

<T extends A, U extends T> void test(T x, Class<U> test) {}

在这种情况下,我们使用一个类型变量(绑定到 A)来捕获对象参数的类型,并使用第二个(绑定到第一个)来约束类参数的类型。

如果我把这个例子写出来并提供给 javac (1.6.0_26),它会及时告诉我:

Test.java:14: cannot find symbol
symbol  : method test(B,java.lang.Class<A>)
location: class Test<T>
        test(b, A.class);
        ^
1 error

正如@JohnB 在他的评论中暗示的那样,你可以通过写作来解决这个问题:

this.<A, A>test(b, A.class);

或者使用原始类型。

【讨论】:

  • 这仍然允许test(b, A.class),因为它将是&lt;A,A&gt;
  • @JohnB:我的编译器不是这么说的。
  • 显示您调用它的代码行。如果指定&lt;B,B&gt;,则编译错误,如果指定&lt;A,A&gt;,则没有编译错误,因为bAA 扩展A
  • 这实际上是我的解决方案。但是,IntelliJ 告诉我这可以编译。非常感谢!
  • 啊哈。我使用 Eclipse,偶尔会发现 javac 和 Eclipse 在泛型的细节上存在分歧。听到 IntelliJ 也这样做,我并不感到惊讶。在这种情况下,javac 和 Eclipse 同意,所以我倾向于将 IntelliJ 确定为最薄弱的环节。
【解决方案2】:

好的,我将分两部分进行尝试。首先,我认为你不能做你想做的事,因为调用者总是可以将超类指定为泛型,然后将子类作为第一个参数传递。

其次,我认为尝试将此作为编译时检查是错误的方法,因为用户总是可以选择使用原始类型而忽略所有泛型。您应该只进行一次运行时检查并提供适当的文档。

【讨论】:

  • 我提供运行时检查。但是,我更喜欢尽可能在编译时验证这一点。
【解决方案3】:

泛型不是用来施加任意“限制”的。泛型仅用于类型安全,即避免不安全的强制转换。您提出的限制没有类型安全的理由。没有函数 test 在其中 test(new Base(), Base.class); 是类型安全的,但 test(new Extended(), Base.class); 不是。

【讨论】:

  • 我的应用程序函数的第一个参数本身是通用的,其中参数只能是同一类型。我需要知道这种类型,但由于类型擦除,这是不可能的。因此,它在某些场合确实有意义。不过,我不想做一个过于复杂的例子,并从我的问题中跳过了这部分。
  • @raphw:不,在任何可以安全使用Base 的地方,使用Extended 都是安全的。这就是继承的重点。再次,我挑战你找到一个函数test,其中test(new Base(), Base.class); 是类型安全的,但test(new Extended(), Base.class); 不是。
  • 在泛型类型中使用它们时。 Base 不是 Base 或 Extended 的超类。我的应用程序是这样定义的,我简化了示例。
  • @raphw: 同样,这与函数test的参数无关
【解决方案4】:

我相信答案是在第一种情况下您指定泛型类型:new Test&lt;B&gt;(...)。注意&lt;B&gt;。但是在第二种情况下,您没有指定泛型类型:test(a, A.class),因此您允许编译器查看它是否可以确定一个有效的泛型类型。对于test(b, A.class)&lt;A&gt; 有效,因为bA 的一个实例。

尝试类似:myInstance.&lt;B&gt;test(b, A.class)

【讨论】:

  • 问题是:我不希望除myInstance.&lt;B&gt;test(b, A.class) 之外的其他方法被调用,因为它会失败。我需要在对象上执行反射的类型。而且我自己不会调用该方法,它是由我的代码的用户调用的。
猜你喜欢
  • 2020-04-13
  • 2014-01-29
  • 2013-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-13
  • 2012-02-05
相关资源
最近更新 更多