0

0

Java记忆游戏:深入理解对象相等性与游戏状态管理

花韻仙語

花韻仙語

发布时间:2025-07-07 19:44:01

|

703人浏览过

|

来源于php中文网

原创

Java记忆游戏:深入理解对象相等性与游戏状态管理

本文深入探讨了Java多米诺记忆游戏开发中常见的两个关键问题:对象比较不当导致的多米诺牌无法正确匹配,以及游戏状态(多米诺牌揭示状态)未及时更新导致游戏无法结束。通过详细解析 equals() 和 hashCode() 方法的正确覆写,以及在游戏逻辑中有效管理对象状态,本教程旨在帮助开发者构建功能完善、逻辑严谨的Java记忆游戏。

1. 问题分析与根源

在开发基于对象的记忆游戏时,两个常见的问题可能导致游戏行为异常:

  • 对象比较不准确: 当需要判断两个游戏元素(如多米诺牌)是否“相等”时,如果仅仅使用 == 运算符,它会比较对象的内存地址,而非其内部属性值。这导致即使两张多米诺牌的数字相同,程序也可能认为它们不匹配。此外,即使尝试使用 equals() 方法,如果该方法在自定义类中未被正确覆写,其默认行为通常与 == 相同,即比较引用。
  • 游戏状态未更新: 游戏元素的内部状态(例如多米诺牌是否已被揭示)未能根据游戏规则及时更新。这会导致即使玩家猜对了,多米诺牌也不会保持翻开状态,进而影响游戏结束条件的判断。

针对上述问题,我们将以一个Java多米诺记忆游戏为例,详细讲解如何通过覆写 equals() 和 hashCode() 方法以及正确管理对象状态来解决这些问题。

2. 解决对象比较问题:覆写 equals() 和 hashCode()

在Java中,当我们需要根据对象的实际内容(而非内存地址)来判断它们是否相等时,必须在自定义类中覆写 Object 类的 equals() 方法。同时,为了遵循Java约定和确保集合类(如 HashMap, HashSet)的正确行为,当覆写 equals() 时,也必须覆写 hashCode() 方法。

2.1 覆写 Domino.equals() 方法

原始的 Domino 类中的 equals() 方法存在逻辑错误:它只检查 top 和 bottom 是否相等,这导致只有双面牌(如 [2][2])才会被认为是相等的,而不同位置但值相同的牌则无法匹配。

public boolean equals(Domino other) { // 错误的覆写方式
    if (top == bottom) // 错误:只检查自身是否为双面牌
        return true;
    return false;
}

正确的 equals() 覆写应该比较当前 Domino 对象与传入的 Object 对象(在类型转换后)的 top 和 bottom 属性。

立即学习Java免费学习笔记(深入)”;

@Override
public boolean equals(Object obj) {
    // 1. 检查是否是同一个对象的引用
    if (this == obj) {
        return true;
    }
    // 2. 检查传入对象是否为null或类型不匹配
    if (obj == null || getClass() != obj.getClass()) { // 或者使用 !(obj instanceof Domino)
        return false;
    }
    // 3. 类型转换
    final Domino other = (Domino) obj;
    // 4. 比较关键属性
    if (this.getTop() != other.getTop()) {
        return false;
    }
    if (this.getBottom() != other.getBottom()) {
        return false;
    }
    return true;
}

注意事项:

  • @Override 注解是可选的,但强烈建议使用,它能帮助编译器检查你是否正确覆写了父类方法。
  • equals() 方法的参数类型必须是 Object,否则它不是真正的覆写,而是一个重载方法。
  • 在比较属性时,考虑到 Domino 构造函数已经保证了 top

2.2 覆写 Domino.hashCode() 方法

根据Java约定,如果两个对象通过 equals() 方法判断为相等,那么它们的 hashCode() 方法必须返回相同的值。覆写 hashCode() 的目的是为相等的对象生成一致的哈希码,这对于基于哈希的集合(如 HashMap, HashSet)的正确运行至关重要。

@Override
public int hashCode() {
    int hash = 7; // 任意一个非零常数
    hash = 59 * hash + this.getTop(); // 将属性值纳入哈希计算
    hash = 59 * hash + this.getBottom();
    return hash;
}

注意事项:

  • 哈希码的计算应基于参与 equals() 比较的所有属性。
  • 使用质数(如59)进行乘法运算有助于减少哈希冲突。
  • 一个好的 hashCode() 实现应尽可能地将不相等的对象分散到不同的哈希桶中。

3. 解决游戏状态更新问题:更新 guess() 方法

多米诺牌在匹配成功后未能保持揭示状态,是因为 Domino 对象的 revealed 属性没有被设置为 true。这个状态更新的逻辑应该发生在 MemoryLane 类的 guess() 方法中,当判断两张牌匹配成功时。

Uni-CourseHelper
Uni-CourseHelper

私人AI助教,高效学习工具

下载

原始的 guess() 方法:

public boolean guess(int i, int k) {
    if(board[i] == board[k]) { // 错误:使用 == 比较对象引用
        return true;
    }
    return false;
}

修正后的 guess() 方法不仅要使用 equals() 进行对象内容比较,还要在匹配成功后调用 setRevealed(true) 来更新多米诺牌的状态。

public boolean guess(int i, int k) {
    // 使用覆写后的 equals 方法进行内容比较
    if (board[i].equals(board[k])) {
        // 如果匹配成功,设置这两张多米诺牌为已揭示状态
        board[i].setRevealed(true);
        board[k].setRevealed(true);
        return true;
    }
    // 如果不匹配,不需要做任何状态改变,直接返回false
    return false;
}

4. 简化 isRevealed() 方法

在 Domino 类中,isRevealed() 方法可以简化,因为它本质上只是返回 revealed 字段的值。

原始的 isRevealed():

public boolean isRevealed() {
    if (revealed)
        return true;
    return false;
}

简化后的 isRevealed():

public boolean isRevealed() {
    return revealed;
}

5. 完整代码示例(修正后的关键类)

以下是经过上述修正后的 Domino 类和 MemoryLane 类中 guess 方法的关键代码片段。MemoryLaneDriver 类保持不变,因为它已经能够正确调用游戏逻辑。

5.1 修正后的 Domino 类

public class Domino {
    private int top, bottom;
    private boolean revealed;

    public Domino(int x, int y) {
        if (x > y) {
            top = y;
            bottom = x;
        } else {
            top = x;
            bottom = y;
        }
    }

    public int getTop() {
        return top;
    }

    public int getBottom() {
        return bottom;
    }

    public boolean isRevealed() {
        return revealed; // 简化后的方法
    }

    public void setRevealed(boolean revealed) {
        this.revealed = revealed;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 59 * hash + this.getTop();
        hash = 59 * hash + this.getBottom();
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        final Domino other = (Domino) obj;
        if (this.getTop() != other.getTop()) {
            return false;
        }
        if (this.getBottom() != other.getBottom()) {
            return false;
        }
        return true;
    }
}

5.2 修正后的 MemoryLane 类(仅展示 guess 方法)

import java.util.Arrays;
import java.util.Random;

public class MemoryLane {
    private Domino[] board;

    public MemoryLane(int max) {
        board = new Domino[(max * max) + max];

        int i = 0;
        for (int top = 1; top <= max; top++) {
            for (int bot = 1; bot <= max; bot++) {
                if (top <= bot) {
                    board[i] = new Domino(top, bot);
                    i++;
                    board[i] = new Domino(top, bot);
                    i++;
                }
            }
        }
        shuffle();
    }

    private void shuffle() {
        int index;
        Random random = new Random();
        for (int i = board.length - 1; i > 0; i--) {
            index = random.nextInt(i + 1);
            if (index != i) {
                Domino temp = board[index];
                board[index] = board[i];
                board[i] = temp;
            }
        }
    }

    // 修正后的 guess 方法
    public boolean guess(int i, int k) {
        if (board[i].equals(board[k])) { // 使用覆写后的 equals 方法
            board[i].setRevealed(true);  // 设置为已揭示
            board[k].setRevealed(true);  // 设置为已揭示
            return true;
        }
        return false;
    }

    public String peek(int a, int b) {
        String text = ""; // 可以直接初始化为空字符串
        text += ("[" + board[a].getTop() + "] [" + board[b].getTop() + "]\n");
        text += ("[" + board[a].getBottom() + "] [" + board[b].getBottom() + "]\n");
        return text;
    }

    public boolean gameOver() {
        int count = 0;
        for (int i = 0; i < board.length; i++) {
            if (board[i].isRevealed()) {
                count++;
            }
        }
        return (count == board.length); // 当所有牌都被揭示时,游戏结束
    }

    public String toString() {
        String text = "";
        for (int i = 0; i < board.length; i++) {
            if (board[i].isRevealed()) {
                text += ("[" + board[i].getTop() + "] ");
            } else {
                text += ("[ ] ");
            }
        }
        text += ('\n');
        for (int i = 0; i < board.length; i++) {
            if (board[i].isRevealed()) {
                text += ("[" + board[i].getBottom() + "] ");
            } else {
                text += ("[ ] ");
            }
        }
        return text;
    }
}

6. 总结

通过本次教程,我们深入理解了在Java中进行对象比较和状态管理的重要性。关键点包括:

  • 正确覆写 equals() 和 hashCode(): 这是实现基于对象内容比较的基础。务必记住,当覆写 equals() 时,必须同时覆写 hashCode(),以确保程序行为的正确性和一致性,尤其是在使用集合类时。
  • 及时更新对象状态: 游戏逻辑中,当满足特定条件时,必须显式地更新相关对象的内部状态(例如通过调用 setRevealed(true))。这是确保游戏流程正确推进和结束条件能够被正确判断的关键。

遵循这些原则,开发者可以构建出功能更健壮、逻辑更清晰的面向对象程序,特别是在游戏开发等需要精细状态管理和对象比较的场景中。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1498

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

231

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

87

2025.10.17

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

56

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

51

2025.11.27

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

56

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

51

2025.11.27

C++类型转换方式
C++类型转换方式

本专题整合了C++类型转换相关内容,想了解更多相关内容,请阅读专题下面的文章。

299

2025.07.15

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 7.9万人学习

Django 教程
Django 教程

共28课时 | 3.5万人学习

Excel 教程
Excel 教程

共162课时 | 13.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号