I have an interface:
public static interface Consumer<T> { void consume(T t); }
And I want to be able to have:
public static class Foo implements Consumer<String>, Consumer<Integer> { public void consume(String t) {..} public void consume(Integer i) {..} }
It doesn't work - the compiler doesn't let you implement the same interface twice.
The question is: Why?
People have asked similar questions here, but the answer was always "type erasure", i.e. you cannot do it because the types are erased at runtime.
And they aren't - some types are retained at runtime. And they are retained in this particular case:
public static void main(String[] args) throws Exception { ParameterizedType type = (ParameterizedType) Foo.class.getGenericInterfaces()[0]; System.out.println(type.getActualTypeArguments()[0]); }
This prints class java.lang.String
(if I only keep Consumer<String>
in order to compile)
So, erasure, in its simplest explanation, is not the reason, or at least it needs elaboration - the type is there, and also, you don't care about the type resolution, because you already have two methods with distinct signature. Or at least it seems so.
A Generic class can have muliple type parameters.
We can also pass multiple Type parameters in Generic classes.
It is prohibited that a type implements or extends two different instantiations of the same interface. This is because the bridge method generation process cannot handle this situation.
You can also use more than one type parameter in generics in Java, you just need to pass specify another type parameter in the angle brackets separated by comma.
The answer is still "type erasure", but it's not that simple. The keywords are: raw types
Imagine the following:
Consumer c = new Foo(); c.consume(1);
What would that do? It appears that consume(String s)
is not actually consume(String s)
- it is still consume(Object o)
, even though it is defined to take String
.
So, the above code is ambiguous - the runtime can't know which of the two consume(..)
methods to invoke.
A funny follow-up example is to remove Consumer<Integer>
, but keep the consume(Integer i)
method. Then invoking the c.consume(1)
on a raw Consumer
. throws ClassCastException
- unable to cast from Integer to String. The curious thing about this exception is that it happens on line 1.
The reason is the use of bridge methods. The compiler generates the bridge method:
public void consume(Object o) { consume((String) o); }
The generated bytecode is:
public void consume(java.lang.String); public void consume(java.lang.Integer); public void consume(java.lang.Object); Code: 0: aload_0 1: aload_1 2: checkcast #39 // class java/lang/String 5: invokevirtual #41 // Method consume:(Ljava/lang/String;)V
So even if the methods you have defined keep their signatures, each of them has a corresponding bridge method that is actually invoked when working with the class (regardless whether it's raw or parameterized).
@Bozho: Thanks for your profound question and your own answer. Even I was at this doubt at some stage.
Plus to add more to your answer, I'd disagree with you on this point in your question:
"some types are retained at runtime".
Answer is NO because of type erasure (which you already pointed it out). Nothing is retained.
Now to answer this key doubt, "how does it then knows the exact type after erasure ?", check out the very definition of ParameterizedType.
When a parameterized type p is created, the generic type declaration that p instantiates is resolved
May be this resolution of exact TypeVariable is obtained through what you specified as bridge methods
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With