Why can't a Java type parameter have a lower bound?

别说谁变了你拦得住时间么 提交于 2019-11-26 14:33:19

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:

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. ....

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. Come up with the irrefutable killer use cases and I'll back the JSR. :-)

the spec does talk about lower bounds of type parameters, for example

4.10.2

a type variable is a direct supertype of its lower bound.

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

RETRACT: the following argument is wrong. OP's example is legitimate.

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. 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. 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. I want it to have the following public 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 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. 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.

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.

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

B) Write computed (not-passed-in) value(s) to that data structure

C) Return that data structure

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, e.g. List<? super T>.


If we are designing an API such as the OP's, which might naturally (but not legally) be expressed as:

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

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

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

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

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!