我想做以下事情:
public class ImmutableList<T> {
public <U super T> ImmutableList<U> add(U element) { ... }
}
也就是说,给定一个不可变列表 T
,您可以添加任何 U
到列表以产生一个不可变列表 U
,约束为 U
必须是 T
的父类(super class)型.例如
- 我可以将一只猴子添加到猴子列表中,生成一个新的猴子列表;
- 我可以将一个人添加到猴子列表中,从而生成一个新的原始人列表(大概是猴子和人类的最小上限);
- 我可以在原始人列表中添加一 block 石头,生成一个新列表
Object
(假设岩石和原始人没有其他共同祖先)。
这在理论上听起来不错,但下界是 U
根据 JLS 是不合法的。我可以改写:
public class ImmutableList<T> {
public ImmutableList<T> add(T element) { ... }
public static <U> ImmutableList<U> add(ImmutableList<? extends U> list, U element) { ... }
}
以这种方式,编译器将正确推断出列表元素类型与 U
之间的最小上限。我们要补充。这是合法的。它也很糟糕。比较:
// 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 方言。所以我有三个问题:
WTF? 为什么
<U super T>
绑定(bind)不合法?这个问题实际上以前在这里被问过 ("Why can't a Java type parameter have a lower bound?"),但我认为这个问题的措辞有点困惑,那里的答案归结为“它不够有用”,我真的不买账。JLS section 4.5.1状态:“与方法签名中声明的普通类型变量不同,使用通配符时不需要类型推断。因此,允许在通配符上声明下限。”给定替代方案
static
上面的方法签名,编译器显然能够推断出它需要的最小上限,所以这个论点似乎被打破了。有人可以反驳说它不是吗?最重要的是:有人能想到一个合法的替代方法来替代我的具有下限的方法签名吗?简而言之,目标是让调用代码看起来像 Java (
list.add(monkey)
) 而不是 Lisp (add(list, monkey)
)。
最佳答案
您几乎可以做到,但 Java 类型推断的糟糕程度足以让它不值得。
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。
关于java - 替代通用 Java 方法的非法下界?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10809234/