Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with using 'extends' and 'super' in java generics with generic methods

There is a method :

public static <T> void addandDisp(Collection<T> cs, T t)

which is being called in the following way :

List<? extends Object> ls2 = new LinkedList<Number>();
addandDisp(ls2,new Object());

This gives a compile time error. On the other hand, if we had only one parameter, then the call is successful. Why is that ?

Moreover, this is successful :

List<? super String> ls1 = new LinkedList<String>();
addandDisp(ls1,new String());

while this is not :

List<? super String> ls1 = new LinkedList<Object>();
addandDisp(ls1,new Object());

What is the underlying logic ?

like image 938
Daud Avatar asked Aug 04 '14 14:08

Daud


People also ask

What is the disadvantages of using generics?

According to oracle documentation, the following points are the disadvantage of generics: Cannot instantiate Generic types with primitive types. Cannot create instances of type parameters. Cannot declare static fields whose types are type parameters.

Why should we avoid generics in Java?

Many people are unsatisfied with the restrictions caused by the way generics are implemented in Java. Specifically, they are unhappy that generic type parameters are not reified: they are not available at runtime. Generics are implemented using erasure, in which generic type parameters are simply removed at runtime.


3 Answers

I would assume that addandDisp means add and display.

When you have a collection, defined as:

List<? extends Object> ls2 = new LinkedList<Number>();

This means that the compiler will allow you to assign the colletion to all the possible unknown subtypes of Object. Since you have the operation add, the compiler denies to give you green light, because it doesn't know if the provided object's type meets the restriction to be of the unknown subtype of Object. This is called covariance.


Similary, when you have a definition like this:

List<? super String> ls1 = new LinkedList<String>();

The compiler allows you to assing ls1 to:

  • LinkedList<String>(); //you can add Strings to the list
  • LinkedList<Object>(); //you can add Strings and Objects to the list

In this case, the compiler will be completely aware if the object you're trying to pass meets the condition to be subtype of the generic type of the collection. This is called contravariance.

More info:

  • What is PECS?
like image 199
Konstantin Yovkov Avatar answered Sep 21 '22 16:09

Konstantin Yovkov


Here is another approach, but others gave a quite detailed response.

The first example:

List<? extends Object> ls0 = new LinkedList<Number>();
addandDisp(ls0, new Object());

Here ls0 may be of any type that is a subclass of Object, like a Number as your example shows.

Now what if this could work? It would mean that if you can put an Object into any of the collections.

Imagine I want to iterate your previous list of Number objects:

for (Number n : numbers) {
    ...
}

If I could put an Object here, then Plaff! I would get a class cast exception immediately.

The second example

List<? super String> ls1 = ...;
addandDisp(ls1,new String());

Let's play a bit with the object hierarchy: String is a subclass of CharSequence which is a subclass of Object.

You get ls1 here which is an unknown supertype of String (e.g., a CharSequence). Is it all right to put a String into a list of character sequences? Yup, it's okay, since they share the same interface for sure! You can handle them through it in the same way.

The third example

List<? super String> ls1 = ...;
addandDisp(ls1,new Object());

Let's suppose the previous situation: you assign ls1 a list of CharSequences. Can you add an object to a List<CharSequence>? Nope, for the same reason as the first example fails.

Hope that helps clarifying your issues a bit :-)

like image 29
rlegendi Avatar answered Sep 19 '22 16:09

rlegendi


On the other hand, if we had only one parameter, then the call is successful. Why is that ?

Your method has a single type parameter. When you invoke a method, you either provide a type argument explicitly or have one be inferred implicitly.

When you invoke it like

addandDisp(ls2, new Object());

the compiler needs to extract a type to bind to T from both method arguments, since both method parameters rely on the method's type parameter, T.

The issue here is that ? extends Object and Object do not produce a single type that can be bound to T safely. Imagine you had

public static <T> void addandDisp(Collection<T> cs, T t) {
    cs.add(t);
}
...
List<? extends Object> ls2 = new LinkedList<Number>();
addandDisp(ls2, new Object());

This add should not be allowed since an Object should not be used where a Number would have been expected. Type safety would break.

If you have a single parameter

public static <T> void addandDisp(Collection<T> cs) {

Then the compiler uses the single argument to infer the type argument. There is then no ambiguity about what to use.

like image 26
Sotirios Delimanolis Avatar answered Sep 18 '22 16:09

Sotirios Delimanolis