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

I understand that you cannot bind a generics Java type parameter to the lower bound (i.e. using the super keyword). I read that Angelika Langer Generics FAQ on this . They say that it basically boils down to the fact that the lower bound is useless ("makes no sense").

I'm not sure. I can imagine using them to help you be more flexible for library calling methods that create a typed result. Imagine a method that created a list of arrays of a user-defined size and filled it with an empty string. A simple announcement would be

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

But this is unnecessarily restrictive for your customers. Why they cannot call your method as follows:

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

My example is above a bad example (ignoring what I say below)? Why is there no bottom line? And if that would be useful, what is the real reason that Java is not allowed?

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

For this purpose, can we pretend that, on demand, we need to perform both operations in one method call?

Edit

@Tom G (rightly) asks what advantage List<CharSequence> for List<String> . Firstly, no one said that the returned list is immutable, so here is one advantage:

 List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5); l2.add(new StringBuilder("foo").append("bar")); 
+46
java generics bounds
Feb 04 '11 at 20:44
source share
6 answers

In principle, its not useful enough.

I think your example points to the only advantage of the lower bound, the function that often sets: Restricted Instantiation :

The bottom line is this: all that "super" would bind you would buy the restriction that only supertypes of Number can be used as arguments to type .....

But, as other posts indicate, the usefulness of even this feature may be limited.

Due to the nature of polymorphism and specialization, the upper bounds are much more useful than the lower bounds, as described in the FAQ (Access to non-stationary members and type deletion). I suspect that the complexity introduced by the lower bounds is not worth its limited value.




OP: I want to add, I think you have shown that this is useful, just not useful enough. Come up with incontrovertible use cases for killers, and I'll get JSR back. :-)

+15
04 Feb 2018-11-21T00:
source share

the specification speaks of 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 variable of a new type ... whose lower bound

It looks like a type variable has only a (nonzero) lower bound if it is synthetic as a result of wildcard capture. What if the language allows lower bounds for all type parameters? This probably does not cause big problems, and he excluded it just to keep the common methods easier (well ...) to Update . They say that a theoretical study of the parameters of the lower limited type is not carried out carefully.

Update: bottom-level document in the order: โ€œJava Type Invalid: Can We Fix It?โ€ Daniel Smith

RETRACT: The following argument is invalid. An example of an OP is legal.

Your specific example is not very convincing. At first it is not safe. The returned list is really a List<String> , it is not safe to view it as another type. Suppose your code compiles:

  List<CharSequence> l2 = createArrayListFullOfEmptyStrings(5); 

then we can add a non-String to it, which is wrong

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

Well, the List<String> not, but somewhat reminds the CharSequence list. Your need can be solved with the help of a template:

 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()); 
+11
Feb 04 2018-11-11T00:
source share

More than an answer, this is another option (maybe a killer?). 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, the given classes A, B extends A, it can be used as follows:

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

But I want to have restrictions on T and SUPER, so I make sure that only subclasses can be created using the API. At this moment I can do:

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

Where B is not inherited from C.

Obviously, if I could:

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

This will solve my problem.

+1
Feb 07 '17 at 7:46
source share

What is the advantage of typing a list at this point? When you iterate over the returned collection, you can still do the following:

 for(String s : returnedList) { CharSequence cs = s; //do something with your CharSequence } 
0
04 Feb 2018-11-11T00:
source share

Change: I brought 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; } List<Object> lis1 = createListFullOfEmptyString(ArrayList::new, 5); // works ArrayList<String> lis2 = createListFullOfEmptyString(ArrayList::new, 5); // works List<Integer> lis3 = createListFullOfEmptyString(ArrayList::new, 5) // breaks 

The disadvantage is that clients need to provide an instance of R for mutation or some means of creating R. There is no other way to create it safely.

I will keep my original answer below for informational purposes.




Eventually:

There is no good reason; it just has not been done.

And until this happens, it will not be possible to write exact types with the correct dispersion for methods that do everything:

A) Accept or create a parameterized data structure

B) Write calculated (not passed) values โ€‹โ€‹to this data structure

C) Revert this data structure

Writing / accepting values โ€‹โ€‹is exactly the case when contravariance is applied, which means that the type parameter in the data structure should be limited from below to the type of value written to the data structure. The only way to express this in Java at this time is to use a wildcard with a lower limit in the data structure, e.g. List <? super T>.




If we are developing an API such as OP, which naturally (but not legally) can 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 wildcard with a lower limit and force callers to play 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) Excessively limit the type parameter in the data structure so that it matches the type of the value being written, and force callers to convert 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 the new type parameter and execute our own unsafe cast inside:

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

Choose your poison.

0
Jul 14 '19 at 4:43
source share

Hmm, well, let it work with that. You define a method:

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

What does it mean? This means that if I call your method, I will return a list of the String superclass. Perhaps it returns a String list. Perhaps it returns a list of objects. I dont know.

Cool.

List<Object> l1 = createArrayListFullOfEmptyStrings(5);

In your opinion, this should compile. But this is not so! I can put Integer on the Object list - l1.add(3) . But if you return a String list, then doing this should be illegal.

List<String> l3 = createArrayListFullOfEmptyStrings(5);

In your opinion, this should compile. But this is not so! l3.get(1) should always return String ... but this method could return a list of objects, which means that l3.get (1) could possibly 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) .

-one
Feb 16 '11 at 8:00
source share



All Articles