为什么Java类型参数不能有下限?

时间:2011-02-04 20:44:02

标签: java generics bounds

我认为你不能将Java泛型类型参数绑定到下限(即使用super关键字)。我正在阅读Angelika Langer Generics FAQ had to say on the subject。他们说这基本上归结为下限无用(“没有任何意义”)。

我不相信。我可以想象它们的用途可以帮助您更灵活地生成类型化结果的库方法的调用者。想象一个方法,它创建一个用户指定大小的数组列表,并用空字符串填充它。一个简单的声明就是

public static ArrayList<String> createArrayListFullOfEmptyStrings(int i);

但这对您的客户来说是不必要的限制。他们为什么不能这样调用你的方法:

//should compile
List<Object> l1 = createArrayListFullOfEmptyStrings(5); 
List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
List<String> l3 = createArrayListFullOfEmptyStrings(5);

//shouldn't compile
List<Integer> l4 = createArrayListFullOfEmptyStrings(5);

此时我很想尝试以下定义:

public static <T super String> List<T> createArrayListFullOfEmptyStrings(int size) {
  List<T> list = new ArrayList<T>(size);
  for(int i = 0; i < size; i++) {
     list.add("");
  }
  return list;
}

但它不会编译;在这种情况下,super关键字是非法的。

上面我的例子是一个不好的例子(忽略我在下面说的话)?为什么这里没有下限?如果它有用,那么在Java中不允许它的真正原因是什么?

P.S。

我知道更好的组织可能是这样的:

public static void populateListWithEmptyStrings(List<? super String> list, int size);

List<CharSequence> list = new ArrayList<CharSequence>();
populateListWithEmptyStrings(list, 5);

我们为了这个问题的目的可以假装由于一个要求,我们需要在一个方法调用中做两个操作吗?

修改

@Tom G(有理由)询问List<CharSequence>List<String>的好处是什么。首先,没有人说返回的列表是不可变的,所以这里有一个优点:

List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);
l2.add(new StringBuilder("foo").append("bar"));

6 个答案:

答案 0 :(得分:15)

基本上,它不够用。

我认为您的示例指出了下限的唯一优势,常见问题解答称之为Restricted Instantiation

  

最重要的是:所有“超级” 绑定将会为您带来 的是 限制 只有数字的超类型可以用作 类型参数 。 ....

但正如其他帖子指出的那样,即使这个功能的用处也是有限的。

由于多态性和特化的性质,上界比下边界更有用,如FAQ(访问非静态成员类型擦除所述) )。我怀疑下界带来的复杂性不值得它有限的价值。


OP:我想补充一下,我觉得你确实表明它很有用,只是没用。想出无可辩驳的杀手级用例,我将支持JSR。 : - )

答案 1 :(得分:11)

规范确实讨论了类型参数的下限,例如

  

4.10.2

     

类型变量是其下限的直接超类型。

     

5.1.10

     

一个新的类型变量......其下限

如果通配符捕获导致类型变量是合成的,那么它似乎只有一个(非空)下界。如果语言允许所有类型参数的下限,该怎么办?可能它不会造成很多麻烦,而且只是为了保持泛型更简单(嗯......)更新,据说对下界类型参数的理论研究并未彻底进行。< / p>

更新:一篇声称下限的论文没问题:“Java类型推断是否破裂:我们能否修复它”作者:Daniel Smith

RETRACT:以下参数错误。 OP的例子是合法的。

你的具体例子不是很有说服力。首先,它不是类型安全的。返回的列表确实是List<String>,将其视为另一种类型是不安全的。假设您的代码编译:

    List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);

然后我们可以添加非String,这是错误的

    CharSequence chars = new StringBuilder();
    l2.add(chars); 

List<String>不是,但有点像CharSequence列表。您的需求可以通过使用通配符来解决:

public static  List<String> createArrayListFullOfEmptyStrings(int size)  

// a list of some specific subtype of CharSequence 
List<? extends CharSequence> l2 = createArrayListFullOfEmptyStrings(5);

// legal. can retrieve elements as CharSequence
CharSequence chars = l2.get(0);

// illegal, won't compile. cannot insert elements as CharSequence
l2.add(new StringBuilder());

答案 2 :(得分:1)

这不仅仅是一个答案,这是另一个(可能是杀手?)用例。 我有一个ModelDecorator帮助器。我希望它具有以下公共API

"false"

因此,给定类A,B扩展A,它可以像这样使用:

class ModelDecorator<T>{
    public static <T> ModelDecorator<T> create(Class<T> clazz);
    public <SUPER> T from(SUPER fromInstance);
}

但我希望对T和SUPER有界限,所以我确保只使用API​​实例化子块。此刻,我可以做到:

A a = new A();
B b = ModelDecorator.create(B.class).from(a);

其中B不从C继承。

显然,如果我能做到:

C c = new C();
B b = ModelDecorator.create(B.class).from(c);

这可以解决我的问题。

答案 3 :(得分:0)

键入List会给你带来什么好处?当您遍历返回的集合时,您仍应该能够执行以下操作:

for(String s : returnedList) {
CharSequence cs = s;
//do something with your CharSequence
}

答案 4 :(得分:0)

编辑:我带来了一个好消息。有一种方法可以使您获得所需的大部分东西。

public static <R extends List<? super String>> R createListFullOfEmptyString(IntFunction<R> creator, int size)
{
  R list = creator.apply(size);
  for (int i = 0; i < size; i++)
  {
    list.add("");
  }
  return list;
}

// compiles
List<Object> l1 = createListFullOfEmptyString(ArrayList::new, 5);
List<CharSequence> l2 = createListFullOfEmptyString(ArrayList::new, 5);
List<String> l3 = createListFullOfEmptyString(ArrayList::new, 5);
// doesn't compile
List<Integer> l4 = createListFullOfEmptyString(ArrayList::new, 5);

缺点是客户确实需要提供R的一个实例以进行突变,或提供一些构造R的方法。没有其他方法可以安全地构造它。

我将在下面保留原始答案,以供参考。


总结:

没有充分的理由,只是没有完成。

并且直到现在为止,对于执行所有操作的方法,不可能编写具有正确方差的精确类型:

A)接受或创建参数化数据结构

B)将计算的(未传入)值写入该数据结构

C)返回该数据结构

写入/接受值恰好是应用协方差的情况,这意味着数据结构上的类型参数必须由要写入数据结构的值的类型下限。目前在Java中表达此信息的唯一方法是在数据结构上使用下界通配符,例如列表<?超级T>。


如果我们正在设计诸如OP之类的API,则自然(但不合法)可以表示为:

// T is the type of the value(s) being computed and written to the data structure

// Method creates the data structure
<S super T> Container<S> create()

// Method writes to the data structure
<S super T> Container<S> write(Container<S> container)

那么我们可以使用的选项是:

A)使用下界通配符,并强制调用方强制输出:

// This one is actually useless - there is no type the caller can cast to that is both read- and write-safe.
Container<? super T> create()

// Caller must cast result to the same type they passed in.
Container<? super T> write(Container<? super T> container)

B)过度限制数据结构上的类型参数以匹配要写入的值的类型,并强制调用方强制转换输入输出:

// Caller must accept as-is; cannot write values of type S (S super T) into the result.
Container<T> create()

// Caller must cast Container<S> (S super T) to Container<T> before calling, then cast the result back to Container<S>.
Container<T> write(Container<T> container)

C)使用一个新的类型参数,并在内部进行我们自己的不安全转换:

// Caller must ensure S is a supertype of T - we cast T to S internally!
<S> Container<S> create()

// Caller must ensure S is a supertype of T - we cast T to S internally!
<S> Container<S> write(Container<S> container)

选择你的毒药。

答案 5 :(得分:-1)

嗯,好的 - 让我们一起工作吧。您定义了一个方法:

public static <T super String> List<T> createArrayListFullOfEmptyStrings(int size) {

这是什么意思?这意味着如果我调用你的方法,那么我会得到一些超类String的列表。也许它返回一个String列表。也许它返回一个Object列表。我不知道。

冷却。

List<Object> l1 = createArrayListFullOfEmptyStrings(5);

据你说,那应该编译。但那不对!我可以将一个Integer放入Object - l1.add(3)列表中。但是如果你要返回一个String列表,那么这样做应该是非法的。

List<String> l3 = createArrayListFullOfEmptyStrings(5);

据你说,那应该编译。但那不对! l3.get(1)应该总是返回一个String ...但是该方法可能返回了一个Object列表,这意味着l3.get(1)可以想象成为一个Integer。

唯一有效的是

List<? super String> l5 = createArrayListFullOfEmptyStrings(5);

我所知道的是,我可以安全地拨打l4.put("foo"),我可以安全地获得Object o = l4.get(2)