刘艺作品试读
Java
程序设计大学教程
刘艺 编著
刘艺作品试读前 言
欢迎进入 Java 的世界学习计算机程序设计课程。这将是一次美妙和激动人心的探索,
可能将为你今后从事的充满挑战和令人兴奋的职业奠定软件编程的基础。 因为众所周知计算机在我们的日常生活中扮演了一个重要的角色而且在未来也将一样。
计算机科学是一个充满了挑战和发展机遇的年轻学科,而计算机程序设计则是这门学科的重要基础。随着计算机在各行各业的广泛应用,很多非计算机专业的课程设置中也把计算机程序设计列为公共基础课之一。
既然是作为基础课的教材,那么本书所假定的读者可以既不具有程序设计经验,也没有面向对象技术的概念和 Web 程序设计知识,甚至没有太多的计算机知识。即使是一个对计算机一无所知的人,也将能通过一天天学习本书而获取所有有关的基本知识,了解和掌握程序设计。如果读者是一位很有经验的程序员,已在其它程序设计语言中掌握了一定的开发技能,也能在本书中发现很多有用的信息。
本书与程序设计课程
计算机程序设计既是一门概念复杂,知识面广的理论课,也是一门面向实战、需要动手的实践课。几乎所有的初学编程者都梦想着有朝一日能在计算机上驰骋,让一行行程序在自己敲击键盘的手下源源不断地流出,真正成为驾驭计算机的主人。然而,学完程序设计课程后,实际开始编写程序时,却往往会觉得难以下手、无所适从。尽管自己刻苦学习,高分通过考试,但并不能体会到所学知识给实际编程带来的便利和优势。
为什么会这样?一方面原因是我们的学生在学习时没有掌握程序设计的一般过程,没有深入了解通用程序设计语言的本质规律。另一方面是我们的教学体制僵化、教材陈旧,教学思想和内容跟不上时代的发展,与软件开发实际情况脱节。
计算机程序设计语言是一种实现对计算机操作和控制的人造语言,与人类的自然语言有一定差距。程序设计语言仅仅是程序设计的手段和途径而并不是程序设计全部。因此,掌握程序设计语言并不意味着就精通程序设计,就能写出优秀的程序。实际上,程序设计所涉及的领域、知识和技能要远远超出我们的想象。因此本教材对于程序设计课程在一些方面有着自己不同的理解,
程序设计首先是一个过程
程序设计过程通常分为问题建模、算法设计、编写代码和编译调试等 4 个阶段。不同阶段的任务是相对独立的,不能混为一谈。即使是一个比较简单的程序,我们也应该养成先分析,再下手,最后调试的习惯,严格遵循程序设计过程。因为在缺乏对问题深入、全面分析的情况下,就匆匆动手编写程序,将会增加失败的风险,带来后期修改、维护的麻烦。因此学习程序设计,不但不能回避程序设计过程,更要从软件开发过程和软件生命周期的高度来了解和掌握程序设计过程,从一开始就要养成遵从程序设计准则从事程序设计的良好习惯。
有别于其他程序设计教材,本书强调程序设计过程和软件开发过程的重要性,为读者介绍了有关软件建模与测试的基本原理和技术。 特别考虑到现代软件开发依赖于集体合作和项目管理,是汇集了很多程序设计过程的更大的过程。因此,除了在书中增加有关软件过程实施和管理的介绍外,还把如何撰写规范的程序代码作为重要一节,使得读者在学习程序设计之初就了解程序设计的规范,注重编写程序的规范性、正确性和可靠性,对于培养将来参与大型软件开发所需要的分工合作团队成员十分重要。
程序设计还是一种解决问题的方法和能力
程序设计课程主要是学习用计算机解决问题的思考方法,培养编程应用能力,而不是仅仅学会某个程序设计语言的语法规则。 很多学生能弄清楚循环,if-else 结构以及算术表达式,
但很难把一个编程问题分解成结构良好的 Java 程序。还有误人子弟的教材,不用面向对象的思想、方法去讲 Java,而是用变量、过程、函数等老概念来硬套 Java 语法,使学生不会利用面向对象语言的优势来解决问题。这些都暴露了程序设计教学中偏重语法细节,忽略总体思想方法和整体过程实现的问题。
尽管程序设计理论的发展为解决问题提供了很多有效方法,但对于初学者而言学习 Java
的捷近应该是抓住最核心的思想方法:面向对象方法。为实现这个目的,我们把面向对象分析和设计作为重点,围绕面向对象的抽象性、继承性、多态性和封装性这 4 个本质特点阐述面向对象程序设计的基本方法。通过强调基本概念、基本方法、基本应用,并结合案例教学,
我们旨在为初学者奠定扎实的程序设计基础,树立良好的编程思想。通过大量的实例分析和范例程序设计过程演示,我们力图给初学者建立完整印象,培养其从整体上思考问题和解决问题的编程能力。
程序设计最终是对程序设计语言的应用
程序设计和程序设计语言存在着有趣的辩证关系。 程序设计可以用不同的程序设计语言来实现,但是不同的程序设计语言又决定着能使用怎样的程序设计思想方法和技术技巧,制约着程序设计的实现能力和效率。本书使用 Java 作为学习程序设计的语言,是因为 Java 不但继承了 C 语言简洁优美的风格,而且还具有面向对象语言的真正优势。更可喜的是 Java
还在继续发展,不断吸取现代编程语言的精华。这一切使得 Java 具备现代通用程序设计语言的主流特征,特别适合跨平台的编程使用。因此学习 Java 语言,掌握 Java 程序设计方法是本课程的另一个重要任务。
本书虽然以 Java 语言为背景介绍程序设计语言的相关知识,但是重点强调的是一些通用的思想方法,而不是对 Java 语言及其类库的全面介绍。读者应该注意到,不同的程序设计语言其语法和风格可能迥异,但无论哪一种语言,都是以数据(类型),操作(运算),控制(逻辑流程)为基本内容。更进一步讲,学习一门程序设计语言,应该超越语言的具体表述格式,不拘泥于繁芜的语法现象,而是站在抽象的高度,掌握程序设计的基本概念,深入了解程序设计语言的本质规律。这样将会为深入学习其他程序设计语言带来便利。
本书的结构
这本书是为计算机程序设计课程编写的。 该课程对于理工科类的大学相当于公共外语那样的公共基础课,它通过讲授一门具体的计算机语言,来帮助学生掌握程序设计的基础知识和基本应用。同时,对于未接触过计算机科学的学生,这本书还涉及和介绍了与程序设计相关的计算机科学知识。全书由程序设计基础、面向对象程序设计、算法与数据结构,Java
应用程序与 applet 程序设计、程序设计高级话题等 5 个部分组成本书的核心知识,并涉及计算机基础、数据和控制、程序设计理论、软件工程知识等四大知识领域,汇集 18 个相关知识点。虽然在本书中讨论的内容有一定的理论性,但这些理论都是用实际程序问题表达的,
是为了帮助读者建立完整的程序设计知识体系结构。
本书的组织结构如下图所示,其中有些知识点是通过各章节的迭代,循序渐进,不断深入的,
图 P.1 本书组织结构与知识点
本书分为 10 章,各章的主要内容如下,
第 1 章 绪论 介绍程序设计的基本概念和初步认识 Java。重点帮助读者搞清什么是计算机程序、程序设计、程序设计语言等基本概念。同时介绍 Java 程序的编写、
编译和运行,以及相关的环境设置和工具使用。
第 2 章 程序设计基础 这一章首先通过一个简单的 Java 程序来了解程序的组成结构、语言要素和编写规范,建立程序的基本概念。然后以数据和运算作为程序设计的基础,通过讲解数据和数据类型、变量和常量、表达式和运算符以及流程控制,
开始 Java 程序设计语言的探索之旅。
第 3 章 面向对象与对象模型 介绍面向对象的概念和对象建模的方法,讲解 Java
对象模型中的核心部分:类及类的成员。使读者学会如何用创建和使用 Java 对象。
第 4 章 面向对象程序设计 本章将站在面向对象程序设计原则和方法的高度,围绕抽象性、继承性、多态性和封装性 4 个特点讲解面向对象程序设计的基本方法。
重点讨论继承、多态和接口的编程。
第 5 章 算法与数据结构 介绍算法的概念及常用算法。并通过数组、链表、栈、
队列等数据结构以及 Java 对象容器,讨论算法的应用及算法的 Java 程序实现。
第 6 章 图形用户界面 讲解 Java 图形界面应用程序的一般设计方法,包括如何创建窗体、设计界面、管理布局、绘制图形、使用组件、事件编程等。通过这一章的学习可以掌握图形用户界面应用程序的设计方法和编程技巧。
第 7 章 程序设计案例分析 通过剖析和研究一个典型的 Java 应用程序设计案例,
读者不仅可以对窗体、菜单、组件、事件、布局等的设计有一个感性的综合的了解,
还可以进一步理解 Java 应用程序的基本架构和完整设计过程,掌握使用开发工具
( NetBeans IDE)完成程序设计项目的一般过程和方法,积累实际编程经验。同时还可学会利用 NetBeans IDE 的可视化设计功能提高程序设计效率。
第 8 章 applet 与 Web 编程 本章详细讲述 applet 的原理、特性、安全机制以及编程方法,并讨论 applet 在 web 编程中的应用。同时还介绍了 web 编程的一些有用知识。
第 9 章 开发过程与程序质量保证 介绍软件的开发过程及过程的实施管理,从程序质量保证的高度讨论了程序的调试与测试,重点讲述了 Java 程序的调试方法、
程序中的异常处理以及单元测试方法。
第 10 章 线程,文件与串行化 本章将讨论有关线程及输入 /输出的一些高级话题。
通过本章的学习,读者可以了解多任务、进程和线程、线程模型、流、文件、输入
/输出、对象串行化等诸多概念和编程方法,为 Java 的高级应用打下基础。
尽管本书包含了以上章节内容,但实际的教学进度和授课内容可以灵活确定,因为这要取决于课堂教学的安排或读者实际技能及对所讨论问题的熟悉程度。教学时数建议安排在
50~80 课时之间。
本书的读者对象
程序设计入门学生,选用本书作为程序设计基础教材,希望掌握一门优秀、实用、
主流的程序设计语言,为深入学习其它计算机课程奠定基础。显然 Java 作为一种跨平台的面向对象编程语言能胜任教学的需要,为学习程序设计提供更全面的知识结构体系。
因工作或科研需要的非专业编程人员,希望迅速掌握 Java 编程以完成不太复杂的编程任务。
非计算机专业的编程爱好者,改行从事程序员工作,有一些实际的 Java 编程经验,
但没有系统学习过相关专业知识。希望通过本书重温程序设计知识,补习相关概念和理论。
有一定经验的程序员,已在其它编程语言及其软件环境中掌握了一定的开发技能,
但没有使用过 Java 语言或对 Java 语言一知半解。 希望在本书中系统学习 Java 程序设计,发现 Java 与其所熟悉语言的不同点,并由此掌握 Java 语言。
本书的特色
本书的一些特色不仅使得本书与众不同,同时也特别有助于入门者去学习。
概念和知识面
贯穿本书,我们始终强调概念要比数学模型更重要,我们认为对概念的理解必然左右对模型的理解,概念与模型的结合可以帮助读者更好地掌握所学内容。同时,我们还特别注意开阔读者的知识面,使读者能够站在现代软件开发和软件工程这个比较开阔的层面上了解程序设计,而不是局限于繁琐的程序设计语言规则上。为此,我们在全书中贯穿了软件工程的思想,使用了诸如 UML 建模、单元测试等与程序设计相关的软件工程实用方法。
代码编写和工具使用能力并重
初学 Java 程序设计的人,大多数会被 Java 强大的功能所吸引,同时也被 Java 的代码编写难度所吓倒。传统的教材让学生使用记事本那样的简单文本编辑器撰写 Java 程序,并在控制台中通过命令行编译和调试程序,无形之中增加了初学者学习的难度,这种方法在本教材中并不提倡。选用优秀的 Java 集成开发环境( IDE)作为教学平台,一方面可以帮助初学者发现代码错误(包括难以察觉的拼写错误),方便程序的调试和编译;另一方面可以让学生了解软件的实际开发环境,学会利用工具来提高学习和编程的效率。
鉴于快速应用开发 (RAD)的思想正在改变程序设计的方法,而高效率的可视化程序设计实际上已经重造了开发者的工作平台。以前编写 Java 程序是通过基于字符的编辑器键入一条条语句的方法,现在某些优秀的 Java IDE 也能像 Delphi,VB 那样支持我们交互式地在窗体上点击和拖放( click-and-drop)组件,并使用短小精悍的代码段连接它们。过去,即使是开发很小的 Java 图形界面应用程序,也需要很多细心而且繁杂的基础工作,先写出非常长的源代码文件,然后才可编译和测试其结果。 Java IDE 工具的出现和不断发展改变了这种模式。本教材中,我们采用 SUN 公司的开源 IDE 工具 NetBeans,在强调编程能力的同时,我们鼓励学生使用 IDE 工具来提高程序设计的效率和质量。
鼓励学生使用 IDE 工具并不是削弱他们编写代码的能力,更不是宣扬那种无须写任何程序就可以用控件组装应用程序的神话。而是为了让学生把精力用于学习程序设计上,而不是浪费在繁琐的配置、设置、调试和重复输入字符上。其实这也是 IDE 工具进化的目标之一,即让程序员面对真正的程序设计任务,而不是浪费在窗口的几个 GUI 组件上或繁琐的手工调试上,程序员的创造力应该体现在结构优良,性能出众,稳定可靠,易于扩展的程序设计上。
真正的程序设计需要很多知识、技能和创造力。基于 IDE 工具的可视化程序设计只是手段不是重点。即使是在 GUI 界面的设计中,我们也同时采用了可视化程序设计和非可视化程序设计两种实践,以便读者学习、比较。因为本书的重点仍然在学习程序设计语言的本质,培养代码的编写能力。这与强调代码编写和工具使用能力并重并不矛盾。
图文并茂
阅读本书后将会发现本书图文并茂。全书有 100 多幅精心设计的图片,这些图片可以帮助读者增进对文字的理解。
示例程序
本书尽可能地运用示例程序来表述概念和模型,同时尽量提供完整的范例程序和程序设计过程。本书所有示例程序和习题中的程序都已在 JDK 1.5 中编译运行通过,建议读者在学习时选择 Java 2 标准版 JDK 1.5 的版本。
习题
每一章的结尾都包括本章习题。 本章小结包括了对本章中所有关键内容和知识点的简明概括,是复习时的参考。本章习题包括了三部分内容:复习题,测试题和练习题。
复习题:测试本章中所有的要点和概念,帮助学生复习巩固重点内容。
测试题:通过多项选择题,客观地测试学生对所学知识的理解和掌握程度。
练习题:通过课后练习题,检查学生能否运用掌握的概念和知识独立思考,解决问题。
本教材配有专用的习题解答及课程设计教辅书籍,Java 程序设计大学教程习题解答与课程设计,。
CC2004 课程体系
从 1990 年开始,美国电气和电子工程师协会计算机社团( the Computer Society of the
Institute for Electrical and Electronic Engineers,简 称 IEEE-CS)和计算机学会( Association for
Computing Machinery,简称 ACM)就着手开发新的本科生计算机课程体系。 1991 年联合推出了 Computing Curricula1991(简称 CC1991),当时仅限于 Computer Science 和 Computer
Engineering 两个专业的课程。 1998 年秋季开始,IEEE-CS 和 ACM 联合投入新的力量更新该课程体系,并在 2001 年开发出 Computing Curricula2001(简称 CC2001),并将该计算机课程体系扩大到 Computer Science,Computer Engineering,Software Engineering,Information
Systems 等多个专业。在 CC2001 的实施中,专家们发现,计算机课程所涉及的学科专业和教学范围正在不断扩大,而且在内容和教学方面的变化也日新月异。 IEEE-CS 和 ACM 意识到 10 年一次的 Computing Curricula 修订已经难以满足要求,于是联合国际信息处理联合会
( International Federation for Information Processing,简称 IFIP),英国计算机协会( British
Computer Society,简称 BCS) 等更多的组织开发了 Computing Curricula2004(简称 CC2004),
使之成为开放的、可扩充的、适合多专业的、整合了计算机教学相关原则体系观点的课程体系指南。其结构参见下图。
为了进一步反映当代计算机科学技术的发展水平,与国际主流计算机教育思想接轨。通过多年来对 IEEE-CS 和 ACM 的 Computing Curricula 课程体系的跟踪研究,我们在本教材的编写中,借鉴了 CC2004 课程体系的最新研究成果,同时吸取了国外同类教材的优秀经验,
其目的是进一步推动教材和课程改革,培养有竞争力人才。
致谢
本书是在作者多年科研和教学基础上编写的,主要参考了作者已发表的文章和著作以及教学中积累的资料。书中还用到了其他中外文教材、资料,由于无法在此一一列举,现谨对这些教材和资料的作者表示衷心的感谢。
参与本教材编写工作的人员还有昆明理工大学的刘迎春,海军工程大学的王永斌、周安栋、段立、罗兵、李启元、杜军、吴苗、曹旭峰,南京航空航天大学无人机研究所的吴英,
太原师范学院计算机中心的刘星,以及杨德刚、刘藩、吴永逸、洪蕾等。
一本书的出版离不开许多人的支持,尤其是这本书。为此感谢我们的家人和朋友。我们在忍受写作之苦的同时,牺牲了与他们共享天伦之乐的宝贵时光。
由于作者水平有限,本书中难免有疏漏和不妥之处,恳请各位专家、同仁和读者不吝赐教,并在此表示特别感谢!
http://www.liu-yi.net
2005年 11 月 24 日南京
Java 程序设计大学教程
目 录
第 1 章 绪论,....................................................................................................................................1
1.1 什么是程序设计,................................................................................................................1
1.1.1 程序与计算机,...............................................................................................................................1
1.1.2 算法与数据结构,..........................................................................................................................,4
1.1.3 程序设计过程,...............................................................................................................................7
1.2 程序设计语言,....................................................................................................................8
1.2.1 发展历史,......................................................................................................................................,9
1.2.2 语言的类型,..................................................................................................................................,9
1.2.3 高级语言的分类,........................................................................................................................,10
1.3 Java 语言介绍,.................................................................................................................,11
1.3.1 Java 发展的历史,........................................................................................................................,11
1.3.2 Java 是什么,Java 不是什么,....................................................................................................,12
1.3.3 下载 JDK 搭建 Java 平台,..........................................................................................................,14
1.4 Java 程序的编写、编译和运行,......................................................................................17
1.4.1 使用命令行工具,........................................................................................................................,17
1.4.2 使用 Java 编辑器 TextPad..........................................................................................................,19
1.4.3 使用集成开发环境 NetBeans IDE.............................................................................................,20
1.4.4 优秀 Java 开发工具介绍,...........................................................................................................,28
1.5 本章习题,..........................................................................................................................30
第 2 章 程序设计基础,..................................................................................................................33
2.1 程序,..................................................................................................................................33
2.1.1 初识 Java 程序,...........................................................................................................................,33
2.1.2 标识符和关键字,........................................................................................................................,36
2.1.3 撰写规范的程序代码,................................................................................................................,37
2.2 数据和数据类型,..............................................................................................................40
2.2.1 数据,............................................................................................................................................,41
2.2.2 常量和变量,................................................................................................................................,43
2.2.3 数据类型,....................................................................................................................................,44
2.3 表达式与运算符,..............................................................................................................53
2.3.1 表达式,........................................................................................................................................,53
2.3.2 运算符,........................................................................................................................................,53
2.3.3 运算符的优先级,........................................................................................................................,57
2.4 流程控制,..........................................................................................................................58
2.4.1 顺序结构,....................................................................................................................................,58
2.4.2 选择结构,....................................................................................................................................,59
2.4.3 循环结构,....................................................................................................................................,64
2.5 本章习题,..........................................................................................................................71
第 3 章 面向对象与对象模型,......................................................................................................80
3.1 面向对象的概念,..............................................................................................................80
3.1.1 面向对象基本原理,....................................................................................................................,80
3.1.2 建立面向对象的思维,................................................................................................................,82
3.1.3 UML 和对象建模,......................................................................................................................,83
Java 程序设计大学教程
3.2 类,......................................................................................................................................87
3.2.1 什么是 Java 类,...........................................................................................................................,87
3.2.2 类成员,........................................................................................................................................,88
3.2.3 类成员的可访问性,....................................................................................................................,90
3.3 方法,..................................................................................................................................90
3.3.1 什么是方法,................................................................................................................................,90
3.3.2 方法参数,....................................................................................................................................,93
3.3.3 静态字段和静态方法,................................................................................................................,94
3.4 对象,..................................................................................................................................97
3.4.1 理解对象,....................................................................................................................................,97
3.4.2 使用对象,....................................................................................................................................,98
3.4.3 对象之间的关系,......................................................................................................................,107
3.5 本章习题,........................................................................................................................109
第 4 章 面向对象程序设计,.......................................................................................................,114
4.1 原则和方法,...................................................................................................................,114
4.2 继承,...............................................................................................................................,116
4.2.1 使用继承,..................................................................................................................................,116
4.2.2 继承与合成,..............................................................................................................................,128
4.3 多态,................................................................................................................................130
4.3.1 多态与动态绑定,......................................................................................................................,130
4.3.2 方法的绑定,..............................................................................................................................,133
4.4 接口,................................................................................................................................136
4.4.1 接口的概念,..............................................................................................................................,136
4.4.2 Java 接口,..................................................................................................................................,137
4.4.3 接口应用实例,..........................................................................................................................,141
4.5 本章习题,........................................................................................................................147
第 5 章 算法与数据结构,............................................................................................................152
5.1 算法,................................................................................................................................152
5.1.1 算法的描述,..............................................................................................................................,153
5.1.2 常用算法,..................................................................................................................................,155
5.2 数组,................................................................................................................................161
5.2.1 数组的创建和使用,..................................................................................................................,161
5.2.2 多维数组和不规则数组,..........................................................................................................,164
5.2.3 排序,..........................................................................................................................................,167
5.2.4 查找,..........................................................................................................................................,170
5.3 对象容器,........................................................................................................................172
5.3.1 Java 容器框架,..........................................................................................................................,172
5.3.2 Collection 与 Iterator,...............................................................................................................,174
5.3.3 List 及 ListIterator.....................................................................................................................,177
5.4 抽象数据类型,................................................................................................................181
5.4.1 链表,..........................................................................................................................................,182
5.4.2 栈,..............................................................................................................................................,183
5.4.3 队列,..........................................................................................................................................,186
5.5 本章习题,........................................................................................................................187
Java 程序设计大学教程
第 6 章 图形用户界面,................................................................................................................192
6.1 GUI 编程基础,................................................................................................................192
6.1.1 图形用户界面,..........................................................................................................................,192
6.1.2 Swing 和 ATW..........................................................................................................................,195
6.1.3 窗体容器,..................................................................................................................................,196
6.2 图形与绘图,....................................................................................................................202
6.2.1 坐标系统,..................................................................................................................................,202
6.2.2 颜色,..........................................................................................................................................,203
6.2.3 绘图,..........................................................................................................................................,204
6.3 事件处理模型,................................................................................................................208
6.3.1 事件和 Java 事件模型,.............................................................................................................,208
6.3.2 事件处理实例分析,..................................................................................................................,209
6.3.3 内部类,......................................................................................................................................,214
6.3.4 常用组件的事件,......................................................................................................................,215
6.4 使用 Swing 组件,............................................................................................................217
6.4.1 MVC 模型,................................................................................................................................,217
6.4.2 布局管理,..................................................................................................................................,221
6.4.3 Swing 组件编程,.......................................................................................................................,223
6.5 本章习题,........................................................................................................................229
第 7 章 程序设计案例分析,........................................................................................................234
7.1 可视化程序设计与 NetBeans IDE,................................................................................235
7.2 设计窗体,........................................................................................................................236
7.2.1 创建主窗体和主面板,..............................................................................................................,236
7.2.2 组件与布局设计,......................................................................................................................,239
7.2.3 添加事件,..................................................................................................................................,246
7.3 设计菜单和对话框,........................................................................................................249
7.3.1 设计菜单,..................................................................................................................................,249
7.3.2 设计对话框,..............................................................................................................................,254
7.4 设计算法,........................................................................................................................262
7.5 完成和部署应用程序,....................................................................................................267
7.6 本章习题,........................................................................................................................273
第 8 章 APPLET 与 WEB 编程,..................................................................................................278
8.1 Java applet 基础,.............................................................................................................278
8.1.1 什么是 applet............................................................................................................................,278
8.1.2 编写 applet 程序,......................................................................................................................,279
8.1.3 applet 的生命周期,...................................................................................................................,281
8.2 applet 在 Web 中的应用,.................................................................................................282
8.2.1 HTML 与 Web 编程,................................................................................................................,283
8.2.2 applet Web 编程技巧,...............................................................................................................,284
8.2.3 applet 的安全机制,...................................................................................................................,287
8.3 把 Java 应用程序转换为 applet.....................................................................................288
8.3.1 转换方法,..................................................................................................................................,288
8.3.2 转换示例,..................................................................................................................................,288
8.4 本章习题,........................................................................................................................292
Java 程序设计大学教程
第 9 章 开发过程与程序质量保证,............................................................................................297
9.1 软件开发过程概述,........................................................................................................297
9.1.1 软件生命周期,..........................................................................................................................,297
9.1.2 软件开发过程,..........................................................................................................................,299
9.1.3 软件质量与测试,......................................................................................................................,302
9.2 程序调试,........................................................................................................................305
9.2.1 程序调试的概念,......................................................................................................................,305
9.2.2 使用断点,..................................................................................................................................,307
9.2.3 监视和检查数据的值,..............................................................................................................,309
9.2.4 调试过程,..................................................................................................................................,310
9.3 单元测试,.......................................................................................................................,311
9.3.1 单元测试与 JUnit,....................................................................................................................,311
9.3.2 在 NetBeans IDE 中使用单元测试,.........................................................................................,312
9.3.3 单元测试的应用举例,..............................................................................................................,313
9.4 异常与异常处理,............................................................................................................317
9.4.1 异常与异常类,..........................................................................................................................,317
9.4.2 异常处理机制,..........................................................................................................................,321
9.4.3 利用异常处理编程,..................................................................................................................,325
9.5 本章习题,........................................................................................................................328
第 10 章 线程、文件与串行化,................................................................................................333
10.1 多线程程序设计,............................................................................................................333
10.1.1 多任务、进程和线程,.........................................................................................................,333
10.1.2 Java 线程模型,.....................................................................................................................,334
10.1.3 设计多线程的应用程序,.....................................................................................................,342
10.2 流和文件,........................................................................................................................346
10.2.1 基本概念,.............................................................................................................................,347
10.2.2 基于文本文件的应用,.........................................................................................................,348
10.2.3 I/O 流与文件,.......................................................................................................................,353
10.3 对象串行化,....................................................................................................................357
10.3.1 串行化的目的,.....................................................................................................................,357
10.3.2 串行化的方法,.....................................................................................................................,358
10.4 本章习题,........................................................................................................................363
参考文献,.........................................................................................................................................367
Java 程序设计大学教程
1
第 1章 绪论
计算机程序设计对于很多初学者来说,充满了神秘的诱惑。本章通过介绍计算机程序设计和程序设计语言的基础知识,揭开了程序设计神秘的面纱。帮助读者搞清什么是计算机程序、程序设计和程序设计语言等基本概念。
Java 作为我们要学习的程序设计语言,是本章要介绍的重点。我们会沿着 Java 的发展历史,探索这门应用广泛的计算机语言,并讨论 Java 是什么,又不是什么。
本章我们还要介绍如何下载 JDK 搭建 Java 平台,如何编写、编译和运行 Java 程序,如何使用 Java 程序的开发工具。总之,通过本章的学习,我们将为开始 Java 程序设计的探索之旅做好最充分的准备。
1.1 什么是程序设计
程序是指按照时间顺序依次安排的工作步骤。而程序设计则是对这些步骤的编排和优化。程序设计有着比计算机更长的历史,只不过计算机的出现使得程序设计有了更专用的领域——计算机程序设计,并得到空前的发展。计算机程序设计又称为编程( programming),
是一门设计和编写计算机程序的科学和艺术。
1.1.1 程序与计算机
人们用程序的形式存储一系列指令已经有几个世纪了。 18 世纪的音乐盒和 19 世纪末与
20 世纪初的自动钢琴,就可以播放音乐程序。这些程序以一系列金属针或纸孔的形式存储,
每一行(针或孔)表示何时演奏一个音符,而针或孔则表明此时演奏什么音符。 19 世纪初,
随着法国发明家约瑟夫—玛丽?雅卡尔的由穿孔卡片控制的编织机的发明,人们对物理设备的控制变得更加精巧。在编织特定图案的过程中,编织机的各个部分得进行机械定位。为了使这个过程自动化,雅卡尔使用一张纸质卡片代表织机的一个定位,用卡片上的孔来指示该执行织机的哪个操作。整条花毯的编织可被编码到一叠这样的卡片上,同样的一叠卡片每次使用都会编出相同的花毯图案。在这种可编程的编织机使用中,有的复杂程序需要由 24,000
多张卡片构成。
世界上第一台可编程的机器是由英国数学家和发明家查尔斯?巴比奇设计的,但从未完全制造成。这台叫做分析机的机器,使用和雅卡尔的织机类似的穿孔卡片来选择每个步骤应执行的具体算术运算。插入不同的卡片组,就会改变机器执行的运算。这种机器几乎能在现代计算机中找到类似的对应物,只不过它是机械化的,而非电气化的。分析机的制造从未完成,是因为制造它所需要的技术当时不存在。
供分析机使用的最早卡片组程序是由诗人拜伦勋爵的女儿——英国数学家奥古斯塔?埃达?拜伦开发的。由于这个原因,她被认为世界上第一位程序员。
现代的内部存储计算机程序的概念是由美籍匈牙利数学家约翰?冯?诺伊曼于 1945 年首先提出来的。冯?诺伊曼的想法是使用计算机的存储器来既存储数据又存储程序。这样,程序可被视作数据,可像数据一样被其他程序处理。这一想法极大地简化了计算机中的程序存储与执行的任务。
计算机程序是指导计算机执行某个功能或功能组合的一套指令。要使指令得到执行,计
Java 程序设计大学教程
2
算机必须执行程序,也就是说,计算机要读取程序,然后按准确的顺序实施程序中编码的步骤,直至程序结束。一个程序可多次执行,而且每次用户输给计算机的选项和数据不同,就有可能得到不同的结果。
现代计算机都是基于冯·诺伊曼模型结构的,此模型着眼于计算机的内部结构,定义了处理机的运行过程。该模型把计算机分为四个子系统:存储器、算术 /逻辑单元、控制单元和输入 /输出单元,
存储器 存储器是用来存储的区域,计算机在处理过程中存储器用来存储数据和程序,我们会在后面讨论存储数据和程序的话题。
算术 /逻辑单元 算术 /逻辑单元是用来进行计算和逻辑操作的地方。如果是一台数字处理用的计算机,它应该能够进行数字运算,(例如进行一系列的数字相加运算) 。
当然它也应该可以对数据进行一系列逻辑操作。 (例如,找出两个数字中的小的一个) 。
控制单元 控制单元是用来对存储器、算术 /逻辑单元、输入输出等子系统进行控制操作的单元。
输入 /输出单元 输入子系统负责从计算机外部接受输入数据和程序;输出子系统负责将计算机的处理结果输出到计算机外部。输入 /输出子系统的定义相当广泛,
它们还包含辅助存储设备,例如,用来存储处理所需的程序和数据的磁盘和磁带等。
当一个磁盘用于存储处理后的输出结果,我们一般就可以认为它是输出设备,如果你是从该磁盘上读取数据,该磁盘就被认为是输入设备。
如果不关心计算机的内部物理结构,我们可以简单的认为计算机是一个黑盒。但是,仍然需要通过定义计算机所完成的工作来区别其和其他黑盒之间的差异。 这里我们提供两种常见的计算机模型。
一种是数据处理器。
可以认为计算机是一个数据处理器。依照这种定义,计算机就可以认为是一个接受输入数据,处理数据,产生输出数据的黑盒(如图 1-1 所示) 。尽管这个模型能够体现现代计算机的功能,但是它的定义还是太狭窄。 按照这种定义,便携式计算器也可以认为是计算机 (按照字面意思,它也符合定义的模型) 。
计算机
图 1-1 数据处理模型
另一个问题是这个模型并没有说明它处理的类型以及是否可以处理一种以上的类型。 换句话说,它并没有清楚的说明一个基于这个模型的机器能够完成操作的类型和数量。它是专用机器还是通用机器呢?
这种模型可以表示为一种设计用来完成特定任务的专用计算机(或者处理器),比如用来控制建筑物温度或汽车油料使用。尽管如此,计算机作为一个当今使用的术语,是一种通用的机器。它可以完成各种不同的工作。这表明我们需要改变我们对计算机定义的模型来反映当今计算机的现实。
输入数据 输出数据
Java 程序设计大学教程
3
另一种是可编程数据处理器。
一个相对较好的适用于具有通用性的计算的模型如图 1-2 所示。图中添加了一个额外的元素——程序到计算机内部。程序是用来告诉计算机对数据进行处理的指令集合。在早期的计算机中,这些指令是通过对配线的改变或一系列开关的打开闭合来实现的。今天,应用程序则是计算机语言所编写的一系列指令的集合。
计算机
图 1-2 可编程数据处理器模型
在这个新模型中,输出数据依赖两方面因素的结合作用:输入数据和程序。对于相同的数据输入,如果改变程序,则可能产生不同的输出。类似的,对于同样的程序,如果你改变输入内容,其输出结果也将不同。最后,如果输入数据和程序保持不变,输出结果也不变。
冯·诺伊曼模型的主要特征在于其存储程序的概念。尽管早期的计算机没有使用这种模型,但它们还是使用了程序的概念。编程在早期的计算机中体现为对一系列开关的开闭合和配线的改变。编程是在数据实际开始处理之前由操作员和工程师完成的一项工作。
冯·诺伊曼模型改变了“程序”的含义。在这种模型中,程序有了两个方面的含义。
首先,程序必须是存储的。在冯·诺伊曼模型中这些程序被存储在计算机的内存中,内存中不仅仅需要存储数据,还要存储程序(参见图 1-3) 。
存储器
图 1-3 内存中的程序和数据
其次,模型中还要求程序必须是有序的指令集。每一条指令操作一个或者多个数据项。
因此,一条指令可以改变它前面指令的作用。例如,示例程序 1-1 演示了输入 a,b 两个整数,将它们相加后显示出结果的程序。这段程序包含了 5 条指令代码。
示例程序 1-1 由多条指令组成的程序
1,write('请输 a,b两个整数,');
2,readln(a);
3,readln(b);
程序
数据
程序
输入数据 输出数据
Java 程序设计大学教程
4
4,c=a+b;
5,writeln('a+b='+IntToStr(c));
也许有人会问为什么程序必须由不同的指令集组成,答案是重用性。如今,计算机完成成千上万的任务,如果每一项任务的程序都是相对独立而且和其他的程序之间没有任何的公用段,程序设计将会变成一件很困难的事情。冯·诺伊曼模型通过仔细地定义计算机可以使用的不同指令集,从而使得程序设计变得相对简单。一个程序员通过组合这些不同的指令来创建任意数量的程序。每个程序可以是不同指令的不同组合。
前面的要求使得程序设计变得可能,但也带来了另外一些使用计算机方面的因素。程序员不仅要了解每条指令所完成的任务,还要知道怎样将这些指令结合起来完成一些特定的任务,对于一些不同的问题,一个程序员首先应该以循序渐进的方式来解决问题,接着尽量找到合适的指令(指令序列)来解决问题。这种按步骤解决问题的方法就是所谓的算法。算法在计算机科学中起到了重要的作用,我们会在后面详细讨论。
在计算机时代的开端,并没有计算机语言。程序员依靠写指令的方式(使用位模式,即直接写二进制代码指令)来解决问题。但是随着程序的越来越大,采用这种模式来编写很长的程序变得单调乏味。计算机科学家们研究出利用符号来代表二进制格式指令。就像人们在日常中用符号(单词)来代替一些常用的指令一样。当然人们在日常生活中所用的一些符号并不相同于计算机中所用的符号。这样计算机语言的概念诞生了。自然语言(例如英语)是一门丰富的语言,并有许多正确组合单词的规则;相对而言,计算机语言只有比较有限的符号和单词。后面我们还会介绍一些计算机语言的知识。
1.1.2 算法与数据结构
1,计算机程序
程序是程序设计中最基本的概念,也是软件中最基本的概念。程序是计算任务的处理对象和处理规则的描述。所谓计算任务是指所有通过计算来解决实际问题的任务。处理对象是数据,如数字、文字和图像等。处理规则一般指处理动作和步骤。在低级语言中,程序是一组指令和相关的数据。在高级语言中,程序一般是一组说明和语句,它包括了算法和数据结构。
我们知道,利用计算机解决问题需要使用程序对问题的求解进行描述。这种解决问题的过程类似人脑的解题过程,即利用一些规则或方法去处理特定的对象,从而解决问题。
通过程序,计算机可以按照人所规定的算法对数据进行处理。首先,人类凭借自然语言进行思维,而计算机使用计算机语言进行“思维”,控制计算机解题过程的算法必须以计算机能够“读得懂”的形式表示出来,也就是需要用计算机语言将算法描述出来,这种以计算机语言描述的算法就是程序。其次,算法只是描述人类思维时对数据的处理过程,人类思维时所用到的数据及其操作,将根据思维者的教育背景以人们无法直接看到的某种方式自然而然地存储在思维者的大脑中,因此人在解决一个具体问题时往往不会过多地考虑所涉及的数据。然而计算机解决问题与此不同,除去一些基本的操作可以由计算机系统提供外,即便是看起来很简单的操作也需要进行专门定义和实现,而且那些“书写”在人脑中,常常被使用的数据,在使用计算机解决问题时将变得不再简单。如何将它们放置在计算机中将是通过计算机使用算法解决问题所不可回避的问题。因此,使用计算机解决问题时,除了需要使用计算机语言描述算法,还必将涉及数据结构。从这个意义上讲,程序是建立在数据结构基础上使用计算机语言描述的算法,因此简单地讲,程序也可以表示成:算法+数据结构。
Java 程序设计大学教程
5
2,算法
算法是一种逐步解决问题或完成任务的方法。 算法完全独立于计算机系统。 更特别的是,
算法接收一组输入数据,同时产生一组输出数据。
算法的定义是:算法是一组明确步骤的有序集合,它产生结果并在有限的时间内终结。
因此我们应该从这几个方面理解算法,
有序集合 算法必须是一组定义完好且排列有序的指令集合。
明确步骤 算法的每一步都必须有清晰明白的定义。如某一步是将两数相加,那么必须定义相加的两个数和加法符号,不能用一个符号在某处用作加法符号,而在其它地方用作乘法符号。
产生结果 一个算法必须产生一个结果否则该算法也就没有意义。 结果集可以是被调用的算法返回的数据或其它效果(如,打印) 。
有限的时间内终结 一个算法必须能够终结。如果不能(例如,无限循环),说明不是算法。显然,任何可解问题的解法形式为一个可终结的算法。
计算机专家们为算法定义了三种结构。实际上已经证实,算法必定是由顺序、选择和循环(图 1-4)这三种基本结构组成,其它结构都是不必要的。仅仅使用这三种结构就可以使算法容易理解、调试或修改。
图 1-4 算法的三种基本结构
顺序结构 一个算法,甚至整个程序,都是一个顺序的指令集。它可以是一简单指令或是其它两种结构之一。
选择结构 有些问题只用顺序结构是不能够解决的。 有时候需要检测一些条件是否满足。假如测试的结果为真,即条件满足,则可以继续顺序往下执行指令;假如结果为假,即条件不满足,程序将从另外一个顺序结构的指令继续执行。这就是所谓的选择结构。
循环结构 在有些问题中,相同的一系列顺序指令需要重复,那么就可以用循环结构来解决这个问题。例如,从指定的数据集中找到最大值的算法就是这种结构的例子。
动作 1
动作 2
动作 n
a) 顺序
判断另一个动作序列 一个动作序列 一个动作序列
当条件
真假真假
b) 选择
c) 循环
Java 程序设计大学教程
6
3,数据结构
算法直观上表现为对各种数据的操作,那么什么是数据?数据又是如何组织的呢?
由于算法作用的对象是数据,而数据的定义又必须是为计算机所能够识别、存储和处理的符号集合,并最终通过计算机加以实现和运行,所以数据对象需要通过一定的数据结构来组织。数据的基本单位是数据元素,数据元素可能具有底层结构,即每个数据元素由一个或多个数据项组成,数据项(又称为字段、域)是具有独立含义的最小单位数据,虽然一些数据元素具有底层结构,但是对它的使用总是将它看作是一个整体。在算法中更常见的是处理多个具有相同性质的数据元素,因此又常把由一个或多个性质相同的数据元素组成的集合称为数据对象,数据对象经常是一个具有底层结构的实体,常常被作为一个整体加以引用。比如,存储在内存中的通讯录也可称为数据对象,该数据对象由每个同学的通讯地址这个数据元素组成,而每个同学的通讯地址又由姓名、电话、住址、邮政编码等数据项组成。
因此,一个数据对象中的各数据元素的存在不是孤立的,其相互之间存在着某些联系,
通常把这些联系统称为数据结构。具体地说,数据结构由数据元素之间的逻辑结构、数据的存储结构以及在这些数据元素上定义的操作组成。
( 1) 数据的逻辑结构
数据的逻辑结构抽象地反映出数据元素之间的逻辑关系。 逻辑结构与数据在计算机中的存储方式无关,它所体现出的数据元素之间的关系完全是抽象的。例如,数字 1,2,3,虽然它们可能书写在纸面上的不同位置,甚至数字 3 出现在数字 1 之前,但这并不能改变 1
小于 3 这种数值之间的关系。 也就是说它们与写在纸面上的不同位置或存储在计算机内不同存储单元的顺序无关。
数据可以根据其是否具有底层结构划分成初等类型(也称基本类型)和构造类型两类,
而常见的初等类型有 5 种,
整数类型 计算机所定义的其值属于一定范围的整数。
实数类型 又称浮点数类型,计算机所定义的其值属于一定范围的小数。
逻辑类型 取值为真和假,通常用非 0 整数和 0 表示,或表示为 true 和 false。
字符类型 取值为计算机所采用的字符集的元素。
指针类型 取值为内存中某存储单元地址,该单元存有某种类型的数据。
构造类型由初等类型或构造类型通过某种方式组合而成,不同的组合方式得到的构造类型不同,例如上文所述的通讯录等。
( 2) 数据的存储结构
算法中出现的数据最终将被存储于计算机中,就像在纸面上书写数据要考虑在什么位置书写,一个数据占几格一样,在计算机中存储数据也要考虑将数据存储在内存中的哪个位置,
占用多大的存储空间。数据的存储结构提供了数据的逻辑结构在计算机存储器中的映像,根据数据的存储结构将逻辑上相联系的数据元素存储在相应的存储单元中,也就是说,数据的存储位置和读写方式体现了数据的逻辑结构。常见的存储映像方式如下,
顺序方式 将逻辑上相邻的数据元素存储在物理上相邻的存储单元中,数据间的逻辑关系通过存储单元的邻接关系体现。
链接方式 数据元素被作为节点的一部分存储在某个存储单元,节点的存储不要求采用顺序方式。每个节点都含有指向逻辑上相邻的节点的指针,数据元素之间的逻辑关系通过节点之间的指针链接体现。
索引方式 按照数据元素的序号建立索引表,索引表中第 i 项的值为第 i 个数据元素的存储地址。
Java 程序设计大学教程
7
散列方式 数据元素的存储地址与惟一标识该元素的关键字之间具有某种确定的函数关系,这里的关键字通常为数据元素的某个或某些数据项。
上面 4 种方式可以混合使用,同一种数据在不同的算法和应用中也可以采用不同的存储映像方式,从而形成不同的数据结构。
1.1.3 程序设计过程
程序是算法在计算机上的具体实现,实现算法时所采用的通常是高级程序设计语言,这种语言的程序是不能直接在计算机上运行的,通常需经由计算机系统提供的高级语言编译器,将其转换成计算机所能识别的机器语言后才能在计算机上运行。程序的设计过程包括,
问题建模
算法设计
编写代码
编译调试
程序将以数据处理的方式解决客观世界中的问题,因此在程序设计之初,首先应该将实际问题抽象成一个求解模型,然后为该模型设计和制定算法。通过问题建模,可以清楚地描述各种概念、已知条件、所求结果,以及已知条件与所求结果之间的联系等各方面的信息。
模型和算法的结合将给出问题的解决方案。
具体的解决方案确定后,需要对所采用的算法进行描述,算法的初步描述可以采用自然语言方式,然后逐步将其转化为程序流程图或其他直观方式。这些描述方式比较简单明确,
能够比较明显地展示程序设计思想,是进行程序调试的重要参考。
使用计算机系统提供的某种程序设计语言,根据上述算法描述,将已设计好的算法表达出来,使得非形式化的算法转变为形式化的由程序设计语言表达的算法,这个过程称为程序编码。
大多数程序只是由少数几种步骤构成,这些步骤在整个程序中在不同的上下文和以不同的组合方式多次重复。最常见的步骤是顺序、选择和循环这 3 种。
程序经常不止一次地使用特定的一系列步骤。这样的一系列步骤可以组合成一个子例程,而子例程根据需要可在主程序的不同部分进行调用或访问。每次调用一个子例程,计算机都会记住它自己在该调用发生时处在程序的那个位置,以便在运行完该子例程后还能够回到那里。在每次调用之前,程序可以指定子例程使用不同的数据,从而做到一个通用性很强的代码段只编写一次,而被以多种方式使用。
大多数程序使用几种不同的子例程。其中最常用的是函数、过程、库程序、系统程序以及设备驱动程序。函数是一种短子例程,用来计算某个值,如角的计算,而该值计算机仅用一条基本指令无法完成计算。过程执行的是复杂一些的功能,如给一组名称排序。库程序是为许多不同的程序使用而编写的子例程。系统程序和库程序相似,但实际上用于操作系统。
它们为应用程序提供某种服务,如打印一行文字。设备驱动程序是一种系统程序,它们加到操作系统中,以使计算机能够与扫描仪、调制解调器或打印机等新设备进行通信。设备驱动程序常常具有可以直接作为应用程序执行的特征。 这样就使用户得以直接控制该设备。 例如,
打印机驱动程序。
现在,程序设计者可以通过特殊的应用程序来设计新程序,这些应用程序常被称作开发工具,如 Java 的开发工具 JBuilder,Visual Basic 的开发工具 MS Visual Studio 等。实际上,
程序员也可以直接使用最简单的文本编辑器(也是一种程序)来依据程序设计语言的语法规则编写新程序。使用文本编辑器,程序员创建一个文本文件,这个文本文件是一个有序指令
Java 程序设计大学教程
8
表,包含了程序员撰写的源代码,因此它也称为程序源文件。所谓源代码是指构成程序源文件的那些指令。在这个时候,一种特殊的应用程序将源代码翻译成机器语言或目标代码——
即能够被操作系统执行的一种格式,真正的可执行程序。
将源代码翻译成目标代码的应用程序有 3 种:编译器、解释器和汇编程序。这 3 种应用程序在不同类型的程序设计语言上执行不同的操作,但是它们都起到将程序设计语言翻译成机器语言的相同目的。
通过编译器,可以将使用 FORTRAN,C 和 Delphi 等高级程序设计语言编写的文本文件一次性从源代码翻译成目标代码。而 BASIC,LISP,Perl 等解释执行的语言所采取的方式则与此不同,在解释执行的语言中程序是通过解释器随着每条指令的执行而逐个语句地翻译成目标代码的。解释执行的语言的优点是,它们可以立即开始执行程序,而不需要等到所有的源代码都得到编译。对程序的更改也可以相当快地做出,而无需等到重新编译好整个程序之后。解释执行的缺点是,它们执行起来要比事先编译好的程序慢,因为每次运行程序,都必须对整个程序一次一条指令地翻译。另一方面,编译执行的语言只编译一次,因此计算机执行起来要比解释执行的语言快得多。由于这个原因,编译执行的语言更常使用。
有意思的是,Java 程序在转换过程中既有编译也有解释。即 Java 源程序先编译成 Java
字节码( bytecode),创建为,class 文件;然后再由不同平台上的 Java 虚拟机( Java Virtual
Machine,JVM)解释执行。
友情提示
微软的.NET和Java编程系统都采用了一种称为虚拟机的机制来执行程序。编译器将Java、C#等程序设计语言的源程序翻译成中间程序(微软称之为中间语言),然后计算机里的虚拟机将中间程序作为应用程序来执行。
另一种翻译器是汇编程序,它用在以汇编语言编写的程序或程序组成部分。汇编语言也是一种程序设计语言,但它比其他类型的高级语言更接近于机器语言。在汇编语言中,一条语句通常可以翻译成机器语言的一条指令。今天,汇编语言很少用来编写整个程序,而是最经常地采用于程序员需要直接控制计算机某个方面功能的场合。
程序经常按照其功能被划分成很多较小的程序段去编写,每段代表整个应用程序的某个方面。各段独立编译之后,使用联接程序将所有编译好的程序段组合成一个可以执行的完整程序。程序的编译和联接过程简称为联编,很多语言的开发工具还提供了自动联编功能。
即使是熟练的程序员,也很少能保证程序第一次就能够正确运行,所以程序设计时经常需要使用调试程序来帮助查找程序错误,解决程序运行中存在的问题。调试程序能够在运行的程序中检测到一个事件,并向程序员指出该事件在程序由哪条代码的触发。
1.2 程序设计语言
在计算机科学中,程序设计语言是用来编写可被计算机运行的一系列指令 (计算机程序)
的人工语言。与英语等自然语言相类似,程序设计语言具有词汇、语法和句法。然而,自然语言不适合计算机编程,因为它们能引起歧义,也就是说它们的词汇和语法结构可以用多种方式进行解释。用于计算程序设计的语言必须具有简单的逻辑结构,而且它们的语法、拼写和标点符号的规则必须精确。
程序设计语言在复杂性和通用程度上大相径庭。 有些程序设计语言是为了处理特定类型的计算问题或为了用于特定型号的计算机系统而编写的。例如,FORTRAN 和 COBOL 等程序设计语言是为解决某些普遍的编程问题类型而编写的—— FORTRAN 为了科学领域的应用,而 COBOL 为了商业领域的应用。尽管这些语言旨在处理特定类型的计算机问题,但是
Java 程序设计大学教程
9
它们具有很高的可移植性,也就是说它们可以用来为多种类型的计算机编程。其他的语言,
譬如机器语言,是为一种特定型号的计算机系统,甚至是一台特定的计算机,在某些研究领域使用而编写的。最常用的程序设计语言具有很高的可移植性,可以用于有效地解决不同类型的计算问题。像 C,Java 和 BASIC 这样的语言就属于这一范畴。
作为在计算机上实现算法的工具,对于理想的程序设计语言来说,所提供的语法应该能够满足描述算法结构、数据、操作等各方面信息的需要。程序设计语言从最初的机器语言发展到今天流行的面向对象语言,语言的抽象程度越来越高,程序的风格越来越接近人类自然语言的风格,因此程序设计过程也越来越接近人类的思维过程。
1.2.1 发展历史
程序设计语言几乎可以追溯到 20 世纪 40 年代数字计算机发明之时。最早的汇编语言,
随着商业计算机的推出,出现于 20 世纪 50 年代末。最早的过程语言是在 20 世纪 50 年代末到 20 世纪 60 年代初开发的,FORTRAN 语言由约翰?巴克斯创造,然后由格雷斯?霍珀创造了 COBOL 语言。第一种函数式语言是 LISP,由约翰?麦卡锡于 20 世纪 50 年代末编写。这
3 种语言今天仍在广泛使用,但经历过大量修改。
20 世纪 60 年代末,出现了最早的面向对象的语言,如 SIMULA 语言。逻辑语言在 20
世纪 70 年代中期随着 PROLOG 语言的推出而变得广为人知; PROLOG 语言是一种用于编写人工智能软件的语言。后来尽管纯逻辑语言受欢迎的程度有所下降,但其变体形式,如关系型数据库所使用结构化查询语言( SQL)却变得越来越重要。
在 20 世纪 70 年代,过程语言继续发展,出现了 ALGOL,BASIC,Pascal,C 和 Ada
等语言。 SMALLTALK 语言是一种具有高度影响力的面向对象的语言,它导致了面向对象语言与传统过程语言相结合的一些语言,如 C++,Delphi 等。但是很快面向对象的方法在软件开发和程序设计中占据了主导地位,出现了像 Java 和 C#这样的纯粹面向对象的语言。
1.2.2 语言的类型
程序设计语言可划分为低级语言和高级语言。低级程序设计语言或机器语言,是程序设计语言中最基础的类型,能被计算机直接理解。机器语言的区别取决于制造商和计算机的型号。 高级语言是在计算机能够理解和处理之前必须首先翻译成机器语言的程序设计语言。 C、
FORTRAN,Pascal 和 Java 都是高级语言的例子。汇编语言是中级语言,非常接近于机器语言,没有其他高级语言所表现出的语言复杂程度,但仍然得翻译成机器语言。
1,机器语言
在机器语言中,指令被写成计算机能够直接理解的被称之为比特的 1 和 0 的序列。机器语言中的一条指令通常告诉计算机 4 件事情,
( 1)到计算机主存储器(随机访问存储器)的哪个位置去找一或两个数字或者简单的数据段;
( 2)要执行的一个简单操作,例如将两个数字加起来;
( 3)将这个简单操作的结果存放在主存储器的什么位置;
( 4)到哪里找要执行的下一条指令。
虽然所有的可执行程序最终都是以机器语言的形式被计算机读取,但是它们并非都是用机器语言编写的。直接用机器语言编程极端困难,因为指令是 1 和 0 的序列。机器语言中的
Java 程序设计大学教程
10
一条典型的指令可能是 10010 1100 1011,意思是将存储寄存器 A 的内容加到存储寄存器 B
的内容中。
2,高级语言
高级语言是利用人类语言中的词和语法的一套相对复杂的语句。 它们比汇编语言和机器语言更类似于正常的人类语言,因此用来编写复杂的程序更容易。这些程序设计语言允许更快地开发更庞大和更复杂的程序。然而,在计算机能够理解之前,高级语言必须由编译器翻译成机器语言。因为这个原因,与用汇编语言编写的程序比较起来,用高级语言编写的程序可能运行的时间更长,占用的内存更多。
3,汇编语言
计算机编程人员使用汇编语言使机器语言程序编写起来更简单一些。在汇编语言中,每条语句大致对应一条机器语言指令。汇编语言的语句是借助易于记忆的命令编写的。在典型的汇编语言的语句中,把存储寄存器 A 的内容加到存储寄存器 B 的内容中这一命令,可以写成 ADD B,A。汇编语言与机器语言具有某些共同特征。例如,对特定的比特进行操作,
用汇编语言和机器语言都是可行的。当尽量减少程序的运行时间很重要时,程序员就使用汇编语言,因为从汇编语言到机器语言的翻译相对简单。当计算机的某个部分必须被直接控制时,如监视器上的单个点或者流向打印机的单个字符,这时也使用汇编语言。
1.2.3 高级语言的分类
高级语言通常分为面向过程的、函数式的、面向对象的或逻辑的语言。当今最常见的高级语言是面向过程的语言。在这种语言中,实现某个完整功能的一个或多个相关的语句块组成了一个程序模块或过程,而且被给予诸如“过程 A”的名称。如果在程序的其他地方需要同样的操作序列,可以使用一个简单的语句调回这个过程。实质上,一个过程就是一个小型程序。可以通过将实现不同任务的过程组合在一起而构成一个大程序。过程语言使程序变得比较短,而且更易于被计算机读取,同时要求程序员将每个过程都设计得足够通用,以便能用于不同的情况,避免了重复编写相同的代码。近几十年来发展的一些高级过程化语言有:
FORTRAN,COBOL,Pascal,C 和 Ada 等。
函数式语言像对待数学函数一样对待过程,并允许像处理程序中的任何其他数据一样处理它们。这就使程序构造在更高、更严密的水平上得以实现。函数式语言也允许变量只赋值一次。这样就简化了编程,因为一个变量没有必要每次在一个程序语句中用到时都重新定义或重新赋值。函数式语言的许多观点已经成为许多现代过程语言的关键部分。这类语言代表性的有十九世纪六十年代由麻省理工大学开发的表处理解释语言 List。
面向对象的语言是函数式语言的发展结果。在面向对象的语言中,把数据和数据处理过程封装成叫做对象的单元。对象进一步抽象成类,而类则定义对象必须具有的属性。某些与对象相关的功能称为方法。计算机通过使用对象的某种方法来使用这个对象。方法对对象中的数据执行某个操作,然后将值返回给方法调用者。面向对象的语言中所提供的这种结构,
使面向对象的语言更符合人脑的思维模式,并有利于解决复杂的编程任务。像 Delphi,C++、
C#和 Java 都是目前主要的面向对象的语言。
逻辑语言将逻辑用作其数学基础,依据逻辑推理的原则响应查询。它是在由希腊数学家定义的规范的逻辑基础上发展而来的,并且后来发展成为一阶谓词演算( first-order predicate
calculus) 。一个逻辑程序由一系列的事实与“如果……则”规则组成,来具体说明一系列事
Java 程序设计大学教程
11
实如何可以从其他实事中推断出来,例如,
如果 X语句为真,则 Y语句为假。
在这样一个程序的执行过程中,一条输入语句可以按照逻辑从程序中的其他语句推断出来。许多人工智能程序使用这种语言编写。最著名的说明性语言是 Prolog( PROgramming in
LOGic),它是由法国人 A.Colmerauer 于 1972 年设计开发的。 Prolog 中的程序全部由论据和规则组成。
1.3 Java 语言介绍
Java 是 SUN 公司
1
开发的源于 Internet 的一种现代程序设计语言。多年来,SUN 公司对
Java 产品不断改进升级,使之紧跟时代步伐,满足了日益复杂的软件开发需求。
1.3.1 Java 发展的历史
Java 的历史可以追溯到 1991 年,那时候两个计算机奇才 Patrick Naughton 和 James
Gosling 带领着 SUN 公司的一个工程师小组,着手设计能够用于家电消费设备的小型计算机语言。由于这些设备没有很强的处理能力和太多内存,所以这个语言必须非常小并能够生成非常紧凑的代码。另外,因为不同厂商可能选择不同的 CPU,所以这个语言不能够限定在一个单一的体系结构下,也就是能够满足跨平台的要求。这个项目的名称是,Green” 。因此,
代码短小、紧凑、与平台无关便是 Java 雏形生来就具有的特性。
这个模型的商业实现是要设计一种能够为假想机器生成中间代码的可移植的语言,这个假想机器就是后来的 Java 虚拟机( JVM) 。这样中间代码可以在任何安装有正确解释器的机器上使用。 Green 项目的工程师们也就是通过使用虚拟机解决了他们的主要问题。
Gosling 起初把他的语言称为,Oak”,但是 SUN 公司的人发现已经存在一门名叫 Oak
的计算机语言。后来开发小组在一次咖啡馆聚会时,从咖啡的灵感中想到了,Java”这个名称,以至于流传至今,无人不知。
Green 项目并没有使 Java 在消费电子产品市场中取得成功。相反,Java 在 Internet 的浏览器上的应用却使其名声鹊起。当时 Patrick Naughton 和 Jonathan Payne 开发的浏览器演化为 HotJava 浏览器,并有在网页中执行 Java 代码的能力,从而引发了人们对 Java 的狂热。
SUN 公司在 1996 年早期发布了 Java 第一版。人们很快认识到 Javal.0 并不适合做真正的应用开发。它的后继者 Java 1.1 很快填补了前者的不足,极大地提高了反射能力并为 GUI
编程增加了新的事件模型。尽管如此,它仍然具有很大的局限性。
1998 年 12 月 Java 1.2 版本发布,SUN 公司将其称为 J2SE SDK1.2( Java2 标准版软件开发工具箱 1.2 版),以示与以前版本的明显不同。
除了 Java 的“标准版” J2SE 之外,SUN 公司还推出了两种其他的版本:用于蜂窝电话等嵌入式设备的“微型版”—— J2ME;用于服务器端处理“企业版”—— J2EE。本书使用的是标准版—— J2SE 5.0。
J2SE 5.0 是 Java 平台和语言最新的重要修订版。它包含了 15 个组件 JSR 以及由 Java
社团( Java Community Process,JCP)开发的其他将近 100 个重大更新。 Java 的版本号自从
1
SUN Microsystems Inc.:(美国)太阳微系统股份有限公司,主要生产 SUN 系列工作站和网络产品,是
JAVA 语言的创始者。 SUN 系 Stanford University Network 的首字母缩略。
Java 程序设计大学教程
12
1.1 开始,似乎就多少显得有点蹩脚。从 1.2 版本开始 Java 被称作 Java 2,而不是 Java 1.2,
现在则显得更加离奇,称为 Java? 2 Platform Standard Edition 5.0 或者 J2SE 5.0,而内部的版本号还是 1.5.0。据 SUN 公司的说法,从 Java 诞生至今已有 9 年时间,而从第二代 Java 平台 J2SE 算起也有 5 个年头了。在这样的背景下,将下一个版本的版本号从 1.5 改为 5.0 可以更好的反映出新版 J2SE 的成熟度、稳定性、可伸缩性和安全性。 Java 演化历史参见图 1-5。
图 1-5 Java 演化历史
1.3.2 Java 是什么,Java 不是什么
1,Java 是一种面向对象的程序设计语言
Java 首先是一种高级程序设计语言。它的优点包括了程序可读性强、可编译成平台无关的中间代码、严格的数据类型检查,以及可将应用程序分解成多个分布在各处的程序单元,
以便在计算机网络上运行。 Java 虽然用到了许多与 C/C++相同的语言结构,但其设计得足够简单,相对于 C/C++而言,用户能够更容易地熟练使用这种语言。
Java 是一种面向对象的语言。面向对象的语言将程序划分为单独的模块,称之为对象,
其中封装了程序的各种属性和动作。 Java 是一种纯粹的面向对象语言,不像 C++和 Delphi
那样是一种面向过程和面向对象都可以混杂使用的语言。 Java 带有许多库,可以用来构建面向对象的程序。因此,Java 能很好地支持面向对象编程( object-oriented programming,OOP)
与面向对象设计( object-oriented design,OOD) 。
2,Java 是健壮的和安全的语言。
Java 在设计之初就注重语言的健壮性,避免了一些不稳定的因素。例如:严格的数据类型检查,避免误用指针。 Java 还具有某些特点,能够在程序运行之前查出许多潜在的错误,
而它的优秀的异常处理能力使它可以在程序运行期间“捕捉”错误。因此,Java 程序中的错误不会像其他程序设计语言中的错误那样经常让系统崩溃。
Java 是安全的语言。因为 Java 是设计在网络上使用的,他有保护免遭不安全代码(不
Java 程序设计大学教程
13
可信任的,可能引入病毒或破坏系统的代码)侵害的一些特性。例如,基于 Web 的 Java 程序下载到浏览器中以后,它们的行为就受到了严格限制。实际上用户下载 Java 程序比下载可执行文件,ActiveX 控件更加安全。
3,Java 是平台无关的语言。
这里的平台是指特定类型的计算机系统,如 Unix 或 Windows 系统。 Java 的口号是“一次编写,到处运行。,这意味着 Java 程序不用修改就可以在不同类型的计算机系统上运行。
Java 主要靠 Java 虚拟机( JVM)在目标码级实现平台无关性。 JVM 是一种抽象机器,
它附着在具体操作系统之上,本身具有一套虚机器指令,并有自己的栈、寄存器组等。但
JVM 通常是在软件上而不是在硬件上实现。 (目前,SUN 系统公司已经设计实现了 Java 芯片,主要使用在网络计算机 NC 上。另外,Java 芯片的出现也会使 Java 更容易嵌入到家用电器中。 ) JVM 是 Java 平台无关的基础,在 JVM 上,有一个 Java 解释器用来解释 Java 编译器编译后的程序。 Java 编程人员在编写完软件后,通过 Java 编译器将 Java 源程序编译为
JVM 的字节代码。任何一台机器只要配备了 Java 解释器,就可以运行这个程序,而不管这种字节码是在何种平台上生成的。 另外,Java 采用的是基于 IEEE 标准的数据类型。 通过 JVM
保证数据类型的一致性,也确保了 Java 的平台无关性。任何其他高级程序设计语言都不是这样的,而这也是 Java 适合 Internet 应用的一个原因。
Java 是分布式的语言,这意味着可以将其程序设计为在计算机网络上运行。它包含了一些特性和代码库,使得构建 Web 应用程序特别容易。这就是 Java 非常适合于企业网络应用的原因之一。
4,Java 不是专用于 Internet 的语言
Java 是编程语言,不是设计网站的 HTML 语言的扩展,而 HTML 是描述网页结构的方式。除了在 HTML 中可以嵌入 Java applet 之外,两者没有任何共同之处。
Java 也不是 XML 的扩展。 而 XML 是 Internet 网上广泛使用的描述数据的语言,可以用任何语言处理 XML 数据,只不过 Java API 对 XML 处理提供了更好的支持。
尽管 Java applet 都是运行在 Web 浏览器中,但并不意味着 Java 程序一定要在浏览器中运行。实际上人们大量编写的是独立的、不依赖浏览器运行的 Java 程序。这些程序通常称为 Java 应用程序,他们可以移植的到不同的平台上独立运行。另外 Java 通过 JDBC( Java
Database Connectivity,Java 数据库连接)等数据库访问工具的支持还可以开发数据库应用程序。毫无疑问,Java 特别适合网络编程,但不妨碍他在其它领域的应用。
5,Java 不是一个集成开发环境
Java 不像 Delphi 或 VB 那样集成有一个功能强大的集成开发环境 ( IDE) 。 即使 Java SDK
也不是个容易使用的编程环境——除非用户习惯于命令行工具。尽管一些软件商为 Java 提供了各种集成开发环境,其中整合了编辑器、编译器、拖放设计器以及便利的调试工具。但它们都很复杂,对初学者并不适合。这些集成开发环境动辄生成数百行代码,复杂的功能会使初学者晕头转向,还不如使用简单的文本编辑器来学习 Java 更有效。在本书中,我们虽然介绍一些优秀的集成开发环境,但我们仍然推荐使用文本编辑器来学习 Java。
6,Java 不是万能的编程语言
从理论上讲,没有一种语言比另一种语言更加优越。语言的差异只是在效率上,这里的效率包括语言的编程效率和语言的运行效率。
Java 只不过是一种编程语而已。很多程序员喜欢 Java,认为 Java 是一种胜过其它语言
Java 程序设计大学教程
14
的好的编程语言。但是,实际上有上百种好的编程语言从来没有获得广泛的流行,而一些有明显缺点的语言,比如 C++和 Visual Basic 却大行其道。
这是为什么呢?这是因为编程语言的成功更多来自于该语言以外的支持。例如:是否有操作系统厂商的支持?有帮助功能强大,方便好用的标准库?有工具厂商生产好的编程和调试环境吗?该语言及其相关工具能否和计算机的其他基础结构整合在一起?
Java 的成功就来自于各方面得天独厚的支持,但者并不意味着 Java 是万能的语言。 Java
还有一些自身的弱点,比如 Java 依赖虚拟机解释执行,因此它对于某些实时处理或 CPU 敏感的任务而言就显得运算太慢。
1.3.3 下载 JDK 搭建 Java 平台
1,Java 平台
Java 平台由 Java 应用编程接口( API)和 Java 虚拟机( JVM)构成,如图 1-6 所示。
这个平台构成了 Java 在不同计算机系统中编译和运行的环境。
图 1-6 Java 平台与计算机系统
Java 应用编程接口是已编译的可在任何 Java 程序中使用的代码库 (即 Java 类库) 。 它们作为可定制的现成功能可以随时添加到我们自己的 Java 程序中,以节约编程时间,避免重复劳动。
Java 程序必须由由 Java 虚拟机来运行。也就是说,Java 程序并不是在本机操作系统上直接运行,而是由 JVM 向本机操作系统解释执行。所以,没有安装 JVM 的计算机系统是无法运行 Java 程序的。
2,下载 JDK
为了搭建 Java 平台,需要下载和安装 Java 软件开发工具箱 ( Java Development Kit,JDK),
并设置 Java 编译和运行环境。
JDK 包含了所有编写、运行 Java 程序所需要的工具,Java 基本组件、库,Java 编译器、
Java 解释器,Java applet 浏览器,以及一些用于开发 Java 应用程序的程序等。 其核心 JavaAPI
Java 程序
Java 虚拟机
Java APIs
计算机系统
Java 平台
(安装 JDK)
Java 程序设计大学教程
15
是一些预定义的类库,开发人员需要用这些类来访问 Java 语言的功能。 JavaAPI 包括一些重要的语言结构以及基本图形、网络和文件 I/O。作为 JDK 实用程序,工具库中有 7 种主要程序,
Javac Java编译器,将 Java 源代码转换成字节码。
Java Java解释器,直接从类文件执行 Java 应用程序字节代码。
Appletviewer 小程序浏览器,一种执行 HTML 文件上的 Java 小程序的 Java 浏览器。
Javadoc 根据 Java 源码及说明语句生成 HTML 文档。
Jdb Java调试器,可以逐行执行程序,设置断点和检查变量。
Javah 产生可以调用 Java 过程的 C 过程,或建立能被 Java 程序调用的 C 过程的头文件。
Javap Java反汇编器,显示编译类文件中的可访问功能和数据,同时显示字节代码的含义。
Sun 公司为 Solaris,Linux 和 Windows 提供了 Java 2 标准版( J2SE)最新、最完全的版本
1
。本书使用的 JDK 是 J2SE Development Kit 5.0 Update2(即 JDK1.5.0_02),读者可以到以下官方网站免费下载,
http://Java.sun.com/j2se/downloads/index.html
在这个网站,读者还可以阅读到 JDK 在不同平台上的安装指南。 JDK 在 Windows 中的安装界面如图 1-7 所示。
图 1-7 在 Windows 中 JDK 的安装界面
安装完 JDK 后,我们可以查看到如图 1-8 所示的文件目录结构。在进行 Java 开发时,
1
本书写作时的 Java 2 标准版 JDK 最新版本是 1.5.0_02
Java 程序设计大学教程
16
我们可能偶尔会察看一下这些被安装的文件,寻找我们需要的资源。注意,根据所安装的
JDK 版本不同,该目录结构可能会有一些变化。
图 1-8 JDK 的安装后的目录结构
JDK 发布的库源文件压缩在 src.zip 中,如果要看源代码,必须首先解压该文件。 src 目录包含了 Java 库公开部分的源代码。当对 Java 熟悉到一定程度的时候,会发现要想知道更多的 Java API 内容就得去深入研究 Java 的源代码了。例如,如果对 System 类的内部工作机制感到好奇,就可以到 src/java/lang/System.java 源代码中看个究竟,了解那些库函数到底是如何工作的。
3,设置路径
安装完 JDK 后,我们还需要进一步设置路径,即把 jdk/bin 目录加入到执行路径中,该路径是操作系统寻找本地 Java 运行坏境的目录列表。在不同的操作系统上,该步骤也各不相同。
在 Windows 95/98/ME 中,需要在 AUTOEXEC.BAT 文件末尾添加以下一行代码,并重启操作系统,
SET PATH=c:\jdk\bin;%PATH%
在 Windows NT/2000/XP 中,打开控制面板,选择系统,环境变量,在用户变量窗口中找到 PATH 变量,然后在路径的开头添加 jdk\bin 目录,使用分号把新加的条目和以前的分开,如下所示,
C:\jdk\bin;(其它条目)
保存设置。并通过下面的步骤测试路径设置是否正确,
启动一个 shell 窗口,具体方法取决于你的操作系统。输入下面一行命令,
java -version
编泽器及一些工具
(这里以可执行文件为主)
演示程序一些示例程序用于本地方法的文件
库文件库源文件的各个子目录
(通过 src.zip 解压缩生成 )
Java 运行时环境文件
Java 程序设计大学教程
17
然后按回车键,应该能够看到如下输出,如图 1-9 所示,
Java version,XXX”
Java(TM) 2 Runtime Environment,Standard Edition (builder XXX)
Java HotSpot(TM) Client VM·(builder XXX)
图 1-9 测试 Java 版本,检查路径设置是否正确
如果得到的输出的是诸如,
Java,command not found
Bad command or filename
Error,could not find java.dll
Error,could not find Java 2 Runtime Environment,
之类的信息,就需要返回去检查安装过程是否有问题了。
1.4 Java 程序的编写、编译和运行
对于简单的 Java 程序,我们可以通过任何文本编辑器来编写代码,然后用命令行工具进行编译和运行。但是这样使用 JDK 比较麻烦。我们还可以考虑使用集成了 JDK 的文本编辑器来编写、编译和运行简单的 Java 程序,比如 TextPad,JEdit 或是别的一些工具。
对于大型项目开发,我们可以使用功能强大的集成开发环境 ( IDE),比如免费的 Eclipse;
或是其他商业产品,如,JBuilder。
下面,我们以一个最简的 Java 程序 HelloWorld 来说明 Java 程序是如何编写、编译和运行的。
1.4.1 使用命令行工具
打开 Windows 的记事本,新建一个文本文件。编写如示例程序 1-2 所示的 Java 程序代码。并将这段代码保存为名为 C:/MyJava/HelloWorld.java 的文件。
示例程序 1-2 最简单的 Java 程序 HelloWorld
1,// The First Java Program
2,
3,public class HelloWorld {
4,public static void main(String[] args){
Java 程序设计大学教程
18
5,System.out.println("Hello World! ");
6,}
7,}
有两种方法可以编译和执行 Java 程序,使用命令行或者其他集成了 JDK 的程序及 IDE。
我们首先使用最基本的命令行方法。
如果是 Windows 2000/XP,通过选择菜单:开始 ->所有程序 ->附件 ->命令提示符,打开一个命令提示符窗口。如果是 Windows 98/ME,则打开一个 MS- DOS 窗口。
然后进入 C:/MyJava 目录,输入如下命令,
javac HelloWorld.java
java HelloWorld
屏幕上应该出现如图 1-10 所示的信息。此时表明我们已经成功编译和运行了第一个简单的 Java 程序!
通过我们刚才的操作,我们可以看到 javac 程序作为 Java 的编译器把 HelloWorld.java
文件编译成 HelloWorld.class 文件。 javac 程序作为 Java 解释器,则负责解释执行编译器生成到 class 文件中的字节码。
HelloWorld 程序非常简单。它只是在命令提示符窗口(通常称为控制台)上输出一条问候世界的消息,Hello World!,。这是所有程序设计语言用来第一次试写程序的惯例。这段程序代码的工作机制我们将在后续章节中讲述。
图 1-10 使用命令行工具编译和运行 Java 程序
这是我们保存的源代码文件
C:/MyJava/HelloWorld.java
编译 HelloWorld 程序
运行 HelloWorld 程序
这是编译后得到的字节码文件,HelloWorld.class
Java 程序设计大学教程
19
易犯错误
使用命令行工具手工输入命令时一定要特别仔细。任何输入错误都可能导致无法出现图 1-10所示的正确信息。例如输入javac HelloWorld或java
HelloWorld.java都是常见的错误。另外,JDK路径设置不对也会导致令人沮丧的结果。
1.4.2 使用 Java 编辑器 TextPad
使用像 JEdit 和 TextPad 这样的 Java 编辑器,不但可以编写 Java 程序,还可以在编辑器内部编译和执行代码,使其成为一个轻量级的 Java 开发环境。从而避免了繁琐的和易出错的命令行操作。
TextPad 是一个集成了 JDK 的 Java 程序编辑器,是 Windows 上深受 Java 程序员喜欢的一个优秀的共享文本编辑器。 TextPad 容易使用,方便快捷,适合初学者使用。我们用其开发和测试了本书中的大部分程序。该软件可以到以下网站免费下载使用,
http://www.textpad.com
在 TextPad 中编辑 HelloWorld.java 程序如图 1-11 所示。 然后通过菜单项,Tools->Compile
Java 或者使用快捷键 CTRL+1 编译程序。如果程序有问题导致编译失败,编译错误信息会出现在一个 Command Results 窗口中,如图 1-12 所示。
实际编程中,即使是熟练的程序员,程序一次性编译成功的也不多。编译后程序如果出现错误,根据 Command Results 窗口中的出错信息可以帮助我们找出程序中的 bug,对程序进行重新修改,直到编译后不再出现错误为止。
假设在我们刚才写 HelloWorld 代码时,第 5 行不慎漏掉了一个引号,
System.out.println("Hello World! );
此时程序编译将无法成功,Command Results 窗口中出错信息如图 1-12 所示。鼠标双键该错误信息或者按回车键,系统会自动切换到 HelloWorld.java 程序中的相关代码行,以便我们更正错误。
Java 程序设计大学教程
20
图 1-11 在 TextPad 中编写 HelloWorld.java 程序
图 1-12 当编译程序无法成功时,Command Results 窗口中出现编译错误提示信息
一旦编译通过,可以通过菜单项,Tools->Run Java Application 或者使用快捷键 CTRL+2
编译程序运行这个 Java 程序。
通常在一个复杂的程序能够运行起来后还要进行大量的调试和测试,具体方法我们后面专门章节会详细介绍。
1.4.3 使用集成开发环境 NetBeans IDE
高级语言及其编译器通常与一些必要的程序开发工具集成在一起,形成所谓的集成开发环境( IDE) 。在 Java 的大型项目开发中,IDE 中就可以完成程序设计、资源重用、代码编
Java 程序设计大学教程
21
译、调试测试等一系列任务。尤其是图形用户界面( GUl)技术出现后,高级语言开发环境的界面更加友好,使程序设计进入到可视化编程时代。
目前流行的 Java 集成开发环境有 10 多种,本教材选用的是获得 Developer 网站 2004 年度开源工具大奖的 NetBeans IDE 4,主要考虑到以下理由,
NetBeans IDE 4 是开放源码的 Java 集成开发环境,读者可以免费下载使用。
NetBeans IDE 4 是 Sun 公司最新发布的,也是支持新的 J2SE 平台 5.0 版的第一个
IDE。 Sun 公司将该软件和 J2SE 5.0 打包在一起,两者可以一并下载和安装,十分方便。
NetBeans IDE 4 主要针对普通的 Java 编程,而不是复杂的企业级应用,简单好用。
国际版还提供了简体中文界面。
NetBeans IDE 4 还是第一个将其项目系统整个建立在 Apache Ant(基于 Java 的一种编译工具)上的 IDE。调试、测试和运行都很方便。
下面我们将介绍 NetBeans IDE 4 基本使用方法。 通过学习,读者可以尝试应用 NetBeans
IDE 4 平台进行 Java 程序的编辑、调试及运行。
1,下载和安装
NetBeans IDE 4.1 和 J2SE 5.0 打包在一起的 Windows 版下载文件是
jdk-1_5_0_06-nb-4_1-win-ml.exe,大小约为 102MB。官方下载网址是,
http://java.sun.com/j2se/1.5.0/download-netbeans.html
要在 Microsoft Windows 计算机上安装 NetBeans IDE,,最好使用自解压的安装程序,
并请执行以下步骤,
1、在下载安装程序文件后,双击安装程序的图标以启动安装向导。
2、指定 NetBeans IDE 的安装目录。
3、指定将运行 NetBeans IDE 的 JRE( Java 运行环境,如果已有 JDK 则不必另外下载了)。
安装成功后,双击桌面上的,NetBeans IDE” 图标,或者从,开始” 菜单中,选择,NetBeans
IDE 4->NetBeans IDE”启动 NetBeans IDE,进入如图 1-13 所示的中文界面。
Java 程序设计大学教程
22
图 1-13 启动 NetBeans IDE 后进入的界面
2,创建 Java 项目
项目的创建是使用 NetBeans IDE 4 编写程序的第一步,一个项目由 Java 源文件及其相关项目文件组成,包括属于类路径的对象,以及生成和运行项目的方式等等。 相关项目文件,
诸如:控制生成和运行设置的 Ant 生成脚本和属性文件,以及将 Ant 目标映射到 IDE 命令的 project.xml 文件等,都必须存储在项目文件夹中,由开发环境进行管理。但用户的 Java
源文件可以不位于项目文件夹中。如果项目已经存在,可以通过工具条上的打开项目按钮或主菜单中的文件 ->打开项目菜单打开,否则必须新创建。
新建项目具体步骤如下,
1) 在 NetBeans IDE 4 中,通过工具条上的新建项目按钮 或主菜单中选择菜单项:
“文件 ->新建项目”,系统将弹出新建项目向导。要创建一个 Java 常规项目,在如图 1-14
所示的向导类别中选择其中的“常规”,以及项目栏的,Java 应用程序”,然后单击下一个 >
按钮,进入下一步。
2) 在接下来如图 1-15 所示的对话框中设置项目名称和文件保存位置。输入所要编写的项目名称,将来生成的项目文件夹与此同名。我们在,项目名称,栏输入的是 JBookCh1。
点击浏览按钮设置项目文件夹及相关文件的保存位置。
3) 将,设置为主项目” 和,创建主类” 复选框保留选中状态。 输入 jbookch1.HelloWorld
作为主类。然后单击完成按钮完成项目向导。
4) 如果要选择其他项目类型,可以在如图 1-14 所示的向导中选择。这里有多种项目可供选择。
Java 程序设计大学教程
23
图 1-14 选择 Java 项目创建向导
图 1-15 设置项目名称和文件保存位置
3,创建和编辑 Java 程序
Java 是面向对象的语言,在 Java 程序中一切都是对象,所有的 Java 程序都是从创建 Java
Java 程序设计大学教程
24
类开始的。
前面我们在创建项目时已经选中“设置为主项目”和“创建主类”的复选框,并输入
jbookch1.HelloWorld 作为主类。所以向导会自动生成一个 Java 程序框架的代码,其中创建了一个 HelloWorld 类,并将这个类放在 jbookch1 包中,如图 1-16 所示。
图 1-16 编辑窗口中自动生成的程序代码框架
如果要在已有的项目中创建一个 Java 类,我们可以通过工具条上的新建文件按钮 或主菜单中选择菜单项:,文件 ->新建文件”,系统将弹出新建文件向导。如图 1-17 所示。
jbookch1 包节点
HelloWorld 类节点
程序代码框架
Java 程序设计大学教程
25
图 1-17 新建文件向导
图 1-18 新建 Java 类对话框
选中类别框中的,Java 类”,并在文件类型框中选择,Java 类”,如果要创建带程序运行的入口方法—— main 方法的主类也可以选择,Java 主类” 。
Java 程序设计大学教程
26
然后单击下一个 >按钮,将会出现如如图 1-18 所示的新建 Java 类对话框。
也可以在主界面上的项目视图中,鼠标右键点击 jbookch1 包节点,通过如图图 1-19 所示的上下文菜单项:,新建 ->Java 类”,来调出如图 1-18 所示的新建 Java 类对话框。
图 1-19 使用上下文菜单项
为了编写如示例程序 1-2 所示的 HelloWorld 程序,接下来,我们只要在 main 方法中填入以下一段代码,就可以实现前面那个问候世界的简单 Java 程序。
System.out.println("Hello World! ");
NetBeans IDE 4 的源代码编辑器有很多强大的功能,可以使编写代码更简单、更快捷的功能,如:输入代码提示功能、代码重构功能、自动生成一些代码段、代码完成、突出显示编译错误、突出显示代码元素的语法以及代码格式化和搜索功能。
NetBeans IDE 4 的源代码编辑器在我们输入代码的过程中能够不断提示可能匹配的类、
方法和变量等的名称,并通过一个列表来提供选择,帮助自动完成表达式,如图 1-20 所示。
这种提示甚至还包含一个 Javadoc 预览窗口,其中显示代码完成框中的当前选择的 Javadoc
文档(如果存在),这样极大地方便了对 Java 类库不太熟悉的初学者。
Java 程序设计大学教程
27
图 1-20 代码提示功能
为了节约时间,对于常用的代码我们可以输入简单的缩写,然后按空格键,使其展开为符合要求的完整代码。 例如,如果输入 sout 并按空格键,则它将展开为 System.out.println("")。
要获取系统缺省缩写的完整列表,请在帮助菜单中选择“快捷键卡”进行查看。
为了使程序代码易于更改或更易于添加新功能,或者为了降低代码复杂性,以便于理解
NetBeans IDE 4 还提供了代码重构的功能。简单地讲,所谓重构就是使用很小的变换进行代码改进,其结果不会更改任何程序行为。 NetBeans IDE 4 提供的代码重构包括:查找使用实例、移动、重命名、封装字段、更改方法参数以及撤消重构。
NetBeans IDE 4 在编写代码时可以自动将代码格式化。无论你的代码格式如何糟糕,只要使用 Ctrl-Shift-F,就可以自动重新格式化整个文件的代码,使其规范、漂亮。
如果我们不慎敲错了代码,此时该程序行左边会立即显示一个带 X 的红色警示图形。
鼠标点击该图形,立即可以得到一个错误修正建议,帮助我们改正程序。借助 NetBeans IDE
4 编辑器的这项功能,可以在代码编辑阶段就发现和修正很多错误,不必等到程序编译时,
这样极大地提高了编程作效率。
4,编译和运行 Java 程序
使用 NetBeans IDE 4 可以方便地在 IDE 中编译和运行 Java 程序。在主菜单中选择菜单项:,运行 ->运行主项目”,或者按 F6,可以直接运行主项目中设计的 Java 应用程序。在主菜单中选择菜单项:运行 ->调试主项目,或者按 F5,可以调试项目中的 Java 应用程序。
如果要运行某个 Java 文件,可以在该文件的编辑窗口中按鼠标右键,选择“运行文件”
菜单项,或者按 Shift-F6 组合键。
图 1-21 是在 IDE 中运行的问候世界的 Java 程序。
Java 程序设计大学教程
28
图 1-21 在 IDE 中编译和运行 Java 程序
1.4.4 优秀 Java 开发工具介绍
在众多流行的 Java 开发工具中,Borland 公司的 JBuilder 是其中优秀一款。它满足多方面的应用,尤其适合企业级应用的大型项目开发。
JBuilder 支持最新的 Java 技术,包括 Applets,JSP/Servlets,JavaBean 以及 EJB 的应用。
JBuilder 还支持各种应用服务器,可以快速开发 J2EE 的电子商务应用。由于 JBuilder 是用纯 Java 语言编写的,其代码不含任何专属代码和标记,它支持最新的 Java 标准。 JBuilder
拥有最专业的 IDE 和图形调试界面,支持远程调试和多线程调试,调试器支持各种 JDK 版本,包括 J2ME/J2SE/J2EE。
JBuilder 环境开发程序方便,它是纯的 Java 开发环境,适合 J2EE 开发。其缺点是过于庞大,不宜上手,新手难于把握整个程序各部分之间的关系;对机器的硬件要求较高,比较吃内存,运行速度显得较慢。 JBuilder 的 IDE 如图 1-22 所示。
显示运行结果
编译程序
Java 程序设计大学教程
29
图 1-22 JBuilder 的 IDE 中可以完成与程序开发相关的一系列任务
除了 JBuilder 这样的商业软件,我们还可以找到一些免费的优秀 Java 集成开发环境,
Eclipse 就是其中的佼佼者,深受 Java 程序员的喜爱。
Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括 Java 开发工具( Java Development Tools,JDT) 。
由于 Eclipse 中的每样东西都是插件,这种平等和一致性并不仅限于 Java 开发工具。
Eclipse 的用途并不限于 Java 语言; 例如,支持诸如 C/C++等编程语言的插件已经可用,Eclipse
框架甚至还可用来作为与软件开发无关的其他应用程序类型的基础。
UI 设计器
组件面板
代码编辑器
项目管理器
对象浏览器
Java 程序设计大学教程
30
Java 开发工具( JDT)是随 Eclipse 平台一起交付的全功能的 Java IDE。 JDT 是对工作台的一组扩展,它允许用户编辑,编译和运行 Java 程序。 最简单的方法是将 JDT 看作和 JBuild
有着很多类似的 Java 程序开发工具。但是随着对它的深入了解,读者会发现可以通过不断添加功能强大的新的 Eclipse 插件来扩展 JDT 的功能,从 UML 建模到可视化 GUI 的开发,
众多的插件使其成为最有发展前景的编程工具,这完全得益于 Eclipse 插件式的开放体系结构。
Eclipse JDT 作为 Java 的集成开发环境包括:菜单、工具栏、代码编辑器、包资源管理器、大纲以及各种观察窗口等,如图 1-23 所示。
图 1-23 功能强大的免费 Java 集成开发环境( IDE)—— Eclipse
使用 Eclipse 进行 Java 编程,不但可以省去许多文件创建、编译和调试中的麻烦,还能得到许多智能化的协助,避免出错,大大地提高编程效率,而且随着应用的深入,你将会发现编程不再是枯燥无味的工作—— Java 的每一个设计细节,都将带给你乐趣。
Eclipse 是一款非常受开发人员欢迎的 java 开发工具,国内的用户越来越多。它的缺点是比较复杂,对初学者来说,理解起来比较困难。
读者可以从 http://www.eclipse.org/中下载这个软件。 Eclipse 是用 Java 开发的,可以在安装了 Java 2 运行时环境( JRE)的任何平台上运行。
现在比较常用的其他 Java 开发环境还有,Sun Java Studio,VisualAge for Java、
JDeveloper,Visual Cafe,IntelliJ Idea,JCreator 等,这些开发工具各有千秋,为 Java 程序员提供了多样的选择。
1.5 本章习题
复习题
[习题 1] 什么是程序设计?
[习题 2] 当今的计算机是基于什么模型?
菜单
包资源管理器
大纲
代码编辑器
工具栏
透视图
工具栏
代码辅助功能 各种观察窗口
Java 程序设计大学教程
31
[习题 3] 为什么不称计算机为数据处理器?
[习题 4] 计算机程序设计语言是如何分类的?
[习题 5] 简述计算机程序设计的过程。
[习题 6] Java 是什么?
[习题 7] Java 平台由哪几部分组成?各有什么作用?
测试题
[习题 8] 以下关于计算机程序的描述不正确的是 。
A、计算机程序是指按照时间顺序依次安排的计算机工作步骤。
B、简单地讲,计算机程序也可以表示成:算法 +数据结构。
C、计算机程序是对计算步骤的编排和优化。
D、计算机程序是计算任务的处理对象和处理规则的描述。
[习题 9] 现代的内部存储计算机程序的概念是由 提出来的。
A、美籍匈牙利数学家约翰?冯?诺伊曼
B、英国数学家奥古斯塔?埃达?拜伦
C、法国发明家约瑟夫
D、英国数学家和发明家查尔斯?巴比
[习题 10] 以下关于算法不正确论述的是 。
A、算法必须是一组定义完好且排列有序的指令集合。
B、一个算法可以产生一个结果也可以没有结果。
C、一个算法必须能够终结。
D、算法的每一步都必须有清晰明白的定义。
[习题 11] 算法的基本结构组成不包括 。
A、顺序结构
B、选择结构
C、递归结构
D、循环结构
[习题 12] 数据的存储结构中,常见的存储映像方式是 。
A、顺序方式、链接方式、索引方式、递归方式
B、顺序方式、链接方式、索引方式、列表方式
C、顺序方式、链接方式、索引方式、指针方式
D、顺序方式、链接方式、索引方式、散列方式
[习题 13] 以下程序设计语言在历史上出现的正确顺序是 。
A,FORTRAN,BASIC,Java,C#
B,FORTRAN,C,Java,BASIC
C,LISP,BASIC,Java,C++
D,COBOL,Java,Pascal,C
[习题 14] 以下的应用程序中,不能将源代码翻译成目标代码是 。
Java 程序设计大学教程
32
A、编译器
B、解释器
C、汇编程序
D、虚拟机
[习题 15] 程序设计语言通常分为 。
A、面向过程的、函数式的、面向对象的或逻辑的语言
B、机器语言、汇编语言、高级语言
C、数据库语言、通用语言、嵌入式语言
D、可视化语言、非可视化语言
[习题 16] 以下可用作 Java 集成开发环境的开放源代码软件是 。
A,TextPad
B,J2EE
C,NetBeans IDE
D,JBuilder
[习题 17] Java 是 推出的一种面向对象的现代程序设计语言。
A,SUN 公司
B,Borland 公司
C,IBM 公司
D,Microsoft 公司
练习题
[习题 18] 按照书中 Java 程序的编写、编译和运行步骤,试编写一个简单的 Java 程序,在屏幕上打印出自己的名字。
[习题 19] 你所知道的程序设计语言有哪些?你知道它们有什么特点吗?
[习题 20] 上网搜索一下,找出除了本书介绍的 Java 编程工具以外的其它可用于 Java 编程的
IDE 产品,看看他们各有什么好的功能。
[习题 21] 上网搜索一下,找出最近 10 年来新出现的程序设计语言,了解为什么人们要推出这些新的语言,并讨论程序设计与语言的关系。
[习题 22] 一种计算机程序设计语言有 10 种不同的指令。如果指令不重复,在这种语言能编出多少 5 条指令的程序?又能编出多少 7 条指令的程序?
Java 程序设计大学教程
80
第 3章 面向对象与对象模型
面向对象的程序设计( OOP)已成为现代软件开发的必然选择。通过掌握面向对象的技术,能开发出复杂、高级的系统,这些系统是完整健全的,但又是可扩充的。 OOP 是建立在把对象作为基本实体看待的面向对象的模型上的,这种模型可以使对象之间能相互交互作用。
面向对象程序设计在一个好的面向对象程序设计语言( OOPL)的支持下能得到最好的实现。 Java 就是一种优秀的 OOPL,它提供了用来支持面向对象程序设计模型所需的一切条件。 Java 有自己完善的对象模型,并提供了一个庞大的 Java 类库,并有一套完整的面向对象解决方案和体系结构。
在如今,程序设计的重点和方法绝对是面向对象的。尽管这本书不是、也不可能是关于面向对象方法学的百科全书,但通过学习 Java 来迈进面向对象程序设计的殿堂可谓是一条捷径。
3.1 面向对象的概念
如果说面向过程符合电脑顺序工作的“思维”方式,那么面向对象则更符合人脑的思维方式,因此面向对象程序设计并不难学习掌握。国外专家的研究表明,对于从来没有接触过程序设计的初学者而言,他们学习面向对象编程要比那些资深计算机教授学习起来更容易。
因为面向对象程序设计不需要借助指令,寻址,流程,指针等与计算机运行机制有关的概念,
而是建立在符合人类思维的更抽象的客观世界模型之上,并以此来解决问题。
建立面向对象的概念有助于我们理解和掌握面向对象程序设计。 不要被不熟悉的术语或面向对象的神秘感所吓倒。面向对象程序设计很值得花功夫去学习。
3.1.1 面向对象基本原理
面向对象方法学是面向对象程序设计技术的理论基础。基于该理论基础,人类不但创造出与人类思维方式和手段相对应的面向对象程序设计语言,而且使得程序开发过程与人类的认知过程同步,通过对人类认识客观世界及事物发展过程的抽象,建立了规范化的分析设计方法,由此使程序具有更好的封装性、可读性、可维护性、可重用性等一系列优点。
根据人类对客观世界的认知规律、思维方式和方法,面向对象方法学对复杂的客观世界进行如下抽象和认识,
( 1)客观世界(事物)由许多各种各样的实体组成,这些实体称为对象。例如:园丁、
香蕉、橘子等。
( 2)每个对象都具有各自的内部状态和运动规律,在外界其他对象或环境的影响下,
对象本身根据发生的具体事件做出不同的反应,进行对象间的交互。例如:园丁种植香蕉就是园丁与香蕉两个对象之间的交互,而香蕉具有生长、收获等运动规律。
( 3)按照对象的属性和运动规律的相似性,可以将相近的对象划分为一类。例如:香蕉、橘子这一类对象可以划分为果树类。
( 4)复杂的对象由相对简单的对象通过一定的方式组成。例如:汽车对象可以由发动
Java 程序设计大学教程
81
机、轮子、底盘等多个对象组成。
( 5)不同对象的组合及其间的相互作用和联系构成了各种不同的系统,构成了人们所面对的客观世界。例如:投资商、园丁、香蕉、橘子等不同对象组合及其相互作用构成了一个果园系统,运营该系统可完成投资、生产、盈利等功能。
上述第( 1)项说明构成客观事物的基本单元是对象;第( 2)项说明对象是一个具有封装性和信息隐藏的模块;第( 3)项说明可以通过相似性原理将对象分类,从那些相似对象的属性和行为中抽象出共同部分加以描述;第( 4)项说明客观事物的可分解性及可组合性,
而第( 5)项则说明由对象组合成系统的原则。
将上述 5 项基本观点形式化就可以得到面向对象语言的主要语法框架。 如果根据上述基本观点,结合人类认知规律制定出进行分析和设计的策略、步骤,就产生了面向对象的分析与设计方法。
客观世界是由许多不同种类的对象构成的,每一个对象都有自己的运动规律和内部状态,不同对象之间相互联系、相互作用。面向对象技术是一种从组织结构上模拟客观世界的方法,从组成客观世界的对象着眼,通过抽象,将对象映射到计算机系统中,又通过模拟对象之间的相互作用、相互联系来模拟现实客观世界,描述客观世界的运动规律。
传统的面向过程的功能分解法属于结构化分析方法,分析者将对象系统的现实世界看作为一个大的处理过程,然后将其分解为若干个子处理过程,直至将整个系统分解为各个易于处理的过程为止,然后解决系统的总体控制问题。在分析过程中,用数据描述各子处理过程之间的联系,整理各个子处理过程的执行顺序。 这种方法缺乏对问题的基本组成对象的分析,
不够完备,尤其是当需求功能变化时,将导致大量修改,不易维护。
面向对象技术以基本对象模型为单位,将对象内部处理细节封装在模型内部,重视对象模块间的接口联系和对象与外部环境间的联系,能层次清晰地表示对象模型。
面向对象方法则从根本上对问题域中的对象及其关系进行详尽的分析,并在此基础上完成需求功能,力求使对系统的修改和增加功能变得很容易,修改时不至于对系统结构产生大的影响。
面向对象技术有着自身鲜明的特点。首先要搞清什么是对象。客观世界中对象是形形色色的,常可以划分成不同类,不同类的对象又是千差万别的。例如自然界中的对象是看得见摸得着的各类实体,而各类生产活动中的对象则是处理或控制过程,程序设计中的对象却是数据结构等。把所有这些概括为对象,不难看出他们有以下几个共同特点,
某类对象是对现实世界具有共同特性的某类事物的抽象。
对象蕴含着许多信息,可以用一组属性来表征。
对象内部含有数据和对数据的操作。
对象之间是相互关联和相互作用的。
面向对象技术,正是利用对现实世界中对象的抽象和对象之间相互关联和相互作用的描述来对现实世界进行模拟,并且使其映射到目标系统中。所以面向对象的特点主要概括为抽象性、继承性、封装性和多态性。
抽象性——指对现实世界中某一类实体或事件进行抽象,从中提取共同信息,找出共同规律,反过来又把它们集中在一个集合中,定义为所设计目标系统中的对象。
继承性——新的对象类由继承原有对象类的某些特性或全部特性而产生出来,原有对象类称为基类(或超类、父类),新的对象类称为派生类(或子类),派生类可以直接继承基类的共性,又允许派生类发展自己的个性。继承性简化了对新的对象类的设计。
封装性——是指对象的使用者通过预先定义的接口关联到某一对象的服务和数据时,无需知道这些服务是如何实现的。即用户使用对象时无需知道对象内部的运行
Java 程序设计大学教程
82
细节。这样,以前所开发的系统中已使用的对象能够在新系统中重新采用,减少了新系统中分析、设计和编程的工作量。
多态性——是指不同类型的对象可以对相同的激励做出适当的不同相应的能力。 多态性丰富了对象的内容,扩大了对象的适应性,改变了对象单一继承的关系。
3.1.2 建立面向对象的思维
对象概念对程序设计中的问题求解和模型建立都具有莫大的好处,在设计优秀合理的情况下尤其如此。现代软件工程强调只编写一次代码而在今后反复重用,而在非面向对象的情况下程序员往往不得不在应用程序内部各个部分反复多次编写同样的功能代码。所以说,面向对象编程有利于减少编写代码的总量,从而加快了开发的进度同时降低了软件中的错误量。
用来创建对象的代码还可能用于多个应用程序。比方说,一个团队可以编写一组标准类库来提供可利用的计算资源,然后开发人员通过将需要的类实例化为具体的对象或通过继承来创建这些类的派生类从而实现代码的重复使用,减少编程工作量。
OOP 的另一优点是对代码结构的影响。像继承之类的面向对象概念通过简化变量和函数的方式而方便了软件的开发过程。 OOP 可以更容易地在团队之间划分编码任务。同时,
由于采用 OOP,辨别派生类代码的依附关系也变得更简单了(比如说继承对象的代码) 。此外,软件的测试和调试也得以大大简化。
但是 OOP 也存在一些固有的缺点。假如某个类被修改了,那么所有依赖该类的代码都必须重新测试,而且还可能需要重新修改以支持类的变更。还有,如果文档没有得到仔细的维护,那么我们很难确定哪些代码采用了基类(被继承的代码) 。假如在开发后期发现了软件中的错误,那么它可能影响应用程序中的相当大部分的代码。面向对象编程在编程思想上同传统开发不同,需要开发人员转变传统开发中所具备的惯性思维方式。
下面我们就看看程序设计中面向对象的思维究竟是如何思考问题的。
对象是建立面向对象程序所依赖的基本单元。用更专业的话来说,所谓对象就是一种代码的实例,这种代码执行特定的功能,具有自包含或者封装的性质。这种封装代码通常叫做类,或者模块,或者在不同程序设计语言中所应用的其他名称。以上这些术语在含义上稍微有些不同,但它们本质上都是代码的集合。
正如上面提到的那样,对象本身是类或者其他数据结构的实例。这就是说,现有的物理代码起到了创建对象的模板作用。执行特定功能的代码只需要编写一次却可以被引用多次。
每一种对象具有自己的标识,也就是让对象相互区别的对象名称。
对象并不是类的实际拷贝。每一对象都有自己的名称空间,在这种名称空间中保存自己的标识符和变量,但是对象要引用执行函数的原有代码。
“封装”的对象具有自己的函数,这种函数被称作“方法”,而对象的属性则被称为“字段” 。 当对象内部定义了字段的时候,它们通常不能扩展到实例以外。 假设现有一个类叫 Fruit
(水果),如果我们把这个类实例化,就可以创建诸如 banana(香蕉)和 grape(葡萄)这样的对象。如果 Fruit 类有一个 size(大小)字段,那么,Fruit 类实例化以后给 banana 对象设置的 size 字段值就不会影响到 grape 的 size 字段值。 Fruit 类自身的 size 字段并没有设置的意义,因为 Fruit 类只是一种模板,只有对于具体的水果实例,如,banana(香蕉)和 grape(葡萄),这个 size 字段才有意义。
在特定的场合下,有些方法和字段确实会影响类而不是由类所创建的实例对象,在 Java
中,他们称为静态方法和字段。静态字段指的是专门设计来用于对象之间共享的值。类的静
Java 程序设计大学教程
83
态方法则用来定义和跟踪静态字段。
在面向对象的程序里,首先要声明对象的类,并通过实例化的方式构造对象。对象可以用其字段来表示状态(数据),用方法来表示操作(对数据的处理) 。当需要某个对象时它可以被创建;当对象不再需要时可以被清除。换言之,对象是有生死的,有生命期的。
用于创建对象的类有一种功能强大的特性,这就是它们可以继承其他类。如果我们在程序设计中不关心水果的具体实例(例如:这种水果是葡萄还是苹果),而是关心葡萄的具体实例(例如:这种葡萄是巨峰葡萄还是马奶子葡萄) 。我们不妨编写一个 Fruit 类的派生类
Grape(葡萄)类,那么它就可以继承 Fruit 类而使我们不必重复编写已经存在的功能,比如:
size 字段。 Grape 类可以使用从 Fruit 类中继承下来的方法和字段。以此类推,Fruit 还可以继承它的祖先类 Food(食品)类。
某些面向对象程序设计语言还具有多重继承的概念。比如说,Grape 类可以继承 Fruit
和 Vine(藤本植物)类。不过这样可能会产生一些问题,比如两个父类都具有名称相同的一些类成员(方法和字段),一旦继承下来如何区分?在具体处理多重继承概念的时候各种语言的方式是不同的,某些语言完全禁用这一概念。 Java 虽然禁用多重继承,但仍然可以通过接口技术来实现类似的功能。
在继承了类后,我们可以通过覆盖( override)方法来获得希望的结果。假设 Fruit 类可能有一个方法名叫 prepare,该方法主要指导如何对收获的水果进行出售前的加工准备,比如将葡萄制成葡萄干,将苹果冷藏。所以在实例化 Grape 类的时候,希望从 Fruit 类继承的
prepare 方法包含与葡萄有关的特殊定义。于是在 Grape 类中的 prepare 方法,它的名字虽然和 Fruit 类一样,但却覆盖( override)了原有方法的行为。如果没有覆盖 prepare 方法,就会用到 Fruit 类中的 prepare 方法 (可能 Fruit 类中的 prepare 方法是把水果都做成水果罐头) 。
这就叫多态性 ( polymorphism) 。 多态性是一种重要的面向对象概念,再结合继承就使得 OOP
在程序设计中能发挥出强大的作用。
面向对象强调从问题域的概念到软件程序和界面的直接映射;心理学的研究也表明,把客观世界看成是许多对象更接近人类的自然思维方式。对象比函数更为稳定;软件需求的变动往往是功能相关的变动,而其功能的执行者对象通常不会有大的变动。另外,面向对象的开发也支持、鼓励软件工程实践中的信息隐藏、数据抽象和封装。在一个对象内部的修改被局部隔离。面向对象开发的软件易于修改、扩充和维护。
现在,面向对象技术已经扩充应用到软件生命周期的各个阶段——从分析建模到编码测试。今天我们要建造的那些软件系统已经不再是一个简单的黑盒应用。这些复杂系统通常包含由多个子系统组成的层次结构。而面向对象技术的使用有力地支持了开放系统的建设,减小了开发复杂系统所面临的危险,使得软件系统的集成有了更大的灵活性。
3.1.3 UML 和对象建模
前面我们大致了解了程序设计中面向对象的思维究竟是如何思考问题的,但这还远远不够,因为我们还要能够把面向对象的思考通过可以描述的方式表达出来,也就是说我们需要能够建立面向对象的求解模型。
1,模型与建模
模型提供了一个物理系统的抽象,模型可以让工程师们忽略无关的细节而把注意力集中到系统的重要部分来进行思考。几乎各种工程中的所有工作形式都依赖于模型来理解复杂的、真实世界的系统,在软件开发中也不例外。软件模型除了用于系统设计还可以用在很多
Java 程序设计大学教程
84
的方面,例如预期系统的质量、当系统的某些方面变化时推理特定的属性、测试关键的系统特征等。模型可以先开发出来用于指导物理系统的开发,或者也可以由一个已存在的系统或者开发中的系统产生,作为理解系统行为、优化系统实现的手段。
从软件建模技术的发展过程中,人们认识到建模语言是一种图形化的文档描述性语言,
利用它可以解决在软件建模过程中存在的沟通障碍问题。例如在面向对象建模中,通常我们用一个矩形框表示类,一个园角矩形表示对象——类的实例,如图 3-1 所示。其中该图右边是一个称为正方形的类,它包含边长、位置、边界颜色和内部颜色等属性,以及画图、插图和移动等方法。
图 3-1 面向对象建模中,类和对象的表示方法
由此可见,面向对象建模中使用了一些带有图形符号的建模语言,就好像是五线谱中的音符一样。如图 3-2 所示,软件建模如同五线谱谱曲一样是为了便于表达和交流。如果没有五线谱,音乐家如何去表达灵感?任何行业要发展和交流都必须有一种大家公认的标识符把思想表达出来,程序员也一样。
Java 程序设计大学教程
85
图 3-2 软件建模如同五线谱谱曲一样是为了便于交流
特定行业的专业人士与 IT 行业的专业人士在沟通上的最大障碍就是行业术语,而要解决这个问题,最有效的方式就是要寻找到一种公共语言来进行交互,这是建模语言所要起到的最为重要的作用。同时,建模语言必须支持的是一种思考复杂问题所必须采用的思维方式
(抽象、分解、求精),并且提供特定问题的特定描述方式,利用不同的图形从各个方面对系统的全部或部分进行描述。
2,统一建模语言 UML
UML 是统一建模语言( The Unified Modeling Language)的英文缩写,UML 是一个通用的可视化建模语言,用于对软件进行描述,可视化处理,构造和建立软件系统制品的文档。
它可以把人们对所需要构建系统的想法和理解记录下来,以便用于对系统的分析、设计、研究、配置和维护。 UML 适用于各种软件开发方法、软件生命周期的各个阶段、各种应用领域以及各种开发工具,是一种总结了以往建模技术的经验并吸收当今优秀成果的标准建模方法。
用 UML 进行面向对象的建模是一个比较复杂的话题,然而这本书并不是专门介绍 UML
建模的读物。书中不少地方使用到了 UML 建模,主要是考虑到 UML 有利于帮助读者建立规范的面向对象思维的表达方式。我们现在简单介绍一下这本书中所使用的,也是 UML 中最常用的类图。
类图主要是由类及类间相互关系构成,这些相互关系包括,关联,泛化和各种依赖关系,
以及这些关系的使用和实现。类图是以类为中心来组织的,类图中的其他元素或属于某个类或与类相关联,所以称其为类图。
图 3-3 是一个果园系统的 UML 类图。其中浆果类( Berry),热带水果类( TropicalFruit)
柑橘类( CitruseFruit)是水果类( Fruit)的派生类。他们之间的关系是泛化关系,也就是继承关系;用带有三角形箭头的线段表示,由派生类指向基类。
关联关系
泛化关系
(继承关系)
字段
方法
类名称
可见性
( - # + )
Java 程序设计大学教程
86
图 3-3 一个果园系统的 UML 类图
这里还有一个园丁类( Gardener),它有一个种植水果的方法 plant,该方法返回一个种植的水果。显然,Gardener 通过 plant 方法与 Fruit 进行关联,因此这两个类之间存在关联关系,用带有折线箭头的线段表示。但是很多情况下这种关系不像泛化关系有很强的方向性,
所以有时只用线段连接,而无需箭头导航。
3,使用建模工具
早在程序设计初期,建模在软件工程中就有着悠久的传统。 UML 的出现,为建模提供了更完善和更通用的符号。 UML 允许开发团队在相应的模型中获取一个系统的各方面的重要特征,但这些模型之间的转换主要是手工进行的。
建模工具的出现提高了建模的生产力。 建模在软件开发中真正体现价值的应用是通过双向工程得到的。 双向工程提供了一种在设计模型和实现代码之间进行双向交换的机制。 通常,
开发人员将系统设计细化到一定的详细级别,然后通过应用模型——代码的转换创建第一轮的实现,这通常是手工完成的。例如,一个工作在系统设计级别的团队也许会向工作在代码实现级别上的团队提供设计模型,他们也许是通过简单的打印出模型图或者为实现团队提供包含模型的文件来进行的。代码实现团队将抽象的设计模型转换成编程语言的实现。由于其中难免存在错误,双方在各自更改错误和完善系统中常常会因为设计模型和实现代码的不一致而产生冲突,降低开发效率,甚至导致无法合作。
一个优秀的建模工具应该能够完成模型与代码之间的自动转换,也可以保持设计模型与实现代码在变更时的步调一致。好的工具还可以帮助用户进一步细化设计模型,生成代码的框架,并在应用的生命周期,维护代码与模型的一致。目前适用于 Java 的 UML 建模工具很多,他们不仅可以实现 UML 建模,还可以将模型变成 Java 代码,将 UML 建模直接转化为生产力,例如,Borland Together Developer for JBuilder2005。图 3-4 演示了该软件的一个建模工作界面。
Java 程序设计大学教程
87
图 3-4 Java 建模工具 Borland Together Developer for JBuilder2005 的工作界面
3.2 类
类是对具有共同实现的一些对象或一系列对象的描述。通俗地讲,如果不同的对象可以根据其类似的属性特征和行为操作划分成不同类型的话,那么决定他们属性特征和行为操作的东西就是类。例如:对于园丁所种植的香蕉、橘子、葡萄等不同的水果对象,它们有共同的名称属性,以及种植和收获的操作,因此可将它们划归为果园系统中的水果类。
3.2.1 什么是 Java 类
当用户创建一个面向对象程序时,是如何建立对象的呢?当然是通过类。类是用来创建对象的模板,类抽象出具体对象的相似性,定义它们的共同特征,包括数据和操作。我们可以通过类声明来定义类,然后使用类来创建用户需要的对象。类声明是用来创建对象的抽象规格说明。当用户编写自己的 Java 程序时,主要工作就是编写类。当程序运行时,已声明的类用来创建新对象。由类创建对象的过程称为实例化( instantiation),每个对象都是类的一个新实例( instance) 。
类是一种抽象数据类型,在 Java 中类也被当作一个数据类型来定义,它的语法是,
语法与规则
class 类名 extends 基类 {
//类的函数成员
构造函数 1;
构造函数 2;
,.,
方法 1;
方法 2;
,.,
//类的数据成员
字段 1;
字段 2;
,.,
}
类的语法结构包括关键字 class、跟在后面的类名称。如果其继承自某个基类,还需要使用 extends 关键字加基类名称。类成员位于类体中,并分成了两大类——数据成员、函数成员,并用 {}包括。另外,我们在前面的 HelloWorld 程序中介绍过,所有的类及类的成员都有访问修饰符关键字来限定其可访问性。
需要说明的是,类通常不需要从头生成。相反,他们可以从其他的类派生而来,继承祖先类的可用类成员,包括:字段、方法等。即使是从头创建的新类也必须是继承自 Object
Java 程序设计大学教程
88
类,只不过我们可以合法省略 extends Object 而已。 在 Java 中,Object 类是所有类的根。 Object
类定义在 java.lang 包中,它是所有 Java 类的基类,即 Java 的任何类都是 Object 类的派生类。
java.lang 包可由编译器自动加入,无须手工导入( import) 。
3.2.2 类成员
类成员包括数据成员和函数成员。数据成员是面向对象的术语,用于表示类中的数据变量,即 Java 中的字段( field) 。函数成员也是面向对象的术语,用于表示类中的操作。 Java
的函数成员包括方法和构造函数,
方法是一些封装在类中的过程和函数,用于执行类的操作,完成类的任务。
构造函数是一种特殊的方法,用于对象的创建和初始化。
图 3-5 形象地显示了一个 Gardener 类的语法结构及其从模型到实现的对应关系。在这个类中,数据成员包括,1 个 String 类型变量 gardenerName 以及 3 个 Fruit 类型变量 banana、
grape 和 orange。函数成员包括,3 个方法 plant(),work()和 gain(); 1 个构造函数 Gardener()。
友情提示
类及类的成员都有访问修饰符关键字来限定其可访问性。UML类图中的类成员可访问性标志为:-表示private(私有),+表示public(公有),#
表示protected(保护)。
Java 程序设计大学教程
89
package jbookch2.gardensys;
public class Gardener {
public Gardener(String name) {
gardenerName = name;
}
public void work() {
System.out.println(gardenerName
+ "开始工作 ");
plant();
gain();
}
private void plant() {
System.out.println(
"——————————————————————————");
banana = new Fruit("香蕉 ",1000);
banana.grow();
banana.harvest();
grape = new Fruit("葡萄 ",2000);
grape.grow();
grape.harvest();
orange = new Fruit("橘子 ",3000);
orange.grow();
orange.harvest();
System.out.println(
"——————————————————————————");
}
private void gain() {
int sum = 0;
//统计收益
sum += banana.gain();
sum += orange.gain();
sum += grape.gain();
System.out.println("果园总收益," + sum);
}
private String gardenerName;
private Fruit banana,grape,orange;
}
类名称
函数成员
(方法)
数据成员
(字段)
-




+




UML 类图
NetBeans IDE 的
Java 类结构视图
从模型到实现
类成员
构造函数
Java 程序设计大学教程
90
图 3-5 Gardener 类的语法结构
3.2.3 类成员的可访问性
类成员包括数据成员和函数成员,类的每个成员都有一个称为可访问性的属性,用来保护类成员。 Java 有四种类成员的保护方式,分别为缺省的,public(公有的),protected(保护的),private(私有的) 。它们决定了一个类成员在哪些地方以及如何能被访问,private 表示最为隐秘的访问程度,protected 和缺省的表示中等程度的访问能力,public 表示最大程度的访问能力。类成员的可访问性属性通过访问修饰符关键字限定,如表 3-1 所示。
表 3-1 Java 访问修饰符关键字的访问权限范围
限访问修饰符关键字 同一个类中 同一个包中 派生类中 其他包中
public √ √ √ √
protected √ √ √
无访问修饰符关键字 √ √
private √
缺省情况下,也就是说不需要任何访问修饰符关键字时,类成员的可访问性是友好的,
此时只要是同一个包中的其他成员都可以访问它。
private 成员仅在该类的方法中被访问,它的派生类和实例都无法访问。通过私有成员的限制,可以更好地封装和保护自己的类;清楚地向用户表明,他们无需关心这些与他们无关的细节。
protected 成员在其类所在的包中是随处可用的。 protected 成员在它的派生类中也是可用的,即使它的派生类出现在别的包中。 protected 成员和 private 成员的本质区别在于类的继承上,就是说 protected 成员仍然可以通过继承在子类中访问到该成员,而 private 成员仅供自己的类使用。
public 成员是完全可访问的成员,可访问性最大。虽然该成员使用方便,不受限制,但在编程中不能滥用。通常在设计中应该保持 public 成员的简明,并尽早定义使之稳定。因为
public 成员作为公共接口显然是会在很多地方被用到,设计不慎既会对使用该接口的其他类带来影响,又会威胁到其自身类的封装性。
为了使程序具有良好易读的风格,建议最好在撰写类代码时用不同的可访问性来组织类成员,并将 public 成员作为对外公布的接口放在最前面,以便引起关注。
3.3 方法
方法是类的函数成员,他们决定了类的行为。
3.3.1 什么是方法
方法是在类中定义,用来实现对象操作的过程或者函数。方法是属于一个给定对象的过程和函数,方法反映的是对象的行为而不是数据。方法是类的成员,通过设置保护方式即可访问性来确定它的调用范围。 Java 程序中,方法的声明和方法的实现是不分开的。
示例程序 3-1 是一个水果类 Fruit 的程序代码,该程序保存在 Fruit.java 源文件中。 Java
Java 程序设计大学教程
91
程序员习惯将每个类放在一个独立的源文件中,并使用这个类的名称作为文件名,.java 作为扩展名。图 3-6 显示了 Fruit 类的 UML 类图及其类成员结构组成。
图 3-6 Fruit 类的 UML 类图及其类成员结构组成
Fruit 类有 2 个可访问性为 private 的数据成员 input 和 fruitName,分别用于存储水果的投入和水果的名称,因为该私有成员仅供在 Fruit 类内部使用,所以受到严格的保护。 Fruit
类的函数成员包括:构造函数 Fruit 和 3 个方法 grow,harvest 和 gain,他们是公有成员,可访问性为 public,可以被外部其他对象调用。 grow 和 harvest 方法实现水果生长和收获的操作。 gain 方法计算并返回种植水果的收益(这里假设收益是投入的两倍) 。
Java 中的构造函数是一种用于创建类的实例的特殊方法,它使用与类名称相同的名称作为方法名。我们还可以在构造函数中实现一些初始化的工作,例如在构造函数 Fruit 中,我们为 input 和 fruitName 字段赋初值。如果我们没有在类中撰写自己的构造函数,编译器会代为创建一个没有参数的隐含构造方法,以保证这个类可以创建。
通常方法可分为有返回值和没有返回值的两种,类似于传统程序中的函数和过程。有返回值方法必须标明返回值的类型。例如 int gain()表明 gain 方法返回值是整数类型,该值是通过方法中的 return 关键字将变量 g 的值作为返回值。
没有返回值方法必须标明 void 关键字。例如 grow,harvest 等使用 void 关键字的方法则无返回值。
示例程序 3-1 Fruit 类的定义及实现代码
1,public class Fruit {
2,public Fruit(String fname,int in) {
3,fruitName=fname;
4,input=in;
5,}
6,
7,public int gain() {
8,int g=input*2;//收益是投入的两倍
9,return g;
10,}
11,
12,public void grow() {
Java 程序设计大学教程
92
13,System.out.println(fruitName+"生长,..");
14,}
15,
16,public void harvest() {
17,System.out.println(fruitName+"收获,..");
18,}
19,
20,private int input;
21,private String fruitName;
22,}
方法与传统的函数和过程不同,是因为方法只应用于特定类及其祖先类的对象。另外,
每一个方法都有一个隐含的参数,称为 this,它引用作为方法调用主体的对象,这也是普通函数和过程所没有的。例如在一个园丁类 Gardener 的 work 方法中所调用的 plant 和 gain 方法就都有一个隐含的参数 this,
public void work() {
System.out.println(gardenerName
+ "开始工作 ");
plant();//plant有一个隐含的参数 this,因此可以写成 this.plant()
this.gain();//标出参数 this这样写可以提醒自己区别 Fruit的 gain方法,
//或其他同名变量。
}
于是外部程序通过 Gardener 的实例对象 gardener 来调用这个方法时,即,
gardener.work();
就会执行到 work 方法中的以下指令,
gardener.plant();
gardener.gain();
其中的 gardener 就是通过隐含参数 this 传递进 work 方法中的。
调用方法要以对象实例作为方法名称的开头,例如在一个园丁类 Gardener 的 plant 方法中就创建了一个水果对象 fruit,并调用该水果对象的 grow,harvest 和 gain 方法,
private void plant() {
,.,
banana = new Fruit("香蕉 ",1000);//创建 Fruit的实例对象 banana
banana.grow();//调用 banana 对象实例的 grow方法
banana.harvest();//调用 banana对象实例的 harvest方法
,.,
}
Java 程序设计大学教程
93
显然从这段程序代码中我们不难看出园丁类 Gardener 的种植方法 plant 实现了以下操作,
创建香蕉对象(香蕉是 Fruit 的一个实例)
香蕉生长
香蕉收获
由此可见,面向对象的程序设计更符合人脑的思维特性,易于阅读和维护。
3.3.2 方法参数
方法除了都有一个隐含的参数 this 外,还可以根据需要使用显式参数。例如作为构造函数的 Fruit 方法就使用了 String 类型的 fname 参数和 int 类型的 in 参数,如示例程序 3-1 的
4-7 行所示。
所谓参数是对象之间通过方法传递和交换数据的通讯基础。 所有方法名称后都带有一个参数列表 ( parameter list) 。 参数列表是一个参数声明序列,多个参数声明之间以英文分号,,”
隔开,参数列表由圆括号封装。参数声明的格式为类型标识符后跟参数名,参数名必需是有效标识符。即使方法不带任何参数,也要带一个空的参数列表,例如 harvest()。
在方法定义中,参数称为形参( formal parameter),它指定了参数的类型和顺序。例如
Fruit(String fname,int in)参数列表中定义并在方法内部使用的 fname 和 int 就是形参。 虽然形参的数目是不受限制的,但为了降低方法访问的复杂性,建议尽可能减少形参的数目。
当方法被访问时,参数列表指定了必需传递给方法的参数的个数、顺序以及类型,此时称为实参( actual parameter) 。例如以下语句中的 fruitName 和 fruitInput 就是来访问 Fruit 方法的实参。
int fruitInput=1000;
String fruitName="香蕉 ";
banana = new Fruit(fruitName,fruitInput);
程序运行时,将根据参数的位置匹配实参和形参。此时实参必须和其对应位置上的形参类型相符。在方法内部,形参可以被作为局部变量使用,无需另外再进行声明(事实上编译器也不允许这样) 。
在 Java 中,方法的参数如果是基本数据类型,参数则采用传值调用;如果是对象数据类型,参数则采用传引用调用。
传值调用是一种传递实参值副本的调用方式。 此时方法得到的是一个保存在形参中的原实参变量的值的副本。在方法中对该形参变量的操作或修改不会影响到原来实参变量的值。
传引用调用传递的不是变量值(对象)的副本,而是这个变量引用(对象引用)的副本。
此时通过参数传递,形参变量和实参变量共同引用了同一个对象,所以相当于传递了一个指针。于是当通过形参变量改变对象的状态时(例如改变对象某一字段的值),其变化也会通过原实参变量对该对象的引用反映出来。因为对于对象数据类型的参数,方法在传递参数时不会复制并传递一个新的对象。
Java 程序设计大学教程
94
3.3.3 静态字段和静态方法
前面我们讲到的字段和方法,都是通过类的实例来访问(调用)的,称为实例字段和实例方法。实例字段和实例方法是属于某一具体实例对象的字段和方法,必须先创建这个实例对象,然后才能使用这些字段和方法。对于同一个类创建的不同的实例对象,其字段可以有不同的取值,以反映该对象的不同状态。
除此之外,还有一种通过类就可以直接访问的静态字段和静态方法,这种静态的字段和方法用 static 关键字标识,不需要创建实例就可以通过类直接访问。下面我们就来进一步讨论。
1,静态字段
在类中用 static 关键字定义的静态字段是类的公共属性不是某个实例对象的个别属性。
也就是说静态字段是这个类的所有实例所共享的,这个类的任何实例改变了静态字段的值,
都会反映在其他实例中。
例如,我们在 Fruit 类中增加一个静态字段 fruitObjs 作为实例对象的计数器,并修改构造函数如下,
public class Fruit {
public Fruit(String fname,int in) {
fruitName=fname;
input=in;
fruitObjs ++;//创建对象时计数
}
private static int fruitObjs;//实例对象计数器
.....,
}
这样,每次创建一个 Fruit 类的实例对象时,fruitObjs 的值就不断累加,实现计数器功能。因为 Fruit 类的任何实例对象都可以共享并操作该静态字段。
2,静态方法
对于作为实例对象计数器的静态字段 fruitObjs,我们可以通过任意实例对象来访问。但是这样做容易让人迷惑,因为静态字段 fruitObjs 与 banana,orange,grape 等具体的实例对象没有什么关系,它属于所有实例对象,属于类。因此我们可以通过一个与实例对象无关的静态方法来访问它,
public class Fruit {
public Fruit(String fname,int in) {
fruitName=fname;
Java 程序设计大学教程
95
input=in;
fruitObjs ++;//创建对象时计数
}
public static int geFruitObjs(){
return fruitObjs;
} //访问实例对象计数器的静态方法
private static int fruitObjs;//实例对象计数器
.....,
}
这样我们需要察看实例数目时,无须通过实例而可以直接通过 Fruit 类来访问
geFruitObjs 方法,
private void plant() {
System.out.println("——————————————————————————");
banana = new Fruit("香蕉 ",1000);
banana.grow();
banana.harvest();
grape = new Fruit("葡萄 ",2000);
grape.grow();
grape.harvest();
orange = new Fruit("橘子 ",3000);
orange.grow();
orange.harvest();
System.out.println("——————————————————————————");
System.out.println("实例数,"+Fruit.geFruitObjs());//显示实例数为 3
}
由此可见,静态方法不是对类的实例的操作,而是对类自身的操作。因此,静态方法中只能出现静态字段和其他静态方法,否则无法通过编译。
前面我们讲过的 main 方法就是一个著名的静态方法,我们用它实现程序的运行入口。
调用 main 静态方法不需要使用任何对象,因为程序开始运行时还不存在任何实例对象。而正是该方法执行并构造了程序所需要的首个对象。
现在我们给 Gardener 类加上一个 main 方法,这样我们就可以测试运行一个简单的果园系统应用程序。这是我们用 Java 编写的第一个面向对象的应用程序,模拟了一个叫张三的园丁种植 3 种水果的过程。 Gardener 类最后实现的源代码如示例程序 3-2 所示。
示例程序 3-2 Gardener 类的定义及实现代码
1,public class Gardener {
2,
3,public static void main(String[] args) {
Java 程序设计大学教程
96
4,Gardener gardensys = new Gardener("张三 ");
5,gardensys.work();
6,}
7,
8,public Gardener(String name) {
9,gardenerName = name;
10,}
11,
12,public void work() {
13,System.out.println(gardenerName + "开始工作 ");
14,plant();
15,gain();
16,}
17,
18,private void plant() {
19,System.out.println("——————————————————————————");
20,banana = new Fruit("香蕉 ",1000);
21,banana.grow();
22,banana.harvest();
23,grape = new Fruit("葡萄 ",2000);
24,grape.grow();
25,grape.harvest();
26,orange = new Fruit("橘子 ",3000);
27,orange.grow();
28,orange.harvest();
29,System.out.println("——————————————————————————");
30,}
31,
32,private void gain() {
33,int sum = 0;
34,int g = 0;
35,//统计收益
36,g = banana.gain();
37,System.out.println(banana.fruitName + "投入 " + banana.input
38,+ " 净收益 " + g);
39,sum += g;
40,g = orange.gain();
41,System.out.println(orange.fruitName + "投入 " + orange.input
42,+ " 净收益 " + g);
43,sum += g;
44,g = grape.gain();
45,System.out.println(grape.fruitName + "投入 " + grape.input
46,+ " 净收益 " + g);
47,sum += g;
Java 程序设计大学教程
97
48,System.out.println("果园总收益," + sum); }
49,
50,private String gardenerName;
51,private Fruit banana,grape,orange;
52,
53,}
编程技巧
实际上每个类都可以有一个main方法。通过给一个类增加一个main方法是对类进行单元测试的技巧之一。因为有了main方法,就可以单独运行和测试这个类,测试完成后可以将其main方法注释掉,并不影响整个系统。
例如,对于Fruit类的测试,我们就可以通过为其增加以下main方法进行,
public static void main(String[] args) {
Fruit banana = new Fruit("香蕉 ",1000);
banana.grow();
banana.harvest();
banana.gain();
}
3.4 对象
要掌握 Java 编程,仅仅学会程序设计的语法是远远不够的。如果不了解面向对象程序设计的机制和对象活动的规律,那么就只能陷于,山穷水尽疑无路” 的境地,很难上升到,柳暗花明又一春”的美妙境界。
3.4.1 理解对象
面向对象程序设计中的对象与客观世界中的对象并不是完全相同的概念,他们既有联系又有区别。所以深刻理解编程中的对象本质将有利于实践面向对象编程和开发。
单纯的结构化思维方式对客观世界的反映不太自然和直接,不利于解决关系复杂的问题。 因此通过理解对象的本质,将有利于彻底地改变这种思维方式,以适应面向对象的编程。
下面我们就从几个方面来理解 OOP 中对象的本质,
对象可以视为一组相关的操作代码和数据的组合 对象封装了方法和数据,并提供外部调用的接口,如图 3-7 所示。 这使得对象可以作为一个独立的整体单元安全使用,维护了自身的完整性和可操作性。在面向对象程序设计中,过程和函数被称作方法,数据被称作属性。在对象模型中,可以用属性来表示对象的内容或状态,
用方法来表示对象的操作。理论上,可以把所有要解决的问题分解成程序中的各个对象,由他们自己解决各自的问题。当然,对象的粒度划分仍然是面向对象编程中的难题。
对象是类的实例 对象可以视为神奇的变量,它相当于“类”类型的变量。
Java 程序设计大学教程
98
对象可以互相协作,共同完成任务 对象之间可以通过发送消息请求而相互联系,
在消息请求中可以调用方法。 面向对象程序设计中的一条典型的代码可以是一个对象发送的一条消息,消息由对象的名字后跟它的方法来表示。一个消息通常由三部分组成:接收对象的名字、对象方法的名字和方法成参数。即,对象,方法(参数) ;
通过继承,组合或封装等方式可以产成新的对象 这种方式产生的新对象不仅最大限度地实现了代码的重用,并以此在程序中构建复杂的体系,将系统的复杂性隐匿于对象的简易性之中。
对象根据特定的意义和用途有不同的划分方法 程序中的对象既可以划分账单对象、收银员对象这样的实体对象,也可以划分安全对象、协调对象、事务对象这样的功能对象。通常我们可以按照界面和逻辑分开的原则,将系统划分为系统逻辑对象和用户界面对象。
图 3-7 对象是一个整体,封装了对外部世界隐藏的数据
3.4.2 使用对象
1,一个面向对象的果园系统
假设我们要开发一个最简单的果园系统 GardenSys。用面向对象的方法来分析,这个果园系统至少包括园丁和水果这两个对象。园丁的工作包括种植各种水果,水果的行为包括生长和收获,不同的水果还有不同的名称。园丁种植水果,园丁与水果存在关联关系。这个程序的面向对象设计类图如图 3-8 所示。
Java 程序设计大学教程
99
图 3-8 GardenSys 果园系统的类图
根据类图的设计,我们可以分别撰写出 Fruit 类的代码和 Gardener 类。 Fruit 类的代码在前面示例程序 3-1 中已经给出,Gardener 类的代码如示例程序 3-2 所示。 Gardener 类中,
显示了园丁的工作是种植香蕉、葡萄等水果。 plant 和 gain 方法是私有方法,说明它只能在
Gardener 类中内部使用; work 方法是公有的,它可以对外公开,它调用了私有的 plant 和 gain
方法作为其工作的一部分。 Gardener 的 gardenerName 是私有的,该信息对外部是隐匿的,
不能随意修改。由此可见,一个设计良好的类可以很好地封装功能,控制对成员的访问,从而保护数据,最大可能地避免了有危害性的操作。
Java 是一种面向对象语言,而面向对象程序设计的基本特征在于对象交互。因此 Java
程序可以看成是多个交互对象的集合,这些对象之间使用消息进行通信来实现交互过程。我们编写的 GardenSys 果园系统程序虽然是由 Gardener 和 Fruit 这两个类组成,但是却产生了
4 个交互对象,分别是 gardener,banana,grape,orange。因此 GardenSys 果园系统程序是这 4 个交互对象集合。他们的交互过程可以由 UML 顺序图和协作图来表示,分别如图 3-9
和图 3-10 所示。
顺序图给出了一组对象之间发生的交互顺序,标出了涉及对象以及这些对象之间传递的消息类型。顺序图的水平轴表示不同的对象,垂直轴表示时间。顺序图用带标签的矩形表示对象,对象可以接受和发送消息。每个对象下方显示一个虚线标出的对象生命线,表示该对象的生存时间。 生命线上包括有一些矩形的对象活动线,表示对象某个交互动作的活动时间。
对象与对象生命线之间的箭头表示对象间的消息发送。 例如图 3-9中,gardener对象向 banana
对象发送一条 grow 消息,则表示为从 gardener 对象生命线指向 banana 对象生命线的标记为
grow 的箭头。这也可以理解成 gardener 对象调用了 banana 对象的 grow 方法,因为方法调用是对象间传递消息的机制。 如果对象是与自身通信而发送消息,则箭头指向自己的生命线,
例如 gardener 对象发送给自己的 plant 消息,即 gardener 对象调用了自己的 plant 方法。另外顺序图中还可以看出对象的依存关系。例如图 3-9 中,gardener 对象通过构造函数创建了
banana 对象,在图上表示为 banana 对象收到消息后被创建激活。最后,我们还注意到顺序图有一个 Actor 角色,该角色是系统外部的使用者,负责激活系统对象。在图 3-9 中,该角色名为 main,即指启动程序的 main 方法。
顺序图显示了对象按照时间顺序收发消息进行交互的过程,而协作图则描述了对象按照消息顺序进行的相互协作过程。 协作图的重点是通过消息流的形式给出了对象之间的交互细节。 协作图中对象之间的关联称之为链接 ( link),一个给定的链接上可以有不止一个的交互,
Java 程序设计大学教程
100
而每个交互涉及一个消息(也就是一个方法调用) 。例如图 3-10 中可以看出,gardener 对象向 banana 对象先后发送的消息多达 4 条。
图 3-9 GardenSys 果园系统的顺序图
对象
消息
对象生命线
对象活动线
通过消息激活对象
对象通过收发消息交互
Java 程序设计大学教程
101
图 3-10 GardenSys 果园系统的协作图
顺序图和协作图常用于建立对象活动的抽象模型,表示对象的交互时序和协作关系。顺序图和协作图是观察对象交互的两种不同角度,他们可以互相转换。 通过如图 3-9 和图 3-10
所示的 GardenSys 果园系统的顺序图和协作图,我们更加直观地了解到对象交互的过程。这将有助于我们深入理解面向对象程序的设计方法和运行机制。
在这个简单的果园系统中,我们将 Gardener 类和 Fruit 类分别保存为 Gardener.java 和
Fruit.java 两个源文件。我们可以分别编译这两个 Java 源文件,也可以简单的敲入,
javac Gardener.java
当编译器发现 Gardener 类使用了 Fruit 类时会自动查找 Fruit.class 文件。如果找不到,
那么还会接着查找 Fruit.java 文件,并把它编译成 Fruit.class 文件。 如果编译器发现 Fruit.class
没有 Fruit.java 的时间戳新,也会重新 Fruit.java 文件的。
我们也可在 Java 集成开发环境 NetBeans IDE 中编译并运行程序。方法是通过选择主菜单项“运行 ->运行其他项目 ->运行 "Gardener"( Shift-F6)” 。
我们还可以使用 JAR 工具将多个 Java 类文件添加到一个称为 JAR 文件的归档文件
( archive)中。 JAR 文件使用 ZIP 格式组织文件及子目录,既节约空间又便于发布运行。对于一个标准的 NetBeans IDE 项目,每次运行“生成”命令时,都会使用项目源来生成 JAR
文件。生成的 JAR 文件却省位置是在是项目文件夹的 dist 目录中。最后我们通过以下命令运行 NetBeans IDE 生成的 GardenSys 果园系统 JAR 文件,
java –jar GardenSys.jar
GardenSys 果园系统运行的显示效果如图 3-11 所示的。
Java 程序设计大学教程
102
图 3-11 果园系统的运行结果
2,对象的创建和销毁
( 1) 对象的生命期
对象是通过类创建的,对象是类的动态实例。每个对象都有生命期。一个对象按其生命期来分析,一般有三个阶段,出生、活动、死亡。而我们在编程中要做的对应为:创建(初始化),运行、销毁。
这里我们提到生命期并不是说对象有生命,而是强调对象有生死,有开始和结束。在实际开发中,对象的生命期还受到系统和网络的资源约束。由于每个对象都将消耗一定的系统资源,分布式系统的对象还消耗网络的带宽,所以在使用对象时应该尽量缩短对象的生命期,
注意及时销毁不用的对象,释放宝贵的资源。不过生成以及销毁对象都需要健全的机制作保证。否则不仅对象本身遭殃,甚至会导致程序乃至整个系统崩溃。
在 Java 中,对象的创建、使用和销毁都一套完善的机制。比如:通过构造函数来实现对象的创建及初始化,使用垃圾回收器来销毁和清理不再使用的对象。
在 Java 中,当声明了一个对象类型的变量后,并没有在内存中建立真正的对象,必须调用该类的构造函数手工创建对象。例如示例程序 3-2 所示的 51 行声明了 Fruit 类型的
banana,grape,orange 变量时并没有在内存中创建这些对象,而是在 20,23,26 行,通过构造函数创建了这些对象,这时这些对象才能使用。
( 2) 对象的构造
通常,当调用构造函数时,该函数返回一个新分配内存并初始化了的类的实例。创建对象时,构造函数完成了以下工作,
首先在堆中开辟一块区域用于存贮对象。
然后对这块区域进行缺省初始化。
执行构造函数中用户编写的代码。
返回一个新分配好的并初始化了的实例对象。返回值的类型必须就是该类的类型。
Java 构造函数是一种比较特殊的函数,它不能由用户来指定返回类型,只能返回所属类的类型;它也不能由用户来指定其它名称,只能使用与类名相同的名称。即便如此,我们仍然可以通过方法的重载( overload)来为一个类提供多个不同的构造函数。
方法重载是指当多个方法具有相同的名称而含有不同的参数时,能够被编译器识别和匹
Java 程序设计大学教程
103
配调用的一种能力。编译器将方法名称、形参数目、类型与顺序组成的特性标识作为识别一个重载方法的方法特征( method signature,又译为方法签名),一个类不能包含两个特征一样的方法,这就是方法重载的基础。所有的方法都可以设计有方法重载,而且重载方法的数量没有限制,唯一的限制是方法特征不能重复。
例如,我们可以为 Fruit 类设计 3 个重载的构造函数,使其在创建一个新的水果对象时,
有 3 种不同的对象初始化选择,
//重载构造函数 1
public Fruit(String fname,int in) {
fruitName=fname;
input=in;
}
//重载构造函数 2
public Fruit(String fname) {
fruitName=fname;
input=1000;//种植水果缺省投入为 1000元
}
//重载构造函数 3
public Fruit() {
this("香蕉 ",2000);//调用其它构造函数,设置缺省值
}
重载构造函数 1 就是示例程序 3-1 中原来已经给出构造函数;重载构造函数 2 只有一个参数,适合那些缺省投入为 1000 元的水果对象;重载构造函数 3 是无参数的构造函数,
适合全部使用缺省设置的水果对象,无参数的构造函数也称为缺省构造函数。在重载构造函数 3 中,我们使用了关键字 this 来调用其它的构造函数,完成缺省值的设置。至于 this 调用的是那个重载构造函数,完全由方法特征(即 this 的参数)所决定。
现在我们不妨对示例程序 3-2 所示的 Gardener 类 plant 方法代码略作改动,以测试 Fruit
重载构造函数的使用效果,
private void plant() {
System.out.println("——————————————————————————");
banana = new Fruit();//设置缺省水果为 "香蕉 ",缺省投入为 2000元
banana.grow();
banana.harvest();
grape = new Fruit("葡萄 ");//设置缺省投入为 1000元
grape.grow();
grape.harvest();
orange = new Fruit("橘子 ",3000);
orange.grow();
orange.harvest();
System.out.println("——————————————————————————");
Java 程序设计大学教程
104
}
易犯错误
如果我们没有在类中撰写自己的构造函数,编译器会代为创建一个无参数的缺省构造函数,以保证这个类可以创建。但是一旦类中存在自己撰写的构造函数,尽管这个构造函数不是无参数的,那么再试图调用一个无参数的缺省构造函数将会出错!(这时已经没有隐含的缺省构造函数了)
例如:对于示例程序 3-1的Fruit类,直接调用banana = new Fruit() 会出错,除非在Fruit类中增加重载的无参数缺省构造函数Fruit()。
在 Fruit 类的构造函数中,我们编写的代码用于接收传入的 fname 等参数,并用它初始化 fruitName 等字段。当然对象在被创建时,其字段实际上已经有了系统赋给的缺省值,赋值规则如下表所示。
表 3-2 字段初始化赋值规则
数据类型 初值
布尔类型( boolean) false
字符类型( char) 0 (注,0 值对应为空白字符)
整数类型( byte,short,int,long) 0
实数类型( float,double) 0.0
对象类型 null
忠 告
尽管对象在被创建时,其字段可以有缺省的初值,但是我们仍需要养成在程序中初始化变量的好习惯。因为系统缺省赋给的初值可能会导致一些逻辑上无法理解的运算结果,并在编程中带来难以预料的麻烦。
一个类除了可以有多个重载的构造函数外,构造函数也可以被继承。关于类成员的继承将在后续章节讲解。
( 3) 对象的清理
在 Java 中没有用于销毁和清理对象的析构函数,因为 Java 提供了垃圾回收( Garbage
Collection,简称 gc)机制负责释放对象所占用的内存空间及相关的其它资源。垃圾回收完全不等同于销毁和清理对象的析构函数,因为垃圾回收仅仅在系统内存紧张时才销毁和清理不用的对象。这就是说,如果资源绰绰有余,可能不用的对象永远得不到清理。实际上,我们无法准确知道(也无须知道)垃圾回收器何时开始工作,这反而使程序员能够把精力专注于编写程序,而无须为销毁和清理对象,管理有限的资源而煞费苦心。
友情提示
在一些没有垃圾回收机制的程序设计语言中,一定要记着手工销毁和清理不再使用的对象,以免越积越多的无用对象塞满内存,造成内存的泄漏,影响计算机的整体工作效率。
Java 程序设计大学教程
105
3,对象和对象变量
( 1) 值和引用
我们知道,从语义上讲,对象是类的实例,类是创建对象的模板;从语言上讲,对象也是类这种数据类型的变量,对象在内存中占有空间。 但是在具体使用中,对象与传统的变量,
也就是 Java 中基本类型的变量,有什么区别?对象存储在那里,我们如何调用和传递对象?
首先我们要搞清 Java 的“引用/值”模型。在 Java 中,基本数据类型(如,int,char、
boolean 等)无论是作为参数还是变量都是按值传递和使用的,通常称之为值类型。值类型也是直接类型,它意味着当我们更改这个变量时,实际上是直接更改了它的数值。如示例程序 3-3 所示。
示例程序 3-3 值类型的演示
int i,j;
i=24;
j=i;//变量 j得到的是变量 i的一个副本
i=i+1;
System.out.println(i); //此时显示 i值为 25
System.out.println(j); //此时显示 j值为 24
而 Java 中的对象数据类型(如,Fruit 类)则是按引用传递和使用的。引用类型是间接类型,它与值类型存储数据的方式不同,它存储的是间接数据,即对该数据的引用。例如我们创建了一个称为 MyClass 的类,变量 a 和 b 都是该类的实例,他们并不存储该对象,他们只保存了该对象的一个引用。这就是说,当变量 a 赋值给 b 时,实际上仅仅将 a 的对象引用赋值给了 b。假设 MyClass 类型的对象有整数类型的数据保存在其字段 myvar 中,那么当对象的 myvar 字段改变时,a 和 b 通过相同的引用,得到的是同一对象的字段值。如示例程序
3-4 所示,a.myvar 和 b.myvar 实际上都指向了同一对象的字段,所以他们的值相同。
示例程序 3-4 引用类型的演示
MyClass a,b;//声明 MyClass类型的变量 a和 b,MyClass是一个 Java类
a = new MyClass();//变量 a引用了 MyClass类创建的对象实例
a.myvar = 24;//给对象实例的字段 myvar赋值
b = a; //变量 b得到的是变量 a的一个引用的副本,结果使他们都引用了同一个对象实例。
a.myvar = a.myvar+1;//改变对象字段的值
System.out.println(a.myvar); //此时显示 a.myvar值为 25
System.out.println(b.myvar); //此时显示 b.myvar值也为 25
现在我们知道了值类型和引用类型变量有着很大的不同。当这两种变量(或参数)传递时,前者传递的是值的副本,后值传递的是引用的副本。为什么要这样呢?这是因为他们存储的地方不同,使用的内存机制不同。
计算机内存通常会按照功能和性能的不同划分成一些块,其中常用的有两块称为栈
( Stack)和堆( Heap) 。在栈中,处理器直接使用栈指针( stack pointer)分配和访问内存。
这种方式速度快、效率高,但必须由 Java 编译器来编译产生控制栈指针的程序代码,掌握数据在栈中占用空间大小和存活时间。这样一来就限制了程序的灵活性,比如 Java 对基本数据类型的规定就比较死,编译时还要进行强制性检查。 所以,我们不能将对象存储于栈中,
Java 程序设计大学教程
106
而只能将对象的引用存储于栈中。从某种程度上讲,这也是考虑到对象的大小和生命期是不确定的,而对象引用的大小和生命期是可以确定的。因此,在栈中的变量是不需要由程序员手工去释放内存空间的。
堆是一种通用性质的内存存储空间,是真正用于存放对象的地方。堆和栈不同之处在于编译器无需知道对象究竟要从堆中分配多少内存空间,占用多长内存时间。因此,从堆中分配存储空间可以获得最大的灵活性,但是这种分配内存的方式需要手工进行,在 Java 中使用构造函数来实现对象的空间分配。而已分配的空间的清理工作则交给垃圾回收器自动进行。
( 2) 对象变量是对象的一个引用
要使用对象,首先必须创建它们,并设定它们的初始状态,然后对对象施加方法。
在 Java 程序设计语言中,使用构造函数来创建新的对象。构造函数是一种静态方法,
即一种属于类的方法,不用实例化就能直接使用。下例中,就是直接调用 Fruit 的构造函数来创建香蕉对象,
new Fruit("香蕉 ",1000);
但是这样构造的对象只能使用一次。通常,我们希望构造的对象可被多次使用,这时需要把对象存储在一个变量中,如 banana,
banana = new Fruit("香蕉 ",1000);
banana.grow();
banana.harvest();
对象变量并不等于对象本身,他们之间存在着重要的不同。假设有一个对象变量
banana2,
Fruit banana2;//声明 banana2变量,此时 banana2 不指向任何对象
新声明的对象变量 banana2 可以指向 Fruit 类型的任何对象。但是,有一点需要明白,
变量 banana2 不是一个对象,而且现在也没有指向任何一个对象。这时不能对这个变量应用任何水果的方法。例如直接使用以下语句会产生一个 NullPointerException 异常。
banana2.grow(); //还不能使用。这会产生一个 NullPointerException异常
在使用 banana2 之前,我们必须首先对它初始化。方法有两种,一种当然是使用新创建的对象对它初始化,
banana2 = new Fruit("香蕉 ",1000);
另一种是通过把另一个对象变量的对象引用赋值给它,从而使其指向另一个已存在的对象,
banana2 = banana;//把 banana对象变量的对象引用赋值给 banana2
Java 程序设计大学教程
107
在 Java 中,任何对象变量的值都是指向存储在别处的对象实例的一个引用。构造函数的返回值也是一个对象引用。因此语句,
banana = new Fruit("香蕉 ",1000);
完成了两步工作。第一步,表达式 new Fruit("香蕉 ",1000)生成了一个类型为 Fruit 的对象,它的隐含返回值是对这个新创建的对象的引用。第二步,这个对象引用随后被存储到变量 banana 中。
可以显式地把一个对象变量设为 null,以表明它当前没有指向任何对象。
banana = new Fruit();
banana = null;
if (banana != null) {
banana.grow();
banana.harvest();
}
语法与规则
判断对象a的引用是否存在,用a!= null或a==null;
判断a、b两个对象的引用是否相等,用a==b;
判断a、b两个对象的值是否相等用a,equals (b)。
如果把方法应用于值为 null 的对象变量,则会产生运行时错误。
banana = null
banana.grow;//会产生一个 NullPointerException异常 !
注 意
对象变量并不是对象,它只是指向一个对象。对象变量的值为null时,表示它没有引用任何对象。
使用对象变量时要确保它已经引用了一个对象,否则会出现
NullPointerException异常。
3.4.3 对象之间的关系
对象之间的关系有,
合成关系(,has— a” )
继承关系(,is— a” )
依赖关系(,use— a” )
合成关系(,has— a”关系)从字面上很容易理解,它是指新对象由已有的对象组合而成,或新对象包含有其他对象。比如,一个园丁对象就包含一些水果对象。也就是说,聚合关系意味着类 A 的对象包含类 B 的对象。 Java 程序设计中,对把已有的对象引用作为新对象的数据成员称为对象的合成 (或对象的复合),这个新对象也称为合成对象 (或复合对象) 。
Java 程序设计大学教程
108
从另一个角度看,合成关系又表现为对象之间的相互关联,有人更喜欢用概念更宽泛的
“关联关系” (association)。但有时,has— a”关系表达更为形象。
继承关系(,is— a”关系)用来表示对象的类之间所具有的泛化和特化关系。比如,Car
类和 Bicycle 类从 Vehicle 类继承而来。 Vehicle 称为 Car 和 Bicycle 的基类(也称为:超类、
父类),Car 和 Bicycle 则称为 Vehicle 的派生类(也称为:子类) 。 Car 类具有特定的方法,
如:踩油门、点火熄火等。但它的其他方法,诸如:刹车、停车等,却都是从 Vehicle 类继承过来的,这些与具体什么类型的车无关。一般而言,如果类 A 扩展了类 B,那么类 A 不仅继承类 B 中的方法,而且还具有更多的功能。后续章节中,我们将花费较多篇幅对继承这一重要的概念进行详尽的讲解。
依赖关系(,use— a”关系)是最明显也最常见的关系。比如,由于园丁对象需要访问某一水果对象,以调用它的 grow,harvest 等方法,所以园丁对象要用到水果对象,并依赖于水果对象的存在,两者间存在着依赖关系,如图 3-8 所示。这就是说,如果一个对象需要通过消息(方法调用)与另一个对象交互,那么这个对象就依赖于另一个对象。所以在设计类的时候就应尽量将相互依赖的类的数量减少到最少。如果类 A 不知道类 B 的存在或不与类 B 交互,那么它就不会关心类 B 的任何改变,这还意味着对类 B 的改变不会使类 A 产生任何 bug。用软件工程术语来说,就是要使类间的耦合最小。
由于对象的关系是由他们的类决定的,习惯上我们使用 UML 符号来绘制描述类关系的类图,从而帮助我们理解和把握对象之间的关系。类间关系用带有各种修饰的箭头表示,如图 3-12 所示。
图 3-12 表达类关系的 UML 符号
面向对象程序设计的高效关键在于它让每个对象负责执行一组相关的任务。 如果对象依赖于其他对象负责处理的任务,那么它就去访问负责这个任务的对象。这时第一个对象就请求第二个对象执行任务,即这个对象依赖于另一个对象。另外,一个对象不能直接操作另一个对象内部的数据,它也不应让其他对象直接访问自己的数据。所有的通信都应该通过方法调用来完成。通过对对象数据的封装,就可以使可重用性最大化,减少数据依赖,并且使调试时间最小化。 当然,就像面向过程语言中的模块一样,不应该让一个对象负责过多的事情。
创建完成少量任务、规模较小的对象会使设计和调试过程都变得较为简单,相反,创建数据复杂、函数众多的对象,则会使开发过程变得极为复杂。
因此理解对象之间的关系可以帮助我们设计出可重用、易维护的代码,降低耦合性,提高灵活性。
Java 程序设计大学教程
109
软件工程
耦合是指两个(或两个以上)的系统之间通过相互作用而彼此影响的现象。在程序设计中,模块之间的耦合度应该越弱越好,这样模块之间互相牵制和依赖的作用就变小,便于模块的维护和重用。通常降低耦合度的方法是通过一种规范的通信机制来保持模块之间的联系,比如对象间的消息传递。相反,滥用全局变量的面向过程编程习惯则会导致模块之间耦合度增加。
3.5 本章习题
复习题
[习题 1] 面向对象的主要特性概括起来有哪些?
[习题 2] 什么是建模语言?什么是 UML?
[习题 3] 如何理解面向对象程序设计中的对象?
[习题 4] 什么是类?类包括那些成员?
[习题 5] 什么是方法? Java 中哪些方法相当于函数,哪些方法相当于过程?
[习题 6] 简述 Java 程序中对象的生命期,说明对象为什么要创建和销毁?
[习题 7] 对象之间有哪些关系?如何理解这些关系?
测试题
[习题 8] 以下关于对象的说法不正确的是 。
A、组成客观世界(事物)的不同实体可以看成是对象。
B、对象是一个具有封装性和信息隐藏的独立模块。
C、对象可以分解和组合,还可以通过相似性原理进行分类和抽象。
D、对象能更好地模拟计算机工作方式,体现计算机运行规律,提高程序执行效率。
[习题 9] 面向对象的特点主要概括为 。
A、可分解性、可组合性、可分类性
B、继承性、封装性和多态性
C、抽象性、继承性、封装性和多态性
D、封装性、易维护性、可扩展性、可重用性
[习题 10] 以下论述不正确的是 。
A、对象变量是对象的一个引用。
B、对象是类的一个实例。
C、一个对象可以作为另一个对象的数据成员。
D、对象不可以作为函数的参数传递。
[习题 11] 对象之间的继承关系是 关系。
A,has— a
B,is— a
C,use— a
Java 程序设计大学教程
110
D,of— a
[习题 12] 以下 Bridge 与 Road 之间是 关系。
class Bridge {
Road road;
}
class Road {
String name;
}
A,has— a
B,is— a
C,use— a
D,of— a
[习题 13] 要使某个类能被同一个包中的其他类访问,但不能被这个包以外的类访问,可以 。
A、让该类不使用任何关键字。
B、使用 private 关键字。
C、使用 final 关键字。
D、使用 protected 关键字。
[习题 14] 在 Java 中最基本的类是 。
A,Window
B,Component
C,Object
D,Class
[习题 15] 以下代码将在屏幕上显示的是字符 。
Boolean bl = new Boolean(true);
Boolean b2 = new Boolean(true);
if (bl == b2)
if (bl.equals(b2))
System.out.println("a");
else
System.out.println("b");
else
if (bl.equals(b2))
System.out.println("c");
else
System.out.printin("d");
A,a
Java 程序设计大学教程
111
B,b
C,c
D,d
[习题 16] UML 是一种 。
A、数据库语言
B、程序设计语言
C、建模语言
D、面向对象语言
[习题 17] 分析以下程序的运行结果,得到的结论是 。
public class MyClass {
String s;
public static void main(String[] args) {
MyClass m = new MyClass ();
m.go() ;
}
void MyClass() {
s = "constructor";
}
void go() {
System.out.println(s);
}
}
A、程序可以运行,但屏幕没有输出任何字符。
B、程序可以运行,屏幕输出字符串为 "null"。
C、程序可以运行,屏幕输出字符串为 "constructor"。
D、程序无法编译运行。
[习题 18] 以下关于类的说法不正确的是 。
A、类是对具有共同实现的一些对象或一系列对象的描述。
B、在 Java 中的每个类都必须有方法,这是类与记录类型不同的地方。
C、在 Java 中类被当作一个数据类型来定义。
D、在 Java 中所有新创建的类都是从其他的类派生而来。
练习题
[习题 19] 设计一个 Retangle 类,提供通过 length 和 width 字段计算面积和周长的 2 个方法。
并用 main 方法来测试程序 。
[习题 20] 分析以下程序,绘出 UML 类图,指出 Vehicle 与 Engine 这两个类之间的关系,并给出程序运行结果。
Java 程序设计大学教程
112
public class Vehicle {
public Vehicle() {
make = "FiatSiena 1.5EL";
color = "法拉利红色 ";
topSpeed = 180;
}
public static void main(String[] args) {
Vehicle car = new Vehicle();
System.out.println(car.showInfo());
car.start();
car.speedUp();
car.slowDown();
car.stop();
}
public void stop() {
engine.stop();
System.out.println("汽车停止,..");
}
public String showInfo() {
String info = "车型," + make + "\t颜色," + color + "\t 最高时速," + topSpeed
+ "\n 发动机," + engine.showInfo();
return info;
}
public void start() {
engine.start();
System.out.println("汽车启动,..");
}
public void speedUp() {
System.out.println("汽车加速,..");
}
public void slowDown() {
System.out.println("汽车减速,..");
}
/**
* @link aggregation
*/
private Engine engine = new Engine();
Java 程序设计大学教程
113
private int topSpeed;
private String make;
private String color;
}
class Engine {
public Engine() {
capacity = 1461;
power = 85;
}
public String showInfo() {
String info = "排量," + capacity + "cc;最大功率 " + power + "匹马力 ";
return info;
}
public void stop() {
System.out.println("发动机熄火,..");
}
public void start() {
System.out.println("发动机点火、启动,..");
}
private int power;
private int capacity;
}
[习题 21] 设计一个 Teacher 类,包括,3 个私有字段 Name,TeacherID,Address,以及供外部访问这三个私有字段的对应方法 getName,setName,getTeacherID,setTeacherID、
getAddress,setAddress。同 时,Teacher 类还有一个能按查询条件字符串( qryString)
查询老师授课课程的方法 queryCourse,queryCourse 返回字符串类型的课程名称。
要求画出 UML 类图,并给出 Teacher 类的定义代码( Teacher.java) 。
[习题 22] 以下是一个学生管理系统的 UML 类图,根据图示分别写出 Students 和 Students 两个类的定义。即方法具体实现不需要给出,但有返回值的则需要给出 return 语句。
Java 程序设计大学教程
152
第 5章 算法与数据结构
在第一章绪论中我们讲过,使用计算机解决问题时,除了需要使用计算机语言描述算法,
还必将涉及数据结构。从这个意义上讲,程序是建立在数据结构基础上使用计算机语言描述的算法,因此简单地讲,程序也可以表示成:算法+数据结构。
学习计算机程序设计,目的在于利用计算机来解决遇到的实际问题。从这个方面来说,
Java 只是一种设计程序的计算机语言工具。至于具体怎么用它来实现程序,解决实际问题,
通常还需要相应的算法和数据结构的支持。算法是抽象的,它并不仅限于 Java 语言,实际上算法理论独立于任何一种程序设计语言,而且算法本身博大精深、自成体系。算法也是具体的,算法的应用还必须结合不同的数据类型,并在具体的程序中得以实现。本章先概括性地介绍算法的基础知识,然后讲解 Java 中较复杂的数据类型和常用的对象容器,并结合数据类型剖析一些典型算法的程序实现。
5.1 算法
算法的英文单词为,Algorithm” 。这个单词一直到 1957 年之前,在《韦氏新世界词典》
中还未出现,我们只能找到带有它的古代涵义的,Algorism(算术),,指的是用阿拉伯数字进行算术运算的过程。一本早期的德文数学词典《数学大全辞典》,给出了另一个单词
,Algorithmus”的如下定义:,在这个名称之下,组合了四种类型的算术计算的概念,即加法,乘法,减法,除法” 。 拉丁语中用到过一个短语,algorithmus infinitesimalis (无限小方法 ),,
在当时就用来表示数学家莱布尼兹所发明的以无限小量进行计算的微积分方法。 1950 年左右,,algorithm”一词经常地同欧几里德算法,Euclid's algorithm”联系在一起(这个算法就是在欧几里德的《几何原本》中所阐述的求两个数的最大公约数的过程,也即辗转相除法 )。
现在,基本可以明确算法的含义:算法是为了求解某一问题在有限步骤内、定义了具体操作序列的规则集合。
通俗点说,算法就是针对一类特定问题,使用计算机解题的过程。在这个过程中,无论是形成解题思路还是编写程序,都是在实施某种算法。前者是推理实现的算法,后者是操作实现的算法。
一个算法应该具有以下五个重要的特征,
确切性( No ambiguity) 算法的每一步骤必须有确切的定义。而不应该有二义性,
例如,在算法中不能出现诸如“赋值为 100 或 1000” 。
输入( Input) 有 0 个或多个输入,用于初始化运算对象。所谓 0 个输入是指无需输入条件,而算法本身定出了初始条件。
输出( Output) 没有输出的算法是毫无意义的。一个算法应该有一个或多个输出,以反映对输入数据加工后的结果。
可行性( Feasibility) 算法原则上能够精确地运行,而且对于算法中的每种运算,
在原理上人们应该能用笔和纸做有限次运算后完成。
有穷性( Finite) 算法必须保证执行有限步之后结束。只具有前面四个特征的规则集合,称不上算法。例如,尽管操作系统能完成很多任务,但是它的计算过程并不终止,而是无穷无尽的执行、等待执行,所以操作系统不是算法。
Java 程序设计大学教程
153
5.1.1 算法的描述
1,伪代码描述
伪代码( Pseudo-code)是一种算法描述语言。使用伪代码的目的是为了使被描述的算法可以容易地以任何一种编程语言(如 Pascal,C,Java 等)实现。因此,伪代码必须结构清晰,代码简单,可读性好,并且类似自然语言。
下面我们介绍一种常用的类似 Pascal 语言的伪代码。语法规则如下,
在伪代码中,每一条指令占一行 (else if 例外 ),指令后不跟任何符号。书写上的“缩进”
表示程序中的分支程序结构,这种缩进风格也适用于 if-then-else语句。 用缩进取代传统 Pascal
中的 begin 和 end 语句来表示程序的块结构可以大大提高代码的清晰性;同一模块的语句有相同的缩进量,次一级模块的语句相对与其父级模块的语句缩进;例如,
line 1
line 2
sub line 1
sub line 2
sub sub line 1
sub sub line 2
sub line 3
line 3
而在 Java 中这种关系用 {}的嵌套来表示。
在伪代码中,赋值语句用符号←表示,x← exp 表示将 exp 的值赋给 x,其中 x 是一个变量,exp 是一个与 x 同类型的变量或表达式(该表达式的结果与 x 同类型) ;多重赋值 i← j
← e 是将表达式 e 的值赋给变量 i 和 j,这种表示与 j← e 和 i← e 等价。例如,
x← y
x← 20*(y+1)
x← y← 30
以上语句用 Java 分别表示为,
x = y;
x = 20*(y+1);
x = 30; y = 30;
选择语句用 if-then-else 来表示,并且这种 if-then-else 可以嵌套,例如,
if (Condition1)
then [ Block 1 ]
else if (Condition2)
then [ Block 2 ]
else [ Block 3 ]
Java 程序设计大学教程
154
循环语句有三种,while 循环,repeat-until 循环和 for 循环。例如,
1,x ← 0
2,y ← 0
3,z ← 0
4,while x < 100
4.1 do x ← x + 1
4.2 y ← x + y
4.3 for t ← 0 to 10
4.3.1 do z ← ( z + x * y ) / 100
4.3.2 repeat
4.3.2.1 y ← y + 1
4.3.2.2 z ← z - y
4.3.3,until z < 0
4.4,z ← x * y
5,y ← y / 2
上述用伪代码描述的语句写成 Java 语句是,
int x = 0;
int y = 0;
int z = 0;
while ( x < 100 ) {
x = x + 1;
y = x + y;
for ( int t = 0,t <= 10,t++ ) {
z = ( z + x * y ) / 100;
do {
y = y + 1;
z = z - y;
} while (z < 0);
};
z = x * y;
}
y = y / 2;
2,图形描述
经验告诉我们画图往往是一种分析和解决问题的好办法。因为图形直观、易懂,容易说明问题。所以,即使不是几何学的问题,如果我们能给出适当的几何图形表示,也会使问题变得容易处理。程序设计中,能够用来表示算法基本概念的图主要有,PAD 图,N\S 盒图、
流程图。
流程图是最古老、最广泛使用的程序设计工具,也是展示程序逻辑流程的有效工具。流
Java 程序设计大学教程
155
程图是最常用的算法图形表示法。它使用框图的形式掩盖了算法所有的细节方面,它只显示算法从开始到结束的整个流程。在程序设计环境下,它能用于设计一个完整的程序或者部分程序。程序流程图常用图形符号及控制结构图例如图 5-1 所示。
图 5-1 程序流程图常用图形符号及控制结构图例
5.1.2 常用算法
1,基本算法
基本算法大都比较简单,是其他算法的基础。这类算法在程序中应用非常普遍,如:累加求和、累乘求积、求最大和最小值等。
这里我们来讨论在一组数据中求最大值的算法。 它的思想是通过一个判断结构找到两个数中的较大值。如果把这个结构放在循环中,就可以得到一组数中的最大值。以一个从 10
个数中找到最大值的算法为例,该算法的流程图如图 5-2 所示。
根据流程图我们还可以写出如示例程序 5-1 所示的伪代码。这里需要一个计数器
Counter 用来计数,并用一个变量 largest 保存当前比较出来的最大数。在初始化阶段给这个计数器赋值为 0,每循环一次就对它加 1。当计数器等于 10 时,退出循环。
之所以用伪代码来表述算法,是因为算法与具体计算机语言无关。也就是说,这个求
1000 个数中最大值的算法既可以用 Java 语言实现,应可以用 C++,Delphi 等语言实现。下面我们就看看用 Java 程序是如何实现的,程序代码如示例程序 5-2 所示,程序编译运行如图 5-3 所示。
处理 1
处理 2
处理 1 处理 2
处理
条件否是
条件
处理

条件
否端点符 处理 判断 预定义处 连接符
顺序结构 选择结构
(while-do) (repeat-until)
循环结构
Java 程序设计大学教程
156
图 5-2 从 20 个数中求最大值算法的流程图
示例程序 5-1 用伪代码写的在 10 个数中求最大值的算法
FindLargest
Input,10 positive integers
1,largest← 0
2,counter← 0
3,while(counter < 10)
3.1 Input theInteger
3.2 if (theInteger > largest)
then
3.2.1 largest← theInteger
end if
3.3 counter← counter+1
end while
开始
初始化,将 largest 和 counter 设为 0
计数器判断
counter <10?
否是
while-do
循环
largest← theInteger
大值比较
theInteger>larges?
否是返回 largest
结束
输入被比较的数 theInteger
计数,counter← counter+1
Java 程序设计大学教程
157
4,Return largest
end
示例程序 5-2 在 10 个数中求最大值算法的 Java 程序实现
1,import java.io.*;
2,
3,public class Max {
4,public static void main(String[] args) throws IOException {
5,//初始化
6,BufferedReader input = new BufferedReader
7,(new InputStreamReader(System.in));
8,int largest=0;
9,int counter=0;
10,int theInteger=0;
11,//循环比较
12,while(counter < 10) {
13,//输入被比较的数
14,counter++;//计数
15,System.out.println("请输入第 "+counter+"个被比较的数,");
16,String inputString = input.readLine();
17,theInteger = Integer.parseInt(inputString);
18,//大值比较
19,if (theInteger > largest) largest=theInteger;
20,}// while
21,System.out.println("求出最大数是,"+largest);
22,}
23,
24,}
在示例程序 5-2 中,我们定义了一个 BufferedReader 类型的 input 变量,用于接收键盘输入的数据,
BufferedReader input = new BufferedReader
(new InputStreamReader(System.in));
BufferedReader 是 Java 标准类库中提供的带缓存的读取器类,可通过 import java.io.*语句导入。 Java 标准类库中与输入输出有关的类位于 java.io 包中。
忠 告
JDK提供的庞大的类库中包含了仔细设计并高效编程实现的类定义,可应用于各种各样的任务。在设计和编写自己的代码之前,先在Java类库里找一找,
看是否已经有了可以解决你所遇问题的类。
Java 程序设计大学教程
158
图 5-3 最大值算法的 Max 程序编译和运行
Java 类库
在Java中,通常我们会用到3个的与输入输出有关的流。System.in、
System.out以及System.err。System.in是一个预定义的InputStream输入流,
它通常与键盘联系在一起。而java.io.BufferedReader类有一个readLine方法可用于简单的键盘输入。BufferedReader类包含执行带有缓冲的输入的方法。
缓冲区(buffer)是一片内存,用于保存输入直到程序需要它。在键盘与程序之间使用缓冲区,即可用退格键来删除一个字符。当按回车键之后,程序在从输入缓冲区里获取字符时将忽略被删除的所有字符。
注意,BufferedReader的构造方法带有一个Reader参数。如果想用键盘进行带缓冲的输入,可以把System.in的一个引用传递给BufferedReader构造函数,这样它就能让我们把System.in转换成一个带缓冲的读取器。
一旦创建了一个 BufferedReader 对象(程序中名为 input),就能用 readLine 方法来从键盘读取一行字符并将它存放在一个字符串对象(如程序第 16 行中的 inputString)中。考虑到在程序的运算中,我们需要把键盘输入的字符串转换成可进行比较的整数,这时可以借助
Java 类库中的 java.lang.Integer 类,
theInteger = Integer.parseInt(inputString);
Integer 类的 parseInt 方法把 inputString 转换成 int 类型,并将返回值赋给了 theInteger
变量。 Integer 类称为包装类( wrapper class),它是无法继承的 final 类,它提供的 parseInt
等类型转换方法都是静态方法,无需实例化就可以调用。
因为输入操作可能遇到各种无法预测的问题,如果发生了某个输入错误,readLine 方法会引发一个 IOException。为了在程序中使用 readLine,我们必须使用 throws 关键字确保能够引发万一出现的 IOException 异常。这样一来,如果出现了 IOException 异常可以被捕获,
Java 程序设计大学教程
159
并交给 Java 虚拟机以默认的方式来处理(通常是停止程序并打印出一条错误信息) 。这段代码如示例程序 5-2 的第 4 行所示。
友情提示
异常(exception)是Java处理在程序运行期间发生的运行时错误的方法。我们在第7章中将讲解异常。
求解一组数中的最小值和上面求最大值的方法相似,只有两个小小的不同。首先,用一个判断结构求出两个数中的较小值。其次,在初始化时使用一个很大的而不能是太小的数。
2,排序算法
排序算法根据数据的值对它们进行排列。排序是为了把不规则的信息进行整理,以提高查找信息的效率。如果没有排序,想象一下,在一个不经排序的电话本中查找某人的电话号码是多么麻烦的一件事。
常用的排序方法包括:选择排序、冒泡排序、插入排序等。这三种方法是程序设计中使用的快速排序的基础。
选择排序 在选择排序中,数字列表被分为两个子列表——已排序和未排序的——
它们通过假想的一堵墙分开。 找到未排序子列表中最小的数字并把它和未排序子列表中第一个数字进行交换,经过每次选择和交换,两个子列表中假想的这堵墙向前移动一个元素,这样每次排序列表中将增加一个元素而未排序列表中将减少一个元素,这样就完成了一次分类扫描。一个含有 n 个元素的数字列表需要 n-1 次扫描来完成数据的重新排列。
冒泡排序 在冒泡排序方法中,数字列表数被分为两个子列:已排序的和未排序的。
在未排序的子列表中,最小的元素通过冒泡的方法选出来并移到已排序的子列表中。当把最小的元素移到已排序列表后,将墙向前移动一个元素,使得已排序数的元素个数增加 1 个,而未排序数的元素个数减少 1 个。每次元素从未排序子列表中移到已排序子列表中,便完成一次分类扫描。一个含有 n 个元素的列表,冒泡排序需要 n-1 次扫描来完成数据排序。
插入排序 插入排序是最常用的排序技术之一,经常在扑克牌游戏中使用。游戏人员将每个拿到的牌插入到手中合适的位置,以便手中的牌以一定的顺序排列。在插入排序中,排序列表被分为两部分:已排序的和未排序的。在每次扫描过程中,未排序子列表中的第一个元素被取出,然后转换到已排序的子列表中,并且插入到合适的位置。可以看到,一个含有 n 个元素列表至少需要 n-1 次排序。
其它的排序算法还有:快速排序、合并排序、希尔( Shell)排序、堆排序等等。也许读者会问为什么会有这么多的排序算法?原因就在于需要排序的数据的类型。 一种方法对大多数已经排序好的数据很有效,而另一种方法对完全未排序的数据很有效。为了决定哪种方法更适合特定的程序,需要一种叫做算法复杂性的尺度来衡量。我们将在下一节的“算法复杂性分析”中讨论这个问题。
有关排序算法的 Java 程序实现则需要用到数据结构方面的知识。在本章的,5.2 数组”
一节中,我们会举例讲解排序算法的程序实现。
3,查找算法
查找是一种在列表( list)中确定目标所在位置的算法。在一个列表中,查找意味给定一个值,并在包含该值的列表中找到该值的第一个元素的位置(索引) 。
Java 程序设计大学教程
160
对于列表有两种基本的查找方法:顺序查找和折半查找。顺序查找可以在任何列表中查找,折半查找则需要列表是有序的。
顺序查找 顺序查找用于查找无序的列表。 通常用这种方法来查找较小的列表或是不常用的列表。其它情况下,为了提高效率,最好的方法是首先将列表排序然后使用后面将要介绍的折半查找进行查找。顺序查找是从表头开始查找,当找到目标元素或确信查找目标不在列表中时,查找过程结束(因为已经查找到列表的末尾了) 。
折半查找 顺序查找是很慢的。如果一个列表里有一百万个元素,在最坏的情况下需要进行一百万次比较。如果这个列表是无序的,则顺序查找是唯一的方法。如果这个列表是有序的,那么就可以使用一个更有效率的方法称之为折半查找。折半查找是最常用的查找算法。折半查找是从一个列表的中间的元素来测试的,这将能够判别出目标在列表里的前半部还是后半部分。如果在前半部分,就不需要查找后半部分。如果在后半部分,就不需要查找前半部分。换句话说,可以通过判断排除一半的列表。重复这个过程直到找到目标或是目标不在这个列表里。
有关查找算法的 Java 程序实现则需要用到数据结构方面的知识。在本章的,5.2 数组”
一节中,我们会举例讲解查找算法的程序实现。
4,迭代和递归算法
迭代和递归是用于编写解决问题的算法的两种途径。一种使用迭代,另一种使用递归。
迭代,迭”是屡次和反复的意思,“代”是替换的意思,合起来,“迭代”就是反复替换的意思,也就是使用一个中间变量保存中间结果,不断反复计算求解最终值。
递归 递归是一个算法自我调用的过程,用递归调用的算法就是递归算法。递归调用会产生无法终止运算的可能,因此必须在适当的情况下终止递归调用。根据递归定义设计的递归算法中,非递归的初始定义就用做程序的终止条件。
考虑一个计算阶乘的简单例子,我们可以分别用迭代和递归这两种算法来实现。示例程序 5-3 是阶乘迭代算法的伪代码,而示例程序 5-4 则是阶乘递归算法的伪代码。
示例程序 5-3 阶乘迭代算法伪代码
Factorial
Input:Apositive integer num
1,FactN← 1
2,i← 1
3,While( i < or = num)
3.1 FactN← FactN × i
3.2 Increment i
end while
Return FactN
end
示例程序 5-4 阶乘递归算法伪代码
Factorial
Input:A positive integer num
1,if( num = 0)
then
1.1 Return 1
Java 程序设计大学教程
161
else
1.2 return num× Factorial(num-1)
end if
end
递归算法的效率往往很低,费时和费内存空间。但是递归调用也有其长处,它能使一个蕴含递归关系且结构复杂的程序简洁、精练,增加可读性。特别是难于找到从边界条件到解的全过程的情况下,采用递归算法编程比较合适。实际上,递归是把一个不能或者不好直接求解的“大问题”转化成一个或者几个“小问题”来解决,在把这些“小问题”进一步分解成更小的“小问题”来解决,如此分解,直至 每个“小问题”都可以直接解决(此时分解到递归出口) 。但递归分解又不是随意地分解,递归分解要保证“大问题”与“小问题”相似,
即求解过程与环境都相似。
在程序设计中,实际运行的程序并非都写成递归形式。有时先写一个递归程序,而后为了使程序在反复执行时避免占用过多的机器时间,需要将递归程序转化为非递归的程序,以提高运行效率。
5.2 数组
数组用于表示相同类型的元素的有序集合,这里所说的“相同类型”即数组的基类型,
可以是基本数据类型,也可以是用户自定义的对象数据类型。数组中每个元素都有一个唯一的索引,同一数组中可以含有多个相同的值。
5.2.1 数组的创建和使用
数组是一个被命名的连续存储区域的集合,存放着相同类型的数据项。数组的每个元素通过它在数组里的位置索引来引用。如果数组名为 myArray,那么元素的名字就是
myArray[0],myArray[1],myArray[2]、…,myArray[n-1]。这里 n 表示数组里元素的个数,
即数组的长度。在 Java 中,数组的第一个元素索引为 0。
数组索引必须是一个整数值或者一个整数表达式。对一个给定的数组来说,一个有效的数组索引必须在 0 到 n-1 的范围内,这里 n 是数组元素的个数。使用一个不在此范围内的索引值将导致一个数组越界错误。该错误是一个运行时错误,即在程序运行时发生的错误,而不是一个能在程序编译时检查到的语法错误,例如,
myArray[k%3] //k值为整数 15
errArr["5"] //错误,"5"不是整数。
errArr[-1] //错误,索引必须在 0到 n-1的范围内。
在 Java 里,大多数情况下数组被当成对象来对待。它们是用 new 操作符来实例化的,
有自己的实例变量(例如 length,可返回数组中第一维的元素数量) 。数组变量是引用类型的变量。当把数组作为参数使用时,传递的是该数组的一个引用,而不是整个数组。数组与真正的对象之间的主要区别是数组不属于 Array 类。因此,数组并不能很好地纳入 Java 的对象层次结构。它们没有从 Object 继承任何属性,而且也不能用它们来派生子类。
一个数组能包含很多变量。一个空数组是只有 0 个变量的数组。正如我们已经看到的,
Java 程序设计大学教程
162
包含在数组里的变量不是用名字,而是用它们在数组里的相对位置即数组索引来引用的。这些变量称为数组元素。如果一个数组对象有 n 个元素,我们就说数组的长度为 n。数据的每个元素类型都—样,称为数组的元素类型。数组元素可以是任何类型,包括基本数据类型与对象数据类型。
和定义与创建其他类型的对象一样,创建一个数组对象需要我们为该数组定义一个类型并创建数组本身。例如,
Fruit[] fruits ; //定义 Fruit 类型的数组变量 fruits
fruits = new Fruit[5]; //新建有 5个元素的数组 fruits
fruits[0] = new Fruit("香蕉 ",1000);//为数组元素赋值(引用对象)
fruits[1] = new Fruit("葡萄 ",2000);
fruits[2] = new Fruit("菠萝 ",2000);
fruits[3] = new Fruit("草莓 ",1000);
fruits[4] = new Fruit("橘子 ",1000);
int n = fruits.length; //测试数组长度
在这个例子里,数组的元素类型是 Fruit,而它的长度为 5。这意味着这个数组有 5 个
Fruit 类型的变量,fruits[0]、…,fruits[5],可通过他们来引用不同的水果对象实例。 fruits
数组本身作为一个独立的对象相当于一个容器,其 5 个 Fruit 类型的数组元素可作为 5 个引用对象的变量,如图 5-4 所示。
图 5-4 fruits 数组本身作为一个独立的对象相当于一个容器,其 5 个 Fruit 类型的数组元素可作为 5 个引用对象的变量。
注 意
创建一个对象类型的数组并没有创建存储在该数组中的对象,因此必须单独实例化各个对象元素。引用一个没有实例化的数组元素会导致语义错误,此时他们的值是null,而不是你所期望的对象。
定义与创建一个数组的语法如下,
数组名,fruits 数组长度为 5
数组元素引用对象
数组索引,
数组内容,
:Fruit
fruitName="香蕉 "
input=1000,Fruit
fruitName="葡萄 "
input=2000,Fruit
fruitName="菠萝 "
input=2000
:Fruit
fruitName="草莓 "
input=1000
:Fruit
fruitName="橘子 "
input=1000
0 1 2 3 4
Java 程序设计大学教程
163
语法与规则
数组类型名称 [] 数组变量名 ;//定义数组变量
(也可以写成:数组类型名称 数组变量名 [];//定义数组变量 )
数组变量名 = new 数组类型名称 [n];//创建长度为 n的数组
以上两步也可以合并写为,
数组类型名称 数组变量名 []= new 数组类型名称 [n];
或者,
数组类型名称 [] 数组变量名 = new 数组类型名称 [n];
下面我们演示一下数组在编程中的应用。示例程序 5-5 是一个掷骰子实验程序,它通过一个计数器数组来统计掷骰子过程中各个点数的面出现的次数。我们将 int 类型的 counter
数组长度定义为 7,即该数组有 counter[0]至 counter[6]共计 7 个元素,但是我们只用 counter[1]
至 counter[6]这 6 个元素来分别记录骰子的 6 个面出现的次数。这主要是为了回避讨厌的
counter[0],这样 counter[1]对应骰子的 1 点,counter[2]对应骰子的 2 点,使得程序在语义上便于理解。
示例程序 5-5 的第 6-7 行初始化 counter,使得骰子的各点清零。第 8-11 行统计掷骰子过程,第 9 行模拟 1~6 点随机出现,第 10 行统计各点出现次数。实验表明,当掷骰子次数越多( NTRIALS 值越大)时,骰子的各点出现概率越接近。
示例程序 5-5 掷骰子实验程序 DieExperiment
1,public class DieExperiment {
2,public static final int NTRIALS = 5000;// 测试次数
3,private int counter[] = new int[7]; // 定义计数器数组
4,
5,public void testDie() {
6,for (int k = 1; k <= 6; k++) // 初始化 counter
7,counter[k] = 0;
8,for ( int k = 0; k < NTRIALS; k++ ) {
9,int roll= (int)(Math.random() * 6)+1;//随机模拟 1~6之间的整数
10,++counter[roll]; // 测试计数
11,}
12,}
13,
14,public void printResults() {
15,System.out.println(" 测试次数," + NTRIALS );
16,for (int k = 1; k <= 6; k++)
17,System.out.println("\t " + k + "点的次数," + counter[k]);
18,}
19,
20,public static void main(String args[]) {
21,DieExperiment tester = new DieExperiment();
22,tester.testDie();
23,tester.printResults();
Java 程序设计大学教程
164
24,}
25,}
Java 类库
Java类库提供的Math.random方法可以产生[0,1)区间中的一个double
类型的随机实数,也就是说0 <= Math.random() < 1。Math.random方法经常在一些随机模拟程序中使用。
5.2.2 多维数组和不规则数组
根据数组索引的个数,又可以将数组分为一维数组和多维数组。顾名思义,一维数组就是指索引个数只有一个的数组;以同样的思路,可以理解多维数组。
例如在一个模拟每日股票指数的程序中,我们就可以按照一年 52 周,每周 5 个交易日的 2 个索引,定义一个 2 维的股指数组 stockValue。该数组的大小应该为 52× 5。但是令人讨厌的是数组索引总是从 0 开始,stockValue[1][2]并不是第 1 周的第 2 个工作日。为此我们可以这样定义 stockValue 数组,
int stockValue[][]= new int[53][6];
在实际使用时不用索引为 0 的元素,这样我们就可以让 stockValue[1][2]表示第 1 周的第
2 个工作日。
示例程序 5-6 使用了随机数来模拟股指( stockIndex)在 1500 点附近的波动,并给 2
维的股指数组赋值,如第 3-10 行所示。
示例程序 5-6 模拟每日股指的程序 Stock
1,public class Stock {
2,public Stock() {
3,for (int week=1;week<=52;week++){
4,stockValue[week][0]=week;
5,for (int weekday=1;weekday<=5;weekday++){
6,stockValue[0][weekday]=weekday;
7,int stockIndex = (int)(Math.random()*1000+1000);
8,stockValue[week][weekday] = stockIndex;
9,}
10,}
11,}
12,
13,public void printStock(){
14,for (int week=0;week<=52;week++){
15,for (int weekday=0;weekday<=5;weekday++){
16,System.out.print(stockValue[week][weekday]+"\t");
17,}
18,System.out.println();
Java 程序设计大学教程
165
19,}
20,}
21,
22,public static void main(String[] args) {
23,Stock s=new Stock();
24,s.printStock();//打印股指年表
25,}
26,
27,int stockValue[][]= new int[53][6];
28,}
另外我们借用索引为 0 的元素标记周数和工作日,这样可以打印出比较漂亮的一个股指年表,该表格横轴是周 1 至周 5 各工作日,纵轴是一年的 52 个周。如下所示,
0 1 2 3 4 5
1 1133 1995 1500 1655 1033
2 1605 1981 1143 1226 1265
3 1226 1015 1648 1411 1007
4 1754 1472 1680 1793 1065
5 1469 1707 1745 1477 1742
...,.,
52 1578 1550 1309 1139 1357
示例程序 5-6 演示了一个 2 维数组的应用,但 Java 中并没有多维数组,多维数组实际上是“数组的数组”,即多维数组可以看作其元素是数组的数组。例如我们可以把示例程序
5-6 第 27 行所示的 stockValue 数组的定义和创建替换为以下代码,
int stockValue[][]= new int[53][];
{//初始化块
for (int n=0 ;n<stockValue.length;n++){
stockValue[n]=new int[6];
}
}
语法与规则
在Java程序中,如果类定义中含有{}块,其中的代码先于构造函数执行。这种代码块称为初始化块。
同样,我们还可以定义不规则数组。例如为了打印图 5-5 的直角三角形的数字阵列,
我们设计一个数组,要求第 i 行有 i 个元素。通常 i×j 的 2 维数组每一行的元素都是不变的
j,而这个不规则数组试图使 j 随着 i 变化。
示例程序 5-7 给出了这个不规则数组演示程序 ArrTest 的源代码,我们可以看到在程序
Java 程序设计大学教程
166
的第 26 行先定义不规则数组,创建该数组的第 1 维;接着在第 5 行创建该数组不规则的第
2 维,动态分配各行的元素。为了避免使用索引为 0 的元素,我创建数组时采用了一个技巧,
为实际需要的索引值加 1,例如,Max+1。
1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
1 2 3 4 5 6
图 5-5 直角三角形的数字阵列
示例程序 5-7 不规则数组演示程序 ArrTest
1,public class ArrTest {
2,
3,public ArrTest() {
4,for (int n=1;n<myArr.length;n++){
5,myArr[n] = new int[n+1];//创建数组的数组,每个数组的长度不一样。
6,for (int m=1; m<myArr[n].length; m++){
7,myArr[n][m]=m;
8,}
9,}
10,}
11,
12,public void printArr(){
13,for (int n=1; n<myArr.length; n++){
14,for (int m=1;m<myArr[n].length;m++){
15,System.out.print(myArr[n][m]+"\t");
16,}
17,System.out.println();
18,}
19,}
20,
21,public static void main(String[] args) {
22,ArrTest arr=new ArrTest();
23,arr.printArr();
24,}
25,
26,int myArr[][]= new int[Max+1][];//定义不规则数组,先创建数组的第 1 维。
27,static int Max=6;
28,}
为了帮助大家理解不规则数组,我们可以把不规则数组看作是“数组的数组” 。图 5-6
直观地画出了示例程序 5-7 所示不规则数组 myArr 的这种关系,
myArr[0]~myArr[6]是 myArr 的数组元素,但这些元素本身又是数组。例如,myArr[1]
Java 程序设计大学教程
167
是一个元素为 myArr[1][0]~myArr[1][1] 的数组,myArr[6] 是一个元素为
myArr[6][0]~myArr[6][6]的数组。
图 5-6 不规则数组 ArrTest 的结构关系
由此可见,Java 中的不规则数组要比普通的多维数组有更大的灵活性。
5.2.3 排序
上面分别讲述了数组的有关概念,从上可以知道在数组中可以存放大量的数据,相当于一个小型的数据集。接下来将讲述数组的两个基本的操作:数组的排序和查找。这两个操作需要用到排序和查找算法。
由于数组中存有同一数据类型的数据,而有时为了更有效地使用数组,就需要对数组中的元素进行排序,使其按一定的顺序排列好,比如按关键字的值从小到大排序。前面我们讲过,排序算法有很多,如,冒泡排序,选择排序,插入排序,快速排序,合并排序,希尔 ( Shell)
排序、堆排序等。在这里仅讨论一维数组的冒泡排序和快速排序这两个算法,以便重点掌握这两个排序算法的基本原理与实现方法。
1,冒泡排序
冒泡法是最简单的排序方法。这种方法的基本思想是,将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮。在冒泡排序算法中我们要对这个“气泡”
序列处理若干遍。所谓一遍处理,就是自上向下检查一遍这个序列,并时刻注意比较两个相邻元素的顺序是否正确。如果发现两个相邻元素的顺序不对,即轻的元素在下面,就交换它们的位置。显然,处理一遍之后,轻的元素上浮,使得最重的元素沉底;处理二遍之后,次最重的元素就沉到了次底位置。在作第二遍处理时,由于最底层的元素已是最重元素,所以不必检查。一般地,第 i 遍处理时,不必检查第 i 底层位置以下的元素,因为经过前面 i-1
遍的处理,它们已正确地排好序。这样一直进行下去就可以实现对该组数据的排序。
myArr
0
0
2
3
4
5
1
0
6
1
1
2
6
0
..,
myArr[1]
myArr[2]
myArr[6]
myArr[1][0]
myArr[1][1]
myArr[2][1]
myArr[2][0]
myArr[2][2]
myArr[2][0]
myArr[6][6]
..,
Java 程序设计大学教程
168
友情提示
冒泡技术也称为下沉技术(sinking sort technique),因为大数(重的元素)在比较和交换的过程之后,沉到了数组的“底部”。
冒泡排序使用了两重循环,外层循环每次扫描过程中迭代一次;每次内层循环则将某一元素冒泡置顶部(左部) 。我们把流程图和伪代码留给读者作为练习。这个算法的 Java 程序实现如示例程序 5-8 所示。
示例程序 5-8 冒泡排序算法的 Java 程序实现
1,public class Sort {
2,public void bubbleSort(int arr[]) { //bubbleSort 按照升序排列数组元素
3,int temp; // 用于临时存放交换数据的变量
4,for (int pass = 1; pass < arr.length; pass++) { // 循环扫描
5,for (int pair = 1; pair < (arr.length-pass+1); pair++) {
6,if (arr[pair-1] > arr[pair]) { // 比较大小
7,temp = arr[pair-1]; // 交换位置
8,arr[pair-1] = arr[pair]; // 小的数往上冒
9,arr[pair] = temp; // 大的数往下沉
10,}//if
11,}//for
12,}//for
13,}
14,
15,public void print(int arr[]) {
16,for (int k = 0; k < arr.length; k++) // 对于每个元素的值
17,System.out.print( arr[k] + " \t "); // 依次打印
18,System.out.println();
19,}
20,
21,public static void main(String args[]) { //main 方法用于测试冒泡排序
22,int intArr[] = { 2,5,3,8,7,6,9,10,49,25 };
23,Sort sorter = new Sort();
24,System.out.print("数组排序前,"+ " \t ");
25,sorter.print(intArr);
26,sorter.bubbleSort(intArr);
27,System.out.print("数组排序后,"+ " \t ");
28,sorter.print(intArr);
29,}
30,}
上述算法将值较小的元素看作较轻的气泡,值较大的元素看作较重的气泡。每轮比较,
最重的气泡沉到数组底部,较轻的气泡上浮,最终实现了元素数值由小到大的升序排序。注意示例程序 5-8 第 5 行,其中的 pair < (arr.length-pass+1)条件表示每一轮已经沉底的重元素
Java 程序设计大学教程
169
符合排序要求,不再参加循环比较。图 5-7 演示了冒泡排序第一轮比较和交换的过程。在该过程中,从上到下(图示为从左到右)依次扫描比较相邻的元素,通过位置交换,最终将
49 移到了最底层位置上,显然这个最重的元素也就是数值最大的数。
图 5-7 冒泡排序第一轮比较和交换的过程演示
示例程序 5-8 运行后就可以实现对这 10 个数据的排列结果,运行显示如下,
数组排序前,2 5 3 8 7 6 9 10 49 25
数组排序后,2 3 5 6 7 8 9 10 25 49
2,快速排序
由著名计算机科学家霍尔( C.A.R.Hoare)设计的快速排序是对冒泡排序的一种改进。
它(这次以升序排列为例)的基本思想是:首先选取某个元素 u,通过一趟排序将待排序的数组元素分割成独立的两部分,其中前一部分元素的均比 u 小,而后一部分元素均比 u 大,
然后再分别对这两部分元素继续进行排序,被分成的两个部分以后不用再归并,最终达到整个数组序列有序。我们称这种重新整理叫做划分,u 称为划分元素。
假设待排序的是数组,例如 A[s..t],我们可以首先选取第一个元素 A[s](这并不非必需的,仅仅是为了方便)作为划分元素,然后重新排列其余元素,将所有值比它小的元素都安置在它的位置之前,所有值比它大的元素都安置在它的位置之后。这样,以该划分元素所在的位置 i作为分界线,将序列分割成两个子序列 {a[s],a[s+1],…,a[i-1]}和 {a[i+1],a[i+2],…,a[t]}。
这个过程称作一趟快速排序(或一次划分) 。一趟快速排序的具体算法是:附设两个指针 i
和 j,它们的初值分别为 s 和 t,设支点元素为 temp=a[s],则首先从 j 所指位置向前搜索找到第一个关键字小于 temp 的元素和 a[i]互换,然后从 i 所指位置起向后搜索,找到第一个值大于 temp 的元素和 a[j]互换,重复这两个步骤,直到 i=j 为止。
实际算法中,要考虑到交换的数据处在末端的情况。即当 a[i]的值在这组数中最大,此时 a[i]要放置到末端,而不需要和其它数据交换。为此,引入一个新的数据 a[t+1],并假定
a[i] <a[t+1],引入 a[t+1]的目的是为了在特殊的情况下,也能控制程序的顺利进行。
2 3 5 8 7 6 9 10 49 25
2 5 3 8 7 6 9 10 49 25
49 2 3 5 7 6 8 9 10 25
第 2 次比较
第 3 次比较
第一轮的最后一次比较
比较交换
比较
比较
交换
.....,
2 5 3 8 7 6 9 10 49 25 第 1 次比较
比较不交换不交换
Java 程序设计大学教程
170
另外,在排序过程中对划分元素 temp 的赋值是多余的,因为只有在一趟排序结束时,
i=j 的位置才是 temp 的最后位置。由此可先将 temp 暂存,排序过程中只作 a[i]或 a[j]的单向移动,直到一趟排序结束后再将 temp 移至正确的位置上。
5.2.4 查找
数组的查找就是从数组中找出需要的数据项,也叫检索。查找问题的一般提法是:设一数组有 n 个元素,每个元素由一个称为关键字的数据项和若干相关数据项的值组成,对给定值 K,要查找出这 n 个数组元素中是否存在关键字等于 K 的某个元素。通常有两种结果,
一种是能够检索到,即查找成功,此时查找的结果为给出整个元素的信息,或指出该元素所在的位置;另一种是找不到,即失败,此时查找的结果为不成功的信息。
查找的算法很多,有顺序查找、折半查找、散列值查找、转移表查找等。
1,顺序查找
顺序查找方法很简单,就是将要查找的数据的关键字按一定的顺序挨个与数组中的数据进行比较,相等时就找到了所要的数据。
示例程序 5-9 第 3-8 行的 sequentialSearch 方法是顺序查找最常见的实现代码。这种方法使用起来很简单,但查找次数多,速度慢。比如说有一数组的数据项的值分别为,1,2,
3,4,5,...,999,1000 现要找出其中最大的元素 1000,如果是从前向后找,就要检索数组中的每一个元素,对它们进行分析比较,才能找到 1000。
示例程序 5-9 顺序查找和折半查找
1,public class Search {
2,//顺序查找
3,public int sequentialSearch(int arr[],int key) {
4,for (int k = 0; k < arr.length; k++)
5,if (arr[k] == key)
6,return k; // 成功,返回该数组元素的位置 (即索引)
7,return -1; // 失败,返回 -1
8,}
9,
10,//折半查找
11,public int binarySearch(int arr[],int key) {
12,int low = 0; // 初始化
13,int high = arr.length - 1;
14,while (low <= high) {
15,int mid = (low + high) / 2; // 取折半值
16,if (arr[mid] == key)
17,return mid; // 成功,返回该数组元素的位置(即索引)
18,else if (arr[mid] < key)
19,low = mid + 1; // 定位查找上半段
20,else
21,high = mid - 1; // 定位查找下半段
22,}
Java 程序设计大学教程
171
23,return -1; // 失败,返回 -1
24,}
25,}
2,折半查找
若数据已按大小顺序排列好了,则采用折半查找(又称二分检索)方法可以有效地减少检索次数,大大提高检索效率。折半查找的方法是:设有一组已排序的数据序列,用序列的中间项与检索的关键字比较,若相等,则表示找到了要找的数据。若不相等,就进一步比较这两个数的大小,若中间项大于关键字,则下一次用序列的前半部的中间项与该关键字比较;
否则,下一次用序列的后半部的中间项与该关键字比较。这样子每检索一次,就可以使检索区间缩小二分之一,故称为折半查找。如此一直进行下去,直至找到或确定数据序列中没有所要找的数据时为止。例如:已知一个已排好序的数组,其数据元素如下,
( 2,13,19,21,37,56,64,75,80,88,98)
现要查找关键字为 21 的数据元素。首先将 21 与这 11 个数据中处于中间位置数据 56
进行比较,21 小,所以下一次就将查找范围变为( 2,13,19,21,37) ;接下来再将 21 与该范围内处于中间位置的 19 进行比较,21 大,就将下一次的查找范围缩小为( 21,37) ;
然后再对比这个范围内的数据 37,21 小,所以下一次查找的范围为( 21) ;最后比较目标数和中间数,它们相等,所以找到了查找的目标,位置为 3。具体过程如图 5-8 所示。
图 5-8 折半查找的过程演示
由此可知,折半查找每查找一次就将查找范围缩小一半,从而可以大大地减少了比较的次数,提高了查找的效率。其 Java 算法实现代码如示例程序 5-9 第 11-24 行的 binarySearch
方法所示。
2 13 19 21 37 56 64 75 80 88
第一步
第二步
第三步
0 1 2 3 4 5 6 7 8 9 10
98 值索引
2 13 19 21 37 56 64 75 80 88
0 1 2 3 4 5 6 7 8 9 10
98 值索引
2 13 19 21 37 56 64 75 80 88
0 1 2 3 4 5 6 7 8 9 10
98 值索引
2 13 19 21 37 56 64 75 80 88
0 1 2 3 4 5 6 7 8 9 10
98 值索引 第四步
因为 21<37
因为 21>19
( 21=21)找到目标,位置为 3
因为 21<56
折半值 mid=5 low = 0 high=10
low = 0
low = 3
high=4
high=4
mid=2
Java 程序设计大学教程
172
注 意
上述算法只适用于数组是从小到大排列的情况,如果给出的数组是从大到小排列的,则只需要将示例程序 5-9第18行的的判断条件(arr[mid] < key)改为(arr[mid] > key)即可。
5.3 对象容器
在面向对象程序设计中,我们需要对众多的对象进行有效的管理,也就是说在程序运行时能够创建、持有、检索到这些对象。 Java 为我们提供了这样的语言支持,例如前面我们学到的数组就可管理一组对象。这些对象具有相同的类型或特征,我们把它们看作是一个特定的对象集。
使用数组管理对象虽然有较高的计算效率,但是数组要求固定对象的数量,操作起来并不方便。特别当对象数量不明确的情况下,我们需要更复杂的方法来管理对象。
Java 类库中提供了一些用于管理对象集的类,称之为容器类( container classes) 。它们可以在程序中用作对象的容器,持有和操作对象而不用担心容量的变化。
Java 容器的缺点是:一旦把对象放进容器,便失去了该对象的类型信息。容器中对象集的元素都是最基本的 Object 类型,也就是说,无论是何种类型的对象,进入容器后都向上转型为 Object。因此,当我们从容器中取出对象时,可能无法知道它原来的类型。如果知道或者可以通过某种算法来判定出原来的类型,则可以将其转型为最初的实际类型。
5.3.1 Java 容器框架
在程序设计中,我们通常所遇到的对象管理问题可以划分为以下 3 种模式,他们的示意图如图 5-9 所示,
列表(list) 按照一定次序排列的对象集,对象之间有次序关系。
集合(set) 无次序的对象集,但这些对象都是唯一的,不会重复。
映射(map) 一群成对的对象集,这些对象各自保持着“键 -值” ( key-value)对应关系。其中,作为“键”的那些对象一定是一个 set,即这些对象是唯一的,不会重复。
针对以上 3 种模式,Java 提供了 List,Set,Map 这 3 种基本的容器接口,并通过几个具体的派生类加以实现,如表 5-1 所示。
表 5-1 List,Set,Map 这 3 种基本的容器接口与实现
实 现
哈希表 可变数组 平衡树 链表 哈希表 +链表
Set
HashSet TreeSet LinkedHashSet
List
ArrayList LinkedList


Map
HashMap TreeMap LinkedHashMap
List,Set,Map 接口以及 ArrayList,HashSet,TreeMap 等具体实现类都包含在 java.util
包中。 Java 标准类库提供了一个高度集中统一的容器框架,如图 5-10 所示。它设计了一些通用的接口,并提供基于动态数组、哈希表、链表、树等不同数据结构的高效实现。
Java 程序设计大学教程
173
图 5-9 对象容器的 3 种模式
图 5-10 简化的 Java 对象容器框架
从图 5-10 中我们可以看出,与管理对象集有关的接口包括,Collection,List,Set,Map。
其中 Collection 是比 List 和 Set 等高层次的抽象,提供了更通用的公共接口。 Java 容器框架中还提供了 Iterator 接口,用于迭代器的功能实现。所谓迭代器( iterator)目的在于提供一
a d a c
a
b
c
d
e
a
b
c
v23
b
0 1 2 … n 4
v41
v23
v17
d
list
map
set
Java 程序设计大学教程
174
个多用途的、标准化的方法,用于每次访问对象集中的一个元素。由于 Java 容器框架中的实现类都实现了 Iterator 接口的方法,所以通过迭代器可以访问到任意容器中的对象集的元素。
由此可见,Java 容器框架优势在于,
既提供了通用接口,又提供了具体实现。程序员可以根据需要,为接口选择满足特定需求的高效实现,避免了编写“容器”的重复劳动。
通用接口允许不同的具体实现类以相同的方式交互工作,便于程序的扩展和修改。
标准的容器接口还允许程序员开发自己的具体实现类。并融入到 Java 容器的框架中。
5.3.2 Collection 与 Iterator
Collection 是 Java 容器框架中的高层接口,包含了添加、清除、比较和持有对象(也称为对象集的元素)的操作。此外,Collection 接口还提供了一个方法用于返回 Iterator。
Collection 是 Java 容器框架的基础,它声明了所有容器类的核心方法,因此熟悉这些方法对于理解容器框架,使用容器类编程十分有益。 表 5-2 列出了 Collection 定义的主要方法。
表 5-2 Collection 定义的主要方法
方法 说明
boolean add(Object obj)
将 obj对象添加到对象集中,成功则返回 true。
boolean addAll(Collection c)
将 c 中的所有元素加入到对象集中,成功则返回 true。
volid clear()
从对象集中删除所有元素。
boolean equals(Object obj)
比较是否与 obj 对象相等。
boolean isEmpty()
如果所调用的容器类为空,则返回 true
boolean remove(Object obj)
从对象集中删除一个 obj 元素,成功删除返回 true。
boolean removeAll(Collection c)
从对象集中删除 c 中的所有元素,成功删除返回 true。
int size()
返回对象集中元素的个数。
Iterator iterator()
返回所调用容器类的迭代器。
示例程序 5-10 演示了 Collection 的用法。在这个程序中,虽然 Collection 接口分别有
ArrayList 和 HashSet 两种实现,但我们在程序中只使用了 Collection 接口定义的通用方法,
并测试出 List 和 Set 容器的不同特性。
示例程序 5-10 的第 6-12 行演示了通过 Collection 接口的 add 方法可以向对象容器添加不同类型的新对象 (实际上是对象引用) 。 这些对象有的是 Student 类型,有的是 String 类型。
用 ArrayList 实现的 c1 允许包含重复的对象,例如 ZhangSan 对象。程序第 20 行演示了通过
Collection 接口的 addAll 方法可以向对象容器添加对象集,即把容器 c1 中的对象添加到容器 c2。用 HashSet 实现的 c2 不允许包含重复的对象,例如 c2 只保留了最后加入的 Student
类型对象 ZhangSan 和 String 类型对象“李四” 。这反映了 List 和 Set 容器的不同之处。程序第 14-15 行演示了通过 Collection 接口的 remove 方法可以从对象容器删除指定的对象。
示例程序 5-10 Collection 的演示程序
Java 程序设计大学教程
175
1,import java.util.*;
2,
3,//CollectionTest类
4,public class CollectionTest {
5,public CollectionTest() {
6,Student ZhangSan=new Student("张三 ",90);
7,c1.add(ZhangSan);
8,c1.add("张三 ");
9,c1.add("李四 ");
10,c1.add(new Student("王武 ",85));
11,c1.add(new Student("赵榴 ",76));
12,c1.add(ZhangSan);
13,printCollection(c1);
14,c1.remove(ZhangSan);
15,c1.remove("张三 ");
16,printCollection(c1);
17,c2.add(ZhangSan);
18,c2.add("李四 ");
19,printCollection(c2);
20,c2.addAll(c1);
21,printCollection(c2);
22,}
23,
24,public static void main(String[] args){
25,new CollectionTest();
26,}
27,
28,private void printCollection(Collection c) {
29,System.out.println("---------------------");
30,Iterator it=c.iterator();
31,while (it.hasNext()){
32,System.out.println(it.next());//遍历所有对象
33,}
34,
35,}
36,
37,//Collection支持多种容器类的实现
38,Collection c1=new ArrayList();
39,Collection c2=new HashSet();
40,
41,}
42,
43,//Student类
44,class Student {
Java 程序设计大学教程
176
45,public Student(String n,int s) {
46,name=n;
47,score=s;
48,}
49,
50,public String toString(){
51,String s=name+" 成绩,"+score;
52,return s;
53,}
54,
55,String name;
56,int score;
57,}
示例程序 5-10 的运行结果如下,
---------------------
张三 成绩,90
张三
李四
王武 成绩,85
赵榴 成绩,76
张三 成绩,90
---------------------
李四
王武 成绩,85
赵榴 成绩,76
张三 成绩,90
---------------------
李四
张三 成绩,90
---------------------
赵榴 成绩,76
王武 成绩,85
李四
张三 成绩,90
示例程序 5-10 的第 28-35 行使用了迭代器来遍历打印对象容器中的所有对象。迭代器是一个实现了 Iterator 或 ListIterator 接口的对象。它是一种“轻量级”的对象,消耗较小的资源,具有较高的效率,能够满足对象集的遍历。 Iterator 迭代器只能单向移动,即依次访问下一个对象;而 ListIterator 迭代器允许双向遍历。 Iterator 或 ListIterator 作为迭代器的接口提供了迭代器的标准方法供用户使用,用户完全无须知道迭代器在不同容器类中的底层实现。 Iterator 和 ListIterator 接口定义的方法参见表 5-3。
表 5-3 Iterator 和 ListIterator 接口定义的方法
Java 程序设计大学教程
177
方法 说明
boolean hasNext()
如果还存在多余的元素,则返回 true。
Object next()
返回下一个元素。
volid remove()
从列表中删除当前元素。
volid add(Object obj)
将 obj 对象插入列表中。
boolean hasPrevious()
如果还存在前一个的元素,则返回 true。
Object Previous()
返回前一个元素。
int previousIndex()
返回前一个元素的索引。
int nextIndex()
返回下一个元素的索引。
void set(Object obj)
将当前元素赋值为 obj。
注,Iterator仅包含表中前 3个方法。 ListIterator包含表中所有方法。
5.3.3 List 及 ListIterator
我们在编程中最常用到的是 List 容器。 List 容器的重要属性是对象按次序排列,它保证以某种特定次序来维护元素。 List 继承并扩展了 Collection,增加了更丰富的对象管理操作。
List 还能产生 ListIterator,通过它可以双向遍历对象集,并能在 List 中进行元素的插入和删除。 List 定义的方法如表 5-4 所示。
表 5-4 List 定义的方法
方法 说明
void add(int index,Object obj)
将 obj 对象插入列表的 index 参数指定位置。
boolean addAll(int index,
Collection c)
将 c 中的所有元素插入到列表中,成功则返回 true。
Object get(int index)
从列表中返回 index 参数指定位置的元素。
Object set(int index,Object obj)
对列表中 index 参数指定位置的元素赋值。
int indexOf(Object obj)
返回首个 obj 对象在列表的位置(索引值),
如果没有该对象则返回 -1。
int lastIndexOf(Object obj)
返回末个 obj 对象在列表的位置(索引值),
如果没有该对象则返回 -1。
Object remove(int index)
从列表中删除索引值等于 index 参数的元素,
并将删除的对象返回。删除后列表收缩,即被删除元素后续的元素索引值减 1。
List subList(int start,int end)
截取列表,返回的子列表包含从索引 start 到
end-1 的元素。
ListIterator listiterator()
返回列表的迭代器,迭代从头开始。 。
ListIterator listiterator(int
index)
返回列表的迭代器,迭代从 index 参数指定的位置开始。
List 通常由 ArrayList 和 LinkList 来实现,Arraylist 功能一般的,优点在于可随机访问其中元素; LinkedList 功能较强的,具备一组更通用的方法。他们特点如下,
ArrayList 以可变数组实现完成的 List 容器。允许快速随机访问,但是当元素的插入或移除发生于 List 中央位置时,效率便很差。对于 ArrayList,建议使用
Java 程序设计大学教程
178
ListIterator 来进行向后或向前遍历,但而不宜用其来进行元素的插入和删除,因为所花代价远高于 LinkedList。
LinkedList 以双向链表 ( double-linked 1ist) 实现完成的 List 容器。 最佳适合遍历,
但不适合快速随机访问。插入和删除元素效率较高。它还提供 addFirst,addLast、
getFirst,getLast,removeFirst,removeLast 等丰富的方法,可用于实现栈和队列的操作。
因为 ArrayList 和 LinkedList 都实现了 List 及 ListIterator 接口,所以不论如何使用哪一个,程序都会产生相同结果。但是考虑到其具体实现方式决定他们各有不同的优势,在具体程序需求下我们要尽可能选择合适的 List 容器。
示例程序 5-11 演示了 List 及 ListIterator 的用法。在这个程序中,虽然 List 接口分别有
ArrayList 和 LinkedList 两种实现,但我们在程序中只使用了 List 及 ListIterator 的接口定义的通用方法,而无需关心底层实现。
通过演示程序,我们可以直观地理解列表的插入、删除、定位、截取、遍历等操作。需要注意的是,删除列表元素后列表收缩,即被删除元素后续的元素索引值会有变化。因此使用 for 循环遍历删除时,应该从后往前逆序删除,以免出错。示例程序 5-11 给出了 2 种遍历删除代码,对 LinkedList 建议使用迭代器遍历删除而 ArrayList 则更适合使用 for 循环遍历删除。
示例程序 5-11 List 及 ListIterator 接口演示程序
1,import java.util.*;
2,
3,public class ListTest {
4,public ListTest() {
5,Student ZhangSan=new Student("张三 ",90);
6,
7,//顺序插入元素
8,System.out.println("-----------[演示 1] 顺序插入元素
---------------------");
9,list1.add(0,ZhangSan);
10,list1.add(1,"张三 ");
11,list1.add(2,"李四 ");
12,list1.add(3,new Student("王武 ",85));
13,list1.add(4,new Student("赵榴 ",76));
14,list1.add(5,ZhangSan);
15,printCollection(list1);
16,
17,//删除元素(对于 LinkedList 建议使用迭代器遍历删除)
18,System.out.println("-----------[演示 2] 删除元素
---------------------");
19,it=list1.listIterator();
20,while (it.hasNext()){
21,Object o=it.next();
22,if (o instanceof String){
23,System.out.println("String 对象 [ "+o
Java 程序设计大学教程
179
24,+" ] ——从列表中清除! ");
25,it.remove();
26,}
27,}
28,/* 使用循环遍历时,要考虑删除元素后的索引变化,因此需要使用逆序循环。
29,for (int i=5;i>-1;i--) {
30,if (list1.get(i) instanceof String){
31,System.out.println("String 对象 [ "+list1.remove(i)
32,+" ] ——从列表中清除! ");
33,}
34,}
35,*/
36,printCollection(list1);
37,
38,//逆序插入元素
39,System.out.println("-----------[演示 3] 逆序插入元素
---------------------");
40,list2.add(0,ZhangSan);
41,list2.add(0,"李四 ");
42,printCollection(list2);
43,
44,//插入列表
45,System.out.println("-----------[演示 4] 插入列表
---------------------");
46,list2.addAll(0,list1);
47,printCollection(list2);
48,
49,//定位元素
50,System.out.println("-----------[演示 5] 定位元素
---------------------");
51,System.out.println("首个 [ " +ZhangSan+" ] 对象位于 "
52,+list2.indexOf(ZhangSan));
53,System.out.println("末个 [ " +ZhangSan+" ] 对象位于 "
54,+list2.lastIndexOf(ZhangSan));
55,
56,//截取子列表
57,System.out.println("-----------[演示 6] 截取子列表
---------------------");
58,list1=list2.subList(1,5);
59,printCollection(list1);
60,}
61,
62,public static void main(String[] args){
63,new ListTest();
Java 程序设计大学教程
180
64,}
65,
66,private void printCollection(List list) {
67,it=list.listIterator();
68,int n=0;
69,while (it.hasNext()){
70,System.out.println(n+":"+it.next());
71,n++;
72,}
73,}
74,
75,//List 支持不同列表类的实现
76,List list1=new LinkedList();
77,List list2=new ArrayList();
78,ListIterator it;
79,
80,}
81,
82,//Student类
83,class Student {
84,public Student(String n,int s) {
85,name=n;
86,score=s;
87,}
88,
89,public String toString(){
90,String s=name+" 成绩,"+score;
91,return s;
92,}
93,
94,String name;
95,int score;
96,}
程序运行结果,
-----------[演示 1] 顺序插入元素 ---------------------
0:张三 成绩,90
1:张三
2:李四
3:王武 成绩,85
4:赵榴 成绩,76
5:张三 成绩,90
-----------[演示 2] 删除元素 ---------------------
String对象 [ 张三 ] ——从列表中清除!
Java 程序设计大学教程
181
String对象 [ 李四 ] ——从列表中清除!
0:张三 成绩,90
1:王武 成绩,85
2:赵榴 成绩,76
3:张三 成绩,90
-----------[演示 3] 逆序插入元素 ---------------------
0:李四
1:张三 成绩,90
-----------[演示 4] 插入列表 ---------------------
0:张三 成绩,90
1:王武 成绩,85
2:赵榴 成绩,76
3:张三 成绩,90
4:李四
5:张三 成绩,90
-----------[演示 5] 定位元素 ---------------------
首个 [ 张三 成绩,90 ] 对象位于 0
末个 [ 张三 成绩,90 ] 对象位于 5
-----------[演示 6] 截取子列表 ---------------------
0:王武 成绩,85
1:赵榴 成绩,76
2:张三 成绩,90
3:李四
编程技巧
示例程序 5-11的第22行通过instanceof来判断o是不是String类型的实例变量,如果是则删除,不是则保留。这样程序从列表中删除了String类型的对象,保留了Student类型的对象。前面我们讲过,无论是何种类型的对象,进入容器后都向上转型为Object。因此,当我们从容器中取出对象时,
可能无法知道它原来的类型。但通过示例程序的这段代码,我们可以判定出容器中对象原来的类型,并进行处理,包括可以将其转型为最初的实际类型。
5.4 抽象数据类型
抽象数据类型( Abstract Data Type,简称 ADT)是把与对该数据类型有意义的操作封装在一起的数据声明。将数据和操作封装起来并对用户隐藏。用户可通过操作接口对数据进行输入、存取、修改和删除等操作。用户使用抽象数据类型时不需要知道数据结构。这就是抽象数据类型的含义,其主要体现了一个抽象的概念。
有的教科书上将面向对象程序设计中的类也看作是一种抽象数据类型。 本书将面向对象程序设计中的类、接口等类型的概念与抽象数据类型的概念分开讨论。本节将重点讨论的抽象数据类型,包括:链表、栈、对列。
Java 程序设计大学教程
182
5.4.1 链表
抽象数据类型的一个实例就是线性列表。线性列表是一种具有顺序结构的列表,在该列表中每个元素都有唯一的后继元素。线性列表可以通过链表来实现。前面我们讲到的
LinkedList 就是典型的链表。
1,几种链表类型
( 1) 单链表
链表是一组元素的序列,在这个序列中每个元素总是与它前面的元素相链接(除第一个元素外),从而形成单链表。单链表关系的实现可以通过指针(引用的地址)来描述。图 5-11
就是一个链表的示意图。
图 5-11 链表示意图
链表中的元素也称为节点,第一个节点称为表头,最后一个节点称为表尾。指向表头的指针称为头指针,在这个指针变量中存放着表头的地址。 节点用记录描述,至少包含两个域,
一个域用来存放数据,其类型根据存放数据的类型而定,称为值域;另一个域用来存放下一个节点的地址,称为指针域。表尾不指向任何节点,是一个空指针。对单链表的操作有创建、
插入、删除等。
( 2) 循环链表
将单链表的形式稍加改动,让表中最后一个节点的指针指向单链表的表头节点,这样就形成了一个循环链表,如图 5-12 所示。
图 5-12 循环链表的示意图
使用循环链表的主要优点是,从表中任一节点均可找到表中其它的节点。
( 3) 双向链表
在单链表中,从任何一个节点能通过指针域找到它的后继节点,但无法找到它的前趋节
1000 A
1200
B
1312
C
1423
head 1000 1200 1312
D
1423
空指针 指针
节点
h
r
非空循环链表
r h
空 表
链接
Java 程序设计大学教程
183
点,而双向链表则正是弥补了单链表的这个不足。在双向链表中的每一个节点除了数据字段外,还包含两个指针,一个指针指向该节点的后继节点,另一个指针指向它的前趋节点;双向链表有两个好处:一是可以从两个方向搜索某个节点;二是提高了链表的可靠性,因为无论利用向前这一链还是向后这一链,都可以遍历整个链表,如果有一根链失效,还可以利用另一根链修复整个链表。
2,链表的应用
链表在编程中的应用主要在于管理大量的字符串、数值、对象等,就其操作的便利性而言往往可以取代动态数组。在 Java 类库中,LinkedList 就是一个可以当作链表使用的抽象数据类型。它能实现链表常用的操作,包括:增加、删除、定位、遍历等,这足够解决一切关于有序列表的问题。示例程序 5-11 演示了 LinkedList 的增加、删除、定位、遍历等操作。
由于 LinkedList 所存储的是对象引用,它使用引用来链接对象,因此 LinkedList 不适用于基本数据类型。链表的一个元素(节点)允许为 null,此时表示没有链接对象。
当删除链表的一个元素(节点)时,该元素后续的各元素索引值将发生变化(例如:被删除的元素索引为 6,其后续元素的索引将由原来的 7 改变为 6,各后续元素依次发生这种
index=index-1 的变化,元素总数减少),因此遍历链表过程中有删除操作发生时,应该采用逆序遍历,否则会出现超界错误。
接下来我们还可以看到,使用链表能实现栈和队列。
5.4.2 栈
栈是限定仅在一端进行插入或删除操作的线性表。对于栈来说,允许进行插入或删除操作的一端称为栈顶( top),而另一端称为栈底( bottom) 。假设有一个栈 S=( a
1
,a
2
,...,a
n
),a
1
先进栈,a
n
最后进栈。出栈时只能在栈顶进行,所以 a
n
先出栈,a
1
最后出栈。因此又称栈为后进先出( Last In First Out,简称 LIFO)线性表。
栈有两种存储结构即顺序存储结构和链式存储结构。 栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时设指针 top 指示栈顶元素的当前位置。空栈的栈顶指针值为零。栈的链式存储结构由其栈顶的指针唯一确定。
栈有许多操作,基本操作有入栈和出栈。
入栈( push) 如图 5-13 所示,入栈是在栈顶添加新的元素。入栈后,新元素成为栈顶元素。 入栈操作要注意栈的空间足够大,以防止栈内没有空间来容纳新元素,
否则栈将处于溢出状态,不能添加元素。
出栈( pop) 如图 5-14 所示,出栈是将栈顶的元素移走并返回给用户。当最后一个元素被删除后,栈必须设为空状态。当栈为空的时候调用出栈操作,栈将处于下溢状态。
Java 程序设计大学教程
184
图 5-13 入栈操作
图 5-14 出栈操作
示例程序 5-12 是我们设计的一个简单的栈演示程序。该程序中我们用 LinkedList 制作了一个 StackL 栈,并实现了栈的 push 和 pop 操作。通过 push 和 pop 操作,我们演示了将一首古词中的句子进行了倒转,看起来这首词顺着读和倒着读都蛮有情趣。
示例程序 5-12 一个简单的栈演示程序
1,import java.util.*;
2,
3,public class StackL {
4,
5,/** Creates a new instance of StackL */
6,public StackL() {
7,list=new LinkedList();
8,}
9,
10,public static void main(String[] args){
11,StackL stack= new StackL();
12,System.out.print("入栈,"+" ");
13,String s="林花谢了春红 ";

栈顶

栈顶
数据
入栈操作

栈顶

栈顶
数据
出栈操作
Java 程序设计大学教程
185
14,System.out.print(s+" ");
15,stack.push(s);
16,s="太匆匆 ";
17,System.out.print(s+" ");
18,stack.push(s);
19,s="无奈朝来寒重 ";
20,System.out.print(s+" ");
21,stack.push(s);
22,s="晚来风 ";
23,System.out.print(s+" ");
24,stack.push(s);
25,System.out.println("");
26,System.out.print("出栈,"+" ");
27,System.out.print(stack.pop()+" ");
28,System.out.print(stack.pop()+" ");
29,System.out.print(stack.pop()+" ");
30,System.out.print(stack.pop()+" ");
31,}
32,
33,public Object pop(){
34,return list.removeFirst();
35,}
36,
37,public void push(Object o){
38,list.addFirst(o);
39,}
40,
41,private LinkedList list;
42,
43,}
程序运行结果,
入栈,林花谢了春红 太匆匆 无奈朝来寒重 晚来风
出栈,晚来风 无奈朝来寒重 太匆匆 林花谢了春红
栈的应用非常广泛,包括倒转数据的应用和回溯。
倒转数据是对一组给定的数据,将其中的数据元素重新排放位置,就像示例程序 5-12
中的倒转词句。
回溯,即回到(或恢复到)前面的数据,在计算机游戏、决策分析和专家系统等应用程序中经常见到。栈的后进先出规则符合时光倒流时的事件恢复顺序。有关用栈实现回溯的应用实例,有兴趣的读者可以参见,Delphi 模式编程,(刘艺著,机械工业出版社 2004 年 9
月出版)的第 392 页,该示例讨论了一个使用备忘录模式的地理信息系统如何利用栈回溯到先前的标注状态。
Java 程序设计大学教程
186
5.4.3 队列
队列也是线性列表的一种特殊情况,其所有的插入均限定在表的一端进行,而所有的删除则限定在表的另一端进行。允许插入的一端称队尾,允许删除的一端称队头。队列的结构特点是先进队列的元素先出队列。因此,通常把队列叫做先进先出( First In First Out,简称
FIFO)线性表。
队列是最常用的抽象数据类型之一,事实上,在所有的操作系统以及网络中都有队列的身影,在其它技术领域更是数不胜数。例如处理用户请求、任务和指令。在计算机系统中,
需要用队列来完成对作业或对系统设备如打印池的处理。
同样,我们也能用 LinkedList 制作了一个队列,并实现了队列的入列( enqueue)和出列( dequeue)操作。示例程序 5-13 演示了通过队列类 Queue 的 enqueue 和 dequeue 方法所实现的先进先出规则。
示例程序 5-13 一个简单的队列演示程序
1,import java.util.*;
2,
3,public class Queue {
4,
5,/** Creates a new instance of Queue */
6,public Queue() {
7,list=new LinkedList();
8,}
9,
10,public static void main(String[] args){
11,Queue queue= new Queue();
12,System.out.print("入列,"+" ");
13,String s="林花谢了春红 ";
14,System.out.print(s+" ");
15,queue.enqueue(s);
16,s="太匆匆 ";
17,System.out.print(s+" ");
18,queue.enqueue(s);
19,s="无奈朝来寒重 ";
20,System.out.print(s+" ");
21,queue.enqueue(s);
22,s="晚来风 ";
23,System.out.print(s+" ");
a
1
a
2
a
3
a
n
队尾
队头
入列
出列
Java 程序设计大学教程
187
24,queue.enqueue(s);
25,System.out.println("");
26,System.out.print("出列,"+" ");
27,System.out.print(queue.dequeue()+" ");
28,System.out.print(queue.dequeue()+" ");
29,System.out.print(queue.dequeue()+" ");
30,System.out.print(queue.dequeue()+" ");
31,}
32,
33,public void enqueue(Object o){
34,list.addLast(o);
35,}
36,
37,public Object dequeue(){
38,return list.removeFirst() ;
39,}
40,
41,private LinkedList list;
42,
43,}
程序运行结果,
入列,林花谢了春红 太匆匆 无奈朝来寒重 晚来风
出列,林花谢了春红 太匆匆 无奈朝来寒重 晚来风
5.5 本章习题
复习题
[习题 1] 什么是算法?算法有哪些特征?
[习题 2] 常用算法包括那些?
[习题 3] 什么是排序算法?排序算法有哪些?
[习题 4] 什么是查找算法?查找算法有哪些?
[习题 5] 什么是抽象数据类型?常见的抽象数据类型有哪些?
[习题 6] 什么是数组? Java 中数组可分为哪两类?
[习题 7] 如何理解 Java 对象容器? Java 容器框架的接口包括哪些?
测试题
[习题 8] 以下方式不可以用来表示算法的是 。
A、伪代码
B、流程图
C、数学公式
D,PAD 图
Java 程序设计大学教程
188
[习题 9] 累加求和是
A、基本算法
B、排序算法
C、查找算法
D、迭代和递归算法
[习题 10] 是用于编写解决问题的算法的两种途径。
A、求最大值和最小值
B、函数和过程
C、查找和排序
D、迭代和递归
[习题 11] 以下代码运行后,则选择项中表达式为真的是
int[] arr = {1,2,3};
for (int i=0; i < 2; i++)
arr[i] = 0;
A,arr[0] == 0
B,arr[0] == 1
C,arr[l] == 1
D,arr[2] == 0
[习题12] 对一组数据( 84,47,25,15,21)排序,数据的排列次序在排序的过程中变化如为,
( 1) 84 47 25 15 21
( 2) 15 47 25 84 21
( 3) 15 21 25 84 47
( 4) 15 21 25 47 84
则采用的排序方法是 。
A、选择排序
B、冒泡排序
C、快速排序
D、插入排序
[习题 13] 关于下面数组定义的正确说法是
String[][] s = new String[10][];
A、数组 s 定义不合语法。
B,s 是 10× 10 的 2 维数组。
C,s 数组的所有元素都是 ""。
D,s 是 10 个数组的数组。
Java 程序设计大学教程
189
[习题 14] 以下说法不正确的是 。
A、在 Java 中,i×j 的 2 维数组每一行的元素都是不变的。因此,我们无法设计出一个数组,要求第
i 行有 j=i 个元素,即这个数组能使 j 随着 i 变化。
B、我们能用 LinkedList 制作一个队列,并实现队列的入列( enqueue)和出列( dequeue)操作。
C、链表常用的操作,包括:增加、删除、定位、查找、遍历等。
D、对于没有排序的数列无法使用折半查找。
[习题 15] 以下创建数组的代码不正确的是 。
A,
String books[][];
books = new String[10][4];
B,
String books[][];
books[][] = new String[10][4];
C,
String books[][] = new String[10][4];
D,
String[][] books;
books = new String[10][4];
[习题 16] 在程序设计中,我们希望管理的对象按照一定次序排列成对象集,对象之间有次序关系,并允许有重复的对象。此时可以选用的对象容器是 。
A,TreeSet。
B,LinkedHashMap
C,HashSet。
D,LinkedList。
[习题 17] List 定义的方法中能够从列表中返回 index 参数指定位置的元素的是 。
A,set(int index,Object obj)
B,get(int index)
C,indexOf(Object obj)
D,listiterator(int index)
练习题
[习题 18] 以下代码欲实现将两个参数值进行互换的算法,请问是否有错?如果有错,错在何处?
public void swap (int a,int b) {
int tmp = a;
a = b;
b = tmp;
}
[习题 19] 编译以下程序,
public class Change {
Java 程序设计大学教程
190
public static void main(String[] args) {
Changer c = new Changer();
c.method(args);
System.out.println(args[0] + " " + args[1]);
}
}
class Changer {
void method(String[] s) {
String temp = s[0];
s[0] = s[1];
s[1] = temp;
}
}
并通过以下命令行运行该程序,
java Change Beijing 2008
试分析运行结果。
[习题 20] 画出冒泡排序的流程图,并给出伪代码。
[习题 21] 分析下列程序运行结果,并将下列程序改写为递归形式。
public class NonRecursive {
public static void main(String args[]) {
NonRecursive re=new NonRecursive();
re.hello(5);
}
public void hello(int N) {
for (int i=N;i>0;i--)
System.out.println("Hello"+i);
}
}
[习题 22] 分析下列程序运行结果,并改写为迭代形式。
public class Recursive {
public static void main(String args[]) {
Recursive re=new Recursive();
System.out.println(re.exam(5));
}
public int exam(int x) {
int j=1;
if (x>0) j=x*exam(x-1);
Java 程序设计大学教程
191
return j;
}
}
[习题 23] 若一本书厚 250 页,每页 20 行,每行 40 个字符。假设全书的内容已经存入了三维数组变量 book 中,其下标为 page,line 和 column。要求编程序在屏幕上输出任意指定的连续几页上的内容(即从某起始页 startp 显示至某结束页 endp) 。
[习题 24] 给定一串正实数数列,求出所有递增和递减子序列的数目,如数列 7.34,2.2,6.8、
9.09,8.81,3,5.2,2.3,1.8,则可以分为( 7.34,2.2),( 2.2,6.8,9.09),( 9.09、
8.81,3),( 3,5.2),( 5.2,2.3,1.8)五个子序列,答案就是 5。现在要求随机产生 10 个正实数的数列,然后进行处理,最后输出答案,请编程实现。
[习题 25] 设计一个学生成绩管理程序。要求能够,
1、增减学生对象(该对象包含学号、姓名、分数等信息) 。
2、登记、编辑分数。
3、计算出平均成绩。
4、统计出优秀( 100-90),良好( 89-75),中等( 74-60),不及格( 59-0)的人数百分比。
5、按学号查找学生及成绩。
6、按成绩分数排序。
7、通过程序代码完成对以上功能的测试并显示在屏幕上。
(建议作为课程设计)
Java 程序设计大学教程
234
第 7章 程序设计案例分析
在上一章中,我们学习了 Java 图形界面应用程序设计。 Java 为我们设计图形界面应用程序提供了丰富的 GUI 组件,但是 Java 图形界面应用程序的设计仍然比较复杂。本章我们配合这章内容将介绍一个设计案例——单词赢家软件。 通过剖析和研究该应用程序的设计过程,我们不仅可以对窗体、菜单、组件、事件、布局等的设计有一个感性的综合的了解,还可以掌握使用开发工具( NetBeans IDE)完成开发项目的一般过程和方法,积累实际编程经验。
单词赢家是一个实用的背单词软件,可以加载不同级别(例如:大学英语 6 级词汇)的词库文件,并以 50 个词汇为一组进行测试。词汇分组可以按照随机方式和顺序方式进行。
随机方式在词库中随机抽取 50 个单词;而顺序方式则按照 A-Z 的顺序 50 个单词为一组,
依次划分成若干组,用户根据组号选择。前一种方式用于测试词汇量,后一种方式用于背记单词。
单词赢家的使用界面是常见的选择题形式,既可以设置为英选中的英译汉测试,也可以设置为中选英的汉译英测试,并及时给出测试评判结果和正确答案。运行效果如图 7-1 所示。
图 7-1 单词赢家的运行界面
这一章我们将对单词赢家的设计过程和程序代码做一个完整分析,通过案例教学来掌握
Java 应用程序的设计和开发。
Java 程序设计大学教程
235
7.1 可视化程序设计与 NetBeans IDE
可视化程序设计是指基于图形用户界面( GUI)和可视化组件的一种快速应用开发
( RAD)方法;可视化程序设计可以根据用户需求快速建立原型,验证并实现用户需求;
可视化程序设计还通过所见即所得的效果提高了编程工作的效率。虽然 Java 传统上依赖于手工编程,无法完全获得可视化编程工具的支持,但还是有一些优秀的 Java 可视化编程工具提供了功能有限的可视化程序设计开发环境,并可以显示设计界面,自动生成一些辅助代码,帮助我们提高编程效率。 NetBeans IDE 是其中一种支持 Java 可视化编程的优秀工具,
也是本教材选用的 Java 开发工具。
前面我们讲过,Java 提供了一组用户界面组件,可以使用它们来生成图形用户界面
( GUI) 。可视化程序设计通常是将组成 GUI 的所有组件“放置”在窗体容器中,并撰写对应的事件处理程序。 NetBeans IDE 提供了一些工具,可帮助我们以可视化方式设计和生成
Java 窗体,简化图形用户界面的设计过程,这些工具包括,
窗体编辑器 在 NetBeans IDE 中提供用于设计 GUI 的主工作区。可用来在“源”
和“设计”视图中查看窗体。
检查器窗口 显示当前活动窗体中包含的所有组件的树状分层结构,包括可视组件和容器,如,:按钮、标签、菜单和面板等。它还可以包括非可视组件,如定时器和数据源。
组件面板窗口 包含可添加到窗体上的可视组件。 可以将组件面板窗口设置为将其内容仅显示为图标,或者显示为带有组件名称的图标。
属性窗口 显示当前选定组件的可编辑设置。
连接向导 帮助在窗体中的组件之间设置事件,而无需编写代码。
图 7-2 NetBeans IDE 的可视化设计环境
窗体编辑器
检查器窗口
组件面板窗口
属性窗口
项目窗口
Java 程序设计大学教程
236
以上工具或窗口如图 7-2 所示。在 NetBeans IDE 的可视化设计环境中,我们使用窗体编辑器来进行可视化设计;从组件面板窗口拖放组件;在属性窗口设置组件属性;在检查器窗口中查看容器与组件的关系并调整布局。
每当打开一个 GUI 窗体时,NetBeans IDE 都将在带有切换按钮的窗体编辑器标签中显示它,可以使用这些按钮在“源”和“设计”视图之间进行切换。可使用“设计”视图可视化处理 GUI 窗体;而“源”视图则允许直接编辑窗体的源代码。
通常,可使用组件面板窗口将组件添加到窗体中,并在窗体编辑器中排列这些组件。然后结合使用检查器窗口和属性窗口,来检查并调整窗体组件和布局管理器的属性,管理组件事件处理程序以及定义生成代码的方式。可视化创建和修改图形用户界面时,NetBeans IDE
将自动生成和更新实现它的 Java 代码。
在“设计”视图中处理某个窗体时,窗体编辑器将自动生成代码,并在“源”视图中以蓝色背景显示这些代码。此代码称为“受保护文本”,不能直接对其进行编辑。
窗体编辑器生成的受保护文本包括,
组件变量声明块。
initComponents 方法,在其中执行窗体初始化。此方法是从窗体构造函数调用的,
尽管不能手动编辑这种方法,但可以在组件属性表单中编辑代码属性以影响其生成方式。
所有事件处理程序的标题(和尾随的结束大括号) 。
NetBeans IDE 中的 GUI 窗体分层结构。
7.2 设计窗体
Java 应用程序的窗体包括:用作顶层窗体的框架窗体( JFrame) ;用于放置内容的面板窗体( JPanel) ;用于用户交互的对话框窗体( JDialog)等。设计窗体除了要设计窗体中的
GUI 组件和布局外,还要设计相关的事件处理程序。
7.2.1 创建主窗体和主面板
首先,我们创建一个 WordWinner 应用程序项目。 NetBeans IDE 的 Java 应用程序项目创建方法详见 1.4.3 小节的示例。然后在该项目中创建主窗体 MainFrom。
在 NetBeans IDE 中创建新窗体的步骤如下,
从主菜单中选择菜单项:,文件 ->新建文件” 。
在“新建文件”向导中展开,Java GUI 窗体”节点,然后选择一个窗体模板——
,JFrame 窗体”,如图 7-3 所示。然后单击下一步。
在如图 7-4 所示的“新建 JFrame 窗体”向导中输入新建窗体的名称和位置。然后单击完成。
NetBeans IDE 将创建具有选定类型的空白窗体,并在“编辑器”标签的“设计”视
注 意
要使用NetBeans IDE的窗体编辑器进行可视化设计,必须使用通过
NetBeans IDE的GUI窗体模板创建的文件。目前还无法在窗体编辑器中编辑在
NetBeans IDE之外创建的GUI窗体。这就是说,你手工编写的GUI窗体及组件是无法在窗体编辑器中可视化显示出来的。
Java 程序设计大学教程
237
图中打开该窗体。
图 7-3,新建文件”向导
图 7-4,新建 JFrame 窗体”向导
示例程序 7-1 NetBeans IDE 自动生成包含基本框架代码的 MainForm.java 文件
Java 程序设计大学教程
238
1,public class MainForm extends javax.swing.JFrame {
2,
3,/** Creates new form MainForm */
4,public MainForm() {
5,initComponents();
6,}
7,
8,/** This method is called from within the constructor to
9,* initialize the form,
10,* WARNING,Do NOT modify this code,The content of this method is
11,* always regenerated by the Form Editor,
12,*/
13,private void initComponents() {
14,
15,setDefaultCloseOperation(
16,javax.swing.WindowConstants.EXIT_ON_CLOSE);
17,pack();
18,}
19,
20,/**
21,* @param args the command line arguments
22,*/
23,public static void main(String args[]) {
24,java.awt.EventQueue.invokeLater(new Runnable() {
25,public void run() {
26,new MainForm().setVisible(true);
27,}
28,});
29,}
30,
31,// Variables declaration - do not modify
32,// End of variables declaration
33,
34,}
一旦我们创建好一个 JFrame 窗体,NetBeans IDE 会为该窗体自动生成包含一些基本代码的 MainForm.java 文件,如示例程序 7-1 所示。其中蓝色区域部分,如第 13-18 行的
initComponents 方法,是不允许用户修改的。但是我们仍然可以编辑蓝色只读区域以外的代码,例如在 MainForm 的构造函数中撰写代码,设置主窗体的内容、大小、标题、位置等。
最后我们修改后的 MainForm 的构造函数如示例程序 7-2 所示。
示例程序 7-2 修改后的 MainForm 的构造函数
public MainForm() {
initComponents();
Java 程序设计大学教程
239
//加入组件
Container container =getContentPane();
mainPanel=new MainPanel();
container.add(mainPanel);
int width=600;
int height=400;
setSize(600,400);//设置框架大小
setTitle("单词赢家 ");//设置框架标题
//将框架显示在屏幕正中
Toolkit kit= Toolkit.getDefaultToolkit();
Dimension screenSize=kit.getScreenSize();
int x=(screenSize.width-width)/2;
int y=(screenSize.height-height)/2;
setLocation(x,y);//设置框架位置
}
主窗体仅仅是一个顶层容器,其中加入到主窗体中的 MainPanel 面板才是我们要花心思设计的图形界面。
在 NetBeans IDE 中创建面板的步骤基本同创建主窗体,只不过在如图 7-3 所示的“新建文件”向导中选择的是,JPanel 窗体”并将其命名为 MainPanel 而已。同样系统会自动生成 MainPanel.java 文件,包括一些基本代码,但是我们需要自己为 MainPanel 面板添加 GUI
组件,编写事件处理程序。
7.2.2 组件与布局设计
创建 JPanel 窗体 MainPanel 后,可以接着添加一些组件,这些组件提供程序所需的显示信息和控制功能。可视化添加组件的方法如下,
null 使用“组件面板”窗口添加组件
在图 7-5 左图所示的“组件面板”窗口中,通过单击组件图标来选择一个组件。在窗体编辑器中,单击要在其中放置组件的容器。 NetBeans IDE 将把组件添加到窗体的选定容器中。如果要添加同一组件的多个实例,请按下 Shift 键,然后在窗体编辑器中单击要放置该组件的每个区域。如果要向“其他组件”节点中添加组件,请单击窗体编辑器中的空白区域。
null 使用“检查器”窗口添加组件
在“检查器”窗口中,选择要在其中添加组件的容器。单击鼠标右键以显示上下文菜单,
然后从“从组件面板上添加”子菜单中选择组件。 NetBeans IDE 将把组件添加到窗体的选定容器中。
Java 程序设计大学教程
240
图 7-5 组件面板上的组件与布局图标
添加组件的次序是,先添加局部容器组件,再添加 GUI 组件。例如,我们先添加局部容器组件有一个 JScrollPane 和 2 个 JPanel,分别命名为 jScrollPane1,buttonPanel、
optionsPanel。然后再分别在这些容器中添加 GUI 组件,如 optionsPanel 中添加的 4 个
JRadioButton 组件。系统会给添加的组件自动命名,但是我们可以通过鼠标右键的上下文菜单,选择“重命名,..”菜单项来更改组件名称,同时还可以选择“属性”菜单项来设置组件的属性。
完成添加组件的工作后,我们可以在“检查器”窗口察看容器及 GUI 组件的层次关系,
如图 7-6 所示。
图 7-6 MainPanel 的可视化设计界面
Java 程序设计大学教程
241
对于初学者来说,可能会发现 Java 的可视化设计并不像在 Delphi 或 VB 中那样顺手。
在 NetBeans IDE 中拖放组件,调整其位置和大小时,这些组件往往不听使唤。 这是因为 Java
容器的缺省布局造成的。要将界面中的组件随心所欲地排列,首先要把容器的布局改为
AbsoluteLayout。这样,就很容易把程序界面设计成如图 7-6 所示那样美观。
AbsoluteLayout 是特殊的 NetBeans IDE 布局管理器,可使用它将组件准确放在窗体中的所需位置、在 NetBeans IDE 中任意移动组件,以及使用组件选择边框调整其大小。这对于生成原型特别有用,因为没有任何格式限制,并且不需要输入任何属性设置。但是,
AbsoluteLayout 不适合用于实际的应用程序,因为当环境发生改变时,组件的固定位置和大小并不会随之改变。
记住,新创建的容器都是使用自己的缺省布局管理器,例如 JPanel 使用缺省布局
FlowLayout。在 NetBeans IDE 中更改容器的布局很方便,可使用“组件面板”窗口、窗体编辑器或“检查器”窗口来更改大多数容器的布局。
null 在窗体编辑器中设置布局管理器
右键单击要更改布局的容器。在上下文菜单中,从“设置布局”子菜单中选择所需的布局。
null 在“组件面板”窗口中设置布局管理器
在“组件面板”窗口中单击“布局”,然后选择该布局,如图 7-5 右图所示。接着在窗体编辑器中单击要更改布局的容器。
null 在“检查器”窗口中设置布局管理器
右键单击要更改布局的容器的节点。在上下文菜单中,从“设置布局”子菜单中选择所需的布局。 NetBeans IDE 将指定的布局管理器应用于选定容器。
在本案例中,对于局部容器 jScrollPane1,buttonPanel 和 optionsPanel 来说布局比较简单,
buttonPanel 采用了 BoxLayout,optionsPanel 采用了 FlowLayout。而 MainPanel 面板自身因为包含了 3 个局部容器和 1 个 JLabel 组件,比较复杂,我们先在 AbsoluteLayout 中排列好这些 GUI 元素,然后将 MainPanel 的布局转换成 GridBagLayout。
编程技巧
在NetBeans IDE可视化设计时,可以先利用AbsoluteLayout布局设计好程序界面,然后再将容器的绝对布局转化成合适的相对布局。如果容器中的
GUI元素比较复杂,则可以转化为功能强大的GridBagLayout布局。
使用 GridBag 定制器可在 GridBagLayout 中对组件的位置和约束进行可视化调整,如图
7-7 所示。它包含 GridBag 约束的属性表单、用于调整约束的按钮以及对组件布局的简短描述。通过右边的窗格还能准确地看出运行环境中组件的外观。
Java 程序设计大学教程
242
图 7-7 使用 GridBag 定制器调整 GUI 元素的布局
使用 GridBag 定制器的方法如下,
将所需组件添加到窗体中,并确保设置了 GridBagLayout。
要打开该定制器,请在“检查器”窗口中右键单击,GridBagLayout”节点,然后通过鼠标右键从上下文菜单中选择“定制” 。
拖动右窗格中的组件,根据需要对其重新定位。拖动组件时,其“网格 X”和“网格 Y”属性也随之改变,以反映新位置。
在右窗格中完成组件的大概布局后,可在右窗格中选择一个组件,并在左窗格中根据需要调整其约束。
在对布局满意后,单击,关闭,退出定制器。
通过 GridBag 定制器对组件布局调整后的新位置会立即在 NetBeans IDE 窗体编辑器中反映出来,并同时调整自动生成的代码。
在完成程序界面的组件与布局设计后,我们可以测试一下窗体。这是 NetBeans IDE 提供的一个不错的功能。 我们无须撰写任何代码,就可以测试窗体原型,看到自己设计的界面。
要快速测试窗体在编译和运行时的显示方式,请单击窗体编辑器工具栏中的,测试窗体”
按钮。此时将出现一个对话框,其中以组件在窗体上的排列格式来显示实际的组件。如果更新窗体设计,请关闭所有现有的“测试窗体”对话框,并再次单击“测试窗体”按钮以查看更新。
示例程序 7-3 是 NetBeans IDE 最后为我们自动生成的 GUI 界面代码,该代码包括了
GUI 组件添加与布局、属性设置与事件监听。其中事件监听及事件处理在下一小节“添加事件”中讲解。
示例程序 7-3 NetBeans IDE 自动生成的 GUI 界面代码
1,private void initComponents() {
2,java.awt.GridBagConstraints gridBagConstraints;
Java 程序设计大学教程
243
3,
4,optionsPanel = new javax.swing.JPanel();
5,keyA = new javax.swing.JRadioButton();
6,keyB = new javax.swing.JRadioButton();
7,keyC = new javax.swing.JRadioButton();
8,keyD = new javax.swing.JRadioButton();
9,buttonPanel = new javax.swing.JPanel();
10,startButton = new javax.swing.JButton();
11,setupButton = new javax.swing.JButton();
12,testWord = new javax.swing.JLabel();
13,jScrollPane1 = new javax.swing.JScrollPane();
14,trace = new javax.swing.JTextArea();
15,
16,setLayout(new java.awt.GridBagLayout());
17,
18,optionsPanel.setLayout(new javax.swing.BoxLayout(optionsPanel,
19,javax.swing.BoxLayout.Y_AXIS));
20,
21,keyA.setLabel("A");
22,keyA.addActionListener(new java.awt.event.ActionListener() {
23,public void actionPerformed(java.awt.event.ActionEvent evt) {
24,keyChosen(evt);
25,}
26,});
27,
28,optionsPanel.add(keyA);
29,
30,keyB.setLabel("B");
31,keyB.addActionListener(new java.awt.event.ActionListener() {
32,public void actionPerformed(java.awt.event.ActionEvent evt) {
33,keyChosen(evt);
34,}
35,});
36,
37,optionsPanel.add(keyB);
38,
39,keyC.setLabel("C");
40,keyC.addActionListener(new java.awt.event.ActionListener() {
41,public void actionPerformed(java.awt.event.ActionEvent evt) {
42,keyChosen(evt);
43,}
44,});
45,
46,optionsPanel.add(keyC);
Java 程序设计大学教程
244
47,
48,keyD.setLabel("D");
49,keyD.addActionListener(new java.awt.event.ActionListener() {
50,public void actionPerformed(java.awt.event.ActionEvent evt) {
51,keyChosen(evt);
52,}
53,});
54,
55,optionsPanel.add(keyD);
56,
57,gridBagConstraints = new java.awt.GridBagConstraints();
58,gridBagConstraints.gridx = 0;
59,gridBagConstraints.gridy = 1;
60,gridBagConstraints.gridwidth = 2;
61,gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
62,gridBagConstraints.ipadx = 107;
63,gridBagConstraints.ipady = 18;
64,gridBagConstraints.anchor =
65,java.awt.GridBagConstraints.NORTHWEST;
66,gridBagConstraints.insets = new java.awt.Insets(40,23,0,0);
67,add(optionsPanel,gridBagConstraints);
68,
69,startButton.setText("\u5f00\u59cb");
70,startButton.addActionListener(new
71,java.awt.event.ActionListener() {
72,public void actionPerformed(java.awt.event.ActionEvent evt) {
73,testWord(evt);
74,}
75,});
76,
77,buttonPanel.add(startButton);
78,
79,setupButton.setText("\u8bbe\u7f6e");
80,setupButton.addActionListener(new
81,java.awt.event.ActionListener() {
82,public void actionPerformed(
83,java.awt.event.ActionEvent evt) {
84,setupButtonActionPerformed(evt);
85,}
86,});
87,
88,buttonPanel.add(setupButton);
89,
90,gridBagConstraints = new java.awt.GridBagConstraints();
Java 程序设计大学教程
245
91,gridBagConstraints.gridx = 0;
92,gridBagConstraints.gridy = 2;
93,gridBagConstraints.gridwidth = 2;
94,gridBagConstraints.gridheight = 4;
95,gridBagConstraints.fill =
96,java.awt.GridBagConstraints.HORIZONTAL;
97,gridBagConstraints.ipadx = 58;
98,gridBagConstraints.anchor =
99,java.awt.GridBagConstraints.NORTHWEST;
100,gridBagConstraints.insets =
101,new java.awt.Insets(40,54,19,52);
102,add(buttonPanel,gridBagConstraints);
103,
104,testWord.setBackground(java.awt.SystemColor.info);
105,testWord.setFont(new java.awt.Font("宋体 ",0,24));
106,testWord.setHorizontalAlignment(
107,javax.swing.SwingConstants.CENTER);
108,testWord.setText("Word Winner");
109,testWord.setBorder(new
110,javax.swing.border.SoftBevelBorder(
111,javax.swing.border.BevelBorder.LOWERED));
112,testWord.setOpaque(true);
113,gridBagConstraints = new java.awt.GridBagConstraints();
114,gridBagConstraints.gridx = 0;
115,gridBagConstraints.gridy = 0;
116,gridBagConstraints.gridwidth = 2;
117,gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
118,gridBagConstraints.ipadx = 40;
119,gridBagConstraints.ipady = 10;
120,gridBagConstraints.insets = new java.awt.Insets(28,16,0,0);
121,add(testWord,gridBagConstraints);
122,
123,jScrollPane1.setViewportView(trace);
124,
125,gridBagConstraints = new java.awt.GridBagConstraints();
126,gridBagConstraints.gridx = 3;
127,gridBagConstraints.gridy = 0;
128,gridBagConstraints.gridheight = 3;
129,gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
130,gridBagConstraints.ipadx = 173;
131,gridBagConstraints.ipady = 257;
132,gridBagConstraints.anchor =
133,java.awt.GridBagConstraints.WEST;
134,gridBagConstraints.weightx = 1.0;
Java 程序设计大学教程
246
135,gridBagConstraints.weighty = 1.0;
136,gridBagConstraints.insets = new java.awt.Insets(0,20,0,0);
137,add(jScrollPane1,gridBagConstraints);
138,
139,}
7.2.3 添加事件
前面我们设计的界面仅仅是一个外观,因为其没有事件处理程序,还不能响应事件,实现用户操作。现在我们来给组件添加事件。
Java 编程语言使用事件来驱动 GUI 组件的行为。源对象可以触发事件,包含事件监听器的一个或多个对象将通过事件处理程序响应这些事件。利用 NetBeans IDE 可视化添加事件处理程序要比手工编写更为方便。
我们可以使用组件的“属性”窗口或上下文菜单定义事件处理程序。使用“属性”窗口定义事件处理程序的步骤如下(以 startButton 的 Action 事件为例),
在“检查器”窗口中选择组件 startButton,然后在“属性”窗口中单击“事件” 。
在列表中单击所需事件的值 actionPerformed。初始时,所有事件的值均为 <无 >。当单击值字段时,<无 >将替换为缺省事件名。如图 7-8 所示。
在属性表单中,键入所需的处理程序名称并按 Enter 键,或者直接按 Enter 键以使用缺省处理程序名称。如果未按 Enter 键,将不生成任何代码。也可以单击事件的省略号(,..) 按钮以显示“处理程序”对话框。单击“添加”按钮,将新的名称
testWord 添加到处理程序列表中,然后单击“确定” 。如图 7-9 所示。
此时系统自动生成事件监听器的代码和处理程序方法的空主体,如下所示。
//自动生成事件监听器的代码(不可编辑)
startButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
testWord(evt);
}
//处理程序方法的空主体(方法头不可编辑,但是方法体可编辑)
private void testWord(java.awt.event.ActionEvent evt) {
// TODO 将在此处添加您的处理代码,
}
Java 程序设计大学教程
247
图 7-8 在组件属性窗口添加事件处理程序
图 7-9 定义事件处理程序
我们还可以使用上下文菜单定义事件处理程序,
在“文件”窗口,“项目”窗口或“检查器”窗口中,右键单击某个窗体组件。
从上下文菜单中选择“事件”,然后在子菜单中移动以选择事件。事件处理程序将被赋以缺省名称。在“事件”子菜单中,粗体菜单项表示已定义的事件处理程序。
在源编辑器中添加新事件处理程序的代码。
选择要添加的事件处理程序
Java 程序设计大学教程
248
如果多个事件具有相同的类型,或者使用的是相同的事件处理程序,此时可以对同一事件的多个组件使用同一个处理程序。例如,本案例中,用作 ABCD4 个选择答案的 4 个
JRadioButton 组件 keyA,keyB,keyC,keyD 就是使用了同一个事件处理程序 keyChosen。
对于自动生成事件监听器的代码和处理程序方法,无法在源编辑器中手工直接删除。要删除事件可以按以下步骤进行,
在“检查器”窗口中,选择要删除其事件处理程序的组件。
在“属性”窗口中单击“事件” 。并在属性表单中删除处理程序的名称。
或者,选择“属性”窗口中的事件并单击省略号(,..)按钮,以显示“处理程序”
对话框。如果已显示“处理程序”对话框,在处理程序列表中选择要删除的处理程序并单击“删除” 。
删除事件处理程序时,将删除代码块。如果多个处理程序使用同一名称和同一代码块,
则删除代码的个别引用不会删除代码。只有删除所有的引用才会删除相应的代码块,并且会先显示一个确认对话框。
在 MainPanel 类中,我们添加事件的组件有,
单选按钮 keyA,keyB,keyC,keyD 事件监听器添加代码如示例程序 7-3 第 22-26、
31-35,40-44,49-53 行所示。事件处理代码如示例程序 7-4 第 1-11 行所示。这是典型的多个组件使用同一个处理程序的情况。
普通按钮 setupButton 事件监听器添加代码如示例程序 7-3 第 80-86 行所示。事件处理代码如示例程序 7-4 第 13-17 行所示。
普通按钮 startButton 事件监听器添加代码如示例程序 7-3 第 70-75 行所示。事件处理代码如示例程序 7-4 第 19-22 行所示。
示例程序 7-4 MainPanel 类中的事件处理程序
1,private void keyChosen(java.awt.event.ActionEvent evt) {
2,//选择答案
3,if (keyRB[keyPosition].isSelected()){
4,score++;
5,trace.append("\n \u221A");
6,}else{
7,trace.append("\n \u00D7");
8,}
9,trace.append(" 答案,"+keyRB[keyPosition].getText());
10,test() ;
11,}
12,
13,private void
14,setupButtonActionPerformed(java.awt.event.ActionEvent evt) {
15,// 设置
16,setup();
17,}
18,
19,private void testWord(java.awt.event.ActionEvent evt) {
20,//测试
21,test();
Java 程序设计大学教程
249
22,}
7.3 设计菜单和对话框
设计菜单和对话框是图形界面应用程序的最常见特征,菜单可以用于调用不同的程序操作模块,而通过对话框则可完成特定的设置或操作任务。
7.3.1 设计菜单
在 Java 程序中编写菜单程序是一件比较麻烦的事,需要写不少代码。通常需要手工创建菜单项对象( JMenuItem 的实例)、菜单对象( JMenu 的实例)、菜单栏对象( JMenuBar
的实例)。然后把菜单项对象添加到菜单对象中;把菜单对象添加到菜单栏对象中。最后为那个能使用菜单的容器组件添加完整的主菜单栏。凡是 JFrame,JDialog,JApplet 及其派生类都有一个 setJMenuBar 方法用于设置菜单栏。每个菜单项 JMenuItem 都有一个相关联的
ActionListener 用于监听和处理菜单点击事件。
使用 NetBeans IDE 在窗体编辑器中可视化设计菜单,使得编写菜单程序颇为简单。具体步骤如下,
null 首先在窗体编辑器中创建菜单栏
在“组件面板”窗口中,单击 JMenuBa 或 MenuBa 组件。
在要添加菜单栏的 JFrame,JDialog 或 JApplet 容器中单击任意位置。
如果这是添加到窗体中的第一个菜单栏,则该菜单栏在窗体上可见。如果这不是窗体的第一个菜单栏,则菜单出现在“检查器”窗口中的“其他组件”节点下面,并且在窗体中不显示该菜单栏。本案例程序中,我们用以上方法为主窗体 MainForm 创建了一个菜单栏,命名为 menuBar,如图 7-10 所示。
null 然后将菜单添加到菜单栏中
在“检查器”窗口中右键单击该菜单栏,然后从上下文菜单中选择“添加 JMenu”。
如果要命名新的菜单,请在“检查器”窗口中展开菜单栏节点,右键单击新创建的菜单节点并选择“属性”。然后,为文本属性输入所需的名称。
本案例程序中,我们用以上方法为主窗体菜单栏创建了 2 个 JMenu 菜单,命名为
menuFile 和 menuView,如图 7-10 所示。
null 接着是添加菜单项
在“检查器”窗口中,右键单击要添加项目的菜单。
从上下文菜单中选择“添加”,然后从子菜单中选择一个项目。
如果要重命名新的菜单项,请在“检查器”窗口中展开菜单节点,右键单击新创建的菜单项并选择“属性”。然后,为文本属性输入所需的名称。
本案例程序中,我们用以上方法为 menuFile 菜单创建了 2 个 JMenuItem 菜单项,命名为 menuItemOpen 和 menuItemExit;为 menuView 菜单创建了 1 个 JMenuItem 菜单项,命名为 menuItemSetup,如图 7-10 所示。
Java 程序设计大学教程
250
图 7-10 为主界面设计菜单
null 最后为菜单项添加事件
在“检查器”窗口中选择菜单项。然后在“属性”窗口中单击“事件”按钮,并选择所需的事件属性。
在“检查器”窗口或窗体编辑器中右键单击菜单项,然后从节点的上下文菜单中的
“事件”菜单中进行选择。
以本案例程序中的“退出”菜单项 menuItemExit 为例,我们在“检查器”窗口中选择菜单项 menuItemExit。然后在“属性”窗口中单击“事件”按钮,为源组件 menuItemExit
选择事件 actionPerformed,回车后系统自动生成的菜单事件处理代码如下,
private void initComponents() {
,..,.,
menuItemExit.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
menuItemExitActionPerformed(evt);
}
,..,.,
});
}
private void menuItemExitActionPerformed(java.awt.event.ActionEvent evt) {
exit();//用户添加的代码
}
Java 程序设计大学教程
251
然后,我们就可以在事件处理程序中添加自己的代码。
单词赢家软件在设计时,主窗体作为顶层容器除了放置主菜单和主面板外,并没有放入其他的 GUI 组件。因此这个 MainForm 类的设计并不复杂,而且使用 NetBeans IDE 的可视化设计,我们已经能够完成大部分工作。主窗体 MainForm.java 的源代码如示例程序 7-5 所示,其中黑粗部分是系统创建且不可手工编辑的部分。由此可见,系统为我们设计的菜单创建了大量代码,提高了编程的工作效率,使我们可以把精力放在菜单项事件的处理程序上。
这里的处理程序调用了主面板 MainPanel 的 open 和 setup 方法。
示例程序 7-5 MainForm.java 源代码
1,package wordwinner;
2,import java.io.*;
3,import java.awt.*;
4,import javax.swing.*;
5,import java.util.*;
6,
7,public class MainForm extends javax.swing.JFrame {
8,
9,/** MainForm构造函数 */
10,public MainForm() {
11,initComponents();
12,//加入组件
13,Container container =getContentPane();
14,mainPanel=new MainPanel();
15,container.add(mainPanel);
16,int width=600;
17,int height=400;
18,setSize(600,400);//设置框架大小
19,setTitle("单词赢家 ");//设置框架标题
20,//将框架显示在屏幕正中
21,Toolkit kit= Toolkit.getDefaultToolkit();
22,Dimension screenSize=kit.getScreenSize();
23,int x=(screenSize.width-width)/2;
24,int y=(screenSize.height-height)/2;
25,setLocation(x,y);//设置框架位置
26,}
27,
28,/** This method is called from within the constructor to
29,* initialize the form,
30,* WARNING,Do NOT modify this code,The content of this method is
31,* always regenerated by the Form Editor,
32,*/
33,private void initComponents() {
34,menuBar = new javax.swing.JMenuBar();
Java 程序设计大学教程
252
35,menuFile = new javax.swing.JMenu();
36,menuItemOpen = new javax.swing.JMenuItem();
37,menuItemExit = new javax.swing.JMenuItem();
38,menuView = new javax.swing.JMenu();
39,menuItemSetup = new javax.swing.JMenuItem();
40,
41,setDefaultCloseOperation(
42,javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
43,setFont(new java.awt.Font("宋体 ",0,14));
44,addWindowListener(new java.awt.event.WindowAdapter() {
45,public void windowClosing(java.awt.event.WindowEvent evt) {
46,formWindowClosing(evt);
47,}
48,});
49,
50,menuFile.setText("\u6587\u4ef6");
51,menuItemOpen.setText("\u6253\u5f00");
52,menuItemOpen.addActionListener(new
53,java.awt.event.ActionListener() {
54,public void actionPerformed(java.awt.event.ActionEvent evt) {
55,menuItemOpenActionPerformed(evt);
56,}
57,});
58,
59,menuFile.add(menuItemOpen);
60,
61,menuItemExit.setText("\u9000\u51fa");
62,menuItemExit.addActionListener(new
63,java.awt.event.ActionListener() {
64,public void actionPerformed(java.awt.event.ActionEvent evt) {
65,menuItemExitActionPerformed(evt);
66,}
67,});
68,
69,menuFile.add(menuItemExit);
70,
71,menuBar.add(menuFile);
72,
73,menuView.setText("\u89c6\u56fe");
74,menuItemSetup.setText("\u8bbe\u7f6e");
75,menuItemSetup.addActionListener(new
76,java.awt.event.ActionListener() {
77,public void actionPerformed(java.awt.event.ActionEvent evt) {
78,menuItemSetupActionPerformed(evt);
Java 程序设计大学教程
253
79,}
80,});
81,
82,menuView.add(menuItemSetup);
83,
84,menuBar.add(menuView);
85,
86,setJMenuBar(menuBar);
87,
88,pack();
89,}
90,
91,private void
92,menuItemExitActionPerformed(java.awt.event.ActionEvent evt) {
93,//退出程序
94,exit();
95,}
96,
97,private void
98,formWindowClosing(java.awt.event.WindowEvent evt) {
99,//退出程序
100,exit();
101,}
102,
103,private void
104,menuItemSetupActionPerformed(java.awt.event.ActionEvent evt) {
105,// 功能设置
106,mainPanel.setup();
107,}
108,
109,private void
110,menuItemOpenActionPerformed(java.awt.event.ActionEvent evt) {
111,// 打开词库文件
112,mainPanel.open();
113,}
114,
115,public static void main(String args[]) {
116,try {
117,UIManager.setLookAndFeel(
118,"com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
119,} catch (Exception e){
120,e.printStackTrace();
121,}
122,java.awt.EventQueue.invokeLater(new Runnable() {
Java 程序设计大学教程
254
123,public void run() {
124,new MainForm().setVisible(true);
125,}
126,});
127,}
128,
129,private void exit(){
130,int selection=JOptionPane.showConfirmDialog(this,
131,"是否立即退出系统? ","操作提示 ",JOptionPane.OK_CANCEL_OPTION,
132,JOptionPane.WARNING_MESSAGE);
133,if (selection==JOptionPane.OK_OPTION)
134,System.exit(0);
135,}
136,
137,// 变量声明 - 不进行修改
138,private javax.swing.JMenuBar menuBar;
139,private javax.swing.JMenu menuFile;
140,private javax.swing.JMenuItem menuItemExit;
141,private javax.swing.JMenuItem menuItemOpen;
142,private javax.swing.JMenuItem menuItemSetup;
143,private javax.swing.JMenu menuView;
144,// 变量声明结束
145,private MainPanel mainPanel;//主面板
146,
147,}
7.3.2 设计对话框
同大多数流行的 Windows 应用程序一样,一个 Java GUI 应用程序除了主窗体外,还包括一些子窗体。其中用于设置操作选项、实现人机对话的子窗体,我们称之为对话框。对话框根据其显示模式分为模态对话框和非模态对话框。 模态对话框在用户处理它之前不允许用户同应用程序的主窗口进行交互。例如,当用户需要读取一个文件时,一个模态的文件对话框就会弹出。直到用户指定一个文件名,然后程序才能开始操作。因此只有当用户退出(关闭)模态对话框后,才能继续其他窗体中的操作。非模态对话框允许用户同时在该对话框和程序其他窗体中切换操作,而不用关闭该对话框。
在设计对话框时,我们可以使用 Java 类库中现成的专用对话框,也可以自己定制复杂的对话框。使用专用对话框无需编写代码,而自己定制对话框则相当于设计一个窗体。
Java 类库中现成的专用对话框包括:用于显示消息的选项对话框( JOptionPane),以及一些标准对话框,如:文件选择对话框( JFileChooser),颜色选择对话框( JColorChooser) 。
JOptionPane 比较简单,使用该类无需编写任何专门对话框代码,即可通过参数设定弹出一个用于提示信息的对话框。文件选择对话框和颜色选择对话框比较复杂,但掌握它们的使用可以节省我们编写很多代码。
Java 程序设计大学教程
255
1,选项对话框
选项对话框 JOptionPane 是典型的模态对话框。 JOptionPane 有四个静态方法来显示不同类型的消息,
showMessageDialog 显示一条消息并等待用户点击确定按钮。无返回值。
showConfirmDialog 显示一条消息并得到确认。可返回代表选择项的整数。
showOptionDialog 显示一条消息并得到用户在一组选项中的选择。 可返回代表选择项的整数。
showInputDialog 先是一条消息并得到用户的一行输入。可返回用户选择或输入的字符串。
图 7-11 显示了一个 showConfirmDialog 类型的 JOptionPane 对话框。可以看到,该对话框有如下组件:一个图标、一条消息,2 个选项按钮。我们在示例程序 7-5 的第 129-135 行中编写了这个对话框,用于用户点击“退出”菜单项或主窗体右上角的退出按钮?时确认是否真的需要退出系统。并在示例程序 7-5 的第 91-101 行的事件处理程序中调用了这个 exit
方法。
private void exit(){
int selection=JOptionPane.showConfirmDialog(this,
"是否立即退出系统? ","操作提示 ",JOptionPane.OK_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE);
if (selection==JOptionPane.OK_OPTION)
System.exit(0);
}
图 7-11 确认是否退出系统的 JOptionPane 对话框
需要注意的是,NetBeans IDE 在创建主窗体时就已经将 setDefaultCloseOperation 自动设为 EXIT_ON_CLOSE,这样用户点击窗体右上角的退出按钮?时程序就会终止。为了使我们的 exit 方法起作用,需要将设置的参数值改为 DO_NOTHING。方法是在“检查器”窗口中选择主窗体对象;然后在“属性”窗口中单击“属性”按钮,如图图 7-12 所示将
setDefaultCloseOperation 属性改为 DO_NOTHING。
Java 程序设计大学教程
256
图 7-12 将 setDefaultCloseOperation 属性改为 DO_NOTHING
对于每个对话框类型,可以指定一条消息。该消息可以是一个字符串、一个图标、一个用户界面组件或者任何其他对象。以我们用到的 showConfirmDialog 为例,该静态方法的声明为,
public static int showConfirmDialog(Component parentComponent,
Object message,String title,int optionType,int messageType)
parentComponent 参数指明该对话框的父组件,message 指定一条消息,title 指定对话框标题,optionType 指定选项类型,messageType 指定消息类型。
showConfirmDialog 选项类型有 4 种,DEFAULT_OPTION,YES_NO_OPTION、
YES_NO_CANCEL_OPTION,OK_CANCEL_OPTION。选项类型决定了显示在对话框底部的按钮。
消息类型有 5 种,ERROR_MESSAGE,INFORMATION_MESSAGE,
WARNING_MESSAGE,QUESTION_MESSAGE,PLAIN_MESSAGE。消息类型决定了对话框左边的图标,并和设置的观感一致。
showConfirmDialog 返回一个整数来表明用户选择了哪个按钮。 返回值有,OK_OPTION、
CANCEL_OPTION,YES_OPTION,NO_OPTION,CLOSED_OPTION。
2,文件选择对话框
在单词赢家软件中,我们可以让用户选择不同学习阶段或专业的词库文件,这些词库文件包括从初中到大学各个阶段的词汇、托福词汇、新概念英语词汇等文件。
虽然在 Java 中对文件及 I/O 的操作比较复杂 (后面章节我们会专门讨论这方面的内容),
Java 程序设计大学教程
257
但是值得庆幸的是 Java 基础类库( JFC)中提供的文件选择对话框( JFileChooser),可以允许用户通过一个图形化文件系统浏览程序访问文件系统,打开或保存一个文件。这是一个流行的 GUI 应用程序常常需要的。在我们的 WordWinner 应用程序中,用户看到的这个文件选择对话框与 Windows XP中其他应用程序中标准的文件选择对话框有着相同的外观和操作习惯,如图 7-13 所示。这是 Swing 组件的观感在起作用。
图 7-13 WordWinner 中用于选择词库文件的文件选择对话框
为了创建一个 JFileChooser,先要实例化一个 JFileChooser 对象,设置它的大小。然后显示打开文件对话框(调用 showOpenDialog 方法)或是保存文件对话框(调用
showSaveDialog 方法),并指明其父组件。这两种对话框都返回一个 int 类型的返回值,表示用户打开或保存一个文件时,最后到底是按下确认按钮还是取消按钮。为了响应打开或保存事件,可以把这个整数返回值与定义在 JFileChooser 类中的 APPROVE_OPTION 常数进行比较判定。最后获得用户选择的文件名称,以便对该文件进行处理。 WordWinner 中用于打开词库文件的方法是 open,如示例程序 7-6 所示。该方法位于主面板类 MainPanel 中,从中我们可以看到文件选择对话框 JFileChooser 的具体使用方法。
示例程序 7-6 在 MainPanell 类中通过 open方法打开和处理文件
public void open(){
int choice;
JFileChooser jfc = new JFileChooser();
jfc.setSize( 400,300 );
choice = jfc.showOpenDialog( this );
if ( choice == JFileChooser.APPROVE_OPTION ) {
Java 程序设计大学教程
258
String fileName = jfc.getSelectedFile().getAbsolutePath();
loadWords(fileName);
}
}
3,自定义对话框
前面我们讲述了如何使用 JFC 中现成的对话框,但是这些对话框都是专用的,功能固定的标准对话框。那么,我们如何手工创建自己的对话框呢?
图 7-4 显示一个典型的模态对话框。它是 WordWinner 应用程序中一个进行操作设置的自定义对话框 SetUpDialog。
图 7-14 单词赢家软件的设置对话框
为了实现这个 SetUpDialog 对话框,需要继承自 JDialog 基类。这同应用程序的窗口继承自 JFrame 基类的过程完全一样。 SetUpDialog 对话框的设计步骤如下,其源程序如示例程序 7-7 所示,
在 SetUpDialog 对话框的构造器中,调用基类 JDialog 的构造器。这里用的的参数有 3 个:对话框的拥有者 frame(调用对话框时可以提供该框架让对话框显示在它上面,该值也可以设为 null 以便使用公用的隐藏框架),对话框的标题以及一个指明对话框是模态对话框还是非模态对话框的布尔标志。如示例程序 7-7 第 12 行所示。
添加该对话框的用户界面组件。如示例程序 7-7 第 13-27 行所示。
添加事件处理器。 如示例程序 7-7 第 17 行,122-140 行监听单击按钮事件; 第 45-49
行监听设置方式变化事件;第 69-73 行监听设置选项变化事件。
设置对话框的大小。如示例程序 7-7 第 31 行所示。
示例程序 7-7 SetUpDialog.java
1,package wordwinner;
2,import javax.swing.*;
3,import javax.swing.border.*;
4,import java.awt.*;
5,import java.awt.event.*;
Java 程序设计大学教程
259
6,import javax.swing.event.*;
7,
8,public class SetUpDialog extends JDialog implements ActionListener {
9,
10,/** SetUpDialog构造函数 */
11,public SetUpDialog(JFrame frame) {
12,super(frame,"设置 ",true); //true 代表为有模态对话框
13,display.setText("\n当前设置,\n \t英汉测试 \t 随机选择单词 ");
14,group.setText(" 0 ");
15,indexPanel.add(groupLabel);
16,indexPanel.add(group);
17,submit.addActionListener(this);
18,buttonPanel.add(submit);
19,initModes();
20,initOptions();
21,//主面板的 BorderLayout布局
22,mainPanel.setLayout(new BorderLayout(0,0));
23,mainPanel.add( display,"North");
24,mainPanel.add(optionPanel,"West");
25,mainPanel.add(modePanel,"East");
26,mainPanel.add( buttonPanel,"South");
27,mainPanel.add( indexPanel,"Center");
28,
29,getRootPane().setDefaultButton(submit);
30,setContentPane(mainPanel);
31,setSize(280,240);
32,
33,}
34,
35,private void initModes() {
36,//设置边框
37,modePanel.setBorder(BorderFactory.createTitledBorder("方式 "));
38,//设置布局
39,modePanel.setLayout(new
40,BoxLayout(modePanel,BoxLayout.Y_AXIS));
41,//添加单选按钮
42,for (int k = 0; k < modes.length; k++) {
43,modes[k] = new JRadioButton(modeLabels[k]);
44,//监听方式变化事件
45,modes[k].addItemListener(new ItemListener() {
46,public void itemStateChanged(ItemEvent e) {
47,modChanged();
48,}
49,});
Java 程序设计大学教程
260
50,modePanel.add(modes[k]);
51,choGroup.add(modes[k]);
52,}
53,//设置缺省选项
54,modes[0].setSelected(true);
55,modePanel.add(indexPanel);
56,}
57,
58,private void initOptions() {
59,//设置边框
60,optionPanel.setBorder(
61,BorderFactory.createTitledBorder("选项 "));
62,//设置布局
63,optionPanel.setLayout(new
64,BoxLayout(optionPanel,BoxLayout.Y_AXIS));
65,//添加单选按钮组
66,for (int k = 0; k < options.length; k++) {
67,options[k] = new JRadioButton(optionLabels[k]);
68,//监听选项变化事件
69,options[k].addItemListener( new ItemListener() {
70,public void itemStateChanged(ItemEvent e) {
71,optChanged();
72,}
73,});
74,optionPanel.add(options[k]);
75,optGroup.add(options[k]);
76,}
77,//设置缺省选项
78,options[0].setSelected(true);
79,}
80,
81,public void optChanged() {
82,for (int k = 0; k < options.length; k++ ){
83,if (options[k].isSelected()){
84,display.setText("\n 选项,\t"
85,+ options[k].getText()+"\n" );
86,option=k;
87,}
88,}
89,}
90,
91,public void modChanged() {
92,if (modes[0].isSelected() ){
93,display.setText(
Java 程序设计大学教程
261
94,"方式,\n 随机方式在词库中随机挑选 50个单词作为一组。 "
95,+"\n 随机方式用于测试单词综合掌握情况。 ");
96,mode=0;
97,} else{
98,display.setText("方式,\n 顺序方式将词库中 "+groupCount
99,+"组单词按照字母顺序排列。 "
100,+"\n选择顺序方式,需输入组号。组号为 0-"
101,+(groupCount-1)+"。 ");
102,mode=1;
103,}
104,}
105,
106,public void setCount(int n){
107,groupCount=n;
108,}
109,
110,public int getIndex(){
111,return groupIndex;
112,}
113,
114,public int getMode(){
115,return mode;
116,}
117,
118,public int getOption(){
119,return option;
120,}
121,
122,public void actionPerformed(ActionEvent e){
123,//文本转化为整数
124,int n=Integer.parseInt(group.getText().trim());
125,if ( modes[0].isSelected() ) {
126,this.setVisible(false);
127,return;
128,}
129,if ((n>=0) && (n<groupCount)){
130,groupIndex=n;
131,this.setVisible(false);
132,} else{
133,group.setText(" 0 " );
134,int selection=JOptionPane.showConfirmDialog(this,
135,"组号输入错! ","操作提示 ",
136,JOptionPane.DEFAULT_OPTION,
137,JOptionPane.ERROR_MESSAGE);
Java 程序设计大学教程
262
138,groupIndex=0;
139,}
140,}
141,
142,private JPanel mainPanel = new JPanel(),
143,indexPanel = new JPanel(),
144,modePanel = new JPanel(),
145,optionPanel = new JPanel(),
146,buttonPanel = new JPanel();
147,private int groupCount,groupIndex,option,mode;
148,private ButtonGroup optGroup = new ButtonGroup();
149,private ButtonGroup choGroup = new ButtonGroup();
150,private JRadioButton modes[] = new JRadioButton[2];
151,private JRadioButton options[] = new JRadioButton[2];
152,private String modeLabels[] = {"随机选择单词 ","顺序选择单词 "};
153,private String optionLabels[] = {"英汉测试 ","汉英测试 "};
154,private JTextArea display = new JTextArea();
155,private JLabel groupLabel = new JLabel("输入组号 ");
156,private JTextField group = new JTextField();
157,private JButton submit = new JButton("确定 ");
158,
159,}
从 SetUpDialog 对话框的设计过程中,读者可以看到:即使完全不依赖于可视化设计工具,使用 Java 仍然可以自如地手工编写 GUI 程序。显然 Java 在手工编写 GUI 程序方面比其他语言(如 VB)更具优势。实际上,一些熟悉 Swing 编程的 Java 程序员更喜欢手工编程。
7.4 设计算法
在单词赢家软件中,要求程序能够从词库文件中解析出单词的中英文对照字符串,并将
50 个词汇一组生成一个供测试的单词组。用户一次进行一组词汇的英译中或中译英的选择题测试。其核心的算法涉及,
如何从词库文件中解析出单词的中英文对照字符串,并生成测试单词组。其中包括了顺序生成和随机生成 50 个测试词汇。
如何产生测试选择题的 4 个选项,其中要求正确答案的 ABCD 位置随机变化以及随机生成另外 3 个用于干扰的假答案。
如何在图形界面上实现用户交互,完成答题过程。
我们将 WordWinner 应用程序的核心算法全部放在 MainPanel 类中。前面我们讲了
MainPanel 类作为主面板的可视化界面设计,这里我们重点分析算法的设计。
WordWinner 核心算法的设计如图 7-15 所示。我们将问题分解在打开文件、操作设置、
词汇测试这 3 个流程中,这 3 个流程也是由用户界面交互事件所驱动的。也就是说,open、
setup,test 这 3 个方法是被事件处理程序所调用的。
在 WordWinner 的核心算法中,我们用到了第 5 章“算法与数据结构”中的知识,包括
List 容器和迭代器的使用。这样,我们既可以建立英文单词表 wordsEnglish 与中文注释表
Java 程序设计大学教程
263
wordsChinese 的对应关系,又可以为每次分组测试提供一个独立的试题与答案的迭代器,测试过程简化为迭代器的遍历过程。下面我们来具体分析这些核心算法的实现程序,它们是
loadWords 方法,makeTestList 方法,makeKeys 方法。
图 7-15 WordWinner 的核心算法
MainPanel 类的 loadWords 方法负责读入并将词库文件转换为中英文对照的 2 个列表
wordsEnglish 与 wordsChinese。在 NetBeans IDE 中,我们在“项目”窗体打开 WordWinner
项目节点,找到 MainPanel 类下面的“方法”节点。鼠标右键调出上下文菜单,选择“添加方法,..”菜单项,此时弹出如图 7-16 所示的添加新方法向导。如图所示输入方法名称,添加参数,设置方法属性,然后单击“确定”按钮。 NetBeans IDE 在程序中自动生成该方法的声明代码。
loadWords 方法的实现代码如示例程序 7-8 所示。第 4-5 行将词库文件读入缓存,第 6
行和 7 行读文件中的一行。第 7-16 行负责将英文单词和中文注释解析出来并添加到中英文列表中。
词库文件中,每一行由一个英文单词和对应的中文注释组成,用,=”连接成对,例如:
,love=vt,n,爱” 。每读入文件的一行,都将该行解析成英文单词和中文注释 2 个部分,例如:,,love”和,vt,n,爱”,并分别添加到 wordsEnglish 与 wordsChinese 这 2 个列表中。
从词库文件中读出所有单词
单词分组
打开文件
open()
将单词解析成中英文字符串英文单词表 wordsEnglish
中文注释表 wordsChinese
操作设置
setup()
词汇测试
test()
随机分组顺序分组构造测试词汇迭代器
试题迭代器 it1
答案迭代器 it2
生成选择题
生成答案随机位置生成随机伪装答案
遍历测试迭代器
评判显示分数统计
loadWords()
makeTestList()
makeKeys()
Java 程序设计大学教程
264
图 7-16 在 NetBeans IDE 添加新方法向导中为类添加方法
示例程序 7-8 loadWords 方法
1,//读入并将词库文件转换为中英文单词列表
2,public void loadWords(String wordfileName) {
3,try {
4,Reader dataReader=new FileReader(wordfileName);//读文件流
5,BufferedReader inStream= new BufferedReader(dataReader);
6,String line = inStream.readLine();// 读文件中的一行
7,while (line != null) {
8,//将英中词汇对解析成英文、中文单词
9,//例如,"love=vt,n,爱 " 解析成,"love"和 "vt,n,爱 "
10,int posColon=line.indexOf('=');
Java 程序设计大学教程
265
11,if (posColon>0) {
12,String e=line.substring(0,posColon).trim();
13,String c=line.substring(posColon+1).trim();
14,wordsEnglish.add(e) ;
15,wordsChinese.add(c) ;
16,}
17,line = inStream.readLine(); // 读下一行
18,}
19,inStream.close();// 关闭文件流
20,} catch (FileNotFoundException e) {
21,e.printStackTrace();
22,} catch (IOException e) {
23,e.printStackTrace();
24,}
25,total=wordsEnglish.size();
26,groupCount=total/50;//50个单词分为一组
27,}
MainPanel 类的 makeTestList 方法负责生成试题迭代器 it1 和答案迭代器 it1,其实现代码如示例程序 7-8 所示。该方法先把一组试题和答案放在 testList 和 keyList 列表中,如第
10-25 行所示。然后生成迭代器,如第 26-27 行所示。
示例程序 7-9 makeTestList 方法
1,private void makeTestList(){
2,java.util.List chosenList1=wordsEnglish;
3,java.util.List chosenList2=wordsChinese;
4,testList.clear();
5,keyList.clear();
6,if (option==1) {
7,chosenList1=wordsChinese;
8,chosenList2=wordsEnglish;
9,}
10,if (mode==0){
11,//产生随机测试单词
12,for (int i=0;i<50;i++){
13,int r=(int)(Math.random()*total);
14,testList.add(chosenList1.get(r));
15,keyList.add(chosenList2.get(r));
16,}
17,} else{
18,//产生顺序测试单词
19,for (int i=0;i<50;i++){
20,int s=i+groupIndex*50;
21,if ( s>(total-1) ) return;
Java 程序设计大学教程
266
22,testList.add(chosenList1.get(s));
23,keyList.add(chosenList2.get(s));
24,}
25,};
26,it1=testList.listIterator();
27,it2=keyList.listIterator();
28,score=0;//计分归 0
29,}
MainPanel 类的 makeKeys 方法负责产生测试选择题的 4 个选项,该方法的实现代码如示例程序 7-8 所示。
我们前面在 NetBeans IDE 中已经创建了 4 个单选按钮 keyA,keyB,keyC,keyD。为了方便对应一组 4 个可选答案,我们借助一个答案数组来管理,其数组索引对应 A,B,C、
D4 个答案位置。该数组声明如下,
private String key[]={"A","B","C","D"};
makeKeys 方法先随机产生 4 个伪装假答案,然后在 A,B,C,D 这 4 个答案中随机选择一个改为正确答案。实际上只要将对应答案位置的 0,1,2,3 这 4 个索引中随机确定一个索引,并为该数组元素赋值正确答案即可。
示例程序 7-10 makeKeys 方法
1,private void makeKeys(){
2,String keyToWord=(String)(it2.next());
3,//随机产生伪装假答案
4,for (int i=0;i<4;i++){
5,int r=(int)(Math.random()*total);
6,if (option==0){
7,key[i]=(String)(wordsChinese.get(r));
8,} else {
9,key[i]=(String)(wordsEnglish.get(r));
10,}
11,//如果随机产生的假答案刚好与真答案相同,则重来。
12,if (key[i]==keyToWord)
13,i--;
14,}
15,//随机产生真答案的位置
16,keyPosition=(int)(Math.random()*4);
17,key[keyPosition]=keyToWord;
18,//显示答案选择项
19,keyA.setText("A,"+key[0]);
20,keyB.setText("B,"+key[1]);
21,keyC.setText("C,"+key[2]);
22,keyD.setText("D,"+key[3]);
Java 程序设计大学教程
267
23,};
7.5 完成和部署应用程序
前面我们已经实现了 WordWinner 应用程序的大部分代码,接下来我们需要进一步整理、
完成应用程序,并打包、部署,使之可以运行。
1,完成 WordWinner 应用程序
最后我们完成的 WordWinner 应用程序包含以下的完整 java 文件和类,
MainForm.java 文件,MainForm 类(参见示例程序 7-5) 。
MainPanel.java 文件,MainPanel 类(参见示例程序 7-11) 。
SetUpDialog.java 文件,SetUpDialog 类(参见示例程序 7-7) 。
其中 MainPanel 类的完整源程序如示例程序 7-11 所示。程序黑粗部分是我们可视化设计界面时 NetBeans IDE 自动创建的代码,用户不可随意改动。
示例程序 7-11 MainPanel.java
1,package wordwinner;
2,import java.io.*;
3,import java.awt.*;
4,import javax.swing.*;
5,import java.util.*;
6,public class MainPanel extends javax.swing.JPanel {
7,
8,/** MainPanel构造函数 */
9,public MainPanel() {
10,initComponents();
11,startButton.setEnabled(false);
12,keyRB[0]=keyA;
13,keyRB[1]=keyB;
14,keyRB[2]=keyC;
15,keyRB[3]=keyD;
16,optionsPanel.setVisible(false);
17,}
18,
19,/** This method is called from within the constructor to
20,* initialize the form,
21,* WARNING,Do NOT modify this code,The content of this method is
22,* always regenerated by the Form Editor,
23,*/
24,private void initComponents() {
25,//略。代码参见示例程序 7-3。
26,}
27,
Java 程序设计大学教程
268
28,private void keyChosen(java.awt.event.ActionEvent evt) {
29,//略。代码参见示例程序 7-4。
30,}
31,
32,private void
33,setupButtonActionPerformed(java.awt.event.ActionEvent evt) {
34,//略。代码参见示例程序 7-4。
35,}
36,
37,private void testWord(java.awt.event.ActionEvent evt) {
38,//略。代码参见示例程序 7-4。
39,}
40,
41,//读入并将词库文件转换为中英文单词列表
42,public void loadWords(String wordfileName) {
43,//略。代码参见示例程序 7-8。
44,}
45,
46,public void open(){
47,//略。代码参见示例程序 7-6。
48,}
49,
50,public void setup(){
51,if (setupDialog==null) {
52,if (total==0)
53,open();
54,setupDialog=new SetUpDialog(null);
55,}
56,setupDialog.setCount( groupCount );
57,setupDialog.setVisible(true);
58,groupIndex = setupDialog.getIndex();
59,mode=setupDialog.getMode();
60,option=setupDialog.getOption();
61,makeTestList();
62,startButton.setText("开始 ");
63,startButton.setEnabled(true);
64,trace.setText("开始,..");
65,}
66,
67,private void makeTestList(){
68,//略。代码参见示例程序 7-9
69,}
70,
71,private void makeKeys(){
Java 程序设计大学教程
269
72,//略。代码参见示例程序 7-10
73,};
74,
75,public void test(){
76,optionsPanel.setVisible(true);
77,if (it1.hasNext()){
78,makeKeys();
79,String curWord=(String)(it1.next());
80,testWord.setText(curWord);
81,startButton.setText("下一个 ");
82,String str="\n "+it1.nextIndex()+" "+curWord;
83,trace.append(str);
84,for (int i=0;i<4;i++){
85,keyRB[i].setSelected(false);
86,}
87,
88,} else {
89,startButton.setText("开始 ");
90,String sum=" 本次成绩,"+(score*2)+"分 ";
91,testWord.setText(sum);
92,trace.append("\n"+sum);
93,startButton.setEnabled(false);
94,optionsPanel.setVisible(false);
95,};
96,}
97,
98,// 变量声明 - 不进行修改
99,private javax.swing.JPanel buttonPanel;
100,private javax.swing.JScrollPane jScrollPane1;
101,private javax.swing.JRadioButton keyA;
102,private javax.swing.JRadioButton keyB;
103,private javax.swing.JRadioButton keyC;
104,private javax.swing.JRadioButton keyD;
105,private javax.swing.JPanel optionsPanel;
106,private javax.swing.JButton setupButton;
107,private javax.swing.JButton startButton;
108,private javax.swing.JLabel testWord;
109,private javax.swing.JTextArea trace;
110,// 变量声明结束
111,
112,private java.util.List wordsEnglish=new ArrayList(),
113,wordsChinese=new ArrayList(),
114,testList=new ArrayList(),
115,keyList=new ArrayList();
Java 程序设计大学教程
270
116,private int total,
117,groupCount,
118,groupIndex,
119,mode,
120,option,
121,keyPosition,
122,score;
123,private SetUpDialog setupDialog;
124,private ListIterator it1,it2;
125,private String key[]={"A","B","C","D"};
126,private JRadioButton keyRB[]=new JRadioButton[4];
127,private static final int GROUPSIZE=50;
128,
129,}
我们在分析完 WordWinner 应用程序后,可以发现整个程序的设计还可以进一步优化为
MVC 模型,如图 7-17 所示。 需要改进的地方是把核心算法和状态变量从用户界面 MainPanel
类中剥离出来,形成一个独立的 model 类。这样不但算法可以重用在其它的选择题测试类程序中,还可以重新定义原来的视图与控制器,获得更丰富的界面效果。
图 7-17 WordWinner 的 MVC 模型
2,打包 Java 应用程序
一旦我们完成了 Java 应用程序的设计与编写,调试通过后就打包发行。记住,我们要
Java 程序设计大学教程
271
发行给用户使用的不是后缀名为,java 的源文件,而是后缀名为,class 的编译后得到的字节码文件。当然作为一种跨平台的语言,不要指望 Java 应用程序能编译成 Windows 平台的 exe
可执行文件。
一个 Java 应用程序可能会包含一大堆,class 文件,大量的类文件肯定会带来部署和使用上的麻烦。通常,我们应当将应用程序所需要的类文件和其他资源打包成一个 JAR 文件再进行发布。一旦将程序打包后,就可以通过一个简单的命令来加载它,而如果正确地配置了系统的话,还可以通过双击 JAR 文件来打开它,犹如运行 Windows 平台的上的 exe 文件一样方便。
JAR 文件是一个简单的 ZIP 格式文件,它包含类文件、程序需要的资源文件(例如图标文件),以及描述该 JAR 文件特性的清单文件 MANIFEST.MF。
MANIFEST.MF 文件存放在 JAR 文件一个特别的 META-INF 子目录中。该文件可以包含多个条目,这些条目被组成多个节。第一节被称为主节。主节作用于整个 JAR 文件。后续的条目是用来指定那些命名条目的属性。
要将应用程序打包创建为一个可以运行的 JAR 文件,我们需要将该应用程序全部的,class 文件放到一个 JAR 文件中,然后在清单文件 MANIFEST.MF 中增加一项来指定该程序的主类——该类一般是要通过 Java 解释器运行的,包含有 main 方法的类。
虽然使用 jar 命令可以来创建 JAR 文件并添加清单,但手工创建 JAR 文件不但繁琐而且容易出错。接下来我们将介绍如何在 NetBeans IDE 中自动打包生成 JAR 文件。
3,在 NetBeans IDE 中生成 JAR 文件
NetBeans IDE 中的所有 Java 应用程序项目都能利用自动生成的 Ant 脚本来编译、运行和调试应用程序的项目。 Ant 是一个基于 Java 的生成工具,用来规格化和自动设置用于开发的生成和运行环境。 Ant 脚本是包含目标的 XML 文件,而这些目标又包含相关任务。
在 Java 应用程序项目中,每次运行“生成项目”命令时,都会使用项目源来生成 JAR
文件。 JAR 文件是在项目文件夹的 dist 目录中生成的。
如果要更改 JAR 名称和位置,可以在“文件”窗口中,转到项目文件夹中的 nbproject
文件夹,然后打开 project.properties。并在 dist.jar 属性中输入 JAR 文件的完整路径。
在项目属性中,我们可以对创建 JAR 进行设置,如图 7-18 所示,
在“项目”窗口中右键单击项目节点,然后选择“属性” 。
单击“创建 JAR”,然后对过滤器和压缩设置进行配置。
Java 程序设计大学教程
272
图 7-18 设置项目属性
我们现在 WordWinner 项目上通过上下文菜单的“生成项目”命令,或者按 F11 键,此时 NetBeans IDE 使用项目源来生成了 JAR 文件,在 NetBeans IDE 的“输出”窗口我们可以看到生成 WordWinner.jar 的信息,如图 7-19 所示。
我们打开“文件”窗口,在 dist 目录下可以看到创建的 WordWinner.jar 文件已经包含了
WordWinner 应用程序的 3 个,class 文件和 1 个清单文件 MANIFEST.MF,如图 7-19 所示。
因为我们使用的是 Java 应用程序模板创建的项目,所以 NetBeans IDE 会为我们自动创建一个清单文件 MANIFEST.MF。
图 7-19 编译 WordWinner 项目生成的 jar 文件
双击该清单文件节点,我们可以查看到该文件内容如下,
Manifest-Version,1.0
Ant-Version,Apache Ant 1.6.2
Created-By,1.5.0_02-b09 (Sun Microsystems Inc.)
Main-Class,wordwinner.MainForm
X-COMMENT,Main-Class will be added automatically by build
其中,,Main-Class,wordwinner.MainForm”一行,指明了该应用程序的主类。
4,部署和运行 Java 应用程序
Java 应用程序可以以 JAR 文件形式部署在本地或网络上。如果部署在网络上,可以通过 Java Web Start 技术运行;也可以将 Java 应用程序改写成一个 applet,直接在网页中运行,
不过这样会有一些安全上的限制。
对于部署在本地上的应用程序,用户可以通过下面的命令来启动应用程序,
java -jar WordWinner.jar
Java 程序设计大学教程
273
在不同的操作系统中,通过相应的配置后,就可以通过双击 JAR 文件的图标启动应用程序。例如:在 Windows 平台上,安装 JRE(即 Java 运行环境,如果本机上没有 JRE 可以到 http://Java.sun.com/下载)时会创建,jar 后缀的文件和 javaw -jar 命令连接起来,用以启动
JAR 文件。与 java 命令不同的在于 javaw 命令并不打开一个控制台窗口。
友情提示
如果你的Windows系统中装有winrar或winzip这样的压缩软件,系统很可能会把.jar后缀的文件和这些软件关联起来,使你无法双击运行JAR文件(误认为你要解压缩)。此时你可以通过更改已注册的文件打开方式来解决这类问题。
7.6 本章习题
复习题
[习题 1] 什么是可视化程序设计?探索并简述 Java 的可视化开发环境 NetBeans IDE。
[习题 2] JOptionPane 模态对话框有哪四个静态方法来显示不同类型的消息?
[习题 3] 简述在 Java 程序中如何创建菜单?
[习题 4] 对话框根据其显示模式分为哪两种?他们有什么不同?
[习题 5] 什么是 JAR 文件?它有什么用处?
测试题
[习题 6] 下面合法的赋值语句是 。
A,Panel p = new Container();
B,Component c1 = new Object();
C,Component c2 = new Container();
D,Container c3 = new Component();
[习题 7] 以下语句的执行效果是 。
JOptionPane.showMessageDialog(null,"OK",button.getText(),
JOptionPane.INFORMATION_MESSAGE);
A、在当前窗体的标题栏显示,OK!,
B、在当前窗体的中央显示,OK!,
C、弹出一消息对话框,其窗体中显示,OK!,
D、发送消息,消息的内容是,OK!,
[习题 8] 以下所定义的符合 Windows 平台的观感常量 WinLF,其中正确的是 。
A,
private static final String WinLF =
"com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
B,
private static final String WinLF =
" javax.swing.plaf.windows.WindowsLookAndFeel";
Java 程序设计大学教程
274
C,
private static final String WinLF =
UIManager.getLookAndFeel(Windows);
D,
private static final String WinLF =
UIManager.setLookAndFeel(Windows);
[习题 9] 以下 go 方法语法正确的是 。
A,
private static final go(int i) {
return "i="+i;
}
B,
private static void go(int i) {
return "i="+i;
}
C,
private static int go(int i) {
return "i="+i;
}
D,
private static final String go(int i) {
return "i="+i;
}
[习题 10] 以下关于 NetBeans IDE 的说法不正确的是 。
A、手工编写的 GUI 窗体及组件是无法在窗体编辑器中可视化显示出来的。
B、通常,可使用组件面板窗口将组件添加到窗体中,并在窗体编辑器中排列这些组件。
C、可使用“设计”视图可视化处理 GUI 窗体;而“源”视图则允许直接编辑窗体的源代码。
D、在“设计”视图中处理某个窗体时,窗体编辑器将自动生成代码,并在“源”视图中以蓝色背景显示这些代码。我们可以直接对这些代码进行编辑,以提高编程效率。
[习题 11] 在 NetBeans IDE“设计”视图中拖放组件,调整其位置和大小时,这些组件往往不听使唤。这是因为 。
A,Java 容器的缺省布局造成的
B,NetBeans IDE 不具备可视化调整组件位置和大小的功能
C、有些组件不可调整其位置和大小
D、没有将容器的布局改为 AbsoluteLayout
[习题 12] 要在 NetBeans IDE 中可视化设计时更改容器的布局,可以 。
A、在窗体编辑器中设置布局管理器
B、在“组件面板”窗口中设置布局管理器
C、在“检查器”窗口中设置布局管理器
D、以上方法都可以设置布局管理器。
Java 程序设计大学教程
275
答案,D
[习题 13] 使用 NetBeans IDE 在窗体编辑器中可视化设计菜单的 具体步骤是 。
1.添加菜单项
2.将菜单添加到菜单栏中
3.在窗体编辑器中创建菜单栏
4.为菜单项添加事件
A,1—〉 3—〉 2—〉 4
B,1—〉 4—〉 2—〉 3
C,3—〉 1—〉 4—〉 2
D,3—〉 2—〉 1—〉 4
[习题 14] JOptionPane 有四个静态方法来显示不同类型的消息,其中 可以显示一条消息并得到用户在一组选项中的选择。可返回代表选择项的整数。
A,showMessageDialog
B,showConfirmDialog
C,showOptionDialog
D,showInputDialog
[习题 15] 一旦 Java 程序设计成功,我们发行给用户最终使用的是后缀名为 的文件。
A,.java
B,.class
C,.exe
D,.dll
[习题 16] JAR 文件是一个简单的 ZIP 格式文件,它包含 。
A,.class 文件、程序需要的资源文件、以及 MANIFEST.MF 文件。
B,.class 文件、程序需要的动态链接库文件、以及 MANIFEST.MF 文件。
C,.java 文件、程序需要的,class 文件、以及 MANIFEST.MF 文件。
D,.class 文件、程序需要的类库文件、以及 MANIFEST.MF 文件。
[习题 17] 以下情况下,不会产生 AWT 的 action 事件的是 。
A、用户点击下拉列表的选项。
B、用户单击按钮。
C、用户在文本域中输入字符并回车。
D、用户选择菜单项。
练习题
[习题 18] 在 NetBeans IDE 中可视化设计以下“打印设置”窗体界面,不要求实现具体功能。
Java 程序设计大学教程
276
[习题 19] 在 NetBeans IDE 中可视化设计如图所示的“菜单设计练习”窗体界面。要求每次点击一个选中菜单项都弹出一个对话框,显示该菜单项的标题,并在窗体中的
JTextArea 组件中,记录每次操作的时间。
Java 程序设计大学教程
277
[习题 20] 设计一个如图所示的换算器程序。其中换算公式如下,
华氏温度转摄氏温度,C=5*(F-32)/9
摄氏温度转华氏温度,F=32+C*9/5
英里转公里,KM=miles / 0.62;
公里转英里,mile=0.62*KM
[习题 21] 在第 5章 [习题 25]的基础上设计一个图形用户界面的学生成绩管理程序。 要求能够,
1、增减学生对象(该对象包含学号、姓名、分数等信息) 。
2、登记、编辑分数。
3、计算出平均成绩。
4、统计出优秀( 100-90),良好( 89-75),中等( 74-60),不及格( 59-0)的人数百分比。
5、按学号查找学生及成绩。
6、按成绩分数排序。
(建议作为课程设计)