Overriding abstract generic method in Java

浪尽此生 提交于 2020-01-23 16:43:28

问题


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?


回答1:


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:

  1. It compiles because of the rules declared in the Java Language Specification.
  2. It's called overriding.
  3. 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.




回答2:


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);
        }
    }


来源:https://stackoverflow.com/questions/27427798/overriding-abstract-generic-method-in-java

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!