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 (i.e. using the super
keyword). I was reading what the Angelika Langer Generics FAQ had to say on the subject. 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.
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?
P.S.
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>
. 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"));
Solution 1:
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. :-)
Solution 2:
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());
Solution 3:
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.