我正在使用带有方法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
几乎相同的版本?
类型TypeA
到TypeC
不共享公共接口,由于它们在外部库中,因此我无法控制它们或foo
。我所能控制的就是实现bar
的方式。
答案 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
内的分支机构bar
,TypeD
,...,TypeE
一起使用,则不必深入研究TypeZ
的代码,您可以简单地提供另一个类型类实现bar
,TypeD
等一起使用,则他们可以提供另一种类型类实施,而不必分叉您的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
,则类型区分不再被编码。 FOO
和fooOf
可以重复使用。如果库发生了更改,并且引入了额外的重载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)