将可能具有多种类型的参数传递给Java中的方法?

时间:2018-11-20 19:49:32

标签: java methods types

我正在使用带有方法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);
}

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

类型TypeATypeC不共享公共接口,由于它们在外部库中,因此我无法控制它们或foo。我所能控制的就是实现bar的方式。

6 个答案:

答案 0 :(得分:4)

我能想到的最接近的东西是这样的(删除与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的三个重载版本相比,此“解决方案”并没有真正更好,甚至更糟。

答案 1 :(得分:3)

理论上,没有什么可以阻止您将类型类用于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进行了很多调用,那么您可以定义类型类一次的每个实例,而不用多个{{1 }} if-instanceof内的分支机构
  • 如果您以后想使其与barTypeD,...,TypeE一起使用,则不必深入研究TypeZ的代码,您可以简单地提供另一个类型类实现
  • 更重要的是:如果其他人想将其与barTypeD等一起使用,则他们可以提供另一种类型类实施,而不必分叉您的TypeE实施。

实际上,问题在于编译器不会自动提供typeclass实例,因此通常不值得麻烦。除非bar巨大且超级复杂,否则请重载它,或使用bar

答案 2 :(得分:1)

您可以首先在栏中定义为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");
}

答案 3 :(得分:1)

我会尝试通过将您的bar()函数分解为处理int second的部分以及处理TypeA / B / { {1}}。这样,您可以编写类似

的内容
C first

答案 4 :(得分:1)

我们可以用对应的foo的调用来代替bar中的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<Class<T>, BiFunction<T, Integer, TypeR>>,但这是不可能的。要解决此问题,我们定义一个带有类型参数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

答案 5 :(得分:-1)

使用类似这样的界面:

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)