当等于无法确定时,Java等于

时间:2012-06-19 15:21:35

标签: java equals

我想知道在(a)没有足够的信息来确定是否适用于C类对象的equals(和hashCode)方法时,适当的Java编程范例是什么? C的两个实例相等或者(b)调用方法不能确定两个C实例是否相等。

例如,在我的项目中,我有一个PlayingCard课程。在我看来,如果PlayingCard面朝上,那么调用方法应该可以访问其属性,但如果它面朝下,那么这些属性应该保持未知:

class PlayingCard {
    private Rank rank;
    private Suit suit;
    private boolean isFaceDown;

    public PlayingCard(Rank rank, Suit suit, boolean isFaceDown) {
        this.rank = rank;
        this.suit = suit;
        this.isFaceDown = isFaceDown;
    }

    public Rank getRank() { return isFaceDown ? null : rank; }

    public Suit getSuit() { return isFaceDown ? null : suit; }

似乎,为了Java Collections Framework,如果两个扑克牌具有相同的等级和套装,那么它们应该是相同的:

    public boolean equals(Object obj) {       // attempt #1
        if(this == obj) return true;
        if(obj == null) return false;
        if(!(obj instanceof PlayingCard)) return false;
        PlayingCard other = (PlayingCard) obj;
        if(rank != other.rank) return false;
        if(suit != other.suit) return false;
        return true;
    }
 }

但这显示了太多信息:

class Malicious {

    public Rank determineRankOfFaceDownCard(PlayingCard faceDownCard) {
        Set<PlayingCard> allCards = /* a set of all 52 PlayingCards face up */;
        for(PlayingCard c : allCards) {
            if(c.equals(faceDownCard)) {
                return c.getRank();
            }
        }
        return null;
    }
}

使用getRank和getSuit`方法似乎也不起作用:

    public boolean equals(Object obj) {       // attempt #1
        if(this == obj) return true;
        if(obj == null) return false;
        if(!(obj instanceof PlayingCard)) return false;
        PlayingCard other = (PlayingCard) obj;
        if(getRank() != other.getRank()) return false;
        if(getSuit() != other.getSuit()) return false;
        return true;
    }
}

/* outside the PlayingCard class */

Set<PlayingCard> s = new HashSet<PlayingCard>();
s.add(new PlayingCard(Rank.ACE, Suit.SPADES, true));
s.contains(new PlayingCard(Rank.TWO, Rank.HEARTS, true)); // returns true

其他开发者如何应对这种情况?这种情况下投掷某种RuntimeException是否合适?感谢您的任何意见和建议。

6 个答案:

答案 0 :(得分:6)

您可以在equals方法中添加此条件:

if(this.isFaceDown || other.isFaceDown) return false;

我认为这是完全隐藏卡片的唯一方法,如果它面朝下。问题是,当你将它添加到一个集合时,如果你要添加的卡面朝下,你可能会有重复。

答案 1 :(得分:3)

我不确定我是否遗漏了某些东西,但也许在课堂外面应该有一些逻辑来保持比较器在不可见时不会比较卡片?

答案 2 :(得分:3)

我不相信多态是这里最好的答案。无论是面朝上还是面朝下的卡都不会更改卡,它是卡的 STATE 。这是逻辑的位置问题。如果您的应用知道卡片面朝下,为什么它甚至会费心检查是否相等,因此无需区分面朝上equals()和面朝下equals().

在这里详细阐述其他答案中的一些建议,可能有方法可以识别不同的集合,其中一个是面朝下的(如绘图板)或面朝上的(如丢弃堆)但有很多情况下OP没有说明堆栈的概念不太有用。在标准solitare中你可以有以下三种组合中的任何一种:所有面朝下(可以说是玩家即将翻转顶部牌),所有面朝上,或者在底部面朝下混合,面朝上一个或多个卡在上面。在二十一点中,一堆(牌上牌)的大小总是一个,玩家通常都有所有面朝上的牌,而经销商有一个面朝下和一个或多个面朝上。

关注.equals()方法有点像过早优化。备份一步并考虑域名。您的Card属性为SuiteRankDeck只是Card(s)的集合,所有复杂性都属于使用Deck的游戏。也许在Blackjack中你会定义一个具有多个属性的Hand,最重要的是Card的集合,你可以用逻辑确定一只手的点值,是否是'软'当比较两只手时,你不比较单个牌,而是手牌的最终价值,即使在他们有一张面朝下的牌时,经销商的情况也是有效的。< / p>

问题中的恶意情况似乎可疑,因为我不得不问,谁的系统正在运行游戏?如果它是一个多客户端系统,它可能是一个有效的问题,但答案是不给每个客户每个人的卡状态。防止这种类型的作弊的典型架构将是中立的VM /服务器负责游戏状态并评估输赢。如果游戏不是多客户端,那么即使运行游戏的CPU可以完全访问游戏数据,也只有一个受影响的玩家(CPU的所有者)并且试图阻止恶意篡改的历史悠久失败 - 去了解过去30年来用于游戏的复制保护方案 - 恶意场景似乎再次尝试复杂的设计解决方案太罕见/偏执。

无论如何,这就是我解决问题的方法。

答案 3 :(得分:1)

这个问题似乎有几个不同的层......

但是我觉得你真正喜欢的是卡片可能需要两个课程,这取决于它们是面朝上还是面朝下。

在设计OO课程时,逻辑思考是有益的,关于如果面朝上或面朝下,我们是否对待卡片会有所不同。

我认为设计良好的课程系统中的惯例是这样的:两张面朝下的卡片不应该有相同的方法,因为你可以从其他游戏逻辑决定它们是否应该是比较。

答案 4 :(得分:1)

对象只需要彼此相等。当人们认为可以在类层次结构的不同层次上查看对象时

所以不支持面朝上或朝下的卡片的Deck可能等于Deck的另一个子类,它支持面朝上或朝下的卡片,只要它是{{1根据{{​​1}}确定平等的政策。

然而,支持面朝上或朝下的卡片的equal永远不会Deck到不支持卡片位置的PlayableDeck;因为如果确实如此,那么这两个就等同(可替换),有效地摧毁了equal知道卡位置的能力。

请记住在考虑子类和超类时应用相等的规则。

  1. A = A
  2. 如果A = B则B必须= A。
  3. 如果A = B且B = C,则A必须= C.
  4. 起初,规则2似乎暗示DeckPlayableDeck在任何情况下都不能相等,但事实并非如此。如果您决定将Deck视为PlayableDeck,那么

    PlayableDeck

    仍然会很好。虽然没有常量转换,但必须失败的equals方法是PlayableDeck。

    Deck

    为什么这种工作如此好用与多态性有关,这确保了在尝试超类实现之前检查基类实现的事实。

答案 5 :(得分:1)

我使用不同的相等方法(可能称之为matches)来实现“未知属性”的游戏逻辑,但是以正常方式实现equals方法以便让其他方法像集合这样的类仍能正常运行。通过这种方式,您可以拥有一个PlayCard对象集合,并且可以保证您没有两个黑桃,例如,在您的套牌中,无论播放器是否知道这些卡的价值。

所以,例如:

abstract class PlayingCard {
    protected Rank rank;
    protected Suit suit;

    public PlayingCard(Rank rank, Suit suit) {
        this.rank = rank;
        this.suit = suit;
    }

    public abstract Rank getRank();
    public abstract Suit getSuit();
    public abstract boolean isComparableWith(PlayingCard other);
    public abstract boolean matches(PlayingCard other);

    @Override public boolean equals(Object obj) {
        boolean isEqual = false;
        if (obj == null || !(obj instanceof PlayingCard)) {
            isEqual = false;
        } else if (obj == this) {
            isEqual = true;
        } else {
            PlayingCard other = (PlayingCard) obj;
            isEqual = (other.rank.equals(rank)) && (other.suit.equals(suit));
        }
        return isEqual;
    }
}

class FaceUpPlayingCard extends PlayingCard {
    public FaceUpPlayingCard(Rank rank, Suit suit) {
        super(rank, suit);
    }
    public boolean isComparableWith(PlayingCard other)  {
        return other instanceof FaceUpPlayingCard;
    }
    public boolean matches(PlayingCard other) {
        return isComparableWith(other) && equals(other);
    }
    public Rank getRank() { return rank; }
    public Suit getSuit() { return suit; }
}

class FaceDownPlayingCard extends PlayingCard {
    public FaceDownPlayingCard(Rank rank, Suit suit) {
        super(rank, suit);
    }
    public boolean isComparableWith(PlayingCard other)  {
        return false;
    }
    public boolean matches(PlayingCard other) {
        return false;
    }
    public Rank getRand() { return null; }
    public Suit getSuit() { return null; }
}

这样,当你有一个集合时,它可以根据equals方法进行排序和其他内置检查,该方法检查属性而不管卡的状态(面朝上或朝下)。当你需要实现游戏逻辑时,你会使用matches方法 - 这会检查两张牌是否正面朝上,如果是,则检查等级和套装。