Substitute for illegal lower bounds on a generic Java method?
我想做以下工作:
1 2 3 | public class ImmutableList<T> { public <U super T> ImmutableList<U> add(U element) { ... } } |
也就是说,给定一个不可变的
- 我可以在猴子列表中添加一只猴子,生成一个新的猴子列表;
- 我可以在猴子列表中添加一个人,生成一个新的原始人列表(大概是猴子和人类的最小上限);
- 我可以在原始人列表中添加一块岩石,生成一个新的
Object 列表(假设岩石和原始人没有其他共同祖先)。 世界跆拳道联盟?为什么
绑定不合法?这个问题以前在这里被问过:"为什么Java类型的参数不能有一个下限?"但我认为问题的措辞有点混乱,答案归结为"它不够有用",我并不真正相信。 JLS第4.5.1节指出:"与方法签名中声明的普通类型变量不同,使用通配符时不需要进行类型推断。因此,允许在通配符上声明下界。"考虑到上面的可选
static 方法签名,编译器显然能够推断出它需要的最小上界,因此该参数似乎已被破坏。有人能说不是吗?最重要的是:有人能想出一个合法的替代方法来代替我的下界方法签名吗?简而言之,目标是调用看起来像Java(EDCOX1(10))的代码,而不是LISP(EDCOX1(11))。
小精灵
这在理论上听起来很好,但是根据JLS,
1 2 3 4 | public class ImmutableList<T> { public ImmutableList<T> add(T element) { ... } public static <U> ImmutableList<U> add(ImmutableList<? extends U> list, U element) { ... } } |
号
以这种方式,编译器将正确地推断列表的元素类型和我们要添加的
1 2 3 4 5 | // if 'U super T' were legal list.add(monkey).add(human).add(rock); // assuming 'import static ImmutableList.add' add(add(add(list, monkey), human), rock); |
我是函数编程的狂热爱好者,但我不希望我的Java代码看起来像Lisp方言。所以我有三个问题:
你几乎可以做到这一点,但Java类型的推理设法吮吸只是足以使它不值得。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | public abstract class ImmutableList< T > { public interface Converter< U, V > { V convert( U u ); } public abstract < U > ImmutableList< U > map( Converter< ? super T, ? extends U > cnv ); public static class EmptyIL< T > extends ImmutableList< T >{ @Override public < U > EmptyIL< U > map( Converter< ? super T, ? extends U > cnv ) { return new EmptyIL< U >(); } } public static class NonEmptyIL< T > extends ImmutableList< T > { private final T tHead; private final ImmutableList< ? extends T > ltTail; public NonEmptyIL( T tHead, ImmutableList< ? extends T > ltTail ) { this.tHead = tHead; this.ltTail = ltTail; } @Override public < U > NonEmptyIL< U > map( Converter< ? super T, ? extends U > cnv ) { return new NonEmptyIL< U >( cnv.convert( tHead ), ltTail.map( cnv ) ); } } public < U > ImmutableList< U > add( U u, final Converter< ? super T, ? extends U > cnv ) { return new NonEmptyIL< U >( u, map( cnv ) ); } public static < V > Converter< V, V > id() { return new Converter< V, V >() { @Override public V convert( V u ) { return u; } }; } public static < W, U extends W, V extends W > Converter< W, W > sup( U u, V v ) { return id(); } static class Rock {} static class Hominid {} static class Human extends Hominid {} static class Monkey extends Hominid {} static class Chimpanzee extends Monkey {} public static void main( String[] args ) { Monkey monkey = new Monkey(); Human human = new Human(); Rock rock = new Rock(); // id() should suffice, but doesn't new EmptyIL< Chimpanzee >(). add( monkey, ImmutableList.< Monkey >id() ). add( human, ImmutableList.< Hominid >id() ). add( rock, ImmutableList.< Object >id() ); // sup() finds the supremum of the two arguments' types and creates an identity conversion // but we have to remember what we last added new EmptyIL< Chimpanzee >(). add( monkey, sup( monkey, monkey ) ). add( human, sup( monkey, human ) ). // add( human, sup( monkey, monkey ) ) also works add( rock, sup( human, rock ) ); } } |
您至少可以在编译时强制实现类型之间的可兑换性,作为额外的好处,您可以定义自己的转换器,而不仅仅是子类化。但是你不能让Java知道,在没有转换器的情况下,它应该使用适当的子类转换器作为默认值,这将把我们带到你的原始API。