Nathan Merrill
Nathan Merrill

Reputation: 8396

Java Generics and overridding

Why does the following happen:

public class one{
   public <T extends Foo> Bar<Foo> function1() {}
   public Bar<Foo> function2(){}
}
public class two<F extends Foo> extends one{
   public Bar<F> function1(){} //Doesn't throw an error
   public Bar<F> function2(){} //Throws an error
}

By saying <T extends Foo> am I saying that Foo can be overridden with a super type?

Note: My question is not why function2() throws an error...but why function1() doesn't throw an error.

Upvotes: 9

Views: 313

Answers (3)

ZhongYu
ZhongYu

Reputation: 19682

This is probably a compiler bug.

The reason that Two.function1 is considered an overriding method of One.function1 is from http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.2

the signature of Two.function1 is the same as the erasure of the signature of One.function1.

This is to allow legacy subtype (Two) still compile after a supertype (One) is generified.

After that, javac needs to check the return type:

http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.8.3

If a method declaration d1 with return type R1 overrides or hides the declaration of another method d2 with return type R2, then d1 must be return-type-substitutable (§8.4.5) for d2, or a compile-time error occurs.

http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.4.5

R1 is either a subtype of R2 or R1 can be converted to a subtype of R2 by unchecked conversion (§5.1.9), or R1 = |R2|

http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.1.9

There is an unchecked conversion from the raw class or interface type (§4.8) G to any parameterized type of the form G.

Unchecked conversion does not apply here where R1=Bar<F>, R2=Bar<Foo>. That's why Javac reports that function2 return type is conflicting.

Javac should report the same error for function1. I'm guessing that in a previous step, javac took the erasure of One.function1, which is Bar function1(), and mistakenly uses that version to check against Two.function1 - here R2=Bar, therefore R1 is a subtype($4.10.2) of R2, therefore the return type is compatible. (However, if that theory is correct, there shouldn't be a "unchecked" warning)

Upvotes: 6

oconnor0
oconnor0

Reputation: 3254

<T extends Foo> defines a type T that must be some subtype of Foo. This isn't actually used anywhere in one or two.

My guess is this is because how the compiler is erasing the types for the functions is being handled differently since there is a generic type definition scoped to one.function1().

Looking at the JLS 8.4.8.3 defines valid method override. There is an example a bit down about the "unchecked conversion" warning. In there, it links to 8.4.5 describing what is "return-type-substitutable" and from there to 5.1.9 about unchecked conversions.

I do not have an answer, but it appears that something about tagging that method as a generic method (not just a method using a parameterized type) triggers allowing the unchecked conversion to be done - whether intentionally or because of a bug.

Edit: Given

public class Main {
  class Bar<X> {}
  class Foo{}
  class one{
   public <T extends Foo> Bar<Foo> function1() { return null; }
   public Bar<Foo> function2(){ return null; }
  }
  class two<F extends Foo> extends one{
   public Bar<F> function1(){ return null; } //Doesn't throw an error
   public Bar<F> function2(){ return null; } //Throws an error
  }
}

Compiling with javac -Xlint:unchecked Main.java:

Main.java:9: warning: [unchecked] function1() in Main.two overrides <T>function1() in Main.one
   public Bar<F> function1(){ return null; } //Doesn't throw an error
                 ^
  return type requires unchecked conversion from Main.Bar<F> to Main.Bar<Main.Foo>
  where F,T are type-variables:
    F extends Main.Foo declared in class Main.two
    T extends Main.Foo declared in method <T>function1()
Main.java:10: error: function2() in Main.two cannot override function2() in Main.one
   public Bar<F> function2(){ return null; } //Throws an error
                 ^
  return type Main.Bar<F> is not compatible with Main.Bar<Main.Foo>
  where F is a type-variable:
    F extends Main.Foo declared in class Main.two
1 error
1 warning

Upvotes: 2

emesx
emesx

Reputation: 12755

The method function2 is not generic. It returns a Bar<Foo>.

Subclasses may return subclasses of return types in overridden methods. This is called a covariant return type.

But Bar<F> is not a subclass of Bar<Foo>, even if F subclasses Foo - generics are not covariant in Java (arrays are, though).


My IDE actually warns me about function1 too: Unchecked overriding: return type requires unchecked conversion. I believe this is only a warning because of the sole existence of a generic type in base class and type erasure. I'd say you're operating on non-generic Bars anyway here..

Upvotes: 3

Related Questions