Java语言程序设计教程课件

PPT
  • 阅读 58 次
  • 下载 0 次
  • 页数 272 页
  • 大小 2.040 MB
  • 2022-11-24 上传
  • 收藏
  • 违规举报
  • © 版权认领
下载文档50.00 元 加入VIP免费下载
此文档由【小橙橙】提供上传,收益归文档提供者,本网站只提供存储服务。若此文档侵犯了您的版权,欢迎进行违规举报版权认领
Java语言程序设计教程课件
可在后台配置第一页与第二页中间广告代码
Java语言程序设计教程课件
可在后台配置第二页与第三页中间广告代码
Java语言程序设计教程课件
可在后台配置第三页与第四页中间广告代码
Java语言程序设计教程课件
Java语言程序设计教程课件
还剩10页未读,继续阅读
【这是免费文档,您可以免费阅读】
/ 272
  • 收藏
  • 违规举报
  • © 版权认领
下载文档50.00 元 加入VIP免费下载
文本内容

【文档说明】Java语言程序设计教程课件.ppt,共(272)页,2.040 MB,由小橙橙上传

转载请保留链接:https://www.ichengzhen.cn/view-45180.html

以下为本文档部分文字说明:

Java语言程序设计教程中国水利水电出版社21世纪高等院校计算机系列教材雷学生主编目录第1章Java语言概述第2章Java语言基础第3章控制语句第4章类及其方法第5章继承与多态第6章包和接口第7章异常处理第8章多线程编程第9章Applet编程第10章输

入与输出第11章常用工具包和类第1章Java语言概述Java语言是由Sun公司于1995年推出的一种新的编程语言,它是一种跨平台、适合于分布式计算环境的纯面向对象语言。Java语言及其扩展正在逐步成为互联网应用的规范,掀起了自PC机以来的又一次技术革命。本章

主要介绍Java语言的起源、特点、简单示例等。1.1Java语言的起源和发展1.2面向对象的程序设计1.3Java语言的特点1.4Java程序的运行[Return]1.1Java语言的起源和发展1.1.1几种典型语言的发展历程1

.1.2Java语言的起源1.1.3Java语言的发展[Return]一般认为,B语言导致了C语言的诞生、C语言演变出C++语言,而Java语言则明显带有C++语言的特征。本节将对Java语言的起源和

发展作简要介绍。1.1.1几种典型语言的发展历程[Return]Java总是和C++联系在一起,而C++则是从C语言派生而来的,所以Java语言继承了这两种语言的大部分特性。Java的语法是从C继承的,Java许多面向对象特性都受到C++的影响。事实上,Java中几个自定义

的特性都来自于或可以追溯到它的这些前驱语言。略有不同的是,Java语言完全面向对象,从而摒弃了二者的不足之处。Java语言的诞生与过去约30年中计算机语言的不断改进和发展密切相关。基于这些原因,下面我们将简要介绍一下这个发展历程。1.现

代编程语言的诞生:C语言2.对编程方法的新需要:C++语言3.时机的到来:Java语言的出现1.1.2Java语言的起源[Return]Java是由JamesGosling、PatrickNaughton、ChrisWarth、EdFrank以及MikeSheridan等人于1991年在S

unMicrosystems公司设计出来的,开发第一个版本花了18个月时间。该语言最初名叫“Oak‖,后来发现“Oak‖已经是Sun公司另外一种语言的注册商标,于1995年更名为“Java‖,即太平洋上一个盛产咖啡的岛屿的名字。从1992的秋天Oak问世,到199

5春天公开发布Java语言,许多人都对Java的设计和改进做出了贡献。1.1.3Java语言的发展[Return]自从于1995年被正式推出之后,Java语言就以其独特的优势迅猛发展,经过短短8、9年时间,成为迄今为止最为优秀的面向对象语言。Java也从当初的一种语言而逐渐形成

一种产业,基于Java语言的J2EE架构已成为微软.NET平台的强大竞争对手。当初,Java语言最初的发布不亚于一场革命,但是它并不标志着Java快速革新时代的结束。在Java1.0发布后不久,Java的设计者就已经制定

出了Java1.1、Java1.2、Java1.3、Java1.4、Java2、Java2.1.4版。1.2面向对象的程序设计1.2.1面向对象技术的提出1.2.2面向对象的编程思想1.2.3面向对象编程的基本原则[Return]面向对象的编程思想由来已久,但真正意义上的纯面向对

象编程语言目前只有Java。本节将结合几种高级语言对面向对象程序设计思想进行简要介绍。1.2.1面向对象技术的提出[Return]我们知道,所有的计算机程序均由两类元素组成:代码和数据。如何实现这两类元素的有效结合而形成可运行

的程序,是多年来程序设计人员所探索的问题。最初,程序的构筑一般围绕“正在发生什么”而编写代码,这种方法被称为面向过程的编程。使用这种方法编写的程序都具有线性执行的特点。面向过程的编程模型可认为是代码作用于数据,像Pascal、C这样的过程式

语言采用此模型是相当成功的。然而,使用面向过程的方法对小程序的编写可能是比较有效的,但当程序变得非常大且更为复杂时,就会出现种种问题,直至失去对代码的有效控制。由此对软件工程中的编程方法问题提出了新的要求。为了管理不断增加的复杂性,另外一

种编程方式被提了出来,即面向对象的编程(OOP,Object-OrientedProgramming)。这种编程方式围绕“谁将受到影响”进行,即以代码的相关数据为核心点进行程序编写。面向对象的编程着眼于它的数据(即对象)和为此数据严格定义的接口来组织程序,程序实际上是用数

据控制对代码的访问。这种方式的最大特点是代码与其相关数据被分离开来进行处理,有利于程序规模的扩大,而程序的可维护性得到增强。1.2.2面向对象的编程思想[Return]前面提到的面向过程程序,它遵循面向过程的问题求解方法,其中心思想是用计算机能够理解的逻辑来描述和表达待解决的问题及其具

体的解决流程。数据结构和算法是面向过程问题求解的核心所在。而面向对象技术则代表了一种全新的程序设计思路,其观察、表述、处理问题的方法,与传统的面向过程的编程方法不同。面向对象的程序设计和问题求解力求符合人们日常自然的

思维习惯,尽量分解、降低问题的难度和复杂性,从而提高整个求解过程的可监测性、可控制性和可维护性,以此达到以较小代价和较高效率获得较满意效果之目的。面向对象编程一个实质性的要素是抽象。1.2.3面向对象编程的基本原则1.封装性封装(Encapsulation)是将代码及其处理的

数据绑定在一起的一种编程机制,该机制保证了程序和数据都不受外部干扰且不被误用。一个对象的基本要素包括属性和作用在属性上的操作(方法或事件)。对象的使用实现了数据抽象,它将一组数据和对这组数据的操作结合成一个内在的整体,不允许外界对这组数据任

意进行访问,这里就用到了封装的原理。封装的目的是为了实现数据隐藏和数据保护,为对象提供一个对外操作的接口,外界只能从对象所提供的操作接口来认识和操作该对象。一般说来,面向对象的系统至少需具备三大特性:封装性、继承性、多态

性。将封装、继承、多态(包括重载)等面向对象方法应用于程序的开发工具和开发过程中,不仅可以加快开发的速度,还可极大地增强程序的可维护性和可扩展性,提高代码重用率。因此,在面向对象编程过程中需要遵循这三项原则。下面对它们分别作简要介绍。2.继承性继承(Inheritanc

e)是一个对象获得另一个对象的属性的过程。继承很重要,因为它支持了层级分类的思想。众所周知,大多数事物均可按层级(即从上到下、从高到低)分类管理。显然,如果不使用层级的概念,在进行描述时,我们就不得不分别定义每个

事物的所有属性。使用了继承,一个对象就只需定义使它在所属类中独一无二的属性即可,因为它可以从它的父类那里继承其他所有的通用属性。所以,完全可以这样说,正是继承机制使一个对象成为一个更具通用性的类的一个特定实例成为可能。继承是现实生活中一个非常容易理解的概念

。在面向对象的程序设计方法中,引入继承机制的目的在于:其一,避免可公用代码的重复开发,减少数据冗余;其二,增强数据的一致性,尽量降低模块间的耦合程度。3.多态性多态(Polymorphism)来自于希腊语,表示“多种形态

”,即允许一个接口被多个同类动作所使用的特征,具体使用哪个动作与应用场合有关。所谓多态性就是当不同的对象收到相同的消息时,产生不同动作的特性。这里所说的消息可以理解为方法或事件。通俗地讲,多态性就是使用一个名称

来定义不同的方法,这些方法执行类似的但又不同的操作,即以相同的接口来访问功能不同的函数,从而实现“一个接口,多种方法”。[Return]4.多态性、封装性与继承性的相互作用如果使用得当,在由多态性、封装性和继承性共同组成的编程环境中可以写出比面向

过程模型环境更健壮、扩展性更好的程序。精心设计的类层级结构是实现代码可重用性的基础;封装可以使你在不破坏依赖于类公共接口的代码基础上对程序进行升级迁移;而多态性则有助于编写清晰、易懂、易读、易修改的程序。1.3Java语言的特点1.3.1语言特点概述1.3.2Java语

言的具体特点1.3.3Java和C/C++的比较[Return]作为当前一种被广泛使用的面向对象编程语言,Java具有多方面的特点。如果与其他众多的编程语言作一下比较,会发现这些特点正是Java语言之所以如此风靡

的原因所在。虽然Java在某些方面(例如资源耗费)也存在一些不足,但这丝毫不影响Java作为目前最优秀面向对象编程语言的地位。1.3.1语言特点概述1.Java的灵魂:字节码2.Java的内涵:丰富的类库[Return]Java是一种被广泛使用的网络编程

语言,这是一种新的计算概念。网络环境下的编程语言最需要解决的是可移植性和安全性问题。以字节方式进行编码,使得程序不受运行平台和环境的限制成为可能。Java语言还提供了丰富的类库,使程序设计人员可以很方便地调用相关类建立起自己的系统。主要表现在:1.3.2Jav

a语言的具体特点1.简单性2.面向对象3.分布性4.鲁棒性5.可移植性[Return]Java作为一种高级程序设计语言,它除具有面向对象、编写简单、脱离机器结构、具有分布性、鲁棒性、可移植性、安全性特点外,

并且提供了并发机制,解释执行具有很高的性能。同时,还具有动态性特点。主要表现在:6.安全性7.结构中立8.高性能9.多线程10.动态性1.3.3Java和C/C++的比较[Return]对于变量声明、参数传递、操作符、流控制等,Java使用了和C/C

++相同的风格,这使得熟悉C/C++的程序员能很方便地进行编程语言切换。同时,Java实现并加强了其简单、鲁棒、安全等特性,也摒弃了C和C++中许多不合理的地方。主要表现在:1.头文件2.全局变量3.指针4.内存管理5.数据类型支持6.类型转换7.结构和联合8.预处理1.4Java程序的运

行1.4.1Java运行环境的安装与配置1.4.2第一个Java程序1.4.3两种类型的Java程序1.4.4Java环境的有关工具1.4.5Java程序的编写开发工具[Return]由于Java是采用Java虚拟机进行

解释执行的编程语言,它需要一定的软件支撑环境才能够运行起来。本节将先介绍Java的运行环境,然后介绍Java程序的编写。1.4.1Java运行环境的安装与配置[Return]编写并运行Java程序,需要Java开发工具包(JDK,JavaDevelopmentKit)的支持。因

此在编写自己的第一个Java程序前,读者需要先在自己的机器上安装JDK。到目前为止,JDK的发展经历了JDK1.0、JDK1.1、JDK1.2、JDK1.3、JDK1.4等几个版本。1.4.2第一个Java程序说明:保留字class来声明一个新的类,其类名为HelloWorldApp

,它是一个公共类(public)。整个类定义由大括号对{}括起来。在该类中,定义了一个main()方法,其中public表示访问权限,指明所有的类都可以使用这一方法;static指明该方法是一个类方法,它可以通过类名直接调用;void则指明main()方法不返回任何值。对于一个应用程序

来说,main()方法是必需的,而且必须按照如上的格式来定义。Java解释器在没有生成任何实例的情况下,以main()作为入口来执行程序。Java程序中可以定义多个类,每个类中可以定义多个方法,但是最多只有一个公共类,main()方法也只能有一个,作为程序的入口。在main()方法定义中的

,括号中的Stringargs[]是传递给main()方法的参数,参数名为args,它是类String的一个实例,参数可以为0个或多个,每个参数用“类名参数名”来指定,多个参数间用逗号分隔。在main()方法的实现中,只有一条语句:System.out.print

ln(″HelloWorld!″);它用来实现字符串的输出。“//‖后的内容为注释。下面是一个经典的Java入门程序,虽然只有短短几行代码,但其中的内容却很丰富,后面将作具体说明:publicclassHelloWorldApp{publicstat

icvoidmain(Stringargs[]){//Outputthefirstsentence:System.out.println(″HelloWorld!″);}}此程序的作用是输出下面一行信息:HelloWorld!Java程序的编译及运行[Retur

n]首先,将其保存到一个名为HelloWorldApp.java的文件中。注意:文件名应该与类名相同,因为Java解释器要求公共类必须放在与其同名的文件中。然后,对它进行编译。C:\JavaBook>javacHel

loWorldApp.java编译的结果是生成字节码文件HelloWorldApp.class。最后,使用java命令来运行该字节码文件。C:\JavaBook>javaHelloWorldApp其结果就是在显示器上显示出“HelloWorld!‖这行文字。1.4.3两种类型的Java程序

在Java中可以编写两类程序:应用程序(applications)和JavaApplet(小应用程序)。应用程序是可以在控制台上直接运行的程序,在创建应用程序时,Java与其他高级编程语言没有太大区别,而Java的特色就在于它具有编制小应用程序的功能。小应用程序是可以在Inter

net中传输并在兼容Java的Web浏览器中运行的应用程序。小应用程序实际上就是小型的Java程序,能像图像文件、声音文件和视频片段那样通过网络动态下载,它与其他文件的重要差别是,小应用程序是一个智能的程序,能对用户的输入作出反应,

并且能动态变化,而不是一遍又一遍地播放同一动画或声音。前面对applications作了介绍,下面我们来介绍一下JavaApplet的结构和应用。JavaApplet的结构和应用[Return]importjava.awt.*;importjava.applet.*;pu

blicclassHelloWorldAppletextendsApplet{//Thisisanapplet.publicvoidpaint(Graphicsg){g.drawString(″Hel

loWorld!″,30,30);}}该程序的功能是:在坐标(30,30)处输出字符串“HelloWorld!”。在这个程序中,没有实现main()方法,这是Applet与Application(应用程序)的区别之一。为了运行

该程序,首先也应将其保存到一个名为HelloWorldApplet.java的文件中,然后对其进行编译:C:\JavaBook>javacHelloWorldApplet.java这样将得到字节码文件HelloWorldApplet.class。由于Applet中没有m

ain()方法作为Java解释器的入口,我们必须编写HTML文件,然后将Applet嵌入其中,接着使用appletviewer来运行,或在支持Java的浏览器上运行。该HTML文件如下。<HTML><HEAD><TI

TLE>AnApplet</TITLE></HEAD><BODY><appletcode=″HelloWorldApplet.class″width=240height=50></applet></BODY></H

TML>其中,使用<applet>标记来启动HelloWorldApplet,code属性指明字节码所在的文件,width和height属性指明applet所占区域范围。最后,我们将此HTML文件存入A

ppletExp.htm,然后运行:C:\JavaBook>appletviewerAppExp.htm此时,将弹出一个Applet浏览窗口,在其中指定区域显示“HelloWorld!‖。下面再看一个例子1.4.4Java环境的

有关工具[Return]Java提供了一些常用的语言工具,主要包括:1、java:解释器2、javac:编译器3、appletviewer:小应用程序浏览器4、javah:头文件生成器5、javadoc:API文档生成器6、javap:类文件反汇编器7、jdb:Java语言调试器这些文件包

括在/java/bin/目录中,并可以在任何目录中运行,前提是设置了运行程序的相应系统路径。1.4.5Java程序的编写开发工具[Return]最后将要说明一下Java程序的编写开发工具。对于一般简单程序的编写,几乎使用任何文本编辑器都可以进行。

例如操作系统所带的记事本、写字板等程序。本书中所涉及的Java程序一般使用UltraEdit编写。UltraEdit是一个非常理想的Java程序编写器,它目前的最新版本是Version10。如果要进行比较复杂的Java应用系统开发,可使用专门的Java集

成开发工具,如JBuilder、VisualJ++、VisualAge、JCreator等。其中优秀的开发工具支持与应用服务如BEAWebLogic、IBMWebSphere的集成。具体选用哪种开发工具要视项目的

具体情况而定。第2章Java语言基础本章将介绍Java语言的基础知识,包括基本语言要素、基本数据类型、变量、数组、运算符等。扎实地掌握这些内容对后续学习是很有必要的。2.1预备知识2.2基本语言要素2.3基本数据类型2.4变量2.5数组2

.6运算符[Return]2.1预备知识2.1.1一个简单的Java程序2.1.2两种控制语句2.1.3关于程序块[Return]在第1章中,我们已经学会了编写“HelloWorld”这种极为简单的Java程序。为了便于本章后面内容的叙述,本节将再介绍一个稍复杂的Java程

序,使读者在学习Java语言的基础知识前也能够编写简单的Java程序。2.1.1一个简单的Java程序classMyExample{publicstaticvoidmain(Stringargs[]){intnum;num=

200;System.out.println("Thisisnum:"+num);num=num*2;System.out.print("Thevalueofnum*2is:");System.out.println(num);}}下面是一个简单的Java程序。读者将会看

到,这个程序虽然不是很复杂,但其中所包含的内容和功能却很丰富。运行结果:Thisisnum:200Thevalueofnum*2is:400对大多数的编程语言来说,程序源代码文件的命名是任意的,只要符合所运行的操作系统平台的要求即可。但这

对于Java来说就行不通。在开始进行Java编程前,读者需要知道的第一件事情就是:源文件的名称必须与主类名一致。这一点非常重要。对于上面的例子,源程序文件名就应该是MyExample.java。下面我们将解

释其中的原因。在Java中,一个源程序文件被称为一个编译单元(CompilationUnit),它是包含一个或多个类定义的文本文件。Java编译器要求源程序文件使用.java作为扩展名。请注意,文件扩展名的长度是4个字符,因此所用操作系统一定要有支持长文件名的能力。这就意味着DOS和W

indows3.x是不支持Java文件命名规则的。从上述示例程序中可以看出,程序中定义的类名也是MyExample,这不是巧合。在Java中,所有的代码都必须驻留在类中。按照约定,类名必须与源程序的文件名相同,同时还要确保文件名的大小写字母与类名一样,因为Java是区分大小写的。虽然文件

名与类名必须一致的约定显得似乎有些死板,但是这个约定有助于编程人员轻松地维护和组织程序。1.关于该程序的命名在第1章中曾经涉及到这方面的内容。要编译并运行示例程序MyExample,首先要运行编译器程序javac,并在命令行上指定源程序文件名,具体格式如下

。C:\>javacMyExample.java这样,编译器javac产生一个名为MyExample.class的文件,该文件包含程序的字节码。前面已讨论过,Java字节码中包含的是Java解释程序将要执行的指令码,因此j

avac的输出结果并不是可以直接运行的代码。要真正运行该程序,必须使用名为java的Java解释器。具体方法是将类名MyExample作为一个命令行参数输入,格式如下:C:\>javaMyExample如果程序运行正常,将

输出如下内容:Thisisnum:200Thevalueofnum*2is:4002.编译和运行程序提示:当Java源代码被编译后,每个单独的类都被放入自己的输出文件中,并以类的名字加.class扩展名为其文件名。这就是为什么Java源程序文件必须与其中包含的

类同名的原因—源程序文件将与.class文件同名。运行Java解释器,实际上是指定想要解释器运行的类的名字,它会自动搜索包含该名字且带有.class扩展名的文件。如果找到,它将运行包含在该指定类中的代码。[Return]2.1.

2两种控制语句1.if控制语句Java中的if控制语句与其他编程语言中的IF语句非常相似,并且与C/C++语言中的if语句的语法完全相同。它最简单的形式如下:if(condition)statement;其中:cond

ition是一个布尔表达式。如果其值为真,那么执行语句statement;如果其值为假,则语句statement将被绕过而不被执行。例如下列语句:if(num<2008)println("Youarewelcome!");该语句的功能是:如果变量num的值小于2008,那么条

件表达式的值为真,方法println()将被调用执行,否则方法println()被绕过而不被执行。尽管在后面的第3章中将详细讨论Java控制语句,这里我们还是先简要介绍两种控制语句,以便能在本章后面的例程中使用它们。classIfSample{publicstaticvoidma

in(Stringargs[]){inta,b;a=100;b=200;if(a<b)System.out.println("aislessthanb");a=a*2;if(a==b)System.out.println("anowequaltob");a=a*2;if(a>b

)System.out.println("anowgreaterthanb");}}下面的程序说明了if控制语句的用法。运行结果:aislessthanbanowequaltobanowgreaterthanb在几乎

所有的编程语言中,循环语句都是很重要的组成部分,这对于Java也不例外。事实上,在后面的有关章节中读者将会看到,Java提供了一套功能强大的循环结构,而for循环也许是最通用的。如果读者对C/C++熟悉,会发现Java的f

or循环和C/C++语言中的for循环操作完全一样。最简单的for循环结构的形式如下:for(initialization;condition;iteration)statement;其中,循环体的初始化部分(initialization)设置循环变量并为变量赋初始值。条件判断部分(c

ondition)是测试循环控制变量的布尔表达式。若测试结果为真,循环体(statement)继续反复执行;若测试结果为假,循环结束。迭代部分(iteration)的表达式决定循环控制变量在每次循环后是如何改变的。2.for循环语句c

lassForTest{publicstaticvoidmain(Stringargs[]){inta;for(a=0;a<10;a=a+1)System.out.println("Thisisa:"+a);}

}下面的这个短程序说明了for循环的使用方法。运行结果如下:Thisisa:0Thisisa:1Thisisa:2Thisisa:3Thisisa:4Thisisa:5Thisisa:6Thisisa:7Thisisa:8Thisisa:9在该例子中,

a是循环控制变量,它在for的初始化部分被初始化为零。在每次重复迭代(包括第一次)的开始,执行条件测试a<10。如果测试的结果为真,则println()语句被执行,然后执行循环体的迭代部分。此过程将持续进行下去,直到条件测试的

结果为假。注意:通常在Java专业程序员编写的程序中,循环体的迭代部分很少会看到象“a=a+1;‖的语句。原因是:Java语言中有一个特殊的增量运算符,即“++‖和“--‖,它们的作用分别是为对象加1和为对象减1。这样,前述的for循环语句通常写成下列

这样:for(a=0;a<10;a++)[Return]2.1.3关于程序块在Java语言中,可以将2个或2个以上的语句组成一个语句组,这样的一组语句被称为程序块(Codeblocks)。程序块通过将所属语句放在大括号中来实现。一旦创建了程序块,它就成为一个逻辑单元,可以作为一

个单独的语句来使用。例如,程序块可以作为Java中if控制语句和for控制语句的目标。下面我们来看一下如下的if控制语句:if(a<b){//beginablocka=b;b=0;}//endofblock本例

中,如果a小于b,那么在程序块内的两条语句都将被执行。因此,程序块中的这2条语句组成一个逻辑单元,不能出现一条语句运行,而另一条语句不运行的情况。其中的关键点是如果你需要将两个或多个语句在逻辑上连接起来,就可以将其放入一个程序块中。2.1.3关于程序块又例如

,下面的程序将for循环作为一个程序块使用:classBlockTest{publicstaticvoidmain(Stringargs[]){inta,b;b=20;//thetargetofthisloopisablockfor(a=0;a<10;

a++){System.out.println("Thisisa:"+a);System.out.println("Thisisb:"+b);b=b-2;}}}在本例中,for循环作为一个程序块使用,而不是一个单独的语句。这样每循环一次,块内的3条语句都要运行一次,此事实当然为程序的执行结果所证实

。在本书的后面,读者将会看到程序块的其他性质和用法。勿庸置疑,程序块存在的主要原因是为了创建逻辑上独立的代码单元。[Return]2.2基本语言要素2.2.1标识符2.2.2Java关键字2.2.3字面量2.2.4分隔符2.2.5注释[Return]前面我们通过几个短的程序使

读者对Java编程有一个初步的轮廓。在本节里,我们将开始对Java语言元素进行介绍,Java的基本语言要素包括标识符、关键字、字面量、分隔符、注释、数据类型、变量以及运算符等。2.2.1标识符标识符(Identifier)是赋给类、方法或者变量的名称。一个Java标识符可以由大写

/小写字母、数字、下划线(_)、美元符号($)按照一定的顺序组合而成,但不能以数字开头,因为这样容易与数字、常量相混淆。下面是一些合法的标识符:TotalTmpCountx4$myvarthis_is_var以下是一些非法的标识符:2thupkuh

igh-digNot/okjava&umlit’s_a+b+c注意:在Java中标识符是严格区分大小写的。例如,MyVal和Myval在Java中是两个完全不同的标识符。[Return]2.2.2Java关键字关键字是指被系统所保留使用的标识符,这些标识符不能

被编程人员用作变量名、类名或方法名。目前,Java语言中保留的关键字如下:abstractassertbooleanbreakbytecasecatchcharclassconstcontinuedefaultdodoubleelseextendsfalse*finalfinallyfloat

forgotoifimplementsimportinstanceofintinterfacelongnativenewnull*packageprivateprotectedpublicreturnshortstaticstri

ctfp∆superswitchSynchronizedthisthrowthrowstransienttrue*tryvoidvolatilewhile其中,关键字strictfp在Java1.2版本中被加入,关键字assert则是在Java

1.4版本中新增的。需要说明的是,关键字const和goto虽然被保留,但尚未被当前的Java规范所使用。在上面的关键字中,true、false和null是Java所定义的常值。虽然它们不用作系统标识符,你也不能使用这些词作

为类名、方法名、变量名等。[Return]2.2.3字面量[Return]在Java中,字面量(literal)是指由文字所表示的取值,也可以称为常量。例如,下面所列的就是一些字面量:10099.99'A'"Thisisafro

g"上面的这些字面量中,第一个表示一个整数,第二个是一个浮点值,第三个是一个字符型的常量,最后一个则是一个字符串值。字面量(常量)能在程序中的任何地方被它所允许的类型直接使用,代表的是所属类型的一个实在值。关于字面量,我们将在介绍完Java语言的数据类

型后作进一步的说明。2.2.4分隔符此外,还有一种“看不见”的分隔符,即空白分隔符(Whitespace)。Java是一种形式自由的语言,这意味着编程人员不需要遵循任何特殊的缩进书写规范。例如,程序的所有代码可以处在同一行上(一般情况下

没人这么做,因为可读性差),也可按照自己喜欢的方式输入和编排程序代码,前提是必须在已经被运算符或分隔符描述的标记之间留出空白分隔符。在Java中,空白分隔符可以是空格符、Tab跳格键,或者是换行符。[Return]在Ja

va中,有一些字符被系统当作分隔符使用,最常用的分隔符是分号(;),用来分隔不同的Java语句。下表中给出了Java中常见的分隔符。分隔符名称功能说明{}大括号(花括号)用来定义程序块、类、方法以及局部范围,也用来包括自动初始化的数组的值。

[]中括号(中括号)用来进行数组的声明,也用来表示撤消对数组值的引用。()小括号(圆括号)在定义和调用方法时用来容纳参数表。在控制语句或强制类型转换组成的表达式中用来表示执行或计算的优先权。;分号用来表示一条语句的结束。,逗号在变量声明中,用于分隔变量表中的各个变量。在for

控制语句中,用来将圆括号内的语句连接起来。.点号用来将软件包的名字与它的子包或类分隔。也用来将引用变量与变量或方法分隔。2.2.5注释[Return]Java中有3种类型的注释(comments)方式,其中前两种注释方式即单行注释和多

行注释已作介绍。第三种方式被称为文档注释(Documentationcomment),它以“/**‖开始,以“*/‖标志结束。文档注释提供将程序信息嵌入到程序中的功能。开发人员可以使用javadoc工具将信息取出,然后转换为HTML文件。这种注释方式提供

了编写程序文档的便捷方式。javadoc工具生成的文档十分常见,因为Sun的JavaAPI文档库就是这么生成的。javadoc所识别的标记见教材P28表2-2。标记的使用方法见教材P29~31。从表2-2中不难发现,在进行文档注释时,所有的文档标记都以

“@‖标志开始。在一个文档注释中,也可以使用其他的标准HTML标记。然而,有些标记(如标题)是不能使用的,因为它们会破坏由javadoc生成的HTML文档外观属性。同时可以使用文档注释为类、接口、域、构造函数和方法提供文档。在所有

这些情况中,文档注释必须紧接在被注释的项目之前。为变量作注释,可使用@see、@since、@serial、@serialField和@deprecated文档标记;为类作注释,可使用@see、@author、@since、

@deprecated和@version文档标记;为方法作注释,可使用@see、@return、@param、@since、@deprecated、@throws、@serialData和@excep

tion作标记。而{@link}和{@docRoot}标记则可以用在任何地方。2.3基本数据类型2.3.1Java是强类型语言2.3.2整数类型2.3.3浮点类型2.3.4字符类型2.3.5布尔类型2.3.6对字面量的进一步讨论[Return]本节将介绍Ja

va语言中最基本的数据类型。同其他所有的高级编程语言一样,Java支持多种数据类型,它们可以用来声明变量、创建数组以及其他更复杂的数据结构。2.3.1Java是强类型语言Java的安全和健壮性很大程度上来自于它是一种强类型语言。究其原因:首先,每个变量有类型,每个表达式有类型,而且每种类

型都是严格定义的;其次,所有的数值传递,不管是直接的还是通过方法调用经由参数传过去的,都要先进行类型相容性的检查,而有些语言就没有自动强迫进行数据类型相容性的检查或对冲突的类型进行转换的机制。Java编译器

对所有的表达式和参数都要进行类型相容性的检查,以保证类型是兼容的。任何类型的不匹配都将被报告为错误而不是警告。在编译器完成编译以前,错误必须被改正过来。Java语言中定义了8种基本数据类型:字节型(byte)、短整

型(short)、整型(int)、长整型(long)、字符型(char)、浮点型(float)、双精度型(double)、布尔型(boolean),这些类型可分为如下几组。1、整数类型:该组包括字节型(byte

)、短整型(short)、整型(int)、长整型(long),它们都是有符号整数;2、浮点类型:该组包括浮点型(float)和双精度型(double),它们代表有小数精度要求的数值;3、字符类型:此组包括

字符型(char),它代表字符集的符号,例如字母和数字;4、布尔类型:此组包括布尔型(boolean),它是一种特殊的类型,表示真/假值。[Return]注意:简单数据类型代表单值,而不是复杂的对象。Java是完全面

向对象的,但简单数据类型却不是,它们类似于其他大多数非面向对象语言中的简单数据类型。这样做的原因是出于效率方面的考虑。在面向对象中引入简单数据类型不会对执行效率产生太多的影响。2.3.2整数类型Java语言中定义了4种整数数据类型:字节型(byte)、短整型(short)、整型(

int)、长整型(long),都是有符号整数,即正数或是负数。Java不支持仅仅是正的无符号整数。许多其他编程语言,例如C/C++,支持有符号或无符号的整数。然而,Java的设计者感到无符号整数是不必要的,因为他们感到无符号(unsi

gned)概念主要是被用来指定高位状态,它定义了当int表示一个数字时的符号。而在后面读者将会看到,Java对高位状态的管理是通过一套机制实现(通过增加一个专门的“无符号右移”运算符来管理高位)。这样,就不需要专门定义

无符号整数类型了。整数类型的长度不应该被理解为它所占用的存储空间,而应该是该类变量和表达式的行为特性。只要对类型进行了说明,Java的运行环境对该类的大小是没有限制的。事实上,为了提高性能,至少字节型和短整型的存储是32位(而非8位和16位),因为这是现在大多数计算机系

统所使用的字的大小。各种整数类型的长度和取值范围见教材P34表2-3所示。其定义和使用方法见教材P34~35。[Return]2.3.3浮点类型浮点类型(Floating-PointTypes)数据,也就是大家

常听说的实型(real)数据,当计算的表达式有精度要求时被使用。例如,计算平方根,或计算正弦、余弦等。这些计算结果的精度要求使用浮点型。Java语言实现了标准(IEEE-754)的浮点型和运算符集。在Java中有两种浮点类型:单精度浮点型(float

)和双精度(double)浮点型。它们的长度和取值范围见教材P36表2-4所示。其定义和使用方法见教材P36。[Return]2.3.4字符类型在Java语言中,存储字符的数据类型是char,形式上和C/C++语言是相似的。但需要注意的是,Java中的char与C/C++中的char是不同的。

在C/C++中,char的取值由8bits整数来代表,而Java中则使用Unicode码代表字符。Unicode所定义的国际化字符集能表示迄今为止人类语言的所有字符集。Unicode字符集是几十种语言字符集的统一,如拉丁文、希腊语、阿拉伯语、古代斯拉夫

语、希伯来语、汉语、日文片假名、匈牙利语等等。这样,它要求使用16位的宽度表示。所以,Java中的char类型是16位,其取值范围是0~65,536,没有负数的char。人们熟知的标准字符集ASCII码的范围仍然是0~127,扩展的8位字符集ISO-Latin-1的范围是0~255。既

然在Java中设计了允许在全球范围内使用的applet小程序,因此使用Unicode码代表字符是必需的。当然,Unicode字符的使用对于英语、德语、西班牙语或法语的语言是有些低效,因为这些语言的字符能够很轻松地被包含在8位以内。但是为了程序的的可移植性和通用性,付出这一点代价是很有必要

的。请看教材P37~38页的例子。[Return]2.3.5布尔类型在Java语言中,有一种表示逻辑值的简单类型,称为布尔型,它的取值只能是true(真)或false(假)这两个值中的一个。它是所有的诸如a<b这样的关系运算的返回类型。布尔类型对管理像if、for这样的控制语句的条件表达式

也是必需的。下面的程序说明了布尔类型的使用。classBoolTest{publicstaticvoidmain(Stringargs[]){booleanb;b=true;System.out.println("bis:"+

b);b=false;System.out.println("bis:"+b);if(b)System.out.println("Thisisexecuted.");b=false;if(b)System.out.println("Thisisnotexe

cuted.");System.out.println("11>1is"+(11>1));}}运行结果如下:bis:truebis:falseThisisexecuted.11>1istrue[Return]下面对以上程序

代码作几点说明。首先,读者已经看到了,当用方法println()输出布尔类型的值时,显示的是“true‖或“false‖;其次,布尔变量的值本身就足以用来对if语句进行控制,没有必要将if语句写成如下的形式。if(b==true).

..另外,关系运算符(例如<)的结果是布尔值。这样,表达式11>1的值就是“true‖。在表达式11>1的两边额外地加上括号,是由于加号“+‖运算符的优先级比运算符“>‖的优先级要高。2.3.6对字面量的进一步讨论在

本章2.2.3小节中我们曾简要介绍过字面量(或常量)。现已介绍完基本数据类型,让我们对字面量作进一步讨论。1.整数型字面整数可能是程序中最常用的类型。任何一个数字的值就是一个整数字面量。例如1、2、3、42等

。这些都是十进制的值,这意味着对它们的描述基于数字10。在Java中,还有另外两种进制被整数字面量使用:八进制(Octal,基数是8)和十六进制(Hexadecimal,基数是16)。八进制的值通过在它的前面加一个前导0来表示,而正常的十进制数字则不用前导零。这样看起来有效

的值09对八进制来说将产生一个编译错误,因为9超出了八进制的范围0~7。对编程人员来说,十六进制更常用,它清楚地与8的大小相匹配,如8、16、32、64等。通过前导的0x或0X表示一个十六进制的字面量。十

六进制数的范围是0~15,用A~F(或a~f)来替代10~15。整数字面量产生int值,在Java中它是32位的整数值。既然Java对类型要求严格,你可能会纳闷,为什么将一个整数字面量赋给Java的其他整数类型如b

yte或long而没有产生类型不匹配的错误呢。庆幸的是,这个问题已很好解决。当一个字面量的值被赋给一个byte或short型的变量时,如果字面量的值没有超过对应类型的范围则不会产生错误。所以,一个字面量总是可以被赋给一个long变量。但是,指定一个long字面量,你需要清楚地告诉编译器

字面量的值是long型,这可以通过在字面量的后面加一个大写或小写的L来做到这一点。例如0x7ffffffffffffffL(或9223372036854775807L)就是long型中最大的。2.浮点型字面量浮点数是指具有小数部分的十进制数值,它们可以通过标准记数法或者科学记数法来表示。

标准记数法(Standardnotation)由整数部分加小数点加小数部分组成。例如2.0、3.14159、0.6667等都是有效的标准记数法的浮点数值。科学记数法(Scientificnotation)是浮点数加一表明乘以10的指定幂次的后缀,指数是紧跟E或e的一个十进制的数字,它

可以是正值、零或者负值。例如6.022E23、314159E-05、2e+100等。Java中的浮点字面量默认是双精度。为了指明一个浮点字面量,编程人员必须在字面量后面加F或f。当然,你也可以通过在字面量后面加D或d来指明一个双精度浮点字面量,但这

样做当然是多余的。默认的双精度类型要占用64位存储空间,而精确度低些的浮点类型仅仅需要32位。3.布尔型字面量布尔型字面量很简单,因为布尔型字面量只有两个逻辑值:treu(真)或false(假)。真值或

假值不会改变任何数字的表示。在Java中,真字面量的值不等于1,假字面量的值也不等于0,它们仅仅能被赋给已定义的布尔变量,或在布尔的运算符表达式中使用。这一点与C/C++中是不同的。4.字符型字面量我们已经知道,Java采用Unicode字符集来表示字符。Java的

字符是16位值,可以被转换为整数并可进行像加或减这样的整数运算。通过将字符包括在单引号之内来表示字符字面量。所有可见的ASCII字符都能直接被包括在单引号之内,例如'a','z',and'@'。对于那些不能直接被包括的字符,有若干转义序列,

这样允许你输入所需要的字符。例如,'\'代表单个引号字符本身,'\n'代表换行符字符。为直接得到八进制或十六进制字符的值也有一个机制。对八进制来说,使用反斜线加3个阿拉伯数字。例如,‘\141’是字母‘a

’。对十六进制来说,使用反斜线和u加上4个十六进制阿拉伯数字。例如,‘\u0061’因为高位字节是零,代表ISO-Latin-1字符集中的‘a’。‘\ua432’是一个日文片假名字符。教材P41页表2-5列出了Java中的字符转义序列。5.字符串字面量同其他大多数编程语言一样,J

ava中的字符串字面量使用双引号括起来。字符串字面量的例子如下。"HelloWorld!""three\nlines""\"Thisisinquotes\""为字符型字面量所规定的字符转义序列和八进制/十六进制记

法,在字符串内同样是适用的。对于Java字符串,应特别注意的是它们必须在代码的同一行开始、同一行结束,而不像某些语言有换行连接转义序列。注意:读者可能知道,在其他大多数语言(包括C/C++)中,字符串作为字

符的数组被实现,然而在Java中却并非如此。在Java中,字符串实际上是对象类型,Java是将字符串作为对象实现的。因此,它有广泛的字符串处理能力,而且功能既强又好用。在Java中,如果要声明真正意义上的常量(即使用标识符代表某个常值),可使用关键字final(类似于C/C++中的const)。例

如,在下面的例子中就声明了一个int类型常量:finalintARRAY_SIZE=5;[Return]2.4变量2.4.1Java变量的声明2.4.2变量的作用域和生存期2.4.3类型转换[Return]变

量是Java程序中的一个基本存储单元。变量由一个数据类型、标识符以及一个可选初始值的组合来进行定义。此外,所有的变量都有一个作用域,指定变量的可见性和生存期。本节将对Java变量及其相关问题作详细说明。2.4.1Java变量的声明在Java中,所有的变量必须

先声明再使用。基本的变量声明方法如下。typeidentifier[=value][,identifier[=value]...];其中,type应是Java的基本类型之一,或者类及接口类型的名字(关于类和接口将在本书后面有关章节中

讨论)。identifier(标识符)是变量的名字,其后用一个等号和一个值来初始化变量。需要注意的是,初始化表达式的取值必须与指定变量类型一样或兼容。在声明指定类型的多个变量时,使用逗号将各变量分开。以下是变量声明的几个例子,其中有一

些已经进行了初始化:inta,b,c;//declaresthreeints,a,b,andc.intd=3,e,f=5;//declaresthreemoreints,initializingdandf.byteg=123;//initializesg.doublepi=3.1

416;//declaresanapproximationofpi.charx='x';//thevariablexhasthevalue'x'.不难知道,你所选择的标识符名称没有任何表明它们相应类型的成分。许多读者可能还记

得FORTRAN语言中预先规定从I到N的所有标识符都为整型变量,而其他标识符为实型变量。Java则允许任何合法的标识符具有任何它所声明的数据类型。1.声明一个变量[Return]尽管前面的例子中仅将字面量作为其初始值,Java语言也允许在变量声明时使用任何有效的表达

式来动态地初始化变量。例如,下面的程序代码用于在给定直角三角形两个直角边长度的情况下,求其斜边长度。//Demonstratedynamicinitialization.classDynInit{publicstaticv

oidmain(Stringargs[]){doublea=3.0,b=4.0;//cisdynamicallyinitialized:doublec=Math.sqrt(a*a+b*b);System.out.println("Hypotenuseis

:"+c);}}在这里,我们定义了3个局部变量a、b、c。前两个变量a和b被初始化为常量,而直角三角形的斜边c则被动态地初始化(使用勾股定理)。该程序用了Java中另外一个内置的方法sqrt(),它是Math类的一个成员,计算它的参数的平方根。这里关键的一点是初始化表达式可以使用

任何有效的元素,包括方法调用、其他变量或字面量。2.动态初始化2.4.2变量的作用域和生存期大多数计算机编程语言定义了两大类作用域:全局和局部。然而,这些传统型的作用域并不适合Java的严格的面向对象编程模型。将一个变量定义为全局变量当然是可行的,但这是例外而不是规则。

在Java中,两个主要的作用域是通过类和方法定义的。尽管类的作用域和方法的作用域的区别有点人为规定,因为类的作用域有若干独特的特点和属性,而且这些特点和属性不能应用到方法定义的作用域,但这些差别还是很有意义的。类(以及在其内定义的变量)的作用域问题我们将在本书后面的章节中介

绍。到现在为止,我们将仅仅考虑由方法或在一个方法内定义的作用域。到目前为止,我们所使用的所有变量都是在方法main()后面被声明。事实上,Java允许变量在任何程序块内被声明。前面已经说明过了,程序块被包括在一对大括号中。一个程序块定义了一个作用域。这样,每当开始一个新块,你就创建了一个新的

作用域。不难发现,一个作用域决定了哪些对象对程序的其他部分是可见的,也决定了这些对象的生存期。下面对作用域问题作几点说明:(1)方法定义的作用域以它的左大括号开始。但是,如果该方法有参数,那么它们也被包括在该方法的作用域中

。参数问题在后面章节中将作进一步的讨论,因此现在可认为它们与方法中其他变量的作用域是一样的。(2)作为一个通用规则,在一个作用域中定义的变量对于该作用域外的程序是不可见(即访问)的。因此,当你在一个作用域中定义一个

变量时,你就将该变量局部化并且保护它不被非授权访问和/或修改。实际上,作用域规则为封装提供了基础。(3)作用域可以进行嵌套。例如,每当你创建一个程序块,就创建了一个新的嵌套的作用域。这样,外面的作用域包含内部的作用域。这意味着外部作用域定义

的对象对于内部作用域中的程序是可见的。但反过来就不是,内部作用域定义的对象对于外部是不可见的。考察下面的程序。//Demonstrateblockscope.classScopeExample{publicstaticvo

idmain(Stringargs[]){intx;//knowntoallcodewithinmainx=10;if(x==10){//startnewscopeinty=20;//knownonl

ytothisblock//xandybothknownhere.System.out.println("xandy:"+x+""+y);x=y*2;}//y=100;//Error!yisnotknownhere.//

xisstillknownhere.System.out.println("xis"+x);}}正如注释中说明的,在方法main()的开始定义了变量x,因此它对于main()中的所有的随后代码都是可见的。在if程序块中定义了变量y,因

为一个块定义一个作用域,y仅仅对在它的块以内的其他代码可见。这就是在它的块之外的程序行y=100;被注释掉的原因。如果你将该行前面的注释符号去掉,编译程序时就会出现错误,因为变量y在它的程序块之外是不可见的。在if程序块中可以使用变量x,因为块(即一个嵌套作用域)中的程序代码可以访问被其

包围作用域中所定义的任意变量。显然,变量可以在程序块内的任何地方被声明,但是只有在他们被声明以后才是合法有效的。因此,如果你在一个方法的开始定义了一个变量,那么它对于在该方法以内的所有程序都是可用的。反之,如果你在一个程序块的末尾声明了一个变量,它就没有任何用处,因为没有程序会访问它。另一个需要注

意的地方是:变量在其作用域内被创建,离开其作用域时被撤消。这意味着一个变量一旦离开它的作用域,将不再保存它的值了。因此,在一个方法内定义的变量在几次调用该方法之间将不再保存它们的值。同样,在块内定义的变量

在该块被舍弃时,也将丢掉它的值。因此,一个变量的生存期就被限定在它的作用域之内。如果一个声明定义包括了动态初始化,那么每次进入声明它的程序块时,该变量都要被重新初始化。例如,考虑如下的程序。//Demonstratelifetimeofavaria

ble.classLifeTime{publicstaticvoidmain(Stringargs[]){intx;for(x=0;x<3;x++){inty=-1;//yisinitializedeachtimebloc

kisenteredSystem.out.println("yis:"+y);//thisalwaysprints-1y=100;System.out.println("yisnow:"+y);}}}可

以看到,每次进入内部的for循环,y都要被重新初始化为-1。这样,即使它随后被赋值为100,该值还是被丢弃了。运行结果如下:yis:-1yisnow:100yis:-1yisnow:100yis:-1yisnow:100[Return]另外还有一点,尽管程序块能被嵌套,但不能将

内部作用域声明的变量与其外部作用域声明的变量重名。在这一点上,Java不同于C和C++。下面的例子企图为两个独立的变量起同样的名字。在Java中,这是不合法的。但在C/C++中,它将是合法的,而且两个变量bar将是独立的。//×Thisprogramwillno

tcompile×classScopeErr{publicstaticvoidmain(Stringargs[]){intbar=100;{//createsanewscopehere.intbar=200;//Compile-timeerror–baralreadydefined!}}}

2.4.3类型转换如果读者以前有一定的编程经验,那么就应该知道将一种类型的值赋给另外类型的一个变量是相当常见的。如果这两种类型是兼容的,那么Java将自动地进行转换。例如,将int类型的值赋给long类型的变量,总是可行的。然而,不是所有的类型都

是兼容的,因此,不是所有的类型转换都是可以隐式实现的。例如,没有将double型转换为byte型的定义。幸好,获得不兼容的类型之间的转换仍然是可能的。要达到这个目的,必须使用一个强制类型转换,它能完成两个不兼容的类型之间的显式变换。让我们看看自动

类型转换和强制类型转换。1.Java的自动类型转换如果下列两个条件都能满足,那么将一种类型的数据赋给另外一种类型变量时,将执行自动类型转换。这两个条件分别是:这两种类型是兼容的;目的数据类型的取值范围比来源数据类型的取值范围要大。当满足以

上两个条件时,自动的“加宽转换”就可以进行。例如,int型的取值范围比所有byte型的合法取值范围要大,因此不需要随后将介绍的显式强制类型转换语句。对于加宽转换,数字类型,包括整数(integer)和浮点(flo

ating-point)类型都是彼此兼容的,但是,数字类型和字符类型(char)或布尔类型(boolean)是不兼容的。字符类型(char)和布尔类型(boolean)也是互相不兼容的。2.不兼容类型的强制类型转换尽管自动类型转换是很有帮助的,但并不能满足所有的编程需要。例如

,当需要将int型的值赋给一个byte型的变量时该怎么办?这种转换不会自动进行,因为byte型的有效取值范围比int型的要小。此时,只有采取被称为“变窄转换”的转换方式。因为你肯定要将源数据类型的值变小才能适合目标数据类型。在这种情况下,为了完成两种不兼容类

型之间的转换,就必须进行强制类型转换。所谓的强制类型转换是一种显式的类型变换方式,其通用格式如下。(target-type)value其中,目标类型(target-type)指定了要将指定值所转换成的类型。例如,下面的程序段将int型强制转换成b

yte型。如果整型变量的取值超出了byte型的取值范围,它的值将会因为对byte型的值域取模(整数除以byte得到的余数)而减少。inta;byteb;//...b=(byte)a;当把一个浮点值赋给整数类型时,将发生一种不同的类型转换

:截断。大家都知道整数没有小数部分。这样,当把浮点值赋给整数类型时,它的小数部分将会被舍去。例如,如果将值1.23赋给一个整数,其结果只是1,0.23被舍弃。当然,如果浮点值太大而不能适合目标整数类型

,那么它的值将会因为对目标类型值域取模而减少。//Demonstratecasts.classConversion{publicstaticvoidmain(Stringargs[]){byteb;inti=258;doubled=338.136;

System.out.println("\nConversionofinttobyte.");b=(byte)i;System.out.println("iandb"+i+""+b);System.out.println("\nConversionofdoubleto

int.");i=(int)d;System.out.println("dandi"+d+""+i);System.out.println("\nConversionofdoubletobyte.")

;b=(byte)d;System.out.println("dandb"+d+""+b);}}下面让我们分析一下每个类型转换。当值258被强制转换为byte变量时,其结果是258除以256(256是byte类型的变化范围)的余数2;当把变量d转换为int型,它的小数部

分被舍弃了;当把变量d转换为byte型,它的小数部分被舍弃了,而且它的值减少为256的模,即82。运行结果如下:Conversionofinttobyte.iandb2582Conversionofdoubletoint.dandi338.136338C

onversionofdoubletobyte.dandb338.13682下面的这个程序说明了强制类型转换过程。以下是不会丢失信息的类型转换方式原始类型目标类型byteshort,char,int,long,float,doubleshortint,long,float,double

charint,long,float,doubleintlong,float,doublelongfloat,doublefloatdouble3.表达式中类型的自动提升除了赋值方式,类型变换还可以在表达式中进行。在表达式中,对中间值的精确要求

有时超过任何一个操作数的范围。例如,考察下面的表达式。bytea=40;byteb=50;bytec=100;intd=a*b/c;中间项结果a*b很容易超过它的任何一个byte型操作数的范围。为处理这种问题,当分析表达式时,Java自动提升

各个byte型或short型的操作数到int型。这意味着子表达式a*b使用整数而不是字节型来执行。这样,尽管变量a和b都被指定为byte型,50*40中间表达式的结果2000是合法的。自动类型提升有好处

,但它也会引起令人疑惑的编译错误。例如,下面这个看起来正确的程序却会引起一些问题。byteb=50;b=b*2;//Error!Cannotassignaninttoabyte!该程序试图将一个完全合法的byte型的值50*2再存储给一个byte型的变量。但是,当表达式求值

的时候,操作数被自动地提升为int型,计算结果也被提升为int型。这样,表达式的结果现在是int型,不强制转换它就不能被赋为byte型。在理解了上述溢出后果的情况下,就知道应该使用一个显式的强制类型转换。byteb=50;b=(byte)(b*2);这样将产生正确的结果100。4.关于类型提升的若

干约定[Return]除了将byte型和shorts型提升到int型以外,Java语言还规定了若干适用于表达式的类型提升规则。首先,如前面所描述的,所有的byte型和short型的值被提升到int型。其次,如果一个操作数是

long型,整个表达式将被提升到long型;如果一个操作数是float型,整个表达式将被提升到float型;如果有一个操作数是double型,计算结果就是double型。例如,下面的程序显示了在表达式中的每个值是如何被提升的,以匹配各自双目运算符的第

二个参数。classPromote{publicstaticvoidmain(Stringargs[]){byteb=42;charc='a';shorts=1024;inti=50000;floatf

=5.67f;doubled=.1234;doubleresult=(f*b)+(i/c)-(d*s);System.out.println((f*b)+"+"+(i/c)+"-"+(d*s));Syste

m.out.println("result="+result);}}让我们进一步考察一下发生在下列程序行的类型提升。doubleresult=(f*b)+(i/c)-(d*s);在第一个子表达式f*b中,变量b被提升为float类型,

该子表达式的结果当然是float类型。接下来,在子表达式i/c,中,变量c被提升为int类型,该子表达式的结果当然是int类型。然后,子表达式d*s中的变量s被提升为double类型,该子表达式的结果当然也是double类型。最后,考虑三个中间值,f

loat类型,int类型,和double类型。float类型加int类型的结果是float类型。然后float类型减去提升为double类型的d*s变量,该表达式的最后结果是double类型。2.5数组2.5.1一维数组2.5.2多维数

组2.5.3声明数组的另一种格式2.5.4关于Java中的字符串[Return]数组(Array)是在基本数据类型的基础上发展而来的一种较高级的数据结构类型,它是相同类型变量的集合,可以使用共同的名字对

它进行引用。本节将具体对Java中的数组问题进行介绍。2.5.1一维数组尽管该例子定义了month_days是一个数组变量的事实,但实际上没有数组变量存在。事实上,month_days的值被设置为空,它代表一个数组没有值。为了使数组month_days成为实际的、

物理上存在的整型数组,必须用运算符new来为其分配地址,并将它赋给month_days。运算符new是专门用来分配内存的运算符。尽管该例子定义了month_days是一个数组变量的事实,但实际上没有数组变量存在。事实上,month_days

的值被设置为空,它代表一个数组没有值。为了使数组month_days成为实际的、物理上存在的整型数组,必须用运算符new来为其分配地址,并将它赋给month_days。运算符new是专门用来分配内存的运算符。[Return]数组可被定义为任何有效数据类型,可以是一维的,也

可以是多维的。对数组元素的访问通过其下标进行。数组提供了一种将有联系的信息进行分组的有效方法。一维数组实质上是相同类型的变量列表。要创建一个数组,首先必须指定数组变量所需的类型。一维数组的通用声明格式如下。typevar-name[]

;其中,type指定了数组的基本类型。基本类型决定了组成数组的每一个基本元素的数据类型。这样,数组的基本类型决定了数组存储的数据类型。例如,下面的例子定义了数据类型为int,名为month_days的数组。i

ntmonth_days[];显然,获得一个数组需要两步:第一步,首先必须定义变量所需的类型;第二步,必须使用运算符new来为数组所要存储的数据分配内存,并将它们分配给数组变量。注意:所有的数组下标从零开始。2.5.2多维数组在Java中,多

维数组实际上就是“数组的数组”。在定义多维数组变量时,要将每个维数放在它们各自的方括号中。例如,下面的语句声明了一个名为twoD的二维数组变量。inttwoD[][]=newint[4][5];该语句

分配了一个4行5列的数组并把它分配给数组twoD。实际上这个矩阵表示了int类型数组的实现过程。下列程序从左到右,从上到下为数组的每个元素赋值,然后显示数组的值。//Demonstrateatwo-dimensiona

larray.classTwoDArray{publicstaticvoidmain(Stringargs[]){inttwoD[][]=newint[4][5];inti,j,k=0;for(i=0;i<

4;i++)for(j=0;j<5;j++){twoD[i][j]=k;k++;}for(i=0;i<4;i++){for(j=0;j<5;j++)System.out.print(twoD[i][j]+"");System.out.println();}}}程序运行的结果如下:012

345678910111213141516171819当给多维数组分配内存时,只需指定第一个(即最左边)维数的内存即可。你可以单独地对余下的维数进行内存分配。例如,下面的程序在数组twoD被定义时给它的第一个维数分配内存,对第二维则是手

工分配内存。inttwoD[][]=newint[4][];twoD[0]=newint[5];twoD[1]=newint[5];twoD[2]=newint[5];twoD[3]=newint[5];对于大多数应用程序来说,我们不推荐使用不规则的多维数组,因为如果把握

不好运行过程中可能出错。但是,不规则多维数组在某些情况下的使用效率较高。例如,如果你需要一个很大的二维数组,而其元素仅仅被稀疏地占用(即其中一维的元素不是全被使用),这时使用不规则数组可能是较好的解决方案。对多维数组进行初始化也是可能的。初始化多维数组

只不过是把每一维的初始化列表用它自己的大括号括起来即可。下面的程序可产生一个矩阵,该矩阵的每个元素取值为数组下标行和列之积。//Initializeatwo-dimensionalarray.classMatrix{publicstaticvo

idmain(Stringargs[]){doublem[][]={{0*0,1*0,2*0,3*0},{0*1,1*1,2*1,3*1},{0*2,1*2,2*2,3*2},{0*3,1*3,2*3,3*3}};inti

,j;for(i=0;i<4;i++){for(j=0;j<4;j++)System.out.print(m[i][j]+"");System.out.println();}}}[Return]程序运行的结果如下:0.00.00.00.00.0

1.02.03.00.02.04.06.00.03.06.09.02.5.3声明数组的另一种格式[Return]在Java中,声明数组还有如下的第二种格式。type[]var-name;这里,方括号紧跟在类型标

识符type的后面,而不是跟在数组变量名的后面。例如,下面的两条声明语句是等价的:intal[]=newint[3];int[]a2=newint[3];以下的两个定义也是等价的。chartwod1[][]=newchar[3][4];char[][]twod2=newchar[3

][4];在Java中同时保留这两种数组声明格式主要是为了增强灵活性。2.5.4关于Java中的字符串在前面关于基本数据类型以及数组的讨论中,并没有提到字符串或字符串数据类型。这不是因为Java不支持这种类型,而是因为在Java中,字符

串类型应该叫做字符串对象(String),从其首字母的大小写即可看出。它不是一种简单的类型,也不是简单的字符数组(在C/C++中是)。字符串(String)在Java中被定义为一种内置对象。要完全了解它需要理解几个与对象

相关的特征,因此有关字符串(String)的详细讨论放到后面的内容中进行讲解。这里,我们只简单地介绍一下字符串的使用。字符串(String)类型通常被用来声明字符串变量。当然,你也可以定义字符串数组。一个被引号引起来的字符串

字面量可以被分配给字符串变量。一个字符串类型的变量可被分配给另一个字符串类型的变量。在进行输出时,也可以使用方法println()进行,就像输出其他类型变量的值一样。Stringstr="Thisisatest";System.out.println(str);这里,str是字符串(S

tring)类型的一个对象,它被分配给字符串“Thisisatest‖,该字符串通过println()语句输出。另外说明一下指针问题。如果读者是经验丰富的C/C++程序员,那么对这些语言中提供的指针肯定比较熟悉。然而,

Java语言不支持(或者说不允许指针)使用指针。之所以不允许指针存在,主要是因为这样做将可能使JavaApplet(小应用程序)突破Java运行环境和主机之间的防火墙限制,带来一系列的安全问题。虽然使用指针有很多好处,但只要在Java的执行环境范围

内,就不需要也不可能使用指针进行程序处理。[Return]2.6运算符2.6.1算术运算符2.6.2关系运算符2.6.3位运算符2.6.4逻辑运算符2.6.5其他运算符2.6.6运算符的优先级[Return]在

Java语言中,运算符可以划分为四大类:算术运算符、关系运算符、位运算符以及逻辑运算符。此外还定义了一些附加运算符,用于某些特殊情况的处理。本章将对这些运算符作具体介绍。2.6.1算术运算符[Retu

rn]算术运算符主要用于数学表达式中,其功能和用法与代数中的含义一样。Java中可以使用的算术运算符及其含义见教材P55页表2-6所示。其使用方法见P55~59页。2.6.2关系运算符注意:关系运算符所产生的结果是一个布尔值,即要么为true,要么为false。其常常用在if控制语句和各种循环语句

的表达式中。在Java中,任何类型包括整数、浮点数、字符以及布尔值等都可以用“==‖来比较是否相等,用“!=‖来测试是否不等。只有数字类型可使用排序运算符进行比较,即只有整数、浮点数和字符运算数可以比较哪个大哪个小。[Return]关系运算符用于比较两个同类型(或兼容类型)值之

间的大小关系,例如决定相等还是不相等,以及排列次序。Java语言中的关系运算符见教材P59页表2-7所示。2.6.3位运算符[Return]在Java中,使用位运算符可直接对整数类型的位进行操作,这些整数类型包括long、int、short、char以及byte。教材P6

0页表2-8中列出Java语言中所有的位运算符号。这些运算符的使用见教材P60~67页。注意:在Java语言中使用2的补码来存储负数,并且所有整数都是有符号的,这样应用位运算符可以容易地达到意想不到的结果。例如,不管你如何计算,Java都用

高位来代表负数。2.6.4逻辑运算符[Return]在Java中,逻辑运算符是指能参加布尔逻辑运算的运算符号。布尔逻辑运算符的运算数只能是布尔型,且其结果也是布尔类型。教材P67页表2-10中给出了可参加逻辑运算的运算符号。注意:布尔逻辑运算符“&‖、“|‖、

“^‖,对布尔值的运算和它们对整数位的运算一样。2.6.5其他运算符1.赋值运算符所谓赋值运算符就是一个等号“=‖,它在Java中的运算功能和在其他语言中的运算功能一样,即赋值。其通用格式如下。var=expres

sion;其中,变量var的类型必须与表达式expression的类型一致。注意:在Java语言中允许对一连串的变量进行赋值。例如,下面的代码:intx,y,z;x=y=z=100;//setx,y,andzto100其功能是

先将100赋值给变量z,然后再将变量z的值赋给变量y,最后将变量y的值赋给变量x。[Return]在Java语言中,选择运算符是“?:”。其通用格式如下:expression1?expression2:expression3其中,expression1

是一个布尔表达式。如果expression1为真,那么expression2被求值;否则,expression3被求值。整个?表达式的值就是被求值表达式(expression2或expression3)的值。express

ion2和expression3是除了void以外的任何类型的表达式,且它们的类型必须相同。例如:ratio=denom==0?0:num/denom;该语句的功能是:首先计算问号左边的表达式“denom==0‖。如果denom等于0,那么在问号和冒号之间的表达式被求值,并且

该值被作为整个?表达式的值;如果denom不等于零,那么在冒号之后的表达式被求值,并且该值被作为整个?表达式的值。然后将整个?表达式的值赋给变量ratio。在Java中,其他的运算符还有:内存分配运算符(new)、实例运算符(Instanceof)、下标

运算符([])、分量运算符(.即点运算符)、括号运算符(())、强制类型转换运算符(各种数据类型关键字)等,这里就不再一一介绍了。2.选择运算符2.6.6运算符的优先级[Return]优先级是指运算符之间的优先顺序。同级的运算符有相同的优先级,而不同级的运算符则优先级不同。这与四则运算中的“先乘除

、后加减”是一样的。教材P71页表2-12中给出了Java语言中运算符的优先顺序。其中,处在同一行上的运算符优先级相同,自上向下优先级逐渐降低。从该表中可以看出,第一行的几个运算符比较特殊:圆括号、方括号、圆点。圆括号被用来改变运算的优先级;方括号用来表示

数组的下标;点运算符用来将对象名和成员名连接起来。另外,使用圆括号可以提高括在其中的运算的优先级。例如,考虑下列表达式a>>b+3该表达式首先将3加到变量b,得到一个中间结果,然后将变量a右移该中间结果位。该表达式可用添加圆括

号的办法重写如下a>>(b+3)然而,如果你想先将a右移b位,得到一个中间结果,然后对该中间结果加3,你需要对表达式加如下的圆括号(a>>b)+3除了改变一个运算的正常优先级外,括号有时被用来帮助澄清表达式的含义。第3章控制语句在Java语言中,提供了很丰富的流程控制语言,包括选择控制

语句、循环控制语句、跳转控制语句。本章将对这些控制语句作详细介绍。3.1选择控制语句3.2循环控制语句3.3跳转控制语句[Return]3.1选择控制语句在Java中,选择控制语句有两种:if语句和switch语句。使用这些语句,编程人员可以在程序代码中根据具体状态给出相应的处理方式,以此控制程序

的执行过程。下面我们对这两种选择控制语句予以介绍。3.1.1if语句3.1.2switch语句[Return]3.1.1if语句格式1:if(condition)statement1;elsestatement2;格式2:if(condition)statement

;elseif(condition)statement;elseif(condition)statement;…elsestatement;[Return]1.if语句的格式2.if语句的功能格式1:如果条件为真,就执行if的对象(statement1);否则,执行else的对象(stateme

nt2)。在任何时候,两条语句都不可能同时执行。格式2:条件表达式从上到下被求值。一旦找到为真的条件,就执行与它关联的语句,该阶梯的其他部分就被忽略了。如果所有的条件都不为真,则执行最后的else语句。最后的else语句经常被作为默认条件,即如果所有其他条件测

试失败,就执行最后的else语句。如果没有最后的else语句,而且所有其他的条件都失败,那么程序就不做任何动作。3.1.2switch语句switch(expression){casevalue1://statementsequencebreak;casevalue2://statementseq

uencebreak;...casevalueN://statementsequencebreak;default://defaultstatementsequence}[Return]1.switch语句的格

式2.switch语句的功能将括号里“expression”的值同每种情况列出的值做比较,若相等就执行后面的语句;若不等,就执行default语句。注意:表达式expression必须为byte、short、int或char类型。每个case语句后的值va

lue必须是与表达式类型兼容的特定的一个常量(它必须为一个常量,而不是变量),重复的case值是不允许的。通常在每一种case情况后都应使用break语句。否则,第一个相等情况后面所有的语句都会被执行,这种情况被称为落空。sw

itch语句的详细应用见教材P76~79页的示例。3.2循环控制语句本章将介绍Java语言的基础知识,包括基本语言要素、基本数据类型、变量、数组、运算符等。扎实地掌握这些内容对后续学习是很有必要的。3.2.1for

循环语句3.2.2while循环语句3.2.3do-while循环语句[Return]3.2.1for循环语句for(initialization;condition;iteration){//body}[Return]1.for语句的格式

2.for语句的执行过程(1)当循环启动时,先执行其初始化部分即initialization。通常,这是设置循环控制变量值的一个表达式,作为控制循环的计数器。重要的是你要理解初始化表达式仅被执行一次。(2)计算条件condition

的值。条件condition必须是布尔表达式。它通常将循环控制变量与目标值相比较。如果这个表达式为真,则执行循环体body;如果为假,则循环终止。(3)执行循环体的反复部分即iteration,这部分通常是增加或减少循环控制变量的一个表达式。(4)接下来重复循环,首先计算条件cond

ition的值,然后执行循环体,接着执行反复表达式。这个过程不断重复直到控制表达式变为假。关于for语句的详细介绍见教材P80~83页。3.2.2while循环语句[Return]1.while语句的格式2.whi

le语句的执行过程while(condition){//bodyofloop}判断控制表达式condition的值,当其是真时,while语句重复执行一个语句或语句块。其中条件condition可以是任何布尔表达

式。只要条件表达式为真,循环体就被执行。当条件condition为假时,程序控制就传递到循环后面紧跟的语句行。若只有单个语句需要重复,大括号则是不必要的。分析教材P84~85页的示例。3.2.3do-while循环语句do{//bodyofloop}while(con

dition);[Return]1.do-while语句的格式2.do-while语句的功能先执行循环体,然后再计算条件表达式condition。如果表达式为真,则循环继续。否则,循环结束。对所有的Java循环都一

样,条件condition必须是一个布尔表达式。分析教材P85~86页的示例。3.3跳转控制语句Java语言支持3种类型的跳转控制语句:break、continue和return。使用这些语句,可把控制转移到程序的其他部分。本节将对它们作具体介绍

。3.3.1break语句3.3.2continue语句3.3.3return语句[Return]3.3.1break语句break;[Return]1.break语句的格式2.break语句的功能第一,在switch语句中,它被用来

终止一个语句序列;第二,在循环体中能被用来退出一个循环;第三,它能作为一种“变形”的goto语句来使用。详细使用情况见教材P87~91页。3.3.2continue语句continue;[Return]1.continue语句的格式2.continue语句的功能在while和d

owhile循环中,continue语句使控制直接转移给控制循环的条件表达式,然后继续循环过程。在for循环中,循环的反复表达式被求值,然后执行条件表达式,循环继续执行。对于这三种循环,任何中间的代码都将被绕过。详细分析教材P87~91页的示例。3.3.3retu

rn语句//Thisprogramdemonstratesreturnstatement.classReturn{publicstaticvoidmain(Stringargs[]){booleant=true;System.out.println("Beforethereturn.");

if(t)return;//returntocallerSystem.out.println("Thiswon'texecute.");}}[Return]最后一个跳转控制语句是return。return语句用来明确地从一个方法返回,也就是return语句使程序控制返回

到调用它的方法。因此,将它分类到跳转语句中。在一个方法的任何时间,return语句可被用来使正在执行的分支程序返回到调用它的方法。分析下面的例子。该程序的运行结果如下:Beforethereturn.第4章类及其方法类是面向对象程序

设计的基础,是Java的核心和本质所在。在Java中,所有的语言元素都必须被封装在类中。本章将对类的概念、类的方法以及相关问题进行介绍。4.1类的基础知识4.2类的方法4.3参数传递4.4访问控制[Return]4.1

类的基础知识4.1.1类的一般格式4.1.2一个简单的类4.1.3关于String类4.1.4对象的声明4.1.5关于Java中的数组4.1.6嵌套类与内部类[Return]事实上,我们从本书一开始就已经接触类了。当然,那些使用的都是

非常简单的类。读者将会看到,类的相关内容比目前为止所见到的要广泛得多。4.1.1类的一般格式classclassname{typeinstance-variable1;typeinstance-var

iable2;//...typeinstance-variableN;typemethodname1(parameter-list){//bodyofmethod}typemethodname2(parameter-list){//bodyofmeth

od}//...typemethodnameN(parameter-list){//bodyofmethod}}[Return]类是Java的核心和本质,它是Java语言建立的基础,因为类定义了对象的本性。既然类是面向对象程序设计Java语言的基础,因此想要在Ja

va程序中实现的每一概念,都必须封装在类之中。创建类通过关键字class进行。类定义的一般格式如下所示:4.1.2一个简单的类classBox{doublewidth;doubleheight;doubled

epth;}[Return]下面定义了一个名为Box的类,它声明了3个实例变量:width、height和depth。当前,Box类不包含任何方法。一个类定义一个新的数据类型。在本例中,新的数据类型名为Box,可以使用这个名字来声明Box类型的对象。注意:类声明只是创

建一个模板(或类型描述),它并不会创建一个实际的对象。因此,上述代码不会生成任何Box类型的对象实体。要真正创建一个Box对象,必须使用下面的语句。Boxmybox=newBox();//createaBoxobjectcalledmybox这个语句执行后,mybox就是B

ox的一个实例了。因此,它将具有“存储意义上的”真实性。关于该Box类的应用见教材P96~97页。4.1.3关于String类字符串可以通过多种方法构造,最简单的一种方法如下:StringmyString="thisisatest";一旦创建了一个字符串对象,就可以在任何允许字符串的地方使用

它。详细应用见教材P98~99页。[Return]String类可以说是Java类库中最常用的类。原因很明显,字符串在编程语言中最为常见。String类型的对象是不可改变的。也就是说,一旦创建了某个字符串对象,它的内容是不能够被改变的。4.1.

4对象的声明[Return]在Java语言中,要获得一个类的对象需要两步。第一步,声明该类类型的一个变量;第二步,声明要创建一个对象的实际物理拷贝,并把对于该对象的引用赋给该变量。这是通过使用new运算符实现的。new运算符为对象动态分配(即运行时分配)内存空间,并返回对它的一个引用。new

运算符动态地为一个对象分配地址,其通用格式如下class-var=newclassname();其中,class-var是所创建类类型的变量。classname是被实例化的类的名字。类的后面跟的圆括号指定了类的构造函数。例如,下面的语句声

明了一个Box类型的对象:Boxmybox=newBox();4.1.5关于Java中的数组[Return]在Java语言中,数组是作为类/对象来实现的。因此,你可能想要利用数组的一种特别的属性。具体地说,例如一个数组的大小(也就

是一个数组能保存的元素的数目),可以在它的length实例变量中找到。所有的数组都具有这个变量,并且它总是保存着数组的大小。关于数组的length属性详见教材P101~102页。4.1.6嵌套类与内部类[Return]所谓嵌套类(nestedcla

ss),就是在另一个类中所定义的类。嵌套类的范围由装入它的类的范围限制。这样,如果类B被定义在类A之内,那么B为A所知,然而不被A的外面所知。嵌套类可以访问嵌套它的类的成员,包括private成员。但是,包围类不能访问嵌套类的成员。嵌套类一般有两种类型:前面加static标识符的和不加stat

ic标识符的。一个static的嵌套类有static修饰符。因为它是static,所以只能通过对象来访问它的包围类的成员。也就是说,它不能直接引用它的包围类的成员。因为有这个限制,所以static嵌套类很少使用。嵌套类最重要的类型是内部类(i

nnerclass)。内部类是非static的嵌套类。它可以访问它的外部类的所有变量和方法,可以直接引用它们,就像外部类中的其他非static成员的功能一样。这样,一个内部类完全在它的包围类的范围之内。内部类的成员只有在内部类的范围之内是可知的,而且不能被外部类使用。见教材P104

~105页的示例。4.2类的方法4.2.1类方法的一般形式4.2.2给类添加一个方法4.2.3方法的返回值4.2.4添加带自变量的方法4.2.5构造函数4.2.6关于finalize()方法[Return]在本章的开始提到,类通常由两个要

素组成:实例变量和方法。方法是个很大的话题,因为Java给它们很大的功能和灵活性。本节将具体介绍类的方法的有关问题。4.2.1类方法的一般形式[Return]使用类的方法的一般形式为typename(parameter-list){//bodyof

method}其中,type指定了方法返回的数据类型,这可以是任何合法有效的类型,包括创建的类的类型。若该方法不返回任何值,则它的返回值type必须为void。方法名由name指定,除了被当前作用域中的其他项使用的标识符以外,方法名可以是任何合法的标识符。parameter-list(自变量列表)

是一系列类型和标识符对,使用逗号分开。自变量本质上是变量,它接收方法被调用时传递给方法的参数值。如果方法没有自变量,那么自变量列表就为空。对于不返回void类型的方法,使用下面格式的return语句,方法返回值到它的调用程序。returnvalue;其中,value是返回

的值。4.2.2给类添加一个方法[Return]当一个实例变量不是被该实例变量所在类的部分代码访问时,它必须通过该对象加点运算符来访问。但是当一个实例变量被定义该变量的类的代码访问时,该变量可以被直接引用。同样的规则也适用于方

法。见教材P106页示例。4.2.3方法的返回值[Return]在Java语言中,使用将方法的返回值赋给变量的办法来保存方法的返回值。见教材P107~108页示例。4.2.4添加带自变量的方法[Return]在大多数情况下,方法不需要自变量。自

变量对方法没有特殊要求。也就是说,带自变量的方法可以完成各种数据操作,它还可以用在很多有微妙差别的情况。详细使用情况见教材P108~110页。4.2.5构造函数[Return]构造函数(constructor)在对象创建时初始化,它与其类同名,语法与方法类似。

一旦定义了构造函数,在对象创建后,在new运算符完成前,构造函数立即自动调用。构造函数看起来有点奇怪,因为它没有任何返回值,即使是void型的值也不返回。这是因为一个类的构造函数的隐含的类型是其自身类的类型。构造函数的任务就是初始化一个对象的内部状态,以便使创建的实例变量能够完全初始化,可以被对象

马上使用。教材P110~112页分别介绍了构造函数的使用和带自变量的构造函数。4.2.6关于finalize()方法[Return]有时当撤消一个对象时,需要完成一些操作。例如,如果一个对象正在处理的是非Java资源,如文件句柄或window字符字体,这时在一个对象被撤消

以前要保证这些资源被释放。为处理这样的状况,Java提供了被称为收尾(finalization)的机制,使用该机制可以定义一些特殊的操作,这些操作在一个对象将要被垃圾回收程序释放时执行。要给一个类增加收尾(finalizer),只需要定义finalize

()方法即可。Java回收该类的一个对象时,就会调用这个方法。在finalize()方法中,你需要指定在一个对象被撤消前必须执行的操作。垃圾回收周期性地运行,检查对象是否不再被运行状态引用,或间接地通过

其他对象引用。就在对象被释放之前,Java运行系统调用该对象的finalize()方法。使用finalize()方法的一般格式如下:protectedvoidfinalize(){//finalizationcodehere}其中,关键字protect

ed是防止在该类之外定义的代码访问finalize()方法。4.3参数传递4.3.1将对象作为参数4.3.2参数的传递方式4.3.3使用命令行参数4.3.4返回对象4.3.5关于递归[Return]在本节中,将介绍与对象有关的参数传递问题,包括如何将对象作为参数、参数的

传递方式、命令行参数的使用、如何使用递归等。4.3.1将对象作为参数[Return]到目前为止,我们都使用简单类型作为方法的参数。同时,对象也可以参数的形式传递给方法。分析教材P114~115页示例即可掌握对象作为参数的使用情况。4.3.2参数的传递方式[Retur

n]一般说来,编程语言中给子程序传递参数的方法有两种:第一种是按值传递,这种方法将一个参数值复制成为子程序的正式参数。在这种方式下,对子程序参数的改变不影响调用它的参数。第二种传递参数的方法是引用调用。在这种方法中,参数的引用(而不是参数值)被传递给子程序参数。在

子程序中,该引用用来访问调用中指定的实际参数。这样,对子程序参数的改变将会影响调用子程序的参数。在Java语言中,当给方法传递一个简单类型时,它是按值传递的。因此,接收参数的子程序参数的改变不会影响到该方法之外。当给方法传递一个对象时,是通过引用传递的

。注意:当创建一个类类型的变量时,仅仅是创建了一个类的引用。因此,当将这个引用传递给一个方法时,接收它的参数将会指向该引用指向的对象。这有力地证明了对象是通过引用调用传递给方法的。该方法中对象的改变确实影响了作为参数的对象。分析教材P117页的示例。4.3.3使用命令行参数[Return]有时,

你可能想在运行程序过程中将信息传递到一个程序中,这可以通过将命令行参数传递给main()来实现。命令行参数是程序执行时在命令行中紧跟在程序名后的信息。在Java程序中,访问命令行参数是很容易的,因为我们知道,这些参数作为字符串存储在传递给main()的String数组中。

分析教材P118页的示例。4.3.4返回对象[Return]在Java中,使用方法能够返回任何类型的数据,包括所创建的类的类型。请看教材P118~119页的示例。在该示例中,每次调用incrByTen(),就产生一个新的对象,同

时将它的引用返回到调用子程序。4.3.5关于递归[Return]递归(recursion)就是依照自身定义事物的过程,它允许调用自身定义的方法。Java支持递归机制,调用自身的方法被称为是递归的。请看教材P119页数字阶乘的例子。这是

一个典型的递归程序的例子。递归的主要优点在于:某些类型的算法采用递归比采用迭代算法要更加清晰和简单。例如快速排序算法按照迭代方法是很难实现的。还有其他一些问题,特别是人工智能问题,就依赖于递归提供解决方案。而且,有些人认为递归要比迭代简单。当编写递归方法时,必须使用if条件语句在递归

调用不执行时来强制方法返回。如果不这么做,一旦你调用方法,它将永远不会返回。这类错误在使用递归时是很常见的。尽量多地使用println()语句,使你可以了解程序的进程;如果发现错误,立即中止程序运行。4.4访问控制4.4.1关于Java中的访问控制4.4.2使用this关键字4.4.3关于sta

tic关键字4.4.4使用final关键字[Return]本节讨论与Java中的访问控制相关的一些问题。另外,还将介绍this、static、final等关键字。4.4.1关于Java中的访问控制[Return]我们知道,封装将数据和

处理数据的代码连接起来。同时,封装也引起了另外一个必须讨论的问题:访问控制(accesscontrol)。通过封装,可以控制程序的哪一部分可以访问类的成员。通过控制访问,可以阻止对象的滥用。一个成员如何被访问,取决于它的声明的访问指示符(accessspecifi

er)。Java提供一套丰富的访问指示符。这些访问指示符有public(公共的,全局的)、private(私有的,局部的)和protected(受保护的)。Java也定义了一个默认访问级别,指示符protected仅用于

继承情况中。当一个类成员被public指示符修饰时,该成员可以被你的程序中的任何其他代码访问。当一个类成员被指定为private时,该成员只能被它的类中的其他成员访问。4.4.2使用this关键字[Return]有时候一个方法需要引用

其所属对象自身。为此,Java定义了this这个关键字。使用this可以引用当前对象的所有方法。也就是,this调用的总是该方法对象的一个引用。可以在当前类的类型所允许对象的任意位置将this作为一个引用来使用。下面是一个版本的Box()程序

的例子,它用width、height、depth作为自变量的名字,然后使用this关键字来存取同名的实例变量。//Usethistoresolvename-spacecollisions.Box(dou

blewidth,doubleheight,doubledepth){this.width=width;this.height=height;this.depth=depth;}4.4.3关于static关键字[Return]有时

我们希望定义一个类成员,使它的使用完全独立于该类的任何对象。通常情况下,类成员必须通过对象访问,但是可以创建这样一个成员,它能够被它自己使用,而不必引用特定的实例。在成员的声明前面加上关键字static(静态的)

就能创建这样的成员。如果一个成员被声明为static,它就能够在它的类的任何对象创建之前被访问,而不必引用任何对象。可以将方法和变量都声明为static。static成员的最常见的例子是main()。因为在程序开始执行时必须调用main(),所以它被声明为static。声明为st

atic的变量实质上就是全局变量。当声明一个对象时,并不产生static变量的拷贝,而是该类所有的实例变量共用同一个static变量。在声明static的方法时,有以下几个方面的限制:第一,仅能调用其

他的static方法;第二,只能访问static数据;第三,不能以任何方式引用this或super。如果需要通过计算来初始化static变量,可以声明一个static块,static块仅在该类被加载时执行一次。4.4.4使用final关键字[Return]一个变量可以声明

为final,这样做的目的是阻止它的内容被修改。这意味着在声明final变量的时候,必须对其初始化。在这种用法上,final类似于C/C++中的const。例如finalintFILE_NEW=100;finalintFILE_OPEN=2

00;finalintFILE_SAVE=300;finalintFILE_SAVEAS=400;finalintFILE_QUIT=500;这样,在程序的随后部分就可以直接使用FILE_OPEN等,就

好像它们是常数一样,不必担心它们的值会被改变。第5章继承与多态面向对象程序设计的三大原则是封装性、继承性、多态性。对于封装性,我们实际上已通过上一章的叙述作了说明。在本章里,将主要介绍Java语言在继承和多

态(重点是重载)方面的特性。5.1继承机制5.2创建多级层次类5.3多态与重载5.4方法的动态调度5.5使用抽象类[Return]5.1继承机制继承是面向对象编程技术的一块基石,因为它允许创建分等级层次的类。运用继承能够创建一

个通用类,该类还可以被更具体的类继承,每个具体的类都增加一些自己特有的性质。5.1.1关于继承5.1.2使用super关键字5.1.3使用final关键字[Return]5.1.1关于继承在Java术语中,被继承的类叫超类(sup

erclass),继承超类的类叫子类(subclass)。因此子类是超类的一个专门用途的版本,它继承了超类定义的所有实例变量和方法,并且为它自己增添了独特的元素。继承一个类,只需要用extends关键字把一个类的定义合并到另一个中就可以了。在Java中,只能给所创建的

每个子类定义一个超类。Java语言不支持多超类的继承。你可按照规定创建一个继承的层次。该层次中,一个子类成为另一个子类的超类。然而,没有类可以成为它自己的超类。关于“继承”的详细情况见教材P128~132页。[Return]5.1.2使用sup

er关键字任何时候一个子类需要引用它直接的超类,可以用关键字super来实现。super有两种通用形式。第一种调用超类的构造函数;第二种用来访问被子类的成员隐藏的超类成员。下面分别介绍每一种用法。1.使用super调用超类构造函数子类可以调用超类中定义的构造函数方法,用sup

er的下面形式:super(parameter-list);这里,parameter-list定义了超类中构造函数所用到的所有参数。super()必须是在子类构造函数中的第一个执行语句。考察并分析教材P134~135页的示例

。Super的第二种形式,除了总是引用它所在子类的超类,它的行为有点像this。这种用法有下面的通用形式:super.member这里,member既可以是一个方法也可以是一个实例变量。Super的第二种形式多数是用于超类成员名被子类中同样的成员名隐藏的情况。考察并分析教材P136~

137页的示例。2.Super关键字的另外一种用法3.关于Object类这里大致介绍一下Object类。在Java中,定义有一种特殊的类Object,,其他所有的类都是Object的子类。也就是说,Object是所有其他类的超类。这意味着

一个Object类型的引用变量可以引用其他任何一个类的对象。同样,因为数组像类一样执行,Object类型变量可以引用任何数组。Object定义了下面的方法,意味着它们可以被任何对象调用,如教材P137页表5-1所示。其中,getClass()、notify

()、notifyAll()和wait()方法被定义成final。你可以重载除这些方法以外的其他方法。注意这两个方法:equals()和toString()。equals()方法比较两个对象的内容。如果对象是相等的,它返回true,否则返回false。toStri

ng()方法返回一个包含调用它的对象描述的字符串。而且,该方法在对象使用println()输出时自动调用。很多类都重载该方法,这样做使它们生成它们创建对象类型的一个特殊描述。[Return]5.1.3使用final关键字Final关键字有三个用途:首先,它可以用来

创建一个已命名的常量,这个用法在前一章中已介绍过。Final的其他两个用法是用于继承中,下面分别予以介绍。1.使用final阻止继承有时候,你可能希望防止一个类被继承。做到这点只需在类声明前加final。声

明一个final类含蓄的宣告了它的所有方法也都是final。你可能会想到,声明一个既是abstract又是final的类是不合法的,因为抽象类本身是不完整的,它依靠它的子类提供完整的实现。下面是一个final类的例子:fina

lclassA{//...}classBextendsA{//ERROR!Can'tsubclassA//...}正如注释所说明的,B继承A是不合法的,因为A声明成final。2.使用final阻止重载[Return]尽管方法重载(本章后面将会介绍)是Ja

va的一个最强大的特性,有些时候也可能希望防止它的发生。不接受方法被重载,在方法前定义final修饰符。声明成final的方法不能被重载。下面的程序段阐述了final的用法。classA{finalvoidmeth(){System.out.println("Thisisafinalm

ethod.");}}classBextendsA{voidmeth(){//ERROR!Can'toverride.System.out.println("Illegal!");}}其中,由于meth()被声明成final,它不能被B重载,

如果你试图这样做,将会生成一个编译时错误。5.2创建多级层次类到目前为止,我们已经用到了只含有一个超类和一个子类的简单类层次结构。同时,我们也可以如自己所愿建立包含任意多层继承的类层次结构。5.2.1多级层次的类5.2.2何时调用构造函数[Return]5.2.1多级层次的类前面

提到过,用一个子类作为另一个类的超类是完全可行的。例如,给定三个类A、B和C。C是B的一个子类,而B又是A的一个子类。当这种类似的情形发生时,每个子类继承它的所有超类的属性。在此情况下,C继承B和A的所有方面。为理解多

级层次的用途,考察教材P139~141页的示例程序。在该程序中,子类BoxWeight用作超类来创建一名为Shipment的子类。Shipment继承了BoxWeight和Box的所有特征,并且增加了一个名为cost的成员,该成员记录了运送这样一个小包的费用。由该示例可知,Shipment可

以利用原先定义好的Box和BoxWeight类,仅为自己增加特殊用途的其他信息。这体现了继承的部分价值:它允许代码重用。同时该例子还说明了另外一点:super()总是引用子类最接近的超类的构造函数。Shipment中super()调用了BoxWeight的构造函数。

BoxWeight中的super()调用了Box中的构造函数。在类层次结构中,如果超类构造函数需要参数,那么不论子类自己需不需要参数,都必须向上传递这些参数。[Return]5.2.2何时调用构造函数类层次结构创建以后,组成层次结构的类的构造函数以怎样的顺序被调用?举个例子来说,给定一

个名为B的子类和超类A,是A的构造函数在B的构造函数之前调用,还是情况相反?回答是在类层次结构中,构造函数以派生的次序调用,从超类到子类。而且,尽管super()必须是子类构造函数的第一个执行语句,无论你用到了super()没有

,这个次序不变。如果super()没有被用到,每个超类的默认的或无参数的构造函数将执行。教材P142页的例子阐述了何时执行构造函数。从该例子可以看出,构造函数以派生的顺序被调用。构造函数以派生的顺序执行是很有意义的。因为超类不知道任何子类的信息,任何它需要完成的初始化是与子类的

初始化分离的,而且它可能是完成子类初始化的先决条件。因此,它必须最先执行。[Return]5.3多态与重载多态是面向对象程序设计的基本特性之一,而重载则是多态的一种体现形式。本节将对这方面的内容进行介绍。5.3.1关于多态5.3.

2方法的重载5.3.3构造函数重载[Return]5.3.1关于多态多态即程序中同名的不同方法共存的情况,常见的两种多态方式为:①子类对父类方法的覆盖;②利用重载在同一个类中定义多个同名的不同方法。重载是类对自

身已有的同名方法的重新定义,常见的如构造函数的重载。构造函数可以从超类那里继承,也可以互相重载。类的若干个构造函数可以相互调用,一个构造函数调用另一构造函数时,可以使用关键字this。同时,这个调用语句应该是整个构造函数的第一个可

执行语句。[Return]5.3.2方法的重载在Java中,同一个类中的两个或两个以上的方法可以有同一个名字,只要它们的参数声明不同即可。在这种情况下,该方法就被称为重载(overloaded),这个过程称为方法重载。方法重载是Java实现多态性的一种方式。如果

读者以前从来没有使用过一种允许方法重载的语言,这个概念最初可能有点奇怪。但后面将看到,方法重载是Java最激动人心和最有用的特性之一。当一个重载方法被调用时,Java用参数的类型和(或)数量来表明实际调用的重载方法的版本。因此,每个重载方法的参数

的类型和(或)数量必须是不同的。虽然每个重载方法可以有不同的返回类型,但返回类型并不足以区分所使用的是哪个方法。当Java调用一个重载方法时,参数与调用参数匹配的方法被执行。分析教材P143~144页的例子。[Return]5.3.3构造函数重载除了重载正常

的方法外,构造函数也能够重载。实际上,对于大多数创建的类,重载构造函数是很常见的,并不是什么例外。为了加深理解,让我们回顾一下上一章中举过的Box类例子。教材P146页给出了最新版本的Box类的例子。在这个例子中,Box()构造函数

需要三个自变量,这意味着定义的所有Box对象必须给Box()构造函数传递三个参数。例如,下面的语句在当前情况下是无效的。Boxob=newBox();由于Box()要求有三个参数,因此如果不带参数地调用它则是一个错误。这会引起一些重要的问

题。如果你只想要一个盒子而不在乎(或知道)它的原始的尺寸该怎么办?或者,如果想用仅仅一个值来初始化一个立方体,而该值可以被用作它的所有的三个尺寸又该怎么办?如果Box类是像现在这样写的,与此类似的其他问题都没有办法解决,因为你只能带三个参数而没有别的选择权。要解决这些问题,实际上是相当容易的

:重载Box构造函数,使它能处理刚才描述的情况。分析教材P146~147页Box的一个改进版本程序,它就是运用对Box构造函数的重载来解决这些问题的。[Return]5.4方法的动态调度前面说明了方法重载机制,

但并没有显示它们的作用。实际上,如果方法重载只是一个名字空间的约定,那就没有实用价值,但实际情况并非如此。5.4.1关于多态方法调用5.4.2为什么要重载方法5.4.3运用方法重载[Return]5.4.1关于多态

方法调用方法重载构成Java的一个最强大概念的基础:动态方法调度(dynamicmethoddispatch)。动态方法调度是一种在运行时而不是编译时调用重载方法的机制。动态方法调度对Java来说是很重要的,因为这是Java实现运行时多态性的基础。下面从一个重要的原则开始谈起:

超类的引用变量可以引用子类对象。Java用这一事实来解决在运行期间对重载方法的调用。过程如下:当一个重载方法通过超类引用被调用,Java根据当前被引用对象的类型来决定执行哪个版本的方法。如果引用的对象类型不同,就会调用一个重载方法的不同版本。换句话说,是被引用对象的类型(而不是引用变量的类型)决定

执行哪个版本的重载方法。因此,如果超类包含一个被子类重载的方法,那么当通过超类引用变量引用不同对象类型时,就会执行该方法的不同版本。分析教材P148页的例子。[Return]5.4.2为什么要重载方法前

面提到过,重载方法允许Java支持运行时多态性。多态性是面向对象编程的本质,原因如下:它允许通用类指定方法,这些方法对该类的所有派生类都是公用的。同时该方法允许子类定义这些方法中的某些或全部的特殊实现。重载方法是Java实现它

的多态性—―一个接口,多个方法”的另一种方式。成功应用多态的关键部分是理解超类和子类形成了一个从简单到复杂的类层次。正确应用多态,超类提供子类可以直接运用的所有元素。多态也定义了这些派生类必须自己实现的方法。这允许子类在加强一致接口的同时,灵活地定义它们自己的方法。这样,通过

继承和重载方法的联合,超类可以定义供它的所有子类使用的方法的通用形式。动态的运行时多态是面向对象设计代码重用的一个最强大的机制。现有代码库在维持抽象接口同时不重新编译的情况下调用新类实例的能力是一个极其强大的工具。[Return]5.4.3运用

方法重载教材P149~150页的例子就是一个运用方法重载的实际的例子。在该程序中,创建了一个名为Figure的超类,存储不同二维对象的大小。还定义了一个方法area(),该方法计算对象的面积。程序从Figure派生了两个子类。第一

个是Rectangle,第二个是Triangle。每个子类重载area()方法,它们分别返回一个矩形和一个三角形的面积。通过继承和运行时多态的双重机制,可以定义一个被很多不同却有关的对象类型运用的一致的接口。这种情况下,如果一个对象是从Figure派生,那么它的面积可以由调用are

a()来获得。无论用到哪种图形的类型,该操作的接口是相同的。[Return]5.5使用抽象类要声明一个抽象方法,其通用形式如下abstracttypename(parameter-list);任何含有一个或多个抽象方法的类都必须声明成抽象类。声明一个抽象类,只需在类声明开始时在

关键字class前使用关键字abstract。抽象类没有对象。也就是说,一个抽象类不能通过new操作符直接实例化。这样的对象是无用的,因为抽象类是不完全定义的。而且,你不能定义抽象构造函数或抽象静态方法

。所有抽象类的子类都必须执行超类中的所有抽象方法或者是它自己也声明成abstract。分析教材P151页的示例。[Return]第6章包和接口包和接口是Java语言最具革新性的两个特点所在。包是Java类的容器,而接口则是类的方法。本章将对这两个方面的内容作具体介绍。6.

1Java中的包6.2接口[Return]6.1Java中的包包(package)是类的容器,用来保存划分的类名空间。包以分层方式保存并被明确地引入新的类定义。本节将对Java中包的相关问题进行讨论。6.1.1包的创建6.1.2关于类路径

6.1.3一个简单的例子6.1.4访问保护6.1.5包的导入[Return]6.1.1包的创建Java提供了把类名空间划分为更多易管理的块的机制,这种机制就是包。包既是命名机制也是可见度控制机制。我们可以在包内定义类,而且在包外的代码不能访问该类。这使得各个

类之间有隐私,但不被外界所知。创建一个包是很简单的:只要包含一个package命令作为一个Java源文件的第一句就可以了。该文件中定义的任何类将属于指定的包。package语句定义了一个存储类的名字空间。如果省略package语句,类名被输入一个默认的没有名称的包(这是为什么在以前

不用担心包的问题的原因)。尽管默认包对于短例程序很好用,但对于实际的应用程序是不适当的。多数情况,需要为自己的代码定义一个包。下面是package声明的一般形式:packagepkg;这里,pkg为包名。Java用文件系统目录来存储包。记住这种规则是很重要的,目录名称必须和包名严格匹配。多个文

件可以包含相同package声明。package声明仅仅指定了文件中所定义的类属于哪一个包。它不拒绝其他文件的其他方法成为相同包的一部分。多数实际的包伸展到很多文件。我们可以创建包层次。为做到这点,只要将每个包名与它的上层包名用点号“.‖

分隔开就可以了。一个多级包的声明的通用形式如下:packagepkg1[.pkg2[.pkg3]];包层次一定要在Java开发系统的文件系统中有所反映。[Return]6.1.2关于类路径假设你在一个test包中创建了一个名为PackTest的类。由于你的目

录结构必须与包相匹配,创建一个名为test的目录并把PackTest.java装入该目录。然后,使test成为当前目录并编译PackTest.java。这导致PackTest.class被存放在test目录下。当试图运行PackTest时,java解释器报告一个与“不能发现PackTe

st类”相似的错误消息。这是因为该类现在被保存在test包中,不再能简单用PackTest来引用。必须通过列举包层次来引用该类。引用包层次时用点号将包名隔开。该类现在必须叫做test.PackTest。然而,如果你试图用test.PackTest,将仍然收到一个与“不能发现test

/PackTest类”相似的出错消息。仍然收到错误消息的原因隐藏在类路径变量中。记住,类路径设置顶层类层次。问题在于在当前工作目录下不存在test子目录,因为你此时是工作在test目录。在这个问题上你有两个选择:改变目

录到上一级然后用javatest.PackTest,或者在类路径环境变量增加开发类层次结构的顶层。然后就可以使用javatest.PackTest了。例如,如果源代码在目录C:\myjava下,那么应设置类路径为:.;C:\myjava;C:\java\c

lasses[Return]6.1.3一个简单的例子详细分析并运行教材P156~157页使用包的例子。[Return]6.1.4访问保护在前面的章节中已经学习了Java的访问控制机制和访问说明符。例如,我们已经知道一个类的private成员

仅可以被该类的其他成员访问。包增加了访问控制的另一个维度。正如读者所看到的,Java提供很多级别的保护以使在类、子类和包中有完善的访问控制。类和包都是封装和容纳名称空间和变量及方法范围的方法。包就像盛装类和下级包的容器。类就

像是数据和代码的容器。类是Java的最小的抽象单元。因为类和包的相互影响,Java将类成员的可见度分为四个种类:l相同包中的子类l相同包中的非子类l不同包中的子类l既不在相同包又不在相同子类中的类三个访问控制符,private、public和

protected,提供了多种方法来产生这些种类所需访问的多个级别,教材P158页表6-1中总结了它们之间的相互作用。1.关于访问保护分析教材P158~160页的例子,该例显示了访问修饰符的所有组合,在该例中有两个包和五个类。记住,这两个不同包中的类需要被存储在以它们的包p1、p2

命名的目录下。第一个包中定义了三个类:Protection,Derived,和SamePackage。第一个类以合法的保护模式定义了四个int变量。变量n声明成默认受保护型。n_pri是private型,n_pr

o是protected,n_pub是public的。该例中每一个后来的类试图访问该类一个实例中的变量。根据访问权限不编译的行用单行注释//。在每个这样的行之前都是列举该级保护将允许访问的地点的注释。第二个类,Derived是同样包p1中Protection类的子类,这允许De

rived访问Protection中的除n_pri以外的所有变量,因为它是private。第三个类,SamePackage,不是Protection的子类,但是是在相同的包中,也可以访问除n_pri以外的所

有变量。[Return]2.一个访问的例子6.1.5包的导入包的存在是划分不同类的好的机制,了解为什么所有Java内部的类都存在包中是很简单的。在未命名的默认包中,不存在核心Java类;所有的标准类都存储在相同的包中

。既然包中的类必须包含它们的包名才能完全有效,为每个想用的包写一个长的逗点分离的包路径名是枯燥的。因为这点,Java包含了import语句来引入特定的类甚至是整个包。一旦被引入,类可以被直呼其名的引用。impor

t语句对于程序员是很方便的,而且在技术上并不需要编写完整的Java程序。如果你在程序中将要引用若干个类,那么用import语句将会节省很多打字时间。在Java源程序文件中,import语句紧接着package语句(如果packag

e语句存在),它存在于任何类定义之前。下面是import声明的一般形式importpkg1[.pkg2].(classname|*);这里,pkg1是顶层包名,pkg2是在外部包中的用逗点(.)隔离的下

级包名。除非是文件系统的限制,不存在对于包层次深度的实际限制。最后,要么指定一个清楚的类名,要么指定一个星号(*),该星号表明Java编译器应该引入整个包。分析教材P161~162页的例子。[Return]6.2接口在前面的章节中,我们已知道如何在类中定义接口的方法。通过使用关键字inte

rface,Java允许编程人员充分抽象它实现的接口。接口自己不定义任何实现,尽管它与抽象类相似。6.2.1关于接口6.2.2接口的定义6.2.3接口的实现6.2.4接口的使用6.2.5接口中的变量6.2.6接口的扩展[Return]6.2.1关

于接口接口是用来实现类间多重继承的功能的,它将完成特定功能的若干属性组织成相对独立的属性集合,该属性集合就是接口。例如,ActionListener接口的功能与actionPerformed()方法相关。需要指出的是,接口定义的仅仅

是实现某一特定功能的一组功能的对外接口和规范,而并没有真正实现这个功能。真正实现在继承这个接口的各个类中完成,因而通常把接口功能的继承称为“实现”。如果一个类要实现接口时,需要注意以下几个方面的内容:(1)在类的声明部分,用implem

ents关键字声明该类将要实现哪些接口。(2)如果实现某接口的类不是abstract的抽象类,则在类的定义部分必须实现指定接口的所有抽象方法。(3)如果实现某接口的类是abstract的抽象类,则它可以不实现该接口所有的方法。但是对于这个抽象类任何一个非抽象的子类面言,它们父类所实现的接口中

的所有抽象方法都必须有实在的方法体。(4)一个类在实现某接口的抽象方法时,必须使用完全相同的方法头,如果所实现的方法与抽象方法有相同的方法名和不同的参数列表,则只是在重载一个新的方法,而不是实现已有的抽象方法。(5)接口的抽象方法的访问限制符都已指定

为public,所以类在实现方法时,必须显式地使用public修饰符,否则将被系统警告为缩小了接口中定义的方法的访问控制范围。[Return]6.2.2接口的定义用关键字interface,你可以从类的实现中抽象一个类的接口。接口在语句

构成上与类相似,但是它们缺少实例变量,而且它们定义的方法是不含方法体的。实际上,这意味着你可以定义不用假设它们怎样实现的接口。一旦接口被定义,任何类成员可以实现一个接口。而且,一个类可以实现多个接口。要实现一个接口,接口定义的类必须

创建完整的一套方法。然而,每个类都可以自由地决定它们自己实现的细节。通过提供interface关键字,Java允许你充分利用多态性的“一个接口,多个方法”。接口是为支持运行时动态方法解决而设计的。接口的定义很像类的定义。下面是一个接口的通用形式。accessinterfacename{retu

rn-typemethod-name1(parameter-list);return-typemethod-name2(parameter-list);typefinal-varname1=value;typefinal-varname2=value;//...return-typemetho

d-nameN(parameter-list);typefinal-varnameN=value;}[Return]这里,access要么是public,要么就没有用修饰符。当没有访问修饰符时,则是默认访问范围,而接口是包中定义的唯一的可以用于其他成

员的东西。当它声明为public时,接口可以被任何代码使用。name是接口名,它可以是任何合法的标识符。注意定义的方法没有方法体。它们以参数列表后面的分号作为结束。它们本质上是抽象方法;在接口中指定的方法没有默认的实现。每个包含接口的类必须实现所有的方法。接口声明中可以声明变量。它们一般是f

inal和static型的,意思是它们的值不能通过实现类而改变。它们还必须以常量值初始化。如果接口本身定义成public,所有方法和变量都是public的。下面是一个接口定义的例子。它声明了一个简单的接口,该接口包含一个带单个整型参

数的callback()方法。interfaceCallback{voidcallback(intparam);}6.2.3接口的实现一旦接口被定义,一个或多个类可以实现该接口。为实现一个接口,在类定义中包括implements子句,然后创建接口定义的方法。一个包括im

plements子句的类的一般形式如下:accessclassclassname[extendssuperclass][implementsinterface[,interface...]]{//class-body}这里,access要么是public的

,要么是没有修饰符的。如果一个类实现多个接口,这些接口被逗号分隔。如果一个类实现两个声明了同样方法的接口,那么相同的方法将被其中任一个接口客户使用。实现接口的方法必须声明成public。而且,实现方法的类型必须严格与接口定义中指定的类型相匹配。我们可以

把变量定义成使用接口的对象引用而不是类的类型。任何实现了所声明接口的类的实例都可以被这样的一个变量引用。当通过这些引用调用方法时,在实际引用接口的实例的基础上,方法被正确调用。这是接口的最显著特性之一。被执行的方法在运行时动态操作,

允许在调用方法代码后创建类。调用代码在完全不知“调用者”的情况下可通过接口来调度。这个过程和前面章节中描述的用超类引用来访问子类对象很相似。[Return]1.通过接口引用实现接口2.局部实现如果一个类包含一个接口但是不完全实现接口定义的方法,那么该类必须定义成abstract型

。例如:abstractclassIncompleteimplementsCallback{inta,b;voidshow(){System.out.println(a+""+b);}//...}6.2.4接口的使用为理解接口的功能,让我们看一个更实际的例子。我们曾开发过一个名为Stack的

类,该类实现了一个简单的固定大小的堆栈。然而,有很多方法可以实现堆栈。例如,堆栈的大小可以固定也可以不固定。堆栈还可以保存在数组、链表和二进制树中等。无论堆栈怎样实现,堆栈的接口保持不变。也就是说,push()和pop()方法定义了独立实现细节的堆栈的接口。因为堆栈的接口与它的实现是分

离的,很容易定义堆栈接口,而不用管每个定义实现细节。让我们看下面的例子。下面定义了一个整数堆栈接口,把它保存在一个IntStack.java文件中。该接口将被两个堆栈实现使用。//Defineanintegerstackinterface.interfaceIntStac

k{voidpush(intitem);//storeanitemintpop();//retrieveanitem}详细情况见教材P168~169页。[Return]6.2.5接口中的变量你可以使用接口

来引入多个类的共享常量,这样做只需要简单地声明包含变量初始化想要的值的接口就可以了。如果一个类中包含那个接口(就是说当你实现了接口时),所有的这些变量名都将作为常量看待。这与在C/C++中用头文件来创建大量

的#defined常量或const声明相似。如果接口不包含方法,那么任何包含这样接口的类实际并不实现什么。这就像类在类名字空间引入这些常量作final变量。教材P170~171页的例子运用了这种技术来实现一个自动的“作决策者”,下面我们来分析该例子。[Return]6.2.

6接口的扩展接口可以通过运用关键字extends被其他接口继承。语法与继承类是一样的。当一个类实现一个继承了另一个接口的接口时,它必须实现接口继承链表中定义的所有方法。教材P171~172页给出了一个例子。分析该例子。[Return]第7

章异常处理异常是指程序在运行过程中的不正常情况。在不支持异常处理的计算机语言中,错误必须被手工检查和处理,这种方法既笨拙又麻烦。Java的异常处理机制则避免了这些问题,而且在处理过程中,把对运行时错误的处理以面向对象的

方式解决。7.1异常处理基础7.2try和catch语句7.3异常抛出7.4finally语句7.5自定义异常类[Return]7.1异常处理基础本节介绍Java中有关异常处理的基础知识,包括异常的概念、异常的类

型、Java的内置异常等方面的内容。7.1.1关于异常处理7.1.2异常的类型7.1.3Java的内置异常7.1.4未被捕获的异常[Return]7.1.1关于异常处理异常指的是程序运行时出现的非正常情况,也称为“例外”。在用

传统的语言编程时,程序员只能通过函数的返回值来发出错误信息,这易于导致很多错误。Java对异常的处理是面向对象的。一个Java的Exception是一个描述异常情况的对象。当出现异常情况时,一个Exception对象就

产生了,并放到产生这个“异常”的成员函数里。Java异常(Exception)用于描述在代码段中发生的异常。当异常情况发生,一个代表该异常的对象被创建并在导致该错误的方法中被引发(throw)。该方法可以选择自己处理

异常或传递该异常。两种情况下,该异常被捕获(caught)并处理。异常可能是由Java运行时系统产生,或者是由手工代码产生。被Java引发的异常与违反语言规范或超出Java执行环境限制的基本错误有关。手工编码产生

的异常基本上用于报告方法调用程序的出错状况。Java中的异常处理通过5个关键字进行控制:try、catch、throw、throws和finally。下面是异常处理块的一般形式:[Return]try{//blockofcodetom

onitorforerrors}catch(ExceptionType1exOb){//exceptionhandlerforExceptionType1}catch(ExceptionType2exOb){//exceptionhand

lerforExceptionType2}//...finally{//blockofcodetobeexecutedbeforetryblockends}其中,ExceptionType是发生异常的类型。7.1.2异常的类型在“异常”类层次的最上层有一个单独的类叫做Throwable。这个类

用来表示所有的异常情况。每个异常类型都是Throwable的子类。Throwable有两个直接的子类。一类是Exception,是用户程序能够捕捉到的异常情况。我们可通过产生它的子类来创建自己的异常。另一类是E

rror,它定义了那些通常无法捕捉到的“异常”。一般说来要谨慎使用Error子类,因为它们通常会导致灾难性的失败。在Exception中有一个子类RuntimeException,它是程序运行时自动地对某些错误作出反应而产生的。另一类分支由Err

or作为顶层,Error定义了在通常环境下不希望被程序捕获的异常。Error类型的异常用于Java运行时系统来显示与运行时系统本身有关的错误。堆栈溢出是这种错误的一例。本章将不讨论关于Error类型的异常处理,因为它们通常是灾

难性的致命错误,不是你的程序可以随意控制的。教材P175页图7-1显示了异常和错误的继承层次关系。[Return]7.1.3Java的内置异常在标准包java.lang中,Java定义了若干个异常类。前面的例子曾用到其

中一些。这些异常一般是标准类RuntimeException的子类。因为java.lang实际上被所有的Java程序引入,多数从RuntimeException派生的异常都自动可用。而且,它们不需要被包含在任何方法的throws列

表中。Java语言中,这被叫做未经检查的异常(uncheckedexceptions)。因为编译器不检查它是否处理或引发了这些异常。java.lang中定义的未经检查的异常见教材P175页表7-1中所列。教材P176页表7-2中列出了由java.lang定义的必须在方法的throws列表

中包括的异常,这些方法能产生其中的某个异常但是不能自己处理它,叫作受检查异常(checkedexceptions)。Java定义了几种与不同类库相关的异常类型。[Return]7.1.4未被捕获的异常在学习如何处理程序中的异常之前,让我们看一下如果不处理它们,将会有什么样

的情况发生。下面的小程序包括一个故意导致被零除错误的表达式classExc0{publicstaticvoidmain(Stringargs[]){intd=0;inta=42/d;}}当Java运行时系统检查到被零除的情况,它构造一个新的异常对象然后引发该异常。这导致Exc0的执行停止,因为一

旦一个异常被引发,它必须被一个异常处理程序捕获并且被立即处理。该例中,我们没有提供任何我们自己的异常处理程序,所以异常被Java运行时系统的默认处理程序捕获。任何不是被你程序捕获的异常最终都会被该默认

处理程序处理。默认处理程序显示一个描述异常的字符串,打印异常发生处的堆栈轨迹并且终止程序。[Return]下面是由标准javaJDK运行时解释器执行该程序所产生的输出:java.lang.ArithmeticException:/byzeroatExc0.main(E

xc0.java:5)其中,需要注意类名Exc0、方法名main、文件名Exc0.java和行数5是怎样被包括在一个简单的堆栈使用轨迹中的。还有,注意引发的异常类型是Exception的一个名为Arithmetic

Exception的子类,该子类更明确地描述了是何种类型的错误方法。本章后面将讨论Java提供的多个内置的与可能产生的不同种类运行时错误相匹配的异常类型。堆栈轨迹将显示导致错误产生的方法调用序列。7.2try和catch语句尽管由Java运行时系统提供的默认异常处理程序对于调试是很有用的,

但通常是希望自己处理异常。这样做有两个好处:第一,它允许你修正错误。第二,它防止程序自动终止。本节将介绍相应的方法。7.2.1try和catch的使用7.2.2显示一个异常的描述7.2.3使用多重catch语句7.2.4嵌套try语句[Return]7.2.

1try和catch的使用为防止和处理一个运行时错误,只需要把所要监控的代码放进一个try块就可以了。紧跟着try块的,包括一个说明你所希望捕获的错误类型的catch子句。完成这个任务的方法很简单,下面的程序包含一个处理因为被零除而产生的Arit

hmeticException异常的try块和一个catch子句。classExc2{publicstaticvoidmain(Stringargs[]){intd,a;try{//monitorablockofcode.d=0;a=42/d;System

.out.println("Thiswillnotbeprinted.");}catch(ArithmeticExceptione){//catchdivide-by-zeroerrorSystem.out.println("Divisionbyzer

o.");}System.out.println("Aftercatchstatement.");}}[Return]7.2.2显示一个异常的描述Throwable重载toString()方法(由Object定义),它返回一个包含异

常描述的字符串。可以通过在println()中传给异常一个参数来显示该异常的描述。例如,前面程序的catch块可以被重写成:catch(ArithmeticExceptione){System.out.println("Exception:"+e);a=0;//setatozeroandco

ntinue}当这个版本代替原程序中的版本,程序在标准javaJDK解释器下运行,每一个被零除错误显示下面的消息Exception:java.lang.ArithmeticException:/byzero

尽管在上下文中没有特殊的值,显示一个异常描述的能力在其他情况下是很有价值的,特别是当对异常进行测试和调试时。[Return]7.2.3使用多重catch语句某些情况,由单个代码段可能引起多个异常。处理这种情况,你可以定义两个或更多的catch

子句,每个子句捕获一种类型的异常。当异常被引发时,每一个catch子句被依次检查,第一个匹配异常类型的子句执行。当一个catch语句执行以后,其他的子句被旁路,执行从try/catch块以后的代码开始继续。当用多个ca

tch语句时,需要记住异常子类必须在它们任何父类之前使用,这一点是很重要的。因为运用父类的catch语句将捕获该类型及其所有子类类型的异常。这样,如果子类在父类后面,子类将永远不会到达。而且,Java中不能到达的代码是一个错误。[Return]7.2.4嵌套try语句在进行

异常处理时,try语句可以被嵌套使用。也就是说,一个try语句可以在另一个try块内部。每次进入try语句,异常的前后关系都会被推入堆栈。如果一个内部的try语句不含特殊异常的catch处理程序,堆栈将弹出,下一个try语句的catch处理程

序将检查是否与之匹配。这个过程将继续直到一个catch语句匹配成功,或者是直到所有的嵌套try语句被检查耗尽。如果没有catch语句匹配,Java在运行时系统将处理这个异常。教材P180~181页给出了一个运用嵌套try

语句的具体例子。[Return]7.3异常抛出前面只是说明了如何获取Java运行时被系统引发的异常。同时,程序还可以用throw语句引发明确的异常,用throws语句来标明一个成员函数可能抛出的各种异常。本节将对这

方面的内容进行介绍。7.3.1throw语句7.3.2throws语句[Return]7.3.1throw语句使用throw语句可以明确地抛出一个“异常”。首先,必须得到一个Throwable的实例的控制柄,通过参数传到catch子句,或者用new

操作符来创建一个。下面是throw语句的一般使用形式:throwThrowableInstance;这里,ThrowableInstance一定是Throwable类类型或Throwable子类类型的一个对象。简单类型,例如int或char,以及非Throwable类,例如St

ring或Object,不能用作异常。有两种可以获得Throwable对象的方法:在catch子句中使用参数或者用new操作符创建。程序执行在throw语句之后立即停止,后面的任何语句不被执行。最紧紧包围的try块用来检查它是否含有一个与异常类型匹配的catch语句。如果发现了匹配的块,控制将

转向该语句;如果没有发现,次包围的try块来检查,以此类推。如果没有发现匹配的catch块,默认异常处理程序中断程序的执行并且打印堆栈轨迹。[Return]7.3.2throws语句如果一个方法可以导致一个异常但不处理它,它必须指定这种行为以使方法的调用者可以保护它

们自己而不发生异常。做到这点你可以在方法声明中包含一个throws子句。一个throws子句列举了一个方法可能引发的所有异常类型。这对于除Error或RuntimeException及它们子类以外类型的所有异常是必要的。一个方法可以引发的所有其他类型的异常必须在thr

ows子句中声明。如果不这样做,将会导致编译错误。下面是包含一个throws子句的方法声明的一般形式[Return]typemethod-name(parameter-list)throwsexception-list{//bodyofmethod}这里,exception-list是该方法可

以引发的以逗号分割的异常列表。7.4finally语句当一个“异常”被抛出时,程序的执行就不再是线性的:跳过某些行,甚至会由于没有与之匹配的catch子句而过早地返回。有时确保一段代码不管发生什么“异常”都被执行到是必要的

,关键词finally就是用来标识这样一段代码的。即使你没有catch子句,finally程序块也会在执行try程序块后的程序之前执行。每个try语句都需要至少一个与之相配的catch子句或finally子句。一个成

员函数返回到调用它的成员函数,或者通过一个没捕捉到的“异常”,或者通过一个明确的return语句,finally子句总是恰好在成员函数返回前执行。下面是一个例子,它有几个成员函数,每个成员函数用不同的途径退出,但

执行了finally子句。finally创建一个代码块。该代码块在一个try/catch块完成之后另一个try/catch出现之前执行。finally块无论有没有异常引发都会执行。如果异常被引发,finally甚至是在没有与该异常相匹配的catch子句情

况下也将执行。一个方法将从一个try/catch块返回到调用程序的任何时候,经过一个未捕获的异常或者是一个明确的返回语句,finally子句在方法返回之前仍将执行。这在关闭文件句柄和释放任何在方法开始时被分配的其

他资源是很有用的。finally子句是可选项,可以有也可以无。然而每一个try语句至少需要一个catch或finally子句。教材P185~186页的例子显示了3种不同的退出方法。每一个都执行了finally子句。[Return]7.5自定义异常类尽管Java的内置异常处理大多数常见错误,你也许希

望创建自己的异常类型来处理所应用的特殊情况。做到这点非常简单:只要定义Exception的一个子类就可以了(Exception当然是Throwable的一个子类)。你的子类不需要实际执行什么——它们在类型系统中的存在允许你把它们当成异常使用。Exception类自己没有定义任何方法。当然,

它继承了Throwable提供的一些方法。因此所有异常,包括你创建的,都可以获得Throwable定义的方法,这些方法见教材P186页表7-3中所示。读者还可以在你创建的异常类中覆盖一个或多个这样的方法。[Return]第8章多线程编程支持多线程编程是Java语言的又一大特色。

多线程是相对于进程或单线程而言的,它具有并发性、执行效率高的特点。本章将对Java中的多线程编程作初步介绍。8.1多线程编程概述8.2线程的创建8.3线程的优先级8.4线程同步8.5线程间通信8.6线程的控制[Return]8.1多线程编程概述本节介绍多线程编程的

基础知识,包括多线程的基本概念、Java的线程模型(线程优先级、同步性、消息传递)等方面的内容。8.1.1什么是多线程8.1.2Java线程模型[Return]8.1.1什么是多线程同其他大多数编程语言不同,Java内置支持多线程编程(multithreadedprogramming)。多线

程程序包含两条或两条以上并发运行的部分,把程序中每个这样的部分都叫作一个线程(thread)。每个线程都有独立的执行路径,因此多线程是多任务处理的一种特殊形式。读者可能知道多任务处理,它实际上被所有的现代操作系统所支持。然而,多任务处理有两种截然不同的类型:基于进

程的和基于线程的。搞清楚两者的区别是很重要的。对大多数读者来说,基于进程的多任务处理是更熟悉的形式。进程(process)本质上是一个执行的程序。因此基于进程的多任务处理的特点是允许你的计算机同时运行两个或更多的程序。举例来说,基于进程的多任务处理使你在运用文本编辑器的时候可以同时

运行Java编译器。在基于进程的多任务处理中,程序是调度程序所分派的最小代码单位。而在基于线程(thread-based)的多任务处理环境中,线程是最小的执行单位。这意味着一个程序可以同时执行两个或者多

个任务的功能。例如,一个文本编辑器可以在打印的同时格式化文本。所以,多进程程序处理“大图片”,而多线程程序处理细节问题。[Return]多线程程序比多进程程序需要更少的管理费用。进程是重量级的任务,需要分配给它们独立的地址空间。进程间通信是昂贵和受限的。进程间的转换也是很需要花费的。另一

方面,线程是轻量级的选手。它们共享相同的地址空间并且共同分享同一个进程。线程间通信是便宜的,线程间的转换也是低成本的。当Java程序使用多进程任务处理环境时,多进程程序不受Java的控制,而多线程则受Java控制。多线程可帮助你编写出CPU最大利用率的高效程序,使得空闲时间保持最低

。这对Java运行的交互式的网络互连环境是至关重要的,因为空闲时间是公共的。例如,网络的数据传输速率远低于计算机处理能力,而本地文件系统资源的读写速度也远低于CPU的处理能力。当然,用户输入也比计算机慢很

多。在传统的单线程环境中,程序必须等待每一个这样的任务完成以后才能执行下一步——尽管CPU有很多空闲时间。多线程使你能够获得并充分利用这些空闲时间。8.1.2Java线程模型Java运行系统在很多方面依赖于线程,所有的类库设计都考虑到多线程。实际上,Jav

a使用线程来使整个环境异步。这有利于通过防止CPU循环的浪费来减少无效部分。为更好地理解多线程环境的优势,我们可以将它与它的对照物相比较。单线程系统的处理途径是使用一种叫作轮询的事件循环方法。在该模型中,单线程控制在

一无限循环中运行,轮询一个事件序列来决定下一步做什么。一旦轮询装置返回信号表明已准备好读取网络文件,事件循环调度控制管理到适当的事件处理程序。直到事件处理程序返回,系统中没有其他事件发生。这就浪费了CPU时间。这导致了程序的一部分独占了系

统,阻止了其他事件的执行。总的来说,单线程环境,当一个线程因为等待资源时阻塞(block,挂起执行),整个程序停止运行。Java多线程的优点就在于取消了主循环/轮询机制。一个线程可以暂停而不影响程序的其他部分。例如,当一个线程从网络读取数据或等待用户输入时产生的空闲时间可以被利用到其

他地方。多线程允许活的循环在每一帧间隙中沉睡一秒而不暂停整个系统。在Java程序中出现线程阻塞,仅有一个线程暂停,其他线程继续运行。线程存在多种状态。线程可以正在运行(running),只要获得了CPU时间它

就可以运行;运行的线程可以被挂起(suspend),并临时中断它的执行;一个挂起的线程可以被恢复(resume),允许它从停止的地方继续运行;一个线程可以在等待资源时被阻塞(block);在任何时候,线程可以被终止(terminate)

,这将立即中断运行。一旦终止,线程不能被恢复。线程的各状态间关系见教材P190页图8-1所示。下面简要介绍与Java线程相关的几个概念Java给每个线程安排优先级以决定与其他线程比较时该如何对待该线程。线程优先级是详细说明线程间优先关系的整数。

作为绝对值,优先级是毫无意义的;当只有一个线程时,优先级高的线程并不比优先级低的线程运行的快。相反,线程的优先级是用来决定何时从一个运行的线程切换到另一个。这叫“上下文转换”(contextswitch)。决定上下

文转换发生的规则很简单:l线程可以自动放弃控制。在I/O未决定的情况下,睡眠或阻塞由明确的让步来完成。在这种假定下,所有其他的线程被检测,准备运行的最高优先级线程被授予CPU。l线程可以被高优先级的线程抢占。在这种情况下,低优先级

线程不主动放弃,处理器只是被先占——无论它正在干什么——处理器被高优先级的线程占据。基本上,一旦高优先级线程要运行,它就执行。这叫做有优先级的多任务处理。当两个相同优先级的线程竞争CPU周期时,情形有一点复杂。对于W

indows这样的操作系统,等优先级的线程是在循环模式下自动划分时间的。对于其他一些非Windows操作系统而,如Solaris2.x,等优先级线程相对于它们的对等体自动放弃。如果不这样,其他的线程就不会运行。1

.线程优先级2.同步性由于多线程在程序中引入了一个异步行为,故在需要的时候必须有加强同步性的方法。举例来说,如果你希望两个线程相互通信并共享一个复杂的数据结构,例如链表序列,就需要某些方法来确保它们没有相互冲突。也就是说,你必须防止一个线程写入数据而另一个线程正在读取链表

中的数据。为此,Java在进程间同步性的老模式基础上实行了另外的一种方法:管程(monitor)。管程是一种由C.A.R.Hoare首先定义的控制机制。你可以把管程想象成一个仅控制一个线程的小盒子。一旦线程进入管程,所有线程必须

等待直到该线程退出了管程。用这种方法,管程可以用来防止共享的资源被多个线程操纵。很多多线程系统将管程作为程序必须明确的引用和操作的对象。但Java提供一个清晰的解决方案,不提供“Monitor‖类;相反,每个对象都拥有自

己的隐式管程,当对象的同步方法被调用时管程自动载入。一旦一个线程包含在一个同步方法中,没有其他线程可以调用相同对象的同步方法。这就使你可以编写非常清晰和简洁的多线程代码,因为同步支持是语言内置的。3.消息传递当把程序分成若干线程后,就要定义各线程之间的联系。用大多数其他语言规划时必须依赖于

操作系统来确立线程间通信,这样当然要增加花费。然而,Java提供了多线程间谈话清洁的、低成本的途径——通过调用所有对象都有的预先确定的方法。Java的消息传递系统允许一个线程进入一个对象的一个同步方法,然后在

那里等待,一直等到其他线程明确通知它出来。[Return]Java的多线程系统建立于Thread类、方法以及共伴接口Runnable基础上。Thread类封装了线程的执行。既然不能直接引用运行着的线程的状态,就要通过它的代理处理它。于是Thread实例产生了。为创建一个新的线程,程

序中必须扩展Thread或实现Runnable接口。Thread类定义了好几种方法来帮助管理线程,见教材P192页表8-1中所列。4.Thread类和Runnable接口8.2线程的创建本节介绍在Java中如何创建线程。主要内容包括主线程、多线程的创建、相关方法的使用等。8.2.

1关于主线程8.2.2创建一个线程8.2.3创建多线程8.2.4使用isAlive()和join()[Return]8.2.1关于主线程当Java程序启动时,一个线程立刻运行,该线程通常就叫做程序的主线程(mainthread),因为它是程序开始时就执行的。主线程的

重要性主要体现在两方面:l它是产生其他子线程的线程;l通常它必须最后完成执行,因为它执行各种关闭动作。尽管主线程在程序启动时自动创建,但它可以由一个Thread对象控制。为此,必须调用方法currentThread()获得它的一个引用,cur

rentThread()是Thread类的公有的静态成员。它的一般形式如下staticThreadcurrentThread()该方法返回一个调用它的线程的引用。一旦获得主线程的引用,就可以像控制其他线程那样控制主线程。下面我们考察一下教材P192~

193页的程序代码。在上面的程序中,当前线程(当然是主线程)的引用通过调用currentThread()获得,该引用保存在局部变量t中。然后,程序显示了线程的信息。接着,程序调用setName()改变线

程的内部名称,线程信息又被显示。然后,一个循环数从5开始递减,每数一次暂停一秒。暂停是由sleep()方法来完成的,sleep()语句明确规定延迟时间是1毫秒。请读者注意循环外的try/catch块。Thread类的sleep()方法可能引发一个InterruptedException异常,这种情

形会在其他线程想要打搅沉睡线程时发生。本例只是打印了它是否被打断的消息。在实际的程序中,必须灵活处理此类问题。[Return]8.2.2创建一个线程大多数情况,通过实例化一个Thread对象来创建一个线程。Java定义了两种方式:l实现Ru

nnable接口;l以继承Thread类的方式。创建线程最简单的方法就是创建一个实现Runnable接口的类,Runnable抽象了一个执行代码单元。可以通过实现Runnable接口的方法创建每一个对象的线程。为实现Runnable接口,一个类仅需

实现一个run()的简单方法,该方法声明如下:publicvoidrun()在run()中,可以定义代码来构建新的线程。重要的是:run()方法能够像主线程那样调用其他方法,引用其他类,声明变量。仅有的不同是:run()在程序中确立另一个并发

的线程执行入口。当run()返回时,该线程结束。在已经创建了实现Runnable接口的类以后,需要在类内部实例化一个Thread类的对象。Thread类定义了好几种构造函数。我们会用到的如下:Thread(RunnablethreadOb,StringthreadName

)在该构造函数中,threadOb是一个实现Runnable接口类的实例。这定义了线程执行的起点,新线程的名称由threadName定义。建立新的线程后,它并不运行直到调用其start()方法,该方法在Thread类中定义。从本质上讲,start()执行的是一个对run()的调用。start()

方法声明如下:voidstart()下面我们分别对这两种方法进行介绍:1.实现Runnable接口2.扩展Thread创建线程的另一个途径是创建一个新类来扩展Thread类,然后再创建该类的实例。当一个类继承Thread时,它必须重载run()方法,这个run()方法是新线程的入口。同时,它也必

须调用start()方法去启动新线程执行。[Return]到这里,读者可能会奇怪为什么Java有两种创建子线程的方法,哪一种更好呢。所有的问题都归于一点。Thread类定义了多种方法可以被派生类重载。对于所有的方法,唯一的必须被重载的是run()

方法。这当然是实现Runnable接口所需的同样的方法。很多Java程序员认为类仅在它们被加强或修改时被扩展。因此,如果你不重载Thread的其他方法,最好只实现Runnable接口,这当然由自己决定。在本章的其他部分,我们应用实现Runnab

le接口的类来创建线程。3.选择合适的方法8.2.3创建多线程到目前为止,我们仅用到两个线程:主线程和一个子线程。然而,我们完全可以创建所需的更多线程。例如,教材P197~198页的程序创建了3个子线程。详细

分析该程序。[Return]8.2.4使用isAlive()和join()如前所述,我们一般是希望主线程最后结束。在上面的例子中,这点是通过在main()中调用sleep()来实现的,经过足够长时间的延迟以确保所有子线程都先于主线程结束。然而,这并不是一个好的解决方法。因为有时候存在这个问

题:一个线程如何知道另一线程已经结束?幸运的是,Thread类提供了解决此问题的有效方法。有两种方法可以判定一个线程是否结束:第一,可以在线程中调用isAlive()。这种方法由Thread定义,它的一般形式如下finalbooleanisAlive()如果所调用线程仍在运行,isAlive()方

法返回true,如果不是则返回false。但isAlive()很少用到,等待线程结束的更常用的方法是调用join(),描述如下finalvoidjoin()throwsInterruptedException该方法等待所调用线程结束,该名字来自于要求线程等待直到

指定线程参与的概念。join()的附加形式允许给等待指定线程结束定义一个最大时间。详细分析教材P199~200页的程序。[Return]8.3线程的优先级线程优先级被线程调度用来判定何时某个线程允许运行。理论上,优先级高的线程比优先级低的线程获得更多的CPU时间。实际上,线程获得的C

PU时间通常由包括优先级在内的多个因素决定。一个优先级高的线程自然比优先级低的线程优先。理论上,等优先级线程有同等的权利使用CPU,但你必须小心。需要记住的是,Java是被设计成能在很多环境下工作的。不同环境下实现多任务处理从本质上来看是可能的

。为安全起见,等优先级线程有时候也受到控制。这保证了所有线程在无优先级的操作系统下都有机会运行。实际上,在无优先级的环境下,多数线程仍然有机会运行,因为很多线程不可避免地会遭遇阻塞,例如等待输入输出。遇到这种情形,阻塞的线程挂起,其他线程运行。但是如

果你希望多线程执行得顺利的话,最好不要采用这种方法。同样,有些类型的任务是占CPU的。对于这些支配CPU类型的线程,有时你希望能够支配它们,以便使其他线程可以运行。设置线程的优先级,用setPriority(

)方法,该方法也是Thread的成员。它的通常形式为finalvoidsetPriority(intlevel)这里,level指定了对所调用的线程的新的优先权的设置。Level的值必须在MIN_PRIORITY到MAX_PRIORITY范围内。通常,它们的值分别是1和10。要返回一个线程为默认的

优先级,指定NORM_PRIORITY,通常值为5。这些优先级在Thread中都被定义为final型变量。[Return]8.4线程同步当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用,达到此目的的过程叫做同步(synch

ronization)。Java为此提供了独特的、很有效的支持机制。8.4.1使用同步方法8.4.2同步语句[Return]8.4.1使用同步方法同步的关键是管程(也叫信号量,即semaphore)的概念。管程是一个互斥独占锁定的对象,或称

互斥体(mutex)。在给定的时间,仅有一个线程可以获得管程。当一个线程需要锁定时,它必须进入管程。所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程。这些其他的线程被称为等待管程。我们可以用两种方法同步化代码。通过调用sleep(),cal

l()方法允许执行转换到另一个线程。两者都包括synchronized关键字的运用。分析教材P204页的示例。[Return]8.4.2同步语句尽管在创建的类的内部创建同步方法是获得同步的简单和有效的方法,但它并非在任何时候都

有效。假设你想获得不为多线程访问设计的类对象的同步访问,也就是该类没有用到synchronized方法。而且,该类不是你自己,而是第三方创建的,就不能获得它的源代码。这样,就不能在相关方法前加synchronized修饰符。怎样才能使该类的一个对象同步化呢?解决的方法很简单:只需将对

这个类定义的方法的调用放入一个synchronized块内就可以了。下面是synchronized语句的一般形式synchronized(object){//statementstobesynchronized}其中,object是对同步对象的引用。如果你想要同步的只

是一个语句,那么不需要花括号。一个同步块确保对object成员方法的调用仅在当前线程成功进入object管程后发生。[Return]8.5线程间通信前面的例子无条件地阻塞了其他线程异步访问某个方法。Java对象中隐式管程

的应用是很强大的,但是我们可以通过进程间通信达到更微妙的境界,这在Java中是很简单的。8.5.1Java中的线程通讯8.5.2关于死锁[Return]8.5.1Java中的线程通讯多线程通过把任务分成离散的和合乎逻辑的单元代

替了事件循环程序。线程还有另外一个优点:它远离了轮询。轮询通常由重复监测条件的循环实现。一旦条件成立,就要采取适当的行动。这浪费了CPU时间。为避免轮询,Java包含了通过wait(),notify()和notifyAll()方法实现的一个进程间通信机制。这些

方法在对象中是用final方法实现的,所以所有的类都含有它们。这三个方法仅在synchronized方法中才能被调用。尽管这些方法从计算机科学远景方向上来说具有概念的高度先进性,实际中用起来却是很简单的。wait()告知被调用的线程放弃管程进入睡眠直到其他线程进入相同管程并且调用

notify()。notify()恢复相同对象中第一个调用wait()的线程。notifyAll()恢复相同对象中所有调用wait()的线程。这些方法在Object中被声明,如下所示finalvoidwait()throwsInte

rruptedExceptionfinalvoidnotify()finalvoidnotifyAll()wait()存在的另外的形式允许你定义等待时间。分析教材P207~210页的程序段。[Return]8.5.2关于死锁

需要避免的与多任务处理有关的特殊错误类型是死锁(deadlock)。死锁发生在当两个线程对一对同步对象有循环依赖关系时。例如,假定一个线程进入了对象X的管程而另一个线程进入了对象Y的管程。如果X的线程试图调用Y的同步方法,它将像预料的一样被锁

定。而Y的线程同样希望调用X的一些同步方法,线程永远等待,因为为到达X,必须释放自己的Y的锁定以使第一个线程可以完成。死锁是很难调试的错误,这是因为:第一,通常它极少发生,只有到两线程的时间段刚好符合时才能发生;第二,它可能包含多于两个的线程和同步对象。也就是说,死锁在比刚讲述的

例子有更多复杂的事件序列的时候可以发生。为充分理解死锁,观察它的行为是很有用的。教材P211~212页的例子生成了两个类,A和B,分别有foo()和bar()方法。这两种方法在调用其他类的方法前有一个短暂的停顿。主类,名为Deadlock

,创建了A和B的实例,然后启动第二个线程去设置死锁环境。foo()和bar()方法使用sleep()强迫死锁现象发生。程序死锁,需要按CTRL-C来结束程序。在PC机上按CTRL-BREAK(或在So

laris下按CTRL-\)可以看到全线程和管程缓冲堆。[Return]8.6线程的控制本节讨论有关线程控制的问题,包括线程的挂起、恢复、终止等方面的问题。8.6.1挂起、恢复和终止线程8.6.2Java2中的线程控制8.6.3使用instanceof[Re

turn]8.6.1挂起、恢复和终止线程有时,线程的挂起是很有用的。例如,一个独立的线程可以用来显示当日的时间。如果用户不希望用时钟,线程被挂起。在任何情形下,挂起线程是很简单的,一旦挂起,重新启动线程也是一件简单的事。挂起、终止和恢复线程机制在Java2和早

期版本中有所不同。尽管你运用Java2的途径编写代码,仍需了解这些操作在早期Java环境下是如何完成的。例如,也许你需要更新或维护老的代码,就需要了解为什么Java2会有这样的变化。因为这些原因,下面内容说明了执行线程控制的原始方法,接着是Java2

的方法。先于Java2的版本(Java1.1或更早版本),程序用Thread定义的suspend()和resume()来暂停和再启动线程。它们的形式如下。finalvoidsuspend()finalvoid

resume()Thread类同样定义了stop()来终止线程,其形式如下:voidstop()一旦线程被终止,它不能被resume()恢复继续运行。[Return]8.6.2Java2中的线程控制在

Java2中不能使用suspend(),resume()和stop()方法来控制线程,读者也许会想那就没有办法来停止、恢复和结束线程,其实不然。相反,线程必须被设计成使用run()方法定期检查来判定线程是否应该被挂起,恢复或终止它自己的执行。有代表性的,这由建立一个指示线程状

态的标志变量来完成。只要该标志设为“running‖,run()方法必须继续让线程执行。如果标志设为“suspend‖,线程必须暂停。若设为“stop‖,线程必须终止。当然,编写这样的代码有很多方法,但中心主题对所有的程序应该是相同的。教材P216~21

7页的例子显示了从Object继承的wait()和notify()方法怎样控制线程的执行。该例与前面讲过的程序很像。然而,不被赞同的方法都没有用到。让我们思考程序的执行。NewTread类包含了用来控

制线程执行的布尔型的实例变量suspendFlag。它被构造函数初始化为false。Run()方法包含一个监测suspendFlag的同步声明的块。如果变量是true,wait()方法被调用以挂起线程。Mys

uspend()方法设置suspendFlag为true。Myresume()方法设置suspendFlag为false并且调用notify()方法来唤起线程。最后,main()方法被修改以调用mysuspend()和

myresume()方法。[Return]8.6.3使用instanceof最后,我们附带介绍一下关键字instanceof。有时,在运行时间内知道对象类型是很有用的。例如,你有一个执行线程生成各种类型的对象,其他线程处理这些对象。这种情况下,让处理线程在接受对象时知道每一个对象的类型是大有裨

益的。另一种在运行时间内知道对象的类型是很有用的情形是强制类型转换。Java中非法强制类型转换导致运行时错误。很多非法的强制类型转换在编译时发生。然而包括类层次结构的强制类型转换可能是仅能在运行时间里被察觉的非法强制类型转换。例如,一个名为A的父类能生成两个子类B和C。这样,在

强制B对象转换为类型A或强制C对象转换为类型A都是合法的,但强制B对象转换为C对象(或相反)都是不合法的。因为类型A的一个对象可以引用B或C。但是你怎么知道,在运行时,在强制转换为C之前哪类对象被引用?它可能是A、B或C的一个对象。如果它是B的对象,一个运行时异常被引发。Java提供

运行时运算符instanceof来解决这个问题。instanceof运算符具有下面的一般形式:objectinstanceoftype这里,object是类的实例,而type是类的类型。如果object是指定的类型或者可以被强

制转换成指定类型,instanceof将它评估成true,若不是,则结果为false。这样,instanceof是程序获得对象运行时类型信息的方法。教材P218~219页的程序说明了instanceof的应用。[Return]在本书第1章中,我们曾简要

地介绍了Applet小程序的一般编译、运行所需步骤。本章将更详细地讨论一下Applet编程问题。9.1关于Applet类9.2Applet中的文件操作9.3使用Applet访问数据库第9章Applet[Return]在这一节里将介

绍有关Applet类的相关知识。Applet类为小应用程序提供了必不可少的支持。9.1关于Applet类[Return]9.1.1Applet基础9.1.2Applet类9.1.3Applet体系结构9.1.1Applet基础Applet类被包

含在名叫java.applet的类库里,它提供了一些方法,使用它们可以在小应用程序的执行过程中进行更严密的控制。除此以外,java.applet还定义了一些接口,例如:AppletContext、AudioClip和AppletStub等所有的小应用程序都是Applet类的子类。因此,所有的小应

用程序都必须引用java.applet类库。既然所有的小应用程序都运行在一个窗口中,那么引入对这个窗口的支持类库就是必不可少的。需要注意的是,小应用程序并非基于控制台的Java运行环境的解释器所执行的,而是由Web浏览器或小应用程序阅读器执行。一般是由标准的小应用程序

阅读器appletviewer生成的,appletviewer由JDK(java开发工具)提供。但读者能够按自己的喜好选择任何小应用程序阅读器或浏览器。与大多数程序不同的是,一个小应用程序的执行不是从main

()开始的。实际上,没有多少小应用程序使用main(),小应用程序的执行用一种完全不同的机制启动和控制,我们接下来将对这种机制进行介绍。小应用程序窗口的输出并不是由函数System.out.println()完成的,而是由各种不同的AWT方法来实现,例如drawS

tring(),这个方法可以向窗口的某个由X、Y坐标决定的特定位置输出一个字符串。同样的,小应用程序窗口的输入与一般的应用程序不同。只要小应用程序经过编译,它就被包含在一个HTML文件中,并使用APPLET标记。这之后当

支持Java的Web浏览器遇到HTML文件中的APPLET标记时,小应用程序就能被执行。为了更方便地观察和测试小应用程序,只需在编制的Java源程序代码的头部加入一个包含APPLET标记的注释即可。用这种方法,代码就能用小应用程序所需的HTML语

句记述下来,这样,只要启动小应用程序阅读器并指定Java源代码文件为目标文件,就可以测试经过编译的小应用程序了。[Return]9.1.2Applet类Applet类定义了教材P221页表9-1中所示的一些方法。App

let类为小应用程序的执行如启动、中止等提供了所有必需的支持。它还提供了装载和显示图像的方法,以及装载和播放语音片断的方法。Applet扩展了AWT类中的Panel。依此类推,Panel扩展了类Container,Co

ntainer扩展了Component。些类都为Java的基于窗口的图形接口提供了支持。这样,Applet为基于窗口的所有活动都提供了支持。[Return]9.1.3Applet体系结构由于小应用程序是一种基于窗口的程序。所以,它的

体系结构与一般的基于控制台的程序是不同的。如果对Windows编程很熟悉,在深入小应用程序编程时就能够得心应手。如果不是这样的话,就必须先要理解几个重要的概念。首先,小应用程序是由事件驱动的。尽管我们在这里并不关心事件处理的机制,但是对于事件驱动机制如何影响到小应用程序的设计

这一问题,获得一般性的理解还是很重要的。一个小应用程序类似于系列提供中断服务的子程序的集合。程序就是这样运行的。在事件发生之前,小应用程序一直处于等待状态中。一旦事件发生,小应用程序就会采取相应措施并迅速将控制权交给AWT。这

一点是很关键的。在大部分时间里,小应用程序都不会进入操作运行模式而长久地保持控制权。相反的,它必须针对特定的事件作出相应的动作并把控制交给AWT的运行环境。在有些情况下,小应用程序需要独立完成一些重复的作业(例如,在窗口中显示滚动信息),这时,必须再启动一个额外的线程。其

次,用户可以与小应用程序进行交互,而不是通过其他方式。我们都知道,在一个非窗口界面的程序中,当程序需要输入时,它会提示用户并调用一定的输入方法,例如readLine()。而在小应用程序中,并不是这样运作的。相反,用户可以按照自己的喜

好随意地与小应用程序进行交互。这些交互被送至小应用程序,作为小应用程序必须作出响应的事件。例如,当用户在小应用程序的窗口中点击鼠标时,一个鼠标点击事件就产生了。如果用户在小应用程序窗口中的焦点处按下一个键,一个按键事件就被产生。几乎

大多数的小应用程序都重载一套方法,这些方法提供了浏览器或小应用程序阅读器与小应用程序之间的接口以及前者对后者的执行进行控制的基本机制。这套方法中的四个:init()、start()、stop()和destroy()是由Applet所定义的。另

一个方法,paint()是由AWT组件类定义的。所有这些方法的具体实现也都被提供。小应用程序并不需要重载那些它们没有用到的方法。但是,只有非常简单的小应用程序才不需要定义全部的方法。这5个方法组成了程序的基本主框架,见教材P223页程序段。1.App

let主框架知道程序主框架中所示的各种方法的排列顺序是很重要的。当一个小应用程序开始执行时,AWT就以如下顺序调用以下的方法:1.init();2.start();3.paint()。当一个小应用程序被终止时,下列方法就按如下顺序被调用:1.stop();2.destroy()

。整个过程见教材P224页图9-1所示。2.Applet的初始化与终止init():这是被调用的第一个方法。是初始化变量的地方。这个方法在小应用程序运行期间仅被调用一次。start():这是在init()之后被调用。它也在小应用程

序被终止后重新启动时调用。注意:Init()仅在小应用程序第一次被装载时调用一次,而start()却在每一次小应用程序的HTML文档被显示在屏幕上时都被调用。因此,如果用户离开一个网页之后重新进入时,小应用程序就会从start()开始重新执行。paint():在每一次小应用程序的输出必须

重画窗口时,paint()方法都被调用。paint()方法也在小应用程序开始执行时被调用。paint()方法有一个Graphics类型的参数。这个参数包含了图像上下文,描述了小应用程序所运行的环境。在需要对小应用程序进行输出时,这个上下文将被用到。stop():当Web浏览器离开包含小

应用程序的HTML文件时,stop()方法就被调用,如在浏览器中去另一个页面时。当stop()被调用时,小应用程序很可能在运行。应该使用stop()来挂起一些在小应用程序不可见时不需要运行的线程。当用户回到此页面时,就能重新启动它们。destroy():当环

境决定了小应用程序需要完全从内存中移去时,destroy()方法被调用。在这时候,应该释放任何小应用程序可能用到的资源。stop()方法总是在destroy()之前被调用。现在让我们更仔细地讨论一下上述方法在某些情况下,小应用程序可能需要覆盖另外一个AWT所定义的方法,即update()。这

个方法在小应用程序要求窗口的一部分被重画时被调用。默认的update()的方法是先用默认的背景颜色填充小应用程序窗口,再调用paint()方法。如果在填充背景时用的颜色与paint()方法中使用的不同,那么在每次

update()被调用时,也就是只要窗口被重画时,用户将会感觉到默认背景的闪动。避免这个问题的一种方法是重载update()方法,从而使它完成所有必要的显示功能。然后,使paint()简单地调用upda

te()。这样,在一些应用中,小应用程序将重载paint()和update(),如下所示。publicvoidupdate(Graphicsg){//redisplayyourwindow,here.}publicvoi

dpaint(Graphicsg){update(g);}3.重载update()方法[Return]9.2Applet中的文件操作本节介绍Applet中与文件操作、字体属性相关的一些问题。[Return]9.2.1图形

文件的读入9.2.2声音文件的读入9.2.3Applet在Java中,我们可以直接载入并输出图形文件格式。所支持的文件格式有两种,分别为.gif和.jpg格式(不支持.bmp文件格式)。只要能够给定图形文件的位置和文件名,就可以通过getImage()这个方法载入所需要的图形。在这里所说的位

置,是指图形文件所在的URL位置。也就是说,读者不仅可以在Java程序中使用存放在自己硬盘上的图形文件,还可以通过URL指定的方式,直接读取网络资源上的图形文件,而且使用的方式非常简单。当然,Applet运行的时候,要保证网络畅

通,这样才能显示出该图形。getImage()方法主要出现在两个类里,第一个是java.applet.Applet中,另外一个是java.awt.Tookit。在编写Java应用程序时,只能使用java.awt.Tookit中的方法。而编写Java小应用程序时,二种都可

以使用。getImage()方法的使用格式包括:(1)在java.applet.Applet类中:ImagegetImage(URL,url)ImagegetImage(URLurl,Stringname)(2)在java.awt.Tookit类中:ImagegetIma

ge(Stringfilename)ImagegetImage(URLurl)9.2.1图形文件的读入[Return]9.2.2声音文件的读入声音文件和Java读入图形格式的原理是一样的。但是,Java目前好像还只支持AU格式的声音文件。这

种格式并不多见,所以需要读者将别的文件格式转化成为这种格式。它的调入和播放方法如下:importjava.applet.AudioClip;AudioClipbgsound=getAudioClip(URLurl):bgsound.play();//播放一次bgsound.loop();//循环

播放bgsound.stop();//停止播放//也可以直接一次性地读入并播放:play(getCodeBase(),“audio/welcome.au”);……它的URL的使用方法同前面读入图像时所使用的方法是一样的。[Return]9.2.3Applet从前面

的介绍中我们已经知道,Graphics类的方法drawString()可以在屏幕的指定位置显示一个字符串,而Java中还有一个类Font,使用它可以获得更加丰富多采和逼真精确的字体效果。一个Font类的对象表示了一种字体的显示效果,包括字体类

型、字型和字号。可用下面的语句创建一个Font类的对象。FontMyfont=newFont("楷体",Font.BOLD,14);其含义是创建一个名为Myfont的Font类:设置字体大小为14磅,类型为楷体,加粗型。另

外的两种是Font.PLAIN(正常)、Font.ITALIC(斜体)。该类定义完后,就可用g.setFont(Myfont)方法设置了。以后在没有设置新的字体之前,都将会用这个已经设置好了的字体显示字体。分析教材P227~228页的例子。[Return]9.3使用Applet访问数据库本节以Ac

cess数据库为例简要说明一下如何实现数据库的相关操作。首先使用MicrosoftAccess创建一个数据库,具体内容读者可自行定义。为了运行这个小应用程序,用户必须首先创建一个与MyTable数据库相对应的ODBC数据源。为了通过ODBC使用Acc

ess,用户必须先安装AccessODBC驱动程序。如果没有安装该驱动程序,程序的运行过程中就会出现异常。安装的具体步骤是:(1)从控制面板中选择ODBC-32(WindowsXP在“管理工具”)图标项,双击之。(2)在“系统DSN‖页面,单击添加,选择MicrosoftAccessDri

ver。(3)输入所要使用的数据源的名称。如果没有,就得创建一个。(4)单击选择按钮,选择已经创建好了的Access数据库的文件。(5)单击确定就可以了。如果读者想为数据库保密的话,可以加上用户名和密码(在高级选项中)。然后,定义变量。变量定义及数据库的连接与操作见教材P22

9~231页示例。[Return][Return]第10章输入与输出10.1Java输入/输出基础10.2读取控制台输入10.3向控制台写输出10.4流类10.5文件的读写在Java语言中,输入与输出完全基于“流”

这个概念。Java输入输出的流式接口为复杂而繁重的I/O编程任务提供了一个简洁的抽象。本章将对这方面的内容作详细介绍。[Return]10.1Java输入/输出基础10.1.1流的概念10.1.2字节流和字符流10.1.3预定义流10.1.4Java输

入/输出类和接口关于Java的输入/输出问题我们在前面的程序代码中一般都接触过,只是没有系统地进行讨论。本节将对Java的输入/输出问题作一个概述。10.1.1流的概念我们知道,多数程序在不获取外部数据的情况下不能顺利完成目标。数据从一个输入源获得,程序的结果被送到

输出目的地。在Java中,这些源和目的地被广泛地定义。例如一个网络连接器,内存缓冲区或磁盘文件可以被Java输入/输出类熟练地操作。尽管从物理意义上很难说明,这些外设都由相同的抽象体流(stream)来处理。流是一个生产或消费信息的逻辑实体,它通过Java输入/输出系统与物理设备相连

。尽管与之相连的实际物理设备各不相同,但是所有的流都以同样的方式运转。Java程序通过流来完成输入/输出,它是生产或消费信息的抽象。流通过Java的输入/输出系统与物理设备链接。尽管与它们链接的物理设备不尽相同,但是所有流的行为具有同样的方式。这样,相

同的输入/输出类和方法适用于所有类型的外部设备。这意味着一个输入流能够抽象多种不同类型的输入:从磁盘文件,从键盘或从网络套接字。同样,一个输出流可以输出到控制台,磁盘文件或相连的网络。流是处理输入/输出的一个洁净的方法,例如它不需要代码理解键盘

和网络的不同。Java中流的实现是基于java.io包定义的类层次结构的。[Return]10.1.2字节流和字符流在Java2中,定义了两种类型的流:字节类和字符类。字节流(bytestream)为处理字节的输入和输出

提供了方便的方法。例如,使用字节流读取或书写二进制数据。字符流(characterstream)为字符的输入和输出处理提供了方便。这两种流采用了统一的编码标准,因而可以国际化。当然,在某些场合字符流比字节流更为有效。在J

ava的早期版本(Java1.0)中不包括字符流,因此所有的输入和输出都是以字节为单位的。后来,Java1.1中加入了字符流,某些字节形式的类和方法不被推荐使用。这也是为什么没用字符流的老代码在适当的地方需要更新的原因。需要说明的是,在最底层,所有的输入/输出都是字节形式的。基

于字符的流只是为处理字符提供方便有效的方法。下面对字节流和字符流分别作简要介绍。字节流由两个类层次结构定义。在顶层有两个抽象类:InputStream和OutputStream。每个抽象类都有多个具体的子类,这些子类对不同的外设进行处理,例如

磁盘文件、网络连接、甚至是内存缓冲区。各种字节流类及其相关说明见教材P235页。1.字节流类字符流类由两个类层次结构定义。顶层有两个抽象类:Reader和Writer。这些抽象类处理统一编码的字符流。在Java中这些类含有多个具体的子类详见教材P235~236页。2.字符流类[Return]10

.1.3预定义流我们已经知道,所有的Java程序运行时自动导入java.lang包,这个包定义了一个名为System的类,该类封装了运行时环境的多个方面。例如,使用它的某些方法,能获得当前时间和与系统有关的不同属性。System同

时还包含有3个预定义的流变量:in、out和err。这些成员在System中是被定义成public和static型的,即意味着它们可以不引用特定的System对象而被用于程序的其他部分。System.out是标准的输出流,在默认情况下它是一个控制台;System.in是标准输入,默认情况下指的是

键盘;System.err指的是标准错误流,它默认是控制台。需要指出的是,这些流可以重定向到任何兼容的输入/输出设备。System.in是InputStream的对象;System.out和System.err是PrintStream的对象。尽管它们

是用来读写外设字符的,它们都是字节流。如果编程人员愿意,可以用基于字符的流来包装它们。在前面章节中多次用到过System.out,我们可以同样的方式使用System.err。System.in的使用则稍微复杂一些。[Retur

n]10.1.4Java输入/输出类和接口在Java2中,java.io包定义的输入/输出类见教材P236~237页。不难发现,其中包含了前面所介绍的字节流和字符流。其中,ObjectInputStream.GetField和ObjectOutputStre

am.PutField是Java2中新添的内部类。对于Java2不推荐使用的两个类LineNumberInputStream和StringBufferInputStream,上面没有列出。虽然java.io中予以保

留,但一般情况下不应使用。由java.io定义的接口见教材237页。其中,FileFilter接口是Java2中新增的。从以上介绍不难发现,java.io包中有很多的类和接口,包括各种字节流、字符流、对象序列化(对象的存储和释放)。后面我们将介绍其中一些最常用的I/

O成员。[Return][Return]10.2读取控制台输入10.2.1如何读取控制台输入10.2.2读取字符10.2.3读取字符串在本节中,我们将介绍Java如何读取控制台输入的有关问题。10.2.1如何读

取控制台输入在Java1.0中,完成控制台输入的唯一途径是字节流,这种方法现在依旧可用。但是,这种做法不值得推荐。在Java2中,读取控制台输入的首选方法应该是字符流,它使程序容易符合国际标准,并且易于维护。在Java中,控制台输入由从System

.in读取数据来完成。为获得属于控制台的字符流,在BufferedReader对象中包装了System.in。BufferedReader支持缓冲输入流,它最常见的构造函数如下:BufferedReader(ReaderinputReader)其中,inputReader是链接被创建的Buf

feredReader实例的流。Reader是一个抽象类,它的一个具体的子类是InputStreamReader,该子类将字节转换成字符。为获得链接System.in的一个InputStreamReader的对象,使用下面的构造函数:InputStreamReader(InputStreamin

putStream)由于System.in引用了InputStream类型的对象,它可以用于inputStream。综上所述,下面的代码创建了与键盘相连的BufferedReader对象。BufferedReaderbr=newBufferedReader(newInp

utStreamReader(System.in));当该语句执行后,br是通过System.in生成的链接控制台的字符流。[Return]10.2.2读取字符要从BufferedReader读取字符,用read()。例如:intread()throwsIOException

该方法每次执行都从输入流读取一个字符,然后以整型形式返回。当遇到流的末尾时,它返回-1。可以看到,它要引发一个IOException异常。分析教材P238页的例子。[Return]10.2.3读取字符串从键盘读取字符串,使用

readLine()方法,它是BufferedReader类的成员。这个方法的一般使用形式如下。StringreadLine()throwsIOException该方法返回一个String对象。例如,教材P239页的程序可生成一个小文本编辑器。它创建了一个S

tring对象的数组,然后依行读取文本,将文本的每一行存入到数组中。当它读到100行或输入“stop‖时才停止。该例程使用一个BufferedReader类来从控制台读取数据。[Return][Return]10.3向控制台写输出1

0.3.1如何向控制台写输出10.3.2PrintWriter类在前面章节中我们接触过的控制台输出方式有print()和println()等简单途径,这两种方法由PrintStream(System.out引用的对象类型)定义。本节介绍如何进行控制台写输出。10.3.1如何向控制

台写输出因为PrintStream是从OutputStream派生的输出流,它同样实现低级方法write()。write()可用来向控制台写数据,PrintStream定义的write()的最简单形式是:voidwrite(

intbyteval)该方法按照byteval指定的数向文件写字节。尽管byteval定义成整数,但只有低位的8个字节被写入。下面的例程用write()向屏幕输出字符“A‖,然后是新的行://DemonstrateSystem.out.write().

classWriteDemo{publicstaticvoidmain(Stringargs[]){intb;b='A';System.out.write(b);System.out.write('\n');}}一般情况下,我们不用write()来完成向控制台的

输出,尽管在某些场合很有效。相比来说print()和println()方法更容易使用。[Return]10.3.2PrintWriter类尽管Java允许使用System.out向控制台写数据,但建议仅用于调试程序时

。对于实际的程序,Java推荐的向控制台写数据的方法是用PrintWriter流。PrintWriter是基于字符的类,用基于字符的类向控制台写数据使程序更为国际化。PrintWriter定义了多个构造函数,我们所用

到的一个如下。PrintWriter(OutputStreamoutputStream,booleanflushOnNewline)这里,outputStream是OutputStream类的对象,flushOnNewline控制Java是否在println()方法被调用时刷新

输出流。如果flushOnNewline为true,刷新自动发生,若为false,则不发生。PrintWriter支持所有类型(包括Object)的print()和println()方法,这样,我们就可以像用System.out那样用这些方法。如果遇到

不同类型的情况,PrintWriter方法调用对象的toString()方法并打印结果。用PrintWriter向外设写数据,指定输出流为System.out,并在每一新行后刷新流。教材P241页的代码创建了与控制台输出相连

的PrintWriter类。[Return][Return]10.4流类10.4.1字节流10.4.2字符流前面我们简单介绍了控制台的输入/输出,下面我们具体介绍各种流类。Java的流式输入/输出建立在4个抽象类的基础上:InputStream

、OutputStream、Reader和Writer。这些类在前面已经提到过,它们用来创建具体流式子类。尽管程序通过具体子类执行输入/输出操作,顶层的类定义了所有流类的基础通用功能。我们已经知道,InputStream和O

utputStream设计成字节流类,而Reader和Writer为字符流设计。字节流类和字符流类形成分离的层次结构。一般说来,处理字符或字符串时应使用字符流类,处理字节或二进制对象时应使用字节流类。下面分别介绍字节流和字符流类。10.4.1字节流字节流类

为处理字节式输入/输出提供了丰富的环境,一个字节流可以和其他任何类型的对象并用,包括二进制数据。这样的多功能性使得字节流对很多类型的程序都很重要。由于字节流类以InputStream和OutputStream为顶层,我们就从讨论这两个类开始。InputStream是一个定义了Java流

式字节输入模式的抽象类,该类的所有方法在出错条件下引发一个IOException异常。InputStream的各种方法见教材P242页。1.InputStream(输入流)OutputStream是定义了流式字节输出模式的抽象类,该类的所有方法返回一个void值并且在出错情

况下引发一个IOException异常。OutputStream的各种方法见教材P242页。2.OutputStream(输出流)FileInputStream类创建一个能从文件读取字节的InputStream类,它的两个常用的构造函数如下:FileInputStream(Str

ingfilepath)FileInputStream(FilefileObj)它们都能引发FileNotFoundException异常。这里,filepath是文件的全称路径,fileObj是描述该文件

的File对象。分析教材P243页的例子。3.FileInputStream(文件输入流)FileOutputStream创建了一个可以向文件写入字节的类OutputStream,它常用的构造函数如下:FileOutputStream(Stri

ngfilePath)FileOutputStream(FilefileObj)FileOutputStream(StringfilePath,booleanappend)它们可以引发IOException或S

ecurityException异常。这里,filePath是文件的全称路径,fileObj是描述该文件的File对象。如果append为true,文件以设置搜索路径模式打开。FileOutputStream的创建不依赖于

文件是否存在。在创建对象时FileOutputStream在打开输出文件之前创建它。这种情况下,如果试图打开一个只读文件,将会引发一个IOException异常。教材P244~245页的例子用于创建一个

样本字节缓冲器。4.FileOutputStream(文件输出流)RandomAccessFile包装了一个随机访问的文件,注意它不是派生于InputStream和OutputStream,而是实现了基本输入/输出方法的DataInput和DataOutput接口。它同样支持定位

请求,也就是说可以在文件内部放置文件指针。它有两个构造函数:RandomAccessFile(FilefileObj,Stringaccess)throwsFileNotFoundExceptionRandomAccessFile(Stringfilename,Stringacces

s)throwsFileNotFoundException第一种形式,fileObj指定了作为File对象打开的文件的名称;第二种形式,文件名是由filename参数传入的。两种情况下,access都决定允许访问何种文件类型。如果是“r‖

,那么文件可读不可写,如果是“rw‖,文件以读写模式打开。5.RandomAccessFile(随机访问文件类)ByteArrayInputStream是把字节数组当成源的输入流。该类有两个构造函数,每个构造函数需要一个字节数组提供数据源。Byt

eArrayInputStream(bytearray[])ByteArrayInputStream(bytearray[],intstart,intnumBytes)这里,array是输入源。第二个构造函数创建了一个InputStream类,该类从字节数组的子集生成,以st

art指定索引的字符为起点,长度由numBytes决定。教材P246页的例子创建了两个ByteArrayInputStream对象,并用字母表的字节初始化它们。6.ByteArrayInputStream(字节数组输入流)ByteAr

rayOutputStream是一个把字节数组当作输出流的实现。ByteArrayOutputStream有两个构造函数,分别如下:ByteArrayOutputStream()ByteArrayOutputStream

(intnumBytes)在第一种形式里,一个32位字节的缓冲器被生成。第二个构造函数生成一个跟指定numBytes相同位数的缓冲器。缓冲器保存在ByteArrayOutputStream的受保护的bu

f成员里。缓冲器的大小在需要的情况下会自动增加。缓冲器保存的字节数是由ByteArrayOutputStream的受保护的count域保存的。教材P247页的例程演示了ByteArrayOutputStream的

使用。7.ByteArrayOutputStream(字节数组输出流)过滤流(filteredstream)仅仅是底层透明地提供扩展功能的输入流(输出流)的包装。这些流一般由普通类的方法访问。典型的扩展是缓冲,字符转换和原

始数据转换。这些过滤字节流是FilterInputStream和FilterOutputStream。它们的构造函数分别如下。FilterOutputStream(OutputStreamos)FilterI

nputStream(InputStreamis)这些类提供的方法和InputStream及OutputStream类的方法相同。8.过滤字节流对于字节流,缓冲流(bufferedstream)是一种字节流,通过把内存缓冲器连到

输入/输出流而扩展一个过滤流类。该缓冲器允许Java对多个字节同时进行输入/输出操作,提高了程序性能。因为缓冲器可用,所以可以跳过、标记和重新设置流。缓冲字节流类是BufferedInputStream和BufferedOutputStrea

m。PushbackInputStream也可实现缓冲流。详细情况见教材P248~251页的介绍。9.缓冲字节流10.SequenceInputStream(顺序输入流)SequenceInputStream类允许连接多个InputStream流

,这个类的构造不同于任何其他的InputStream。SequenceInputStream构造函数要么使用一对InputStream,要么用InputStream的一个Enumeration,一般形式如下:SequenceIn

putStream(InputStreamfirst,InputStreamsecond)SequenceInputStream(EnumerationstreamEnum)从操作上来讲,该类满足读取完第一个InputStream后转去读取第二个流的读取要求。使用Enumeration的情

况下,它将继续读取所有InputStream流直到最后一个被读完。分析教材P251~252页的示例。PrintStream具有我们在System文件句柄使用过的System.out所有的格式化性能。PrintStream

有两个构造函数:PrintStream(OutputStreamoutputStream)PrintStream(OutputStreamoutputStream,booleanflushOnNewline)当flushOnNewline控制Java每次刷新输出流时,输出

一个换行符(\n)。如果flushOnNewline为true,自动刷新;若为false,刷新不能自动进行。第一个构造函数不支持自动刷新。Java的PrintStream对象支持包括Object在内的各种类型的print()和println()方

法。如果参数不是一个简单类型,PrintStream方法将调用对象的toString()方法,然后打印结果。11.PrintStream(打印流)[Return]尽管字节流提供了处理任何类型输入/输出操作的足够的功能,它们不能直接操作Unicode字符。既然Java的主要目标

是支持“只写一次,到处运行”的信念,包括直接的字符输入/输出支持是必要的。本节将讨论几个字符输入/输出类。如前所述,字符流层次结构的顶层是Reader和Writer抽象类,下面从这两个类开始介绍。10.4.2字符流Reader是定义Java的流式字符输入模式的抽象类,该类的所有方法在出错

情况下都将引发IOException异常。教材P253页给出了Reader类中的各种方法。1.Reader类Writer是定义流式字符输出的抽象类,所有该类的方法都返回一个void值并在出错条件下引发IOException异常。教材P253页给出了Writer类中的各种方

法。2.Writer类FileReader类创建了一个可以读取文件内容的Reader类,它最为常用的构造函数形式如下:FileReader(StringfilePath)FileReader(FilefileObj)每一个都能引发一个FileNotFoundException异常。在这里,fil

ePath是一个文件的完整路径,fileObj是描述该文件的File对象。教材P254页的例子演示了怎样从一个文件逐行读取并把它输出到标准输入流。3.FileReader类FileWriter创建一个可以写文件的Writer类,它最常用的构造函数形式如下FileWriter

(StringfilePath)FileWriter(StringfilePath,booleanappend)FileWriter(FilefileObj)它们可以引发IOException或者SecurityException异常

。在这里,filePath是文件的完全路径,fileObj是描述该文件的File对象。如果append为true,输出是附加到文件尾的。FileWriter类的创建不依赖于文件存在与否。在创建文件之前,FileWriter将在创建对象时打开它来作为输出。如果试图打开一个只读文件,将引发

一个IOException异常。教材P254~255页的例子是对前面讨论FileOutputStream时用到例子的字符流形式的版本。它创建了一个样本字符缓冲器,开始生成一个String,然后用getChars(

)方法提取字符数组。4.FileWriter类CharArrayReader是一个将字符数组作为源的输入流的实现。该类有两个构造函数,每一个都需要一个字符数组提供数据源。CharArrayReader(chararray[])CharArrayReader(char

array[],intstart,intnumChars)这里,array是输入源。第二个构造函数从字符数组的子集创建了一个Reader,该子集以start指定的索引开始,长度为numChars。教材P255页的例子用到了上述CharArrayReade

r的两个构造函数。5.CharArrayReader类CharArrayWriter实现了以数组作为目标的输出流。CharArrayWriter有两个构造函数。CharArrayWriter()CharArrayW

riter(intnumChars)第一种形式,创建了一个默认长度的缓冲器;第二种形式,缓冲器长度由numChars指定。缓冲器保存在CharArrayWriter的buf成员中。缓冲器大小在需要的情况下可以自动增长。缓冲器保持的字符

数包含在CharArrayWriter的count成员中。buf和count都是受保护的域。教材P256页的例子演示了CharArrayWriter的使用。6.CharArrayWriter类BufferedReader通过缓冲输入提

高性能,它有两个构造函数:BufferedReader(ReaderinputStream)BufferedReader(ReaderinputStream,intbufSize)第一种形式创建一个默认缓冲器长度的缓冲

字符流;第二种形式,缓冲器长度由bufSize传入。和字节流的情况相同,缓冲一个输入字符流,Java同样支持并提供缓冲器中流内的反向移动机制。为支持这点,BufferedReader实现了mark()和reset()

方法,并且BufferedReader.markSupported()返回true。教材P257页的例子改写了前面的BufferedInputStream例子,它用一个BufferedReader字符流而不是用一个缓冲字节流。7.BufferedReader类Buffere

dWriter是一个增加了flush()方法的Writer。flush()方法可以用来确保数据缓冲器确实被写到实际的输出流。用BufferedWriter可以通过减小数据被实际地写到输出流的次数而提高程序的性能。BufferedWriter类有下面的

两个构造函数:BufferedWriter(WriteroutputStream)BufferedWriter(WriteroutputStream,intbufSize)第一种形式创建了使用默认大小缓冲器的缓冲流;第二种形式中,缓冲器大小是由b

ufSize参数传入的。8.BufferedWriter类PushbackReader类允许一个或多个字符被送回输入流,这使你可以对输入流进行预测。下面是它的两个构造函数:PushbackReader(ReaderinputStream)

PushbackReader(ReaderinputStream,intbufSize)第一种形式创建了一个允许单个字节被推回的缓冲流;第二种形式,推回缓冲器的字符流大小由bufSize参数传入。PushbackReader提供了unread()方法,该方法返回一个或多

个字符到调用的输入流。它有下面的三种形式:voidunread(intch)voidunread(charbuffer[])voidunread(charbuffer[],intoffset,intnumChars)第一种形式推回ch传

入的字符。它是被并发调用的read()返回的下一个字符;第二种形式返回buffer中的字符;第三种形式推回buffer中从offset开始的numChars个字符。如果在推回缓冲器为满的条件下试图返回一个字符,一个IOE

xception异常将被引发。分析教材P259页的例子。9.PushbackReader类PrintWriter本质上是PrintStream的字符形式的版本,它提供格式化的输出方法print()和println(

)。PrintWriter有下面的4个构造函数:PrintWriter(OutputStreamoutputStream)PrintWriter(OutputStreamoutputStream,booleanflushOnNewline)PrintWriter(Writ

eroutputStream)PrintWriter(WriteroutputStream,booleanflushOnNewline)flushOnNewline控制Java是否在每次输出换行符(\n)时刷新输出流。若flushOnNewline

为true,刷新自动发生;若为false,不进行自动刷新。第一个和第三个构造函数不能进行自动刷新。10.PrintWriter类[Return][Return]10.5文件的读写10.5.1如何进行文件读写10.5.2File类10.5.3目录前面我们在许多地方都已经接触到了文件的读写问题,

但不是很系统。在本节里,将对文件的读写问题作一个集中的说明介绍。10.5.1如何进行文件读写Java为编程人员提供了一系列的读写文件的类和方法。在Java中,所有的文件都是字节形式的。Java提供了从文件读写字节的方法,而且允许在字符形式的对象中使

用字节文件流。这些在前面已作描述。两个最常用的流类是FileInputStream和FileOutputStream,它们生成与文件链接的字节流。为打开文件,只需创建这些类中某一个类的一个对象,在构造函数中以参数形式指定文件的名称。这两

个类都支持其他形式的重载构造函数。下面是我们将要用到的形式:FileInputStream(StringfileName)throwsFileNotFoundExceptionFileOutputStream(StringfileName)thro

wsFileNotFoundException这里,fileName指定需要打开的文件名。当你创建了一个输入流而文件不存在时,引发FileNotFoundException异常。对于输出流,如果文件不能生成,则

引发FileNotFoundException异常。如果一个输出文件被打开,所有原先存在的同名的文件被破坏。当对文件的操作结束后,需要调用close()来关闭文件。该方法在FileInputStream和FileOutputStream中都有定义,具体形式如下。voidclose()throws

IOException为读文件,可以使用在FileInputStream中定义的read()方法,形式如下。intread()throwsIOException该方法每次被调用,它仅从文件中读取一个字节并将该字节以整数形式返回。当读

到文件尾时,read()返回-1。该方法可以引发IOException异常。教材P260页的程序使用read()来输入和显示文本文件的内容。该文件名以命令行形式指定。教材P261页的例子用write()拷贝一个文本文件。[Retur

n]10.5.2File类尽管java.io定义的大多数类是实行流式操作的,但File类不是,它直接处理文件和文件系统。也就是说,File类没有指定信息怎样从文件读取或向文件存储;它描述了文件本身的属性。File对象用来获取或处理与

磁盘文件相关的信息,例如权限、时间、日期和目录路径。此外,File还浏览子目录层次结构。很多程序中文件是数据的根源和目标。尽管它们在小应用程序中因为安全原因而受到严格限制,文件仍是存储固定和共享信息的主要资源。Java中的目录当成File对待,它具有附加的属性

:一个可以被list()方法检测的文件名列表。下面的构造函数可以用来生成File对象。File(StringdirectoryPath)File(StringdirectoryPath,Stringfilename)File(FiledirOb

j,Stringfilename)在这里,directoryPath是文件的路径名,filename是文件名,dirObj是一个指定目录的File对象。教材P263页的例子演示了几个File方法的使用。[Return]10.5.3目录目录实质上是一个包含其他文件和路径列

表的File类。当创建一个File对象并且它是目录时,isDirectory()方法返回ture。在这种情况下,可以调用该对象的list()方法来提取该目录内部其他文件和目录的列表。该方法有两种形式,第一种形式如下:String[

]list()文件列表在一个String对象数组中返回。教材P264页程序说明如何用list()来检查一个目录的内容。如果总是希望能够限制由list()方法返回的文件数目,使它仅返回那些与一定的文件名方式或者过滤(filter)相匹配的文件。

为达到这样的目的,必须使用list()的第二种形式。String[]list(FilenameFilterFFObj)该形式中,FFObj是一个实现FilenameFilter接口的类的对象。FilenameFi

lter仅定义了一个方法accept(),该方法被列表中的每个文件调用一次。它的一般形式如下:booleanaccept(Filedirectory,Stringfilename)当被directory指定的目录中的

文件(也就是那些与filename参数匹配的文件)包含在列表中时,accept()方法返回true;当这些文件没有包括在列表中时,accept()返回false。教材P265页显示的OnlyExt类实现FilenameFilter接口,它被用来修饰前面的程序,限制由list()返回的文件名的可见度

,把对象被创建时以指定扩展名结束的文件归档。1.使用FilenameFilter接口在Java2中,增加了list()方法的一个变化形式,名为listFiles()。读者将会发现该方法很有用,其使用形式如下:File[]listFiles()File[

]listFiles(FilenameFilterFFObj)File[]listFiles(FileFilterFObj)上述三种形式以File对象矩阵的形式返回文件列表,而不是用字符串形式返回。第

一种形式返回所有的文件,第二种形式返回满足指定FilenameFilter接口的文件。除了返回一个File对象数组,这两个listFiles()方法就像list()方法一样工作。第三种listFiles()形式返回满足指定Fi

leFilter的路径名的文件。FileFilter只定义了一个accept()方法,该方法被列表中的每个文件调用一次。它的一般形式如下:booleanaccept(Filepath)如果文件被包括在列表中(即与path参数

匹配的文件),accept()方法返回true;如果不被包括,则返回false。2.listFiles()方法另外两个有用的File类的方法是mkdir()和mkdirs()。mkdir()方法创建了一个目录,创建成功返回true,创建失败返回false。创建失败是指

File对象指定的目录已经存在,或者是因为整个路径不存在而不能创建目录。创建路径不存在的目录,用mkdirs()的方法,它创建目录以及该目录所有的父目录。3.创建目录[Return][Return]第11章常用工具包

和类11.1Java常用工具包11.2简单类型包装器11.3Object类11.4Class类11.5Package类11.6Runtime类11.7System类11.8Math类本章将介绍Java语言中的常用工具包和类。这些工具包和类在编程过程中经常用到,有些甚至是必不可少的,如

java.lang包、System类等。熟练掌握本章所介绍的系统工具包和系统类,将为进一步学习Java编程打下良好的基础。[Return]11.1Java常用工具包在Java中,系统提供了大量的包以满足面向对象、网络化、编程的需要。本节将对一些常用的工具包作简要介绍。11.1.1JavaAPI包1

1.1.2java.lang11.1.1JavaAPI包在Java1.0被发布时,它包括一系列的8个包,被称作核心应用编程接口(API)。其中许多包在前面的章节中我们已介绍过,而且编程时经常用到。现在,每当发布一个Java新版

本时,都会增加一些核心API,当然还有其他一些有用的工具包。在J2SE1.2版本中,60个包总共提供了1781个公共类和接口;在J2SE1.3版本中,70多个包提供的公共类和接口超过2100个;在J2SE1.4版本中,1

20多个包提供了总数超过2600个的公共类和接口。以下是一些经常用到的工具包:l语言包(java.lang)提供的支持包括字符串处理、多线程处理、例外处理、数学函数处理等,可以用它简单地实现Java程序的运行平台;l输入输出包(java.io)使用统一的“流”模型来实现所有格式的I/O

,包括文件系统、网络传输、输入/出设备等;l实用程序包(java.util)提供的支持包括哈希表、堆栈以及时间和日期等;l抽象窗口工具集包(java.awt)实现了不同平台上的计算机图形用户接口部件,包括窗口、菜单、滚动条、对话框等,使得Java可以移植到不同的运行环境;l网络

包(java.net)支持Internet的TCP/IP协议,提供了与Internet的接口。它支持URL连接,WWW的即时访问,并且简化了C/S(客户/服务器)模型的程序设计。另外比较常见的Java包还有:java

.math、java.sql、java.text、java.rmi、java.beans以及java.security等。教材P268页中的表11-1,列出了所有被Java2定义的Java核心API包并对它们作了简要描述。[Return]11.1.2java.lang其实,我们前面章

节编写的每个Java程序都与java.lang有关。但之所以没说明,是因为java.lang被自动导入所有的程序。它所包含的类和接口对所有Java程序都是必要的,是Java中最广泛使用的包。教材P269页给出了jav

a.lang中主要包括的类。另外,还有两个由Character定义的类:Character.Subset和Character.UnicodeBlock,它们是在Java2中新增加的。java.lang中也定义了如下的接口:lClone

able接口lComparable接口lRunnable接口其中,Comparable接口是在Java2中新增加的。java.lang中的几个类包含了过时的方法,其中的大多数可以追溯到Java1.0。在Java2中仍然提供了这些方法,用于支持逐渐减少的老程序,而这些方法在

新程序中不被推荐使用。大多数过时的方法出现在Java2之前,因此我们不准备讨论这些方法。[Return][Return]11.2简单类型包装器11.2.1Number11.2.2DoubleFloat11.2.3Byte、Short、Integer和Long11.2.4Character11.

2.5Boolean类11.2.6关于Vector11.2.7Void和Process在教材2.3节中我们提到过,Java使用简单的类型,如整型(int)、字符(char)等数据类型不是对象层次结构的组成部分,它们通过值传递给方法而不能直接通过引

用传递。而且也没有办法使两种方法对整型(int)引用同一实例(sameinstance)。但是,有时需要对这些简单的类型建立对象表达式,如处理对象的枚举类,要将简单类型存储到这些类中的一个,需要在类中包装简单类型。为了满足这种需要,Java提供了与每一个简单类型相对应的类。从本

质上讲,这些类在类中包装简单类型,因此通常被称作类型包装器(wrappers)。[Return]11.2.1Number抽象类Number定义了一个由包装数字类型字节型(Byte)、短整型(Short)、整型(Int)、长整型(Long)、浮点型(Float)和双精度型(Double)的类实现的超

类。Number有返回上面不同数字格式的对象值的抽象方法。也就是,doubleValue()方法返回双精度(Double)值,floatValue()方法返回浮点(Float)值等。这些方法如下:bytebyteValue()dou

bledoubleValue()floatfloatValue()intintValue()longlongValue()shortshortValue()Number有6个具体的子类包含了6种数字类型的显式值:双

精度型(Double)、浮点型(Float)、字节型(Byte)、短整型(Short)、整型(Integer)和长整型(Long)。11.2.2DoubleFloat双精度(Double)和浮点(Float)分别是对类型double和类型float的浮点值的包装器。浮点(

Float)构造函数如下所示:Float(doublenum)Float(floatnum)Float(Stringstr)throwNumberFormatException如上可见,浮点(Float)对象可以由类型float或类型d

ouble的值创建。它们也能由浮点数的字符串表达式创建。双精度(Double)的构造函数如下。Double(doublenum)Double(Stringstr)throwNumberFormatException双精度(Double)对象可以被双精度(Double)值或包含

了浮点值的字符串创建。浮点(Float)和双精度(Double)都定义了一些常数,这些常数见教材P270页。由浮点(Float)定义的方法见教材P271页表11-2。由双精度(Double)定义的方法见教材P271页表11-3。[Return]11.2.3Byte、

Short、Integer和LongByte、Short、Integer和Long类分别是字节型(byte)、短整型(short)、整型(int)和长整型(long)整数类型的包装器。它们的构造函数分别如下:Byte(bytenum)

Byte(Stringstr)throwNumberFormatExceptionShort(shortnum)Short(Stringstr)throwNumberFormatExceptionInteger(in

tnum)Integer(Stringstr)throwNumberFormatExceptionLong(longnum)Long(Stringstr)throwNumberFormatException正如上所示,这些对象可由数值或含有有效整数值的字

符串创建。下面讨论如何进行数字和字符串的转换问题。在程序设计中,一个最常见的问题是将一个数字的字符串表达式转换成内部的二进制格式。Java为此提供了一个方便的方法去完成这项任务。Byte、Short、Integer和Long类分别提供了parseByte()、parse

Short()、parseInt()和parseLong()方法。这些方法返回与调用它们的数值字符串相应的字节(byte)、短整型(short)、整型(int)和长整型(long)值。在Float和Double类中也有相似的方法。教材P274页的

程序说明了parseInt()方法的使用。[Return]11.2.4Character字符(Character)是围绕字符型(char)的一个简单的包装器。字符(Character)的构造函数如下:Character(charch)这里,ch指定了被创建的字符(Character

)对象所包装的字符。调用如下的charValue()方法可以获得包含在字符(Character)对象中的字符型(char)值。charcharValue()调用的结果返回字符。字符(Character)类定义的常数见教材P275页。字符(Ch

aracter)包括了几个静态方法,这些方法将字符分类并改变其大小写。这些方法见教材P275~276页表11-4。关于这些方法的使用见教材P276页的示例。[Return]11.2.5Boolean类

Boolean是一个围绕布尔(boolean)值的非常细小的包装器,主要用在通过引用传递布尔(boolean)变量的场合。它包含了常数TRUE和FALSE,这些常数定义了布尔(Boolean)对象的真与假。Boo

lean也定义了TYPE域,它是boolean的Class对象。在Boolean中定义了如下的构造函数:Boolean(booleanboolValue)Boolean(StringboolString)在第一种形式中,boolValue要么是true,要么是false

。在第二种形式中,如果在boolString中包含了字符串“true‖(无论是大写形式还是小写形式),则新的布尔(Boolean)对象将为真,否则为假。Boolean定义了教材P277页表11-5中所列出的方法。[Return]11.2.6关于Vector向量(Vector)是由j

ava.util包提供的,它允许提供不同类型元素共存的变长数组。Vector类的特点如下。l需要处理的对象数目不定,序列中的元素都是对象,或者可以表示成对象。l需要将不同类的对象组合成一个数据序列。l需要做频繁的对象序列中元素的插入和删除。l经常需要定位序列中的对象或其他查

找操作。l在不同的类之间传递大量的数据。其局限性是对象不能是简单类型,但这可以通过数组(Array)实现。(1)创建向量类的对象:publicVector(intinitCapacity,intcapacityIncrement);其中,i

nitCapacity表示刚创建时Vector序列包含的元素数目,capacityIncrement表示如果向Vector序列中加元素,一次性加多少个。(2)向向量序列中加元素:addElement(Objectobj)insertElement(Objectobj,intindex)

(3)修改和删除向量序列中元素:setElementAt(Objectobj,intindex)removeElement(Objectobj)removeElementAt(intindex)removeA

llElements()(4)查找向量序列中元素:elementAt(intindex)contains(Objectobj)lastIndexOf(Objectobj,intstart_index)[Return][Return]

11.2.7Void和ProcessVoid类有一个TYPE域,该域保存对类型void的Class对象的引用。这样做将不创建类的实例。抽象类Process封装了一个进程(process),也就是一个正在执行的程序。它主要被当作由Runtime类中的exec()方法所创建的对象的类

型的超类。Runtime类将在后面介绍。在抽象类Process中,包含了教材P278页表11-6中所列出的抽象方法。[Return]11.3Object类11.3.1Object11.3.2使用clone()和Cloneable接口这里大致

介绍一下Object类。在Java中,定义有一种特殊的类Object,其他所有的类都是Object的子类。也就是说,Object是所有其他类的超类。这意味着一个Object类型的引用变量可以引用其他任何一个类的对象。同样,因为

数组像类一样执行,Object类型变量可以引用任何数组。[Return]11.3.1ObjectObject定义了教材P279页表11-7的方法,意味着它们可以被用于任何对象。这些类方法对于每一个对象都是可引用的。其中,getClass()、notify()、notify

All()和wait()方法被定义成final。你可以重载除这些方法以外的其他方法。注意这两个方法:equals()和toString()。equals()方法比较两个对象的内容。如果对象是相等的,它返回true,

否则返回false。toString()方法返回一个包含调用它的对象描述的字符串。而且,该方法在对象使用println()输出时自动调用。很多类都重载该方法,这样做使它们生成它们创建对象类型的一个特殊描述。11.3.2使用clon

e()和Cloneable接口由Object类定义的绝大部分方法在本书其他部分讨论。而一个特别值得关注的方法是clone()。clone()方法创建调用它的对象的一个复制副本。只有那些实现Cloneable接口的类能被复制。Cloneable接口没有定义成

员。它通常用于指明被创建的一个允许对对象进行位复制(也就是对象副本)的类。如果试图用一个不支持Cloneable接口的类调用clone()方法,将引发一个CloneNotSupportedException异常。当一个副本被创建时,并没有调用被复制对象

的构造函数。副本仅仅是原对象的一个简单精确的拷贝。复制是一个具有潜在危险的操作,因为它可能引起不是你所期望的副作用。例如,假如被复制的对象包含了一个称为obRef的引用变量,当副本创建时,副本中的obRef如同原对象中的obRef一样引

用相同的对象。如果副本改变了被obRef引用的对象的内容,那么对应的原对象也将被改变。这里是另一个例子。如果一个对象打开一个I/O流并被复制,两个对象将可操作相同的流。而且,如果其中一个对象关闭了流,而另一个对象仍试图对I/O流进行写操作的话,将导致错误。由于复制可能引起问题,因此在Object

内,clone()方法被说明为protected。这就意味着它必须或者被由实现Cloneable的类所定义的方法调用,或者必须被那些类显式重载以便它是公共的。分析教材P280~281页的例子。[Retu

rn]11.4Class类Class封装对象或接口运行时的状态。当类被加载时,类型Class的对象被自动创建。不能显式说明一个类(Class)对象。一般地,通过调用由Object定义的getClass()方法来获得一个类(Class)对象。由C

lass定义的一些最常用的方法见教材P282页表11-8所示。由Class定义的方法经常用在需要知道对象的运行时类型信息的场合。如上表中所说明的那样,由Class提供的方法确定关于特定的类的附加信息。例如它的公共构造函数,域以及方法。教材P283页的程序说明了getCla

ss()(从Object继承的)和getSuperclass()方法(从Class继承的)。说明:关于ClassLoader的问题:抽象类ClassLoader规定了类是如何加载的。应用程序可以创建扩展ClassLoader的子类,实现它的方法。这样做允许使用不同于通常由Jav

a运行时系统加载的另一些方法来加载类。由ClassLoader定义的一些方法见教材P283页表11-9。[Return]11.5Package类在Java2中,增加了一个称为Package的类,这个类封装了与包有关的版本数据。关于包版本信息,由于包的增值以及由于Java程序

可能需要知道哪些包版本可以利用,而变得更加重要。Package中定义的方法见教材P284页表11-10所列。教材P284页的程序通过显示程序当前已知的包而说明了Package的用法。[Return]11.6Runtime类11.6.1内存管理11.6.2执行其他的程序[Return]Runtime

类封装了运行时环境。一般情况下,不实例化一个Runtime对象,但是可以通过调用静态方法Runtime.getRuntime()而获得对当前Runtime对象的引用。一旦获得了对当前对象的引用,就可以调用几个控制Java虚拟机的状态和行为的方法。小应用程序(Applets)和其他不

可信赖的编码由于没引起一个安全异常(SecurityException),故不能调用任何的Runtime方法。教材P285页表11-11给出了由Runtime定义的方法。Java2中不建议使用方法runFinalizersOnExit(),这种方法是在Java1.1中增加的,因

为它现在被认为是一种不稳定的方法。下面让我们来看一看Runtime类的两个最普遍的用法:内存管理和执行附加进程。11.6.1内存管理尽管Java提供了自动垃圾回收,有时也想知道对象堆的大小以及它还剩下多少。可以利用这些信息检验你的代码的效率,或估计对某些类型,有多少

对象可以被实例化。为了获得这些值,可以使用totalMemory()和freeMemory()方法。正如我们在前面章节提及的,Java的垃圾回收器周期性地运行将不再使用的对象放入回收站。然而,有时想在收集器的下一个指定循环之前收集被丢弃的

对象。如果这样可通过调用gc()方法按照要求运行垃圾回收器。一个比较好的策略是调用gc()方法,然后再调用freeMemory()方法以获得内存使用的底线。接着执行你的程序,并再一次调用freeMemory()方法看分配了多少内存。教材P286页的例子说明

了这个思想。[Return]11.6.2执行其他的程序在可靠的环境中,可以在你的多任务操作系统中使用Java去执行其他特别繁重的进程(也即程序)。exec()方法的几种形式允许命名想运行的程序以及它们的输入参数。exec()方法返回一个Process对象,这个对象可以被

用来控制你的Java程序如何与这个正在运行的新进程相互作用。因为Java可以运行在多种平台和多种操作系统的情况下,exec()方法本质上是依赖于环境的。教材P287页的第一个例子使用exec()方法装入Window的简单文本编辑器NotePad。显而易见,这个例子必须在

Windows操作系统下运行。exec()方法有几个形式可用,而在本例子中展示的是最常用的一种。在新程序开始运行之后,由exec()方法返回的Process对象可以被Process方法使用。可以使用destroy()方法杀死子进程。waitFor

()方法暂停程序直至子进程结束。当子进程结束后,exitValue()方法返回子进程返回的值。如果没有问题发生,它通常返回0。教材P287页的第二个例子是前面关于exec()方法例子的改进版本。例子被修改为等待直至正在运行的进程退出。[Return][Return]11.7System类11.7.

1使用currentTimeMillis()方法11.7.2使用arraycopy()方法11.7.3环境属性System类保存静态方法和变量的集合。标准的输入,输出和Java运行时错误输出存储在变量in,out和err中。由System类定义的方

法见教材P288页表11-12中所示。注意,当所做操作为安全方式所不允许时,许多方法引发一个安全异常(SecurityException)。同样,Java2不赞成使用runFinalizersOnEx

it()方法。该方法是在Java1.1中增加的,同时也被证明是不可靠的。下面让我们看一看System类的一些用法。11.7.1使用currentTimeMillis()方法可以发现System类的一个特别有意义的用法是:利用currentTimeM

illis()方法来记录程序的不同部分的执行时间。currentTimeMillis()方法返回自从1970年1月1号午夜起到现在的时间,时间单位是毫秒。如果要记录程序中一段有问题程序的运行时间可以在这段程序开始之前存储当前时间,在该段程序结束之际再次调用currentTim

eMillis()方法。执行该段程序所花费的时间为其结束时刻的时间值减去其开始时刻的时间值。教材P289页的程序说明了这一点。[Return]11.7.2使用arraycopy()方法使用arraycopy()方法可以将一个任意类型的数组快速地从一个地方复制到另一个地

方。这比使用Java普通语句编写的循环算法要快得多。教材P290页是一个用arraycopy()方法复制两个数组的例子。首先,将数组a复制给数组b;接下来,数组a中的所有元素向后移一位,然后数组b中元素向前

移一位。[Return]11.7.3环境属性教材P290页的属性在Java2的所有环境中可以使用。可以通过调用System.getProperty()方法来获得不同环境变量的值。例如,下面的程序可显示当前用户目录的路径。classShowUserDir{public

staticvoidmain(Stringargs[]){System.out.println(System.getProperty("user.dir"));}[Return][Return]11.8Math类11.8.1超越函数11.8.2指数

函数11.8.3舍入函数11.8.4其他数学方法Math类提供了进行复杂数学运算的一些途径,它保留了所有用于几何学、三角学以及几种一般用途方法的浮点函数。Math定义了两个双精度(double)常数:E(近似为2.72)和PI(近似为3.14)。11.8.

1超越函数下面的三种方法对一个以弧度为单位的角度接收一个双精度(double)参数并且返回它们各自的超越函数的结果。staticdoublesin(doublearg):返回由以弧度为单位由arg指定的角度的

正弦值;staticdoublecos(doublearg):返回由以弧度为单位由arg指定的角度的余弦值;staticdoubletan(doublearg):返回由以弧度为单位由arg指定的角度的正切值。下面的方法将超越函数的结果作为一个参数,按弧度返回产生这个结果的角

度值。它们是其非弧度形式的反。staticdoubleasin(doublearg):返回一个角度,该角度的正弦值由arg指定;staticdoubleacos(doublearg):返回一个角度,该角度的余弦值由arg指定;staticdouble

atan(doublearg):返回一个角度,该角度的正切值由arg指定;staticdoubleatan2(doublex,doubley):返回一个角度,该角度的正切值为x/y。[Return][Return]11.8.2指数函数M

ath类定义了如下的指数方法。staticdoubleexp(doublearg):返回arg的estaticdoublelog(doublearg):返回arg的自然对数值staticdoublepow(doubley,doublex):返回以y为底数,以x为指数的幂值;例如p

ow(2.0,3.0)返回8.0staticdoublesqrt(doublearg):返回arg的平方根[Return]11.8.3舍入函数Math类定义了一些提供不同类型舍入运算的方法,见教材P291~292页表11-13中所列。11.8.4其他数学方法除了给出的方法,Math还定

义了下面这些方法。staticdoubleIEEEremainder(doubledividend,doubledivisor)staticdoublerandom()staticdoubletoRadians(doubleangle)staticdo

ubletoDegrees(doubleangle)IEEEremainder()方法返回dividend/divisor的余数。random()方法返回一个伪随机数,其值介于0与1之间。在大多数情况下,当需要产生随

机数时,通常用Random类。toRadians()方法将角度的度转换为弧度。而toDegrees()方法将弧度转换为度。教材P292页是一个说明toRadians()和toDegrees()方法的例子。[Return

]

小橙橙
小橙橙
文档分享,欢迎浏览!
  • 文档 25747
  • 被下载 7
  • 被收藏 0
相关资源
广告代码123
若发现您的权益受到侵害,请立即联系客服,我们会尽快为您处理。侵权客服QQ:395972555 (支持时间:9:00-21:00) 公众号
Powered by 太赞文库
×
确认删除?