Why Can't a Generic Type Parameter Have a Lower Bound in Java

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

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

Why does Java have lower bounds in generics?

First: The method declaration

public static <T extends Comparable<T>> void sort(List<T> list)

does not make much sense for me. I thing it should be

public static <T extends Comparable<? super T>> void sort(List<T> list)

Then it would be possible to write sort(listOfStudents). Now I will explain the advantage of upper and lower bounded wildcards:


Polymorphism of type parameters is not transferred to it's generic type

This mean a list of students (List<Student>) is not a list of persons (List<Person>). A instruction like

List<Person> list = new List<Student>();

would fail in Java. There is a simple reason: list.add(new Person()); would be illegal for a list of students but not for a list of persons.

Upper Bounded Wildcards

But maybe you have a function which doesn't care whether the objects are subclasses or not. For example: You could have a method like this:

void printAll(List<Person> list)

They just print some data about all persons to stdout. If you have a list of students (List<Student> listOfStudents) you could write:

List<Person> listOfPersons = new ArrayList<>();
for (final Student student : listOfStudents) {
listOfPersons.add(student);
}
printAll(listOfPersons);

But you may see that it isn't a very nice solution. Another solution would be to use upper bounded wildcards for printAll:

void printAll(List<? extends Person> list)

You can write something like Person person = list.get(0) in printAll. But you cannot write print.add(new Person()) because list could be a list of students or something else.

Lower Bounded Wildcards

Now the same in the other direction: Lets say you have a function which generates some students and put them in a list. Something like this:

void generateStudents(List<Student> list) {
for (int i = 0; i < 10; ++i) {
list.add(new Student());
}
}

Now you have a list of persons (List<Person> listOfPersons) and want to generate students in this list. You could write

List<Student> listOfStudents = new ArrayList<>();
generateStudents(listOfStudents);
for (Student student : listOfStudents) {
listOfPersons.add(student);
}

You may see again, that it is not a very nice solution. You could also change the declaration of generateStudents to

void generateStudents(List<? super Student> list)

Now, you can just write generateStudents(listOfPersons);.

Lower and upper bound generic constraints on same wildcard

Section 4.5.1 of the JLS specifies the syntax for generic wildcards:

TypeArguments:
< TypeArgumentList >
TypeArgumentList:
TypeArgument {, TypeArgument}
TypeArgument:
ReferenceType
Wildcard
Wildcard:
{Annotation} ? [WildcardBounds]
WildcardBounds:
extends ReferenceType
super ReferenceType

In here, WildcardBounds is written in square brackets. In Section 2.4 of the JLS, it is explained that in this context, square brackets indicate optional elements that can be put only once:

The syntax [x] on the right-hand side of a production denotes zero or one occurrences of x. That is, x is an optional symbol. The alternative which contains the optional symbol actually defines two alternatives: one that omits the optional symbol and one that includes it.

For bounded generic wildcards, this means that only one wildcard bound is permitted.

Understanding upper and lower bounds on ? in Java Generics

? as a type parameter can only be used in methods. eg: printAll(MyList<? extends Serializable>) I cannot define classes with ? as type parameter.

A wildcard (?) isn't a formal type parameter, but rather can be used as a type argument. In the example you give, ? extends Serializable is given as a type argument to the generic type MyList, of the printAll method's parameter.

Methods can also declare type parameters like classes, for example:

static <T extends Serializable> void printAll(MyList<T> myList)

I understand the upper bound on ?. printAll(MyList<? extends Serializable>) means printAll will print MyList if it has objects that implement the Serialzable interface

More accurately, it means a call to printAll will compile only if it is passed a MyList with some generic type that is or implements Serializable. In this case it would accept a MyList<Serializable>, MyList<Integer>, etc.

I have a bit of an issue with the super. printAll(MyList<? super MyClass>) means printAll will print MyList if it has objects of MyClass or any class which extends MyClass (the descendants of MyClass)

A wildcard bounded with super is a lower bound. So we could say a call to printAll will compile only if it is passed a MyList with some generic type that is MyClass or some super-type of MyClass. So in this case it would accept MyList<MyClass>, e.g. MyList<MyParentClass>, or MyList<Object>.

So, say if MyClass looks like:

public class MyClass extends Thread implements ActionListener{
// whatever
}

then, printAll() will print if

  1. There are objects of MyClass in the list
  2. There are objects of Thread or ActionListener in the list

You're on the right track. But I think saying e.g. "it will print if there are objects of MyClass in the list" is problematic. That makes it sound like you're defining runtime behavior - generics are all about compile time checks. For example wouldn't be able to pass a MyList<MySubclass> as an argument for MyList<? super MyClass>, even though it might contain instances of MyClass, by inheritance. I would reword it to:

A call to printAll(MyList<? super MyClass>) will compile only if it is passed a:

  1. MyList<MyClass>
  2. MyList<Thread>
  3. MyList<Runnable>
  4. MyList<ActionListener>
  5. MyList<EventListener>
  6. MyList<Object>
  7. MyList<? super X> where X is MyClass, Thread, Runnable, ActionListener, EventListener, or Object.

So, after having read the many answers to the question, here is my
understanding:

? extends T means any class which extends T. Thus, we are referring to
the children of T. Hence, T is the upper bound. The upper-most class
in the inheritance hierarchy

? super T means any class / interface which is super of T. Thus we are
referring to all the parents of T. T is thus the lower bound. The
lower-most class in the inheritance hierarchy

Close, but I wouldn't say "children of T" or "parents of T", since these bounds are inclusive - it would be more accurate to say "T or its subtypes", and "T or its supertypes".

Why does java require a cast for the instantiation of a bounded type parameter to its upper bound class?

T doesn't mean Integer, it must be valid for Integer or any class that extends from it. Let's say that StrangeInteger extends from Integer and replace T with StrangeInteger:

void passVal (StrangeInteger t) {
Integer number = 5;
t = (StrangeInteger) number;
}

It attempts to assign a Integer variable to a StrangeInteger variable, you can't do that unless number was a StrangeInteger or a derived class in the first place. In fact your code should (conceptually) throw an Exception at runtime unless t is an Integer, but it won't actually do that in this case due to type erasure (see Edit 2).

The situation is similar to:

Object obj = "Hello"
String t = (String)obj; // this will not fail, but requires a cast

Object obj2 = getDBConnection();
String t2 = (String)obj2; // this will fail at runtime

Edit: Integer is indeed final, so T can only be Integer, but it's possible that the compiler is not checking if the upper bound is final, after all it makes little sense for an upper bound to be final, so allowing that special case adds complexity for very little real gain.

Edit 2:
TL; DR: You are confusing upper and lower bounds, but there are caveats with type erasure. This will break as soon as you do anything that is worth doing using generics rather than just using the base type.

English is not my first language so I may be not totally clear.

I think you are struggling with the difference between using a generic type with an upper bound and just using the upper bound as type. The idea of generics (from C++ and other languages) is that the code must be valid if you replace T by any type T allowed by the bounds, so you can't call any method that is not defined in the upper bound.

A being an upper bound on T also means that you can always assign a T object to an A variable. You can't assign safely an A object to a T variable (unless A == T), you could only do that if A was a lower bound on T, not an upper bound. Also see Understanding upper and lower bounds on ? in Java Generics.

Java uses type erasure to implement generics, there are a few advantages, but that causes some limitations that are not always apparent. Because of type erasure in this case the cast itself won't fail, after type checking T is replaced by the upper bound in the type erasure step, i.e. (T)number is replaced by (Integer)number. An exception will still occur if you do anything that causes a cast to the subclass, for example if you return the altered t and assign the result to a variable of the subclass, because the compiler adds an implicit cast.

This will also fail if you call a method that depends on an subclass of T, which is a common pattern, for example:

List<Person> persons = ...
Comparator<Person> nameComparator = (p1,p2) -> p1.getName().compareTo(p2.getName())
java.util.Collections.sort(persons,nameComparator);

The following code sample display the behavior in several cases. I used System.err on everything to avoid order issues in the output.

import java.util.function.Consumer;
import java.util.function.Function;

class A {
@Override public String toString(){ return "A";}
public String foo(){ return "foo";}
}

class B extends A {
@Override public String toString(){ return "B";}
public String bar(){ return "bar";}
}

class C extends B { }

public class Main {

public static void main(String[] args) {
Function<A,String> funA = a -> a.foo();
Function<B,String> funB = b -> b.bar();
Function<C,String> funC = c -> c.bar();
Consumer<B> ignoreArgument = b -> {
System.err.println(" Consumer called");
};

B b = new B();
System.err.println("* voidTest *");
voidTest(b);
System.err.println("------------");
System.err.println("* returnTest *");
returnTest(b);
System.err.println("returnTest without using result did not throw");
System.err.println("------------");
try {
System.err.println("Returned " + returnTest(b).toString());
System.err.println("returnTest: invoking method on result did not throw");
}
catch(Exception ex) {
System.err.println("returnTest: invoking method on result threw");
ex.printStackTrace();
}
System.err.println("------------");
B b2 = null;
try {
b2 = returnTest(b);
System.err.println("returnTest: assigning result to a B variable did not throw");
}
catch(Exception ex) {
System.err.println("returnTest: assigning result to a B variable threw");
ex.printStackTrace();
}
System.err.println("------------");
System.err.println("* functionTest funA *");
functionTest(b, funA);
System.err.println("------------");
System.err.println("* functionTest funB * ");
functionTest(b, funB);
System.err.println("------------");
System.err.println("* consumerTest *");
consumerTest(b, ignoreArgument);
// The following won't work because C is not B or a superclass of B
// Compiler error functionTest(T, Function<? super T,String>) is not applicable for the arguments (B, Function<C,String>)
// functionTest(b, funC);
}

private static <T extends A> void voidTest(T t){
System.err.println(" Before: " + t.toString());
t = (T)new A(); // warning Type safety: Unchecked cast from A to T
System.err.println(" After: " + t.toString());
}

private static <T extends A> T returnTest(T t){
System.err.println(" Before: " + t.toString());
t = (T)new A();
System.err.println(" After: " + t.toString());
return t;
}

private static <T extends A> void functionTest(T t, Function<? super T,String> fun) {
System.err.println(" fun Before: " + fun.apply(t));
t = (T)new A();
try {
System.err.println(" fun After: " + fun.apply(t));
}
catch(Exception ex) {
System.err.println(" fun After: threw");
ex.printStackTrace();
}
}

private static <T extends A> void consumerTest(T t, Consumer<? super T> c) {
System.err.print(" Before: ");
c.accept(t);
t = (T)new A();
try {
System.err.println(" After: ");
c.accept(t);
System.err.println(" c.accept(t) After: worked");
}
catch(Exception ex) {
System.err.println(" c.accept(t) After: threw");
ex.printStackTrace();
}
}
}

The output under OpenJDK 11 is:

* voidTest *
Before: B
After: A
------------
* returnTest *
Before: B
After: A
returnTest without using result did not throw
------------
Before: B
After: A
returnTest: invoking method on result threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.main(Main.java:35)
------------
Before: B
After: A
returnTest: assigning result to a B variable threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.main(Main.java:45)
------------
* functionTest funA *
fun Before: foo
fun After: foo
------------
* functionTest funB *
fun Before: bar
fun After: threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.functionTest(Main.java:83)
at Main.main(Main.java:57)
------------
* consumerTest *
Before: Consumer called
After:
c.accept(t) After: threw
java.lang.ClassCastException: class A cannot be cast to class B (A and B are in unnamed module of loader 'app')
at Main.consumerTest(Main.java:97)
at Main.main(Main.java:60)

I'm not entirely sure resultTest why didn't caused an exception if the result is completely ignored, maybe a cast is not required by the language in that case, or the compiler removed it. Calling a method defined in the upper bound on the result still caused an exception. Finally an observation from consumerTest is that it didn't need to call bar() to cause a ClassCastException, it just needed to pass t to the consumer that expects a B argument.

Is it possible to specify both upper and lower bound constraints on type parameters in Java?

I don't believe so - as far as I can tell from the language specification, "super" is only valid for wildcard types in the first place. The syntax for wildcards also suggests you can only have one wildcard bound, too - so you can't use something like this either:

// Invalid
void foo(List<? extends Foo super Bar> list)

Even though both of these are okay:

// Valid
void foo(List<? extends Foo> list)

// Valid
void foo(List<? super Bar> list)

As noted in comments, it's possible to have multiple upper bounds - but only for type parameters and cast expressions. For example:

// Valid
<T extends Number & Comparable> void foo(List<T> list)

Substitute for illegal lower bounds on a generic Java method?

You can almost do it, but Java type inference manages to suck just enough to make it not worth it.

public abstract class ImmutableList< T > {
public interface Converter< U, V > {
V convert( U u );
}

public abstract < U > ImmutableList< U > map( Converter< ? super T, ? extends U > cnv );

public static class EmptyIL< T > extends ImmutableList< T >{
@Override
public < U > EmptyIL< U > map( Converter< ? super T, ? extends U > cnv ) {
return new EmptyIL< U >();
}
}

public static class NonEmptyIL< T > extends ImmutableList< T > {
private final T tHead;
private final ImmutableList< ? extends T > ltTail;
public NonEmptyIL( T tHead, ImmutableList< ? extends T > ltTail ) {
this.tHead = tHead;
this.ltTail = ltTail;
}
@Override
public < U > NonEmptyIL< U > map( Converter< ? super T, ? extends U > cnv ) {
return new NonEmptyIL< U >( cnv.convert( tHead ), ltTail.map( cnv ) );
}
}

public < U > ImmutableList< U > add( U u, final Converter< ? super T, ? extends U > cnv ) {
return new NonEmptyIL< U >( u, map( cnv ) );
}

public static < V > Converter< V, V > id() {
return new Converter< V, V >() {
@Override public V convert( V u ) {
return u;
}
};
}

public static < W, U extends W, V extends W > Converter< W, W > sup( U u, V v ) {
return id();
}

static class Rock {}
static class Hominid {}
static class Human extends Hominid {}
static class Monkey extends Hominid {}
static class Chimpanzee extends Monkey {}

public static void main( String[] args ) {
Monkey monkey = new Monkey();
Human human = new Human();
Rock rock = new Rock();

// id() should suffice, but doesn't
new EmptyIL< Chimpanzee >().
add( monkey, ImmutableList.< Monkey >id() ).
add( human, ImmutableList.< Hominid >id() ).
add( rock, ImmutableList.< Object >id() );

// sup() finds the supremum of the two arguments' types and creates an identity conversion
// but we have to remember what we last added
new EmptyIL< Chimpanzee >().
add( monkey, sup( monkey, monkey ) ).
add( human, sup( monkey, human ) ). // add( human, sup( monkey, monkey ) ) also works
add( rock, sup( human, rock ) );
}
}

You at least get compile-time enforcement of convertibility between types, and as an added bonus you can define your own converters that go beyond just subclassing. But you can't have Java figure out that in the absence of a converter it should use the appropriate subclassing converter as a default, which would bring us to your original API.

On Java generics lower bound usage: ? super T

Here's an example:

The following snippet passes compilation with the signature <T> void copy(List<? super T> dest, List<? extends T> src) but doesn't work with the signature <T> void copy(List<T> dest, List<? extends T> src):

YourClass obj = new YourClass ();
List<HashMap<String,String>> lhm = new ArrayList<>();
List<Map<String,String>> lm = new ArrayList<>();
obj.<HashMap<String,String>>copy (lm,lhm);


Related Topics



Leave a reply



Submit