【问题标题】:cyclical generics (try 2)循环泛型(尝试 2)
【发布时间】:2012-02-23 23:21:39
【问题描述】:

第二次尝试this 问题(最初的代码不足以突出问题)

这里是没有编译的代码:

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);
    }
}

我遇到的是试图让 AbstractGame 中的 play 方法编译。似乎我必须在游戏和播放器中循环运行,将泛型添加到扩展/实现,但对于我的生活,我无法弄清楚。

play 方法在 AbstractGame 类中必须是 final 的,并且没有办法进行强制转换,如果我不需要的话,我不想写像 turnTaken 那样的另一个方法来让它工作.

编辑:这里要求的是编译的代码,但需要演员:

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);
    }
}

【问题讨论】:

  • Ech,你似乎遇到了 java 通用地狱,就像我曾经一样。然后我放弃了泛型,转而使用接口。
  • 如何通过接口实现?我不介意,当我能弄明白的时候:-)
  • 我的意思是,我没有使用泛型,而是创建了需要实现适当对象的接口,并且我的方法采用了这些接口...
  • 嗯...对我的问题没有任何帮助。

标签: java generics


【解决方案1】:

混合泛型和原始类型是行不通的。如果需要这些接口相互引用,它们也需要自己引用:

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);
}

虽然这看起来有点脑筋急转弯,但我不确定你为什么需要它。

编辑:

我能够基于上述实现您的AbstractGame

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);
}

但是我无法用XGameXPlayer 关闭电路:

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);
    }
}

问题似乎是XGameXPlayer 的每个通用声明都需要另一个正确。这是您的设计真正具有周期性的地方。如果编译器“假设”每个都是正确的,那么理论上它会起作用。但事实并非如此。

编辑 2:

这个怎么样:

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);
    }
}

这里的关键是在AbstractGame 中声明一个抽象方法self(),它返回一个G 类型的实例。扩展类必须用自己的类型解析继承的类型参数,并实现self()以返回this。这仅适用于内部代码,因为扩展类很容易撒谎,例如:

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

有关此模式的更多详细信息,请参阅我的回答 herethis post

【讨论】:

  • 让我知道它是如何工作的 - 我只是修正了一些拼写错误,以防你已经复制了代码。
  • 几乎完美...唯一的问题是我必须这样做: value = player.takeTurn((G)this);
  • 嗯,在我的尝试中,我从来不需要做那个演员。相反,我遇到了上述问题。你能用你的新代码更新你的问题吗?
  • 我们的AbstractGame 类之间的区别在于,我的使用自己的类型解析继承的类型参数G - 这就是为什么它不需要从this 转换为GPlayer&lt;R, P, G&gt;。你的保留G,所以它仍然是一个变量。无论哪种方式,我都建议按照yshavit's answer 的方式重新设计,就像尝试制作一样有趣。
  • 另一个答案不符合要求(请参阅我的 cmets 对他的回答)。如果有更好的东西,我可以重新设计(我想不出任何既是类型安全又让大部分工作发生在抽象类中的东西)。
【解决方案2】:

正如 Paul Bellora 所指出的,您正在混合泛型和原始类型——正确的、完全泛型的解决方案有点混乱,需要大量冗余。在 Java 中没有很好的方法(据我所知)来执行循环(但不是递归)泛型。

我不会为此苦苦挣扎,而是将PlayerGame 都设置在一个参数上,即正在使用的值的类型——你所拥有的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);
    }
}

现在,任何知道如何采取基于R 动作的玩家都可以玩任何基于R 的游戏。

【讨论】:

  • 我知道原始和通用的混合是行不通的——我只是把代码留在了我拔头发的地方 :-) 问题是,在实际系统中,玩家和游戏需要知道具体的类型,并且强制转换不是一个有用的解决方案,因为强制转换的位置在通用(没有双关语)代码的层次结构中较高。另一种选择是我会看看我是否可以工作,但我认为这将是一次重大的重新设计......
  • +1 真正的答案是更好的设计,因为你的答案试图得到。在我看来,我只是看看这个难题是否真的可以编译。
  • 啊,但我不希望 XPlayers 玩同样使用 Integer 作为返回值的 YGame。想想两个猜谜游戏,每个游戏都使用整数,但玩起来完全不同。这就像一个网球运动员仅仅因为他们都使用球而尝试打高尔夫球。
  • 另外 takeTurn 方法需要它正在玩的游戏的具体细节,只是游戏使用 Integer 还不够好。
  • @TofuBeer 网球和高尔夫球不一样;这实际上就像一个球员打 8 球,另一个球员打 9 球,都在台球桌上。您是否想在编译时阻止它,或者是否需要其他不变量来确保 8-ball 球员和 9-ball 球员不会出现在同一张桌子上,这取决于您。这两种方法各有优缺点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-09
  • 2011-02-22
  • 2012-02-25
  • 2013-02-08
  • 2013-12-15
相关资源
最近更新 更多