4

Second attempt at this question (the initial code wasn't enough to highlight the issue)

Here is the code that does not compile:

interface Player<R, G extends Game>
{
    R takeTurn(G game);
}

interface Game<P extends Player>
{
    void play(P player);
}

abstract class AbstractGame<R, P extends Player>
    implements Game<P>
{
    public final void play(final P player)
    {
        final R value;

        value = player.takeTurn(this);
        turnTaken(value);
    }

    protected abstract void turnTaken(R value);
}

public class XPlayer
    implements Player<Integer, XGame>
{
    @Override
    public Integer takeTurn(final XGame game)
    {
        return (42);
    }
}

public class XGame<P extends Player<Integer, XGame>>
    extends AbstractGame<Integer, XPlayer>
{
    @Override
    protected void turnTaken(final Integer value)
    {
        System.out.println("value = " + value);
    }
}

public class Main
{
    public static void main(final String[] argv) 
    {
        final XPlayer player;
        final XGame   game;

        player = new XPlayer();
        game   = new XGame();
        game.play(player);
    }
}

What I am running up against is trying to get the play method in the AbstractGame to compile. It seems that I have to run in circles with the Game and the Player adding generics to the extends/implements but for the life of me I cannot get it straight.

The play method has to be final in the AbstractGame class, and there is no way to do casting, and I don't want to write another method like the turnTaken one to get it to work if I don't have to.

EDIT: as requested here is the code that compiles, but needs the cast:

interface Player<R, P extends Player<R, P, G>, G extends Game<R, G, P>>
{
    R takeTurn(G game);
}

interface Game<R, G extends Game<R, G, P>, P extends Player<R, P, G>>
{
    void play(P player);
}

abstract class AbstractGame<R, G extends Game<R, G, P>, P extends Player<R, P, G>>
    implements Game<R, G, P>
{
    public final void play(final P player)
    {
        final R value;

        value = player.takeTurn((G)this);
        turnTaken(value);
    }

    protected abstract void turnTaken(R value);
}

class XPlayer
    implements Player<Integer, XPlayer, XGame>
{
    @Override
    public Integer takeTurn(final XGame game)
    {
        return (42);
    }
}

class XGame
    extends AbstractGame<Integer, XGame, XPlayer>
{
    @Override
    protected void turnTaken(final Integer value)
    {
        System.out.println("value = " + value);
    }
}

class Main
{
    public static void main(final String[] argv) 
    {
        final XPlayer player;
        final XGame   game;

        player = new XPlayer();
        game   = new XGame();
        game.play(player);
    }
}
Community
  • 1
  • 1
TofuBeer
  • 60,850
  • 18
  • 118
  • 163

2 Answers2

8

Mixing generics and raw types isn't going to work. If you need these interfaces to reference each other, they also need to reference themselves:

interface Player<R, P extends Player<R, P, G>, G extends Game<R, G, P>>
{
    R takeTurn(G game);
}

interface Game<R, G extends Game<R, G, P>, P extends Player<R, P, G>>
{
    void play(P player);
}

Although this is looking rather hairbrained, and I'm not sure why you need it.

Edit:

I was able to implement your AbstractGame based on the above:

abstract class AbstractGame<R, P extends Player<R, P, AbstractGame<R, P>>>
    implements Game<R, AbstractGame<R, P>, P>
{
    public final void play(final P player)
    {
        final R value;

        value = player.takeTurn(this);
        turnTaken(value);
    }

    protected abstract void turnTaken(R value);
}

However I couldn't quite close the circuit with XGame and XPlayer:

public class XGame
    extends AbstractGame<Integer, XPlayer> //compile error on XPlayer
{

    protected void turnTaken(Integer value) { }
}

public class XPlayer
    implements Player<Integer, XPlayer, XGame> //compile error on XGame
{
    @Override
    public Integer takeTurn(final XGame game)
    {
        return (42);
    }
}

The issue seems to be that each of the generic declarations of XGame and XPlayer needs the other to be correct. This is where your design is truly cyclical. If the compiler 'assumed' each was correct, it would in theory work. But it doesn't.

Edit 2:

How about this:

interface Game<R, G extends Game<R, G>>
{
    void play(Player<R, G> player);
}

interface Player<R, G extends Game<R, G>>
{
    R takeTurn(G game);
}

abstract class AbstractGame<R, G extends AbstractGame<R, G>>
    implements Game<R, G>
{
    public final void play(final Player<R, G> player)
    {
        final R value;

        value = player.takeTurn(self());
        turnTaken(value);
    }

    protected abstract G self();

    protected abstract void turnTaken(R value);
}

public final class XGame extends AbstractGame<Integer, XGame>
{
   protected XGame self() {
      return this;
   }

   protected void turnTaken(Integer value) { }
}

public class XPlayer implements Player<Integer, XGame>
{
    @Override
    public Integer takeTurn(final XGame game)
    {
       return (42);
    }
}

The key here was declaring an abstract method self() in AbstractGame that returns an instance of type G. Extending classes must resolve the inherited type parameter with their own type, and implement self() to return this. This is only suitable for internal code, since an extending class could easily lie, for example:

public class EvilGame extends AbstractGame<Integer, AnotherGame> { ... }

See my answer here and this post for more details on this pattern.

Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • Let me know how it works out - I just fixed some typos in case you copied the code already. – Paul Bellora Feb 23 '12 at 23:31
  • Almost perfect... only issues is that I have to do: value = player.takeTurn((G)this); <--- cast to G which I can live with if there is no way to get rid of it – TofuBeer Feb 23 '12 at 23:40
  • Hmm, I never needed to make that cast in my attempt. Instead, I ran into the issue described above. Could you update your question with your new code? – Paul Bellora Feb 23 '12 at 23:55
  • The difference between our `AbstractGame` classes is that mine resolves the inherited type parameter `G` with its own type - that's why it doesn't need a cast from `this` to the `G` in `Player`. Yours keeps `G`, so it's still a variable. Either way I suggest a redesign along the lines of [yshavit's answer](http://stackoverflow.com/a/9423233/697449), as fun as this was to try and make work. – Paul Bellora Feb 24 '12 at 01:35
  • The other answer doesn't meet the requirements (see my comments to his answer). I am fine with a redesign if there is something better (I cannot think of anything that is both typesafe and lets most of the work happen in the abstract class). – TofuBeer Feb 24 '12 at 01:38
2

As Paul Bellora points out, you're mixing generic and raw types -- and the correct, fully-generic solution is a bit of a mess and requires a lot of redundancy. There's no nice way (that I know of) to do circular (but not recursive) generics in Java.

Rather than struggling with this, I would make both Player and Game generic on just one parameter, the type of value being played with -- what you had as R.

interface Game<R> {
    void play(Player<? extends R> player);
}

interface Player<R> {
    R takeTurn(Game<? super R> game);
}

abstract class AbstractGame<R> implements Game<R> {
    public final void play(Player<? extends R> player) {
        final R value;

        value = player.takeTurn(this);
        turnTaken(value);
    }

    protected abstract void turnTaken(R value);
}

class XPlayer implements Player<Integer> {
    @Override
    public Integer takeTurn(Game<? super Integer> game) {
        return 42;
    }
}

class XGame extends AbstractGame<Integer> {
    @Override
    public void turnTaken(Integer value) {
        System.out.println("value = " + value);
    }
}

public class Main {
    public static void main(String[] argv) {
        XPlayer player = new XPlayer();
        XGame game = new XGame();
        game.play(player);
    }
}

Now, any player who knows how to take R-based moves can play any R-based game.

yshavit
  • 42,327
  • 7
  • 87
  • 124
  • I knew the raw and generic mixing wouldn't work - I just left the code at the point where I was pulling my hair out :-) The issue is that, in the real system, both the player and the game need to know the specific type and casting isn't a useful solution since the places where the casts would go are high up the hierarchy in generic (no pun untended) code. The alternative is one I'll see if I can make work, but I think it would be a big redesign... – TofuBeer Feb 23 '12 at 23:43
  • +1 The real answer is better design, as your answer tries to get at. In mine I'm just seeing if this conundrum is actually possible to compile. – Paul Bellora Feb 23 '12 at 23:57
  • Ah, but I don't want a YGame that also uses Integer as the return value to be played by XPlayers. Think of two guessing games each using Integer but playing totally different. It would be like having a Tennis player trying to play Golf just because they both use a ball. – TofuBeer Feb 24 '12 at 00:32
  • Also the takeTurn method needs the specific details of the game it is playing, just the fact that the game uses Integer is not good enough. – TofuBeer Feb 24 '12 at 00:33
  • @TofuBeer A tennis ball and a golf ball are different; it would actually be like one player playing 8-ball and another playing 9-ball, both at a pool table. It's up to you whether you want to block that at compile time, or whether you want some other invariant to ensure that 8-ball players and 9-ball players don't show up to the same table. Both approaches have their advantages and disadvantages. – yshavit Feb 24 '12 at 02:00
  • I meant that they are both balls, just like 1 and 2 are both Integers. I do want the compiler doing the work rather than people having to remember to do the appropriate instanceof checks. – TofuBeer Feb 24 '12 at 06:31