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.
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).
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.
Related
Problem outline
I'm generifying the better part of my current project's base and I had an idea that I decided to test regarding overriding an abstract method. Here are my test classes in Java:
public abstract class Base {
public abstract <T extends Base> T test();
}
First implementation:
public class Inheritor extends Base {
#Override
public Inheritor test() {
return null;
}
}
Second implementation:
public class Inheritor2 extends Base {
#Override
public <T extends Base> T test() {
return null;
}
}
Question 1
Why does it compile? I admit I had high hopes it would be legal, since it makes the contract not only ensure it returns something that does extend Base, but is more specialized already (so that I don't need to cast the result to my specialized class somewhere later).
All sounds nice but do I really fulfill the contract that the base class forces me into? My overriden implementation in Inheritor loses certain layer of genericness doesn't it? My implementation of this method in Inheritor doesn't ever return an instance of Inheritor2, possibility of which the abstract method seemed to enforce (as both extend Base).
I would like pointing to some excerpt from documentation. My guess is it has something to do with type erasure, would be nice if someone mentioned it's accuracy in his/her answer.
Question 2
Does this procedure have a formal name other than one I stated in the title?
Question 3
Is this possible in C#? Colleague's scratch test seemed to fail on compilation. Is there then a difference in approach to generic abstract method overriding?
Here are the technicalities.
Concerning overriding:
An instance method mC declared in or inherited by class C, overrides
from C another method mA declared in class A, iff all of the following
are true:
A is a superclass of C.
C does not inherit mA.
The signature of mC is a subsignature (§8.4.2) of the signature of mA.
One of the following is true:
mA is public.
[...]
In your case, A is Base and C is Inheritor, Base#test() is mA and Inheritor#test() is mC.
mC is a subsignature of mA because
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.
The erasure of mA is
public abstract Base test()
and mC
public Inheritor test()
is a subsignature. What about the return type?
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.
Following the return-type-substitutable, we see
If R1 is a reference type then one of the following is true:
R1 can be converted to a subtype of R2 by unchecked conversion (§5.1.9).
Inheritor is a subtype of T extends Base through unchecked conversion, so we're all good (though you should have gotten a warning from your compiler).
So to answer your questions:
It compiles because of the rules declared in the Java Language Specification.
It's called overriding.
I don't have a full answer for you, but C# doesn't seem to have type erasure, so these rules wouldn't apply.
The dangers of unchecked conversion would allow you to do
class Inheritor extends Base {
#Override
public Inheritor test() {
return new Inheritor();
}
}
and then
Base ref = new Inheritor();
Inheritor2 wrong = ref.<Inheritor2>test();
which would cause a ClassCastException at runtime. Use it at your own risk.
I can tell you why it should work - Liskov substitution principle
The question to ask is, if you replace Base with Inheritor, or Inheritor2, will all the consumers continue to work without negative consequences? If they expect anything that extends Base from test, then swapping Inheritor2 with Inheritor, or vice versa, will be fine from the perspective a consumer. So, the compiler ought to allow it.
You do really fulfill the contract, which says any subtype of Base can be returned. Any subtype can be one subtype, a random subtype, etc.
Like the commenter Elliott, I believe it's just called overriding a method.
Here's a similar implementation in C# but with generics on the class level.
public abstract class Base<Type>
where Type : Base<Type>
{
public abstract Type test();
}
public class Inheritor:Base<Inheritor>
{
public override Inheritor test()
{
return null;
}
}
public class Inheritor2<Type> : Base<Type>
where Type : Base<Type>
{
public override Type test()
{
return default(Type);
}
}
I'm trying to understand the name clash error I get with the following code:
import java.util.*;
import javax.swing.*;
class Foo<R extends Number> {
public void doSomething(Number n, Map<String, JComponent> comps) {
}
}
class Bar extends Foo {
public void doSomething(Number n, Map<String, JComponent> comps) {
}
}
Error message:
error: name clash: doSomething(Number,Map<String,JComponent>) in Bar
and doSomething(Number,Map<String,JComponent>) in Foo have the same
erasure, yet neither overrides the other
I know I can fix it by either remove the generic type from Foo, or by changing the Bar declaration to class Bar extends Foo<Integer>; what I want to know is why this error occurs in the specific case, but goes away if I remove the comps parameter from each method. I've done some reading about type erasure, but it still appears to me that both methods should have the same erasure with or without generics, and therefore be a valid override in either case. (Note that I haven't even used the generic parameter anywhere yet, which is why I'm so surprised.)
I know that I've added generic types to parent classes before but only got warnings about the subclasses, not errors. Can anyone explain this scenario?
Luiggi is right in the comments. This is a consequence of raw types.
The supertype of a class may be a raw type. Member accesses for the
class are treated as normal, and member accesses for the supertype are
treated as for raw types. In the constructor of the class, calls to
super are treated as method calls on a raw type.
This applies when invoking a supertype method, but also when overriding one.
Take for example, the following
class Bar extends Foo {
public Bar() {
doSomething(1, new HashMap<Number, String>());
}
}
You'll notice that it compiles, even though HashMap<Number, String> is not a type that is assignable to Map<String, JComponent>.
The type of a constructor (§8.8), instance method (§8.4, §9.4), or
non-static field (§8.3) of a raw type C that is not inherited from its
superclasses or superinterfaces is the raw type that corresponds to
the erasure of its type in the generic declaration corresponding to C.
(Note that C in our case is Bar.)
And the same thing happens when trying to override a method. When trying to override the Foo#doSomething(..) method, your Bar class is actually seeing it declared as
public void doSomething(Number n, Map comps) {
}
In other words, every usage of type parameters is erased. So attempting to declare the method
public void doSomething(Number n, Map<String, JComponent> comps) {
}
in the subtype Bar is actually an attempt at overloading, not overriding. And this fails because of type erasure. The proper override, which you can verify with #Override, is
public void doSomething(Number n, Map comps) {
}
Further reading:
What is a raw type and why shouldn't we use it?
In book for scjp preparation I have read following:
Keep the following points in mind for correct overriding. The overriding method
1. Should have the same argument list types (or compatible types) as
the base version.
...
Can you provide example with compatible types arguments ?
class Parent {
public void m(Number n){}
}
class Child extends Parent {
#Override
public void m(/*what can i write here?*/){
}
}
Here's the definition from JLS
Overriding
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.
Moreover, if m1 is not abstract, then m1 is said to implement any and
all declarations of abstract methods that it overrides.
What is subsignature?
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.
Two method signatures m1 and m2 are override-equivalent iff either m1
is a subsignature of m2 or m2 is a subsignature of m1.
What are compatible types?
Collection (raw type) and Collection<String>, because after type erasure they are same
An example of "compatible types":
class C1 {
public void m1 (List<Integer> l) {}
}
class C2 extends C1 {
#Override
public void m1 (List l) {} // legal, this overrides
}
Basically, the only way a parameter list can have "compatible" types without having the exact same types is if the parameters of the overriding method are the same as the parameters of the overridden method with all type parameters removed.
The rule is explained in JLS §8.4.8.1 (SE8), which says that the signature of the overriding method must be a subsignature of the overridden one; following the references, we find that this means that "subsignature" means either it's the same signature or the type erasure of the overridden one.
(P.S. Of course, you shouldn't do this. In real life, overriding methods should have the same parameter types, period.)
(PPS. Note that compatibility only goes one way here. This is not legal:)
class C1 {
public void m1 (List l) {}
}
class C2 extends C1 {
#Override
public void m1 (List<Integer> l) {} // illegal
}
Reading the Javadoc for the #Override annotation, I came across the following rule:
If a method is annotated with this
annotation type compilers are required to generate an error message
unless at least one of the following conditions hold:
The method does override or implement a method declared in a supertype.
The method has a signature that is override-equivalent to that of any public method
declared in Object.
I'm clear on the first point, but I'm unsure about the second one.
What does it mean by "override-equivalent"? How are public methods of Object special in this respect? And why is this not covered under the first criterion?
Moreover, this is only true of the Java 7+ documentation. The Java 6 doc doesn't say anything about override-equivalence. Why the change?
Update:
After further consulting the JLS (Section 8.4.2), I found the following explanation of override-equivalence:
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.
Two method signatures m1 and m2 are override-equivalent iff either m1 is a
subsignature of m2 or m2 is a subsignature of m1.
As far as I can tell, this answers the first question ("What does it mean?") and the third question ("Why doesn't the first condition cover this?").
If I understand correctly (please inform me if I don't!), there is only one case where two methods are override-equivalent and which doesn't fall under the first condition of the original question. This is the case when the erasure of the signature of the subclass method is the same as the signature of the superclass method, but not the other way around.
The second condition of the original question, then, would only come into play when we attempt to add type parameters when attempting to "override" a public method of the Object class. I tried the following simple example to test this, with an unused type parameter:
public class Foo {
#Override
public <T> boolean equals(Object obj) {
return true;
}
}
Of course, this class doesn't compile, because the method doesn't actually override the equals method and thus clashes with it. But I also still receive a compiler error for using the #Override annotation. Am I wrong in assuming that this example meets the second condition for #Override usage? Or is the compiler generating this error despite not being required to?
The reason for this is to allow you to use the #Override annotation in interfaces, which do not inherit from Object but implicitly declare all public methods from Object (see JLS section 9.2 interface members). You are thus allowed to declare an interface like:
interface Bar { #Override int hashCode(); }
However, you would not be allowed to declare the following interface:
interface Quux { #Override Object clone(); }
since the clone() method is not implicitly declared in an interface (it is not public).
This is described in JLS section 9.6.3.4 #Override (the Javadoc for #Override still refers to an old section number)
Your question is basically a design question and JLS explains its:
"The notion of subsignature is designed to express a relationship
between two methods whose signatures are not identical, but in which
one may override the other. Specifically, it allows a method whose
signature does not use generic types to override any generified
version of that method. This is important so that library designers
may freely generify methods independently of clients that define
subclasses or subinterfaces of the library."
Your code is not a valid example of this , see the below code it works:
public class SubSignatureTest extends SignatureTest {
#Override
public List test(Collection p) {
return null;
}
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
class SignatureTest {
public <T> List<T> test(Collection<T> t) {
return null;
}
}
Whole point is that signature of superclass and subclass should be same after erasure.
EDIT:
When we talk of override equivalence then parent class should have generic method and child class should have non generic method. Here is an example to explain this .Below code will not work because child class have generic method. For a moment lets assume that java allowed that then the call in main method will always fail :
class A{
public int compareTo(Object o){
return 0;
}
}
class B extends A implements Comparable<B>{
public int compareTo(B b){
return 0;
}
public static void main(String[] argv){
System.out.println(new B().compareTo(new Object()));
}
}
In class B method will be like this after compilation:
public int compareTo(Object x){
return compareTo((B)x);
}
Which means this is always error: new B().compareTo(new Object()) .
Therefore java will not allow child class to have generic method if parent class has non generic method. So you can't define override equivalence methods for object class.
Hope that clarifies.
I used the post http://lists.seas.upenn.edu/pipermail/types-list/2006/001091.html for reference, it has lot more details.
The following code uses the concept of method overriding in Java.
package pkg;
import java.util.ArrayList;
import java.util.List;
abstract class SuperClass
{
abstract public List<String>getList();
}
final class SubClass extends SuperClass
{
private List<String>list=null;
#Override
public ArrayList<String> getList()
{
list=new ArrayList<String>();
list.add("A");
list.add("B");
return (ArrayList<String>) list;
}
}
final public class Main
{
public static void main(String[] args)
{
SuperClass s=new SubClass();
List<String>list=s.getList();
for(String str:list)
{
System.out.println(str);
}
}
}
By convention, method overriding uses the same signature (with return type) in both super class and subclass. In the above code, the return type of the getList() method in the SuperClass is List and in its subclass the return type is ArrayList. How does method overriding work here?
By the way, it's obvious that ArrayList is an implementation of the List interface but how does the compiler treat the return type here while overriding the getList() method?
Should I believe something like this... The return type of the overridden method is allowed to be a subtype of the overridden method's return type.
Yes.
In early java that was not the case, but it was changed in Java 5.0.
You cannot have two methods in the same class with signatures that only differ by return type. Until the J2SE 5.0 release, it was also true that a class could not override the return type of the methods it inherits from a superclass. In this tip you will learn about a new feature in J2SE 5.0 that allows covariant return types. What this means is that a method in a subclass may return an object whose type is a subclass of the type returned by the method with the same signature in the superclass. This feature removes the need for excessive type checking and casting.
The source of this information is no longer available on the interwebs.
In object-oriented programming, a covariant return type of a method is one that can be replaced by a "narrower" type when the method is overridden in a subclass. A notable language in which this is a fairly common paradigm is C++.
Covariant return types have been (partially) allowed in the Java language since the release of JDK5.0, so the following example wouldn't compile on a previous release:
// Classes used as return types:
class A {
}
class B extends A {
}
// "Class B is more narrow than class A"
// Classes demonstrating method overriding:
class C {
A getFoo() {
return new A();
}
}
class D extends C {
B getFoo() {
return new B();
}
}
More specifically, covariant (wide to narrower) or contravariant (narrow to wider) return type refers to a situation where the return type of the overriding method is changed to a type related to (but different from) the return type of the original overridden method.
The relationship between the two covariant return types is usually one which allows substitution of the one type with the other, following the Liskov substitution principle.
This usually implies that the return types of the overriding methods will be subtypes of the return type of the overridden method. The above example specifically illustrates such a case. If substitution is not allowed, the return type is invariant and causes an compile error.
Reference: https://en.wikipedia.org/wiki/Covariant_return_type
Yes, that is correct. Since an ArrayList is a List, you can return an ArrayList when the original method returned a List.
I checked the byte code with javap command for the OPs example and found out, that the Compiler generates an additional method in the SubClass class:
//1
public java.util.List getList();
which invokes the
//2
public java.util.ArrayList getList();
method and returns it's result.
SubClass sc=new SubClass();
//the ArrayList getList() will be invoked:
sc.getList();
SuperClass s=...;
//the List getList() will be invoked:
s.getList();