5

I am trying to learn Java Generics, and found the following code.

public static <T> void print(T a, T b){
    System.out.println(a);
    System.out.println(b);
}

public static void main(String[] args){
    print(new ArrayList<String>(), 1);
}

Which works with no problem.

However when I change print method to the following, it gives me compiling errors.

public static <T> void print(List<T> a, T b){
    System.out.println(a);
    System.out.println(b);
}

Error:

GenericTest.java:9: error: method print in class GenericTest cannot be applied to given types;
  print(new ArrayList<String>(), 1);
    ^
  required: List<T>,T
  found: ArrayList<String>,int
  reason: no instance(s) of type variable(s) T exist so that argument type int conforms to formal parameter type T
  where T is a type-variable:
    T extends Object declared in method <T>print(List<T>,T)
1 error

Can anyone help me understand the errors?

Tunaki
  • 132,869
  • 46
  • 340
  • 423
Xin
  • 1,169
  • 1
  • 10
  • 20

3 Answers3

8

The first thing you should understand is that, with the following method signature

public static <T> void print(T a, T b)

Both T must be the same type, that is to say both a and b will have the same infered type.

So why does it work for new ArrayList<String>() and 1? Because both parameters can actually be represented as Serializable, which is the nearest common super type of ArrayList and Integer:

  • ArrayList implements the Serializable interface.
  • 1 can be boxed into an Integer, which is also Serializable.

So in this case, the compiler will infer T as Serializable.


In the second case, with the signature

public static <T> void print(List<T> a, T b)

There is no common super type T that would be valid for both List<String> and Integer. It is true that both String and Integer are Serializable, but since generics aren't polymorphic, it doesn't work.

Community
  • 1
  • 1
Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • Can you also explain why it's resolved to `Serializable` and not any other common super type? – Codebender Jan 22 '16 at 10:23
  • @Codebender Because it is the nearest common super type between `ArrayList` and `Integer`. `Object` would be one level up in the hierarchy. – Tunaki Jan 22 '16 at 10:26
  • I don't get how the hierarchy is formed between Interface & Class (I will ask a new question about that), but eclipse shows it's resolved to Serializable (+1 for that)... – Codebender Jan 22 '16 at 10:38
  • 1
    @Codebender To put it simply, make a tree of the hierarchy (in Eclipse, press F4 on the type and show supertype hierarchy) and add 1 each time you go up one level. The type that will be common to ArrayList and Integer with the lowest count is Serializable. – Tunaki Jan 22 '16 at 10:40
  • @Tunaki I just read Java Doc about "Erasure of Generic Methods" that "Because `T` is unbounded, the Java compiler replaces it with `Object`" Reference: docs.oracle.com/javase/tutorial/java/generics/genMethods.html – Xin Jan 22 '16 at 10:46
  • 2
    @Xin, erasure doesn't have anything to do with your scenario (Yes at runtime it will be Object because of erasure)... Your's is about inference... – Codebender Jan 22 '16 at 10:48
  • @Tunaki Thanks for the quick answer. I will look into inference. (+1) – Xin Jan 22 '16 at 10:50
5

Edit: It doesn't get resolved to Object as I had mentioned but to Serializable. See Tunaki's correct answer for the reason.

In your first case, T is resolved to the most specific type which can refer to both your arguments.

And in your example, it will get resolved to Object Serializable, since ArrayList and Integer are both Object Serializable sub-types.

But in your second example, you have a List<T> as the parameter...

Now, when you call it with ArrayList<String>, the type T is resolved to a String. It cannot be resolved to any other type because an ArrayList<Object> ArrayList<Serializable> is NOT a supertype of ArrayList<String>

Community
  • 1
  • 1
Codebender
  • 14,221
  • 7
  • 48
  • 85
3

You call the method in following ways :

print(new ArrayList<String>(), "SimpleString");

or

print(new ArrayList<Integer>(),5)

because <T> is a type and it cannot be both String and integer at same time.

Your call could work if you do something like this :

public static <T, K> void print(List<T> a, K b){
    System.out.println(a);
    System.out.println(b);
}

public static void main(String[] args){
    print(new ArrayList<String>(),1);
}
Amal
  • 154
  • 4
  • This is the best answer as it's the only one having code with 2 type parameters. As the syntax isn't necessarily intuitive to a beginner. The example clarifies how to use it. Nice work. Will +1 tommorow (vote limit). – HopefullyHelpful Feb 09 '17 at 14:50