Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic type error: Instance cannot be converted to its own type?

Tags:

java

generics

I am receiving a compiler error from the following code segment. The error occurs at the indicated line:

static abstract class AbstractRoot<R extends Root<R>> implements Root<R> {

    private final Map<Grouping, Phrase> m_ph;

    AbstractSqlRoot() { this.m_ph = new HashMap<>(); }

    @Override public PhraseBuilder<R>    phrase() { 
        PhraseBuilder<R> fr = (PhraseBuilder<R>) m_ph.get(Grouping.PHRASE);
        if (fr == null) {
-->         fr = new PhraseBuilder<R>(this);
            m_ph.put(Grouping.PHRASE, fr);
        }
        return fr; 
    }

The HashMap m_phis a kind of typesafe heterogenous container. The type linkage is provided by the Grouping enum class, and so the cast that is present in the code is safe.

The class definition for the PhraseBuilder (and its own AbstractPhraseBuilder) follow in the section below:

public static abstract class AbstractPhrase<R extends Root<R>> implements Phrase {

    private final List<Element>     m_elem;
    private final R                 m_root;

    AbstractPhrase(R r) 
            { m_elem = new ArrayList<>();  m_root = r; }
    :
    :
}


public static final class PhraseBuilder<R extends Root<R>> extends AbstractPhrase<R> {

    public PhraseBuilder()          { this(null);  }
    public PhraseBuilder(R r)       { super(r); }
    :
    :
}

The compiler reports "Incompatible types: AbstractRoot<R> cannot be converted to R where R is a type variable: R extends Root<R> declared in class AbstractRoot."

I am confused. AbstractRoot is declared to implement Root<R>, so it should be compatible with a parameter that is defined for "extends Root<R>". I do not see a conflict between the indicated constructor invocation and the class definitions.

Anybody able to help me with this dilemma? I'm stuck.


UPDATE: Thanks to the answers given below, I get where I went wrong. The design of the SqlRoot<R> interface really, really requires that an R be sent to the PhraseBuilder class. The code has been patched as follows to solve this problem:

static abstract class AbstractRoot<R extends Root<R>> implements Root<R> {

    private final Map<Grouping, Phrase> m_ph;

    AbstractSqlRoot() { this.m_ph = new HashMap<>(); }

    @Override public PhraseBuilder<R>    phrase() { 
        PhraseBuilder<R> fr = (PhraseBuilder<R>) m_ph.get(Grouping.PHRASE);
        if (fr == null) {
-->         fr = new PhraseBuilder<R>(typedThis());
            m_ph.put(Grouping.PHRASE, fr);
        }
        return fr; 
    }
    :
    :
    protected abstract R typedThis();
}

The class definition for the subclass ConcreteRoot now includes a method declaration for typedThis(). Not an ideal solution since the method becomes part of the class's exported API, but it solves the problem:

static final class ConcreteRoot extends AbstractRoot<ConcreteRoot> {

    ConcreteRoot() { super(); }

    @Override protected ConcreteRoot typedThis() { return this; }
    :
    :
}
like image 467
scottb Avatar asked Feb 27 '26 10:02

scottb


2 Answers

new PhraseBuilder<R>(this) is trying to invoke this constructor:

public PhraseBuilder(R r)       { super(r); }

The problem is that this is not an R: it's an AbstractRoot<R>, which also happens to be a Root<R>, but it's simply not an R. Perhaps you meant this?

fr = new PhraseBuilder<Root<R>>(this);
like image 115
StriplingWarrior Avatar answered Feb 28 '26 23:02

StriplingWarrior


The problem is that this is a Root<R>, not necessarily an R. Sound confusing? It is. Here's a counterexample where it would break:

public class MyRootA extends AbstractRoot<MyRootA> {
    //R is MyRootA - "this" is an instance of R
}

public class MyRootB extends AbstractRoot<MyRootA> {
   //R is MyRootA - "this" is NOT an instance of R
}

In other words, there's nothing that says that R is the same type as this.

Self-typing like this is often counterproductive. It's usually sufficient to just use the more generic type Root<R>. If you really want this, one trick is to obtain a reference to this indirectly:

 fr = new PhraseBuilder<R>(this.typedThis());

Where in Root<R> you declare:

 R typedThis();

and the implementation at the concrete class will normally just be:

 public MyRootA typedThis() {
    return this;
 }
like image 20
Mark Peters Avatar answered Feb 28 '26 22:02

Mark Peters



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!