6

This question goes from my previous post in here.. Before I post my question, I am pasting the contents from oracle docs;

8.4.8.1. Overriding (by Instance Methods)

An instance method m1, declared in class C, overrides another instance method m2, declared in class A iff all of the following are true:

    C is a subclass of A.

    The signature of m1 is a subsignature (§8.4.2) of the signature of m2.

8.4.2. Method Signature 
The signature of a method m1 is a subsignature of the signature of a method m2 if either:

    m2 has the same signature as m1, or

    the signature of m1 is the same as the erasure (§4.6) of the signature of m2.

My understanding of type erasure when overriding is involved is as follows: if after erasure, the signature of m1 and m2 are same, then then they are considered overridden. so in my previous post above, I tried to override a parent class method that takes List<String> by a subclass method that takes List<Integer> assuming after type erasure what is left is just List<Object>. but that is wrong. so my understanding of the above definition of method overriding when erasure is involved is totally wrong. can some give a simple example to explain the above point.

Thanks. btw the above points come from here.

Community
  • 1
  • 1
brain storm
  • 25,842
  • 54
  • 187
  • 349

2 Answers2

3

Whether a method overrides another doesn't just deal with the erasures of the methods. The compiler determines whether a method overrides another, and it has access to the generic type parameters involved before type erasure occurs.

Your thoughts about using erasure to determine overrides are not quite correct. Let's add the following JLS Section, 8.4.8.1, to the discussion:

An instance method m1, declared in class C, overrides another instance method m2, declared in class A iff all of the following are true:

  • C is a subclass of A.

  • The signature of m1 is a subsignature (§8.4.2) of the signature of m2.

  • Either:

    • m2 is public, protected, or declared with default access in the same package as C, or

    • m1 overrides a method m3 (m3 distinct from m1, m3 distinct from m2), such that m3 overrides m2.

It's required that m1 is a subsignature of m2, but not the other way around.

Example 1:

class A {
   public void foo(List<String> list) { }
}

class B extends A {
   @Override
   public void foo(List list) {}
}

This is legal, because the signature of B's foo method is the same as the erasure of A's foo method.

Example 2:

class A {
   public void foo(List list) { }
}

class B extends A {
   @Override
   public void foo(List list) {}
}

This is legal, because the signatures are the same (even if they are raw).

Example 3:

class A {
   public void foo(List list) { }
}

class B extends A {
   @Override
   public void foo(List<Integer> list) {}
}

This is not legal, because the erasure of the overriding method is not taken into account. That is, List<Integer> is compared with the erasure of List, which is still just List, and they're not the same.

Example 4:

class A {
   public void foo(List<String> list) { }
}

class B extends A {
   @Override
   public void foo(List<Integer> list) {}
}

This is again not legal, because the erasure of the overriding method is not taken into account. That is, List<Integer> is compared with the erasure of List<String> (List), and they're not the same.

You cannot change the generic type parameters of the parameters in the overriding method (e.g. List<String> to List<Integer>. You cannot introduce generics when overriding a method that didn't utilize generics (e.g. (List to List<Integer>). However, you can remove generics when overriding (e.g. List<String> to List).

Community
  • 1
  • 1
rgettman
  • 167,281
  • 27
  • 248
  • 326
  • how about using foo(List extends Number>) in the parent class and foo(List) in the subclass..that is not allowed, but why is it so? btw, your explanation is great. – brain storm Feb 11 '14 at 00:47
  • If a parent class's method has `List extends Number>`, then it needs to be able to accept a `List` as an argument. If the child class has `List`, then it can't accept a `List` as an argument. But to override, it must accept the same things the overridden method accepts. The child method can't even have as a parameter `List`, because that would disallow a `List` as an argument, which the superclass would accept. So, the overriding method's signature can't be `List` or `List`; it must match (`List extends Number>`). – rgettman Feb 11 '14 at 00:52
  • The parent class can accept `Number` or anything that `extends Number` such as `Double, Integer` etc. correct? The child class cannot `override` with `List as parameter` and thats simply because `Lists` are `invariant` correct?? – brain storm Feb 11 '14 at 01:51
  • That's correct; the child class's method needs to be `List extends Number>` also, because it also needs to accept everything that the superclass method accepts, e.g. a `List`, a `List`, a `List`. It's that Java generics are invariant, and `List` uses generics. – rgettman Feb 11 '14 at 01:59
  • In the example 4, if i comment out `@override`, there is still compile error. I thought it should now be `overloading`, since `class B inherits foo(List) from A` and it has its own `foo(List) method`. If due to `type erasure`, what we get is the raw type `foo (List)`, then where is the type information stored for it to `cast` back ? – brain storm Feb 11 '14 at 02:14
  • The erasures of the methods counts for overloading. We know it's not overriding, but the erasure of both methods is `List`, so it's not overloading either. You have found the no-man's land between overriding and overloading. Example 4 is neither overloading nor overriding, because you have found signatures that are different enough for it not to be overriding, yet too similar to be overloading. – rgettman Feb 11 '14 at 02:20
  • ok, I am following it now. so when the raw type is stored is as `foo(List)`, where is the type information stored when I do a for-each loop to call `toString` methods on the elements of `List` – brain storm Feb 11 '14 at 02:25
2

Because Java is a strictly-typed language, it has to be careful about covariance and how types work together. Erasure is not an excuse to break the type rules.

For example:

class BaseGood<T> {
    public void doStuff(T elem) {
                // ...
    }
}
class DerivedGood<T> extends BaseGood {
    public void doStuff(Object elem) {
        super.doStuff(elem);
    }
}
class BaseBad {
    public void doStuff(List<Double> list) {
        // ...
    }
}
class DerivedBad extends BaseBad {
    public void doStuff(List<Integer> list) {
        super.doStuff(list);
        // ...
    }
}

Here, we have two different cases for erasure.

With the BaseGood and DerivedGood classes, the two methods have the same erasure: void doStuff(Object elem) and it can be known that T will always be of type Object and therefore the function is type-safe.

With the BaseBad and DerivedBad classes, the two methods have the same erasure: void doStuff(List list); however, the type system cannot convert from a List<Integer> to a List<Double> safely (or to and from any List for that matter). Allowing this conversion would potentially allow attempts to put Doubles into lists of Integers (or the issue that generics moves detection of to compile-time).

Having the methods override friendly after erasure does not mean that the type system cannot check for incompatibilities pre-erasure.

EDIT

Also, real erasure as applied to class/method definitions does not occur at compile-time, it occurs at runtime. Code that references generic methods just compile as if the function was called without generics, with type-safety enforced by the compiler and by run-time checking.

See: Java generics - type erasure - when and what happens

There is also another case where you pass a type-erased generic class to an override and because the type system is unable to check whether you passed a proper list based on the type of the method, it must allow it.

Community
  • 1
  • 1
hsun324
  • 549
  • 3
  • 9
  • ok, I think I am following your explanation. how about using `double[]` and `int[]` in the above, since `arrays` are `covariant`, there should not be an issue, right? – brain storm Feb 11 '14 at 02:04
  • That will not work because `double` and `int` are not on an inheritance hierarchy. `int` is not a super or sub-set of `double`. However, if you override `A[]` with `DerivedFromA[]` then it will compile because the type system can guarantee that all of the elements in `DerivedFromA[]` will be `A`s; however, if you attempt to store a different class inside the `A[]`, you will get a `ClassCastException` (basically, moving type checking to runtime). – hsun324 Feb 11 '14 at 04:14