简体   繁体   English

为什么通用类型参数在 Java 中不能有下限?

[英]Why can't a Generic Type Parameter have a lower bound in Java?

I gather that you cannot bind a Java generics type parameter to a lower bound (ie using the super keyword).我了解到您不能将 Java 泛型类型参数绑定到下限(即使用super关键字)。 I was reading what the Angelika Langer Generics FAQ had to say on the subject .我正在阅读Angelika Langer Generics FAQ 关于这个主题的内容 They say it basically comes down to a lower bound being useless ("not making any sense").他们说这基本上归结为无用的下限(“没有任何意义”)。

I'm not convinced.我不相信。 I can imagine a use for them to help you be more flexible to callers of a library method that produces a typed result.我可以想象它们的用途是帮助您更灵活地调用生成类型化结果的库方法。 Imagine a method that created an array list of a user-specified size and filled it with the empty string.想象一个创建用户指定大小的数组列表并用空字符串填充它的方法。 A simple declaration would be一个简单的声明是

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

But that's unnecessarily restrictive to your clients.但这对您的客户来说是不必要的限制。 Why can't they invoke your method like this:为什么他们不能像这样调用你的方法:

//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);

At this point I would be tempted to try the following definition:在这一点上,我很想尝试以下定义:

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;
}

But it will not compile;但它不会编译; the super keyword is illegal in this context. super关键字在这种情况下是非法的。

Is my example above a bad example (ignoring what I say below)?我上面的例子是不是一个坏例子(忽略我下面说的)? Why isn't a lower bound useful here?为什么下限在这里没有用? And if it would be useful, what's the real reason that it is not permitted in Java?如果它有用,那么在 Java 中不允许它的真正原因是什么?

PS聚苯乙烯

I know that a better organization might be something like this:我知道一个更好的组织可能是这样的:

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

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

Can we for the purpose of this question pretend that due to a requirement, we need to do both operations in one method call?出于这个问题的目的,我们是否可以假装由于需求,我们需要在一个方法调用中执行这两个操作?

Edit编辑

@Tom G (justifiably) asks what benefit having a List<CharSequence> would have over a List<String> . @Tom G(有理由)询问拥有List<CharSequence>List<String>有什么好处。 For one, nobody said the returned list is immutable, so here's one advantage:首先,没有人说返回的列表是不可变的,所以这里有一个优点:

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

Basically, its not useful enough.基本上,它不够有用。

I think your example points out the only advantage of a lower bound, a feature the FAQ calls Restricted Instantiation :我认为您的示例指出了下限的唯一优势,常见问题解答称为Restricted Instantiation的功能:

The bottom line is: all that a " super " bound would buy you is the restriction that only supertypes of Number can be used as type arguments .底线是:“超级”绑定能为您带来的只是限制,即只有 Number 的超类型才能用作类型参数 .... ....

But as the other posts point out, the usefulness of even this feature can be limited.但正如其他帖子所指出的,即使是此功能的用处也可能受到限制。

Due to the nature of polymorphism and specialization, upper bounds are far more useful than lower bounds as described by the FAQ ( Access To Non-Static Members and Type Erasure ).由于多态性和专业化的性质,如常见问题解答(访问非静态成员类型擦除)所述,上限比下限有用得多。 I suspect the complexity introduced by lower bounds aren't worth its limited value.我怀疑下限引入的复杂性不值得它的有限价值。


OP: I want to add I think you did show it is useful, just not useful enough. OP:我想补充一点,我认为你确实展示了它是有用的,只是不够有用。 Come up with the irrefutable killer use cases and I'll back the JSR.提出无可辩驳的杀手级用例,我将支持 JSR。 :-) :-)

the spec does talk about lower bounds of type parameters, for example例如,规范确实讨论了类型参数的下限

4.10.2 4.10.2

a type variable is a direct supertype of its lower bound.类型变量是其下限的直接超类型。

5.1.10 5.1.10

a fresh type variable... whose lower bound一个新的类型变量......其下限

It appears that a type variable only has a (non-null) lower bound if it's a synthetic one as result of wildcard capture.如果类型变量是通配符捕获的结果,则它似乎只有一个(非空)下限。 What if the language allow lower bounds on all type parameters?如果语言允许所有类型参数的下界怎么办? Probably it doesn't cause a lot of trouble, and it's excluded only to keep generics simpler (well...) Update it is said that theoretical investigation of lower bounded type parameters is not thoroughly conducted.可能它不会造成很多麻烦,并且它被排除只是为了让泛型更简单(好吧......)更新据说下界类型参数的理论研究没有彻底进行。

Update: a paper claiming lower bounds are ok: "Java Type Infererence Is Broken: Can We Fix It" by Daniel Smith更新:一篇声称下界没问题的论文:Daniel Smith 的“Java Type Infererence Is Broken: Can We Fix It”

RETRACT: the following argument is wrong. RETRACT:以下论点是错误的。 OP's example is legitimate. OP的例子是合法的。

Your particular example is not very convincing.你的具体例子不是很有说服力。 First it's not type safe.首先,它不是类型安全的。 The returned list is indeed a List<String> , it's unsafe to view it as another type.返回的列表确实是一个List<String> ,将其视为另一种类型是不安全的。 Suppose your code compiles:假设您的代码编译:

    List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5);

then we can add non-String to it, which is wrong然后我们可以向其中添加非字符串,这是错误的

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

Well a List<String> is not, but somewhat like a list of CharSequence.好吧, List<String>不是,但有点像 CharSequence 列表。 Your need can be solved by using wildcard:您的需求可以通过使用通配符来解决:

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());

More than an answer, this is another (possibly killer?) use case.不仅仅是一个答案,这是另一个(可能是杀手?)用例。 I have a ModelDecorator helper.我有一个 ModelDecorator 助手。 I want it to have the following public API我希望它具有以下公共 API

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

So, given classes A, B extends A, it can be used like this:所以,给定类 A,B 扩展 A,它可以像这样使用:

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

But I want to have bounds on T and SUPER, so I make sure that only subclases can be instantiated using the API.但我想限制 T 和 SUPER,所以我确保只有子类可以使用 API 实例化。 At this moment, I can do:此时此刻,我可以做到:

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

Where B DOES not inherit from C.其中 B 不继承自 C。

Obviously, if I could do:显然,如果我能做到:

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

That would solve my problem.那会解决我的问题。

What advantage does typing the List give you at that point?此时键入列表会给您带来什么优势? When you iterate over the returned collection, you should still be able to do the following:当您遍历返回的集合时,您应该仍然能够执行以下操作:

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

Edit: I bring good news.编辑:我带来了好消息。 There is a way to get most of what you want.有一种方法可以得到你想要的大部分东西。

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);

The downside is clients do need to provide either an instance of R to mutate, or some means to construct an R. There is no other way to safely construct it.缺点是客户确实需要提供 R 的实例进行变异,或者提供某种方法来构建 R。没有其他方法可以安全地构建它。

I'll retain my original answer below for informational purposes.我将在下面保留我的原始答案以供参考。


In summary:总之:

There is not a good reason, it just has not been done.没有充分的理由,只是还没有完成。

And until such time as it is, it will be impossible to write exact types with correct variance for methods that do all of:直到现在,不可能为执行以下所有操作的方法编写具有正确方差的精确类型:

A) Accept or create parametrized data structure A)接受或创建参数化数据结构

B) Write computed (not-passed-in) value(s) to that data structure B)将计算的(未传入的)值写入该数据结构

C) Return that data structure C)返回该数据结构

Writing/accepting values is exactly the case where contravariance applies, which means the type parameter on the data structure must be lower-bounded by the type of the value being written to the data structure.写入/接受值正是逆变应用的情况,这意味着数据结构上的类型参数必须低于写入数据结构的值的类型。 The only way to express that in Java currently is using a lower-bounded wildcard on the data structure, eg List<?目前在 Java 中表达这一点的唯一方法是在数据结构上使用下界通配符,例如 List<? super T>.超级T>。


If we are designing an API such as the OP's, which might naturally (but not legally) be expressed as:如果我们正在设计一个 API,例如 OP,它可能自然地(但不合法地)表示为:

// 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)

Then the options available to us are:那么我们可用的选项是:

A) Use a lower-bounded wildcard, and force callers to cast the output: 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) Overly restrict the type parameter on the data structure to match the type of the value being written, and force callers to cast the input and output: 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) Use a new type parameter and do our own unsafe casting internally: 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)

Pick your poison.选择你的毒药。

Hmm, ok - let's work with this.嗯,好的 - 让我们来处理这个。 You define a method:您定义一个方法:

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

What does that mean?那是什么意思? It means that if I call your method, then I get back a list of some superclass of String.这意味着如果我调用你的方法,那么我会得到一些字符串超类的列表。 Maybe it returns a list of String.也许它返回一个字符串列表。 Maybe it returns a list of Object.也许它返回一个对象列表。 I don't know.我不知道。

Cool.凉爽的。

List<Object> l1 = createArrayListFullOfEmptyStrings(5);

According to you, that should compile.根据你的说法,那应该编译。 But that's not right!但那是不对的! I can put an Integer into a list of Object - l1.add(3) .我可以将 Integer 放入 Object - l1.add(3)的列表中。 But if you are returning a list of String, then doing that should be illegal.但是,如果您要返回一个字符串列表,那么这样做应该是非法的。

List<String> l3 = createArrayListFullOfEmptyStrings(5);

According to you, that should compile.根据你的说法,那应该编译。 But that's not right!但那是不对的! l3.get(1) should always return a String... but that method might have returned a list of Object, meaning that l3.get(1) could conceivably be an Integer. l3.get(1)应该总是返回一个字符串...但是该方法可能返回一个对象列表,这意味着 l3.get(1) 可能是一个整数。

The only thing that works is唯一有效的是

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

All I know is that I can safely call l4.put("foo") , and I can safely get Object o = l4.get(2) .我所知道的是我可以安全地调用l4.put("foo") ,并且我可以安全地获取Object o = l4.get(2)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在Java通用编程中,Object类是没有通配符的参数类型的上限还是下限? - In java generic programming, is the Object class upper bound or lower bound of the parameter type without wildcard? Java Generic和中的通配符 <? super T> 含义,下限或上限 - Wild card in java Generic and <? super T> meaning, lower or upper bound 如果不同的Java泛型类型参数具有相同的名称,为什么可以为其分配不同的类型? - If different Java generic type parameter have the same name, why it can be assigned different types? 为什么Java不能使用泛型类型参数推断lambda表达式的类型? - Why can't Java infer the type of a lambda expression with a generic type parameter? 可以使用上限参数实例化使用绑定参数的Java泛型吗? - Can a Java Generic Using a Bound Parameter be Instantiated with its Upper Bound? Java嵌套通配符绑定类型多次或作为泛型类型参数 - Java nested wildcard bound types multiple times or as generic type parameter 在Java中,为什么数组不能是类型变量的绑定,但可以是通配符的绑定? - In Java, why can't an array be a Type Variable's bound, but can be a Wildcard's bound? 实例化中绑定的Java通用参数 - Java Generic Parameter Bound In Instantiation Java断言绑定到泛型类型 - Java assert bound on generic type Java通用类绑定到类型扩展T <E> - Java Generic class bound to type extending T<E>
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM