cyclical generics (try 2)

前端 未结 2 1622
甜味超标
甜味超标 2020-12-11 08:50

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<         


        
相关标签:
2条回答
  • 2020-12-11 09:15

    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.

    0 讨论(0)
  • 2020-12-11 09:24

    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.

    0 讨论(0)
提交回复
热议问题