【问题标题】:How to go from a wildcard type to a parameterized type?如何从通配符类型转到参数化类型?
【发布时间】:2018-05-21 17:48:32
【问题描述】:

假设有一个带有多个类型参数的框架类型Row,以及一个适用于Row 类型实例并使用所有这些类型参数的方法。

我有一个方法可以处理任何类型的Row,甚至可以同时处理不同的类型,所以很明显我使用的是通配符类型Row<?,?>。问题是,如何调用带有Row<R,K>Row<?,?> 的方法?

我的思路:我不完全知道Row<?,?> 是什么类型,但肯定是某种Row 好吧。当一个泛型方法采用Row<R,K> 时,它意味着它想对RK 做一些事情,否则它可以处理任何类型的Row。所以我的“any”类型应该与采用“any”类型的方法一起工作,对吧?

我在下面附上了我尝试过的示例代码。最奇怪的是最后一行确实有效,但它并不比我认为的其他任何东西都更安全。所以基本上我想要一个比这更干净的解决方案,或者解释为什么这是要走的路。

package foo;

public class Experiment {
  // Defined by a framework.
  interface Key<K extends Key<K>> {}

  interface Row<R extends Row<R, K>, K extends Key<K>> {}

  static <R extends Row<R, K>, K extends Key<K>> R copyRow(R row) {
    return row;
  }

  // My experiments below.
  static class Wrapper<R extends Row<R, K>, K extends Key<K>> {
    public final R row = null; // fixme
    public final Class<R> clazz = null; // fixme
  }

  static <R extends Row<R, K>, K extends Key<K>> R upcast(Row<?, ?> row) {
    return (R) row;
  }

  static <R extends Row<R, K>, K extends Key<K>> R upcast(Row<?, ?> row, Class<R> clazz) {
    assert row.getClass().equals(clazz);
    return (R) row;
  }

  public static void main(String[] args) {
    Wrapper<?, ?> wr = null; // fixme
    copyRow(wr.row); // Compilation error
    copyRow(upcast(wr.row)); // Compilation error
    copyRow(upcast(wr.row, wr.clazz)); // This works, why?
  }
}

(您可以将此示例直接发送到 javac 以查看会发生什么。使用 Java 1.8:https://pastebin.com/LB10ySsD

【问题讨论】:

  • 您应该能够将Row&lt;?, ?&gt; 传递给采用Row&lt;R, K&gt; 的泛型方法,其中RK 是泛型类型。我不确定是什么问题?你的行 copyRow(wr.row) 也适合我。
  • 当 R 和 K “相关”时不会。这就是我在评论说“错误”的地方得到的:pastebin.com/LB10ySsD
  • 我用的是openjdk的javac 1.8,你用什么编译这段代码?
  • 我使用的是 eclipse 4.7.3a,但看起来 javac 拒绝它直到版本 11。
  • 是的,IntelliJ 也不给我红色下划线,只有当我尝试编译它时。当我将静态助手移动到 Wrapper 中时,最后一行也不再起作用。 upcast() hack 必须在外部,在静态上下文中,然后最后一行工作......

标签: java generics types wildcard f-bounded-polymorphism


【解决方案1】:

这是一种可能性:

public class WildcardsExperiment {
  // Defined by a framework.  <begin unmodifiable>
  interface Key<K extends Key<K>> {}
  interface Row<R extends Row<R, K>, K extends Key<K>> {}

  static <R extends Row<R, K>, K extends Key<K>> R copyRow(R row) {
    return row;
  }
  // <end unmodifiable>

  interface NaturalRowTransformer {
    <R extends Row<R, K>, K extends Key<K>> R apply(R r);
  }

  class Wrapper<R extends Row<R, K>, K extends Key<K>> {
    private final R row = null; // fixme (constructor etc.)
    public final Class<R> clazz = null; // fixme

    R invokeNat(NaturalRowTransformer nat) {
      return nat.apply(row);
    }
  }

  static final NaturalRowTransformer CopyRow = new NaturalRowTransformer() {
    public <R extends Row<R, K>, K extends Key<K>> R apply(R row) {
      return copyRow(row);
    }
  };

  public static void main(String[] args) {
    Wrapper<?, ?> wr = null; // fixme
    wr.invokeNat(CopyRow); // compiles
  }
}

本质上,copyRow 方法被包装成一个访问者NaturalRowTransformer,这保证了它可以处理所有可能的 F 有界类型对 RK 的有效组合。然后Wrapper 提供了一个接受方法invokeNat,它接受访问者并在RK 是具体的(不是通配符)的范围内执行copyRow 操作。

该技巧受到类别理论的启发(因此名称中的“自然”),并从 Scala 导入,尽管当前 Scala 的类型模式匹配实现允许更简洁的解决方案。众所周知,此解决方案适用于 slightly more complex constraints

【讨论】:

  • 有趣的方法,虽然它看起来比我的更复杂。扩展多种方法需要做更多的工作。但我完全同意这完全避免了我的类型绑定问题。
  • @fejesjoco 它避免了所有类型转换。 new NatTrafo() {}; 恰好需要两行来包装每个多态方法,这有点样板,是的,但并不过分。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-30
相关资源
最近更新 更多