Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 InterfaceAGeneric<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?

like image 641
Omnaest Avatar asked Feb 20 '11 11:02

Omnaest


People also ask

What if return type is different in overriding?

The overriding method was said to be invariant with respect to return type. Java version 5.0 onwards it is possible to have different return types for an overriding method in the child class, but the child's return type should be a subtype of the parent's return type.

Does return type matter in overriding?

Before Java5, it was not possible to override any method by changing the return type. But now, since Java5, it is possible to override method by changing the return type if subclass overrides any method whose return type is Non-Primitive but it changes its return type to subclass type.

Can we use interface as return type in Java?

Since an interface provides all of that, you can call methods on it, just as you can on a regular class. Of course, in order for the method to actually return some object, there needs to be some class that implements that interface somewhere.

Can a method have return type as interface?

If an interface is defined to be the return type of a method then instances of classes derived from that interface can be returned. The benefit of doing that is no different from returning objects of classes derived from a class.


2 Answers

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

like image 181
Christian Semrau Avatar answered Oct 03 '22 11:10

Christian Semrau


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> { ... }
like image 31
Mihir Avatar answered Oct 03 '22 12:10

Mihir