You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

重写Card类equals方法引发发牌逻辑异常,求原因指引

卡牌游戏equals重写引发的发牌逻辑异常问题

开发学校项目的卡牌游戏时,需要判断两张牌是否拥有相同的牌面等级,于是重写了Card类的equals方法。但发现只要这个equals方法存在,即便没有主动调用.equals(),发牌逻辑也会出现异常:

  • 玩家可能拿到重复卡牌
  • 从牌组移除卡牌时会错误移除同等级但不同花色的牌
  • 部分已发卡牌仍留在牌组中

将equals方法注释后,发牌逻辑恢复正常:玩家获得的卡牌正确,对应卡牌也会从牌组中移除。

重写的equals方法

public boolean equals(Object obj) {
    if (!(obj instanceof Card)){
        return false;
    } else {
        Card card = (Card) obj;
        return card.cardNum == this.cardNum;
    }
}

发牌与移除卡牌的方法

deal方法

/**
 * @return randomCard, the randomly selected card
 */
public Card deal() {
    Random rand = new Random();
    Card randomCard;

    randomCard = m_cards.get(rand.nextInt(m_cards.size()));
    m_cards.remove(randomCard);

    return randomCard;
}

deals方法

/**
 * @param n, the number of times deal is called
 * @return cardsDealt, a LinkedList containing the cards removed from the Deck.
 */
public LinkedList<Card> deals(int n) {
    LinkedList<Card> cardsDealt = new LinkedList<Card>();

    for(int i = 0; i < n; i++) {
        cardsDealt.add(m_deck.deal());
    }
    // System.out.print("\nRemoving: ");
    return cardsDealt;
}

Player类代码

import java.util.LinkedList;
public class Player {
    private int playerNum;
    private boolean hasSpoon;
    private boolean isDealer;

    static Dealer dealer = new Dealer();
    LinkedList<Card> hand;

    public Player(int playerNum) {
        this.playerNum = playerNum;
        this.hasSpoon = false;
        if(this.playerNum == 1) {
            this.isDealer = true;
        }
        this.hand = new LinkedList<Card>(dealer.deals(4));
    }

    public String toString() {
        return "\nPlayer " + playerNum + 
               "\nHas spoon? " + hasSpoon + 
               "\nIs dealer? " + isDealer + 
               "\nHand: " + hand
              + "\nDealer: " + dealer;
    }

    public static void main(String[] args) {
        Player player1 = new Player(1);

        System.out.println(player1);
    }
}

问题原因指引

  • Java集合的remove(Object o)方法,内部是通过调用元素的equals方法来匹配要移除的对象,找到第一个匹配的元素就会将其移除
  • 你重写的equals仅比较牌面等级(cardNum),这会导致所有同等级的Card对象都会被判定为相等
  • 当执行m_cards.remove(randomCard)时,集合不会移除你随机选中的那一张具体卡牌,而是移除集合中第一张与它equals返回true的卡牌——也就是同等级的另一张牌,你选中的那张反而可能留在牌组里
  • 这就引发了后续的异常:牌组残留已“发牌”的卡牌,后续发牌时可能再次选中,出现重复;同时错误移除的同等级牌会导致玩家拿到的牌和实际移除的牌不匹配

解决方向指引

  1. 明确equals的设计原则:equals应反映对象的「逻辑唯一性」,对于卡牌来说,默认的equals(比较对象引用)是判断是否为同一张物理牌;而判断等级相同是另一种业务逻辑,不应通过重写equals实现,建议单独编写方法(比如hasSameRank(Card other))来做等级比较
  2. 正确重写equals(如果需要):如果坚持要重写equals,必须遵守Java的equals约定:
    • 同时重写hashCode方法(equals相等的对象hashCode必须相等)
    • equals的逻辑要能区分不同的物理卡牌(比如同时比较牌面等级和花色)
  3. 调整移除卡牌的方式:可以改用通过索引移除的方式(m_cards.remove(index)),而不是通过对象移除,这样就不会触发equals的匹配逻辑,直接移除选中位置的卡牌

内容的提问来源于stack exchange,提问作者Seba

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 19:05:22