Java Unchecked Overriding Return Type

前端 未结 1 503
清酒与你
清酒与你 2021-02-05 15:00

I have a project that has the following components:

public abstract class BaseThing {
    public abstract  ThingDoer g         


        
1条回答
  •  离开以前
    2021-02-05 15:14

    Let's look first at your BaseThing class that you don't want to make generic:

    public abstract class BaseThing {
        public abstract  ThingDoer getThingDoer();
    }
    

    This is not a generic class, but it contains a generic method. Frequently, generic methods like this are designed so that the type is bound by the compiler based on some argument to the method. For example: public Class classOf(T object). But in your case, your method takes no arguments. That is also somewhat common, in cases where the implementation of the method returns something "universally" generic (my term) like this method from the Collections utility class: public List emptyList(). This method takes no arguments, but the type will be inferred from the calling context; it works only because the implementation of emptyList() returns an object that is type-safe in all cases. Due to type erasure, the method doesn't ever actually know the type of T when it's called.

    Now, back to your classes. When you create these subclasses of BaseThing:

    public class SomeThing extends BaseThing {
        public ThingDoer getThingDoer() {
            return Things.getSomeThingDoer();
        }
    }
    
    public class SomeOtherThing extends BaseThing {
        public ThingDoer getThingDoer() {
            return Things.getSomeOtherThingDoer();
        }
    }
    

    Here, you want to override the abstract method from the base class. Overriding the return type is allowed in Java as long as the return type is still valid in the context of the original method. For instance you can override a method that returns Number with a specific implementation that always returns Integer for that method, because Integer is a Number.

    With generics, however, a List is not a List. So while your abstract method is defined to return ThingDoer (for some T extends BaseThing), your overloads that return ThingDoer and ThingDoer are not generally compatible with some unknown T even though SomeThing and SomeOtherThing both extend from BaseThing.

    The caller (from the abstract API) expects some unknown, unenforceable T that cannot be guaranteed to be satisfied by either of your concrete implementations. In fact, your concrete overloads are no longer generic (they return specific, statically-bound type parameters) and that conflicts with the definition in the abstract class.

    EDIT: The "correct" way (no warnings) to define the abstract method should be something like:

    public abstract ThingDoer getThingDoer();
    

    This makes it clear to the caller that it's getting a ThingDoer with its first type parameter bound to something that extends BaseThing (so it can use it as if it were a BaseThing) but the caller will not know the specific implementation when accessed by the abstract API.

    EDIT #2 - Findings from our discussion in chat...

    The OP's original example usage is:

    BaseThing thing = /* ... */;
    thing.getThingDoer().do(thing);
    

    Notice how the same thing reference is passed back into a method in the object returned from that same thing's getThingDoer() method. The object returned by getThingDoer() needs to be tightly bound to the concrete implementation type of thing (according to the OP). To me, this smells like broken encapsulation.

    Instead, I suggest exposing the logical operation as a part of the BaseThing API and encapsulating the delegation to the ThingDoer as an internal implementation detail. The resulting API would look something like:

    thing.doTheThing();
    

    And implemented somewhat like:

    public class SomeThing extends BaseThing {
        @Override public void doTheThing() {
            Things.getSomeThingDoer().do(this);
        }
    }
    
    public class SomeOtherThing extends BaseThing {
        @Override public void doTheThing() {
            Things.getSomeOtherThingDoer().do(this);
        }
    }
    

    0 讨论(0)
提交回复
热议问题