Java实现蜘蛛纸牌 设计与实现.doc

Java实现蜘蛛纸牌 设计与实现.doc

ID:61005042

大小:551.00 KB

页数:57页

时间:2021-01-19

上传者:U-5649
Java实现蜘蛛纸牌 设计与实现.doc_第1页
Java实现蜘蛛纸牌 设计与实现.doc_第2页
Java实现蜘蛛纸牌 设计与实现.doc_第3页
Java实现蜘蛛纸牌 设计与实现.doc_第4页
Java实现蜘蛛纸牌 设计与实现.doc_第5页
资源描述:

《Java实现蜘蛛纸牌 设计与实现.doc》由会员上传分享,免费在线阅读,更多相关内容在应用文档-天天文库

Java实现蜘蛛纸牌摘要蜘蛛纸牌的每一代的window的系统都有,同时也时一款很受用户喜欢的休闲类游戏,很多人都喜欢玩蜘蛛纸牌。本人做的蜘蛛纸牌游戏开发理念是基于window7操作系统中的蜘蛛纸牌游戏。利用java语言实现蜘蛛纸牌游戏的主要功能:纸牌的移动,放置,回收,重发。利用javax.swing包的类实现纸牌游戏的用户界面,通为各个菜单组件添加监视器来实现鼠标单击事件所触发的接口方法,使得用户可以单击菜单项来实现具体的功能。通过这次游戏程序的设计,让我对Java有了更深的了解和练习,这对于我以后找到工作打下了的坚实的基础。关键字:java语言;游戏的背景;功能实现 JavaSpiderSolitaireAbstractSystemofSpiderSolitaireeachgenerationofbothwindow,butalsoaverypopularwhenuserslikeleisuregame,alotofpeopleliketoplayspidersolitaire.IdoSpiderSolitairegamedevelopmentconceptisthewindow7operatingsystemofSpiderSolitairegamebasedon.ToachievethemainfunctionofSpiderSolitairegameusingjavalanguage:Solitairemobile,placement,recovery,repeat.Thecardgameuserinterfaceusingthejavax.swingpackageclass,interfacemethodforeachmenucomponentstoaddmonitortoachievethemouseclickeventtriggered,sothattheusercanclickonamenuitemtoachievespecificfunctions.Throughthedesignofthegameprogram,letmehaveadeeperunderstandingandPracticeonJava,thisrightafterIfindworktolayasolidfoundation.Keywords:Javalanguage;gamebackground;functionrealizati 目录摘要iAbstractii1绪论11.1游戏开发的背景知识11.2需要做的准备工作12开发工具介绍22.1java语言概述22.2java语言的特点22.3Eclipse简介33可行性分析43.1概述43.2本系统的可行性分析53.3系统分析53.3.1限定问题53.3.2确定目标63.3.3调查研究,收集数据63.3.4提出方案和评价标准63.3.5方案评估63.3.6提交可行方案64总体设计74.1系统设计74.2主要模型84.3系统功能结构图85详细设计105.1代码功能功能模块设计105.2模块一的详细介绍105.2.1主要的类105.2.2主要的变量115.2.3主要的方法115.3模块二的详细介绍125.3.1主要的类125.3.2主要的变量125.3.3主要的方法125.4模块三的详细介绍135.4.1主要类介绍135.4.2主要变量135.4.3主要方法135.5模块四的详细介绍145.5.1主要的类145.5.2主要的变量145.5.3主要的方法14 6具体功能的设计166.1需要实现的主要功能166.2主要功能的代码实现176.2.1主界面的实现176.2.2游戏按钮的选项及其下拉列表186.2.3纸牌移动操作196.2.4回收纸牌操作206.2.5帮助菜单功能226.2.6退出模块设计237程序的运行及发布247.1运行程序247.2发布程序248软件测试258.1简介258.2软件测试的原则258.3软件测试的目标268.4软件测试内容268.5测试的方法268.5.1等价类268.5.2边界值27结论33参考文献34致谢35外文原献36中文翻译48 1绪论1.1游戏开发的背景知识蜘蛛纸牌时一款很受大家喜欢的休闲类游戏,随着科学技术的不断发展的蜘蛛纸牌的的功能也越来越强大,界面变的越来越美观,玩蜘蛛纸牌的人也很多。蜘蛛纸牌在window2000的时候就已经存在于系统中,一直是大家在休闲的最佳游戏。通过这次蜘蛛纸牌游戏的设计,实现蜘蛛纸牌的主要功能,不论是对游戏的了解,还是对自己设计程序的能力都是很好的提高。游戏的目标是以最少的移动次数将牌面中的十叠牌以及待发的五组,共计八副牌整理移除。当所有牌被移除整理到界面的左下方,游戏获胜。1.2需要做的准备工作需要做的工作有学习掌握开发程序的方法,安装所需的编程的平台eclipse,了解开发工具,进行进程分析,根据得需求规格说明书设计具体的程序流程图,编写程序,修改程序,运行程序,系统调试程序、测试程序,发布程序。 2开发工具介绍2.1java语言概述java是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由SunMicrosystems公司于1995年5月推出的Java程序设计语言和Java平台(即JavaEE,JavaME,JavaSE)的总称。Java自面世后就非常流行,发展迅速,对C++语言形成了有力冲击。Java技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于个人PC、数据中心、游戏控制台、科学超级计算机、移动电话和互联网,同时拥有全球最大的开发者专业社群。在全球云计算和移动互联网的产业环境下,Java更具备了显著优势和广阔前景。现在常用的浏览器比如说360浏览器,IE浏览器中均配备有javaapplet。Java不同于一般的编译执行计算机语言和解释执行计算机语言。它首先将源代码编译成二进制字节码(bytecode),然后依赖各种不同平台上的虚拟机来解释执行字节码。从而实现了“一次编译、到处执行”的跨平台特性。不过,每次的执行编译后的字节码需要消耗一定的时间,这同时也在一定程度上降低了Java程序的性能。2.2java语言的特点java语言是一种简单的、面向对象的、分布式的、健壮的、安全的、与平台无关的、多线程、高性能的、动态程序设计语言。1.java语言简单易学的:java语言的语法与C语言和C++语言很接近,使得多数程序员很容易学习和使用java。另一方面,java丢弃了C++中很少使用的,很难理解的、令人迷惑的那些特性,如操作符重载、多继承、自动的强制类型转换。特别地。java语言不使用指针,并提供了自动的废料收集,使得程序员不必为内存管理而担忧。2.平台无关性:平台无关性是指Java能运行于不同的平台。Java引进虚拟机原理,并运行于虚拟机,实现不同平台的Java接口之间。使用Java编写的程序能在世界范围内共享。Java的数据类型与机器无关,Java虚拟机(JavaVirtualMachine)是建立在硬件和操作系统之上,实现Java二进制代码的解释执行功能,提供于不同平台的接口的。  3.安全性:Java通常被用在网络环境中,为此,Java提供了一个安全机制以防恶意代码的攻击。除了Java语言具有的许多安全特性以外,Java对通过网络下载的类具有一个安全防范机制(类classloader),如分配不同的名字空间以防替代本地的同名类、字节代码检查,并提供安全管理机制(类SecurityManager)让Java应用设置安全哨兵。 4.面向对象:Java语言提供类、接口和继承等原语,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制。Java语言全面支持动态绑定,而C++语言只对虚函数使用动态绑定。总之,Java语言是一个纯的面向对象程序设计语言。  5.分布式:Java语言支持Internet应用的开发,在基本的Java应用编程接口中有一个网络应用编程接口,它提供了用于网络应用编程的类库,包括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程方法激活)机制也是开发分布式应用的重要手段。 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实现的。11.Java语言是体系结构中立的:Java程序(后缀为java的文件)在Java平台上被编译为体系结构中立的字节码格式(后缀为class的文件),然后可以在实现这个Java平台的任何系统中运行。这种途径适合于异构的网络环境和软件的分发。2.3Eclipse简介Eclipse是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse附带了一个标准的插件集,包括Java开发工具(JavaDevelopmentTools,JDT)。  虽然大多数用户很乐于将Eclipse当作Java集成开发环境(IDE)来使用,但Eclipse的目标却不仅限于此。Eclipse还包括插件开发环境(Plug-inDevelopmentEnvironment,PDE),这个组件主要针对希望扩展Eclipse的软件开发人员,因为它允许他们构建与Eclipse环境无缝集成的工具。由于Eclipse中的每样东西都是插件,对于给Eclipse提供插件,以及给用户提供一致和统一的集成开发环境而言,所有工具开发人员都具有同等的发挥场所。这种平等和一致性并不仅限于Java开发工具。尽管Eclipse是使用Java语言开发的,但它的用途并不限于Java语言;例如,支持诸如C/C++和COBOL等编程语言的插件已经可用,或预计将会推出。Eclipse框架还可用来作为与软件开发无关的其他应用程序类型的基础,比如内容管理系统。基于Eclipse的应用程序的一个突出例子是IBM®Rational®SoftwareArchitect,它构成了IBMJava开发工具系列的基础。EclipseIDEforJavaEEDevelopers。 3可行性分析可行性分析是通过对项目的主要内容和配套条件,如市场需求、资源供应、建设规模、工艺路线、设备选型、环境影响、资金筹措、盈利能力等,从技术、经济、工程等方面进行调查研究和分析比较,并对项目建成以后可能取得的财务、经济效益及社会环境影响进行预测,从而提出该项目是否值得投资和如何进行建设的咨询意见,为项目决策提供依据的一种综合性的系统分析方法。可行性分析应具有预见性、公正性、可靠性、科学性的特点。3.1概述可行性研究主要内容是要求以全面、系统的分析为主要方法,经济效益为核心,围绕影响项目的各种因素,运用大量的数据资料论证拟建项目是否可行。对整个可行性研究提出综合分析评价,指出优缺点和建议。为了结论的需要,往往还需要加上一些附件,如试验数据、论证材料、计算图表、附图等,以增强可行性报告的说服力。化工项目可行性研究软件(RSGL-KX2.0),以化工行业《可行性研究报告》、《立项申请书》规范为基础,通过文本、数据灵活调用,生成符合国家规定的《可行性研究报告》、《项目立项申请书》的格式。本软件的意义在于提高化工应用课题立项及项目生产转化时的可行性研究的准确预测,旨在克服化工科技人员在技术指标、生产销售、环境评价、成本核算、利润分配、风险评估等方面的知识欠缺,降低化工项目的投资风险。该软件的内容翔实,使用简便,只需要输入基础技术数据和基础经济数据就可得到规范的完整《可行性研究报告》和《项目立项申请书》文档。我国可行性研究存在的主要问题,一是工程技术方案的研究论证深度不够。按照国外的通常做法,可行性研究阶段的研究深度应能达到定方案的程度,因此要求在工程技术方案论证,应达到BasicDesign或ConceptDesign的程度,基本相当于我国的初步设计应达到的水平,应提出明确的设备清单;二是财务评价就项目论项目,这与国外利用企业理财的理论和方法进行资本预算管理,对投资项目进行投资决策和融资决策的通行做法存在重大差异,并且在经济评价方面不恰当地使用了"国民经济评价"的概念,由此引起一系列的认识误区;三是在市场分析、组织机构分析等方面与国外差别较大,研究深度严重不足;四是不重视多方案的比选及项目风险分析,或者分析的内容、深度严重不足,缺乏项目周期各阶段风险管理的统一筹划及策略论证。可行性研究的依据:一个拟建项目的可行性研究,必须在国家有关的规划、政策、法规的指导下完成,同时,还必须要有相应的各种技术资料。进行可行性研究工作的主要依据主要包括:①国家经济和社会发展的长期规划,部门与地区规划,经济建设的指导方针、任务、产业政策、投资政策和技术经济政策以及国家和地方法规等;②经过批准的项目建议书和在项目建议书批准后签订的意向性协议等;③由国家批准的资源报告,国土开发整治规划、区域规划和工业基地规划。对于交通运输项目建设要有有关的江河流域规划与路网规划等;④国家进出口贸易政策和关税政策;⑤当地的拟建厂址的自然、经济、社会等基础资料;⑥有关国家、地区和行业的工程技术、经济方面的法令、法规、标准定额资料等;⑦由国家颁布的建设项目可行性研究及经济评价的有关规定;⑧包含各种市场信息的市场调研报告。 可行性研究的一般要求:可行性研究工作对于整个项目建设过程乃至整个国民经济都有非常重要的意义,为了保证可行性研究工作的科学性、客观性和公正性,有效地防止错误和遗漏,在可行性研究中,(1)首先必须站在客观公正的立场进行调查研究,做好基础资料的收集工作。对于收集的基础资料,要按照客观实际情况进行论证评价,如实地反映客观经济规律,从客观数据出发,通过科学分析,得出项目是否可行的结论。(2)可行性研究报告的内容深度必须达到国家规定的标准,基本内容要完整,应尽可能多地占有数据资料,避免粗制滥造,搞形式主义。(3)为保证可行性研究的工作质量,应保证咨询设计单位足够的工作周期,防止因各种原因的不负责任草率行事。具体工作周期由委托单位与咨询设计单位在签订合同时协商确定。3.2本系统的可行性分析(1).投资必要性主要根据市场调查及预测的结果,以及有关的产业政策等因素,论证项目投资建设的必要性;(2).技术的可行性主要从事项目实施的技术角度,合理设计技术方案,并进行比选和评价;(3).财务的可行性主要从项目及投资者的角度,设计合理财务方案,从企业理财的角度进行资本预算,评价项目的财务盈利能力,进行投资决策,并从融资主体(企业)的角度评价股东投资收益、现金流量计划及债务清偿能力;(4).组织的可行性制定合理的项目实施进度计划、设计合理组织机构、选择经验丰富的管理人员、建立良好的协作关系、制定合适的培训计划等,保证项目顺利执行;(5).经济的可行性从资源配置的角度衡量项目的价值,评价项目在实现区域经济发展目标、有效配置经济资源、增加供应、创造就业、改善环境、提高人民生活等方面的效益。(6).社会可行性分析项目对社会的影响,包括政治体制、方针政策、经济结构、法律道德、宗教民族、妇女儿童及社会稳定性等;(7).风险因素控制的可行性对项目的市场风险、技术风险、财务风险、组织风险、法律风险、经济及社会风险等因素进行评价,制定规避风险的对策,为项目全过程的风险管理提供依据。3.3系统分析3.3.1限定问题所谓问题,是现实情况与计划目标或理想状态之间的差距。系统分析的核心内容有两个:其一是进行“诊断”,即找出问题及其原因;其二是“开处方”,即提出解决问题的最可行方案。所谓限定问题,就是要明确问题的本质或特性、问题存在范围和影响程度、问题产生的时间和环境、问题的症状和原因等。限定问题是系统分析中关键的一步,因为如果“诊断”出错,以后开的“处方” 就不可能对症下药。在限定问题时,要注意区别症状和问题,探讨问题原因不能先入为主,同时要判别哪些是局部问题,哪些是整体问题,问题的最后确定应该在调查研究之后。通过亲身体验总结各银行ATM提款机界面流程,得到本系统开发的主要流程界面(主要功能取款、转账、查询、修改密码等)。3.3.2确定目标系统分析目标应该根据客户的要求和对需要解决问题的理解加以确定,如有可能应尽量通过指标表示,以便进行定量分析。对不能定量描述的目标也应该尽量用文字说明清楚,以便进行定性分析和评价系统分析的成效。3.3.3调查研究,收集数据调查研究和收集数据应该围绕问题起因进行,一方面要验证有限定问题阶段形成的假设,另一方面要探讨产生问题的根本原因,为下一步提出解决问题的备选方案做准备。调查研究常用的有四种方式,即阅读文件资料、访谈、观察和调查。收集的数据和信息包括事实(facts)、见解(opinions)和态度(attitudes)。要对数据和信息去伪存真,交叉核实,保证真实性和准确性。3.3.4提出方案和评价标准通过深入调查研究,使真正有待解决的问题得以最终确定,使产生问题的主要原因得到明确,在此基础上就可以有针对性地提出解决问题的备选方案。备选方案是解决问题和达到咨询目标可供选择的建议或设计,应提出两种以上的备选方案,以便提供进一步评估和筛选。为了对备选方案进行评估,要根据问题的性质和客户具备的条件。提出约束条件或评价标准,供下一步应用。3.3.5方案评估根据上述约束条件或评价标准,对解决问题备选方案进行评估,评估应该是综合性的,不仅要考虑技术因素,也要考虑社会经济等因素,评估小组应该有一定代表性,除咨询项目组成员外,也要吸收客户组织的代表参加。根据评估结果确定最可行方案。3.3.6提交可行方案最可行方案并不一定是最佳方案,它是在约束条件之内,根据评价标准筛选出的最现实可行的方案。如果客户满意,则系统分析达到目标。如果客户不满意,则要与客户协商调整约束条件或评价标准,甚至重新限定的问题,开始新一轮系统分析,直到客户满意为止。4总体设计4.1系统设计 即对有关系统全局问题的设计,也就是设计系统总的处理方案,又称系统概要设计。它包括:计算机配置设计、系统模块结构设计、数据库和文件设计、代码设计以及系统可靠性与内部控制设计等内容。软件功能分解属于下列软件开发中的总体设计阶段。概要设计解决软件系统的模块划分和模块的层次机构以及数据库设计;详细设计解决每个模块的控制流程,内部算法和数据结构的设计。这个阶段结束,要交付概要设计说明书和设计说明,也可以合并在一起,称为设计说明书。系统设计通常应用两种方法:一种是归纳法,另一种是演绎法。应用归纳法进行系统设计的程序是:首先尽可能地收集现有的和过去的同类系统的系统设计资料;在对这些系统的设计、制造和运行状况进行分析研究的基础上,根据所设计的系统的功能要求进行多次选择,然后对少数几个同类系统作出相应修正,最后得出一个理想的系统。演绎法是一种公理化方法,即先从普遍的规则和原理出发,根据设计人员的知识和经验,从具有一定功能的元素集合中选择能符合系统功能要求的多种元素,然后将这些元素按照一定形式进行组合(见系统结构),从而创造出具有所需功能的新系统。在系统设计的实践中,这两种方法往往是并用的。系统设计原则:(1)阶段开发原则系统框架和数据结构全面设计,具体功能实现分阶段进行。网站的建设过程可以采取以下三期:第一期工程搭建网站的基本构架,实现电子商务网的大部分功能,初步实现网上交易;第二期工程实现网上竞价系统的全部功能;第三期工程实现网站在线的BtoB交易。(2)易用性原则方便上网客户浏览和操作,最大限度地减轻后台管理人员的负担,做到部分业务的自动化处理。(3)业务完整性原则对于业务进行中的特殊情况能够做出及时、正确的响应,保证业务数据的完整性。(4)业务规范化原则在系统设计的同时,也为将来的业务流程制定了较为完善的规范,具有较强的实际操作性。(5)可扩展性原则系统设计要考虑到业务未来发展的需要,要尽可能设计得简明,各个功能模块间的耦合度小,便于系统的扩展。如果存在旧有的数据库系统,则需要充分考虑兼容性。本系统参照windows游戏蜘蛛纸牌,自己编写设计实现其功能,它具有如下一些功能:设计一个游戏界面,包括玩牌区、发牌区和回收区。(1)纸牌以及其背景的设定。(2)移动纸牌。使用鼠标将较小的纸牌拖放到较大的纸牌下方,可以拖动一组纸牌,当要求这组纸牌是同一个花色,并且这一组纸牌从上到下是从大到小排列的,且不间断。(3)回收纸牌。当某列出现同一花色,从上到下依次是从K到A的不间组合时,这组纸牌将被回收到回收区中。(4)发牌。用鼠标单击发牌区,若发牌区还有剩余的纸牌,则将发出一叠纸牌共10张,依次添加到玩牌区的10列纸牌最下方,但要求这10列纸牌没有空白列。.若玩家不了解游戏规则可以点击帮助获得游戏方法。 (5)级别设定。点击“游戏”菜单中选取游戏难以级别,有3种级别,分别对应初级、中级、高级。(6)退出游戏。4.2主要模型该游戏的开发及相关功能的实现需要在Eclipse下建立javaproject,同时编写Spider.java、PKCard.java、SpiderMenu..java、AboutDialog.class,另外将纸牌的图片文件保存在images文件夹中。该游戏可分为以下各项功能:(1)Spider.java用于实现纸牌初始化、设置游戏等级以及异常处理。(2)PKCard.java用于定义纸牌的显示的各种属性以及鼠标事件的相应属性。(3)SpiderMenu..java用于添加游戏下拉菜单的各个选项,包含图形用户界面的构建,组件监听的实现,以及显示可执行操作的线程。(4)SpiderMenu.class生成Spider.class,AboutDialog.class和PKCard.class。4.3系统功能结构图所谓功能结构图就是将系统的功能进行分解,按功能从属关系表示的图表。管理信息系统的各子系统可以看作是系统目标下层的功能,对其中每项功能还可以继续分解为第三层、第四层……甚至更多的功能。功能结构图就是按照功能的从属关系画成的图表,图中的每一个框都称为一个功能模块。功能模块可以根据具体情况分的大一点或小一点,分解得最小功能模块可以是一个程序中的每个处理过程,而较大的功能模块则可能是完成某一个任务的一组程序。功能结构图是对硬件、软件、解决方案等进行解剖,详细描述功能列表的结构,构成,剖面的从大到小,从粗到细,从上到下等而描绘或画出来的结构图。从概念上讲,上层功能包括(或控制)下层功能,愈上层功能愈笼统,愈下层功能愈具体。功能分解的过程就是一个由抽象到具体、由复杂到简单的过程。图中每一个框称为一个功能模块。功能模块可以根据具体情况分得大一点或小一点。分解得最小的功能模块可以是一个程序中的每个处理过程,而较大的功能模块则可能是完成某一任务的一组程序。系统设计是新系统的物理设计阶段。根据系统分析阶段所确定的新系统的逻辑模型、功能要求,在用户提供的环境条件下,设计出一个能在计算机网络环境上实施的方案,即建立新系统的物理模型。 图4-1系统功能结构图 5详细设计5.1代码功能功能模块设计蜘蛛纸牌游戏共由4个部分组成,分别是:Spider.java,SpiderMenuBar.java,PKCard.java,AboutDialog.java。SpiderMenuBar.java包含名为SpiderMenuBar的public类,其主要功能为生成蜘蛛纸牌游戏的菜单栏,实现菜单栏中各个组件的事件侦听。主要包括3个模块:图形用户界面的构建;组件监听接口的实现:显示可执行操作的线程。PKCard.java包含名为PKCard的public类,其主要功能为:定义纸牌的属性,包括名称,位置等相关信息。并通过相关方法实现纸牌的移动等。AboutDialog.java包含名为AboutDialog的public类,其主要功能为生成蜘蛛纸牌游戏的帮助栏。Spider.java包含名为Spider的public类,其主要功能为生成蜘蛛纸牌游戏的框架,实现游戏中的方法,包括:纸牌的随机生成,位置的摆放等。图5-1程序的总体设计流程图5.2模块一的详细介绍SpiderMenuBar.java的功能是生成蜘蛛纸牌游戏的菜单栏。实现菜单栏中的各个组件的事件。5.2.1主要的类 JMenuBar类、JMenu类、JMenuItem类、JRadioButton类都是设计GUI(图形用户界面)的包javax.swing中的类。可以用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类创建的对象用于主界面窗口的实现。将在Spider.java文件中介绍。5.2.3主要的方法publicJMenuBar()创建新的菜单栏。publicJMenu(Stringtext)构造一个新菜单,用提供的字符串作为其文本。publicJMenuItem(Stringtext)创建带有指定文本的菜单项。publicJRadioButtonMenuItem(Stringtext)创建一个带文本的单选按钮菜单项。publicButtonGroup()创建一个新的单选按钮对象组。publicvoidadd(MenuItemitem)向菜单增加由参数item指定的菜单项对象。publicvoidadd(AbstractButtonb)将单选按钮添菜单项加到组中。publicvoidadd(JMenuc)将指定的菜单添加加到菜单栏中。publicvoidaddSeparator()将新分隔符追加到菜单的末尾。所需要添加的事件处理的接口方法将在后来的模块中进行介绍。事件源即能够产生的事件的对象。监视器用于对事件源进行监视以便对发生的事件做出处理。事件源通过调用相应的方法将某个对象作为自己的监视器。这个方法是addActionListen(ActionListenerlisten)该方法中的参数是ActionListener类型的接口。因此必须用ActionListener接口的类创建的对象传递给该方法的参数,使得该对象成为事件源的的监视器。监视器负责调用特定的方法处理事件,创建监视器的类必须提供处理事件的特定的方法,即实现接口方法。publicvoidaddMenuListener(MenuListenerl)添加菜单事件的侦听器。voidmenuSelected(MenuEvente)选择某个菜单时调用。voidmenuDeselected(MenuEvente)取消选择某个菜单时调用。voidmenuCanceled(MenuEvente)取消菜单时调用。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主要的方法publicJTabbedPane()创建一个具有默认的JTabbedPane.TOP选项卡布局的空TabbedPane.publicJDialog()创建一个没有标题并且没有指定Frame所有者的无模式对话框。一个共享的、隐藏的窗体将被设置为该对话框的所有者。publicJPanel()创建具有双缓冲和流布局的新面板容器。publicJTextArea(Stringtext)构造显示指定文本为text的新的文本区。publicvoidsetTittle(Strings)设置一个标题为s的对话框。publicvoidsetSize(intwidth,intheigth)创建宽为width,高位heigth大小的对话框。publicvoidsetResizable(booleanb)设置对话框是否可调整大小。b为true时,对话框可调整大小。publicvoidsetDefaultCloseOperation(intoperation)该方法用来设置单机窗体右上角的关闭图标后,程序会做出怎样的处理。operation取DISPOSE_ON_CLOSE时//隐藏当前窗口,并释放窗体所占有的其他资源。publicvoidsetVisible(booleanb)设置框口是可见还是不可见。b取true是为可见。publicContainergetContentPane()方法可得到窗口的内容面板容器。publicvoidJTextArea.setSize(intwidth,intheigth)设置文本区的大小。publicvoidJTextArea.setEditable(booleanb)设置文本区是否可以编辑。publicvoidJTextArea.setLineWrap(booleanb)设置文本区中输入的文本是否可以实现在右边界自动换行。publicvoidJTextArea.setFont(Fontf)设置文本区内的字体。publicvoidJTextArea.setForeground(Colorc)设置文本区组件的前景色。publicvoidaddTab(Stringtitle,Iconicon,Componentcomponent,Stringtip)添加由title和/或icon表示的component和tip,其中任意一个都可以为nul参数:title-此选项卡中要显示的标题,icon-此选项卡中要显示的图标,component-单击此选项卡时要显示的组件,tip-此选项卡要显示的工具提示。publicvoidpack()调整此窗口的大小,以适合其子组件的首选大小和布局。 5.4模块三的详细介绍PKCard的作用是定义纸牌的属性,包括名称、位置等相关信息。并通过相关方法实现纸牌的移动。5.4.1主要类介绍JLable类负责创建标签对象。标签用于短文本字符串或图像或二者的显示区。Thread类负责创建线程对象。MouseListener接口、MouseMotionListener接口负责处理事件源所触发的鼠标事件,包括处理鼠标按下、释放、进入、退出、单击、连击、拖动、移动所触发鼠标事件。Point类负责创建表示(x,y)坐标空间中的位置的点对象,以整数精度指定。MouseEvent类负责创建鼠标所发生的事件对象。Flash类负责创建线程对象,不断的获取下一张纸牌。PKCard类是文件的主类,实现MouseListener接口的JLable的子类。负责创建纸牌对象。5.4.2主要变量Pointpoint变量表示纸牌的位置对象。PointinitPoint表示纸牌的初始化位置对象。intvalue变量表示纸牌的内容值。inttype变量表示纸牌的类型。Stringname变量表示纸牌的名称。Containerpane变量表示标签内主容器。booleancanMove变量表示纸牌是否可以移动,值为true时,可以移动。booleanisFront变量表示纸牌是否正面显示,值为true时,则为正面显示。PKCardpreviousCard变量表示上面一张纸牌5.4.3主要方法publicvoidflashCard(PKCardcard)方法启动Flash线程不停的获取下一张纸牌直至完成。publicvoidrun()方法为纸牌的正面设置白色图片。card.updateUI()方法将UI属性重置为当前外观的值。publicvoidmousePressed(MouseEventmp)方法按下鼠标时事件的处理方法。publicvoidmouseReleased(MouseEventmr)方法释放鼠标时事件处理的方法。publicvoidsetNextCardLocation(Pointpoint)方法放置鼠标时事件处理方法。publicintwhichColumnAvailable(Pointpoint)方法为判断可用列。publicvoidmouseDragged(MouseEventargO)方法鼠标拖动纸牌时事件处理方法。publicvoidmoving(intx,inty)方法将纸牌移动(x,y)个位置。pane.setComponentZOrder(this,1)方法将主件移动到容器中指定的顺序索引。publicPKCard(Stringname,Spiderspider)方法纸牌的构造函数。publicvoidturnFront()方法令纸牌显示正面。publicvoidturnRear()方法令纸牌显示背面。publicvoidmoveto(Pointpoint()方法将纸牌移动到点pointpublicvoidsetCanMove(booleancan)方法判断纸牌是否能够移动。publicbooleanisCardFront()方法判断纸牌是否正面显示。publicbooleanisCardCanMove()方法判断纸是否能够移动。 publicintgetCardValue()方法获得纸牌的内容值。publicintgetCardType()方法获得纸牌的类型。5.5模块四的详细介绍Spider.java文件是蜘蛛纸牌游戏的主类文件,其主要功能是生成蜘蛛纸牌游戏的框架,实现游戏中的方法,包括纸牌的随机生成、位置的摆放等。5.5.1主要的类JFrame类及其子类负责创建的对象称为窗体。JLable类负责创建标签对象。标签用于短文本字符串或图像或二者的显示区。Container类负责创建一个容器对象,此容器可以包含其它组件。PKCard类负责创建纸牌对象。Hashtable类实现一个哈希表,该哈希表将键映射到相应的值。5.5.2主要的变量publicstaticfinalintEASY=1;代表“简单”等级。publicstaticfinalintNATURAL=2代表“普通”等级。publicstaticfinalintHARD=3代表“难”等级。privateintgrade=Spider.EASY设定初始等级为简单等级;privateContainerpane变量为Container类创建的面板容器。privatePKCardcard[]变量为PKCard类创建的纸牌数组[]。privateJLableclickLable1变量为JLable类创建的右下角发牌区的鼠标点击响应区域。privateJLablegroudLable[]变量为JLable类创建的背景框数组。Hshtabletable变量为Hashtable类创建的一个哈希表对象,用于存储键值数据对。privateintc变量为纸牌的数量。privateintn变量为纸牌的等级。privateinta变量为纸牌所在的列号。privateintfinish变量为纸牌从小到大排列成功的次数。5.5.3主要的方法publicvoidsetTittle(Strings)设置一个标题为s的窗体。publicvoidsetVisible(booleanb)设置框体是可见还是不可见。b取true是为可见。publicvoidsetSize(intwidth,intheigth)创建宽为width,高位heigth大小的窗体。publicvoidsetDefaultCloseOperation(intoperation)该方法用来设置单机窗体右上角的关闭图标后,程序会做出怎样的处理。operation取EXIT_ON_CLOSE时结束窗体所在的应用程序。publicvoidsetMenuBar(MenuBarmb)将此窗体的菜单栏设置为指定的菜单栏。publicvoidpane.setBackground(Colorc)设置面板的背景颜色。publicvoidpane.setLayout(布局对象)设置面板的布局。publicContainergetContentPane()方法可得到窗口的内容面板容器。publicvoidsetBounds(inta,intb,intweidth,intheigth)设置出现在屏幕中的组件距离屏幕的左面a个像素,距屏幕上面b个像素,组件宽为weidth,高为heigth。publicvoidnewGame()方法开始新游戏。publicintgetC()方法返回纸牌的数量。publicvoidsetGrade(intgrade)方法设置纸牌游戏的等级。 publicvoidinitCards()方法进行初始化纸牌publicvoidrandomCards()方法令纸牌随机分配。publicvoidsetNA()方法设置还原纸牌游戏。publicvoidsetCardsLocation()方法设置待展开纸牌级表面初始化纸牌的位置。publicvoidshowEnableOperator()方法进行显示是否有可以移动的纸牌。publicvoiddeal()方法开始运行游戏。publicPKCardgetPreviousCard(PKCardcard)方法获取当前纸牌上面的那一张纸牌。publicPKCardgetNextCard(PKCardcard)方法获取当前纸牌的下面的那一张纸牌。publicPointgetLastCardLocation(intn)方法获取第n列纸牌最后一张纸牌的位置。publicPointgetGroundLabelLocation(intn)方法获取第n列纸牌的背景框架的位置。publicvoidsetGroundLabelZOrder()放置groundLable组件。将组件groundLable移动到容器中指定的顺序索引。顺序(105+i)确定了绘制组件的顺序;具有最高顺序的组件将第一个绘制,具有最低顺序的组建将最后一个绘制。在组建重叠的地方,具有较低顺序的组建将覆盖具有较高顺序的组件。publicvoidhaveFinish(intcolumn)判断纸牌的摆放是否成功。6具体功能的设计6.1需要实现的主要功能(1)游戏主界面的设计包括背景颜色、框架大小、玩牌区域、发牌区域、回收纸牌的区域组件的设置。(2)点击鼠标、释放鼠标、拖动鼠标时纸牌需要实现的事件处理程序。(3)移动放置一组或者单个纸牌时。需要纸牌按照由小到大的顺序排列,并列类型相同。(4)当某列的纸牌按照从A到K得顺序排列时,程序会自动回收这组纸牌到回收纸牌的区域。 (5)当用鼠标单击发牌区域的纸牌时,如果10列纸牌中没有空白列,则顺序把纸牌发到每列纸牌的最下面。否则,弹出有空位不能发牌对话框。(6)当玩家不知道当前游戏如何进行时,可以点击菜单项显示可行的操作提示玩家下一步该移动哪一张纸牌。(7)等级设置,玩家通过单击游戏菜单里相应的等级菜单项来设置游戏的难度等级。游戏的默认等级是简单等级。图6-1功能设计流程图该流程图根据实际的蜘蛛纸牌进行设计,可以分为六个层面,开始运行程序,显示出程序的标题(蜘蛛牌),初始化游戏的界面,此时可以进入游戏,在进入游戏时还可以选择游戏的相关操作,比如开局,重新发牌,显示可行的操作,也就是提示,选择等级,退出等,在等级的选择中设置了三个相应的等级,简单,中级和高级。对于新手来说还可以选择帮助界面,可以查看关于游戏的东西,比如游戏规则和声明。6.2主要功能的代码实现6.2.1主界面的实现publicclassAboutDialogextendsJDialog//JDialog类是创建对话框窗口的主要类。可以使用此类创建自定义的对话框,或者调用JOptionPane中的多个类方法来创建各种标准对话框{JPaneljMainPane=newJPanel();//Jpanel类是一个面板类负责创建一个面板容器,再向这个面板添加组件,然后将面板添加到底层容器中JTabbedPanejTabbedPane=newJTabbedPane();//JTabbedPane类负责创建一个组件,它允许用户通过单击具有给定标题和/或图标的选项卡,在一组组件之间进行切换privateJPaneljPanel1=newJPanel();privateJPaneljPanel2=newJPanel();privateJTextAreajt1=newJTextArea("将电脑多次分发给你的牌按照相同的花色由大至小排列起来。直到桌面上的牌全都消失。");//JTextArea类负责创建一个显示纯文本的多行区域。它作为一个轻量级组件privateJTextAreajt2=new JTextArea("该游戏中,纸牌的图片来自于Windows7的纸牌游戏,图片权属于原作者所有!");publicAboutDialog()//AboutDialog类是JDialog类的子类,负责创建一个显示对话框{setTitle("蜘蛛牌");setSize(300,200);setResizable(false);//生成的窗口由程序员决定大小,用户不能随意改变窗口大小setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);//设置框的大小Containerc=this.getContentPane();//初始化容器jt1.setSize(260,200);jt2.setSize(260,200);jt1.setEditable(false);//是调用这个函数的控件不能被编辑jt2.setEditable(false);jt1.setLineWrap(true);//设置自动换行jTabbedPane.setSize(300,200);//publicJTabbedPane()创建一个具有默认的JTabbedPane.TOP选项卡布局的空TabbedPane.jTabbedPane.addTab("游戏规则",null,jPanel1,null);jTabbedPane.addTab("声明",null,jPanel2,null);publicclassSpiderMenuBarextendsJMenuBar{//生成spider框架对象Spidermain=null;//生成菜单组JMenujNewGame=newJMenu("游戏");JMenujHelp=newJMenu("帮助"); 图6-2游戏初始化界面在“游戏”菜单上,单击“开局”开始游戏。牌面上有十叠牌,前四叠每叠6张,后六叠每叠5张,只有每叠的第一张正面朝外,其他均为正面朝内。该界面使用组件和容器来设置显示游戏和帮助两个选项,玩家可以根据自己的需要选择相关按钮,而且游戏的初始化界面也已经生成,纸牌等待移动。6.2.2游戏按钮的选项及其下拉列表该功能是游戏键的下拉列表,有开局、重新发牌、显示可行操作以及难度选择三个键、退出等功能键,玩家可以根据自己的需要选择相应的操作,起实现代码如下://生成菜单项JMenuItemjItemAbout=newJMenuItem("关于");JMenuItemjItemOpen=newJMenuItem("开局");JMenuItemjItemPlayAgain=newJMenuItem("重新发牌");//生成单选框JRadioButtonMenuItemjRMItemEasy=newJRadioButtonMenuItem("简单:单一花色");JRadioButtonMenuItemjRMItemNormal=newJRadioButtonMenuItem("中级:双花色");JRadioButtonMenuItemjRMItemHard=newJRadioButtonMenuItem("高级:四花色");; JMenuItemjItemExit=newJMenuItem("退出");JMenuItemjItemValid=newJMenuItem("显示可行操作");图6-3游戏下拉列表当鼠标移动到对应的按钮并且点击时,系统会根据组件的监听器实现事件的监听,从而产生相应的跳转操作。6.2.3纸牌移动操作移动纸牌。使用鼠标将较小的纸牌拖放到较大的纸牌下方,可以拖动一组纸牌,当要求这组纸牌是同一个花色,并且这一组纸牌从上到下是从大到小排列的,且不间断。实现的代码如下://用鼠标拖动纸牌publicvoidmouseDragged(MouseEventarg0){if(canMove){intx=0;inty=0;Pointp=arg0.getPoint();x=p.x-point.x;y=p.y-point.y;this.moving(x,y);}}publicvoidsetNextCardLocation(Pointpoint){PKCardcard=main.getNextCard(this);if(card!=null){if(point==null){card.setNextCardLocation(null);main.map.remove(card.getLocation());//先从HashMap中删除cardcard.setLocation(card.initPoint);//为card设置新的坐标main.map.put(card.initPoint,card);//再将card添加到HashMap中 }else{point=newPoint(point);point.y+=20;card.setNextCardLocation(point);point.y-=20;main.map.remove(card.getLocation());card.setLocation(point);main.map.put(card.getLocation(),card);card.initPoint=card.getLocation();}}}图6-4纸牌移动界面使用鼠标将较小的纸牌拖放到较大的纸牌下方,可以拖动一组纸牌,当要求这组纸牌是同一个花色,并且这一组纸牌从上到下是从大到小排列的,且不间断。鼠标拖动移动一张或一组牌到另一张牌的上面或空牌叠。每次移动的牌都只能放在一叠牌全部移除后的空白位置或者比它最下面的一张牌大1点的牌之上。只有当一组牌全部为同一花色,方可以移动这一组牌。否则,只能移动这一组最上面一张或同一花色的多张。当移动形成同一花色由K到A顺序的一组牌时,这组牌会被自动移除整理到左下方,同时获得分数奖励。6.2.4回收纸牌操作回收纸牌。 当某列出现同一花色,从上到下依次是从K到A的不间组合时,这组纸牌将被回收到回收区中。实现的代码如下:publicvoidhaveFinish(intcolumn){Pointpoint=this.getLastCardLocation(column);PKCardcard=(PKCard)this.map.get(point);do{this.map.remove(point);card.moveto(newPoint(20+finish*10,580));//将组件移动到容器中指定的顺序索引。pane.setComponentZOrder(card,1);//将纸牌新的相关信息存入HashMapthis.map.put(card.getLocation(),card);card.setCanMove(false);point=this.getLastCardLocation(column);if(point==null)card=null;elsecard=this.map.get(point);}while(card!=null&&card.isCanMove());finish++;//如果8付牌全部组合成功,则显示成功的对话框if(finish==8){JOptionPane.showMessageDialog(this,"恭喜你,顺利通过!","成功",JOptionPane.PLAIN_MESSAGE);}if(card!=null){card.turnFront();card.setCanMove(true);}}}jNewGame、jHelp变量分别是JMenu类创建的“游戏”、“帮助”菜单。jItemAbout、jItemOpen、jItemPlayAgain、jItemExit、jItemValid变量分别是JMenuItem类创建的“关于”、“开局”、“重新发牌”、“退出”、“显示可行操作”菜单项。jRMItemEasy、jRMItemNormal、jRMItemHard变量分别是JRadioButtonMenuItem类创建的“简单:单一花色”、“中级:双花色”、“高级:四花色”的单选按钮菜单项。group变量是ButtonGroup类创建的一组按钮选择对象。spider变量是Spider类创建的对象用于主界面窗口的实现。将在Spider.java文件中介绍。 图6-5回收纸牌界面6.2.5帮助菜单功能该模块使用与新手玩家可以知道游戏规则以及该游戏的版权归属。实现的代码如下:publicclassAboutDialogextendsJDialog{JPaneljMainPanel=newJPanel();JTabbedPanejTabbedPane=newJTabbedPane();privateJPaneljPanel1=newJPanel();privateJTextAreajt1=newJTextArea("将电脑多次分发给你的牌按照相同的花色由大至小排列起来。直到桌面上的牌全都消失。");publicAboutDialog(){setTitle("蜘蛛牌");setSize(300,200);setResizable(false);setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);Containerc=this.getContentPane();jt1.setSize(260,200);jt1.setEditable(false);jt1.setLineWrap(true);jt1.setFont(newFont("楷体_GB2312",java.awt.Font.BOLD,13));jt1.setForeground(Color.BLUE);jPanel1.add(jt1);jTabbedPane.setSize(300,200); jTabbedPane.addTab("游戏规则",null,jPanel1,null);jMainPanel.add(jTabbedPane);c.add(jMainPanel);pack();this.setVisible(true);}}图6-6游戏规则说明图6-7版权声明该模块使用与新手玩家可以知道游戏规则以及该游戏的版权归属。6.2.6退出模块设计该模块实现了退出游戏的功能,玩家可以选择此模块退出游戏。主要代码如下://“退出”jItemExit.addActionListener(newActionListener(){publicvoidactionPerformed(ActionEvente){main.dispose();System.exit(0);}}); 7程序的运行及发布7.1运行程序将文件Spider.java、SpiderMenuBar.java、PKCard.java、AboutDialog.java及所需要的images图像文件保存到同一个文件中。利用javac命令对文件进行编译,使用的命令如下:JavacSpider.java之后利用java命令执行程序,使用的JavaSpider7.2发布程序要发布此应用程序,需要将应用程序打包。使用jar.exe,可以吧应用程序涉及的类和图片压缩成一个jar文件,这样就可以发布程序啦。首先编写一个清单文件,名为MANIFEST.MF,其代码如下:Manifest-Version:1.0Created-By:1.6.0(SunMicrosystemsInc.)Main-Class:Spider将此清单文件保存起来然后,使用如下命令生成jar文件:JarcfmSpider.jarMANIFEST.MF*.class其中参数c表示要生成一个新的jar文件;f表示要生成的jar文件的名字;m表示要生成的清单文件的名字。如果机器安装过WinRAR解压软件,并将.jar文件与解压缩软件做了关联,那么Spider.java文件的类型是WinRAR,使得java程序无法运行。因此,在发布软件时还应该再写一个有如下内容的bat文件(Spider.bat):Javaw-jarSpider.jar然后可以通过双击Spider.bat来运行程序。 8软件测试软件测试,描述一种用来促进鉴定软件的正确性、完整性、安全性和质量的过程。软件测试的经典定义是:在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估的过程。8.1简介软件测试是使用人工操作或者软件自动运行的方式来检验它是否满足规定的需求或弄清预期结果与实际结果之间的差别的过程。它是帮助识别开发完成(中间或最终的版本)的计算机软件(整体或部分)的正确度(correctness)、完全度(completeness)和质量(quality)的软件过程;是SQA(softwarequalityassurance)的重要子域。GlenfordJ.Myers曾对软件测试的目的提出过以下观点:图8-1软件测试过程(1)测试是为了发现程序中的错误而执行程序的过程。(2)好的测试方案是极可能发现迄今为止尚未发现的错误的测试方案。(3)成功的测试是发现了至今为止尚未发现的错误的测试。(4)测试并不仅仅是为了找出错误。通过分析错误产生的原因和错误的发生趋势,可以帮助项目管理者发现当前软件开发过程中的缺陷,以便及时改进。(5)这种分析也能帮助测试人员设计出有针对性的测试方法,改善测试的效率和有效性。(6)没有发现错误的测试也是有价值的,完整的测试是评定软件质量的一种方法。(7)另外,根据测试目的的不同,还有回归测试、压力测试、性能测试等,分别为了检验修改或优化过程是否引发新的问题、软件所能达到处理能力和是否达到预期的处理能力等。8.2软件测试的原则(1)测试应该尽早进行,最好在需求阶段就开始介入,因为最严重的错误不外乎是系统不能满足用户的需求。(2)程序员应该避免检查自己的程序,软件测试应该由第三方来负责。(3)设计测试用例时应考虑到合法的输入和不合法的输入以及各种边界条件,特殊情况下要制造极端状态和意外状态,如网络异常中断、电源断电等。 (1)应该充分注意测试中的群集现象。(2)对错误结果要进行一个确认过程。一般由A测试出来的错误,一定要由B来确认。严重的错误可以召开评审会议进行讨论和分析,对测试结果要进行严格地确认,是否真的存在这个问题以及严重程度等。(3)制定严格的测试计划。一定要制定测试计划,并且要有指导性。测试时间安排尽量宽松,不要希望在极短的时间内完成也有一个高水平的测试。(4)妥善保存测试计划、测试用例、出错统计和最终分析报告,为维护提供方便。8.3软件测试的目标(1)发现一些可以通过测试避免的开发风险。(2)实施测试来降低所发现的风险。(3)确定测试何时可以结束。(4)在开发项目的过程中将测试看作是一个标准项目。8.4软件测试内容软件测试主要工作内容是验证(verification)和确认(validation),下面分别给出其概念:验证(verification)是保证软件正确地实现了一些特定功能的一系列活动,即保证软件以正确的方式来做了这个事件(Doitright)(1)确定软件生存周期中的一个给定阶段的产品是否达到前阶段确立的需求的过程。(2)程序正确性的形式证明,即采用形式理论证明程序符合设计规约规定的过程。(3)评审、审查、测试、检查、审计等各类活动,或对某些项处理、服务或文件等是否和规定的需求相一致进行判断和提出报告。确认(validation)是一系列的活动和过程,目的是想证实在一个给定的外部环境中软件的逻辑正确性。即保证软件做了你所期望的事情。(Dotherightthing)(1)静态确认,不在计算机上实际执行程序,通过人工或程序分析来证明软件的正确性。(2)动态确认,通过执行程序做分析,测试程序的动态行为,以证实软件是否存在问题。软件测试的对象不仅仅是程序测试,软件测试应该包括整个软件开发期间各个阶段所产生的文档,如需求规格说明、概要设计文档、详细设计文档,当然软件测试的主要对象还是源程序。8.5测试的方法8.5.1等价类定义是把所有可能的输入数据,即程序的输入域划分成若干部分(子集),然后从每一个子集中选取少数具有代表性的数据作为测试用例。该方法是一种重要的,常用的黑盒测试用例设计方法。划分等价类 等价类是指某个输入域的子集合。在该子集合中,各个输入数据对于揭露程序中的错误都是等效的,并合理地假定:测试某等价类的代表值就等于对这一类其它值的测试,因此,可以把全部输入数据合理划分为若干等价类,在每一个等价类中取一个数据作为测试的输入条件就可以用少量代表性的测试数据取得较好的测试结果。等价类划分可有两种不同的情况:有效等价类和无效等价类。(1)有效等价类是指对于程序的规格说明来说是合理的、有意义的输入数据构成的集合。利有效等价类可检验程序是否实现了规格说明中所规定的功能和性能。(2)无效等价类与有效等价类的定义恰巧相反。无效等价类指对程序的规格说明是不合理的或无意义的输入数据所构成的集合。对于具体的问题,无效等价类至少应有一个,也可能有多个。设计测试用例时,要同时考虑这两种等价类。因为软件不仅要能接收合理的数据,也要能经受意外的考验,这样的测试才能确保软件具有更高的可靠性。划分等价类的标准(1)完备测试、避免冗余;(2)划分等价类重要的是:集合的划分,划分为互不相交的一组子集,而子集的并是整个集合;(3)并是整个集合:完备性;(4)子集互不相交:保证一种形式的无冗余性;(5)同一类中标识(选择)一个测试用例,同一等价类中,往往处理相同,相同处理映射到"相同的执行路径"。划分等价类的方法(1)在输入条件规定了取值范围或值的个数的情况下,则可以确立一个有效等价类和两个无效等价类。如:输入值是学生成绩,范围是0~100。(2)在输入条件规定了输入值的集合或者规定了"必须如何"的条件的情况下,可确立一个有效等价类和一个无效等价类。8.5.2边界值定义边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充,这种情况下,其测试用例来自等价类的边界。与等价划分的区别(1)边界值分析不是从某等价类中随便挑一个作为代表,而是使这个等价类的每个边界都要作为测试条件。(2)边界值分析不仅考虑输入条件,还要考虑输出空间产生的测试情况。边界值分析方法的考虑:长期的测试工作经验告诉我们,大量的错误是发生在输入或输出范围的边界上,而不是发生在输入输出范围的内部。因此针对各种边界情况设计测试用例,可以查出更多的错误。 使用边界值分析方法设计测试用例,首先应确定边界情况。通常输入和输出等价类的边界,就是应着重测试的边界情况。应当选取正好等于,刚刚大于或刚刚小于边界的值作为测试数据,而不是选取等价类中的典型值或任意值作为测试数据。常见的边界值:(1)对16-bit的整数而言32767和-32768是边界(2)屏幕上光标在最左上、最右下位置(3)报表的第一行和最后一行(4)数组元素的第一个和最后一个(5)循环的第0次、第1次和倒数第2次、最后一次边界值分析(1)边界值分析使用与等价类划分法相同的划分,只是边界值分析假定错误更多地存在于划分的边界上,因此在等价类的边界上以及两侧的情况设计测试用例。例:测试计算平方根的函数--输入:实数--输出:实数--规格说明:当输入一个0或比0大的数的时候,返回其正平方根;当输入一个小于0的数时,显示错误信息"平方根非法-输入值小于0"并返回0;库函数Print-Line可以用来输出错误信息。从是否关心软件内部结构和具体实现的角度划分(按测试分类)白盒测试、黑盒测试、灰盒测试。从是否执行程序的角度:静态测试、动态测试、阶段细分从软件开发的过程按阶段划分有.单元测试、集成测试、确认测试、系统测试、验收测试、回归测试、Alpha测试、Beta测试测试过程按4个步骤进行,即单元测试、集成测试、确认测试和系统测试及发布测试。开始是单元测试,集中对用源代码实现的每一个程序单元进行测试,检查各个程序模块是否正确地实现了规定的功能。集成测试把已测试过的模块组装起来,主要对与设计相关的软件体系结构的构造进行测试。确认测试则是要检查已实现的软件是否满足了需求规格说明中确定了的各种需求,以及软件配置是否完全、正确。系统测试把已经经过确认的软件纳入实际运行环境中,与其它系统成份组合在一起进行测试。单元测试(UnitTesting)(1)单元测试又称模块测试,是针对软件设计的最小单位─程序模块,进行正确性检验的测试工作。其目的在于发现各模块内部可能存在的各种差错。(2)单元测试需要从程序的内部结构出发设计测试用例。多个模块可以平行地独立进行单元测试。单元测试的内容:在单元测试时,测试者需要依据详细设计说明书和源程序清单,了解该模块的I/O条件和模块的逻辑结构,主要采用白盒测试的测试用例,辅之以黑盒测试的测试用例,使之对任何合理的输入和不合理的输入,都能鉴别和响应。模块接口测试 在单元测试的开始,应对通过被测模块的数据流进行测试。测试项目包括:调用本模块的输入参数是否正确;本模块调用子模块时输入给子模块的参数是否正确;全局量的定义在各模块中是否一致在做内外存交换时要考虑:文件属性是否正确;OPEN与CLOSE语句是否正确;缓冲区容量与记录长度是否匹配;在进行读写操作之前是否打开了文件;在结束文件处理时是否关闭了文件;正文书写/输入错误,I/O错误是否检查并做了处理。局部数据结构测试不正确或不一致的数据类型说明,使用尚未赋值或尚未初始化的变量错误的初始值或错误的缺省值,变量名拼写错或书写错,不一致的数据类型,全局数据对模块的影响路径测试选择适当的测试用例,对模块中重要的执行路径进行测试,应当设计测试用例查找由于错误的计算、不正确的比较或不正常的控制流而导致的错误,对基本执行路径和循环进行测试可以发现大量的路径错误。错误处理测试出错的描述是否难以理解,出错的描述是否能够对错误定位,显示的错误与实际的错误是否相符,对错误条件的处理正确与否,在对错误进行处理之前,错误条件是否已经引起系统的干预等边界测试注意数据流、控制流中刚好等于、大于或小于确定的比较值时出错的可能性。对这些地方要仔细地选择测试用例,认真加以测试。如果对模块运行时间有要求的话,还要专门进行关键路径测试,以确定最坏情况下和平均意义下影响模块运行时间的因素。单元测试的步骤模块并不是一个独立的程序,在考虑测试模块时,同时要考虑它和外界的联系,用一些辅助模块去模拟与被测模块相联系的其它模块。驱动模块(driver)桩模块(stub)──存根模块,如果一个模块要完成多种功能,可以将这个模块看成由几个小程序组成。必须对其中的每个小程序先进行单元测试要做的工作,对关键模块还要做性能测试。,对支持某些标准规程的程序,更要着手进行互联测试。有人把这种情况特别称为模块测试,以区别单元测试。集成测试集成测试(组装测试、联合测试),通常,在单元测试的基础上,需要将所有模块按照设计要求组装成为系统。这时需要考虑的问题是:在把各个模块连接起来的时候,穿越模块接口的数据是否会丢失;一个模块的功能是否会对另一个模块的功能产生不利的影响各个子功能组合起来,能否达到预期要求的父功能;全局数据结构是否有问题;单个模块的误差累积起来,是否会放大,从而达到不能接受的程度。在单元测试的同时可进行集成测试,发现并排除在模块连接中可能出现的问题,最终构成要求的软件系统。子系统的集成测试特别称为部件测试,它所做的工作是要找出集成后的子系统与系统需求规格说明之间的不一致。通常,把模块集成成为系统的方式有两种一次性集成方式增殖式集成方式一次性集成方式(bigbang)它是一种非增殖式组装方式。也叫做整体拼装。使用这种方式,首先对每个模块分别进行模块测试,然后再把所有模块组装在一起进行测试,最终得到要求的软件系统。增殖式集成方式这种集成方式又称渐增式集成首先对一个个模块进行模块测试,然后将这些模块逐步组装成较大的系统在集成的过程中边连接边测试,以发现连接过程中产生的问题通过增殖逐步组装成为要求的软件系统。 自顶向下的增殖方式这种集成方式将模块按系统程序结构,沿控制层次自顶向下进行组装。自顶向下的增殖方式在测试过程中较早地验证了主要的控制和判断点。选用按深度方向组装的方式,可以首先实现和验证一个完整的软件功能。自底向上的增殖方式这种集成的方式是从程序模块结构的最底层的模块开始集成和测试。因为模块是自底向上进行组装,对于一个给定层次的模块,它的子模块(包括子模块的所有下属模块)已经组装并测试完成,所以不再需要桩模块。在模块的测试过程中需要从子模块得到的信息可以直接运行子模块得到。自顶向下增殖的方式和自底向上增殖的方式各有优缺点。一般来讲,一种方式的优点是另一种方式的缺点。混合增殖式测试衍变的自顶向下的增殖测试首先对输入/输出模块和引入新算法模块进行测试;再自底向上组装成为功能相当完整且相对独立的子系统;然后由主模块开始自顶向下进行增殖测试。自底向上-自顶向下的增殖测试首先对含读操作的子系统自底向上直至根结点模块进行组装和测试;然后对含写操作的子系统做自顶向下的组装与测试。回归测试这种方式采取自顶向下的方式测试被修改的模块及其子模块;然后将这一部分视为子系统,再自底向上测试。关键模块问题在组装测试时,应当确定关键模块,对这些关键模块及早进行测试。关键模块的特征:满足某些软件需求;在程序的模块结构中位于较高的层次(高层控制模块);较复杂、较易发生错误;有明确定义的性能要求。确认测试(ValidationTesting)确认测试又称有效性测试。任务是验证软件的功能和性能及其它特性是否与用户的要求一致。对软件的功能和性能要求在软件需求规格说明书中已经明确规定。它包含的信息就是软件确认测试的基础。进行有效性测试(黑盒测试)有效性测试是在模拟的环境(可能就是开发的环境)下,运用黑盒测试的方法,验证被测软件是否满足需求规格说明书列出的需求。首先制定测试计划,规定要做测试的种类。还需要制定一组测试步骤,描述具体的测试用例。通过实施预定的测试计划和测试步骤,确定软件的特性是否与需求相符;所有的文档都是正确且便于使用;同时,对其它软件需求,例如可移植性、兼容性、出错自动恢复、可维护性等,也都要进行测试在全部软件测试的测试用例运行完后,所有的测试结果可以分为两类:测试结果与预期的结果相符。这说明软件的这部分功能或性能特征与需求规格说明书相符合,从而这部分程序被接受。测试结果与预期的结果不符。这说明软件的这部分功能或性能特征与需求规格说明不一致,因此要为它提交一份问题报告。软件配置复查软件配置复查的目的是保证软件配置的所有成分都齐全;各方面的质量都符合要求;具有维护阶段所必需的细节;而且已经编排好分类的目录。应当严格遵守用户手册和操作手册中规定的使用步骤,以便检查这些文档资料的完整性和正确性。系统测试系统测试,是将通过确认测试的软件,作为整个基于计算机系统 的一个元素,与计算机硬件、外设、某些支持软件、数据和人员等其它系统元素结合在一起,在实际运行环境下,对计算机系统进行一系列的组装测试和确认测试。系统测试的目的在于通过与系统的需求定义作比较,发现软件与系统的定义不符合或与之矛盾的地方。验收测试在通过了系统的有效性测试及软件配置审查之后,就应开始系统的验收测试。验收测试是以用户为主的测试。软件开发人员和QA(质量保证)人员也应参加。由用户参加设计测试用例,使用生产中的实际数据进行测试。在测试过程中,除了考虑软件的功能和性能外,还应对软件的可移植性、兼容性、可维护性、错误的恢复功能等进行确认。确认测试应交付的文档有:确认测试分析报告最终的用户手册和操作手册项目开发总结报告。编写规范目的:统一测试用例编写的规范,以保证使用最有效的测试用例,保证测试质量。范围:适用于公司对产品的业务流程、功能测试测试用例的编写。术语解释测试分析:对重要业务、重要流程进行测试前的分析。业务流程测试用例:关于产品业务、重要流程的测试用例。业务流程测试用例编写原则系统性对于系统业务流程要能够完整说明整个系统的业务需求、系统由几个子系统组成以及它们之间的关系;对于模块业务流程要能够说明清楚子系统内部功能、重要功能点以及它们之间的关系;连贯性对于系统业务流程来说,各个子系统之间是如何连接在一起,如果需要接口,各个子系统之间是否有正确的接口;如果是依靠页面链接,页面链接是否正确;对于模块业务流程来说,同级模块以及上下级模块是如何构成一个子系统,其内部功能接口是否连贯;人名、地名、电话号码等应具有模拟功能,符合一般的命名惯例;不允许出现与知名人士、小说中人物名等雷同情况。BUG级别测试流程制定测试计划[7]编辑测试用例执行测试用例发现并提交BUG开发组修正BUG对已修正BUG进行返测修正完成的BUG将状态置为已关闭,未正确修正的BUG重新激活单元测试单元测试是对软件组成单元进行测试,其目的是检验软件基本组成单位的正确性,测试的对象是软件设计的最小单位:模块集成测试集成测试也称联合测试,将程序模块采用适当的集成策略组装起来,对系统的接口及集成后的功能进行正确性检测的测试工作。其主要目的是检查软件单位之间的接口是否正确,集成测试的对象是已经经过单元测试的模块。系统测试系统测试:主要包括功能测试、界面测试、可靠性测试、易用性测试、性能测试。功能测试主要针对包括功能可用性、功能实现程度(功能流程&业务流程、数据处理&业务数据处理)方面测试。回归测试 回归测试指在软件维护阶段,为了检测代码修改而引入的错误所进行的测试活动。回归测试是软件维护阶段的重要工作,有研究表明,回归测试带来的耗费占软件生命周期的1/3总费用以上。与普通的测试不同,在回归测试过程开始的时候,测试者有一个完整的测试用例集可供使用,因此,如何根据代码的修改情况对已有测试用例集进行有效的复用是回归测试研究的重要方向,此外,回归测试的研究方向还涉及自动化工具,面向对象回归测试,测试用例优先级,回归测试用例补充生成等。V模型是软件开发瀑布模型的变种,它反映了测试活动与分析和设计的关系。从左到右,描述了基本的开发过程和测试行为,非常明确地标明了测试过程中存在的不同级别,并且清楚地描述了这些测试阶段和开发过程期间各阶段的对应关系。左边依次下降的是开发过程各阶段,与此相对应的是右边依次上升的部分,即各测试过程的各个阶段。软件测试V模型测试是开发之后的一个阶段。测试的对象就是程序本身。实际应用中容易导致需求阶段的错误一直到最后系统测试阶段才被发现。.整个软件产品的过程质量保证完全依赖于开发人员的能力和对工作的责任心,而且上一步的结果必须是充分和正确的,如果任何一个环节出了问题,则必将严重的影响整个工程的质量和预期进度W模型由Evolutif公司公司提出,相对于V模型,W模型增加了软件各开发阶段中应同步进行的验证和确认活动。W模型由两个V字型模型组成,分别代表测试与开发过程,图中明确表示出了测试与开发的并行关系。W模型强调:测试伴随着整个软件开发周期,而且测试的对象不仅仅是程序,需求、设计等同样要测试,也就是说,测试与开发是同步进行的。W模型有利于尽早地全面的发现问题。例如,需求分析完成后,测试人员就应该参与到对需求的验证和确认活动中,以尽早地找出缺陷所在。同时,对需求的测试也有利于及时了解项目难度和测试风险,及早制定应对措施,这将显著减少总体测试时间,加快项目进度。但W模型也存在局限性。在W模型中,需求、设计、编码等活动被视为串行的,同时,测试和开发活动也保持着一种线性的前后关系,上一阶段完全结束,才可正式开始下一个阶段工作。这样就无法支持迭代的开发模型。对于当前软件开发复杂多变的情况,W模型并不能解除测试管理面临着困惑。结论在2014年的3月我开始我的论文的工作,经过长时间的编写,现在我已经完成了我的论文。论文是一个是一个长期的过程,需要不断的进行精心的修改,不断的研究文献,认真总结。在搜集资料后,我在电脑中都进行分类的整理,然后针对自己不同部分的写作内容进行归纳和总结。尽量使的资料和论文的内容符合,这有利于论文的撰写。然后及拿给老师进行沟通,听取老师的意见后再进行相关的修改老师的意见总是很宝贵的,可以很好的指出我的资料收集不足以及需要什么样的资料来完善文章。 1月初,资料已经查找完毕了,我开始着手论文初稿的写作。初稿的写作显得逻辑结构有点不清晰,总是想到相关的问题就去写,而没有很好的分出清晰的层次,让章显得有点凌乱,这样的文章必然是不符合要求的,但毕是初稿,在老师的指导下还要进行反复的修改。写作毕业论文是我每个大学生必须经的一段过程,也是我们毕业前的一段宝贵的回忆。当我们看到自的努力有收获的时候,总是会有那么一点点自豪和激动。何事情都是这样子,需要我们脚踏实地的去做,一步一个印的完成,认真严谨,有了好的态度才能做好一件事情开始都觉得毕业论文是一个很困难的任务,大家都难免会一点畏惧之情,但是经过长时间的努力和积累,经过不断查找资料后总结,我们都很好的按老师的要求完成了毕业文的写作,这种收获的喜悦相信每个人都能够体会到。这是一次意志的磨练,是对我实际能力的一次提升,相信对我来的学习和工作有很大的帮助。在这次毕业论文中同学之间互相帮助,共同商量相关专业问题,这种交流对于即将面临毕业的我们来是一次很有意义的经历,大学四年都一起走过了,在最后我们可以聚在一起讨论学习,研究专业问题,进而更好的了解我们每个人的兴趣之所在,明确我们的人生理想,进而在今后的生活和工作中更好的发挥自己的优势,学好自己的专业,成为一个对于社会有用的人参考文献[1]JoshuaBloch,NealGafter著.JavaPuzzlers:Traps,Pitfalls,andCornerCases.JoshuaAddison-WesleyEducationalPublishersInc(2005-06)[2]BillSinggelkow编著.O’reilly-JakartaStruts.O’reillyMedia2005[3]Deitel,HarveyM.SmallJavahowtoprogram.Deitel,PaulJ.PrenticeHall(2004-08)[4]袁然编著.Java项目案例集锦.电子工业大学出版社2008[5]朱庆生,古平等著.Java程序设计.清华大学出版社,2011,1[6]王伟平等著.Java编程.清华大学出版社,2010,5[7]黄晓东编著.Java课程设计案例精编.中国水利水电出版社出版[8]张永常主编.Java程序设计实用教程.电子工业出版社出版 [1]魔乐科技著.java从入门到精通.人民邮电大学出版社2010[2]吴亚峰著.30天学通java项目案例开发.电子工业出版社2008[3]常建华著.零基础学java.机械工业出版社2009致谢我在设计期间都是在任聚财老师全面,具体指导下完成进行的。任聚财老师渊博的学识、敏锐的思维、民主而严谨的作风使学生受益非浅,并终生难忘。感谢任聚财老师等在毕业设计工作中给予的帮助。感谢我的学友和朋友对我的关心和帮助。 外文原献JoshuaBloch,NealGafter著.JavaPuzzlers:Traps,Pitfalls,andCornerCases.JoshuaAddison-WesleyEducationalPublishersInc(2005-06)Puzzle1:OddityThefollowingmethodpurportstodeterminewhetheritssoleargumentisanoddnumber.Doesthemethodwork?publicstaticbooleanisOdd(inti){returni%2==1;}Solution1:Oddity Anoddnumbercanbedefinedasanintegerthatisdivisibleby2witharemainderof1.Theexpressioni%2computestheremainderwheniisdividedby2,soitwouldseemthatthisprogramoughttowork.Unfortunately,itdoesn't;itreturnsthewrongansweronequarterofthetimeWhyonequarter?Becausehalfofallintvaluesarenegative,andtheisOddmethodfailsforallnegativeoddvalues.Itreturnsfalsewheninvokedonanynegativevalue,whetherevenorodd.ThisisaconsequenceofthedefinitionofJava'sremainderoperator(%).Itisdefinedtosatisfythefollowingidentityforallintvaluesaandallnonzerointvaluesb:(a/b)*b+(a%b)==aInotherwords,ifyoudivideabyb,multiplytheresultbyb,andaddtheremainder,youarebackwhereyoustarted[JLS15.17.3].Thisidentitymakesperfectsense,butincombinationwithJava'struncatingintegerdivisionoperator[JLS15.17.2],itimpliesthatwhentheremainderoperationreturnsanonzeroresult,ithasthesamesignasitsleftoperand.TheisOddmethodandthedefinitionofthetermoddonwhichitwasbasedbothassumethatallremaindersarepositive.Althoughthisassumptionmakessenseforsomekindsofdivision[Boxing],Java'sremainderoperationisperfectlymatchedtoitsintegerdivisionoperation,whichdiscardsthefractionalpartofitsresult.Wheniisanegativeoddnumber,i%2isequalto-1ratherthan1,sotheisOddmethodincorrectlyreturnsfalse.Topreventthissortofsurprise,testthatyourmethodsbehaveproperlywhenpassednegative,zero,andpositivevaluesforeachnumericalparameter.Theproblemiseasytofix.Simplycomparei%2to0ratherthanto1,andreversethesenseofthecomparison:publicstaticbooleanisOdd(inti){returni%2!=0;}IfyouareusingtheisOddmethodinaperformance-criticalsetting,youwouldbebetteroffusingthebitwiseANDoperator(&)inplaceoftheremainderoperator:publicstaticbooleanisOdd(inti){return(i&1)!=0; }Thesecondversionmayrunmuchfasterthanthefirst,dependingonwhatplatformandvirtualmachineyouareusing,andisunlikelytorunslower.Asageneralrule,thedivideandremainderoperationsareslowcomparedtootherarithmeticandlogicaloperations.It'sabadideatooptimizeprematurely,butinthiscase,thefasterversionisasclearastheoriginal,sothereisnoreasontoprefertheoriginal.Insummary,thinkaboutthesignsoftheoperandsandoftheresultwheneveryouusetheremainderoperator.Thebehaviorofthisoperatorisobviouswhenitsoperandsarenonnegative,butitisn'tsoobviouswhenoneorbothoperandsarenegative.Puzzle2:TimeforaChangeConsiderthefollowingwordproblem:Tomgoestotheautopartsstoretobuyasparkplugthatcosts$1.10,butallhehasinhiswalletaretwo-dollarbills.Howmuchchangeshouldhegetifhepaysforthesparkplugwithatwo-dollarbill?Hereisaprogramthatattemptstosolvethewordproblem.Whatdoesitprint?publicclassChange{publicstaticvoidmain(Stringargs[]){System.out.println(2.00-1.10);}}Solution2:TimeforaChangeNaively,youmightexpecttheprogramtoprint0.90,buthowcoulditknowthatyouwantedtwodigitsafterthedecimalpoint?Ifyouknowsomethingabouttherulesforconvertingdoublevaluestostrings,whicharespecifiedbythedocumentationforDouble.toString[Java-API],youknowthattheprogramprintstheshortestdecimalfractionsufficienttodistinguishthedoublevaluefromitsnearestneighbor,withatleastonedigitbeforeandafterthedecimalpoint.Itseemsreasonable,then,thattheprogramshouldprint0.9.Reasonable,perhaps,butnotcorrect.Ifyourantheprogram,youfoundthatitprints0.99999.Theproblemisthatthenumber1.1can'tberepresentedexactlyasadouble,soitis representedbytheclosestdoublevalue.Theprogramsubtractsthisvaluefrom2.Unfortunately,theresultofthiscalculationisnottheclosestdoublevalueto0.9.Theshortestrepresentationoftheresultingdoublevalueisthehideousnumberthatyouseeprinted.Moregenerally,theproblemisthatnotalldecimalscanberepresentedexactlyusingbinaryfloating-point.Ifyouareusingrelease5.0oralaterrelease,youmightbetemptedtofixtheprogrambyusingtheprintffacilitytosettheprecisionoftheoutput://Poorsolution-stillusesbinaryfloating-point!System.out.printf("%.2f%n",2.00-1.10);Thisprintstherightanswerbutdoesnotrepresentageneralsolutiontotheunderlyingproblem:Itstillusesdoublearithmetic,whichisbinaryfloating-point.Floating-pointarithmeticprovidesgoodapproximationsoverawiderangeofvaluesbutdoesnotgenerallyyieldexactresults.Binaryfloating-pointisparticularlyill-suitedtomonetarycalculations,asitisimpossibletorepresent0.1—oranyothernegativepowerof10—exactlyasafinite-lengthbinaryfraction[EJItem31].Onewaytosolvetheproblemistouseanintegraltype,suchasintorlong,andtoperformthecomputationincents.Ifyougothisroute,makesuretheintegraltypeislargeenoughtorepresentallthevaluesyouwilluseinyourprogram.Forthispuzzle,intisample.Hereishowtheprintlnlooksifwerewriteitusingintvaluestorepresentmonetaryvaluesincents.Thisversionprints90cents,whichistherightanswer:System.out.println((200-110)+"cents");AnotherwaytosolvetheproblemistouseBigDecimal,whichperformsexactdecimalarithmetic.ItalsointeroperateswiththeSQLDECIMALtypeviaJDBC.Thereisonecaveat:AlwaysusetheBigDecimal(String)constructor,neverBigDecimal(double).Thelatterconstructorcreatesaninstancewiththeexactvalueofitsargument:newBigDecimal(.1)returnBigDecimarepresenting0..UsingBigDecimalcorrectly,theprogramprintstheexpectedresultof0.90:importjava.math.BigDecimal;publicclassChange{publicstaticvoidmain(Stringargs[]){ System.out.println(newBigDecimal("2.00").subtract(newBigDecimal("1.10")));}}Thisversionisnotterriblypretty,asJavaprovidesnolinguisticsupportforBigDecimal.CalculationswithBigDecimalarealsolikelytobeslowerthanthosewithanyprimitivetype,whichmightbeanissueforsomeprogramsthatmakeheavyuseofdecimalcalculations.Itisofnoconsequenceformostprograms.Insummary,avoidfloatanddoublewhereexactanswersarerequired;formonetarycalculations,useint,long,orBigDecimal.Forlanguagedesigners,considerprovidinglinguisticsupportfordecimalarithmetic.Oneapproachistoofferlimitedsupportforoperatoroverloading,sothatarithmeticoperatorscanbemadetoworkwithnumericalreferencetypes,suchasBigDecimal.Anotherapproachistoprovideaprimitivedecimaltype,asdidCOBOLandPL/I.Puzzle3:LongDivisionThispuzzleiscalledLongDivisionbecauseitconcernsaprogramthatdividestwolongvalues.Thedividendrepresentsthenumberofmicrosecondsinaday;thedivisor,thenumberofmillisecondsinaday.Whatdoestheprogramprint?publicclassLongDivision{publicstaticvoidmain(String[]args){finallongMICROS_PER_DAY=24*60*60*1000*1000;finallongMILLIS_PER_DAY=24*60*60*1000;System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY);}}Solution3:LongDivisionThispuzzleseemsreasonablystraightforward.Thenumberofmillisecondsperdayandthenumberofmicrosecondsperdayareconstants.Forclarity,theyareexpressedasproducts.Thenumberofmicrosecondsperdayis(24hours/day·60minutes/hour·60 seconds/minute·1,000milliseconds/second·1,000microseconds/millisecond).Thenumberofmillisecondsperdaydiffersonlyinthatitismissingthefinalfactorof1,000.Whenyoudividethenumberofmicrosecondsperdaybythenumberofmillisecondsperday,allthefactorsinthedivisorcancelout,andyouareleftwith1,000,whichisthenumberofmicrosecondspermillisecond.Boththedivisorandthedividendareoftypelong,whichiseasilylargeenoughtoholdeitherproductwithoutoverflow.Itseems,then,thattheprogrammustprint1000.Unfortunately,itprints5.Whatexactlyisgoingonhere?TheproblemisthatthecomputationoftheconstantMICROS_PER_DAYdoesoverflow.Althoughtheresultofthecomputationfitsinalongwithroomtospare,itdoesn'tfitinanint.Thecomputationisperformedentirelyinintarithmetic,andonlyafterthecomputationcompletesistheresultpromotedtoalong.Bythen,it'stoolate:Thecomputationhasalreadyoverflowed,returningavaluethatistoolowbyafactorof200.Thepromotionfrominttolongisawideningprimitiveconversion[JLS5.1.2],whichpreservesthe(incorrect)numericalvalue.ThisvalueisthendividedbyMILLIS_PER_DAY,whichwascomputedcorrectlybecauseitdoesfitinanint.Theresultofthisdivisionis5.Sowhyisthecomputationperformedinintarithmetic?Becauseallthefactorsthataremultipliedtogetherareintvalues.Whenyoumultiplytwointvalues,yougetanotherintvalue.Javadoesnothavetargettyping,alanguagefeaturewhereinthetypeofthevariableinwhicharesultistobestoredinfluencesthetypeofthecomputation.It'seasytofixtheprogrambyusingalongliteralinplaceofanintasthefirstfactorineachproduct.Thisforcesallsubsequentcomputationsintheexpressiontobedonewithlongarithmetic.AlthoughitisnecessarytodothisonlyintheexpressionforMICROS_PER_DAY,itisgoodformtodoitinbothproducts.Similarly,itisn'talwaysnecessarytousealongasthefirstvalueinaproduct,butitisgoodformtodoso.Beginningbothcomputationswithlongvaluesmakesitclearthattheywon'toverflow.Thisprogramprints1000asexpected:publicclassLongDivision{publicstaticvoidmain(String[]args){ finallongMICROS_PER_DAY=24L*60*60*1000*1000;finallongMILLIS_PER_DAY=24L*60*60*1000;System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY);}}Thelessonissimple:Whenworkingwithlargenumbers,watchoutforoverflow—it'sasilentkiller.Justbecauseavariableislargeenoughtoholdaresultdoesn'tmeanthatthecomputationleadingtotheresultisofthecorrecttype.Whenindoubt,performtheentirecomputationusinglongarithmetic.Thelessonforlanguagedesignersisthatitmaybeworthreducingthelikelihoodofsilentoverflow.Thiscouldbedonebyprovidingsupportforarithmeticthatdoesnotoverflowsilently.Programscouldthrowanexceptioninsteadofoverflowing,asdoesAda,ortheycouldswitchtoalargerinternalrepresentationautomaticallyasrequiredtoavoidoverflow,asdoesLisp.Bothoftheseapproachesmayhaveperformancepenaltiesassociatedwiththem.Anotherwaytoreducethelikelihoodofsilentoverflowistosupporttargettyping,butthisaddssignificantcomplexitytothetypesystem[Modula-31.4.8].Puzzle4:It'sElementaryOK,sothelastpuzzlewasabittricky,butitwasaboutdivision.Everyoneknowsthatdivisionistough.Thisprograminvolvesonlyaddition.Whatdoesitprint?publicclassElementary{publicstaticvoidmain(String[]args){System.out.println(12345+5432l);}}Solution4:It'sElementaryOnthefaceofit,thislookslikeaneasypuzzle—soeasythatyoucansolveitwithoutpencilorpaper.Thedigitsoftheleftoperandoftheplusoperatorascendfrom1to5,andthedigitsoftherightoperanddescend.Therefore,thesumsofcorrespondingdigitsremainconstant,andtheprogrammustsurelyprint66666.Thereisonlyoneproblemwiththis analysis:Whenyouruntheprogram,itprints17777.CoulditbethatJavahasanaversiontoprintingsuchabeastlynumber?Somehowthisdoesn'tseemlikeaplausibleexplanation.Thingsareseldomwhattheyseem.Takethisprogram,forinstance.Itdoesn'tsaywhatyouthinkitdoes.Takeacarefullookatthetwooperandsofthe+operator.Weareaddingtheintvalue12345tothelongvalue5432l.Notethesubtledifferenceinshapebetweenthedigit1atthebeginningoftheleftoperandandthelowercaseletterelattheendoftherightoperand.Thedigit1hasanacuteanglebetweenthehorizontalstroke,orarm,andtheverticalstroke,orstem.Thelowercaseletterel,bycontrast,hasarightanglebetweenthearmandthestem.Beforeyoucry"foul,"notethatthisissuehascausedrealconfusion.Alsonotethatthepuzzle'stitlecontainedahint:It'sEl-ementary;getit?Finally,notethatthereisareallessonhere.Alwaysuseacapitalel(L)inlongliterals,neveralowercaseel(l).Thiscompletelyeliminatesthesourceofconfusiononwhichthepuzzlerelies:System.out.println(12345+5432L);Similarly,avoidusingaloneel(l)asavariablename.Itisdifficulttotellbylookingatthiscodesnippetwhetheritprintsthelistlorthenumber1://Badcode-usesel(l)asavariablenameListl=newArrayList();l.add("Foo");System.out.println(1);Insummary,thelowercaseletterelandthedigit1arenearlyidenticalinmosttypewriterfonts.Toavoidconfusingthereadersofyourprogram,neverusealowercaseeltoterminatealongliteralorasavariablename.JavainheritedmuchfromtheCprogramminglanguage,includingitssyntaxforlongliterals.Itwasprobablyamistaketoallowlongliteralstobewrittenwithalowercaseel.Puzzle5:TheJoyofHexThefollowingprogramaddstwohexadecimal,or"hex,"literalsandprintstheresultinhex.Whatdoestheprogramprint?publicclassJoyOfHex{publicstaticvoidmain(String[]args){ System.out.println(Long.toHexString(0xL+0xcafebabe));}}Solution5:TheJoyofHexItseemsobviousthattheprogramshouldprint1cafebabe.Afterall,thatisthesumofthehexnumbersandcafebabe16.Theprogramuseslongarithmetic,whichpermits16hexdigits,soarithmeticoverflowisnotanissue.Yet,ifyourantheprogram,youfoundthatitprintscafebabe,withnoleading1digit.Thisoutputrepresentsthelow-order32bitsofthecorrectsum,butsomehowthethirty-thirdbitgetslost.Itisasiftheprogramweredoingintarithmeticinsteadoflong,orforgettingtoaddthefirstoperand.What'sgoingonhere?Decimalliteralshaveanicepropertythatisnotsharedbyhexadecimaloroctalliterals:Decimalliteralsareallpositive[JLS3.10.1].Towriteanegativedecimalconstant,youusetheunarynegationoperator(-)incombinationwithadecimalliteral.Inthisway,youcanwriteanyintorlongvalue,whetherpositiveornegative,indecimalform,andnegativedecimalconstantsareclearlyidentifiablebythepresenceofaminussign.Notsoforhexadecimalandoctalliterals.Theycantakeonbothpositiveandnegativevalues.Hexandoctalliteralsarenegativeiftheirhigh-orderbitisset.Inthisprogram,thenumber0xcafebabeisanintconstantwithitshigh-orderbitset,soitisnegative.Itisequivalenttothedecimalvalue-.Theadditionperformedbytheprogramisamixed-typecomputation:Theleftoperandisoftypelong,andtherightoperandisoftypeint.Toperformthecomputation,Javapromotestheintvaluetoalongwithawideningprimitiveconversion[JLS5.1.2]andaddsthetwolongvalues.Becauseintisasignedintegraltype,theconversionperformssignextension:Itpromotesthenegativeintvaluetoanumericallyequallongvalue.Therightoperandoftheaddition,0xcafebabe,ispromotedtothelongvalue0xffffffffcafebabeL.Thisvalueisthenaddedtotheleftoperand,whichis0xL.Whenviewedasanint,thehigh-order32bitsofthesign-extendedrightoperandare-1,andthehigh-order32bitsoftheleftoperandare1.Addthesetwovaluestogetherandyouget0,whichexplainstheabsenceoftheleading1digitin theprogram'soutput.Hereishowtheadditionlookswhendoneinlonghand.(Thedigitsatthetopoftheadditionarecarries.)0xffffffffcafebabeL+0x00000L0xcafebabeLFixingtheproblemisassimpleasusingalonghexliteraltorepresenttherightoperand.Thisavoidsthedamagingsignextension,andtheprogramprintstheexpectedresultof1cafebabe:publicclassJoyOfHex{publicstaticvoidmain(String[]args){System.out.println(Long.toHexString(0xL+0xcafebabeL));}}Thelessonofthispuzzleisthatmixed-typecomputationscanbeconfusing,moresogiventhathexandoctalliteralscantakeonnegativevalueswithoutanexplicitminussign.Toavoidthissortofdifficulty,itisgenerallybesttoavoidmixed-typecomputations.Forlanguagedesigners,itisworthconsideringsupportforunsignedintegraltypes,whicheliminatethepossibilityofsignextension.Onemightarguethatnegativehexandoctalliteralsshouldbeprohibited,butthiswouldlikelyfrustrateprogrammers,whooftenusehexliteralstorepresentvalueswhosesignisofnosignificance.Puzzle6:MulticastCastsareusedtoconvertavaluefromonetypetoanother.Thisprogramusesthreecastsinsuccession.Whatdoesitprint?publicclassMulticast{publicstaticvoidmain(String[]args){System.out.println((int)(char)(byte)-1);} }Solution6:MulticastThisprogramisconfusinganywayyousliceit.Itstartswiththeintvalue-1,thencaststheinttoabyte,thentoachar,andfinallybacktoanint.Thefirstcastnarrowsthevaluefrom32bitsdownto8,thesecondwidensitfrom8bitsto16,andthefinalcastwidensitfrom16bitsbackto32.Doesthevalueendupbackwhereitstarted?Ifyourantheprogram,youfoundthatitdoesnot.Itprints65535,butwhy?Theprogram'sbehaviordependscriticallyonthesignextensionbehaviorofcasts.Javausestwo's-complementbinaryarithmetic,sotheintvalue-1hasall32bitsset.Thecastfrominttobyteisstraightforward.Itperformsanarrowingprimitiveconversion[JLS5.1.3],whichsimplylopsoffallbutthelow-order8bits.Thisleavesabytevaluewithall8bitsset,which(still)represents–1.Thecastfrombytetocharistrickierbecausebyteisasignedtypeandcharunsigned.Itisusuallypossibletoconvertfromoneintegraltypetoawideronewhilepreservingnumericalvalue,butitisimpossibletorepresentanegativebytevalueasachar.Therefore,theconversionfrombytetocharisnotconsideredawideningprimitiveconversion[JLS5.1.2],butawideningandnarrowingprimitiveconversion[JLS5.1.4]:Thebyteisconvertedtoanintandtheinttoachar.Allofthismaysoundabitcomplicated.Luckily,thereisasimplerulethatdescribesthesignextensionbehaviorwhenconvertingfromnarrowerintegraltypestowider:Signextensionisperformedifthetypeoftheoriginalvalueissigned;zeroextensionifitisachar,regardlessofthetypetowhichitisbeingconverted.Knowingthisrulemakesiteasytosolvethepuzzle.Becausebyteisasignedtype,signextensionoccurswhenconvertingthebytevalue–1toachar.Theresultingcharvaluehasall16bitsset,soitisequalto216–1,or65,535.Thecastfromchartointisalsoawideningprimitiveconversion,sotheruletellsusthatzeroextensionisperformedratherthansignextension.Theresultingintvalueis65535,whichisjustwhattheprogramprints.Althoughthereisasimpleruledescribingthesignextensionbehaviorofwideningprimitiveconversionsbetweensignedandunsignedintegraltypes,itisbestnottowriteprogramsthatdependonit.Ifyouaredoingawideningconversiontoorfromachar,whichistheonly unsignedintegraltype,itisbesttomakeyourintentionsexplicit.Ifyouareconvertingfromacharvaluectoawidertypeandyoudon'twantsignextension,considerusingabitmaskforclarity,eventhoughitisn'trequired:inti=c&0xffff;Alternatively,writeacommentdescribingthebehavioroftheconversion:inti=c;//SignextensionisnotperformedIfyouareconvertingfromacharvaluectoawiderintegraltypeandyouwantsignextension,castthechartoashort,whichisthesamewidthasacharbutsigned.Giventhesubtletyofthiscode,youshouldalsowriteacomment:inti=(short)c;//CastcausessignextensionIfyouareconvertingfromabytevaluebtoacharandyoudon'twantsignextension,youmustuseabitmasktosuppressit.Thisisacommonidiom,sonocommentisnecessary:charc=(char)(b&0xff);Ifyouareconvertingfromabytetoacharandyouwantsignextension,writeacomment:charc=(char)b;//SignextensionisperformedThelessonissimple:Ifyoucan'ttellwhataprogramdoesbylookingatit,itprobablydoesn'tdowhatyouwant.Striveforclarity.Althoughasimpleruledescribesthesignextensionbehaviorofwideningconversionsinvolvingsignedandunsignedintegraltypes,mostprogrammersdon'tknowit.Ifyourprogramdependsonit,makeyourintentionscler 中文翻译Java谜题1——表达式谜题谜题1:奇数性下面的方法意图确定它那唯一的参数是否是一个奇数。这个方法能够正确运转吗?publicstaticbooleanisOdd(inti){returni%2==1;}奇数可以被定义为被2整除余数为1的整数。表达式i%2计算的是i整除2时所产生的余数,因此看起来这个程序应该能够正确运转。遗憾的是,它不能;它在四分之一的时间里返回的都是错误的答案。为什么是四分之一?因为在所有的int数值中,有一半都是负数,而isOdd方法对于对所有负奇数的判断都会失败。在任何负整数上调用该方法都回返回false,不管该整数是偶数还是奇数。这是Java对取余操作符(%)的定义所产生的后果。该操作符被定义为对于所有的int数值a和所有的非零int数值b,都满足下面的恒等式:(a/b)*b+(a%b)==a换句话说,如果你用b整除a,将商乘以b,然后加上余数,那么你就得到了最初的值a。该恒等式具有正确的含义,但是当与Java的截尾整数整除操作符相结合时,它就意味着:当取余操作返回一个非零的结果时,它与左操作数具有相同的正负符号。当i是一个负奇数时,i%2等于-1而不是1,因此isOdd方法将错误地返回false。为了防止这种意外,请测试你的方法在为每一个数值型参数传递负数、零和正数数值时,其行为是否正确。这个问题很容易订正。只需将i%2与0而不是与1比较,并且反转比较的含义即可:publicstaticbooleanisOdd(inti){returni%2!=0;}如果你正在在一个性能临界(performance-critical)环境中使用isOdd方法,那么用位操作符AND(&)来替代取余操作符会显得更好:publicstaticbooleanisOdd(inti){return(i&1)!=0; }总之,无论你何时使用到了取余操作符,都要考虑到操作数和结果的符号。该操作符的行为在其操作数非负时是一目了然的,但是当一个或两个操作数都是负数时,它的行为就不那么显而易见了。谜题2:找零时刻请考虑下面这段话所描述的问题:Tom在一家汽车配件商店购买了一个价值$1.10的火花塞,但是他钱包中都是两美元一张的钞票。如果他用一张两美元的钞票支付这个火花塞,那么应该找给他多少零钱呢?下面是一个试图解决上述问题的程序,它会打印出什么呢?publicclassChange{publicstaticvoidmain(Stringargs[]){System.out.println(2.00-1.10);}}你可能会很天真地期望该程序能够打印出0.90,但是它如何才能知道你想要打印小数点后两位小数呢?如果你对在Double.toString文档中所设定的将double类型的值转换为字符串的规则有所了解,你就会知道该程序打印出来的小数,是足以将double类型的值与最靠近它的临近值区分出来的最短的小数,它在小数点之前和之后都至少有一位。因此,看起来,该程序应该打印0.9是合理的。这么分析可能显得很合理,但是并不正确。如果你运行该程序,你就会发现它打印的是0.99999。问题在于1.1这个数字不能被精确表示成为一个double,因此它被表示成为最接近它的double值。该程序从2中减去的就是这个值。遗憾的是,这个计算的结果并不是最接近0.9的double值.表示结果的double值的最短表示就是你所看到的打印出来的那个可恶的数字。更一般地说,问题在于并不是所有的小数都可以用二进制浮点数来精确表示的。如果你正在用的是JDK5.0或更新的版本,那么你可能会受其诱惑,通过使用printf工具来设置输出精度的方订正该程序://拙劣的解决方案——仍旧是使用二进制浮点数System.out.printf("%.2f%n",2.00-1.10);这条语句打印的是正确的结果,但是这并不表示它就是对底层问题的通用解决方案:它使用的仍旧是二进制浮点数的double运算。浮点运算在一个范围很广的值域上提供了很好的近似,但是它通常不能产生精确的结果。二进制浮点对于货币计算是非常不适合的,因为它不可能将0.1——或者10的其它任何次负幂——精确表示为一个长度有限的二进制小数解决该问题的一种方式是使用某种整数类型,例如int或long,并且以分为单位来执行计算。如果你采纳了此路线,请确保该整数类型大到足够表示在程序中你将要用到的所有值.对这里举例的谜题来说,就足够了int下面是我们用int类型来以分为单位表示货币值后重写的println语句.个版本将打印出正确案90分:System.out.println((200-110)+"cents");解决该问题的另一种方式是使用执行精确小数运算的BigDecimal。它还可以通过JDBC与SQLDECIMAL类型进行互操作。这里要告诫你一点: 一定要用BigDecimal(String)构造器,而千万不要用BigDecimal(double)。后一个构造器将用它的参数的“精确”值来创建一个实例:newBigDecimal(.1)将返回一个表示0.的BigDecimal。通过正确使用BigDecimal,程序就可以打印出我们所期望的结果0.90:importjava.math.BigDecimal;publicclassChange1{publicstaticvoidmain(Stringargs[]){System.out.println(newBigDecimal("2.00").subtract(newBigDecimal("1.10")));}}因为Java并没有为BigDecimal提供任何语言上这个版本并不是十分地完美,支持。使用BigDecimal的计算很有可能比那些使用原始类型的计算要慢一些,对某些大量使用小数计算的程序来说,这可能会成为问题,而对大多数程序来说,这显得一点也不重要。总之,在需要精确答案的地方,要避免使用float和double;对于货币计算,要使用int、long或BigDecimal。对于语言设计者来说,应该考虑对小数运算提供语言支持。一种方式是提供对操作符重载的有限支持,以使得运算符可以被塑造为能够对数值引用类型起作用,例如BigDecimal。另一种方式是提供原始的小数类型,就像COBOL与PL/I所作的一样。谜题3:长整除这个谜题之所以被称为长整除是因为它所涉及的程序是有关两个long型数值除的。被除数表示的是一天里的微秒数;而除数表示的是一天里的毫秒数。这个程序会打印出什么呢?publicclassLongDivision{publicstaticvoidmain(Stringargs[]){finallongMICROS_PER_DAY=24*60*60*1000*1000;finallongMILLIS_PER_DAY=24*60*60*1000;System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY);}}这个谜题看起来相当直观.每天的毫秒数和每天的微秒数都是常量。为清楚起见,它们都被表示成积的形式。每天的微秒数是(24小时/天*60分钟/小时*60秒/分钟*1000毫秒/秒*1000微秒/毫秒)。而每天的毫秒数的不同之处只是少了最后一个因子1000。当你用每天的毫秒数来整除每天的微秒数时,除数中所有的因子都被约掉了,只剩下1000,这正是每毫秒包含的微秒数。除数和被除数都是long类型的,long类型大到了可以很容易地保存这两个乘积而不产生溢出。因此,看起来程序打印的必定是1000。遗憾的是,它打印的是5。这里到底发生了什么呢?问题在于常数MICROS_PER_DAY的计算“确实”溢出了。尽管计算的结果适合放入long中,并且其空间还有富余,但是这个结果并不适合放入int中。这个计算完全是以int运算来执行的,并且只有在运算完成之后,其结果才被提升到long,而此时已经太迟了:计算已经溢出了,它返回的是一个小了200倍的数值。从int提升到long是一种拓宽原始类型转换(wideningprimitiveconversion),它保留了(不正确的)数值。这个值之后被 MILLIS_PER_DAY整除,而MILLIS_PER_DAY的计算是正确的,因为它适合int运算。这样整除的结果就得到了。那么为什么计算会是以int运算来执行的呢?因为所有乘在一起的因子都是int数值。当你将两个int数值相乘时,你将得到另一个int数值。Java不具有目标确定类型的特性,这是一种语言特性,其含义是指存储结果的变量的类型会影响到计算所使用的类型。通过使用long常量来替代int常量作为每一个乘积的第一个因子,我们就可以很容易地订正这个程序。这样做可以强制表达式中所有的后续计算都用long运作来完成。尽管这么做只在MICROS_PER_DAY表达式中是必需的,但是在两个乘积中都这么做是一种很好的方式。相似地,使用long作为乘积的“第一个”数值也并不总是必需的,但是这么做也是一种很好的形式。在两个计算中都以long数值开始可以很清楚地表明它们都不会溢出。下面的程序将打印出我们所期望的1000:publicclassLongDivision{publicstaticvoidmain(Stringargs[]){finallongMICROS_PER_DAY=24L*60*60*1000*1000;finallongMILLIS_PER_DAY=24L*60*60*1000;System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY);}}这个教训很简单:当你在操作很大的数字时,千万要提防溢出——它可是一个缄默杀手。即使用来保存结果的变量已显得足够大,也并不意味着要产生结果的计算具有正确的类型。当你拿不准时,就使用long运算来执行整个计算.语言设计者从中可以吸取的教训是:也许降低缄默溢出产生的可能性确实是值得做的一件事。这可以通过对不会产生缄默溢出的运算提供支持来实现。程序可以抛出一个异常而不是直接溢出,就像Ada所作的那样,或者它们可以在需要的时候自动地切换到一个更大的内部表示上以防止溢出,就像Lisp所作的那样。降低缄默溢出的另一种方式两种方式都可能会遭受与其相关的性能方面的损失。是支持目标确定类型,但是这么做会显著地增加类型系统的复杂度谜题4:初级问题得啦,前面那个谜题是有点棘手,但它是有关整除的,每个人都知道整除是很麻烦的。那么下面的程序只涉及加法,它又会打印出什么呢?publicclassElementary{publicstaticvoidmain(String[]args){System.out.println(12345+5432l);}}从表面上看,这像是一个很简单的谜题——简单到不需要纸和笔你就可以解加号的左操作数的各个位是从1到5升序排它而右操作数是降序排列的。因此,相应各位的和仍然是常数,程序必定打印66666。对于这样的分析,只有一个问题:当你运行该程序时,它打印出的是17777。难道是Java对打印这样的非常数字抱有偏见吗?不知怎么的,这看起来并不像是一个合理的解释。事物往往有别于它的表象就以这个问题为例,它并没有打印出我们想要的输出。请仔细观察+操作符的两个操作数,我们是将一个 int类型的12345加到long类型的5432l上。请注意左操作数开头的数字1和右操作数结尾的小写母l之间的细微差异。数字1的水平笔划(称为“臂(arm)”)和垂直笔划(称为“茎(stem)”)之间是一个锐角,而与此相对照的是,小写字母l的臂和茎之间是一个直角。在你大喊“恶心!”之前,你应该注意到这个问题确实已经引起了混乱,这里确实有一个教训:long型字面常量中在一定要用大写的L,千万不要用小写的l。这样就可以完全掐断这个谜题所产生的混乱的源头。System.out.println(12345+5432L);相类似的,要避免使用单独的一个l字母作为变量名。例如,我们很难通过观察下面的代码段来判断它到底是打印出列表l还是数字1。//不良代码-使用了l作为变量名Listl=newArrayList();l.add("Foo");System.out.println(1);总之,小写字母l和数字1在大多数打字机字体中都是几乎一样的。为避免你的程序的读者对二者产生混淆,千万不要使用小写的l来作为long型字面常量的结尾或是作为变量名。Java从C编程语言中继承良多,包括long型字面常量的语法。也许当初允许用小写的l来编写long型字面常量本身就是一个错误。谜题5:十六进制的趣事下面的程序是对两个十六进制(hex)字面常量进行相加,然后打印出十六进制的结果。这个程序会打印出什么呢?publicclassJoyOfHex{publicstaticvoidmain(String[]args){System.out.println(Long.toHexString(0xL+0xcafebabe));}}看起来很明显,该程序应该打印出1cafebabe。毕竟,这确实就是十六与cafebabe16的和。该程序使用的是long型运算,它可以支持16位十六进制数,因此运算溢出是不可能的。然而,如果你运行该程序,你就会发现它打印出来的是cafebabe,并没有任何前导的1。这个输出表示的是正确结果的低32位,但是不知何故,第33位丢失了。看起来程序好像执行的是int型运算而不是long型运算,或者是忘了加第一个操作数。这里到底发生了什么呢?十进制字面常量具有一个很好的属性,即所有的十进制字面常量都是正的,而十六进制或是八进制字面常量并不具备这个属性。要想书写一个负的十进制常量,可以使用一元取反操作符(-)连接一个十进制字面常量。以这种方式,你可以用十进制来书写任何int或long型的数值,不管它是正的还是负的,并且负的十进制常数可以很明确地用一个减号符号来标识但是十六进制和八进制字面常量并不是这么回事,它们可以具有正的以及负的数值。如果十六进制和八进制字面常量的最高位被置位了,那么它们就是负数在这个程序中数字0xcafebabe是一个int常量,它的最高位被置位了,所以它是一个负数。它等于十进制数值-。该程序执行的这个加法是一种“混合类型的计算(mixed-typecomputation):左操作数是long类型的,而右操作数是int类型的。为了执行该计算,Java将int类型的数值用拓宽原始类型转换提升为一个long类型,然后对两个long类型数值相加。因为int 是一个有符号的整数类型,所以这个转换执行的是符合扩展:它将负的int类型的数值提升为一个在数值上相等的long类型数值。这个加法的右操作数0xcafebabe被提升为了long类型的数值0xffffffffcafebabeL。这个数值之后被加到了左操作数0xL上。当作为int类型来被审视时,经过符号扩展之后的右操作数的高32位是-1,而左操作数的高32位是1,将这两个数值相加就得到了0,这也就解释了为什么在程输出中前导1丢失了。下面所示是用手写的加法实现。(在加法上面的数字是进位。)0xffffffffcafebabeL+0x00000L---------------------0xcafebabeL只需用一个long十六进制字面常量来表示右操作数即可。订正该程序非常简单,这就可以避免了具有破坏力的符号扩展,并且程序也就可以打印出我们所期望的结果1cafebabe:publicclassJoyOfHex{publicstaticvoidmain(String[]args){System.out.println(Long.toHexString(0xL+0xcafebabeL));}}这个谜题给我们的教训是:混合类型的计算可能会产生混淆,尤其是十六进制和八进制字面常量无需显式的减号符号就可以表示负的数值。为了避免这种窘境,通常最好是避免混合类型的计算。对于语言的设计者们来说,应该考虑支持无符号的整数类型,从而根除符号扩展的可能性。可能会有这样的争辩:负的十六进制和八进制字面常量应该被禁用,但是这可能会挫伤程序员,他们经常使用十六进制字面常量来表示那些符号没有任何重要含义的数值。谜题6:多重转型转型被用来将一个数值从一种类型转换到另一种类型。下面的程序连续使用了三个转型。那么它到底会打印出什么呢?publicclassMulticast{publicstaticvoidmain(String[]args){System.out.println((int)(char)(byte)-1);}}无论你怎样分析这个程序,都会感到很迷惑。它以int数值-1开始,然后从int转型为byte,之后转型为char,最后转型回int。第一个转型将数值从32位窄化到了8位,第二个转型将数值从8位拓宽到了16位,最后一个转型又将数值从16位拓宽回了32位。这个数值最终是回到了起点吗?如果你运行该程序,你就会发现不是。它打印出来的是65535,但是这是为什么呢?该程序的行为紧密依赖于转型的符号扩展行为。Java使用了基于2的补码的二进制运算,因此int类型的数值-1的所有32位都是置位的。从int到byte的转型是很简单的,它执行了一个窄化原始类型转化(narrowingprimitivconversion),直接将除低8位之外的所有位全部砍掉。这样做留下的是一个 8位都被置位了的byte,它仍旧表示-1。从byte到char的转型稍微麻烦一点,因为byte是一个有符号类型,而char是一个无符号类型。在将一个整数类型转换成另一个宽度更宽的整数类型时,通常是可以保持其数值的,但是却不可能将一个负的byte数值表示成一个char。因此,从byte到char的转换被认为不是一个拓宽原始类型的转换,而是一个拓宽并窄化原始类型的转换(wideningandnarrowingprimitiveconversion):byte被转换成了int,而这个int又被转换成了char。所有这些听起来有点复杂,幸运的是,有一条很简单的规则能够描述从较窄的整型转换成较宽的整型时的符号扩展行为:如果最初的数值类型是有符号的,那么就执行符号扩展;如果它是char,那么不管它将要被转换成什么类型,都执行零扩展。了解这条规则可以使我们很容易地解决这个谜题因为byte是一个有符号的类型,所以在将byte数值-1转换成char时,会发生符号扩展。作为结果的char数值的16个位就都被置位了,因此它等于216-1,即65535。从char到int的转型也是一个拓宽原始类型转换,所以这条规则告它将执行零扩展而不是符号扩展作为结果的int数值也就成了65535,诉我们这正是程序打印出的结果。尽管这条简单的规则描述了在有符号和无符号整型之间进行拓宽原始类型时的符号扩展行为,你最好还是不要编写出依赖于它的程序。如果你正在执行一个转型到char或从char转型的拓宽原始类型转换,并且这个char是仅有的无符号整型,那么你最好将你的意图明确地表达出来。如果你在将一个char数值c转型为一个宽度更宽的类型,并且你不希望有符号扩展,那么为清晰表达意图,可以考虑使用一个位掩码,即使它并不是必需的inti=c&0xffff;或者,书写一句注释来描述转换的行为:inti=c;//不会执行符号扩展如果你在将一个char数值c转型为一个宽度更宽的整型,并且你希望有符号扩展,那么就先将char转型为一个short,它与char具有同样的宽度,但是它有符号的。在给出了这种细微的代码之后,你应该也为它书写一句注释:nti=(short)c;//转型将引起符号扩展如果你在将一个byte数值b转型为一个char,并且你不希望有符号扩展,那么你必须使用一个位掩码来限制它。这是一种通用做法,所以不需要任何注释:charc=(char)(b&0xff);这个教训很简单:如果你通过观察不能确定程序将要做什么,那么它做的就很有可能不是你想要的。要为明白清晰地表达你的意图而努力。尽管有这么一条简单的规则,描述了涉及有符号和无符号整型拓宽转换的符号扩展行为,但是大多数程序员都不知道它。如果你的程序依赖于它,那么你就应该把你的意图表达清楚。

当前文档最多预览五页,下载文档查看全文

此文档下载收益归作者所有

当前文档最多预览五页,下载文档查看全文
温馨提示:
1. 部分包含数学公式或PPT动画的文件,查看预览时可能会显示错乱或异常,文件下载后无此问题,请放心下载。
2. 本文档由用户上传,版权归属用户,天天文库负责整理代发布。如果您对本文档版权有争议请及时联系客服。
3. 下载前请仔细阅读文档内容,确认文档内容符合您的需求后进行下载,若出现内容与标题不符可向本站投诉处理。
4. 下载文档时可能由于网络波动等原因无法下载或下载错误,付费完成后未能成功下载的用户请联系客服处理。
关闭