52

我认为您不能将 Java 泛型类型参数绑定到下限(即使用super关键字)。我正在阅读Angelika Langer Generics 常见问题解答关于该主题的内容。他们说这基本上归结为无用的下限(“没有任何意义”)。

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

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 中不允许它的真正原因是什么?

附言

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

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

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

出于这个问题的目的,我们可以假设由于需求,我们需要在一个方法调用中执行这两项操作吗?

编辑

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

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

6 回答 6

15

基本上,它还不够有用。

我认为您的示例指出了下限的唯一优势,即常见问题解答调用的功能Restricted Instantiation

底线是:“超级”绑定会给您带来的限制是只有 Number 的超类型可以用作类型参数。……

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

由于多态性和专业化的性质,上限比常见问题解答(访问非静态成员类型擦除)中描述的下限更有用。我怀疑下限引入的复杂性不值得其有限的价值。


OP:我想补充一点,我认为你确实表明它很有用,只是不够有用。提出无可辩驳的杀手级用例,我将支持 JSR。:-)

于 2011-02-04T21:20:37.573 回答
10

规范确实谈到了类型参数的下限,例如

4.10.2

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

5.1.10

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

如果作为通配符捕获的结果,类型变量是合成变量,则类型变量似乎只有一个(非空)下限。如果语言允许所有类型参数的下限怎么办?可能它不会造成很多麻烦,并且它只是为了保持泛型更简单而被排除在外(嗯......)更新据说没有彻底进行下界类型参数的理论研究。

更新:一篇声称下限没问题的论文:Daniel Smith 的“Java 类型推断被破坏:我们可以修复它吗”

RETRACT:以下论点是错误的。OP的例子是合法的。

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

    List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);

然后我们可以添加非字符串,这是错误的

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

嗯 aList<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());
于 2011-02-04T23:49:09.610 回答
1

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

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

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

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

但是我想限制 T 和 SUPER,所以我确保只有子类可以使用 API 实例化。此时,我可以这样做:

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

其中 B 不继承自 C。

显然,如果我能做到:

    public <SUPER super T> T from(SUPER fromInstance);

那将解决我的问题。

于 2017-02-07T07:46:29.957 回答
0

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

for(String s : returnedList) {
CharSequence cs = s;
//do something with your CharSequence
}
于 2011-02-04T20:47:16.573 回答
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 中表达这一点的唯一方法是在数据结构上使用下界通配符,例如 List<? 超级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)

选择你的毒药。

于 2019-07-14T04:43:55.023 回答
-2

嗯,好吧 - 让我们来解决这个问题。你定义一个方法:

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

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

凉爽的。

List<Object> l1 = createArrayListFullOfEmptyStrings(5);

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

List<String> l3 = createArrayListFullOfEmptyStrings(5);

据你说,那应该编译。但这是不对的!l3.get(1)应该总是返回一个字符串......但该方法可能返回了一个对象列表,这意味着 l3.get(1) 可能是一个整数。

唯一有效的是

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

我所知道的是,我可以安全地打电话l4.put("foo"),也可以安全地接听电话Object o = l4.get(2)

于 2011-02-16T08:00:55.220 回答