4

As mentioned below (to remark it as I might have explained myself badly):

I want to understand the principle behind this issue so that I can apply that knowledge to the real problem.


ISSUE START

I am working in a system intended to be an abstract library to be used by many subsystems. The idea is to have an standard behaviour extensible by implementations. My problem is that java compiler is not able to inferr method parameter type, even though it makes no sense since the boundaries are well set on every related generic class/method.

Below there is an example, the minimum example to reproduce the problem. I am aware it looks a bit silly in this example, but that is because of the simplification:

    public class Test {
    public static void main(String[] args) {
        Gen<? extends Base> gen = new HijaGen();
        gen.applyTo(Hija.EXAMPLE);
    }
    private interface Base {
        String getName();
    }
    private enum Hija implements Base {
        EXAMPLE;
        @Override
        public String getName() {
            return this.name();
        }
    }
    private interface Gen<T extends Base> {
        boolean applyTo(T base);
    }
    private static class HijaGen implements Gen<Hija> {
        @Override
        public boolean applyTo(Hija base) {
            return false;
        }
    }
}

Basically it says that applyTo expects a subclass of Base and Hija is not valid, which from my perspective makes no sense.

Thanks in advance.

EDIT:

This code is for a library, so that the solution could not be specifying the type since then it could not be extensible through particular implementations.

I am already aware that if I specify the generic type instead of throwing a type wildcard it will perfectly work. But my question is how is it possible that even though Hija subclasses Base, and method firm requires a subclass of Base it will never compile...

I want to understand the principle behind this issue so that I can apply that knowledge to the real problem.

Diego Farras
  • 77
  • 1
  • 7
  • The statement "this code is for a library" does not really help. It is important to know how the library is supposed to be accessed. The `main` method you provide is a sample call, but not one you'd expect to need a library for. What variable types will a client be concerning himself with when using the lib is the relevant question. – daniu Jan 13 '20 at 13:59
  • @daniu I think you are focused on something not important. The specific question is: How is it possible that this method is not accepting Hija eventhough Hija satisfies (at least theoretically) what the method firm requires? The mention about the library was just intended as a clarification about why I can not just use Gen which is the fast solution – Diego Farras Jan 13 '20 at 14:21
  • Well the answer you marked as accepted contains exactly what I was going for with my comment, namely that the call into the library will probably need to be changed. – daniu Jan 13 '20 at 14:52

3 Answers3

4

A Gen<? extends Base> is a Gen of something which extends Base. It applies to some specific type of Base but we do not know which. In practice, this means you will never be able to call applyTo on a variable with type Gen<? extends Base>, except by passing null.

Change your code to

Gen<Hija> gen = new HijaGen();
gen.applyTo(Hija.EXAMPLE);

I suspect you'll probably say you can't do that, because your code is just an example, and the above is not possible in the real code. In which case you will need to give a better example

Michael
  • 41,989
  • 11
  • 82
  • 128
  • Or `Gen super Hija>`. – Tom Hawtin - tackline Jan 13 '20 at 12:28
  • @TomHawtin-tackline or just `HijaGen` – Michael Jan 13 '20 at 12:38
  • 1
    I think there is a general principle the OP is after rather than just getting some random code to compile. – Tom Hawtin - tackline Jan 13 '20 at 13:00
  • @TomHawtin-tackline See first paragraph – Michael Jan 13 '20 at 13:09
  • Hi Thanks to all, I will try to clarify a bit more: This code is for a library, so that the solution could not be specifying the type since then it could not be extensible through particular implementations. I am already aware that if I specify the generic type instead of throwing a type wildcard it will perfectly work. But my question is how is it possible that even though Hija subclasses Base, and method firm requires a subclass of Base it will never compile... I want to understand the principle behind this issue so that I can apply that knowledge to the real problem. – Diego Farras Jan 13 '20 at 13:41
  • @DiegoFarras See first paragraph. – Michael Jan 13 '20 at 13:41
2

I think there is a similar problem explained in the docs here and the problem that they propose is to make a helper (or wrapper) to clarify the types. So I guess you can try adding this function:

private static <T extends Base> boolean applyTo(Gen<T> gen, T base){
    return gen.applyTo(base);
}

And change the main function as follows:

public static void main(String[] args) {
    boolean b = applyTo(new HijaGen(), Hija.EXAMPLE);
}
1

On the lines of what Carlos has mentioned, there is one more way to resolve this. It can be used if you are sure of the suppliers of the implementation of Gen<T>.

Here, instead of the helper to call applyTo(), we define a factory method to create the instance of the Gen implementation and cast it to Gen<Base>, which in my opinion is safe for all practical purposes. Note that the factory method get() need not be static. The rest of the code remains unchanged from your sample.

public static void main( String[] args ){
    Gen<Base> gen = (Gen<Base>) get();
    gen.applyTo( Hija.EXAMPLE );
}

static Gen<? extends Base> get(){
    /* Create the instance of the Gen interface implementation here. */
    return new HijaGen();
}

Explanation

Gen<? extends Base> expects to refer to an instance of an implementation of Gen that uses a type that is either Base or an sub-type of it. Since a "sub-type" can be from one of many possible hierarchies from Base downward (as shown in the picture below), it cannot be sure that the parameter passed to applyTo() method is of the same child hierarchy path and not just a 'relative' with the same ancestral parent Base. That is why it won't allow a call to applyTo() with its reference being of Gen<? extends Base> for a parameter of with reference as Base.

However, when the reference is Gen<Base>, it knows that applyTo() will accept any parameter that is a child type of Base. And hence, it stops worrying about type mismatch.

I have tried to show what I mean by different child hierarchy paths. It is like the case of a family tree.

enter image description here

Sree Kumar
  • 2,012
  • 12
  • 9