Generics are used to specify a type.
Consider the following class def:
public interface List<E> extends Collection<E>
This tells us there we have a List that can hold elements of any type.
Notice how there are no restrictions on E
.
If we define a class like so:
public class MyList<T extends String> implements List<T>
We now have a MyList that implements List, but only accepts String
s (or decedents).
Inside this class we can refer to T
.
public class MyList<T extends String> implements List<T> {
private ArrayList<T> internalStorage;
Key point
In the class definition we have defined what T
is; it's any class based on String.
Inside the class T can thus be referenced.
However the flavor of T does not get fixed until the class actually gets instantiated:
MyList<MyStringType> test = new MyList<MyStringType>(parameters); //java 6
MyList<MyStringType> test = new MyList<>(parameters); //java 7, same but shorter.
Now Java knows what T
means inside the MyList
class T is a MyStringType.
Armed with is knowledge Java compiles the class and replaces all references to T
with references to the actual class MyStringType
.
It completely forgets about T.
Now inside the class note that everywhere T
is mentioned it will get replaced by MyStringType.
But what if I want to handle a string, but not necessarily the MyStringType.
Solution:
I define a member like so:
List<? extends String> strs; //Will fill the data in later
Now we have a List called strs that will only accept strings, but that will not be forced to use strings of type MyStringType.
This list is not bound to the definition of T
that was fixed when MyList
was instantiated.
When we assign a value to the strs
, the flavor of the List if fixed. In your example it will be a List of String.
? extends String str;//error
The variable cannot be fixed, because its type cannot be nailed down when the class that holds it gets created.