2024年2月19日发(作者:)

北京邮电大学毕业设计

Java实现蜘蛛纸牌

摘 要

蜘蛛纸牌的每一代的window的系统都有,同时也时一款很受用户喜欢的休闲类游戏,很多人都喜欢玩蜘蛛纸牌。本人做的蜘蛛纸牌游戏开发理念是基于window 7操作系统中的蜘蛛纸牌游戏。

利用java语言实现蜘蛛纸牌游戏的主要功能:纸牌的移动,放置,回收,重发。利用包的类实现纸牌游戏的用户界面,通为各个菜单组件添加监视器来实现鼠标单击事件所触发的接口方法,使得用户可以单击菜单项来实现具体的功能。通过这次游戏程序的设计,让我对Java有了更深的了解和练习,这对于我以后找到工作打下了的坚实的基础。

关键字:java语言;游戏的背景;功能实现

i

北京邮电大学毕业设计

Java Spider Solitaire

Abstract

System of Spider Solitaire each generation of both window, but also a very popular

when users like leisure game, a lot of people like to playspider solitaire. I do Spider Solitaire

game development concept is the window 7 operating system of Spider Solitaire game based

on.

To achieve the mainfunction of Spider Solitaire game using java language: Solitaire

mobile,placement, recovery, repeat. The card game user interface using the

package class, interface method for each menu components to add monitor to achieve the

mouse click event triggered, so that the user canclick on a menu item to achieve specific

functions. Through the design of thegame program, let me have a deeper understanding and

Practice on Java, this right after I find work to lay a solid foundation.

Keywords: Java language; game background; function realizati

ii

北京邮电大学毕业设计

目 录

摘 要 .................................................................. i

Abstract ............................................................... ii

1 绪论 ................................................................. 1

1.1 游戏开发的背景知识 ............................................. 1

1.2 需要做的准备工作 ............................................... 1

2 开发工具介绍 ......................................................... 2

2.1 java语言概述 ................................................... 2

2.2 java语言的特点 ................................................. 2

2.3 Eclipse简介 .................................................... 3

3 可行性分析 ........................................................... 4

3.1 概述 ........................................................... 4

3.2 本系统的可行性分析 ............................................. 5

3.3 系统分析 ....................................................... 5

3.3.1 限定问题 ................................................. 5

3.3.2 确定目标 ................................................. 6

3.3.3 调查研究,收集数据 ....................................... 6

3.3.4 提出方案和评价标准 ....................................... 6

3.3.5 方案评估 ................................................. 6

3.3.6 提交可行方案 ............................................. 6

4 总体设计 ............................................................. 7

4.1 系统设计 ....................................................... 7

4.2 主要模型 ....................................................... 8

4.3 系统功能结构图 ................................................. 8

5 详细设计 ............................................................ 10

5.1 代码功能功能模块设计 .......................................... 10

5.2 模块一的详细介绍 .............................................. 10

5.2.1 主要的类 ................................................ 10

5.2.2 主要的变量 .............................................. 11

5.2.3 主要的方法 .............................................. 11

5.3 模块二的详细介绍 .............................................. 12

5.3.1 主要的类 ................................................ 12

5.3.2 主要的变量 .............................................. 12

5.3.3 主要的方法 .............................................. 12

5.4 模块三的详细介绍 .............................................. 13

5.4.1 主要类介绍 .............................................. 13

5.4.2 主要变量 ................................................ 13

5.4.3 主要方法 ................................................ 13

5.5 模块四的详细介绍 .............................................. 14

5.5.1 主要的类 ................................................ 14

5.5.2 主要的变量 .............................................. 14

北京邮电大学毕业设计

5.5.3 主要的方法 .............................................. 14

6 具体功能的设计 ...................................................... 16

6.1 需要实现的主要功能 ............................................ 16

6.2 主要功能的代码实现 ............................................ 16

6.2.1 主界面的实现 ............................................ 16

6.2.2 游戏按钮的选项及其下拉列表 .............................. 18

6.2.3 纸牌移动操作 ............................................ 19

6.2.4 回收纸牌操作 ............................................ 20

6.2.5 帮助菜单功能 ............................................ 22

6.2.6 退出模块设计 ............................................ 23

7 程序的运行及发布 .................................................... 24

7.1 运行程序 ...................................................... 24

7.2 发布程序 ...................................................... 24

8 软件测试 ............................................................ 25

8.1 简介 .......................................................... 25

8.2 软件测试的原则 ................................................ 25

8.3 软件测试的目标 ................................................ 26

8.4 软件测试内容 .................................................. 26

8.5 测试的方法 .................................................... 26

8.5.1 等价类 .................................................. 26

8.5.2 边界值 .................................................. 27

结论 ................................................................... 33

参考文献 ............................................................... 34

致 谢 ................................................................. 35

外文原献 ............................................................... 36

中文翻译 ............................................................... 48

北京邮电大学毕业设计

1 绪论

1.1 游戏开发的背景知识

蜘蛛纸牌时一款很受大家喜欢的休闲类游戏,随着科学技术的不断发展的蜘蛛纸牌的的功能也越来越强大,界面变的越来越美观,玩蜘蛛纸牌的人也很多。蜘蛛纸牌在window 2000 的时候就已经存在于系统中,一直是大家在休闲的最佳游戏。通过这次蜘蛛纸牌游戏的设计,实现蜘蛛纸牌的主要功能,不论是对游戏的了解,还是对自己设计程序的能力都是很好的提高。游戏的目标是以最少的移动次数将牌面中的十叠牌以及待发的五组,共计八副牌整理移除。当所有牌被移除整理到界面的左下方,游戏获胜。

1.2 需要做的准备工作

需要做的工作有学习掌握开发程序的方法,安装所需的编程的平台eclipse,了解开发工具,进行进程分析,根据得需求规格说明书设计具体的程序流程图,编写程序,修改程序,运行程序,系统调试程序、测试程序,发布程序。

1

北京邮电大学毕业设计

2 开发工具介绍

2.1 java语言概述

java是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由Sun

Microsystems公司于1995年5月推出的Java程序设计语言和Java平台(即JavaEE,

JavaME, JavaSE)的总称。Java自面世后就非常流行,发展迅速,对C++语言形成了有力冲击。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于个人PC、数据中心、游戏控制台、科学超级计算机、移动电话和互联网,同时拥有全球最大的开发者专业社群。在全球云计算和移动互联网的产业环境下,Java更具备了显著优势和广阔前景。现在常用的浏览器比如说360浏览器,IE浏览器中均配备有java

applet。

Java不同于一般的编译执行计算机语言和解释执行计算机语言。它首先将源代码编译成二进制字节码(bytecode),然后依赖各种不同平台上的虚拟机来解释执行字节码。从而实现了―一次编译、到处执行‖的跨平台特性。不过,每次的执行编译后的字节码需要消耗一定的时间,这同时也在一定程度上降低了 Java 程序的性能。

2.2 java语言的特点

java语言是一种简单的、面向对象的、分布式的、健壮的、安全的、与平台无关的、多线程、高性能的、动态程序设计语言。

语言简单易学的: java语言的语法与C语言和C++语言很接近,使得多数程序员很容易学习和使用java。另一方面,java丢弃了C++中很少使用的,很难理解的、令人迷惑的那些特性,如操作符重载、多继承、自动的强制类型转换。特别地。java语言不使用指针,并提供了自动的废料收集,使得程序员不必为内存管理而担忧。

2.平台无关性:平台无关性是指Java能运行于不同的平台。Java引进虚拟机 原理,并运行于虚拟机,实现不同平台的Java接口之间。使用Java编写的程序能在世界范围内共享。Java的数据类型与 机器无关,Java虚拟机(Java Virtual Machine)是建立在硬件和操作系统之上,实现Java二进制代码的解释执行功能, 提供于不同平台的接口的。

3.安全性:Java通常被用在网络环境中,为此,Java提供了一个安全机制以防恶意代码的攻击。除了Java语言具有的许多安全特性以外,Java对通过网络下载的类具有一个安全防范机制(类classloader),如分配不同的名字空间以防替代本地的同名类、字节代码检查,并提供安全管理机制(类SecurityManager)让Java应用设置安全哨兵。

4.面向对象:Java语言提供类、接口和继承等原语,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制。Java语言全面

支持动态绑定,而C++ 语言只对虚函数使用动态绑定。总之,Java语言是一个纯的面向对象程序设计语言。

5.分布式:Java语言支持Internet应用的开发,在基本的Java应用编程接口中有一个网络应用编程接口,它提供了用于网络应用编程的类库,包括URL、URLConnection、Socket、 ServerSocket等。Java的RMI(远程方法激活)机制也是开发分布式应用的重要手段。

2

北京邮电大学毕业设计

6.健壮性:Java的强类型机制、异常处理、废料的自动收集等是Java程序健壮性的重要保证。对指针的丢弃是Java的明智选择。Java的安全检查机制使得Java更具健壮性。

7.解释型:Java程序在Java平台上被编译为字节码格式, 然后可以在实现这个Java平台的任何系统中运行。在运行时,Java平台中的Java解释器对这些字节码进行解释执行,执行过程中需要的类在联接阶段被载入到运行环境中。

8.动态:java程序的基本组成单元就是类,有些类是自己编写的,有些是从类库中引入的,而类又是运行时动态装载的,这就使得Java可以在分部环境中动态的维护程序及分类,而不像C++那样,没档期类库升级以后,如果想让程序具有新类库提供的功能,就需要修改程序,重新编译。

9.多线程:在Java语言中,线程是一种特殊的对象,它必须由Thread类或其子类来创建。通常有两种方法来创建线程:其一,使用型构为Thread 的构造子将一个实现了Runnable接口的对象包装成一个线程,其二,从Thread类派生出子类并重写run方法,使用该子类创建的对象即为线程。值得注意的是Thread类已经实现了Runnable接口,因此,任何一个线程均有它的run方法,而run方法中包含了线程所要运行的代码。线程的活动由一组方法来控制。 Java语言支持多个线程的同时执行,并提供多线程之间的同步机制。

10.可以移植的:这种可移植性来源于体系结构中立性,另外,java还严格规定了各个基本数据类型的长度。java系统本身也具有很强的可移植性,java编译器是用java实现的,java运行环境是用ANSIC实现的。

语言是体系结构中立的:Java程序(后缀为java的文件)在Java平台上被编译为体系结构中立的字节码格式(后缀为class的文件), 然后可以在实现这个Java平台的任何系统中运行。这种途径适合于异构的网络环境和软件的分发。

2.3 Eclipse简介

Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括 Java 开发工具(Java Development Tools,JDT)。 虽然大多数用户很乐于将 Eclipse 当作 Java 集成开发环境(IDE)来使用,但 Eclipse 的目标却不仅限于此。Eclipse 还包括插件开发环境(Plug-in Development Environment,PDE),这个组件主要针对希望扩展 Eclipse 的软件开发人员,因为它允许他们构建与

Eclipse 环境无缝集成的工具。由于 Eclipse 中的每样东西都是插件,对于给 Eclipse

提供插件,以及给用户提供一致和统一的集成开发环境而言,所有工具开发人员都具有同等的发挥场所。这种平等和一致性并不仅限于 Java 开发工具。尽管 Eclipse 是使

用 Java 语言开发的,但它的用途并不限于 Java 语言;例如,支持诸如 C/C++ 和

COBOL 等编程语言的插件已经可用,或预计将会推出。Eclipse 框架还可用来作为与软件开发无关的其他应用程序类型的基础,比如内容管理系统。基于 Eclipse 的应用程序的一个突出例子是 IBM® Rational® Software Architect,它构成了 IBM

Java 开发工具系列的基础。Eclipse IDE for Java EE Developers。

3

北京邮电大学毕业设计

3 可行性分析

可行性分析是通过对项目的主要内容和配套条件,如市场需求、资源供应、建设规模、工艺路线、设备选型、环境影响、资金筹措、盈利能力等,从技术、经济、工程等方面进行调查研究和分析比较,并对项目建成以后可能取得的财务、经济效益及社会环境影响进行预测,从而提出该项目是否值得投资和如何进行建设的咨询意见,为项目决策提供依据的一种综合性的系统分析方法。可行性分析应具有预见性、公正性、可靠性、科学性的特点。

3.1 概述

可行性研究主要内容是要求以全面、系统的分析为主要方法,经济效益为核心,围绕影响项目的各种因素,运用大量的数据资料论证拟建项目是否可行。对整个可行性研究提出综合分析评价,指出优缺点和建议。为了结论的需要,往往还需要加上一些附件,如试验数据、论证材料、计算图表、附图等,以增强可行性报告的说服力。

化工项目可行性研究软件(RSGL-KX2.0),以化工行业《可行性研究报告》、《立项申请书》规范为基础,通过文本、数据灵活调用,生成符合国家规定的《可行性研究报告》、《项目立项申请书》的格式。

本软件的意义在于提高化工应用课题立项及项目生产转化时的可行性研究的准确预测,旨在克服化工科技人员在技术指标、生产销售、环境评价、成本核算、利润分配、风险评估等方面的知识欠缺,降低化工项目的投资风险。

该软件的内容翔实,使用简便,只需要输入基础技术数据和基础经济数据就可得到规范的完整《可行性研究报告》和《项目立项申请书》文档。

我国可行性研究存在的主要问题,一是工程技术方案的研究论证深度不够。按照国外的通常做法,可行性研究阶段的研究深度应能达到定方案的程度,因此要求在工程技术方案论证,应达到Basic Design或Concept Design的程度,基本相当于我国的初步设计应达到的水平,应提出明确的设备清单;二是财务评价就项目论项目,这与国外利用企业理财的理论和方法进行资本预算管理,对投资项目进行投资决策和融资决策的通行做法存在重大差异,并且在经济评价方面不恰当地使用了"国民经济评价"的概念,由此引起一系列的认识误区;三是在市场分析、组织机构分析等方面与国外差别较大,研究深度严重不足;四是不重视多方案的比选及项目风险分析,或者分析的内容、深度严重不足,缺乏项目周期各阶段风险管理的统一筹划及策略论证。

可行性研究的依据:

一个拟建项目的可行性研究,必须在国家有关的规划、政策、法规的指导下完成,同时,还必须要有相应的各种技术资料。进行可行性研究工作的主要依据主要包括:

①国家经济和社会发展的长期规划,部门与地区规划,经济建设的指导方针、任务、产业政策、投资政策和技术经济政策以及国家和地方法规等;

②经过批准的项目建议书和在项目建议书批准后签订的意向性协议等;

③由国家批准的资源报告,国土开发整治规划、区域规划和工业基地规划。对于交通运输项目建设要有有关的江河流域规划与路网规划等;

④国家进出口贸易政策和关税政策;

⑤当地的拟建厂址的自然、经济、社会等基础资料;

⑥有关国家、地区和行业的工程技术、经济方面的法令、法规、标准定额资料等;

⑦由国家颁布的建设项目可行性研究及经济评价的有关规定;

4

北京邮电大学毕业设计

⑧包含各种市场信息的市场调研报告。

可行性研究的一般要求:

可行性研究工作对于整个项目建设过程乃至整个国民经济都有非常重要的意义,为了保证可行性研究工作的科学性、客观性和公正性,有效地防止错误和遗漏,在可行性研究中,

(1)首先必须站在客观公正的立场进行调查研究,做好基础资料的收集工作。对于收集的基础资料,要按照客观实际情况进行论证评价,如实地反映客观经济规律,从客观数据出发,通过科学分析,得出项目是否可行的结论。

(2)可行性研究报告的内容深度必须达到国家规定的标准,基本内容要完整,应尽可能多地占有数据资料,避免粗制滥造,搞形式主义。

(3)为保证可行性研究的工作质量,应保证咨询设计单位足够的工作周期,防止因各种原因的不负责任草率行事。

具体工作周期由委托单位与咨询设计单位在签订合同时协商确定。

3.2 本系统的可行性分析

(1). 投资必要性

主要根据市场调查及预测的结果,以及有关的产业政策等因素,论证项目投资建设的必要性;

(2). 技术的可行性

主要从事项目实施的技术角度,合理设计技术方案,并进行比选和评价;

(3). 财务的可行性

主要从项目及投资者的角度,设计合理财务方案,从企业理财的角度进行资本预算,评价项目的财务盈利能力,进行投资决策,并从融资主体(企业)的角度评价股东投资收益、现金流量计划及债务清偿能力;

(4). 组织的可行性

制定合理的项目实施进度计划、设计合理组织机构、选择经验丰富的管理人员、建立良好的协作关系、制定合适的培训计划等,保证项目顺利执行;

(5). 经济的可行性

从资源配置的角度衡量项目的价值,评价项目在实现区域经济发展目标、有效配置经济资源、增加供应、创造就业、改善环境、提高人民生活等方面的效益。

(6). 社会可行性

分析项目对社会的影响,包括政治体制、方针政策、经济结构、法律道德、宗教民族、妇女儿童及社会稳定性等;

(7). 风险因素控制的可行性

对项目的市场风险、技术风险、财务风险、组织风险、法律风险、经济及社会风险等因素进行评价,制定规避风险的对策,为项目全过程的风险管理提供依据。

3.3 系统分析

3.3.1 限定问题

所谓问题,是现实情况与计划目标或理想状态之间的差距。系统分析的核心内容有两个:其一是进行“诊断”,即找出问题及其原因;其二是“开处方”,即提出解决问题的最可行方案。所谓限定问题,就是要明确问题的本质或特性、问题存在范围和 5

北京邮电大学毕业设计

影响程度、问题产生的时间和环境、问题的症状和原因等。限定问题是系统分析中关键的一步,因为如果“诊断”出错,以后开的“处方”就不可能对症下药。在限定问题时,要注意区别症状和问题,探讨问题原因不能先入为主,同时要判别哪些是局部问题,哪些是整体问题,问题的最后确定应该在调查研究之后。

通过亲身体验总结各银行ATM提款机界面流程,得到本系统开发的主要流程界面(主要功能取款、转账、查询、修改密码等)。

3.3.2 确定目标

系统分析目标应该根据客户的要求和对需要解决问题的理解加以确定,如有可能应尽量通过指标表示,以便进行定量分析。对不能定量描述的目标也应该尽量用文字说明清楚,以便进行定性分析和评价系统分析的成效。

3.3.3 调查研究,收集数据

调查研究和收集数据应该围绕问题起因进行,一方面要验证有限定问题阶段形成的假设,另一方面要探讨产生问题的根本原因,为下一步提出解决问题的备选方案做准备。

调查研究常用的有四种方式,即阅读文件资料、访谈、观察和调查。

收集的数据和信息包括事实(facts)、见解(opinions)和态度(attitudes)。要对数据和信息去伪存真,交叉核实,保证真实性和准确性。

3.3.4 提出方案和评价标准

通过深入调查研究,使真正有待解决的问题得以最终确定,使产生问题的主要原因得到明确,在此基础上就可以有针对性地提出解决问题的备选方案。备选方案是解决问题和达到咨询目标可供选择的建议或设计,应提出两种以上的备选方案,以便提供进一步评估和筛选。为了对备选方案进行评估,要根据问题的性质和客户具备的条件。提出约束条件或评价标准,供下一步应用。

3.3.5 方案评估

根据上述约束条件或评价标准,对解决问题备选方案进行评估,评估应该是综合性的,不仅要考虑技术因素,也要考虑社会经济等因素,评估小组应该有一定代表性,除咨询项目组成员外,也要吸收客户组织的代表参加。根据评估结果确定最可行方案。

3.3.6 提交可行方案

最可行方案并不一定是最佳方案,它是在约束条件之内,根据评价标准筛选出的最现实可行的方案。如果客户满意,则系统分析达到目标。如果客户不满意,则要与客户协商调整约束条件或评价标准,甚至重新限定的问题,开始新一轮系统分析,直到客户满意为止。

6

北京邮电大学毕业设计

4 总体设计

4.1 系统设计

即对有关系统全局问题的设计,也就是设计系统总的处理方案,又称系统概要设计。它包括:计算机配置设计、系统模块结构设计、数据库和文件设计、代码设计以及系统可靠性与内部控制设计等内容。软件功能分解属于下列软件开发中的总体设计阶段。

概要设计解决软件系统的模块划分和模块的层次机构以及数据库设计;详细设计解决每个模块的控制流程,内部算法和数据结构的设计。这个阶段结束,要交付概要设计说明书和设计说明,也可以合并在一起,称为设计说明书。

系统设计通常应用两种方法:一种是归纳法,另一种是演绎法。应用归纳法进行系统设计的程序是:首先尽可能地收集现有的和过去的同类系统的系统设计资料;在对这些系统的设计、制造和运行状况进行分析研究的基础上,根据所设计的系统的功能要求进行多次选择,然后对少数几个同类系统作出相应修正,最后得出一个理想的系统。演绎法是一种公理化方法,即先从普遍的规则和原理出发,根据设计人员的知识和经验,从具有一定功能的元素集合中选择能符合系统功能要求的多种元素,然后将这些元素按照一定形式进行组合(见系统结构),从而创造出具有所需功能的新系统。在系统设计的实践中,这两种方法往往是并用的。

系统设计原则:

(1)阶段开发原则

系统框架和数据结构全面设计,具体功能实现分阶段进行。网站的建设过程可以采取以下三期:第一期工程搭建网站的基本构架,实现电子商务网的大部分功能,初步实现网上交易;第二期工程实现网上竞价系统的全部功能;第三期工程实现网站在线的B to B 交易。

(2)易用性原则

方便上网客户浏览和操作,最大限度地减轻后台管理人员的负担,做到部分业务的自动化处理。

(3)业务完整性原则

对于业务进行中的特殊情况能够做出及时、正确的响应,保证业务数据的完整性。

(4)业务规范化原则

在系统设计的同时,也为将来的业务流程制定了较为完善的规范,具有较强的实际操作性。

(5)可扩展性原则

系统设计要考虑到业务未来发展的需要,要尽可能设计得简明,各个功能模块间的耦合度小,便于系统的扩展。如果存在旧有的数据库系统,则需要充分考虑兼容性。

本系统参照windows游戏蜘蛛纸牌,自己编写设计实现其功能,它具有如下一些功能:

设计一个游戏界面,包括玩牌区、发牌区和回收区。

(1)纸牌以及其背景的设定。

(2)移动纸牌。使用鼠标将较小的纸牌拖放到较大的纸牌下方,可以拖动一组纸牌,当要求这组纸牌是同一个花色,并且这一组纸牌从上到下是从大到小排列的, 7

北京邮电大学毕业设计

且不间断。

(3)回收纸牌。当某列出现同一花色,从上到下依次是从K到A的不间组合时,这组纸牌将被回收到回收区中。

(4)发牌。用鼠标单击发牌区,若发牌区还有剩余的纸牌,则将发出一叠纸牌共10张,依次添加到玩牌区的10列纸牌最下方,但要求这10列纸牌没有空白列。

.若玩家不了解游戏规则可以点击帮助获得游戏方法。

(5)级别设定。点击“游戏”菜单中选取游戏难以级别,有3种级别,分别对应初级、中级、高级。

(6)退出游戏。

4.2 主要模型

该游戏的开发及相关功能的实现需要在Eclipse下建立java project,同时

编写、、SpiderMenu..java、,另外将纸牌的图片文件保存在images文件夹中。

该游戏可分为以下各项功能:

(1) 用于实现纸牌初始化、设置游戏等级以及异常处理。

(2)用于定义纸牌的显示的各种属性以及鼠标事件的相应属性。

(3)SpiderMenu..java用于添加游戏下拉菜单的各个选项,包含图形用户界面的构建,组件监听的实现,以及显示可执行操作的线程。

(4)生成,和。

4.3 系统功能结构图

所谓功能结构图就是将系统的功能进行分解,按功能从属关系表示的图表。管理信息系统的各子系统可以看作是系统目标下层的功能,对其中每项功能还可以继续分解为第三层、第四层……甚至更多的功能。

功能结构图就是按照功能的从属关系画成的图表,图中的每一个框都称为一个功能模块。功能模块可以根据具体情况分的大一点或小一点,分解得最小功能模块可以是一个程序中的每个处理过程,而较大的功能模块则可能是完成某一个任务的一组程序。

功能结构图是对硬件、软件、解决方案等进行解剖,详细描述功能列表的结构,构成,剖面的从大到小,从粗到细,从上到下等而描绘或画出来的结构图。从概念上讲,上层功能包括 (或控制)下层功能,愈上层功能愈笼统,愈下层功能愈具体。功能分解的过程就是一个由抽象到具体、由复杂到简单的过程。图中每一个框称为一个功能模块。功能模块可以根据具体情况分得大一点或小一点。分解得最小的功能模块可以是一个程序中的每个处理过程,而较大的功能模块则可能是完成某一任务的一组程序。

系统设计是新系统的物理设计阶段。根据系统分析阶段所确定的新系统的逻辑模型、功能要求,在用户提供的环境条件下,设计出一个能在计算机网络环境上实施的方案,即建立新系统的物理模型。

8

北京邮电大学毕业设计

运行进入游戏界面菜单帮助等级开始退出游戏发牌移牌消牌游戏结束

图4-1 系统功能结构图

9

北京邮电大学毕业设计

5 详细设计

5.1 代码功能功能模块设计

蜘蛛纸牌游戏共由4个部分组成,分别是:,,,。

包含名为SpiderMenuBar的public类,其主要功能为生成蜘蛛纸牌游戏的菜单栏,实现菜单栏中各个组件的事件侦听。主要包括3个模块:图形用户界面的构建;组件监听接口的实现:显示可执行操作的线程。

包含名为PKCard的public类,其主要功能为:定义纸牌的属性,包括名称,位置等相关信息。并通过相关方法实现纸牌的移动等。

包含名为AboutDialog的public类,其主要功能为生成蜘蛛纸牌游戏的帮助栏。

包含名为Spider的public类,其主要功能为生成蜘蛛纸牌游戏的框架,实现游戏中的方法,包括:纸牌的随机生成,位置的摆放等。

图5-1 程序的总体设计流程图

5.2 模块一的详细介绍

的功能是生成蜘蛛纸牌游戏的菜单栏。实现菜单栏中的各个组件的事件。

5.2.1 主要的类

JMenuBar类、JMenu类、JMenuItem类、JRadioButton类都是设计GUI(图形用 10

北京邮电大学毕业设计

户界面)的包中的类。可以用import引用这些类。JMenuBar类是JComponent类的子类负责创建菜单条的。即JMenuBar类的一个实例就是一个菜单条。

JMenu类是JComponent类的子类负责创建菜单组的。即JMenu类的一个实例化就是一个菜单。

JMenuItem类是JMenu类的父类负责创建菜单项。即JMenuItem类的一个实例化就是一个菜单项。

JRadioButtonMenuItem类负责一个单选按钮菜单项的实现。JRadioButtonMenuItem

是属于一组菜单项中的一个菜单项,该组中只能选择一个项。

ButtonGroup 类用于为一组按钮创建一个多斥作用域。使用相同的 ButtonGroup

对象创建一组按钮意味着―开启‖其中一个按钮时,将关闭组中的其他所有按钮。

SpiderMenuBar类是此文件的一个主类是JMenuBar的子类。

Show类是Thread的子类用于创建线程。

5.2.2 主要的变量

jNewGame、jHelp变量分别是JMenu类创建的―游戏‖、―帮助‖菜单。

jItemAbout、jItemOpen、jItemPlayAgain、jItemExit、jItemValid变量分别是JMenuItem

类创建的―关于‖、―开局‖、―重新发牌‖、―退出‖、―显示可行操作‖菜单项。

jRMItemEasy、jRMItemNormal、jRMItemHard变量分别是JRadioButtonMenuItem类创建的―简单:单一花色‖、―中级:双花色‖、―高级:四花色‖的单选按钮菜单项。

group变量是ButtonGroup 类创建的一组按钮选择对象。

spider变量是Spider类创建的对象用于主界面窗口的实现。将在文件中介绍。

5.2.3 主要的方法

public JMenuBar()创建新的菜单栏。

public JMenu(String text)构造一个新菜单,用提供的字符串作为其文本。

public JMenuItem(String text)创建带有指定文本的菜单项。

public JRadioButtonMenuItem(String text)创建一个带文本的单选按钮菜单项。

public ButtonGroup()创建一个新的单选按钮对象组。

public void add(MenuItem item)向菜单增加由参数item指定的菜单项对象。

public void add(AbstractButton b)将单选按钮添菜单项加到组中。

public void add(JMenu c)将指定的菜单添加加到菜单栏中。

public void addSeparator()将新分隔符追加到菜单的末尾。所需要添加的事件处理的接口方法将在后来的模块中进行介绍。事件源即能 够产生的事件的对象。监视器用于对事件源进行监视以便对发生的事件做出 处理。事件源通过调用相应的方法将某个对象作为自己的监视器。这个方法是addActionListen(ActionListener listen)该方法中的参数是ActionListener类型的接口。因此必须用ActionListener接口的类创建的对象传递给该方法的参数,使得该对象成为事件源的的监视器。监视器负责调用特定的方法处理事件,创建监视器的类必须提供处理事件的特定的方法,即实现接口方法。

public void addMenuListener(MenuListener l)添加菜单事件的侦听器。

void menuSelected(MenuEvent e)选择某个菜单时调用。

void menuDeselected(MenuEvent e)取消选择某个菜单时调用。

void menuCanceled(MenuEvent e)取消菜单时调用。

11

北京邮电大学毕业设计

5.3 模块二的详细介绍

PKCard的功能是定义纸牌的属性,包括名称,位置等相关信息。并通过相关方法实现了纸牌的移动等。

5.3.1 主要的类

JDialog类是创建对话框窗口的主要类。可以使用此类创建自定义的对话框,或者调用 JOptionPane 中的多个类方法来创建各种标准对话框。

Jpanel类是一个面板类负责创建一个面板容器,再向这个面板添加组件,然后将面板添加到底层容器中。

JTabbedPane类负责创建一个组件,它允许用户通过单击具有给定标题和/或图标的选项卡,在一组组件之间进行切换。

JTextArea类负责创建一个显示纯文本的多行区域。它作为一个轻量级组件

Container类负责创建一个容器对象,此容器可以包含其它组件。

AboutDialog类是JDialog类的子类,负责创建一个显示对话框。

5.3.2 主要的变量

jMainPane、jPanel1、jPanel2、变量是JPane1创建的面板容器。

jt1、jt2是JTextArea类创建的文本区对象。

jTabbedPane是JTabbedPane类创建的选项卡窗格对象。

c变量是Container类创建的容器。

5.3.3 主要的方法

public JTabbedPane()创建一个具有默认的 选项卡布局的空

TabbedPane.

public JDialog()创建一个没有标题并且没有指定 Frame 所有者的无模式对话框。一个共享的、隐藏的窗体将被设置为该对话框的所有者。

public JPanel()创建具有双缓冲和流布局的新面板容器。

public JTextArea(String text)构造显示指定文本为text的新的文本区。

public void setTittle(String s)设置一个标题为s的对话框。

public void setSize(int width,int heigth)创建宽为width,高位heigth大小的对话框。

public void setResizable(boolean b)设置对话框是否可调整大小。b为true时,对话框可调整大小。

public void setDefaultCloseOperation(int operation)该方法用来设置单机窗体右上角的关闭图标后,程序会做出怎样的处理。operation取DISPOSE_ON_CLOSE时//隐藏当前窗口,并释放窗体所占有的其他资源。public void setVisible(boolean b)设置框口是可见还是不可见。b取true是为可见。

public Container getContentPane()方法可得到窗口的内容面板容器。

public void e(int width,int heigth)设置文本区的大小。

public void table(boolean b)设置文本区是否可以编辑。

public void eWrap(boolean b)设置文本区中输入的文本是否可以实现在右边界自动换行。

public void t(Font f)设置文本区内的字体。

public void eground(Color c)设置文本区组件的前景色。

public void addTab(String title,Icon icon,Component component,String tip)添加由 title

12

北京邮电大学毕业设计

和/或 icon 表示的 component 和 tip,其中任意一个都可以为 nul参数:title - 此选项卡中要显示的标题,icon - 此选项卡中要显示的图标,component - 单击此选项卡时要显示的组件,tip - 此选项卡要显示的工具提示。

public void pack()调整此窗口的大小,以适合其子组件的首选大小和布局。

5.4 模块三的详细介绍

PKCard的作用是定义纸牌的属性,包括名称、位置等相关信息。并通过相关方法实现纸牌的移动。

5.4.1 主要类介绍

JLable类负责创建标签对象。标签用于短文本字符串或图像或二者的显示区。

Thread类负责创建线程对象。

MouseListener接口、MouseMotionListener接口负责处理事件源所触发的鼠标事件,包括处理鼠标按下、释放、进入、退出、单击、连击、拖动、移动所触发鼠标事件。

Point类负责创建表示 (x,y) 坐标空间中的位置的点对象,以整数精度指定。

MouseEvent类负责创建鼠标所发生的事件对象。

Flash类负责创建线程对象,不断的获取下一张纸牌。

PKCard类是文件的主类,实现MouseListener接口的JLable的子类。负责创建纸牌对象。

5.4.2 主要变量

Point point变量表示纸牌的位置对象。

Point initPoint表示纸牌的初始化位置对象。

int value变量表示纸牌的内容值。

int type变量表示纸牌的类型。

String name变量表示纸牌的名称。

Container pane变量表示标签内主容器。

boolean canMove 变量表示纸牌是否可以移动,值为true时,可以移动。

boolean isFront变量表示纸牌是否正面显示,值为true时,则为正面显示。

PKCard previousCard变量表示上面一张纸牌

5.4.3 主要方法

public void flashCard(PKCard card)方法启动Flash线程不停的获取下一张纸牌直至

完成。

public void run()方法为纸牌的正面设置白色图片。

UI()方法将UI属性重置为当前外观的值。

public void mousePressed(MouseEvent mp)方法按下鼠标时事件的处理方法。

public void mouseReleased(MouseEvent mr)方法释放鼠标时事件处理的方法。

public void setNextCardLocation(Point point)方法放置鼠标时事件处理方法。

public int whichColumnAvailable(Point point)方法为判断可用列。

public void mouseDragged(MouseEvent argO)方法鼠标拖动纸牌时事件处理方法。

public void moving(int x,int y)方法将纸牌移动(x,y)个位置。

ponentZOrder(this,1)方法将主件移动到容器中指定的顺序索引。

public PKCard(String name,Spider spider)方法纸牌的构造函数。

public void turnFront()方法令纸牌显示正面。

13

北京邮电大学毕业设计

public void turnRear()方法令纸牌显示背面。

public void moveto(Point point()方法将纸牌移动到点point

public void setCanMove(boolean can)方法判断纸牌是否能够移动。

public boolean isCardFront()方法判断纸牌是否正面显示。

public boolean isCardCanMove()方法判断纸是否能够移动。

public int getCardValue()方法获得纸牌的内容值。

public int getCardType()方法获得纸牌的类型。

5.5 模块四的详细介绍

文件是蜘蛛纸牌游戏的主类文件,其主要功能是生成蜘蛛纸牌游戏的框架,实现游戏中的方法,包括纸牌的随机生成、位置的摆放等。

5.5.1 主要的类

JFrame类及其子类负责创建的对象称为窗体。

JLable类负责创建标签对象。标签用于短文本字符串或图像或二者的显示区。

Container类负责创建一个容器对象,此容器可以包含其它组件。

PKCard类负责创建纸牌对象。

Hashtable类实现一个哈希表,该哈希表将键映射到相应的值。

5.5.2 主要的变量

public static final int EASY=1;代表―简单‖等级。

public static finalint NATURAL=2 代表―普通‖等级。

public static finalint HARD=3 代表―难‖等级。

private int grade=设定初始等级为简单等级;

private Container pane 变量为Container类创建的面板容器。

private PKCard card[]变量为PKCard类创建的纸牌数组[]。

private JLable clickLable1变量为JLable类创建的右下角发牌区的鼠标点击响应区域。

private JLable groudLable[]变量为JLable类创建的背景框数组。

Hshtable table变量为Hashtable类创建的一个哈希表对象,用于存储键值数据对。

private int c变量为纸牌的数量。

private int n变量为纸牌的等级。

private int a变量为纸牌所在的列号。

private int finish变量为纸牌从小到大排列成功的次数。

5.5.3 主要的方法

public void setTittle(String s)设置一个标题为s的窗体。

public void setVisible(boolean b)设置框体是可见还是不可见。b取true是为可见。

public void setSize(int width,int heigth)创建宽为width,高位heigth大小的窗体。

public void setDefaultCloseOperation(int operation)该方法用来设置单机窗体右上角的关闭图标后,程序会做出怎样的处理。operation取EXIT_ON_CLOSE时结束窗体所在的应用程序。

public void setMenuBar(MenuBar mb)将此窗体的菜单栏设置为指定的菜单栏。

public void kground(Color c)设置面板的背景颜色。

public void out(布局对象)设置面板的布局。

14

北京邮电大学毕业设计

public Container getContentPane()方法可得到窗口的内容面板容器。

public void setBounds(int a,int b,int weidth, int heigth)设置出现在屏幕中的组件距离屏幕的左面a个像素,距屏幕上面b个像素,组件宽为weidth,高为heigth。

public void newGame()方法开始新游戏。

public int getC()方法返回纸牌的数量。

public void setGrade(int grade)方法设置纸牌游戏的等级。

public void initCards()方法进行初始化纸牌

public void randomCards()方法令纸牌随机分配。

public void setNA()方法设置还原纸牌游戏。

public void setCardsLocation()方法设置待展开纸牌级表面初始化纸牌的位置。

public void showEnableOperator()方法进行显示是否有可以移动的纸牌。

public void deal()方法开始运行游戏。

public PKCard getPreviousCard(PKCard card)方法获取当前纸牌上面的那一张纸牌。

public PKCard getNextCard(PKCard card)方法获取当前纸牌的下面的那一张纸牌。

public Point getLastCardLocation(int n)方法获取第n列纸牌最后一张纸牌的位置。

public Point getGroundLabelLocation(int n)方法获取第n列纸牌的背景框架的位置。

public void setGroundLabelZOrder()放置groundLable组件。将组件groundLable移动到容器中指定的顺序索引。顺序(105+i)确定了绘制组件的顺序;具有最高顺序的组件将第一个绘制,具有最低顺序的组建将最后一个绘制。在组建重叠的地方,具有较低顺序的组建将覆盖具有较高顺序的组件。

public void haveFinish(int column)判断纸牌的摆放是否成功。

15

北京邮电大学毕业设计

6 具体功能的设计

6.1 需要实现的主要功能

(1)游戏主界面的设计包括背景颜色、框架大小、玩牌区域、发牌区域、

回收纸牌的区域组件的设置。

(2)点击鼠标、释放鼠标、拖动鼠标时纸牌需要实现的事件处理程序。

(3)移动放置一组或者单个纸牌时。需要纸牌按照由小到大的顺序排列,并列类型相同。

(4)当某列的纸牌按照从A到K得顺序排列时,程序会自动回收这组纸牌到回收纸牌的区域。

(5)当用鼠标单击发牌区域的纸牌时,如果10列纸牌中没有空白列,则顺序把纸牌发到每列纸牌的最下面。否则,弹出有空位不能发牌对话框。

(6)当玩家不知道当前游戏如何进行时,可以点击菜单项显示可行的操作提示玩家下一步该移动哪一张纸牌。

(7)等级设置,玩家通过单击游戏菜单里相应的等级菜单项来设置游戏的难度等级。游戏的默认等级是简单等级。

图6-1 功能设计流程图

该流程图根据实际的蜘蛛纸牌进行设计,可以分为六个层面,开始运行程序,显示出程序的标题(蜘蛛牌),初始化游戏的界面,此时可以进入游戏,在进入游戏时还可以选择游戏的相关操作,比如开局,重新发牌,显示可行的操作,也就是提示,选择等级,退出等,在等级的选择中设置了三个相应的等级,简单,中级和高级。对于新手来说还可以选择帮助界面,可以查看关于游戏的东西,比如游戏规则和声明。

6.2 主要功能的代码实现

6.2.1 主界面的实现

public class AboutDialog extends JDialog//JDialog类是创建对话框窗口的主要类。可 16

北京邮电大学毕业设计

以使用此类创建自定义的对话框,或者调用 JOptionPane 中的多个类方法来创建各种标准对话框

{

JPanel jMainPane = new JPanel();//Jpanel类是一个面板类负责创建一个面板容器,再向这个面板添加组件,然后将面板添加到底层容器中

JTabbedPane jTabbedPane = new JTabbedPane();//JTabbedPane类负责创建一个组件,它允许用户通过单击具有给定标题和/或图标的选项卡,在一组组件之间进行切换

private JPanel jPanel1 = new JPanel();

private JPanel jPanel2 = new JPanel();

private JTextArea jt1 = new JTextArea("将电脑多次分发给你的牌按照相同的花色由大至小排列起来。直到桌面上的牌全都消失。"); //JTextArea类负责创建一个显示纯文本的多行区域。它作为一个轻量级组件

private JTextArea jt2 = new JTextArea("该游戏中,纸牌的图片来自于Windows7的纸牌游戏,图片权属于原作者所有!");

public AboutDialog()//AboutDialog类是JDialog类的子类,负责创建一个显示对话框

{

setTitle("蜘蛛牌");

setSize(300,200);

setResizable(false);//生成的窗口由程序员决定大小,用户不能随意改变窗口大小

setDefaultCloseOperation (E_ON_CLOSE);

//设置框的大小

Container c = tentPane();//初始化容器

e(260,200);

e(260,200);

table(false);//是调用这个函数的控件不能被编辑

table(false);

eWrap(true);//设置自动换行

e(300,200);//public JTabbedPane()创建一个具有默认的

选项卡布局的空 TabbedPane.

("游戏规则", null, jPanel1, null);

("声明", null, jPanel2, null);

public class SpiderMenuBar extends JMenuBar{

//生成spider框架对象

Spider main = null;

//生成菜单组

JMenu jNewGame = new JMenu("游戏");

JMenu jHelp = new JMenu("帮助");

17

北京邮电大学毕业设计

图6-2 游戏初始化界面

在 ―游戏‖菜单上,单击―开局‖开始游戏。牌面上有十叠牌,前四叠每叠6张,后六叠每叠5张,只有每叠的第一张正面朝外,其他均为正面朝内。

该界面使用组件和容器来设置显示游戏和帮助两个选项,玩家可以根据自己的需要选择相关按钮,而且游戏的初始化界面也已经生成,纸牌等待移动。

6.2.2 游戏按钮的选项及其下拉列表

该功能是游戏键的下拉列表,有开局、重新发牌、显示可行操作以及难度选择三个键、退出等功能键,玩家可以根据自己的需要选择相应的操作,起实现代码如下:

//生成菜单项

JMenuItem jItemAbout = new JMenuItem("关于");

JMenuItem jItemOpen = new JMenuItem("开局");

JMenuItem jItemPlayAgain = new JMenuItem("重新发牌");

//生成单选框

JRadioButtonMenuItem jRMItemEasy = new JRadioButtonMenuItem("简单:单一花色");

JRadioButtonMenuItem jRMItemNormal = new JRadioButtonMenuItem("中级:双花色");

JRadioButtonMenuItem jRMItemHard = new JRadioButtonMenuItem("高级:四花色");;

JMenuItem jItemExit = new JMenuItem("退出");

JMenuItem jItemValid = new JMenuItem("显示可行操作");

18

北京邮电大学毕业设计

图6-3 游戏下拉列表

当鼠标移动到对应的按钮并且点击时,系统会根据组件的监听器实现事件的监听,从而产生相应的跳转操作。

6.2.3 纸牌移动操作

移动纸牌。

使用鼠标将较小的纸牌拖放到较大的纸牌下方,可以拖动一组纸牌,当要求这组纸牌是同一个花色,并且这一组纸牌从上到下是从大到小排列的,且不间断。实现的代码如下:

//用鼠标拖动纸牌

public void mouseDragged(MouseEvent arg0) {

if (canMove) {

int x = 0;

int y = 0;

Point p = nt();

x = p.x - point.x;

y = p.y - point.y;

(x, y);

}

}

public void setNextCardLocation(Point point) {

PKCard card = tCard(this);

if (card != null) {

if (point == null) {

tCardLocation(null);

(ation());// 先从HashMap中删除card

ation(int); // 为card设置新的坐标

(int, card); // 再将card添加到HashMap中

} else {

point = new Point(point);

19

北京邮电大学毕业设计

}

}

}

point.y += 20;

tCardLocation(point);

point.y -= 20;

(ation());

ation(point);

(ation(), card);

int = ation();

图6-4 纸牌移动界面

使用鼠标将较小的纸牌拖放到较大的纸牌下方,可以拖动一组纸牌,当要求这组纸牌是同一个花色,并且这一组纸牌从上到下是从大到小排列的,且不间断。

鼠标拖动移动一张或一组牌到另一张牌的上面或空牌叠。每次移动的牌都只能放在一叠牌全部移除后的空白位置或者比它最下面的一张牌大1点的牌之上。

只有当一组牌全部为同一花色,方可以移动这一组牌。否则,只能移动这一组最上面一张或同一花色的多张。

当移动形成同一花色由K到A顺序的一组牌时,这组牌会被自动移除整理到左下方,同时获得分数奖励。

6.2.4 回收纸牌操作

回收纸牌。

当某列出现同一花色,从上到下依次是从K到A的不间组合时,这组纸牌将被回收到回收区中。 20

北京邮电大学毕业设计

实现的代码如下:

public void haveFinish(int column) {

Point point = tCardLocation(column);

PKCard card = (PKCard) (point);

do {

(point);

(new Point(20 + finish * 10, 580));

// 将组件移动到容器中指定的顺序索引。

ponentZOrder(card, 1);

// 将纸牌新的相关信息存入HashMap

(ation(), card);

Move(false);

point = tCardLocation(column);

if (point == null)

card = null;

else

card = (point);

} while (card != null && ove());

finish++;

// 如果8付牌全部组合成功,则显示成功的对话框

if (finish == 8) {

ssageDialog(this, "恭喜你,顺利通过!", "成功",

_MESSAGE);

}

if (card != null) {

ont();

Move(true);

}

}

}

jNewGame、jHelp变量分别是JMenu类创建的―游戏‖、―帮助‖菜单。

jItemAbout、jItemOpen、jItemPlayAgain、jItemExit、jItemValid变量分别是JMenuItem

类创建的―关于‖、―开局‖、―重新发牌‖、―退出‖、―显示可行操作‖菜单项。

jRMItemEasy、jRMItemNormal、jRMItemHard变量分别是JRadioButtonMenuItem类创建的―简单:单一花色‖、―中级:双花色‖、―高级:四花色‖的单选按钮菜单项。

group变量是ButtonGroup 类创建的一组按钮选择对象。

spider变量是Spider类创建的对象用于主界面窗口的实现。将在文件中介绍。

21

北京邮电大学毕业设计

图6-5 回收纸牌界面

6.2.5 帮助菜单功能

该模块使用与新手玩家可以知道游戏规则以及该游戏的版权归属。实现的代码如下:

public class AboutDialog extends JDialog {

JPanel jMainPanel = new JPanel();

JTabbedPane jTabbedPane = new JTabbedPane();

private JPanel jPanel1 = new JPanel();

private JTextArea jt1 = new JTextArea(

"将电脑多次分发给你的牌按照相同的花色由大至小排列起来。直到桌面上的牌全都消失。");

public AboutDialog() {

setTitle("蜘蛛牌");

setSize(300, 200);

setResizable(false);

setDefaultCloseOperation(E_ON_CLOSE);

Container c = tentPane();

e(260, 200);

table(false);

eWrap(true);

t(new Font("楷体_GB2312", , 13));

eground();

(jt1);

22

北京邮电大学毕业设计

}

}

e(300, 200);

("游戏规则", null, jPanel1, null);

(jTabbedPane);

(jMainPanel);

pack();

ible(true);

图6-6 游戏规则说明

图6-7 版权声明

该模块使用与新手玩家可以知道游戏规则以及该游戏的版权归属。

6.2.6 退出模块设计

该模块实现了退出游戏的功能,玩家可以选择此模块退出游戏。

主要代码如下:

// ―退出‖

ionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {

e();

(0);

}

});

23

北京邮电大学毕业设计

7 程序的运行及发布

7.1 运行程序

将文件、、、及所需要的images图像文件保存到同一个文件中。利用javac命令对文件进行编译,使用的命令如下:

Javac

之后利用java命令执行程序,使用的Java Spider

7.2 发布程序

要发布此应用程序,需要将应用程序打包。使用,可以吧应用程序涉及的类和图片压缩成一个jar文件,这样就可以发布程序啦。

首先编写一个清单文件,名为,其代码如下:

Manifest-Version:1.0

Created-By:1.6.0(Sun Microsystems Inc.)

Main-Class:Spider

将此清单文件保存起来

然后,使用如下命令生成jar文件:

Jar cfm *.class

其中参数c表示要生成一个新的jar文件;f表示要生成的jar文件的名字;m表示要生成的清单文件的名字。

如果机器安装过WinRAR解压软件,并将.jar文件与解压缩软件做了关联,那么文件的类型是WinRAR,使得java程序无法运行。因此,在发布软件时还应该再写一个有如下内容的bat文件():

Javaw -jar

然后可以通过双击来运行程序。

24

北京邮电大学毕业设计

8 软件测试

软件测试,描述一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。软件测试的经典定义是:在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。

8.1 简介

软件测试是使用人工操作或者软件自动运行的方式来检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别的过程。

它是帮助识别开发完成(中间或最终的版本)的计算机软件(整体或部分)的正确度(correctness) 、完全度(completeness)和质量(quality)的软件过程;是SQA(software

quality assurance)的重要子域。

Glenford 曾对软件测试的目的提出过以下观点:

图8-1 软件测试过程

(1)测试是为了发现程序中的错误而执行程序的过程。

(2)好的测试方案是极可能发现迄今为止尚未发现的错误的测试方案。

(3)成功的测试是发现了至今为止尚未发现的错误的测试。

(4)测试并不仅仅是为了找出错误。通过分析错误产生的原因和错误的发生趋势,可以帮助项目管理者发现当前软件开发过程中的缺陷,以便及时改进。

(5)这种分析也能帮助测试人员设计出有针对性的测试方法,改善测试的效率和有效性。

(6)没有发现错误的测试也是有价值的,完整的测试是评定软件质量的一种方法。

(7)另外,根据测试目的的不同,还有回归测试、压力测试、性能测试等,分别为了检验修改或优化过程是否引发新的问题、软件所能达到处理能力和是否达到预期的处理能力等。

8.2 软件测试的原则

(1)测试应该尽早进行,最好在需求阶段就开始介入,因为最严重的错误不外乎是系统不能满足用户的需求。

(2)程序员应该避免检查自己的程序,软件测试应该由第三方来负责。

(3)设计测试用例时应考虑到合法的输入和不合法的输入以及各种边界条件,特 25

北京邮电大学毕业设计

殊情况下要制造极端状态和意外状态,如网络异常中断、电源断电等。

(4)应该充分注意测试中的群集现象。

(5)对错误结果要进行一个确认过程。一般由A测试出来的错误,一定要由B来确认。严重的错误可以召开评审会议进行讨论和分析,对测试结果要进行严格地确认,是否真的存在这个问题以及严重程度等。

(6)制定严格的测试计划。一定要制定测试计划,并且要有指导性。测试时间安排尽量宽松,不要希望在极短的时间内完成也有一个高水平的测试。

(7)妥善保存测试计划、测试用例、出错统计和最终分析报告,为维护提供方便。

8.3 软件测试的目标

(1)发现一些可以通过测试避免的开发风险。

(2)实施测试来降低所发现的风险。

(3)确定测试何时可以结束。

(4)在开发项目的过程中将测试看作是一个标准项目。

8.4 软件测试内容

软件测试主要工作内容是验证(verification)和确认(validation),下面分别给出其概念:

验证(verification)是保证软件正确地实现了一些特定功能的一系列活动, 即保证软件以正确的方式来做了这个事件(Do it right)

(1)确定软件生存周期中的一个给定阶段的产品是否达到前阶段确立的需求的过程。

(2)程序正确性的形式证明,即采用形式理论证明程序符合设计规约规定的过程。

(3)评审、审查、测试、检查、审计等各类活动,或对某些项处理、服务或文件等是否和规定的需求相一致进行判断和提出报告。

确认(validation)是一系列的活动和过程,目的是想证实在一个给定的外部环境中软件的逻辑正确性。即保证软件做了你所期望的事情。(Do the right thing)

(1)静态确认,不在计算机上实际执行程序,通过人工或程序分析来证明软件的正确性。

(2)动态确认,通过执行程序做分析,测试程序的动态行为,以证实软件是否存在问题。

软件测试的对象不仅仅是程序测试,软件测试应该包括整个软件开发期间各个阶段所产生的文档,如需求规格说明、概要设计文档、详细设计文档,当然软件测试的主要对象还是源程序。

8.5 测试的方法

8.5.1 等价类

定义

是把所有可能的输入数据,即程序的输入域划分成若干部分(子集),然后从每一个子集中选取少数具有代表性的数据作为测试用例。该方法是一种重要的,常用的黑 26

北京邮电大学毕业设计

盒测试用例设计方法。

划分等价类

等价类是指某个输入域的子集合。在该子集合中,各个输入数据对于揭露程序中的错误都是等效的,并合理地假定:测试某等价类的代表值就等于对这一类其它值的测试,因此,可以把全部输入数据合理划分为若干等价类,在每一个等价类中取一个数据作为测试的输入条件就可以用少量代表性的测试数据取得较好的测试结果。等价类划分可有两种不同的情况:有效等价类和无效等价类。

(1)有效等价类

是指对于程序的规格说明来说是合理的、有意义的输入数据构成的集合。利有效等价类可检验程序是否实现了规格说明中所规定的功能和性能。

(2)无效等价类

与有效等价类的定义恰巧相反。无效等价类指对程序的规格说明是不合理的或无意义的输入数据所构成的集合。对于具体的问题,无效等价类至少应有一个,也可能有多个。

设计测试用例时,要同时考虑这两种等价类。因为软件不仅要能接收合理的数据,也要能经受意外的考验,这样的测试才能确保软件具有更高的可靠性。

划分等价类的标准

(1)完备测试、避免冗余;

(2)划分等价类重要的是:集合的划分,划分为互不相交的一组子集,而子集的并是整个集合;

(3)并是整个集合:完备性;

(4)子集互不相交:保证一种形式的无冗余性;

(5)同一类中标识(选择)一个测试用例,同一等价类中,往往处理相同,相同处理映射到"相同的执行路径"。

划分等价类的方法

(1)在输入条件规定了取值范围或值的个数的情况下,则可以确立一个有效等价类和两个无效等价类。

如:输入值是学生成绩,范围是0~100。

(2)在输入条件规定了输入值的集合或者规定了"必须如何"的条件的情况下,可确立一个有效等价类和一个无效等价类。

8.5.2 边界值

定义

边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充,这种情况下,其测试用例来自等价类的边界。

与等价划分的区别

(1) 边界值分析不是从某等价类中随便挑一个作为代表,而是使这个等价类的每个边界都要作为测试条件。

(2) 边界值分析不仅考虑输入条件,还要考虑输出空间产生的测试情况。

边界值分析方法的考虑:

长期的测试工作经验告诉我们,大量的错误是发生在输入或输出范围的边界上,而不是发生在输入输出范围的内部。因此针对各种边界情况设计测试用例,可以查出更 27

北京邮电大学毕业设计

多的错误。

使用边界值分析方法设计测试用例,首先应确定边界情况。通常输入和输出等价类的边界,就是应着重测试的边界情况。应当选取正好等于,刚刚大于或刚刚小于边界的值作为测试数据,而不是选取等价类中的典型值或任意值作为测试数据。

常见的边界值:

(1) 对16-bit 的整数而言 32767 和 -32768 是边界

(2) 屏幕上光标在最左上、最右下位置

(3) 报表的第一行和最后一行

(4) 数组元素的第一个和最后一个

(5) 循环的第 0 次、第 1 次和倒数第 2 次、最后一次

边界值分析

(1) 边界值分析使用与等价类划分法相同的划分,只是边界值分析假定错误更多地存在于划分的边界上,因此在等价类的边界上以及两侧的情况设计测试用例。

例:测试计算平方根的函数

--输入:实数

--输出:实数

--规格说明:当输入一个0或比0大的数的时候,返回其正平方根;当输入一个小于0的数时,显示错误信息"平方根非法-输入值小于0"并返回0;库函数Print-Line可以用来输出错误信息。

从是否关心软件内部结构和具体实现的角度划分(按测试分类)白盒测试、黑盒测试、灰盒测试。

从是否执行程序的角度:静态测试、动态测试、阶段细分

从软件开发的过程按阶段划分有.单元测试、集成测试、确认测试、系统测试、验收测试、回归测试、Alpha测试、Beta测试

测试过程按4个步骤进行,即单元测试、集成测试、确认测试和系统测试及发布测试。

开始是单元测试,集中对用源代码实现的每一个程序单元进行测试,检查各个程序模块是否正确地实现了规定的功能。

集成测试把已测试过的模块组装起来,主要对与设计相关的软件体系结构的构造进行测试。

确认测试则是要检查已实现的软件是否满足了需求规格说明中确定了的各种需求,以及软件配置是否完全、正确。

系统测试把已经经过确认的软件纳入实际运行环境中,与其它系统成份组合在一起进行测试。

单元测试 (Unit Testing)

(1)单元测试又称模块测试,是针对软件设计的最小单位 ─ 程序模块,进行正确性检验的测试工作。其目的在于发现各模块内部可能存在的各种差错。

(2)单元测试需要从程序的内部结构出发设计测试用例。多个模块可以平行地独立进行单元测试。

单元测试的内容:

在单元测试时,测试者需要依据详细设计说明书和源程序清单,了解该模块的I/O条件和模块的逻辑结构,主要采用白盒测试的测试用例,辅之以黑盒测试的测试用例,使之对任何合理的输入和不合理的输入,都能鉴别和响应。

28

北京邮电大学毕业设计

模块接口测试

在单元测试的开始,应对通过被测模块的数据流进行测试。测试项目包括:调用本模块的输入参数是否正确;本模块调用子模块时输入给子模块的参数是否正确;全局量的定义在各模块中是否一致在做内外存交换时要考虑:文件属性是否正确;OPEN与CLOSE语句是否正确;缓冲区容量与记录长度是否匹配;在进行读写操作之前是否打开了文件;在结束文件处理时是否关闭了文件;正文书写/输入错误,I/O错误是否检查并做了处理。

局部数据结构测试不正确或不一致的数据类型说明,使用尚未赋值或尚未初始化的变量错误的初始值或错误的缺省值,变量名拼写错或书写错,不一致的数据类型,全局数据对模块的影响

路径测试

选择适当的测试用例,对模块中重要的执行路径进行测试,应当设计测试用例查找由于错误的计算、不正确的比较或不正常的控制流而导致的错误,对基本执行路径和循环进行测试可以发现大量的路径错误。

错误处理测试

出错的描述是否难以理解,出错的描述是否能够对错误定位,显示的错误与实际的错误是否相符,对错误条件的处理正确与否,在对错误进行处理之前,错误条件是否已经引起系统的干预等

边界测试

注意数据流、控制流中刚好等于、大于或小于确定的比较值时出错的可能性。对这些地方要仔细地选择测试用例,认真加以测试。

如果对模块运行时间有要求的话,还要专门进行关键路径测试,以确定最坏情况下和平均意义下影响模块运行时间的因素。

单元测试的步骤

模块并不是一个独立的程序,在考虑测试模块时,同时要考虑它和外界的联系,用一些辅助模块去模拟与被测模块相联系的其它模块。驱动模块 (driver)桩模块 (stub)

── 存根模块,如果一个模块要完成多种功能,可以将这个模块看成由几个小程序组成。必须对其中的每个小程序先进行单元测试要做的工作,对关键模块还要做性能测试。,对支持某些标准规程的程序,更要着手进行互联测试。有人把这种情况特别称为模块测试,以区别单元测试。

集成测试

集成测试 (组装测试、联合测试),通常,在单元测试的基础上,需要将所有模块按照设计要求组装成为系统。这时需要考虑的问题是:在把各个模块连接起来的时候,穿越模块接口的数据是否会丢失;一个模块的功能是否会对另一个模块的功能产生不利的影响各个子功能组合起来,能否达到预期要求的父功能;全局数据结构是否有问题;单个模块的误差累积起来,是否会放大,从而达到不能接受的程度。

在单元测试的同时可进行集成测试,发现并排除在模块连接中可能出现的问题,最终构成要求的软件系统。子系统的集成测试特别称为部件测试,它所做的工作是要找出集成后的子系统与系统需求规格说明之间的不一致。通常,把模块集成成为系统的方式有两种一次性集成方式增殖式集成方式

一次性集成方式(big bang)

它是一种非增殖式组装方式。也叫做整体拼装。使用这种方式,首先对每个模块分 29

北京邮电大学毕业设计

别进行模块测试,然后再把所有模块组装在一起进行测试,最终得到要求的软件系统。

增殖式集成方式

这种集成方式又称渐增式集成首先对一个个模块进行模块测试,然后将这些模块逐步组装成较大的系统在集成的过程中边连接边测试,以发现连接过程中产生的问题通过增殖逐步组装成为要求的软件系统。

自顶向下的增殖方式

这种集成方式将模块按系统程序结构,沿控制层次自顶向下进行组装。自顶向下的增殖方式在测试过程中较早地验证了主要的控制和判断点。选用按深度方向组装的方式,可以首先实现和验证一个完整的软件功能。

自底向上的增殖方式

这种集成的方式是从程序模块结构的最底层的模块开始集成和测试。因为模块是自底向上进行组装,对于一个给定层次的模块,它的子模块(包括子模块的所有下属模块)已经组装并测试完成,所以不再需要桩模块。在模块的测试过程中需要从子模块得到的信息可以直接运行子模块得到。自顶向下增殖的方式和自底向上增殖的方式各有优缺点。一般来讲,一种方式的优点是另一种方式的缺点。

混合增殖式测试

衍变的自顶向下的增殖测试首先对输入/输出模块和引入新算法模块进行测试;再自底向上组装成为功能相当完整且相对独立的子系统;然后由主模块开始自顶向下进行增殖测试。自底向上-自顶向下的增殖测试首先对含读操作的子系统自底向上直至根结点模块进行组装和测试;然后对含写操作的子系统做自顶向下的组装与测试。

回归测试

这种方式采取自顶向下的方式测试被修改的模块及其子模块;然后将这一部分视为子系统,再自底向上测试。

关键模块问题

在组装测试时,应当确定关键模块,对这些关键模块及早进行测试。关键模块的特征:满足某些软件需求;在程序的模块结构中位于较高的层次(高层控制模块);较复杂、较易发生错误;有明确定义的性能要求。

确认测试(Validation Testing)

确认测试又称有效性测试。任务是验证软件的功能和性能及其它特性是否与用户的要求一致。对软件的功能和性能要求在软件需求规格说明书中已经明确规定。它包含的信息就是软件确认测试的基础。

进行有效性测试(黑盒测试)

有效性测试是在模拟的环境 (可能就是开发的环境) 下,运用黑盒测试的方法,验证被测软件是否满足需求规格说明书列出的需求。首先制定测试计划,规定要做测试的种类。还需要制定一组测试步骤,描述具体的测试用例。通过实施预定的测试计划和测试步骤,确定软件的特性是否与需求相符;所有的文档都是正确且便于使用;同时,对其它软件需求,例如可移植性、兼容性、出错自动恢复、可维护性等,也都要进行测试在全部软件测试的测试用例运行完后,所有的测试结果可以分为两类:测试结果与预期的结果相符。这说明软件的这部分功能或性能特征与需求规格说明书相符合,从而这部分程序被接受。测试结果与预期的结果不符。这说明软件的这部分功能或性能特征与需求规格说明不一致,因此要为它提交一份问题报告。

软件配置复查

30

北京邮电大学毕业设计

软件配置复查的目的是保证软件配置的所有成分都齐全;各方面的质量都符合要求;具有维护阶段所必需的细节;而且已经编排好分类的目录。应当严格遵守用户手册和操作手册中规定的使用步骤,以便检查这些文档资料的完整性和正确性。

系统测试

系统测试,是将通过确认测试的软件,作为整个基于计算机系统的一个元素,与计算机硬件、外设、某些支持软件、数据和人员等其它系统元素结合在一起,在实际运行环境下,对计算机系统进行一系列的组装测试和确认测试。系统测试的目的在于通过与系统的需求定义作比较, 发现软件与系统的定义不符合或与之矛盾的地方。

验收测试

在通过了系统的有效性测试及软件配置审查之后,就应开始系统的验收测试。验收测试是以用户为主的测试。软件开发人员和QA(质量保证)人员也应参加。由用户参加设计测试用例,使用生产中的实际数据进行测试。在测试过程中,除了考虑软件的功能和性能外,还应对软件的可移植性、兼容性、可维护性、错误的恢复功能等进行确认。确认测试应交付的文档有:确认测试分析报告最终的用户手册和操作手册项目开发总结报告。

编写规范目的:统一测试用例编写的规范,以保证使用最有效的测试用例,保证测试质量。范围:适用于公司对产品的业务流程、功能测试测试用例的编写。

术语解释

测试分析:对重要业务、重要流程进行测试前的分析。业务流程测试用例:关于产品业务、重要流程的测试用例。

业务流程测试用例编写原则

系统性

对于系统业务流程要能够完整说明整个系统的业务需求、系统由几个子系统组成以及它们之间的关系;对于模块业务流程要能够说明清楚子系统内部功能、重要功能点以及它们之间的关系;连贯性 对于系统业务流程来说,各个子系统之间是如何连接在一起,如果需要接口,各个子系统之间是否有正确的接口;如果是依靠页面链接,页面链接是否正确;对于模块业务流程来说,同级模块以及上下级模块是如何构成一个子系统,其内部功能接口是否连贯;

人名、地名、电话号码等应具有模拟功能,符合一般的命名惯例;不允许出现与知名人士、小说中人物名等雷同情况。

BUG级别

测试流程制定测试计划[7] 编辑测试用例执行测试用例发现并提交BUG开发组修正BUG对已修正BUG进行返测修正完成的BUG将状态置为已关闭,未正确修正的BUG重新激活

单元测试

单元测试是对软件组成单元进行测试,其目的是检验软件基本组成单位的正确性,测试的对象是软件设计的最小单位:模块

集成测试

集成测试也称联合测试,将程序模块采用适当的集成策略组装起来,对系统的接口及集成后的功能进行正确性检测的测试工作。其主要目的是检查软件单位之间的接口是否正确,集成测试的对象是已经经过单元测试的模块。

系统测试

31

北京邮电大学毕业设计

系统测试:主要包括功能测试、界面测试、可靠性测试、易用性测试、性能测试。

功能测试主要针对包括功能可用性、功能实现程度(功能流程&业务流程、数据处理&业务数据处理)方面测试。

回归测试

回归测试指在软件维护阶段,为了检测代码修改而引入的错误所进行的测试活动。回归测试是软件维护阶段的重要工作,有研究表明,回归测试带来的耗费占软件生命周期的1/3总费用以上。与普通的测试不同,在回归测试过程开始的时候,测试者有一个完整的测试用例集可供使用,因此,如何根据代码的修改情况对已有测试用例集进行有效的复用是回归测试研究的重要方向,此外,回归测试的研究方向还涉及自动化工具,面向对象回归测试,测试用例优先级,回归测试用例补充生成等。

V模型是软件开发瀑布模型的变种,它反映了测试活动与分析和设计的关系。从左到右,描述了基本的开发过程和测试行为,非常明确地标明了测试过程中存在的不同级别,并且清楚地描述了这些测试阶段和开发过程期间各阶段的对应关系 。

左边依次下降的是开发过程各阶段,与此相对应的是右边依次上升的部分,即各测试过程的各个阶段。

软件测试V模型测试是开发之后的一个阶段。测试的对象就是程序本身。实际应用中容易导致需求阶段的错误一直到最后系统测试阶段才被发现。.整个软件产品的过程质量保证完全依赖于开发人员的能力和对工作的责任心,而且上一步的结果必须是充分和正确的,如果任何一个环节出了问题,则必将严重的影响整个工程的质量和预期进度

W模型由Evolutif公司公司提出,相对于V模型,W模型增加了软件各开发阶段中应同步进行的验证和确认活动。W模型由两个V字型模型组成,分别代表测试与开发过程,图中明确表示出了测试与开发的并行关系。 W模型强调:测试伴随着整个软件开发周期,而且测试的对象不仅仅是程序,需求、设计等同样要测试,也就是说,测试与开发是同步进行的。W模型有利于尽早地全面的发现问题。例如,需求分析完成后,测试人员就应该参与到对需求的验证和确认活动中,以尽早地找出缺陷所在。同时,对需求的测试也有利于及时了解项目难度和测试风险,及早制定应对措施,这将显著减少总体测试时间,加快项目进度。 但W模型也存在局限性。在W模型中,需求、设计、编码等活动被视为串行的,同时,测试和开发活动也保持着一种线性的前后关系,上一阶段完全结束,才可正式开始下一个阶段工作。这样就无法支持迭代的开发模型。对于当前软件开发复杂多变的情况,W模型并不能解除测试管理面临着困惑。

32

北京邮电大学毕业设计

结论

在2014年的3月我开始我的论文的工作,经过长时间的编写,现在我已经完成了我的论文。论文是一个是一个长期的过程,需要不断的进行精心的修改,不断的研究文献,认真总结。

在搜集资料后,我在电脑中都进行分类的整理,然后针对自己不同部分的写作内容进行归纳和总结。尽量使的资料和论文的内容符合,这有利于论文的撰写。然后及拿给老师进行沟通,听取老师的意见后再进行相关的修改老师的意见总是很宝贵的,可以很好的指出我的资料收集不足以及需要什么样的资料来完善文章。

1月初,资料已经查找完毕了,我开始着手论文初稿的写作。初稿的写作显得逻辑结构有点不清晰,总是想到相关的问题就去写,而没有很好的分出清晰的层次,让章显得有点凌乱,这样的文章必然是不符合要求的,但毕是初稿,在老师的指导下还要进行反复的修改。

写作毕业论文是我每个大学生必须经的一段过程,也是我们毕业前的一段宝贵的回忆。当我们看到自的努力有收获的时候,总是会有那么一点点自豪和激动。何事情都是这样子,需要我们脚踏实地的去做,一步一个印的完成,认真严谨,有了好的态度才能做好一件事情开始都觉得毕业论文是一个很困难的任务,大家都难免会一点畏惧之情,但是经过长时间的努力和积累,经过不断查找资料后总结,我们都很好的按老师的要求完成了毕业文的写作,这种收获的喜悦相信每个人都能够体会到。这是一次意志的磨练,是对我实际能力的一次提升,相信对我来的学习和工作有很大的帮助。

在这次毕业论文中同学之间互相帮助,共同商量相关专业问题,这种交流对于即将面临毕业的我们来是一次很有意义的经历,大学四年都一起走过了,在最后我们可以聚在一起讨论学习,研究专业问题,进而更好的了解我们每个人的兴趣之所在,明确我们的人生理想,进而在今后的生活和工作中更好的发挥自己的优势,学好自己的专业,成为一个对于社会有用的人

33

北京邮电大学毕业设计

参考文献

[1] Joshua Bloch,Neal Gafter著. Java Puzzlers : Traps, Pitfalls, and Corner Cases.

Joshua Addison-Wesley Educational Publishers Inc (2005-06)

[2] Bill Singgelkow 编著. O’reilly-Jakarta Struts. O’reilly Media 2005

[3] Deitel, Harvey M. Small Java how to program. Deitel, Paul J. Prentice Hall

(2004-08)

[4] 袁然编著. Java项目案例集锦. 电子工业大学出版社2008

[5] 朱庆生,古平等著. Java程序设计. 清华大学出版社,2011,1

[6] 王伟平等著. Java编程. 清华大学出版社,2010,5

[7] 黄晓东编著. Java课程设计案例精编. 中国水利水电出版社出版

[8] 张永常主编. Java程序设计实用教程. 电子工业出版社出版

[9] 魔乐科技著. java从入门到精通. 人民邮电大学出版社2010

[10] 吴亚峰著. 30天学通java项目案例开发. 电子工业出版社2008

[11] 常建华著. 零基础学java . 机械工业出版社2009

34

北京邮电大学毕业设计

致 谢

我在设计期间都是在任聚财老师全面,具体指导下完成进行的。任聚财老师渊博的学识、敏锐的思维、民主而严谨的作风使学生受益非浅,并终生难忘。

感谢任聚财老师等在毕业设计工作中给予的帮助。

感谢我的学友和朋友对我的关心和帮助。

35

北京邮电大学毕业设计

外文原献

Joshua Bloch,Neal Gafter著. Java Puzzlers : Traps, Pitfalls, and Corner Cases. Joshua

Addison-Wesley Educational Publishers Inc (2005-06)

Puzzle 1: Oddity

The following method purports to determine whether its sole argument is an odd number.

Does the method work?

public static boolean isOdd(int i) {

return i % 2 == 1;

}

Solution 1: Oddity

An odd number can be defined as an integer that is divisible by 2 with a remainder of 1. The

expression i % 2 computes the remainder when i is divided by 2, so it would seem that this

program ought to work. Unfortunately, it doesn't; it returns the wrong answer one quarter of

the timeWhy one quarter? Because half of all int values are negative, and the isOdd method

fails for all negative odd values. It returns false when invoked on any negative value,whether

even or is a consequence of the definition of Java's remainder operator (%). It is

defined to satisfy the following identity for all int values a and all nonzero int values b:

(a / b) * b + (a % b) == a

In other words, if you divide a by b, multiply the result by b, and add the remainder,

you are back where you started [JLS 15.17.3]. This identity makes perfect sense, but in

combination with Java's truncating integer division operator [JLS 15.17.2], it implies that

when the remainder operation returns a nonzero result, it has the same sign as its left

operand.

The isOdd method and the definition of the term odd on which it was based both assume that

all remainders are positive. Although this assumption makes sense for some kinds of division

[Boxing], Java's remainder operation is perfectly matched to its integer division operation,

which discards the fractional part of its result.

When i is a negative odd number, i % 2 is equal to -1 rather than 1, so the isOdd method

36

北京邮电大学毕业设计

incorrectly returns false. To prevent this sort of surprise, test that your methods behave

properly when passed negative, zero, and positive values for each numerical parameter.

The problem is easy to fix. Simply compare i % 2 to 0 rather than to 1, and reverse the sense

of the comparison:

public static boolean isOdd(int i) {

return i % 2 != 0;

}

If you are using the isOdd method in a performance-critical setting, you would be

better off using the bitwise AND operator (&) in place of the remainder operator:

public static boolean isOdd(int i) {

return (i & 1) != 0;

}

The second version may run much faster than the first, depending on what platform

and virtual machine you are using, and is unlikely to run slower. As a general rule, the divide

and remainder operations are slow compared to other arithmetic and logical operations. It's a

bad idea to optimize prematurely, but in this case, the faster version is as clear as the original,

so there is no reason to prefer the original.

In summary, think about the signs of the operands and of the result whenever you use

the remainder operator. The behavior of this operator is obvious when its operands are

nonnegative, but it isn't so obvious when one or both operands are negative.

Puzzle 2: Time for a Change

Consider the following word problem:

Tom goes to the auto parts store to buy a spark plug that costs $1.10, but all he has in his

wallet are two-dollar bills. How much change should he get if he pays for the spark plug

with a two-dollar bill?

Here is a program that attempts to solve the word problem. What does it print?

public class Change {

public static void main(String args[]) {

37

北京邮电大学毕业设计

n(2.00 - 1.10);

}

}

Solution 2: Time for a Change

Naively, you might expect the program to print 0.90, but how could it know that you

wanted two digits after the decimal point? If you know something about the rules for

converting double values to strings, which are specified by the documentation for

ng [Java-API], you know that the program prints the shortest decimal fraction

sufficient to distinguish the double value from its nearest neighbor, with at least one digit

before and after the decimal point. It seems reasonable, then, that the program should print

0.9. Reasonable, perhaps, but not correct. If you ran the program, you found that it prints

problem is that the number 1.1 can't be represented exactly as a

double, so it is represented by the closest double value. The program subtracts this value

from 2. Unfortunately, the result of this calculation is not the closest double value to 0.9. The

shortest representation of the resulting double value is the hideous number that you see

generally, the problem is that not all decimals can be represented exactly using

binary floating-point. If you are using release 5.0 or a later release, you might be tempted to

fix the program by using the printf facility to set the precision of the output:

// Poor solution - still uses binary floating-point!

("%.2f%n", 2.00 - 1.10);

This prints the right answer but does not represent a general solution to the underlying

problem: It still uses double arithmetic, which is binary floating-point. Floating-point

arithmetic provides good approximations over a wide range of values but does not generally

yield exact results. Binary floating-point is particularly ill-suited to monetary calculations, as

it is impossible to represent 0.1—or any other negative power of 10—exactly as a

finite-length binary fraction [EJ Item 31].

One way to solve the problem is to use an integral type, such as int or long, and to perform

the computation in cents. If you go this route, make sure the integral type is large enough to

38

北京邮电大学毕业设计

represent all the values you will use in your program. For this puzzle, int is ample. Here is

how the println looks if we rewrite it using int values to represent monetary values in cents.

This version prints 90 cents, which is the right answer:

n((200 - 110) + " cents");

Another way to solve the problem is to use BigDecimal, which performs exact decimal

arithmetic. It also interoperates with the SQL DECIMAL type via JDBC. There is one caveat:

Always use the BigDecimal(String) constructor, never BigDecimal(double). The latter

constructor creates an instance with the exact value of its argument: new BigDecimal(.1)

returnBigDecimarepresenting0.41015625. Using BigDecimal correctly, the program prints the expected result of 0.90:

import imal;

public class Change {

public static void main(String args[]) {

n(new BigDecimal("2.00").

subtract(new BigDecimal("1.10")));

}

}

This version is not terribly pretty, as Java provides no linguistic support for BigDecimal.

Calculations with BigDecimal are also likely to be slower than those with any primitive type,

which might be an issue for some programs that make heavy use of decimal calculations. It

is of no consequence for most summary, avoid float and double where exact

answers are required; for monetary calculations, use int, long, or BigDecimal. For language

designers, consider providing linguistic support for decimal arithmetic. One approach is to

offer limited support for operator overloading, so that arithmetic operators can be made to

work with numerical reference types, such as BigDecimal. Another approach is to provide a

primitive decimal

type, as did COBOL and PL/I.

Puzzle 3: Long Division

39

北京邮电大学毕业设计

This puzzle is called Long Division because it concerns a program that divides two long

values. The dividend represents the number of microseconds in a day; the divisor, the

number of milliseconds in a day. What does the program print?

public class LongDivision {

public static void main(String[] args) {

final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;

final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;

n(MICROS_PER_DAY / MILLIS_PER_DAY);

}

}

Solution 3: Long Division

This puzzle seems reasonably straightforward. The number of milliseconds per day and

the number of microseconds per day are constants. For clarity, they are expressed as products.

The number of microseconds per day is (24 hours/day · 60 minutes/hour · 60

seconds/minute · 1,000 milliseconds/second · 1,000 microseconds/millisecond). The number

of milliseconds per day differs only in that it is missing the final factor of 1,000. When you

divide the number of microseconds per day by the number of milliseconds per day, all the

factors in the divisor cancel out, and you are left with 1,000, which is the number of

microseconds per millisecond. Both the divisor and the dividend are of type long, which is

easily large enough to hold either product without overflow. It seems, then, that the program

must print 1000. Unfortunately, it prints 5. What exactly is going on here?

The problem is that the computation of the constant MICROS_PER_DAY does overflow.

Although the result of the computation fits in a long with room to spare, it doesn't fit in an

int. The computation is performed entirely in int arithmetic, and only after the computation

completes is the result promoted to a long. By then, it's too late: The computation has already

overflowed, returning a value that is too low by a factor of 200. The promotion from int to

long is a widening primitive conversion [JLS 5.1.2], which preserves the (incorrect)

numerical value. This value is then divided by MILLIS_PER_DAY, which was computed

40

北京邮电大学毕业设计

correctly because it does fit in an int. The result of this division is 5.

So why is the computation performed in int arithmetic? Because all the factors that are

multiplied together are int values. When you multiply two int values, you get another int

value. Java does not have target typing, a language feature wherein the type of the variable in

which a result is to be stored influences the type of the computation.

It's easy to fix the program by using a long literal in place of an int as the first factor in each

product. This forces all subsequent computations in the expression to be done with long

arithmetic. Although it is necessary to do this only in the expression for

MICROS_PER_DAY, it is good form to do it in both products. Similarly, it isn't always

necessary to use a long as the first value in a product, but it is good form to do so. Beginning

both computations with long values makes it clear that they won't overflow. This program

prints 1000 as expected:

public class LongDivision {

public static void main(String[] args) {

final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;

final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;

n(MICROS_PER_DAY / MILLIS_PER_DAY);

}

}

The lesson is simple: When working with large numbers, watch out for overflow—it's a

silent killer. Just because a variable is large enough to hold a result doesn't mean that the

computation leading to the result is of the correct type. When in doubt, perform the entire

computation using long arithmetic.

The lesson for language designers is that it may be worth reducing the likelihood of silent

overflow. This could be done by providing support for arithmetic that does not overflow

silently. Programs could throw an exception instead of overflowing, as does Ada, or they

could switch to a larger internal representation automatically as required to avoid overflow,

as does Lisp. Both of these approaches may have performance penalties associated with

41

北京邮电大学毕业设计

them. Another way to reduce the likelihood of silent overflow is to support target typing, but

this adds significant complexity to the type system [Modula-3 1.4.8].

Puzzle 4: It's Elementary

OK, so the last puzzle was a bit tricky, but it was about division. Everyone knows that

division is tough. This program involves only addition. What does it print?

public class Elementary {

public static void main(String[] args) {

n(12345 + 5432l);

}

}

Solution 4: It's Elementary

On the face of it, this looks like an easy puzzle—so easy that you can solve it without

pencil or paper. The digits of the left operand of the plus operator ascend from 1 to 5, and the

digits of the right operand descend. Therefore, the sums of corresponding digits remain

constant, and the program must surely print 66666. There is only one problem with this

analysis: When you run the program, it prints 17777. Could it be that Java has an aversion to

printing such a beastly number? Somehow this doesn't seem like a plausible explanation.

Things are seldom what they seem. Take this program, for instance. It doesn't say what you

think it does. Take a careful look at the two operands of the + operator. We are adding the int

value 12345 to the long value 5432l. Note the subtle difference in shape between the digit 1

at the beginning of the left operand and the lowercase letter el at the end of the right operand.

The digit 1 has an acute angle between the horizontal stroke, or arm, and the vertical stroke,

or stem. The lowercase letter el, by contrast, has a right angle between the arm and the stem.

Before you cry "foul," note that this issue has caused real confusion. Also note that the

puzzle's title contained a hint: It's El-ementary; get it? Finally, note that there is a real lesson

here. Always use a capital el (L) in long literals, never a lowercase el (l). This completely

eliminates the source of confusion on which the puzzle relies:

n(12345 + 5432L);

42

北京邮电大学毕业设计

Similarly, avoid using a lone el (l) as a variable name. It is difficult to tell by looking at this

code snippet whether it prints the list l or the number 1:

// Bad code - uses el (l) as a variable name

List l = new ArrayList();

("Foo");

n(1);

In summary, the lowercase letter el and the digit 1 are nearly identical in most typewriter

fonts. To avoid confusing the readers of your program, never use a lowercase el to terminate

a long literal or as a variable name. Java inherited much from the C programming language,

including its syntax for long literals. It was probably a mistake to allow long literals to be

written with a lowercase el.

Puzzle 5: The Joy of Hex

The following program adds two hexadecimal, or "hex," literals and prints the result in hex.

What does the program print?

public class JoyOfHex {

public static void main(String[] args) {

n(

tring(0x100000000L + 0xcafebabe));

}

}

Solution 5: The Joy of Hex

It seems obvious that the program should print 1cafebabe. After all, that is the sum of the hex

numbers 1 and cafebabe16. The program uses long arithmetic, which permits 16

hex digits, so arithmetic overflow is not an issue. Yet, if you ran the program, you found that

it prints cafebabe, with no leading 1 digit. This output represents the low-order 32 bits of the

correct sum, but somehow the thirty-third bit gets lost. It is as if the program were doing int

arithmetic instead of long, or forgetting to add the first operand. What's going on here?

Decimal literals have a nice property that is not shared by hexadecimal or octal literals:

43

北京邮电大学毕业设计

Decimal literals are all positive [JLS 3.10.1]. To write a negative decimal constant, you use

the unary negation operator (-) in combination with a decimal literal. In this way, you can

write any int or long value, whether positive or negative, in decimal form, and negative

decimal constants are clearly identifiable by the presence of a minus sign. Not so for

hexadecimal and octal literals. They can take on both positive and negative values. Hex and

octal literals are negative if their high-order bit is set. In this program, the number

0xcafebabe is an int constant with its high-order bit set, so it is negative. It is equivalent to

the decimal value -889275714.

The addition performed by the program is a mixed-type computation: The left operand is of

type long, and the right operand is of type int. To perform the computation, Java promotes

the int value to a long with a widening primitive conversion [JLS 5.1.2] and adds the two

long values. Because int is a signed integral type, the conversion performs sign extension: It

promotes the negative int value to a numerically equal long right operand of the

addition, 0xcafebabe, is promoted to the long value 0xffffffffcafebabeL. This value is then

added to the left operand, which is 0x100000000L. When viewed as an int, the high-order 32

bits of the sign-extended right operand are -1, and the high-order 32 bits of the left operand

are 1. Add these two values together and you get 0, which explains the absence of the

leading 1 digit in the program's output. Here is how the addition looks when done in

longhand. (The digits at the top of the addition are carries.)

1111111

0xffffffffcafebabeL

+ 0x0000L

0x00000000cafebabeL

Fixing the problem is as simple as using a long hex literal to represent the right operand.

This avoids the damaging sign extension, and the program prints the expected result of

1cafebabe:

public class JoyOfHex {

public static void main(String[] args) {

44

北京邮电大学毕业设计

n(

tring(0x100000000L + 0xcafebabeL));

}

}

The lesson of this puzzle is that mixed-type computations can be confusing, more so given

that hex and octal literals can take on negative values without an explicit minus sign. To

avoid this sort of difficulty, it is generally best to avoid mixed-type computations. For

language designers, it is worth considering support for unsigned integral types, which

eliminate the possibility of sign extension. One might argue that negative hex and octal

literals should be prohibited, but this would likely frustrate programmers, who often use hex

literals to represent values whose sign is of no significance.

Puzzle 6: Multicast

Casts are used to convert a value from one type to another. This program uses three casts

in succession. What does it print?

public class Multicast {

public static void main(String[] args) {

n((int) (char) (byte) -1);

}

}

Solution 6: Multicast

This program is confusing any way you slice it. It starts with the int value -1, then casts

the int to a byte, then to a char, and finally back to an int. The first cast narrows the value

from 32 bits down to 8, the second widens it from 8 bits to 16, and the final cast widens it

from 16 bits back to 32. Does the value end up back where it started? If you ran the

program, you found that it does not. It prints 65535, but why?

The program's behavior depends critically on the sign extension behavior of casts. Java uses

two's-complement binary arithmetic, so the int value -1 has all 32 bits set. The cast from int

to byte is straightforward. It performs a narrowing primitive conversion [JLS 5.1.3], which

45