【问题标题】:Passing an argument that could be of multiple types to a method in Java?将可能是多种类型的参数传递给Java中的方法?
【发布时间】:2019-04-23 08:11:15
【问题描述】:

我正在使用带有 foo 方法的库,其签名如下:

TypeR foo(TypeA first, int second)
TypeR foo(TypeB first, int second)
TypeR foo(TypeC first, int second)

我正在编写自己的方法bar调用foo,我想直接将第一个参数传递给foo

bar(??? first, int baz) {
    int second = someCalculation(baz);
    return foo(first, second);
}

我的问题是我不知道应该给bar 中的参数first 赋予什么类型。 Java 中似乎没有联合类型。我怎样才能解决这个问题,而不必编写三个几乎相同的bar 版本?

TypeATypeC 类型不共享公共接口,我无法控制它们或 foo,因为它们位于外部库中。我能控制的只是我如何实现bar

【问题讨论】:

  • 如果 TypeA、TypeB 和 TypeC 都扩展了一个公共基类或实现了一个公共接口,那么就按照“那个”类型来编写你的方法。否则,您可以使用'Generic Type'

标签: java methods types


【解决方案1】:

我能想到的最接近的是这样的(删除与second 相关的内容,因为它无关紧要)。

TypeR bar(Object first) {
  TypeR retvalue;
  if (first instanceof TypeA)
    retvalue = foo( (TypeA)first );
  else if (first instanceof TypeB)
    retvalue = foo( (TypeB)first );
  else if (first instanceof TypeC)
    retvalue = foo( (TypeC)first );
  return retvalue;
}

如果有更具体的已知超类型 TypeA 等,您显然可以使用它而不是 Object 作为参数。

这只有在foo 的所有三个版本都具有相同的返回类型时才有可能。

遗憾的是,这个“解决方案”并不比编写三个 bar 的重载版本更好,甚至可能更糟。

【讨论】:

  • 我对什么是“良好实践”的笼统陈述并不特别感兴趣。如果你真的这么认为,也许你可以解释为什么。无论如何,我只是发布了我能想到的唯一一种接近发帖人要求的技术。
  • @SotiriosDelimanolis 也许我误解了你的评论,但我想我可能没有解释。由于 OP 没有指定函数的返回类型,我只是在示例代码中使用 T 作为占位符。实际上,我会将其替换为 int 或任何适当的返回类型。我并不是要暗示编译器会自己找出返回类型。
  • +1 既指向解决方案,又强调重载可能会更好。我更新了问题以澄清foo 的所有版本共享相同的返回类型。
  • @BraulioLopez 我很想参考为什么使用instanceof 是不好的做法。如果使用您无法控制类层次结构的第 3 方 API,并且这些 API 可能是在 Java 泛型出现之前编写的,使用 instanceof 似乎是完全合理的。
  • @Mike 因为代码将与库中的类型耦合,那么如果我们想更改库或测试我们的代码,将很难做到。例如,让我们认为这些类型是连接到远程服务器的方式,并且库提供 ssh 和 ftp 连接为types。如果我们想测试这段代码,我们需要一个真实的连接来满足这些类型。所以我更喜欢 Andrey Tyukin 的回答,即隔离每种类型,然后它们可以很容易地被我们将来想要的任何东西替换。
【解决方案2】:

理论上,没有什么能阻止您在 Java 中使用类型类来实现即席多态:

interface Fooable<T> {
    int foo(T t, int second);
}

public int bar<T>(T t, int baz, Fooable<T> fooable) {
    int second = someCalculation(baz);
    return fooable.foo(t, second);
}

实现大致如下:

public class TypeAFooable implements Fooable<TypeA> {
    public int foo(TypeA t, int second) { return yourLibrary.foo(t, second); }
}

调用看起来像

bar(myTypeAThing, 1234, new TypeAFooable());

如果bar 再长一点,这将带来几个好处:

  • 如果bar 在很多不同的地方多次调用foo,那么您可以定义类型类的每个实例一次,而不是在其中包含多个if-instanceof-分支bar
  • 如果你以后想让它与TypeDTypeE、...、TypeZ一起工作,你不必深入研究bar的代码,你可以简单地提供另一个类型类实现
  • 更重要的是:如果其他人想将它与TypeDTypeE 等一起使用,那么他们可以提供另一个类型类实现,而无需分叉你的bar 实现。

实际上,问题在于编译器不会自动提供类型类实例,因此通常不值得麻烦。除非bar 有点庞大且超级复杂,否则只需超载它,或使用instanceof

【讨论】:

    【解决方案3】:

    您可以先定义为generic type 输入栏,如下所示。

    bar(Object first, int baz) {
    int second = someCalculation(baz);
    if(first instanceOf TypeA) return foo((TypeA) first, second);
    else if(first instanceOf TypeB) return foo((TypeB) first, second);
    else if(first instanceOf TypeC) return foo((TypeC) first, second);
    else throw new CustomException("Object pass in is not of correct type");
    }
    

    【讨论】:

      【解决方案4】:

      我会尝试通过将bar() 函数分解为处理计算int second 的部分和处理TypeA/B/C first 的部分来避免这个问题。这样你就可以写出类似的东西

      int second = decomposedPortion(baz);
      TypeR result = foo(first, second);
      

      【讨论】:

        【解决方案5】:

        我们可以将bar 中的foo 调用替换为对应的BiFunction 调用。因此,对于所有重载的 foo 方法,必须定义一个 BiFunction

          private static final Map<Class<?>, BiFunction<?, Integer, TypeR>> FOO = Map.of(
            TypeA.class, (first, second) -> foo((TypeA) first, second),
            TypeB.class, (first, second) -> foo((TypeB) first, second),
            TypeC.class, (first, second) -> foo((TypeC) first, second));
        

        但是对于每个重载的foo 映射,只需编写一行,而每个重载的bar 方法如果按照问题中的方式编写则需要四行。

        现在我们可以将bar 设为通用。但是下面的方法不起作用,因为返回的BiFunction 有一个未绑定的通配符作为第一个类型参数。

          <T> TypeR bar(T first, int baz) {
            int second = someCalculation(baz);
            return FOO.get(first.getClass()).apply(first, second);
          }
        

        我们需要将FOO 声明为Map&lt;Class&lt;T&gt;, BiFunction&lt;T, Integer, TypeR&gt;&gt;,但这是不可能的。为了解决这个问题,我们定义了一个带有类型参数T 的方法,它应该建立缺少的类型相等性。但这不是免费的。这是以强制转换导致的警告为代价的:

          private static <T> BiFunction<T, Integer, TypeR> fooOf(Object o) {
            return (BiFunction<T, Integer, TypeR>) FOO.get(o.getClass());
          }
        

        现在我们可以在bar中使用这个方法了:

          <T> TypeR bar(T first, int baz) {
            int second = someCalculation(baz);
            return fooOf(first).apply(first, second);
          }
        

        这类似于instanceof 方法,但将类型区别从方法bar 替换为Map FOO。如果有除bar以外的其他方法也需要以类似方式调用foo,则不再编码类型区分。 FOOfooOf 可以重复使用。如果库发生变化并引入了额外的重载TypeR foo(TypeD first, int second),则只需添加一行即可更新FOO

        这种方法要求first 不是null

        【讨论】:

          【解决方案6】:

          使用这样的接口:

          interface Type { //...}
          
          class TypeA implements Type { //...}
          
          class TypeB implements Type { //...}
          
          class TypeC implements Type { //...}
          

          那么你只需要一个 foo 函数

          foo(Type first, int second)

          一栏功能

          bar(Type first, int baz)

          【讨论】:

          • 是的,如果发帖人可以选择,这将是一个很好的设计方法。不幸的是,问题指出foo() 方法在库中。
          • 它们没有实现通用接口,我无法控制typeAtypeCfoo
          • 你如何决定必须使用哪种类型来调用 foo?
          • 我不明白你的问题?
          猜你喜欢
          • 1970-01-01
          • 2018-12-16
          • 2011-01-16
          • 1970-01-01
          • 2018-03-26
          • 1970-01-01
          • 1970-01-01
          • 2013-01-28
          相关资源
          最近更新 更多