开发者

Java - Overriding return type of extended interface when return type uses generics for own method parameter types

i've stumbled upon a curiosity in the java inheritance, and I wanted you to ask for better ideas on that:

Assume two interfaces A and A1

Interface A1 extends A

Interface A has a method which returns a generic type.

The generic type would be like GenericType<T>.

A basic idea is now to change this generic return type from GenericType<Object> in Interface A into GenericType<String> in Interface A1

Well seems to be easy at first (bad things will come later on)

We declare Interface A like

public interface InterfaceA {
  public GenericType<? extends Object> getAGenericType();  
}

and Interface A1 like

public interface InterfaceA1 extends InterfaceA
{
  @Override
  public GenericType<String> getAGenericType();
}

As you see we are forced to write GenericType<? extends Object> in Interface A itself to allow overriding it with generic based "subclasses". (In fact the generic parameter of the generictype is subclassed not the generic type itself)

Now assume the GenericType has its own method looking like:

public interface GenericType<D>
{
  public void doSomethingWith( D something );
}

Now trying to instantiate A1 works great.

Rather trying to instantiate A will suck. To see why look at this "use the interface" class:

public class LookAtTheInstance
{
  @SuppressWarnings("null")
  public static void method()
  {
    InterfaceA a = null;
    InterfaceA1 a1 = null;

    GenericType<String> aGenericType = a1.getAGenericType();

    GenericType<? extends Object> aGenericType2 = a.getAGenericType();
    Object something = null;
    aGenericType2.doSomethingWith( something );
  }
}

You ask: "And now?"

It does not work on the last lines. In fact the parameter "something" is not even from type "Object" it is from Type "? extends Object". So you cannot pass the declared "Object" type. You can't pass anything at all.

So you end up declaring nice interfaces which, as it turns out, cannot be instantiated right.

Do you have ideas how to model such a use case, where the subclasses will have to override the return type, while the return type is a generics?

Or how would you go around such a model case?

Or am I just missing a simple point in the generic declaration and my example is possible this way?

----------- (1) edit due to answers -----------

A very good basic idea is making the interface A more abstract! I had exactly the same idea first, but... (this has to come)

Assume doing this:

We introduce a new interface AGeneric

public interface InterfaceAGeneric<T>{
  public GenericType<T> getAGenericType();
}

Now we will have to extend A and A1 from this new interface:

public interface InterfaceA extends Interface开发者_运维技巧AGeneric<Object>{}
public interface InterfaceA1 extends InterfaceAGeneric<String>{}

That works fine, althought it breaks the path of the original inheritance.

If we want A1 still be extendable from A, we have to change A1 to

public interface InterfaceA1 extends InterfaceA, InterfaceAGeneric<String>{}

and there a problem is again. This does not work, since we extend indirectly the same interface with different generic types. This is unfortunately not allowed.

You see the problem?

-

And to point to another circumstance:

If you cast the GenericType<? extends Object> to GenericType<Object> it obviously works. Example:

public class LookAtTheInstance
{
  public static void main( String[] args )
  {
    InterfaceA a = new InterfaceA()
    {
      @Override
      public GenericType<? extends Object> getAGenericType()
      {
        return new GenericType<Object>()
        {
          @Override
          public void doSomethingWith( Object something )
          {
            System.out.println( something );
          }
        };
      }
    };
    ;

    @SuppressWarnings("unchecked")
    GenericType<Object> aGenericType2 = (GenericType<Object>) a.getAGenericType();

    Object something = "test";
    aGenericType2.doSomethingWith( something );
  }  
}

So it seems for me that the resolving of the parameter type of the method

public interface GenericType<D extends Object>
{
  public void doSomethingWith( D something );
}

is wrong.

If D is unified with "? extends Object" why the parameter type is not forced to be "Object"?

Wouldnt this make more sence?


A basic idea is now to change this generic return type from GenericType in Interface A into GenericType in Interface A1

This is not possible, because Java Generics are invariant. [1]

As you found out, you cannot have an interface declaring a method that returns GenericType<Object> and in a sub interface override the method to return GenericType<String>: The latter return type is not a subtype of the former. And for good reason!

You tried to

extend indirectly the same interface with different generic types. This is unfortunately not allowed.

There is no way this could possibly work: E.g. what should be the type of E in public E set(int index, E element) in a class that implemented both List<String> and List<Object>? Your subclassed interface would have to produce a similar hybrid: The return value of getAGenericType in the sub interface would have to implement both the GenericType<String> and the GenericType<Object> interface. And as we saw, this is impossible.

The compiler does not know what you are going to do with the type parameter in GenericType (although it theoretically could find out, it doesn't). If you had a variable of type GenericType<String> and assigned a GenericType<Object> to it, you may very well end up putting a Long instance where a String is expected, and get a ClassCastException where you won't expect one.

In the doSomethingWith method of your variable GenericType<? extends Object> aGenericType2 you can pass one thing: null. null is the only object reference that has a subtype of ? extends Object. The lower bound type of ? extends Object is the null type, which cannot be expressed in Java, and only implicitly exists as the type of the null reference.

[1] http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29#Java


I don't know if this is what you are expecting, but you can declare your interface something like:

public interface Interface <K extends Object> { ... } 

While your class might look like:

public class InterfaceImpl implements Interface<String> { ... }


@Override annotation:

When overriding a method, you might want to use the @Override annotation that instructs the compiler that you intend to override a method in the superclass. If, for some reason, the compiler detects that the method does not exist in one of the superclasses, it will generate an error.

With this annotation you cannot change return type of function.

If you want to override return type, just make interface A more abstract, add generic to this interface:

public interface InterfaceA<T> {
  public GenericType<T> getAGenericType();  
}

Sample about overriding a generic method in a generic class.


The trouble is that InterfaceA doesn't know what type it's holding. If you get InterfaceA to take a generic argument then you could do this:

public interface InterfaceA<T>
{
  public GenericType<T> getAGenericType();  
}

public interface InterfaceA1 extends InterfaceA<String>
{
  @Override
  public GenericType<String> getAGenericType();
}

public class LookAtTheInstance
{
  @SuppressWarnings("null")
  public static void method()
  {
    InterfaceA<String> a = null;
    InterfaceA1 a1 = null;

    GenericType<String> aGenericType = a1.getAGenericType();

    GenericType<String> aGenericType2 = a.getAGenericType();
    String something = null;
    aGenericType2.doSomethingWith( something );
  }
}


I'm several years late to the party, but I found this page while searching for a related question and none of the answers really hit on the central issue, which I think is worth clarifying. Let's look at a slightly-more-fleshed-out example:

interface GenericType<D> {
    D getAValue();
    void doSomethingWith(D value);
}

class StringType implements GenericType<String> {
    @Override
    public String getAValue() {
        return "Hello World";
    }

    @Override
    public void doSomethingWith(final String value) {
        System.out.println(value.length());
    }
}


interface InterfaceA {
    GenericType<? extends Object> getAGenericType();
}

interface InterfaceA1 extends InterfaceA {
    @Override
    GenericType<String> getAGenericType();
}

class AnActualA1 implements InterfaceA1 {
    @Override
    public GenericType<String> getAGenericType() {
        return new StringType();
    }
}


class LookAtTheInstance {
    public static void method() {
        InterfaceA1 a1 = new AnActualA1();

        // 'g1' is a StringType, which implements GenericType<String>; yay!
        GenericType<String> g1 = a1.getAGenericType();

        // Everything here is fine.
        String value = g1.getAValue();
        g1.doSomethingWith("Hello World");


        // But if we upcast to InterfaceA???
        InterfaceA a = (InterfaceA) a1;

        // Note: a.getAGenericType() still returns a new StringType instance,
        // which is-a GenericType<? extends Object>.
        GenricType<? extends Object> g = a.getAGenericType();

        // StringType.getAValue() returns a String, which is-an Object; yay!
        Object object = g.getAValue();

        // StringType.doSomethingWith() method requires a String as the parameter,
        // so it is ILLEGAL for us to pass it anything that cannot be cast to a
        // String. Java (correctly) prevents you from doing so.

        g.doSomethingWith(new Object()); // Compiler error!
    }
}

Conceptually, GenericType is NOT a GenericType, since a GenericType can only doSomethingWith() Strings, while a GenericType needs to be able to doSomethingWith() any object. GenericType is a compromise which the compiler allows you to use as a "base class" for any GenericType where D is-an Object, but only allows you to use a reference of that type to call methods that are type-safe for any possible runtime value of '?' (such as getAValue(), whose return value can always be safely cast to an Object since D is-an Object regardless of runtime type).

It's hard to tell what (if anything) the original poster was actually trying to model with this code, and in particular how much of the generic-ness of GenericType was really needed, but perhaps the inheritance should have gone the other way around?

/**
 * I can do something with instances of one particular type and one particular
 * type only.
 */
interface GenericType<D> {
    void doSomethingWith(D value);
}

/**
 * I can do something with instances of any type: I am-a GenericType<String>
 * because I can totally do something with a String (or any other kind of
 * Object).
 */
interface NonGenericType extends GenericType<Object>, GenericType<String> {
    @Override
    void doSomethingWith(Object value);
}


interface StringHandlerFactory { // nee InterfaceA1
    GenericType<String> getAGenericType();
}

/**
 * I extend StringHandlerFactory by returning a NonGenericType (which is-a
 * GenericType<String>, satisfying the interface contract, but also so much
 * more).
 */
interface ObjectHandlerFactory extends StringHandlerFactory { // nee InterfaceA
    @Override
    NonGenericType getAGenericType();
}

The downside being that there's no good way to express to the java compiler that NonGenericType extends GenericType, even though conceptually it could in this case, since GenericType never uses D as a return value. You have to manually specify each GenericType that you want it to extend. :(


So you end up declaring nice interfaces which, as it turns out, cannot be instantiated right.

I think that the purpose of InterfaceA is not to be instantiated at all, because one of its dependable classes are generic. That's what you meant declaring:

public GenericType<? extends Object> getAGenericType()
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜