我在Java中有一套相当复杂的泛型类。例如,我有一个接口
interface Doable<X,Y> {
X doIt(Y y);
}
和实施
class DoableImpl implements Doable<Foo<Bar<Baz,Qux>>,Foo<Bar<Zot,Qux>>> {
Foo<Bar<Baz,Qux>> doIt(Foo<Bar<Zot,Qux>> fooBZQ) { ... }
}
在实际实施中,Doable
有很多方法,因此Foo<Bar<Baz,Qux>>
等会一遍又一遍地出现。
(信不信由你,通用类型比这更痛苦。我已经简化了它们的例子。)
我想简化这些,以节省自己的打字和缓解眼睛的压力。我想要的是为Foo<Bar<Baz,Qux>>
等提供一个简单的“类型别名”,比如FooBBQ
和FooBZQ
。
我目前的想法是定义包装类:
class FooBBQ {
public static FooBBQ valueOf(Foo<Bar<Baz,Qux>> fooBBQ) {
return new FooBBQ(fooBBQ);
}
private Foo<Bar<Baz,Qux>> fooBBQ;
private FooBBQ(Foo<Bar<Baz,Qux>> fooBBQ) {
this.fooBBQ = fooBBQ;
}
public Foo<Bar<Baz,Qux>> toGeneric() {
return fooBBQ;
}
}
class FooBZQ { /* pretty much the same... */ }
class DoableImpl implements Doable<FooBBQ,FooBZQ> {
FooBBQ doIt(FooBZQ fooBZQ) { ... }
}
这很有效,但它有一些缺点:
我们有调用valueOf
和toGeneric
以在FooBBQ
和Foo<Bar<Baz,Qux>>
之间进行转换的翻译开销(概念上,如果不是操作上)。例如,如果doIt
调用某个期望Foo<Bar<Zot,Qux>>
的库例程(实际实现的那个),我们最终会得到类似
return FooBBQ.valueOf( libraryCall( fooBZQ.toGeneric() ) )
我们最初的地方
return libraryCall(fooBZQ);
是否有其他方法可以获得我想要的“类型别名”行为?也许使用一些第三方宏工具集?或者我是否需要接受我将不得不进行大量的输入,一种方式(使用实现中的泛型类型)或另一种方式(为它们编写包装器)?也许有这么多通用参数飞来飞去只是一个坏主意,我需要重新思考这个问题吗?
[更新]好的,我禁止任何进一步的“不要那样做”的答案。假设Foo<Bar<Baz,Qux>>
在我的问题域中具有真正的价值(Pete Kirkham可能是正确的,它具有足够的值以获得具有描述性名称的正确包装类)。但这是一个编程问题;不要试图解决问题。
答案 0 :(得分:10)
如果你想要完全的类型安全,我认为如果没有某种包装类,你可以做得更好。但是,为什么不让这些类继承/实现原始的通用版本,如下所示:
public class FooBBQ extends Foo<Bar<Baz,Qux>> {
...
}
这消除了对toGeneric()
方法的需要,在我看来,它更清楚,它只是一个类型别名。此外,泛型类型可以在没有编译器警告的情况下强制转换为FooBBQ
。如果可能的话,我个人倾向于制作Foo, Bar, Baz...
接口,即使在实现中会出现一些代码重复。
现在,在不知道具体问题域的情况下,很难说你是否需要,比如FooBBQ
,就像在你的例子中,或者可能是:
public class FooBar<X, Y> extends Foo<Bar<X, Y>> {
...
}
另一方面,您是否考虑过简单地配置Java编译器以不显示某些通用警告,而只是省略通用定义的部分?或者,使用策略性地@SuppressWarnings("unchecked")
?换句话说,您是否可以DoableImpl
仅“部分通用化”:
class DoableImpl implements Doable<Foo<Bar>>,Foo<Bar>> {
Foo<Bar> doIt(Foo<Bar> foobar) { ... }
}
并忽略警告,以减少代码混乱?再说一次,如果没有一个具体的例子,很难做出决定,但这是你可以尝试的另一件事。
答案 1 :(得分:5)
Scala对类型别名有很好的支持。例如:
type FooBBQ = Foo[Bar[Baz,Qux]]
我意识到如果您没有切换到Scala的选项,这个答案将没有用。但如果您确实可以选择切换,那么您可能会有更轻松的时间。
答案 2 :(得分:2)
也许有这么多通用参数飞来飞去只是一个坏主意,我需要重新思考这个问题?
很可能。是否需要将8个维度的“Doit”专门化?
在很多情况下,这些类型并不存在于真空中,您应该考虑“包装器”所代表的域对象,而不是将它们用作编码方便。
答案 3 :(得分:1)
嗯,Java没有类型别名,所以你运气不好。但是,类型别名有时可以用类型变量替换!所以我们用更多的泛型解决了太多泛型的问题!
当您从示例中删除所有内容时,我无法猜测引入其他类型变量的位置或是否有意义,但这是一种可能的分解:
class DoableImplFoo<A,B> implements Doable<Foo<A>,Foo<B>> {
public DoableImplFoo(SomePropertyOf<A,B> aAndBAreGoodEnough) { ... }
Foo<A> doIt(Foo<B> fooB) { ... }
}
当您将A
实例化为Bar<Baz,Qux>
并B
再次Bar<Zot,Qux>
时,您可能会发现有一些样板,但它可能比您原来的更少
答案 4 :(得分:0)
我会说,是的,你需要重新思考这个问题。声明
class DoableImpl implements Doable<Foo<Bar<Baz,Qux>>,Foo<Bar<Zot,Qux>>> {
Foo<Bar<Baz,Qux>> doIt(Foo<Bar<Zot,Qux>> fooBZQ) { ... }
}
是一个非常明显的过度使用泛型的案例。