Override after erasure?

Hello, please have a look at the following problem that totally baffles me:

Why doesn't a method override a base class method if it has exactly the same signature after erasure has been applied?

Instead, the following program throws an UnsupportedOperationException, indicating that the base class method from AbstractList has not been overridden!

The error was introduced by me when I refactored some code and did not use the correct signature for the methodadd. But since the erasued signature is still identical, why does it compile silently and crashes at runtime?

package com.neppert;

import java.lang.ref.Reference;

import java.lang.ref.WeakReference;

import java.util.AbstractList;

import java.util.ArrayList;

import java.util.List;

publicclass RefList<T>extends AbstractList<Reference><?extends T>>{

// Here's the line with the typo. Should be add(index, Refeference<? extends T>)

publicvoid add(int index, Reference<T> element){

delegate.add(index, element);

}

public Reference<?extends T> get(int index){

return delegate.get(index);

}

publicint size(){

return delegate.size();

}

publicstaticvoid main(String[] args){

RefList<String> list =new RefList<String>();

list.add(new WeakReference<String>("hello"));

}

privatefinal List<Reference><?extends T>> delegate =new ArrayList<Reference><?extends T>>();

}

[2898 byte] By [McNeppa] at [2007-10-3 5:14:17]
# 1

OK, here are two more pieces of the puzzle:

1. Both the Sun compiler and the Eclipse 3.2 compiler agree on this behaviour.

This seems to reduce the chance of a compiler bug.

2. The technical reason for the phenomenon is easy to see if you list the public methods named "add" of class RefList:

The compiler does not generate the bridge method necessary to override AbstractList.add(int, Object). It does do so, however, if I declare the method with the precise signature public void add(int, Reference<? extends T>).

This seems to increase the chance of a compiler bug ;-)

McNeppa at 2007-7-14 23:20:48 > top of Java-index,Core,Core APIs...
# 2

> Hello, please have a look at the following problem

> that totally baffles me:

>

> Why doesn't a method override a base class method if

> it has exactly the same signature after erasure has

> been applied?

If I'm not mistaken, this is the correct behavior. Methods are bound at compile-time and at compile-time the actual signature of the method is known. What I'm not sure about is why the compiler doesn't complain that the erasures are the same given that you are overriding and not overloading, assuming you are correct that the erasures are the same.

> Instead, the following program throws an

> UnsupportedOperationException, indicating that the

> base class method from AbstractList has not been

> overridden

Right, you are overloading the method and not overriding it. The method is not abstract in the base class.

dubwaia at 2007-7-14 23:20:48 > top of Java-index,Core,Core APIs...
# 3
OK, the reason appears to be that because the signature is not compatible with the base class, it is not recognizing the method as overriding the base class version and therefore not adding the bridge method.But you seem to know this already. What's bugging you about this?
dubwaia at 2007-7-14 23:20:48 > top of Java-index,Core,Core APIs...
# 4

> known. What I'm not sure about is why the compiler

> doesn't complain that the erasures are the same

> given that you are overriding and not overloading,

> assuming you are correct that the erasures are the

> same.

Yes, that's the point. The signatures of the two methods are definitely the same, which can be proven by trying to declare both of them. The following two method declarations in the same class trigger a compiler error, as was to be expected:

public void add(int index, Reference<T> element);

public void add(int index, Reference<? extends T> element);

So let me rephrase my question: Why are these declarations considered equal to each other, while at the same time they do not both override the base class method?

Is there any logic in this?

McNeppa at 2007-7-14 23:20:48 > top of Java-index,Core,Core APIs...
# 5

> So let me rephrase my question: Why are these

> declarations considered equal to each other,

> while at the same time they do not both

> override the base class method?

> Is there any logic in this?

They aren't equal to each other. They cannot be declared together because some compatiblilty issue that I can't recall at the moment. The compiler uses the actual signature to determine if you are overriding a method, not the erased signature.

BTW: This is one really good example of when the @Overrides annotation is really useful.

dubwaia at 2007-7-14 23:20:48 > top of Java-index,Core,Core APIs...
# 6

Thanks for delving with me into this muddy terrain!

Actually, I'm leaning towards a compiler bug right now: the invariant "a method with the same erased signature than its base class' method overrides it" must not be violated, even in the presence of generics.

The compiler must flag this attempt as an error!

My theory is the following: the compiler finds no method with the exact signature add(int,Reference<T>) in AbstractList<Reference><? extends T>>, thus it never bothers to check whether the erased signatures would be idenctical.

If I correct the original typing error, then extend the class RefArrayList once more and put the typing error into that new class, then all of a sudden, the compiler spots the inexact override, and DerivedArrayList won't compile:

public class RefArrayList<T> extends AbstractList<Reference><? extends T>> implements RefList<T> {

private final ArrayList<Reference><? extends T>> list = new ArrayList<Reference><? extends T>>();

public void add(int index, Reference<? extends T> e) {

System.out.println("RefArrayList.add");

list.add(e);

}

public Reference<? extends T> get(int index) {

return list.get(index);

}

public int size() {

return list.size();

}

}

class DerivedArrayList<T> extends RefArrayList<T> {

public void add(int index, Reference<T> e) {

System.out.println("DerivedArrayList.add");

super.add(e);

}

}

>

> BTW: This is one really good example of when the

> @Overrides annotation is really useful.

Thanks for pointing that out. I've just tried it out: using @Overrides would have prevented my refactoring mistake in the first place!

McNeppa at 2007-7-14 23:20:48 > top of Java-index,Core,Core APIs...
# 7
I decided a while back that if I'm ever migrating a project from 1.4 to 1.5 (which looks like it might happen quite soon) the first step will be to load it all into Eclipse, turn on the "missing @Overrides" warning, and then QuickFix them all before I make any changes to the code!
dannyyatesa at 2007-7-14 23:20:48 > top of Java-index,Core,Core APIs...
# 8
When doing so, check out the followings:Refactor>Infer Generic Type ArgumentsSource>Cleanup
muleta at 2007-7-14 23:20:48 > top of Java-index,Core,Core APIs...
# 9

Hi Philippe,

I have generally found Infer Generic Type Arguments to be not so good - even in fairly obvious cases, and the last time I used Source Cleanup on a serious project, it wouldn't work. (https://bugs.eclipse.org/bugs/show_bug.cgi?id=128784)

:-(

(But otherwise... good job!!!)

dannyyatesa at 2007-7-14 23:20:48 > top of Java-index,Core,Core APIs...
# 10

> Thanks for delving with me into this muddy terrain!

> Actually, I'm leaning towards a compiler bug right

> now: the invariant "a method with the same erased

> signature than its base class' method overrides

> it" must not be violated, even in the presence of

> generics.

I'm not sure I understand this comment but I'll assume you mean:

"a method with the same erased signature as a base class' method overrides that method"

If that's the case, it doesn't apply here. The base class signature is

add(int, Object)

and the erased signature in the subclass is:

add(int, Reference)

As you pointed out earlier, the compiler has to add a bridge method to make the subclass appear to override add(int, Object) method. The point I am trying to make is that the compiler determines whether to add the bridge method based on the full-non-erased generic signatures and the generics overriding rules.

dubwaia at 2007-7-14 23:20:48 > top of Java-index,Core,Core APIs...
# 11

> I'm not sure I understand this comment but I'll

> assume you mean:

>

> "a method with the same erased signature as a

> base class' method overrides that method"

Yes, that's what I meant - I just couldn't put it into a correct English sentence ;-)

> If that's the case, it doesn't apply here. The base

> class signature is

> > add(int, Object)

>

Hmm, I declared a class that extended AbstractList<Reference><? extended T>>.

So, theoretically, the base class should contain a method with the erased signature add(int,Reference). Unfortunately, AbstractList is shipped pre-compiled, thus it only has a method add(int,Object).

I'm trying to argue that if you derive from a specific instantiation of a parameterized base class, the compiler should treat this as if the necessary bridge method had already been defined in the base class.

Please look at the code in my last post: Don't you think it's strange that the mere introduction of an "intermediate class" changes the behaviour of the compiler so much?

Phew, I'm certain I have a valid point here, I just seem to reach the limits of what I can epxress in English ;-(

McNeppa at 2007-7-14 23:20:48 > top of Java-index,Core,Core APIs...
# 12

> Hmm, I declared a class that extended

> AbstractList<Reference><? extended T>>.

> So, theoretically, the base class should contain a

> method with the erased signature

> add(int,Reference). Unfortunately,

> AbstractList is shipped pre-compiled, thus it only

> has a method add(int,Object).

> I'm trying to argue that if you derive from a

> specific instantiation of a parameterized base class,

> the compiler should treat this as if the

> necessary bridge method had already been defined in

> the base class.

> Please look at the code in my last post: Don't you

> think it's strange that the mere introduction of an

> "intermediate class" changes the behaviour of the

> compiler so much?

> Phew, I'm certain I have a valid point here, I just

> seem to reach the limits of what I can epxress in

> English ;-(

There are a lot of things that I could pick out in Java Generics that I don't think work well or the way they 'should'. I've done so in the past but it doesn't change anything so I've stopped complaining for the most part. This is something I can definitely agree smells funny.

But in terms of whether it's a compiler bug, I have to say no, at this point. I believe this is exactly what the JLS specifies as the correct behavior. If you find something that suggests otherwise, please post it.

This is a good example of how wonky erasure in Java generics can be. It's reasonable to think that erasure means that the erased singatures are all that matters. This isn't the case, however and you have to pretend that there is no erasure except for when you can't. Clumsy by design, I guess.

dubwaia at 2007-7-14 23:20:48 > top of Java-index,Core,Core APIs...