0

I have a (for me) complex Java generics problem. I read through some documentation and understand some but certainly not all of what I should. Basically, for me, trying to solve it would result in try and error.

In the following, I give a condensed example of my code, once without any generics (so one can hopefully understand what I want to achieve) and the other with some additions that come closer to the solution. Please correct my second version and/or point me to specific documentation. (I have general documentation of Java generics. But my code seems to have several interfering challenges and it is hard to a correct solution)

About my example: There is an abstract base type and several implementing variants (only one is given). Method combine() calls getOp1(), which decides (depending on <some condition>) if it should operate on its own instance or on a new one. After the calculation, it returns the target instance.

abstract class Base {
    protected final Base getOp1() {
        if(Util.isConditionMet()) { return getNewInstance(); }
        else { return this; }
    }
    protected abstract Base getNewInstance(); // returns a new instance of an implementing class
    public abstract Base combine(Base other);
}

class Variant extends Base {
    public Variant getNewInstance() { return new Variant(); }   
    public combine(Variant op2) {
        Variant op1 = getOp1();
        op1.calculate(op2);
        return op1;
    }
    private void calculate(Variant other) { /* some code */ }
} 

The version with some generics added. This version is faulty and does not compile.

abstract class Base<T extends Base<T>> {
    protected final T getOp1() {
         if(Util.isConditionMet()) { return getNewInstance(); }
         else { return this; }
    }
    protected abstract T getNewInstance(); // returns a new instance of an implementing class
    public abstract T combine(T other);
}

class Variant<T extends Variant<T>> extends Base<T> {
    protected T getNewInstance() { return new Variant(); }  
    public T combine(T op2) {
        T op1 = getOp1();
        op1.calculate(op2);
        return op1;
    }
    private void calculate(T other) { /* some code */ }
}
Ulrich Scholz
  • 2,039
  • 4
  • 30
  • 51
  • 1
    `...and does not compile.` Please include compiler error messages with respective line numbers – default locale Oct 16 '15 at 13:35
  • Well your compiler errors most certainly have to do with you returning `this` in `Base's getOp1` method. How can you return `this` from an abstract object? The whole point of an abstract object is that it cannot be instantiated. – thatidiotguy Oct 16 '15 at 13:37
  • @thatidiotguy because he is delegating to getNewInstance() which is also abstract – Chris K Oct 16 '15 at 13:38
  • @ChrisK In the else he returns this. – thatidiotguy Oct 16 '15 at 13:41
  • ... So what is ``? – sdgfsdh Oct 16 '15 at 13:42
  • 1
    note that `this` which your return from `getOpt()` is not of type `T`, it is always of type `Base`, and `getNewInstance()` thus also should return `Base` – Alex Salauyou Oct 16 '15 at 13:42
  • It is mock up code, so I did not compile this particular code piece. For example, on line `return this`: Type mismatch: cannot convert from Base to T. On line `return new Variant()`: Type mismatch: cannot convert from Variant to T – Ulrich Scholz Oct 16 '15 at 13:43
  • Second, `calculate()` is defined only for `Variant` and cannot be invoked on `op1` which is of type `T`. – Alex Salauyou Oct 16 '15 at 13:45
  • @Sasha Salauyou. But then, shouldn't `combine()` of `Base` also return `Base`? And which type should return `combine()` of `Variant`? – Ulrich Scholz Oct 16 '15 at 13:46
  • 1
    @UlrichScholz What you seem to be trying to enforce is that for every concrete subclass `C` of `Base`, `getOp1` always returns a `C`. It is not possible to express this in Java (although I believe it is possible in other languages). For this reason, I don't think generics help here. All you are doing is adding noise. – Paul Boddington Oct 16 '15 at 13:57

2 Answers2

0

I have seen a couple of such combinational, operational classes, though never too elaborate. Maybe inheritance is not the right tool.

Better to use a lookup mechanism for capabilities, features.

class Base {

    // Untyped
    private Map<Class<?>, Object> capabilities = new HashMap<>();

    protected <I> void register(Class<I> intf, I obj) {
        capabilities.put(intf, obj);
    }

    public <T> Optional<T> lookup(Class<T> intf) {
        Object obj = capabilities.get(intf);
        return obj == null ? Optional.emtpy() : Optional.of(intf.cast(obj));
    }
}

interface Flying {
    void fly(double altitude);
}

Base pelican = new Pelican();
Flying flying = pelical.lookup(Flying.class).orElse(null);
flying.fly(0.5);

This also allows dynamic changes, and combining things with respect to two aspects.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
0

To make this code working, you need to resolve incompatibility type issues: replace T returning types by Base<T> and cast result of Variant#getOp1() to Variant<T> to allow invoke calculate() on it (this is safe here because Variant#getOp1() always returns Variant:

abstract class Base<T extends Base<T>> {
    protected final Base<T> getOp1() {
         return condition() ? getNewInstance() : this;
    }
    protected abstract Base<T> getNewInstance(); 
    public abstract Base<T> combine(T other);
}



class Variant<T extends Variant<T>> extends Base<T> {
    protected Base<T> getNewInstance() { 
        return new Variant(); 
    }

    public Base<T> combine(T op2) {
        Variant<T> op1 = (Variant<T>) getOp1();   // <- explicit cast
        op1.calculate(op2);
        return op1;
    }

    private void calculate(Base<T> other) { 
        // ...
    }
}

Btw, I still see no reason of such complicated type structure.

Alex Salauyou
  • 14,185
  • 5
  • 45
  • 67
  • How to use Variant>? if I want to use Variant, I have to use a child of Variant to replace T, but I did not have Variant. It is same as the chicken and egg. Can your give me an example? Thanks – xxxzhi Oct 17 '15 at 09:03
  • @Sasha Salauyou. You probably meant `private void calculate(Variant other) {`, didn't you? Just because your code piece is marked as solution – Ulrich Scholz Oct 17 '15 at 14:38