《配套课件:《Java程序设计教程》》由会员分享,可在线阅读,更多相关《配套课件:《Java程序设计教程》(989页珍藏版)》请在金锄头文库上搜索。
1、JAVA程 序 设计本章学习目标:熟悉Java语言的产生、特点以及编写规范掌握Java开发环境和开发工具的使用掌握编写简单的Java程序掌握创建、编译和运行Java程序的基本步骤第一章第一章Java概述概述第第1节节partJava简介Java是一种可用于编写跨平台应用软件的面向对象程序设计语言,也是JavaSE(标准版)、JavaEE(企业版)和JavaME(微型版)三种平台的总称。由于Java具有“一次编写,多处应用(WriteOnce,RunAnywhere)”的特点,使其广泛应用于个人PC、数据中心、游戏控制台、科学超级计算机、移动电话和互联网等不同的媒介。Java具有卓越的通用性、高
2、效性、平台移植性和安全性,为其赢得了大量的爱好者和专业社群组织。J av a简介本节概述Java自1995诞生,至今已经20多年的历史。Java的名字来源于印度尼西亚爪哇岛的英文名称。该地因盛产咖啡而闻名,因此,JAVA的图标也正是一杯正冒着热气的咖啡,如图1.1所示。Java来自于Sun公司的一个“绿色项目(GreenProject)”,其原先的目的是为家用消费电子产品开发一个分布式代码系统,目标是把E-mail发给电冰箱、电视机等家用电器,对这些电器进行控制以及信息交流。詹姆斯高斯林(JamesGosling)加入到该项目小组。开始,项目小组准备采用C+,但C+太复杂,安全性差,最后高斯林
3、用C+开发了一种新的语言Oak(橡树),这就是Java的前身,在1994年Oak被正式更名为Java。1.1.1.1.1 1J av a起源Java起源詹姆斯高斯林(JamesGosling)也被人们亲切的称为Java之父,如图1.2所示。从1995年Java诞生以来,Java先后经历了8个版本的变更,当然版权的所有者也一度由Sun变为Oracle。如表1-1所示为Java发展过程中几个重要的里程碑。1.1.1.1.2 2J av a发展史Java发展史表1-1Java发展史Java语言之所以受到广大编程爱好者的青睐,是因为Java有着以下几方面的语言优势。1.1.1.1.3 3J av a的
4、特点Java的特点资源开源性跨平台性健壮、安全性高性能性简单性面向对象动态性多线程支持分布式网络应用第第2节节partJava体系(JavaEnterpriseEdition,Java企业版)JavaEE是企业级解决方案,支持开发、部署和管理等相关复杂问题的体系结构,主要用于分布式系统的开发、构建企业级的服务器应用,例如,电子商务网站、ERP系统等。JavaEE在SE基础上定义了一系列的服务、API和协议等,如Servlet、JSP、RMI、EJB、JavaMail、JTA等。JavaEE(JavaMicroEdition,Java微型版)JavaME是各版本中最小的,是在SE基础上进行裁剪和
5、高度优化,目的是在小型的受限设备上开发和部署应用程序,例如,手机、PDA、智能卡、机顶盒、汽车导航或家电系统等。JavaME遵循微型开发规范和技术,如MIDLet、CLDC、PersonalProfile等。JavaME1.2.1.2.1 1Java应用平台(JavaStandardEdition,Java标准版)JavaSE是Java技术的基础,适用于桌面系统应用程序(Application)、网页小程序(Applet)以及服务器程序的开发。JavaSE主要包括Java语言核心技术和应用,如数据库访问、I/O、网络编程、多线程等。JavaSE1999年,在美国旧金山的JavaOne大会上,S
6、un公司公布了Java体系架构,该架构根据不同级别的应用开发划分了三个版本:Java应用平台1.2.1.2.1 1Java应 用平台Java应用平台JDK(JavaDevelopmentKit,Java开发工具包)是Sun公司提供的一套用于开发Java程序的开发工具包。JDK提供编译、运行Java程序所需要的各种工具及资源,包括Java开发工具,Java运行时环境,以及Java的基础类库。1、JDKJRE(JavaRuntimeEnvironment,Java运行时环境)是运行Java程序所依赖的环境的集合,包括类加载器、字节码校验器、Java虚拟机、JavaAPI。JRE已包含在JDK中,但
7、是如果仅仅是为了运行Java程序,而不是从事Java开发,可以直接下载安装JRE。2、JRESDK(SoftwareDevelopmentKit,开发工具包)在版本1.2到1.4时,被称为JavaSDK,在某些场合下,还可以看到执行过时的术语。4、SDKJVM(JavaVirtualMachine,Java虚拟机)是一个虚构出来的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,只需将Java语言程序编译成在Java虚拟机上运行的目标代码(.class
8、,字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。3、JVM1.2.1.2.2 2专 有 名词专有名词1.2.1.2.3 3Java跨 平 台原理JVM在具体的操作系统之上运行,其本身具有一套虚拟指令,但它通常是在软件上而不是在硬件上实现。JVM形成一个抽象层,将底层硬件平台、操作系统与编译过的代码联系起来。Java字节码的格式通用,具有跨平台特性,但这种跨平台建立在JVM虚拟机的基础之上,只有通过JVM处理后才可以将字节码转换为特定机器上的机器码,然后在特定的机器上运行。JVM跨平台特性如图1.5所示:Java跨
9、平台原理1.2.1.2.3 3Java跨 平 台原理JVM虚拟机是Java程序具有“一次编译,多处应用”的特性,如图1.6所示。首先,Java编译器将Java源程序编译成Java字节码;其次,字节码在本地或通过网络传达给JVM虚拟机;再次,JVM对字节码进行即时编译或解释执行后形成二进制的机器码;最后,生成的机器码可以在硬件设备上直接运行。JVM执行时将在其内部创建一个运行时环境,每次读取并执行一条Java语句会经过三个过程:装载代码、校验代码和执行代码,如图1.7所示。1.2.1.2.3 3Java跨 平 台原理Java字节码有两种执行方式:字节码有两种执行方式:解释执行方式。JVM通过解释
10、器将字节码逐条读入,逐条解释翻译成对应的机器指令。很显然,这种执行方式虽灵活但执行速度会比较慢。为了提高执行速度,引入了JIT(Just-in-timecompilation)技术。即时编译方式(即JIT编译)。当JIT编译启用时(默认是启用的),JVM将解释后的字节码文件发给JIT编译器,JIT编译器将字节码编译成机器代码,并把编译过的机器码保存起来,已备下次使用。为了加快执行速度,JIT目前只对经常使用的热代码进行编译。通常采用的是解释执行方式,由于JVM规格描述具有足够的灵活性,使得将字节码翻译为机器代码的工作具有较高的效率。对于那些对运行速度要求较高的应用程序,解释器可将Java字节码
11、即时编译为机器码,从而很好地保证了Java代码的可移植性和高性能。第第3节节partJDK工具“工欲善其事,必先利其器。”在开发的第一步,必须搭建起开发环境。本书以JavaSEDevelopmentKit8在Windows操作系统的下载、安装作为范例,讲解整个Java开发环境的安装及配置过程。JDK工具本节概述1.3.1.3.1 1JDK介绍JDK(JavaDevelopmentKit)是SunMicrosystems公司针对Java开发人员发布的免费软件开发工具包。JDK是整个Java的核心,是学好Java的第一步,如果没有JDK,则无法安装或者运行Eclipse。普通用户并不需要安装JDK
12、来运行Java程序,而只需要安装JRE(JavaRuntimeEnvironment),而程序开发者必须安装JDK来编译、调试程序。从Sun公司的JDK5.0开始,提供了泛型等非常实用的功能,其版本也不断更新,运行效率得到了非常大的提高,其环境变量也可以不需要手动配置。JDK介绍Java虚拟机可以运行在各种操作系统平台上,负责解析和执行Java程序。Java运行环境提供了最基础的Java类以及各种实用类。如java.lang、java.io、java.util、java.awt、java.swing和java.sql包中的类都位于JDK类库中。Java继承类库(rt.jar)1.3.1.3.1
13、 1JDK介绍这些开发工具都是可执行程序,主要包括:javac.exe(编译工具)、java.exe(运行工具)、javadoc.exe(生成JavaDoc文档的工具)和jar.exe(打包工具)等。Java开发工具JDK包含一批用于Java开发的组件,其中包括以下几部分:1.3.1.3.2 2JDK安 装配置下述内容分别介绍JDK下载、安装及配置。JDK安装配置1.下载下载JDK进入Oracle官方网站可以下载JDK的最新版本。Oracle官方网站:http:/JDK8的下载地址:http:/ 2JDK安 装配置2.安装安装JDK【步骤1】运行JDK的安装文件,进入JDK的安装向导界面,如图
14、1.9所示。【步骤2】单击“下一步”按钮,进入定制安装界面,如图1.10所示。可以单击右下方的“更改”按钮,设置JDK的安装路径,否则进入默认安装路径。1.3.1.3.2 2JDK安 装配置【步骤3】单击“下一步”按钮,进入安装进度界面,如图1.11所示。【步骤4】JDK安装进度完成,进入目标文件夹安装对话框,如图1.12所示。可以单击“更改”按钮,选择JRE的安装路径。一般要求JDK和JRE安装在同一个文件夹内。1.3.1.3.2 2JDK安 装配置【步骤5】单击“下一步”按钮,进入JRE安装进度界面,如图1.13所示。当安装完成以后,进入安装成功界面,如图1.14所示。单击“关闭”按钮,完
15、成JDK整个安装过程。1.3.1.3.3 3JDK目录JDK安装完成后,在安装的位置中可以找到如图1.15所示的目录。JDK目录JDK主要目录如下:主要目录如下:bin:JDK包中命令及工具所在目录。这是JDK中非常重要的目录,它包含大量开发当中的常用工具程序,如编译器、解释器、打包工具、代码转换器和相关调试工具等。jre:运行环境目录。JDK自己附带的Java运行环境。lib:类库所在目录。包含了开发所需要的类库(即JavaAPI)和支持文件。db:附带数据库目录。在JDK6.0以上的版本中附带ApacheDerby数据库,这是一个Java编写的数据库,支持JDBC4.0。include:包
16、含本地代码的C头文件的目录。用于支持java本地接口和Java虚拟机调试程序接口的本地代码编译。src.zip:源代码压缩文件。Java提供的API都可以通过此文件查看其源代码是如何实现的。1.3.1.3.3 3JDK目录在在JDK的的bin目录下,提供了大量的开发工具程序,以下是几个常用的工具:目录下,提供了大量的开发工具程序,以下是几个常用的工具:javac:Java语言编译器。可以将Java源文件编译成与平台无关的字节码文件(.class文件)。java:Java字节码解释器。将字节码文件在不同的平台中解释执行。javap:Java字节码分解程序。可以查看Java程序的变量以及方法等信息
17、。javadoc:文档生成器。可以将代码中的文档注释生成HTML格式的JavaAPI文档。javah:JNI编程工具。用于从Java类调用C+代码。appletviewer:小应用程序浏览工具,用于测试并运行Java小应用程序。jar:打包工具。在JavaSE中压缩包的扩展名为.jar。第第4节节part集成开发环境安装配置好JDK后可以直接使用记事本编写Java程序,但是,当程序复杂到一定程度、规模逐渐增大后,使用记事本就远远满足不了开发的需求。一个好的集成开发环境(IDE,IntegratedDevelopmentEnvironment)可以起到事半功倍的效果。集成开发环境具有很多优势:不
18、仅可以检查代码的语法,还可以调试、跟踪、运行程序;此外,通过菜单、快捷键可以自动补全代码;且在编写代码的时候回自动进行编译;运行Java程序时,只需要单击运行按钮即可,大大缩短了开发时间。目前,最流行的两种是Eclipse和NetBeans,为了正当“领头羊”,两者之间展开了激烈的竞争。这些年来由于Eclipse的开放性、极为高校的GUI、先进的代码编辑器等特性,在IDE的市场占有率上远远超越NetBeans。本节仅介绍Eclipse这一款IDE工具的下载、安装和使用。集 成 开 发环境本节概述1.4.1.4.1 1Eclipse简介Eclipse是一个开放源代码、可扩展的、跨平台的集成开发环
19、境。Eclipse最初主要用来进行Java语言开发,如今也是一些开发人员通过插件使其作为其他语言如C+和PHP的开发工具。Eclipse本身只是一个框架平台,众多插件的支持使得Eclipse具有更高的灵活性,这也是其他功能相对固定的IDE工具很难做到的。Eclipse发行版本如表1-2所示。Eclipse简介1.4.1.4.2 2Eclipse下载及安装1.Eclipse下载下载进入Eclipse官方网站可以下载最新版本的Eclipse安装文件。Eclipse官方网站:http:/www.eclipse.orgeclipse下载地址:https:/www.eclipse.org/downloa
20、ds/download.php?file=/oomph/epp/photon/R/eclipse-inst-win64.exe&mirror_id=1261Eclipse下载页面如图1.16所示。Eclipse下载及安装1.4.1.4.2 2Eclipse下载及安装2.Eclipse安装安装Eclipse的安装一般有两种方式:一种是在网上下载绿化版的Eclipse开发工具,不需要安装,直接解压即可使用;另一种,是在官网下载需要安装的Eclipse软件。我们这里主要介绍第二种安装方式。下面对Eclipse的安装分步骤进行详细介绍。Eclipse下载及安装【步骤1】下载完成后解压下载包,可以看到E
21、clipseInstaller安装器,双击它,弹出安装类型选择页面,如图1.17所示。可以选择各种不同的语言的开发环境(包括Java、C/C+、JavaEE、PHP等)。我们这里选择“EclipseIDEforJavaDevelopes”项。1.4.1.4.2 2Eclipse下载及安装【步骤2】单击“next”按钮,进入安装路径选择界面,如图1.18所示。可以单击右侧的文件夹图标,进行安装路径选择。【步骤3】按回车键,进入安装版本选择界面,如图1.19所示。我们这里选择64位的Oxygen(氧气)版本进行安装。1.4.1.4.2 2Eclipse下载及安装【步骤4】以后依次单击“next”按
22、钮,直至安装完成即可。根据安装路径,打开Eclipse安装文件夹,其目录如图1.20所示。1.4.1.4.3 3Eclipse基 本操作1.Eclipse启动启动单击eclipse.exe启动开发环境,第一次运行Eclipse,启动向导会让你选择Workspace(工作区),如图1.21所示。在Workspace中输入某个路径,表示接下来的代码和项目设置都将保存在该工作目录下。单击“Launch”按钮,进入启动页面,如图1.22所示。Eclipse基本操作1.4.1.4.3 3Eclipse基 本操作启动成功后,第一次运行会显示欢迎界面,如图1.23所示,单击Welcome标签页上的关闭按钮,
23、关闭欢迎界面,将显示Eclipse开发环境布局界面,如图1.24所示。1.4.1.4.3 3Eclipse基 本操作开发环境分为如下几个部分:开发环境分为如下几个部分:菜单栏。工具栏。IDE的透视图,用于切换Eclipse不同的视图外观,通常根据开发项目的需要切换不同的视图。项目资源导航,主要有包资源管理器。代码编辑区,用于编写程序代码。程序文件分析工具,主要有大纲、任务列表。问题列表、文档注释、声明和控制台窗口。显示区域,主要有编译问题列表、运行结果输出等。1.4.1.4.3 3Eclipse基 本操作2.创建创建Java项目项目打开Eclipse集成开发工具,选择FileNewJavaPr
24、oject菜单项,如图1.25所示。或直接在项目资源管理器空白处右击,在弹出菜单中选择NewJavaProject菜单项。在弹出的创建项目对话框中输入项目名称,如图1.26所示。直接单击“Finish”按钮,项目创建成功。1.4.1.4.3 3Eclipse基 本操作3.创建类创建类在student项目中的src节点上右击,在弹出菜单中选择NewClass菜单项。在弹出的新建类对话框中,如图1.27所示,输入包名和类名,选中“publicstaticvoidmain(Stringargs)”复选框,然后单击“Finish”按钮,创建类完成。新建类后,Eclipse会自动打开新建类的代码编辑窗口
25、,在main()方法中输入如下代码:System.out.println(我是一个好学生,我要好好学习Java!);单击工具栏中的保存按钮,或者按“Ctrl+S”快捷键保存代码。单击工具栏上的运行按钮,选择“RunAsStudent”选项,即可运行Student.java程序,并且在控制台中可以看到输出结果如下:我是一个好学生,我要好好学习Java!1.4.1.4.3 3Eclipse基 本操作4.Eclipse调试调试【步骤1】设置断点单击需要设置断点的程序行左侧,在弹出的对话框中选择“设置断点”选项,会出现一个蓝色的断点标识,如图1.28所示。1.4.1.4.3 3Eclipse基 本操作
26、【步骤2】调试程序单击工具栏的调试按钮,或选择“DebugAsJavaApplication”选项,如图1.29所示,调试Student.java程序。此时弹出一个对话框如图1.30所示,询问是否切换到Debug透视图,单击“Yes”按钮,进入程序调试界面,如图1.31所。单击调试工具栏的或按钮,观察Variables窗口中的局部变量的变化,以及输出的变化,对代码进行调试并运行。1.4.1.4.3 3Eclipse基 本操作5.Eclipse导入导入在开发过程中,经常会需要从其他位置复制已有的项目,这些项目不需要重新创建,可以通过Eclipse的导入功能,将这些项目导入到Eclipse的工作空
27、间。首先,选择“FileImport”菜单项,在弹出的对话框中选择“GeneralExistingProjectsintoWorkspace”选项,如图1.32所示。1.4.1.4.3 3Eclipse基 本操作单击“Next”按钮,弹出导入项目窗口,如图1.33所示。可以导入两种形式的项目:项目根目录,即该项目以文件夹形式存放,则单击“Browse”按钮,指定其项目的根目录即可。项目压缩存档文件,即整个项目压缩成zip文件,则单击“Browse”按钮,指定其项目的压缩存档文件即可。最后,单击“Finish”按钮,完成项目导入。此时需要导入的项目已经引入到Eclipse工作空间中。注意:注意:
28、能够向Eclipse中导入的项目必须是使用Eclipse导出的项目。导出项目与导入项目正好相反,选择“FileExport”菜单项。第第5节节partJava应用程序Java程序分为Application程序和Applet程序两类。Application程序是普通的应用程序,其编译结果不是通常的exe文件而是class文件。Application程序能够在任何具有Java解释器的计算机上运行。Applet程序不是独立的程序,使用时必须把编译时生成的class文件嵌入到HTML文件中,借助浏览器解释执行。Java应 用程序本节概述1.5.1.5.1 1Java语 言 编写规范在Java语言中,为
29、包、类、接口、变量、常量和方法等取的名字,称为标识符。有关标识符的命名规则在第二章详细介绍,不遵循标识符命名规则会导致编译错误。在Java中,还有一种推荐的编程习惯,如果不遵守,虽然不会导致编译错误,但是编写的程序后期维护成本较高,可读性也较差。一般素质良好的程序员,在编写Java程序时,通常都会遵守如下的编程规范。Java语言编写规范1.包名包名包名是全小写的名词,具有多个层次结构的包名中间用点号分隔开。例如:com.student或java.sql.jdbc等。2.类名和接口名类名和接口名类名和接口名通常由多个单词构成,要求每个单词的首字母都要大写,其余字母小写。例如:HelloWorld
30、或StudentInformation等。3.方法名方法名方法名如果有多个单词组成,则第一个单词首字母要求小写,其余每个单词首字母大写,其余所有字母都小写。例如:createBookSaleRecord。4.变量名和对象名变量名和对象名变量名和对象名的编程规范和方法名相同,只是一般为名词。例如:name,age等。5.常量名常量名基本数据类型的常量名为全大写,如果由多个单词构成,可以用下划线隔开。例如:MAX_VALUE、MIN_AGE等。1.5.1.5.2 2Java注释注释是对程序代码做出注销或者解释说明的作用。在程序编译时,注释的内容不会被编译器处理,所以对于编译和运行的结果不会有任何影
31、响。但是在复杂的项目中,注释往往用来帮助开发人员阅读和理解程序,同时也有利于程序修改和调试。Java注释Java语言支持单行注释、多行注释和文档注释三种方法。1.单行注释单行注释单行注释使用“/”符合进行标记,可放置于代码后面或单独成行,标记之后的内容都被视为注释。例如:publicstaticvoidmain(Stringargs)inti=0;/定义变量i,并赋初值0。/向控制台输出语句System.out.println(HelloWorld!);1.5.1.5.2 2Java注释2.多行注释多行注释多行注释使用“/*/”进行标记,注释内容可以跨越多行,从“/*”开始到“*/”结束,之间
32、的内容都被视为注释。多行注释主要用于注释内容较多的文本,如说明文件、接口、方法和相关功能块描述,一般放在一个方法或接口的前面,起到解释说明的作用,也可以根据需要放在合适的位置。例如:publicstaticvoidmain(Stringargs)/*System.out.print()输出内容后不换行*System.out.println()输出内容后换行*/System.out.print(输出内容后不换行!);System.out.println(输出内容后换行!);1.5.1.5.2 2Java注释3文档注释文档注释文档注释使用“/*/”进行标记,其注释的规则与用途相似于多行注释。文档注
33、释不同于多行注释的是可以通过“javadoc”工具将其注释的内容生成HTML格式JavaAPI文档。程序的文档是项目产品的重要组成部分,将注释抽取出来可以更好地供使用者参阅。因此,在实际应用中,文档注释应用更为广泛,尤其是对类、接口、构造方法、方法的注释应尽量使用文档注释。例如:/*单位重庆机电职业技术学院*作者向守超*/publicclassHelloWorldpublicstaticvoidmain(Stringargs)/向控制台输出语句System.out.println(HelloWorld!);1.5.1.5.3 3Java打 印输出在Java程序中,向控制台输出信息是必不可少的。
34、输出的工作是通过打印语句来完成的。据不完全统计,打印语句是在代码中使用频率最高的语句之一,对于初学者来说是验证结果、测试代码、记录系统信息最普遍的方法。本书介绍两个Java中最常用的打印方法:System.out.println()和System.out.print(),以便于后续学习中的应用,两者都是向控制台输出信息,不同的是System.out.println()方法会在输出字符串后在输出回车换行符,而System.out.print()方法则不会输出回车换行符。Java打印输出1.5.1.5.3 3Java打 印输出下述代码示例了分别使用两种打印方法实现各种数据的输出,代码如下所示。【代
35、码1.1】PrintExample.javapackagecom;publicclassPrintExamplepublicstaticvoidmain(Stringargs)Strings=Hello;charc=c;System.out.print(Stringis:);System.out.println(s);System.out.print(charis:);System.out.println(c);上述代码运行结果如下所示:Stringis:Hellocharis:c1.5.1.5.4 4Hello World程序编写编写Java程序需要注意以下几点:程序需要注意以下几点:Jav
36、a是区分字母大小写的编程语言,Java语言的源程序文件是以.java为后缀的。所有代码都写在类体之中,因为Java是纯面向对象的编程语言,一个完整的Java程序,至少需要有一个类(class)。一个Java文件只能有一个公共类(public),且该公共类的类名与Java文件名必须相同,但一个Java文件可以有多个非公共类。每个独立的、可执行的Java应用程序必须要有main()方法才能运行。main()方法时程序的主方法,是整个程序的入口,运行时执行的第一句就是main()方法。Java语法对main()方法有固定的要求,方法名必须是小写的“main”,且方法必须是公共、静态、返回值类型为空的
37、“publicstaticvoid”类型,且其参数必须是一个字符串数组。HelloWorld程序1.5.1.5.4 4Hello World程序下面以HelloWorld程序为例,详细讲解Java程序的基本结构和代码含义。代码程序如下所示。【代码1.2】HelloWorld.java/定义包,指定类存放路径packagecom;/import语句,导入Java核心类库importjava.lang.*;/*使用“class”关键字定义一个名称为“HelloWorld”的类*该类的访问权限修饰符为public,表示在整个应用程序中都可以用访问该类*该公共类的类名必须与源文件的文件名一致*类的类体
38、是由一对大括号“”括起来的,起到封装作用*/publicclassHelloWorld/定义程序的主方法main()方法,即程序的入口publicstaticvoidmain(Stringargs)/向控制台输出双引号内的语句,通常一个语句书写一行,/语句必须以英文格式的分号“;”来结束System.out.println(HelloWorld!);本章课后作业见教材JAVA程 序 设计本章学习目标:本章主要学习Java语言中的基本数据类型、运算符与表达式掌握Java中的字符集、分隔符、标识符、关键字掌握变量和常量的定义和初始化掌握基本数据类型掌握Java中数据类型的转换、运算符和表达式第二章
39、第二章数据类型与运算符数据类型与运算符第第1节节part字符字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。字符集是多个字符的集合,不同的字符集所包含的字符个数也不同。字符集种类较多,常见字符集有ASCII字符集、GB2312字符集和Unicode字符集。计算机要准确处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。Unicode字符集是由一个名为UnicodeConsortium的非盈利机构制订的字符编码系统,支持各种不同语言的书面文本的转换、处理及显示。Unicode为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行
40、文本转换、处理的要求。Unicode支持UTF-8、UTF-16和UTF-32这三种字符编码方案,这三种方案的区别如表2-1所示。2.1.2.1.1 1字 符集字符集注意:注意:Java语言中基本所有输入元素都是采用ASCII字符集编码,而标识符、字符、字符串和注解则采用Unicode字符集编码。2.1.2.1.1 1字 符集Java中使用多种字符作为分隔符,用于辅助程序编写、阅读和理解。这些分隔符可以分为两类:空白符:没有确定意义,但帮助编译器正确理解源程序,包括空格、回车、换行和制表符(Tab);普通分隔符:拥有确定含义,常用的普通分隔符如表2-2所示。2.1.2.1.2 2分 隔符分隔符
41、需要需要注意的是:注意的是:任意两个相邻的标识符之间至少有一个分隔符,便于编译程序理解;空白符的数量多少没有区别,使用一个和多个空白符实现相同的分隔作用;分隔符不能相互替换,比如该用逗号的地方不能使用空白符。在各种编程语言中,通常要为程序中处理的各种变量、常量、方法、对象和类等起个名字作为标记,以便通过名字进行访问,这些名字统称标识符。Java中的标识符由字母、数字、下划线或美元符组成,且必须以字母、下划线(_)或美元符($)开头。Java中标识符的命名规则如下:可以包含数字,但不能以数字开头;除下划线“_”和“$”符以外,不包含任何其他特殊字符,如空格;区分大小写,例如“abc”和“ABC”
42、是两个不同的标识符;不能使用Java关键字。标识符可有任意长度。2.1.2.1.3 3标 识符标识符以下是合法标识符的示例:以下是合法标识符的示例:varName_varNamevar_Name$varName_9Name以下是非法标识符的示例以下是非法标识符的示例:VarName/包含空格9varName/以数字开头a+b/加号“+”不是字母和数字,属于特殊字符,不是Java标识符组成元素关键字又叫保留字,是编程语言中事先定义的、有特别意义的标识符。关键字对编译器具有特殊的意义,用于表示一种数据类型或程序的结构等,关键字不能用于变量名、方法名、类名以及包名。Java中常用的关键字如表2-3所
43、示。2.1.2.1.4 4关 键字关键字第第2节节part变量和常量变量是数据的基本存储形式,因Java是一种强类型的语言,所以每个变量都必须先声明后再使用。变量的定义包括变量类型和变量名,其定义的基本格式如下:数据类型变量名=初始值;例如:定义整型变量inta=1;/声明变量并赋初始值其中,int是整型数据类型;a是变量名称;=是赋值运算符;1是赋给变量的初始值。变量的声明与赋值也可以分开,例如:inta;/声明变量a=1;/给变量赋值声明变量时,可以几个同一数据类型的变量同时声明,变量之间使用逗号“,”隔开,例如:inti,j,k;2.2.2.2.1 1变量变量常量是指一旦赋值之后其值不能
44、再改变的变量。在Java语言中,使用final关键字来定义常量,其语法格式如下:final数据类型变量名=初始值;例如:定义常量finaldoublePI=3.1416;/声明了一个double类型的常量,初始化值为3.1416finalbooleanIS_MAN=true;/声明了一个boolean类型的常量,初始化值为true需要注意的是:在开发过程中常量名习惯采用全部大写字母,如果名称中含有多个单词,则单词之间以“_”分隔。此外常量在定义时,需要对常量进行初始化,初始化后,在应用程序中就无法再对该常量赋值。2.2.2.2.2 2常量常量第第3节节part数据类型定义变量或常量时需要使用数
45、据类型,Java的数据类型分为两大类:基本类型和引用类型。基本类型是一个单纯的数据类型,表示一个具体的数字、字符或布尔值。基本类型存放在内存的“栈”中,可以快速从栈中访问这些数据。引用类型是一个复杂的数据结构,是指向存储在内存的“堆”中数据的指针或引用(地址)。引用类型包括类、接口、数组和字符串等,由于要在运行时动态分配内存,所以其存取速度较慢。数据类型本节概述Java的基本数据类型主要包括如下四类:整数类型:byte、short、int、long浮点类型:float、double字符类型:char布尔类型:boolean2.3.2.3.1 1基 本 数 据类型基本数据类型Java各种基本类型
46、的大小和取值范围如表2-4所示。1.整型类型整型类型整数类型根据大小分为byte(字节型)、short(短整型)、int(整型)和long(长整型)四种,其中int是最常用的整数类型,因此通常情况下,直接给出一个整数值默认就是int类型。其中,在定义long类型的变量时,其常量后面需要用后缀l或L。例如:声明整型类型变量byteb=51;/声明字节型变量shorts=34;/声明短整型变量inti=100;/声明整型变量longm=12l;/声明长整型变量longn=23L;/声明长整型变量Java中整数值有4种表示方式:2.3.2.3.1 1基 本 数 据类型二进制:每个数据位上的值是0或1
47、,二进制是整数在内存中的真实存在形式,从Java7开始新增了对二进制整数的支持,二进制的整数以“0b”或“0B”开头。八进制:每个数据位上的值是0,1,2,3,4,5,6,7,其实八进制是由3位二进制数组成的,程序中八进制的整数以“0”开头。十进制:每个数据位上的值是0,1,2,3,4,5,6,7,8,9,十进制是生活中常用的数值表现形式,因此在程序中如无特殊指明,数值默认为十进制。十六进制:每个数据位上的值是0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,与八进制类似,十六进制是由4位二进制数组成的,程序中十六进制的整数以“0x”或“0X”开头。下述案例示例了整数类型的不同表
48、示形式,代码如下所示。【代码2.1】IntValueExample.javapackagecom;publicclassIntValueExamplepublicstaticvoidmain(Stringargs)inta=0b1001;/二进制数System.out.println(二进制数0b1001的值是:+a);intb=071;/八进制数System.out.println(八进制数071的值是:+b);intc=19;/十进制数System.out.println(十进制数19的值是:+c);/Integer.toBinaryString()方法将一个整数以二进制形式输出Syste
49、m.out.println(19的二进制表示是:+Integer.toBinaryString(c);intd=0xFE;/十六进制数System.out.println(十六进制数0xFE的值是:+d);System.out.println(十六进制数0xFE的二进制表示是+Integer.toBinaryString(d);inte=19;/负数以补码形式存储System.out.println(19的二进制表示是:+Integer.toBinaryString(e);2.3.2.3.1 1基 本 数 据类型上述代码中,Integer是int基本数据类型对应的封装类,该类提供一些对整数的一
50、些常用静态方法,其中Integer.toBinaryString()方法可以将一个整数以二进制形式输出。2.3.2.3.1 1基 本 数 据类型该程序运行结果如下所示:二进制数0b1001的值是:9八进制数071的值是:57十进制数19的值是:1919的二进制表示是:10011十六进制数0xFE的值是:254十六进制数0xFE的二进制表示是1111111019的二进制表示是:100112.浮点类型浮点类型浮点数据类型有单精度(float)和双精度(double)两种,主要用来存储小数数值,也可以用来存储范围更大的整数。单精度浮点型变量使用关键字float来声明,常量后面必须要有后缀f或F。例如
51、:声明单精度浮点型变量floatheight=1.78f;/声明变量height为单精度浮点型,并赋初始值为1.78floatweight=56.8F;/声明变量weight为单精度浮点型,并赋初始值为56.8float变量在存储float类型数据时保留8位有效数字,例如,如果将常量12345.123456789f赋值给float类型变量x,则x实际输出值为12345.123。对于float类型变量,分配4个字节内存,占32位,float类型变量的取值范围为1.4E-453.4028235E38和-3.4028235E38-1.4E-45。双精度浮点型变量使用关键字double来声明,常量后面
52、可以有后缀d或D,也可以省略,浮点类型默认为double型。例如:声明双精度浮点型变量doublea=1.24d;doubleb=4.87D;doublec=3.14;2.3.2.3.1 1基 本 数 据类型double变量在存储double类型数据时保留16位有效数字,分配8个字节内存,占64位,double类型变量的取值范围为4.9E-3241.7976931348623157E308和-1.7976931348623157E308-4.9E-324。2.3.2.3.1 1基 本 数 据类型下述案例示例了浮点类型变量保留的有效位数,代码如下所示。【代码2.2】FloatExample.ja
53、vapackagecom;publicclassFloatExamplepublicstaticvoidmain(Stringargs)floatx=12345.123456789f;System.out.println(x=+x);doubley=12345.12345678912345678d;System.out.println(y=+y);程序运行结果如下:x=12345.123y=12345.1234567891243.字符型字符型Java语言中字符型char是采用16位的Unicode字符集作为编码方式,因此支持世界上各种语言的字符。char通常用于表示单个字符,字符值必须使用单引
54、号()括起来。例如:char=A;/声明变量c为字符型,并赋初值为A字符型char的值有以下三种表示形式:通过单个字符来指定字符型值,例如:A、8、Z等;通过转义字符来表示特殊字符型值,例如:n、t等;直接使用Unicode值来表示字符型值,格式是uXXXX,其中XXXX代表一个十六进制的整数,例如:u00FF、u0056等。2.3.2.3.1 1基 本 数 据类型例如:使用转义字符赋值chara=;/变量a表示一个单引号charb=;/变量b表示一个反斜杠4.布尔类型布尔类型布尔类型又称逻辑类型,使用关键字boolean来声明,只有true和false两种值。布尔类型的默认值是false,即
55、如果定义一个布尔变量但没有赋初始值,默认的布尔变量值是false。布尔类型通常用于逻辑判断,尤其多用在程序的流程控制中。例如:声明一个boolean类型变量booleanmale=true;/声明变量male为布尔类型,并赋初始值为true2.3.2.3.1 1基 本 数 据类型2.3.2.3.1 1基 本 数 据类型程序运行结果如下:isA=falseisB=trueisBistrue下述案例示例了布尔数据类型的应用,代码如下所示。【代码2.3】BooleanExample.javapackagecom;publicclassBooleanExamplestaticbooleanisA;/定
56、义一个布尔值,使用默认值staticbooleanisB=true;/定义一个布尔值,赋初始值为truepublicstaticvoidmain(Stringargs)System.out.println(isA=+isA);/输出不布尔值isA的结果System.out.println(isB=+isB);/输出不布尔值isB的结果/输出isA为true则输出isAistrueif(isA=true)System.out.println(isAistrue);/输出isB为true则输出isBistrueif(isB)System.out.println(isBistrue);2.3.2.3
57、.2 2引 用 数 据类型引用类型变量中的值是指向内存“堆”中的指针,即该变量所表示数据的地址。Java语言中通常有5种引用类型:数组:具有相同数据类型的变量的集合。类(class):变量和方法的集合。接口(interface):一系列方法的声明,方法特征的集合。枚举(enum):一种独特的值类型,用于声明一组命名的常数。空类型(nulltype):空引用,即值为null的类型。空类型没有名称,不能声明一个null类型的变量,null是空类型的唯一值。空引用只能被转换成引用类型,不能转换成基本类型,因此不要把一个null值赋给基本数据类型的变量。引用数据类型2.3.2.3.3 3数 据 类 型
58、转换在Java程序中,不同的基本类型的值经常需要进行相互转换。Java语言提供7个基本数据类型间的相互转换,转换的方式有两种:自动类型转换和强制类型转换。1.自动类型转换自动类型转换自动类型转换是将某种基本类型变量的值直接赋值给另一种基本类型变量。当把一个数值范围小的变量直接赋值给一个数值范围大的变量时,系统将进行自动类型转换,否则就需要强制类型转换。Java语言中7个基本数据类型间的自动类型转换如图2.1所示。在图2.1中,顺着箭头方向可以进行自动类型转换。其中,实线箭头表示无精度损失的转换,而虚线箭头则表示在转换过程中可能丢失精度,即会保留正确的量级,但精度上会有一些损失(小数点后所保留的
59、位数)。数据类型转换2.3.2.3.3 3数 据 类 型转换2.强制类型转换强制类型转换当把一个数值范围大的变量赋值给一个数值范围小的变量时,即沿图2.1中箭头反方向赋值时,则必须使用强制类型转换。强制类型转换的基本格式如下:数据类型变量1=(数据类型)变量2;例如:inta=56;charc=(char)a;/把整型变量a强制类型转换为字符型2.3.2.3.3 3数 据 类 型转换下述案例示例了自动类型转换与强制类型转换的应用,代码如下所示。【代码2.4】TypeChangeExample.javapackagecom;publicclassTypeChangeExamplepublicst
60、aticvoidmain(Stringargs)byteb=8;charc=B;inta=12;longl=789L;floatf=3.14f;doubled=5.3d;inti1=a+c;/字符型变量c自动转换为整型,参加加法运算System.out.println(i1=+i1);longl1=l-i1;/整型变量i1自动转换为长整型,参加减法运算System.out.println(l1=+l1);floatf1=b*f;/字节型变量b自动转换为浮点型,参加乘法运算System.out.println(f1=+f1);doubled1=d/a;/整型变量a自动转换为双精度,参加除法运算S
61、ystem.out.println(d1=+d1);inti2=(int)f1;/将浮点型变量f1强制类型转换为整数System.out.println(i2=+i2);/整型变量a自动类型转换为长整型后参加除法运算,/算出的长整型结果再强制类型转换为字符型charc2=(char)(l/a);System.out.println(c2=+c2);该程序运行结果如下所示:i1=78l1=711f1=25.12d1=0.44166666666666665i2=25c2=A第第4节节part运算符运算符也称为操作符,是一种特殊的符号,用来将一个或多个操作数连接成执行性语句,以实现特定功能。Java
62、中的运算符按照操作数的个数可以分为三大类型:一元运算符:只操作一个操作数;二元运算符:操作两个操作数;三元运算符:操作三个操作数。运 算符本节概述Java中的运算符按操作数分类和功能分类如表2-6所示。2.4.2.4.1 1自增、自减运算符+是自增运算符,将操作数在原来的基础上加1,-是自减运算符,将操作数在原来的基础上减1。使用自增、自减运算符时需要注意以下两点:自增、自减运算符只能操作单个数值型的变量(整型、浮点型都行),不能操作常量或表达式;自增、自减运算符可以放在操作数的前面(前缀自增自减),也可以放在操作数后面(后缀自增自减)。前缀自增自减的特点是先把操作数自增或自减1后,再放入表达
63、式中运算,后缀自增自减的特点是先使用原来的值,当表达式运算结束后再将操作数自增或自减1。自增、自减运算符2.4.2.4.1 1自增、自减运算符下述案例示例了自增自减运算符的应用,代码如下所示。【代码2.5】SelfIncreaseExample.javapackagecom;publicclassSelfIncreaseExamplepublicstaticvoidmain(Stringargs)inta=5;intb=+a+8;/a先自增变为6,再与8相加,最后b的值是14System.out.println(b的值是:+b);intc=a+;System.out.println(c的值是:
64、+c);System.out.println(a的值是:+a);intd=10;System.out.println(前缀自减-d的值是:+-d);System.out.println(当前d的值是:+d);System.out.println(后缀自减d-的值是:+d-);System.out.println(当前d的值是:+d);程序运行结果如下:b的值是:14c的值是:6a的值是:7前缀自减-d的值是:9当前d的值是:9后缀自减d-的值是:9当前d的值是:82.4.2.4.2 2算 术 运 算符算术运算符用于执行基本的数学运算,包括加(+)、减(-)、乘(*)、除(/)以及取余(%)运算
65、符,如表2-7所示。算术运算符需要注意的是:需要注意的是:如果/和%运算符的两个操作数都是整数类型,则除数不能是0,否则引发除数为0异常。但如果两个操作数有一个是浮点数,或者两个都是浮点数,此时允许除数是0或0.0,任何数除0得到的结果是正无穷大(Infinity)或负无穷大(-Infinity),任何数对0取余得到的结果是非数:NaN。2.4.2.4.2 2算 术 运 算符packagecom;publicclassMathOperExamplepublicstaticvoidmain(Stringargs)inta=5;intb=3;System.out.println(a=+a+,b=+
66、b);System.out.println(a+b=+(a+b);/字符串连接System.out.println(a连接b=+a+b);System.out.println(a-b=+(a-b);/两个数相减,结果为2System.out.println(a*b=+(a*b);/两个数相乘,结果为15System.out.println(a/b=+(a/b);/两个整数相除,结果为1System.out.println(a%b=+(a%b);/两个整数取余,结果为2System.out.println(5.1/3=+(5.1/3);/两个浮点数相除,结果为1.7System.out.prin
67、tln(5.2%3.1=+(5.2%3.1);/两个浮点数取余,结果为2.1System.out.println(3.1/0=+(3.1/0);/正浮点数除以0,结果为InfinitySystem.out.println(-8.8/0=+(-8.8/0);/负浮点数除以0,结果为-InfinitySystem.out.println(5.1%0=+(5.1%0);/正浮点数对0取余,结果为NaNSystem.out.println(6.6%0=+(6.6%0);/负浮点数对0取余,结果为NaNSystem.out.println(3/0=+(3/0);/整数除以0,将引发异常下述案例示例了算术
68、运算符的应用,代码如下所示。【代码2.6】MathOperExample.java程序运行结果如下:a=5,b=3a+b=8a连接b=53a-b=2a*b=15a/b=1a%b=25.1/3=1.75.2%3.1=2.13.1/0=Infinity-8.8/0=-Infinity5.1%0=NaN6.6%0=NaNExceptioninthreadmainjava.lang.ArithmeticException:/byzeroatcom.MathOperExample.main(MathOperExample.java:20)2.4.2.4.3 3关 系 运算符关系运算符用于判断两个操作数的
69、大小,其运算结果是一个布尔类型值(true或false)。Java语言中的关系运算符如表2-8所示。关系运算符需要注意的是:需要注意的是:关系运算符中=比较特别,如果进行比较的两个操作数都是数值类型,即使它们的数据类型不同,只要它们的值相等,都将返回true。例如a=97返回true,5=5.0也返回true。如果两个操作数都是引用类型,则只有当两个引用变量的类型具有继承关系时才可以比较,且这两个引用必须指向同一个对象(地址相同)才会返回true。如果两个操作数是布尔类型的值也可以进行比较。例如true=false返回false。2.4.2.4.3 3关 系 运算符下述案例示例了关系运算符的应
70、用,代码如下所示。【代码2.7】CompareOperaExample.javapackagecom;publicclassCompareOperaExamplepublicstaticvoidmain(Stringargs)inta=5;intb=3;System.out.println(a+b+结果为+(ab);System.out.println(a+=+b+结果为+(a=b);System.out.println(a+b+结果为+(ab);System.out.println(a+=+b+结果为+(a3结果为true5=3结果为true53结果为false5)、左移位()。位运算符Ja
71、va语言中的位运算符功能描述如表2-11所示。2.4.2.4.5 5位运算符按位运算符所遵循的真值表如表2-12所示。2.4.2.4.5 5位运算符下述案例示例了位运算符的应用,代码如下所示。【代码2.9】ByteOperaExample.javapackagecom;publicclassByteOperaExamplepublicstaticvoidmain(Stringargs)inta=0b0101010;intb=0b0001111;System.out.println(a=+Integer.toBinaryString(a);System.out.println(b=+Intege
72、r.toBinaryString(b);System.out.println(a=+Integer.toBinaryString(a);/按位非System.out.println(a&b=+Integer.toBinaryString(a&b);/按位与System.out.println(a|b=+Integer.toBinaryString(a|b);/按位或System.out.println(ab=+Integer.toBinaryString(ab);/按位异或System.out.println(a1=+Integer.toBinaryString(a1=+Integer.toB
73、inaryString(a1);/右移System.out.println(a1=+Integer.toBinaryString(a1);/无符号右移程序运行结果如下:a=101010b=1111a=11111111111111111111111111010101a&b=1010a|b=101111ab=100101a1=10101a1=101012.4.2.4.6 6赋 值 运算符赋值运算符用于为变量指定变量值,Java中使用“=”作为赋值运算符。通常使用“=”可以直接将一个值赋给变量。例如:inta=3;floatb=3.14f;除此以外,也可以使用“=”将一个变量值或表达式的值赋给另一个
74、变量。例如:inta=3;floatb=a;doubled=b+3;赋值运算符赋值运算符可与算术运算符、位运算符结合,扩展成复合赋值运算符。扩展后的复合赋值运算符如表2-13所示。2.4.2.4.6 6赋 值 运算符下述案例示例了赋值运算符的使用,代码如下所示。【代码2.10】ValueOperExample.javapackagecom;publicclassValueOperExamplepublicstaticvoidmain(Stringargs)inta=8;intb=3;System.out.println(a=+a+,b=+b);System.out.println(a+=b,a
75、=+(a+=b);System.out.println(a-=b,a=+(a-=b);System.out.println(a*=b,a=+(a*=b);System.out.println(a/=b,a=+(a/=b);System.out.println(a%=b,a=+(a%=b);System.out.println(a&=b,a=+(a&=b);System.out.println(a|=b,a=+(a|=b);System.out.println(a=b,a=+(a=b,a=+(a=b);System.out.println(a=b,a=+(a=b);程序运行结果如下所示:a=8,
76、b=3a+=b,a=11a-=b,a=8a*=b,a=24a/=b,a=8a%=b,a=2a&=b,a=2a|=b,a=3a=b,a=3a=b,a=02.4.2.4.7 7条 件 运算符Java语言中只有一个条件运算符是“?:”,也是唯一的一个三元运算符,其语法格式如下:表达式?value1:value2其中:表达式的值必须为布尔类型,可以是关系表达式或逻辑表达式;若表达式的值为true,则返回value1的值;若表达式的值为false,则返回value2的值。例如:/判断ab是否为真,如果为真则返回a的值,否则返回b的值,/实现获取两个数中的最大数ab?a:b条件运算符2.4.2.4.7 7
77、条 件 运算符下述案例示例了条件运算符的使用,代码如下所示。【代码2.11】ConditionOperExample.javapackagecom;publicclassConditionOperExamplepublicstaticvoidmain(Stringargs)inta=56;intb=45;intc=78;System.out.println(ab?a:b=+(ab?a:b);System.out.println(ac?a:c=+(ac?a:c);程序运行结果如下所示:ab?a:b=56ac?a:c=782.4.2.4.8 8运 算 符 优先级通常数学运算都是从左到右,只有一元运
78、算符、赋值运算符和三元运算符除外。一元运算符、赋值运算符和三元运算符是从右向左结合的,即从右向左运算。乘法和加法是两个可结合的运算,即+、*运算符左右两边的操作数可以互换位置而不会影响结果。运算符具有不同的优先级,所谓优先级是指在表达式运算中的运算顺序。在表达式求值时,会先按运算符的优先级别由高到低的次序执行,例如,算术运算符中采用“先乘除后加减,先括号内后括号外”。表2-14列出了包括分隔符在内的所有运算符的优先级,上一行的运算符总是优先于下一行的。运算符优先级2.4.2.4.8 8运 算 符 优先级需要注意的是:需要注意的是:不要把一个表达式写得太复杂,如果一个表达式过于复杂,则可以把它分
79、成多步来完成;不要过多依赖运算符的优先级来控制表达式的执行顺序,以免降低可读性,尽量使用()来控制表达式的执行顺序。本章课后作业见教材JAVA程 序 设计本章学习目标:掌握程序控制语句的基本语法结构掌握分支语句和循环语句的应用理解并熟练使用程序跳转语句第第三三章章流程控制结构流程控制结构程序是由一系列指令组成的,这些指令称为语句。Java中有许多语句,有些语句用来控制程序的执行流程,即执行顺序,这样的语句称为“控制语句”。Java中的控制语句有以下三大类:分支语句:if和switch语句;循环语句:while、do-while和for循环语句;转移语句:break、continue和retur
80、n语句。第第1节节part语句概述1方法调用语句方法调用语句如:System.out.println(Hello);2表达式语句表达式语句由一个表达式构成一个语句,即表示式尾加上分号。比如赋值语句:x=23;3复合语句复合语句可以用把一些语句括起来构成复合语句,如:z=123+x;System.out.println(Howareyou);语句概述语句概述4空语句空语句一个分号也是一条语句,叫做空语句。5控制语句控制语句控制语句分为条件分支语句、开关语句和循环语句。6package语句和语句和import语句语句package语句和import语句与类、对象有关,将在以后的章节中讲解。Java
81、里的语句可分为以下六类里的语句可分为以下六类:第第2节节part分支结构分支结构是根据表达式条件的成立与否,决定执行哪些语句的结构。其作用是让程序根据具体情况有选择性地执行代码。Java中提供的分支语句有以下两个:if条件语句switch开关语句分支结构本节概述3.2.3.2.1 1if条件语句if条件语句是最常用的分支结构,其语法格式如下:if(条件表达式1)语句块1elseif(条件表达式2)语句块2elseif(条件表达式3)语句块3.else语句块nif条件语句其中,其中,if语句需要注意以下几点:语句需要注意以下几点:所有条件表达式的结果为布尔值(true或false);当“条件表达
82、式1”为true时执行if语句中的“语句块1”部分;当“条件表达式1”为false时,执行elseif语句,继续向下判断条件表达式,哪个条件表达式成立,执行相应的语句块;当所有条件表达式为false时执行else语句中的“语句块n”部分。elseif可以有多个;括起来的elseif、else可以省略。根据语法规则,可以将if语句分为如下三种形式。3.2.3.2.1 1if条件语句1.if语句语句if语句是单条件单分支语句,即根据一个条件来控制程序执行的流程。if语句的语法格式如下:if(条件表达式)语句块需要注意注意的是,在if语句中,其中的语句块如果只有一条语句,可以省略不写,但为了增强程序
83、的可读性,最好不要省略。if语句的流程图如图3.1所示。在if语句中,关键字if后面的一对小括号()内的条件表达式的值必须为boolean类型,当值为true时,则执行紧跟着的语句块,否则,结束当前if语句的执行。3.2.3.2.1 1if条件语句下述案例通过将变量a,b,c数值按大小顺序进行互换(从小到大排列),示例了if语句的使用,代码如下所示。【代码3.1】IfExample.javapackagecom;publicclassIfExamplepublicstaticvoidmain(Stringargs)inta=9,b=5,c=7,t=0;if(ba)t=a;a=b;b=t;if(
84、ca)t=a;a=c;c=t;if(c=90)System.out.println(优秀);elseif(score=80)System.out.println(良好);elseif(score=70)System.out.println(中等);elseif(score=60)System.out.println(及格);elseSystem.out.println(不及格);程序运行结果如下:良好3.2.3.2.2 2switch开 关语句switch开关语句是由一个控制表达式和多个case标签组成,与if语句不同的是,switch语句后面的控制表达式的数据类型只能是byte、short、
85、char、int四种类型,boolean类型等其他类型是不被允许的,但从Java7开始允许枚举类型和String字符串类型。Switch开关语句switch语句的语法格式语句的语法格式如下:如下:switch(控制表达式)casevalue1:语句1;break;casevalue2:语句2;break;.casevalueN:语句N;break;default:默认语句;其中,switch语句需要注意以下几点:控制表达式的数据类型只能是byte、short、char、int、String和枚举类型;case标签后的value值必须是常量,且数据类型必须与控制表达式的值保持一致;break用于
86、跳出switch语句,即当执行完一个case分支后,终止switch语句的执行;只有在一些特殊情况下,当多个连续的case值要执行一组相同的操作时,此时可以不用break。default语句是可选的。用在当所有case语句都不匹配控制表达式值时,默认执行的语句。3.2.3.2.2 2switch开 关语句switch语句执行顺序是先对控制表达式求值,然后将值依次匹配case标签后的value1,value2,valueN,遇到匹配的值就执行对应的语句块,如果所有的case标签后的值都不能与控制表达式的值匹配,则执行default标签后的默认语句块。switch语句的执行流程图如图3.4所示。3
87、.2.3.2.2 2switch开 关语句下述案例通过判断学生成绩的等级,示例了switch开关语句的使用,代码如下所示。【代码3.4】SwitchExample1.javapackagecom;publicclassSwitchExample1publicstaticvoidmain(Stringargs)intscore=67;switch(score/10)/使用switch判断g的等级case10:case9:System.out.println(优秀);break;case8:System.out.println(良好);break;case7:System.out.println(
88、中等);break;case6:System.out.println(及格);break;default:System.out.println(不及格);上述代码中先计算“score/10”,因score是整数,所以结果也是整数,即取整数部分值,例如:67/10=6;case10后面没有语句,将向下进入到case9中,即当值为10或9时都输出“优秀”。程序运行结果如下:及格3.2.3.2.2 2switch开 关语句从Java7开始增强了switch语句的功能,允许控制表达式的值是Stirng字符串类型的变量或表达式,下述案例示例了switch增强功能。【代码3.5】SwitchExample
89、2.javapackagecom;publicclassSwitchExample2publicstaticvoidmain(Stringargs)/声明变量season是字符串,注意JDK版本是7以上才能支持Stringseason=夏天;/执行swicth分支语句switch(season)case春天:System.out.println(春暖花开.);break;case夏天:System.out.println(夏日炎炎.);break;case秋天:System.out.println(秋高气爽.);break;case冬天:System.out.println(冬雪皑皑.);br
90、eak;default:System.out.println(季节输入错误);程序运行结果如下:夏日炎炎.第第3节节part循环结构循环结构是根据循环条件,要求程序反复执行某一段代码,直到条件终止为止的程序控制结构。循环结构由四部分组成:初始化部分。开始循环之前,需要设置循环变量的初始值。循环条件。循环条件是一个含有循环变量的布尔表达式,循环体的执行需要循环条件来控制,每执行一次循环体都需要判断该表达式的值,用于决定循环是否继续。循环体。需要反复执行的语句块,可以是一条语句,也可以是多条语句。迭代部分。改变循环变量值的语句。Java语言中提供的循环语句有以下三种:for循环while循环do-
91、while循环循环结构本节概述3.3.3.3.1 1for循环for循环是最常见的循环语句,其语法结构非常简洁,一般用在知道循环次数的情况下,即固定循环。for循环的语法结构如下:for(初始化表达式;条件表达式;迭代表达式)循环体其中:初始化表达式只在循环开始之前执行一次;初始化表达式、条件表达式以及迭代表达式都可以省略,但分号不能省,当三者都省略时将成为一个无限循环(死循环);在初始化表达式和迭代表达式中可以使用逗号隔开多个语句。for循环3.3.3.3.1 1for循环for循环的执行顺序是首先执行初始化表达式;接下来判断条件表达式是否为true,如果为true,则执行循环体中的语句,紧
92、接着执行迭代表达式,完成一次循环,进入下一次循环;如果条件表达式为false,则终止循环。注意注意,下次循环依然要先判断条件表达式是否成立,并根据判断结果进行相应操作。for循环执行流程图如图3.5所示3.3.3.3.1 1for循环下述案例使用for循环求1100整数和,示例了for循环的应用,代码如下所示。【代码3.6】ForExample1.javapackagecom;publicclassForExample1publicstaticvoidmain(Stringargs)/使用for循环求1100的和intsum=0;for(inti=1;i=100;i+)sum+=i;Syste
93、m.out.println(1100的和是:+sum);上述代码中,for循环语句将循环体执行100次,每次循环将当前i的值加到sum中,当循环终止时,sum的值就是1100的和。程序运行结果如下:1100的和是:50503.3.3.3.1 1for循环for循环可以嵌套,下述案例使用嵌套的for循环打印九九乘法表,代码如下所示。【代码3.7】ForExample2.javapackagecom;publicclassForExample2publicstaticvoidmain(Stringargs)/嵌套的for循环打印九九乘法表/第一个for控制行for(inti=1;i=9;i+)/第
94、二个for控制列,即每行中输出的算式for(intj=1;j=i;j+)/输出j*i=n格式,例如2*3=6System.out.print(j+*+i+=+i*j+);/换行System.out.println();上述代码中,第二个for循环体中的输出语句使用的是System.out.print(),该语句输出内容后不换行;而第一个for循环体中的使用System.out.println()直接换行3.3.3.3.2 2while循环while循环语句的语法格式循环语句的语法格式如下:如下:while(条件表达式)循环体迭代部分while循环while循环语句的执行顺序是先判断条件表达式是
95、否为true,如果为true,则执行循环体内的语句,再进入下一次循环;如果条件表达式为false,则终止循环。while循环的执行流程图如图3.6所示3.3.3.3.2 2while循环下述案例使用while循环实现求1100整数和,代码如下所示。【代码3.8】WhileExample.javapackagecom;publicclassWhileExamplepublicstaticvoidmain(Stringargs)/使用while循环求1100的和intsum=0;inti=1;while(i=100)sum+=i;i+;System.out.println(1100的和是:+sum
96、);上述代码中,在while循环体的最后一个语句是i+,属于循环结构的迭代部分,进行循环变量的增丽运算,如果没有这条语句,则会出现死循环。程序运行结果如下:1100的和是:50503.3.3.3.3 3do-while循环do-while循环与while循环类似,只是while循环要先判断后循环,而do-while循环则是先循环后判断,do-while循环至少会循环一次。do-while循环的语法格式如下:循环的语法格式如下:do循环体迭代部分while(条件表达式);do-while循环do-while循环的执行顺序是先执行一次do语句块,然后再判断条件表达式是否为true,如果为true,
97、则进入下一次循环;如果为false,则终止循环。do-while循环的执行流程图如图3.7所示。3.3.3.3.3 3do-while循环下述案例使用do-while循环实现求1100整数和,代码如下所示。【代码3.9】DoWhileExample.javapackagecom;publicclassDoWhileExamplepublicstaticvoidmain(Stringargs)/使用do-while循环求1100的和intsum=0;inti=1;dosum+=i;i+;while(i=100);System.out.println(1100的和是:+sum);程序运行结果如下:
98、1100的和是:5050第第4节节part转移语句Java语言没有提供goto语句来控制程序的跳转,此种做法提高了程序的可读性,但也降低了程序的灵活性。为了弥补这种不足,Java语言提供一些转移语句来控制分支和循环结构,使程序员更方便地控制程序执行的方向。Java语言提供了三种转移语句:break语句、continue语句和return语句。转移语句本节概述3.4.3.4.1 1break语句break语句用于终止分支结构或循环结构,其主要用在以下3种情况:在switch语句中,用于终止case语句,跳出switch分支结构。在循环结构中,用于终止循环语句,跳出循环结构。与标签语句配合使用从内
99、层循环或内层程序块中退出。break语句3.4.3.4.1 1break语句下述案例示例了使用break语句终止循环,代码如下所示。【代码3.10】BreakExample1.javapackagecom;publicclassBreakExample1publicstaticvoidmain(Stringargs)inti=1;for(;i=10;i+)if(i=5)System.out.println(找到目标,结束循环!);/终止循环break;System.out.println(i);/打印当前的i值/打印终止循环的i值System.out.println(终止循环的i=+i);程序
100、运行结果如下:1234找到目标,结束循环!终止循环的i=53.4.3.4.1 1break语句在嵌套的循环语句中,break语句不仅可以终止当前所在的循环,还可以直接结束其外层的循环,此时需要在break后跟一个标签,该标签用于识别一个外层循环。下述案例示例了带标签的break语句的使用,代码如下所示。【代码3.11】BreakExample2.javapackagecom;publicclassBreakExample2publicstaticvoidmain(Stringargs)/外层循环,outer作为标识符outer:for(inti=0;i5;i+)/内层循环for(intj=0;
101、j3;j+)System.out.println(i的值为:+i+j的值为:+j);if(j=1)/跳出outer标签所标识的循环。breakouter;上述代码在外层for循环前增加“outer:”作为标识符,当breakouter时,则跳出它所标志的外层循环。程序运行结果如下:i的值为:0j的值为:0i的值为:0j的值为:13.4.3.4.2 2continue语句continue的功能与break有点类似,区别是continue只是忽略本次循环体剩下的语句,接着进入到下一次循环,并不会终止循环;而break则是完全终止循环。continue语句【代码3.12】ContinueExampl
102、e.javapackagecom;publicclassContinueExamplepublicstaticvoidmain(Stringargs)for(inti=1;i=10;i+)if(i=5)System.out.println(找到目标,继续循环!);/跳出本次循环,进入下一次循环continue;System.out.println(i);/打印当前的i值程序运行结果如下:1234找到目标,继续循环!6789103.4.3.4.3 3return语句return语句并不是专门用于结束循环的,通常是用在方法中,以便结束一个方法。return语句主要有以下两种使用格式:单独一个ret
103、urn关键字。return关键字后面可以跟变量、常量或表达式。例如:return0;当含有return语句的方法被调用时,执行return语句将从当前方法中退出,返回到调用该方法的语句处。如果执行的return语句是第一种格式,则不返回任何值;如果是第二种格式,则返回一个值。return语句3.4.3.4.3 3return语句下述案例示例return语句的第一种格式使用,代码如下所示。【代码3.13】ReturnExample.javapackagecom;publicclassReturnExamplepublicstaticvoidmain(Stringargs)/一个简单的for循环f
104、or(inti=0;i10;i+)System.out.println(i的值是+i);if(i=3)/返回,结束main方法return;System.out.println(return后的输出语句);上述代码中,使用return语句返回并结束main方法,相应for循环也结束。程序运行结果如下:i的值是0return后的输出语句i的值是1return后的输出语句i的值是2return后的输出语句i的值是3本章课后作业见教材JAVA程 序 设计本章学习目标:掌握数组的声明和创建掌握数组的初始化了解二维数组的定义和访问掌握foreach遍历数组了解Arrays类的基本应用第四章第四章数组数组
105、数组是编程语言中常见的一种数据结构,是多个数据类型相同元素的有序集合。数组可用于存储多个数据,每个数组元素存放一个数据,通常可通过数组元素的索引来访问数组元素,包括为数组元素赋值和取出数组元素的值。根据数组中组织结构的不同,数组可以分为一维数组,二维数组和多维数组。第第1节节part创建数组Java数组要求所有的元素具有相同的数据类型。因此,在一个数组中,数组元素的类型是唯一的,即一个数组里只能存储一种数据类型的数据,而不能存储多个数据类型的数据。Java数组既可以存储基本类型的数据,也可以存储引用类型的数据,只要所有的数组元素具有相同的类型即可。值得注意的是,数组也是一种数据类型,它本身是一
106、种引用类型。创建数组本节概述在Java编程语言中,定义数组时并不为数组元素分配内存。只有在初始化后才会为数组中的每一个元素分配空间,并且值得注意的是数组必须经过初始化后才可以引用。Java语言支持两种语法格式来定义数组:dataTypearrayName;dataTypearrayName;对这两种语法格式而言,通常推荐使用第一种格式。因为第一种格式不仅具有更好的语意,而且具有更好的可读性。其中,dataType是数据元素的数据类型,arrayName是用户自定义的数组名称,数组名的命名要符合标识符的命名规则。4.1.4.1.1 1数 组 的声明数组的声明下面代码示例了一维数组的声明。intn
107、umber;/声明一个整型数组floatf;/声明一个单精度浮点型数组doubled;/声明一个双精度浮点型数组booleanb;/声明一个布尔型数组charc;/声明一个字符型数组Stringstr;/声明一个字符串型数组上述代码只是声明了数组变量,在内存中并没有给数组分配空间,因此还不能访问数组中的数据。要访问数组,需在内存中给数组分配存储空间,并指定数组的长度。4.1.4.1.1 1数 组 的声明数组是一种引用类型的变量,因此使用它定义一个变量时,仅仅表示定义了一个引用变量,这个引用变量还未指向任何有效的内存,因此定义数组时不能指定数组的长度。而且由于定义数组只是定义了一个引用变量,并未
108、指向任何有效的内存空间,所以还没有内存空间存储数组元素,因此这个数组也不能使用,只有对数组进行初始化后才可以使用。所谓初始化,就是用关键字new为数组分配内存空间,并为每个数组元素赋初始值。一旦数组的初始化完成,数组在内存中所占的空间将被固定下来,因此数组的长度将不可改变。即使把某个数组元素的数据清空,但它所占的空间依然被保留,依然属于该数组,数组的长度依然不变。数组的初始化有静态初始化和动态初始化两种方式。静态初始化时由程序员显示指定每个数组元素的初始值,由系统决定数组长度;动态初始化时程序员只指定数组长度,由系统为数组元素分配初始值。4.1.4.1.2 2数 组 的 初始化数组的初始化1.
109、静态初始化静态初始化静态初始化的语法格式如下:arrayName=newdataTypenum1,num2,num3.;在上面的语法格式中,dataType就是数组元素的数据类型,此处的dataType必须与定义数组变量时所使用的dataType相同,也可以是定义数组时所指定的dataType的子类,并使用花括号把所有的数组元素括起来,多个元素之间用逗号隔开。下面代码示例了数组的静态初始化。4.1.4.1.2 2数 组 的 初始化【代码4.1】ArrayInit.javapackagecom;publicclassArrayInitpublicstaticvoidmain(Stringargs
110、)/定义一个int数组类型的变量intarrayA;/使用静态初始化方式初始化数组arrayA=newint1,2,3,4;/上述定义数组和初始化可以简化为:intarrayA=1,2,3,4;/定义一个Object类型数组ObjectarrayObject;/定义数组时数组元素类型的子类arrayObject=newStringJava,MySql,单片机技术;2.动态初始化动态初始化动态初始化的语法格式如下:arrayName=newdataTypelength;在上面的语法中,需要指定一个数组长度的length参数,也就是可以容纳数组元素的个数。与静态初始化相似的是,此处的dataTyp
111、e必须与定义数组变量时所使用的dataType相同,或者是定义数组时所指定的dataType的子类。下面代码示例了数组的动态初始化。4.1.4.1.2 2数 组 的 初始化【代码4.2】ArrayInitTwo.javapackagecom;publicclassArrayInitpublicstaticvoidmain(Stringargs)/定义一个int数组类型的变量intarrayA;/使用动态初始化方式初始化数组arrayA=newint4;/定义一个Object类型数组ObjectarrayObject;/初始化数组arrayObject=newString5;需要注意注意的是,不
112、要同时使用静态初始化和动态初始化,也就是说,不要在进行数组初始化时,既指定数组的长度,又为数组元素分配初始值。在执行动态初始化时,系统按如下规则为数组元素分配初始值:1、基本类型中的整数类型(byte、short、int和long),则数组元素默认值为0。2、基本类型中的浮点类型(float和double),则数组元素默认值为0.0。3、基本类型中的字符类型(char),则数组元素默认值为u0000。4、基本类型中的布尔类型(boolean),则数组元素默认值为false。5、引用类型(类、接口和数组),则数组元素的值为null。4.1.4.1.2 2数 组 的 初始化第第2节节part访问数
113、组访问数组数组最常用的用法就是访问数组元素,包括对数组元素进行赋值和取出数组元素的值。访问数组元素中某个元素的语法格式如下:arrayNameindex在上面的语法中,index表示数组的下标索引,其取值范围从0开始,最大值为数组的长度-1。例如,array0表示数组array的第1个元素,array10表示数组array的第11个元素。数组的长度可以通过“数组名.length”进行获取。如果访问数组元素时指定的下标索引值小于0,或者大于等于数组的长度,编译程序不会出现任何错误,但运行时会出现异常:java.lang.ArrayIndexOutOfBoundsException:N(数组索引越
114、界异常),异常信息后的N就是程序员试图访问的数组下标索引。下面案例示例了数组的使用。访问数组访问数组【代码4.3】ArrayDemo.javapackagecom;publicclassArrayDemopublicstaticvoidmain(Stringargs)/声明一个整型数组并动态初始化intarray=newint10;/输出数组array的长度System.out.println(数组array的长度为:+array.length);/为数组赋值for(inti=0;iarray.length;i+)arrayi=(int)(Math.random()*100);/遍历数组arr
115、ay的元素System.out.println(数组array的元素为:);for(inti=0;iarray.length;i+)System.out.print(arrayi+);运行结果如下:数组array的长度为:10数组array的元素为:40284687858410941795第第3节节part冒泡排序算法冒 泡 排 序算法冒泡排序是一种简单的排序算法。这个算法的名字由来是因为越大的元素会经数据交换慢慢“浮”到数列的顶端而得名,它重复访问要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。冒泡排序算法的原理如下:1.比较相邻的元素,如果第一个比第二个大,就交换他们两
116、个。2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。3.针对所有的元素重复以上的步骤,除了最后一个。4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。下面代码示例了冒泡排序的算法。冒泡排序算法冒 泡 排 序算法【代码4.4】Bubbling.javapackagecom;publicclassBubblingpublicstaticvoidmain(Stringargs)/声明一个数组并静态初始化intarray=32,12,45,67,89,54,64,74,80,79;inttemp=0;/定义一个交换元素的中间
117、变量for(inti=1;iarray.length;i+)for(intj=0;jarray.length-i;j+)/判断相邻两个数的大小,并按大小交换位置if(arrayj+1arrayj)temp=arrayj+1;arrayj+1=arrayj;arrayj=temp;/输出排序后的结果System.out.println(数组排序后的结果为:);for(inti=0;iarray.length;i+)System.out.print(arrayi+);运行结果如下:数组排序后的结果为:12324554646774798089第第4节节partforeach遍历数组Java从JDK1
118、.5版本之后,提供了一种更简单的循环:foreach循环。这种循环用于遍历数组或集合更为简洁,使用foreach循环遍历数组或集合元素时,无须获得数组或集合长度,也无须根据索引来访问数组元素或集合元素,foreach循环能自动遍历数组或集合的每个元素。foreach语句的语法结构如下所示:for(数据类型变量名:数组名)注意,foreach语句中的数据类型必须与数组的数据类型一致。foreach遍 历数组Foreach遍历数组下述案例示例了foreach语句的应用,代码如下所示。【代码4.5】ForeachExample.javapackagecom;publicclassForeachExa
119、mplepublicstaticvoidmain(Stringargs)/定义并初始化数组charch=a,b,c,d;/使用foreach语句遍历数组的元素System.out.println(“数组中的元素有:”);for(chare:ch)System.out.print(e+);foreach遍 历数组运行结果如下所示:数组中的元素有:abcd上述代码中foreach语句代码的功能等价于下面一段代码:for(inti=0;ich.length;i+)System.out.print(chi+);第第5节节part二维数组如果一维数组中的每个元素还是一维数组,则这种数组就被称为二维数组。
120、二维数组经常用于解决矩阵方面的问题。定义二维数组的基本语法格式如下:dataTypearrayName;二维数组的创建和初始化与一维数组类似,也可以使用静态初始化和动态初始化两种方式,示例代码如下。二 维 数组二维数组【代码4.6】Array2DExample.javapackagecom;publicclassArray2DExamplepublicstaticvoidmain(Stringargs)/创建二维数组并初始化intarrayA=1,2,3,4,5,6;intarrayB=newint55;for(inti=0;iarrayB.length;i+)for(intj=0;jarra
121、yBi.length;j+)arrayBij=(int)(Math.random()*100);/遍历数组System.out.println(arrayB数组元素如下:);/外循环控制行,内循环控制列每行结束时就换行for(inti=0;iarrayB.length;i+)for(intj=0;jarrayBi.length;j+)System.out.print(arrayBij+);System.out.println();二 维 数组运行结果如下:arrayB数组元素如下:5584772757523132418478882374939508935333448773781下述案例实现了用
122、二维数组实现杨辉三角,代码如下。【代码4.7】YangHui.javapackagecom;publicclassYangHuipublicstaticvoidmain(Stringargs)intn=6;/定义一个二维数组intmat=newintn;inti,j;for(i=0;in;i+)/为二维数组分配空间mati=newinti+1;/为二维数组元素赋值matii=1;for(j=1;ji;j+)matij=mati-1j-1+mati-1j;二 维 数组运行结果如下所示:111121133114641for(i=1;imat.length;i+)/打印空格for(j=1;jn-i;
123、j+)System.out.print();/输出二维数组的元素for(j=1;jmati.length;j+)System.out.print(+matij);System.out.println();第第6节节partArrays类Arrays类是java.util包中的核心类,为了使用在程序中使用该类,必须在程序前导入java.util.Arrays类。该类提供了一系列操作数组的方法,使用这些方法可以完成一些对数组的常见操作,例如排序、检索、复制、比较等,但这些方法都是静态方法,在调用时无须产生Arrays的实例,直接通过类名Arrays来使用这些方法即可。下面仅对Arrays类中几个常
124、用方法进行解释,其他方法的使用可以查考JavaAPI帮助文档。二 维 数组本节概述在前面介绍一维数组时,曾经使用一维数组对一系列整型数据进行冒泡排序,其排序的过程是通过我们自己编写代码实现的,而在Arrays类中提供了一个名为sort的方法,利用它可以直接对数组进行排序,而不需要再编写代码。sort()方法在Arrays类中是重载方法,它不仅提供了对基本数据类型的支持,而且也支持对对象进行排序。下面通过示例演示sort()方法的应用,代码如下。4.6.4.6.1 1数组排序数组排序【代码4.8】SortExample.javapackagecom;importjava.util.Arrays;
125、publicclassSortExamplepublicstaticvoidmain(Stringargs)/创建两个数组intscoreArr=34,56,12,76,54,98,25,58,86,19;StringstrArr=java,Applet,PhP,Basic,Math,Chinese;/对数组进行排序Arrays.sort(scoreArr);Arrays.sort(strArr);System.out.println(遍历排序后的结果为:);for(inte:scoreArr)System.out.print(e+);System.out.println();for(Stri
126、nge:strArr)System.out.print(e+);4.6.4.6.1 1数组排序运行结果如下所示:遍历排序后的结果为:12192534545658768698AppletBasicChineseMathPhPjava从数组中检索指定值是否存在是一个常见操作,类Arrays提供了一系列重载的binarySearch()方法可以用二分查找法对指定数组进行检索。所谓二分查找法,是指在对一个有序序列进行检索时,首先将要检索的值与该序列的中间元素进行比较,如果比较结果不相同,则知道被检索值应当在该比较值之前或之后,这样检索区间就缩小了一半,重复这个过程,最终就可以找到要查找的元素,或者在最
127、后只剩下一个元素并且这个元素与要查找的值不相等时,知道要查找的元素并不存在于这个有序序列中。binarySearch()方法用于在已经排好序的数组中查找元素。如果找到了要查找的元素,则返回一个等于或大于0的值,否则将返回一个负值,表示在该数组目前的排序状态下此目标元素应该插入的位置。负值的计算公式是“-n-1”,n表示第一个大于查找对象的元素在数组中的位置,如果数组中所有元素都小于要查找的对象,则n为数组的长度,如果数组中包含重复元素,则无法保证找到的是哪一个元素。因此,在调用binarySearch()方法对数组进行检索之前,一定要确保被检索的数组是有序的。下面通过示例演示binarySea
128、rch()方法的应用,代码如下。4.6.4.6.2 2数组检索数组检索【代码4.9】BinarySearchExample.javapackagecom;importjava.util.Arrays;publicclassBinarySearchExamplepublicstaticvoidmain(Stringargs)/创建两个数组intscoreArr=34,56,12,76,54,98,25,58,86,19;/对数组进行排序Arrays.sort(scoreArr);inta=Arrays.binarySearch(scoreArr,25);intb=Arrays.binarySea
129、rch(scoreArr,155);System.out.println(检索结果为:+a+和+b);4.6.4.6.2 2数组检索运行结果如下所示:检索结果为:2和-11Arrays类提供了copyOf()方法和copyOfRange()方法实现数组的复制功能。copyOf()方法的第一个参数为源数组,第二个参数为生成的目标数组的元素个数。如果指定的目标数组元素个数小于源数组元素个数,源数组前面的元素将被复制到目标数组中;如果指定的目标数组元素个数大于源数组元素个数,则将源数组所有元素复制到目标数组,目标数组中多出的元素以0或null进行填充。使用copyOfRange()方法可以指定将源数
130、组中的一段元素复制到目标数组。下面示例演示数组复制的应用,代码如下。4.6.4.6.3 3数组复制数组复制【代码4.10】CopyArrayExample.javapackagecom;importjava.util.Arrays;publicclassCopyArrayExamplepublicstaticvoidmain(Stringargs)/声明数组intscoreArr=34,56,12,76,54,98,25;intarrCopy1=newint5;intarrCopy2=newint10;intarrCopy3=newint5;/复制数组arrCopy1=Arrays.copyO
131、f(scoreArr,arrCopy1.length);arrCopy2=Arrays.copyOf(scoreArr,arrCopy2.length);arrCopy3=Arrays.copyOfRange(scoreArr,1,6);/遍历数组System.out.print(arrCopy1数组的元素为:);for(inti=0;iarrCopy1.length;i+)System.out.print(arrCopy1i+);System.out.println();System.out.print(arrCopy2数组的元素为:);4.6.4.6.3 3数组复制4.6.4.6.3 3数
132、组复制for(inti=0;i10;i+)System.out.print(arrCopy2i+);System.out.println();System.out.print(arrCopy3数组的元素为:);for(inti=0;iarrCopy3.length;i+)System.out.print(arrCopy3i+);运行结果如下所示:arrCopy1数组的元素为:3456127654arrCopy2数组的元素为:34561276549825000arrCopy3数组的元素为:5612765498除了上面介绍的方法之外,Arrays类还包含了用于判断两个数组是否相等的方法equals
133、()方法,以及用于填充数组全部或部分元素的方法fill()等,下面通过示例演示数组比较和填充方法的应用,代码如下。4.6.4.6.3 3数组复制【代码4.11】EqualsArrayExample.javapackagecom;importjava.util.Arrays;publicclassEqualsArrayExamplepublicstaticvoidmain(Stringargs)/创建数组intarrayA=1,2,3,4,5;intarrayB=newint5;intarrayC=newint1,2,3,4,5;/判断数组是否相等System.out.println(array
134、A和arrayB是否相等:+Arrays.equals(arrayA,arrayB);System.out.println(arrayA和arrayC是否相等:+Arrays.equals(arrayA,arrayC);Arrays.fill(arrayB,3);/为数组arrayB填充数据/遍历数组arrayB的元素System.out.println(数组arrayB的元素为:);for(inti=0;i120&age0)System.out.println(“你的年龄输入有误,要求0120岁);return;elsethis.age=age;完成类的定义后,就可以使用这种新类型来创建该类
135、的对象。创建对象需要通过关键new为其分配内存空间,其语法格式如下:类名对象名=new构造方法();例如:Personperson=newPerson();上面一行代码使用new关键字创建了Person类的一个对象person。new关键字为对象动态分配内存空间,并返回对它的一个引用,且将该内存初始化为默认值。创建对象也可以分开写,代码如下:Personperson;/声明Person类型的对象personperson=newPerson();/使用new关键字创建对象,给对象分配内存空间5.2.5.2.2 2对象的创建和使用对象的创建和使用声明对象后,如果不想给对象分配内存空间,则可以使用“
136、null”关键字给对象赋值,例如:Personperson=null;“null”关键字表示“空”,用于标识一个不确定的对象,即该对象的引用为空。因此可以将null赋给引用类型变量,但不可以赋给基本类型变量。例如:privateStringname=null;/是正确的privateintage=null;/是错误的null本身虽然能代表一个不确定的对象,但null不是对象,也不是类的实例。null的另外一个用途就是释放内存,在Java中,当某一个非null的引用类型变量指向的对象不再使用时,若想加快其内存回收,可让其指向null,这样该对象将不再被使用,并由JVM拉结回收机制去回收。5.2.
137、5.2.2 2对象的创建和使用创建对象之后,接下来就可以使用该对象。通过使用对象运算符“.”,对象可以实现对自己的变量访问和方法的调用。使用对象大致有以下两个作用:访问对象的属性,即对象的实例变量,格式是“对象名.属性名”。访问对象的方法,格式是“对象名.方法名()”。如果访问权限允许,类里定义的成员变量和方法都可以通过对象来调用,例如:person.display();/调用对象的方法下述代码示例Person对象的创建和使用过程。5.2.5.2.2 2对象的创建和使用packagecom;publicclassPersonExamplepublicstaticvoidmain(Stringa
138、rgs)/创建p1对象Personp1=newPerson();p1.display();/创建p2对象Personp2=newPerson();p2.setName(张三);p2.setAge(20);p2.setAddress(重庆南泉);p2.display();5.2.5.2.2 2对象的创建和使用【代码5.3】PersonExample.java程序运行结果如下:姓名:null年龄:0地址:null姓名:张三年龄:20地址:重庆南泉对象数组就是一个数组中的所有元素都是对象。声明对象数组与普通基本数据类型的数组格式一样,具体格式如下:类名数组名=new类名数组长度;下面语句示例了创建一
139、个长度为5的Person类的对象数组:PersonarrayPerson=newPerson5;上面的语句也可以分成两行,等价于;PersonarrayPerson;arrayPerson=newPerson5;5.2.5.2.3 3对象数组对象数组由于对象数组中的每个元素都是对象,所以每个元素都需要单独实例化,即还需使用new关键字实例化每个元素,代码如下所示。arrayPerson0=newPerson(李四,20,重庆);arrayPerson1=newPerson(张三,21,成都);arrayPerson2=newPerson(王五,22,西安);arrayPerson3=newPe
140、rson(周一,20,北京);arrayPerson4=newPerson(唐二,21,上海);5.2.5.2.3 3对象数组创建对象数组时也可以同时实例化数组中的每个元素对象,此时无须指明对象数组的长度,类似基本数据类型的数组静态初始化。示例代码如下PersonarrayPerson=newPersonnewPerson(李四,20,重庆),newPerson(张三,21,成都),newPerson(王五,22,西安),newPerson(周一,20,北京),newPerson(唐二,21,上海);5.2.5.2.3 3对象数组也可以直接简化成如下代码:PersonarrayPerson=n
141、ewPerson(李四,20,重庆),newPerson(张三,21,成都),newPerson(王五,22,西安),newPerson(周一,20,北京),newPerson(唐二,21,上海);下述代码示例对象数组的应用。【代码5.4】ObjectArrayExample.javapackagecom;publicclassObjectArrayExamplepublicstaticvoidmain(Stringargs)/创建对象数组PersonarrayPerson=newPerson(李四,20,重庆),newPerson(张三,21,成都),newPerson(王五,22,西安),
142、newPerson(周一,20,北京),newPerson(唐二,21,上海);/变量对象数组for(Persone:arrayPerson)e.display();5.2.5.2.3 3对象数组程序运行结果如下:姓名:李四年龄:20地址:重庆姓名:张三年龄:21地址:成都姓名:王五年龄:22地址:西安姓名:周一年龄:20地址:北京姓名:唐二年龄:21地址:上海Java语言类中定义的变量包括成员变量和局部变量两大类。成员变量定义在类体中,局部变量定义在成员方法中。成员变量又分为实例成员变量和类成员变量(全局变量或静态成员变量)。如果一个成员变量的定义前有static关键字,那么它就是类成员变量
143、(静态成员变量),其他形式的成员变量都是实例成员变量。例如:classTestinta=45;/实例成员变量staticintb=34;/类变量voidshow(intx)/方法参数也是局部变量inty=8;/局部变量5.2.5.2.4 4变 量变量成员变量和局部变量可以定义为Java语言中的任何数据类型,包括简单类型和引用类型。成员变量和局部变量的区别如下:成员变量可以定义在整个类体中的任意位置,其有效性与它在类体中书写的先后位置无关,它的作用域是整个类;局部变量的作用域从定义它的位置开始,直到定义它的语句块结束。成员变量和局部变量可以重名,成员方法中访问重名的局部变量时,成员变量被隐藏。如
144、果想在成员方法中访问重名的成员变量,需要在前面加关键字this。成员变量有默认值,但局部变量没有默认值。因此,在使用局部变量之前,必须保证局部变量有具体的值。5.2.5.2.4 4变 量第第3节节part方法类体中定义的方法分为实例成员方法、类成员方法(静态成员方法)和构造方法。方法本节概述5.3.5.3.1 1方 法 的声明方法是类的行为的体现,定义方法的语法格式如下所示:访问符修饰符方法名(参数列表)/方法体其中:访问符用于指明方法的访问权限,可以是public(公共的)、protected(受保护的)、private(私有的)或默认的。修饰符用于指明所定义的方法的特性,可以是abstra
145、ct(抽象的)、static(静态的)、final(最终的)或默认的,这些修饰符在定义类方法不是必需的,需要根据方法的特性进行使用。返回值类型是该方法运行后返回值的数据类型,如果一个方法没有返回值,则该方法的返回类型为void。方法名是指所定义方法的名字,方法名的命名与变量名一样必须符合命名规则,Java中的方法名通常由一个或多个有意义的单词连缀而成,第一个单词的首字母小写,其他单词的首字母大写,其他字母小写。参数列表是方法运行所需要特定类型的参数。方法体是大括号括起来的部分,用于完成方法功能的实现。下面代码示例方法的声明和调用。方法的声明5.3.5.3.1 1方 法 的声明packageco
146、m;publicclassMethodExample/定义一个公共的计算圆面积方法publicdoublearea(doublerouble)returnMath.PI*rouble*rouble;/定义一个私有的静态的求和方法privatestaticintadd(inta,intb)returna+b;/定义一个保护型最终的显示信息方法protectedfinalvoiddisplay()System.out.println(HelloWorld);publicstaticvoidmain(Stringargs)/静态方法通过类名直接调用System.out.println(4+5=+Me
147、thodExample.add(4,5);MethodExampleme=newMethodExample();System.out.println(半径为5的圆面积为:+me.area(5);me.display();【代码5.5】MethodExample.java程序运行结果如下:4+5=9半径为5的圆面积为:78.53981633974483HelloWorld5.3.5.3.2 2方法的参数传递机制方法的参数列表可以带参数,也可以不带参数,是否带参数根据定义方法的具体情况而定,通过参数可以给方法传递数据,例如:publicvoidsetAge(intage)this.age=age;
148、上述代码定义了一个带参数的setAge()方法,参数在方法名的小括号内,一个方法可以带多个参数,多个参数之间使用逗号隔开,例如:privateintadd(inta,intb)returna+b;方法的参数传递机制5.3.5.3.2 2方法的参数传递机制根据参数的使用场合,可以将参数分为“形参”和“实参”两种:形参是声明方法时给方法定义的形式上的参数,此时形参没有具体的数值,形参前必须有数据类型,其格式是“方法名(数据类型形参)”;实参是调用方法时程序给方法传递的实际数据,实参前面没有数据类型,其使用格式是“对象名.方法名(实参)”。形参本身没有具体的数值,需要实参将实际的数值传递给它之后才具
149、有数值。实参和形参之间传递数值的方式有两种:值传递和引用传递。5.3.5.3.2 2方法的参数传递机制1.值值传递传递值传递是将实参的值传递给形参,被调方法为形参创建一份新的内存拷贝来存储实参传递过来的值,然后再对形参进行数值操作。值传递时,实参和形参在内存中占不同的空间,当实参的值传递给形参后,两者之间将互不影响,因此形参值的改变不会影响原来实参的值。在Java中,当参数的数据类型是基本数据类型时,实参和形参之间是按值传递的。下述代码示例参数的值传递。5.3.5.3.2 2方法的参数传递机制【代码5.6】ValueByCallExample.javapackagecom;publicclas
150、sValueByCallExample/定义显示交换参数值的方法publicvoiddisplay(inta,intb)/交换参数a,b的值inttemp=a;a=b;b=temp;System.out.println(display方法里a=+a+,b=+b);publicstaticvoidmain(Stringargs)inta=2,b=8;ValueByCallExamplevbc=newValueByCallExample();vbc.display(a,b);System.out.println(调用display()方法后a=+a+,b=+b);运行结果如下所示:display方
151、法里a=8,b=2调用display()方法后a=2,b=8通过运行结果可以看出:main()方法中的实参a和b,在调用display()方法之前和之后,其值没有发生任何变化;而声明display()方法时的形参a和b,并不是main()方法中的实参a和b,只是main()方法中的实参a和b的复制品,在执行display()方法时形参值发生变化。5.3.5.3.2 2方法的参数传递机制2.引用引用传递传递引用传递时将实参的地址传递给形参,被调方法通过传递的地址获取其指向的内存空间,从而在原来的内存空间直接进行操作。引用传递时,实参和形参指向内存中的同一空间,因此当修改了形参的值时,实参的值也会
152、改变。在Java中,当参数的数据类型是引用类型时,如类、数组,实参和形参之间会是按引用传递值的。下述代码示例参数的引用传递。5.3.5.3.2 2方法的参数传递机制【代码5.7】ReferenceByCallExample.javapackagecom;classMydatapublicinta;publicintb;publicclassReferenceByCallExamplepublicstaticvoiddisplay(Mydatadata)inttemp=data.a;data.a=data.b;data.b=temp;System.out.println(在display方法里,
153、a成员变量的值是:+data.a+b成员变量的值是:+data.b);publicstaticvoidmain(Stringargs)Mydatamd=newMydata();md.a=6;md.b=9;System.out.println(调用display方法前,a成员变量的值是:+md.a+b成员变量的值是:+md.b);display(md);System.out.println(调用display方法后,a成员变量的值是:+md.a+b成员变量的值是:+md.b);运行结果如下所示:调用display方法前,a成员变量的值是:6b成员变量的值是:9在display方法里,a成员变量的
154、值是:9b成员变量的值是:6调用display方法后,a成员变量的值是:9b成员变量的值是:65.3.5.3.2 2方法的参数传递机制通过执行结果可以看出,data对象的成员变量a和b在调用display()方法前后发生了变化,因为被传递的是一个对象,对象是引用类型,所以实参会将地址传递给形参,它们都指向内存同一个存储空间,此时的形参相当于实参的别名,形参值的改变会直接影响到实参值的改变。5.3.5.3.3 3构 造 方法构造方法是类的一个特殊方法,用于创建对象是初始化对象中的属性值。它具有如下特点。(1)访问权限符一般是public,也可以是其他访问符。(2)没有返回值,也不能有void修饰
155、符。(3)方法名称必须和类名相同。(4)如果没有定义构造方法,系统自动定义默认的构造方法,该构造方法不带参数,将类成员属性进行默认赋值;如果为类定义了构造方法,系统将不创建默认构造方法,而执行用户定义的构造方法。(5)构造方法可以重载,通过构造方法中参数个数不同、参数类型不同及顺序的不同实现构造方法的重载。(6)在生成类的对象时调用,一个类中如果定义了多个构造方法,则根据参数自动选择调用相应的构造方法。构造方法定义的语法结构如下所示。访问符构造方法名(参数列表)/初始化语句构造方法5.3.5.3.3 3构 造 方法【代码5.8】Person.javapackagecom;publicclass
156、Person/声明类/声明属性或成员变量privateStringname;privateintage;privateStringaddress;/声明构造方法publicPerson()/不带参数的构造方法publicPerson(Stringname,intage,Stringaddress)this.name=name;this.age=age;this.address=address;publicvoiddisplay()System.out.println(姓名:+name+年龄:+age+地址:+address);publicstaticvoidmain(Stringargs)Pe
157、rsonp1=newPerson();p1.display();Personp2=newPerson(李四,20,重庆南泉);p2.display();运行结果如下所示:姓名:null年龄:0地址:null姓名:李四年龄:20地址:重庆南泉5.3.5.3.3 3构 造 方法上述代码中为Person类增加了两个构造方法,一个是不带参数的构造方法,一个是带三个参数的构造方法。我们可以看出,通过不带参数的构造方法创建的对象,其属性赋予了默认值,通过带参数的构造方法创建的对象,其参数值赋给了对象属性值。由于构造方法三个参数名与类中定义的属性名相同,为了避免在赋值过程中产生混淆,所以使用“this”关键
158、字进行区分。5.3.5.3.4 4方 法 的重载在Java语言程序中,如果同一个类中包含了两个或两个以上方法的方法名相同,但参数列表不同,则被称为方法重载,方法的重载也是多态的一种。对于重载的方法,编译器是根据方法的参数来进行方法绑定的。根据方法重载的概念,也可以将方法重载分成三种类型,分别是参数类型的重载、参数数量的重载和参数顺序的重载。参数类型的重载是当实现重载方法的参数个数相同时,而参数的类型不同;参数数量的重载是指实现重载的方法的参数数量不同;最后一种重载是相同参数数量的方法,但参数顺序不同。注意,方法的返回值类型不是方法签名的一部分,因此进行方法重载的时候,不能将返回值类型的不同当成
159、两个方法的区别。方法重载必须遵守以下三条原则:(1)在同一个类中;(2)方法名相同(3)方法的参数类型、个数及顺序至少有一项不同。下述代码示例了方法的重载,代码如下所示。方法的重载5.3.5.3.4 4方 法 的重载【代码5.9】OverloadExample.javaPackagecom;publicclassOverloadExample/计算正方形面积的方法publicintarea(inta)returna*a;/计算圆面积的方法publicdoublearea(doublea)returnMath.PI*a*a;/计算长方形面积的方法publicdoublearea(inta,dou
160、bleb)returna*b;/计算圆柱体表面积的方法publicdoublearea(doublea,intb)return2*Math.PI*a*a+2*Math.PI*a*b;publicstaticvoidmain(Stringargs)OverloadExampleoe=newOverloadExample();System.out.println(正方形面积为:+oe.area(5);System.out.println(圆面积为:+oe.area(5.0);System.out.println(长方形面积为:+oe.area(5,6.0);System.out.println(圆
161、柱体表面积为:+oe.area(5.0,5);运行结果如下所示:正方形面积为:25圆面积为:78.53981633974483长方形面积为:30.0圆柱体表面积为:314.15926535897935.3.5.3.5 5static关 键字static用来修饰类的成员变量和成员方法,同时与可以修饰代码块,表示静态的代码块。静态的成员变量和成员方法独立于类的对象,被类的所有实例共享。因此可不生成类的任何对象,直接通过类实现对静态成员的访问。当类加载后,Java虚拟机能根据类名在运行时数据区的方法区内找到它们。访问类的静态变量和静态方法时,可以直接通过类名访问,语法格式如下:类名.静态方法名(形参
162、列表);类名.静态变量名;static关键字5.3.5.3.5 5static关 键字当Java虚拟机加载类时,静态代码块被一次性执行,称为加载时执行。若类中有多个静态代码块,JVM将按照它们出现的先后依次执行,且每个代码块只被执行一次。类的方法可以相互调用,但当一个方法定义为静态方法时,它可以直接访问类的静态变量,调用类的静态方法,但不能直接访问类的非静态变量和调用类的非静态方法,只能通过生成类的对象,并通过该对象访问相应的变量和调用相应的方法。同时静态的方法不能以任何方式引用this和super关键字,因为静态方法独立于类的任何对象,因此静态方法必须被实现,而不能定义静态抽象方法。利用静态
163、代码块可对static类变量赋值。下述示例示范了类的static方法、static变量以及static代码块的定义和使用。5.3.5.3.5 5static关 键字【代码5.10】StaticDemoExample.javapackagecom;classStaticTextinta;staticintb;static/a=10;调试出错,静态块中不能访问非静态变量b=15;/静态块中可以访问静态变量 System.out.println(这是StaticText类的静态语句块!);publicstaticvoiddisplay()/System.out.println(a=+a);静态方法中
164、不能访问非静态变量System.out.println(b=+(b+10);/静态方法中可以访问静态变量publicclassStaticDemoExamplepublicstaticvoidmain(Stringargs)StaticText.display();System.out.println(b=+StaticText.b);运行结果如下所示:这是StaticText类的静态语句块!b=25b=15static成员最常见的例子是main()方法,因为在程序开始执行时必须调用主类的main()方法,JVM在运行main()方法时可以直接调用而不用创建主类对象,所以该方法被声明为stat
165、ic。5.3.5.3.6 6this关 键字this是Java语言中一个非常重要的关键字,用来表示当前实例。它可以出现在实例成员方法和构造方法中,但不可以出现在类成员方法中。this关键字有两种用法,其一是作用在成员方法和构造方法中,代表正在调用该方法的实例,如果方法的参数和类中的成员变量重名时,必须使用this关键字,以区分实例变量和成员方法变量;如果方法的参数名和成员变量名不重名时,this可以省略。下述代码示例了this的第一种用法,示例代码如下。this关键字5.3.5.3.6 6this关 键字【代码5.11】ThisFirstExample.javapackagecom;publi
166、cclassThisFirstExampleprivatedoublelength;privatedoublewidth;privatedoubleheight;publicThisFirstExample(doublelength,doublew,doubleh)this.length=length;/this不能省略this.width=w;height=h;/this可以省略publicdoublegetLength()returnthis.length;publicvoidsetLength(doublelength)/参数名与实例变量名重名,不可以省略this.length=leng
167、th;publicdoublegetWidth()returnwidth;5.3.5.3.6 6this关 键字publicvoidsetWidth(doublew)/参数名与实例变量名不重名,可以省略this.width=w;publicdoublegetHeight()returnheight;publicvoidsetHeight(doubleheight)this.height=height;publicvoiddisplay()/this可以有也可以省略System.out.println(“长方形的长为:”+this.length+“,宽为:”+width+“,高为:”+heigh
168、t);publicstaticvoidmain(Stringargs)ThisFirstExampletfe=newThisFirstExample(3,4,5);tfe.display();程序运行结果如下:长方形的长为:3.0,宽为:4.0,高为:5.05.3.5.3.6 6this关 键字this关键字的第二种用法,就是放在构造方法中,使用this(参数列表)的方式,来调用类中其他的构造方法,而且this(参数列表)必须放在第一行。下述代码示例了this的第二种用法,示例代码如下。【代码5.12】ThisSecondExample.javapackagecom;publicclassTh
169、isSecondExampleprivatedoublelength;privatedoublewidth;privatedoubleheight;publicThisSecondExample()publicThisSecondExample(doublelength)this.length=length;publicThisSecondExample(doublelength,doublewidth)this(length);this.width=width;5.3.5.3.6 6this关 键字publicThisSecondExample(doublelength,doublewidt
170、h,doubleheight)this(length,width);this.height=height;/this可以省略publicvoiddisplay()/this可以有也可以省略System.out.println(长方形的长为:+this.length+,宽为:+this.width+,高为:+this.height);publicstaticvoidmain(Stringargs)ThisSecondExampletfe=newThisSecondExample(3,4,5);tfe.display();程序运行结果如下:长方形的长为:3.0,宽为:4.0,高为:5.05.3.5
171、.3.7 7可变参数前面讲述方法时定义的参数个数都是固定的,而从JDK1.5之后,Java允许定义方法时参数的个数可以变化,这种情况称为“可变参数”。定义可变参数非常简单,只需在方法的最后一个参数的数据类型后增加3个点()即可,其具体语法格式如下:访问符修饰符方法名(参数列表,数据类型变量)/方法体可变参数需要注意以下几点:可变参数只能处于参数列表的最后。一个方法中最多只能包含一个可变参数。可变参数的本质就是一个数组,因此在调用一个包含可变参数的方法时,既可以传入多个参数,也可以传入一个数组。下述代码示例了可变参数的应用。可变参数5.3.5.3.7 7可变参数【代码5.13】ChangePar
172、amExample.javapackagecom;publicclassChangeParamExamplepublicstaticintadd(inta,int.b)intsum=a;for(inti=0;ib.length;i+)sum+=bi;returnsum;publicstaticvoidmain(Stringargs)inta=1,2,3,4,5;System.out.println(3+4=+add(3,4);System.out.println(“3+4+5=”+add(3,4,5);System.out.println(3+数组a的所有元素=+add(3,a);运行结果如下
173、所示:3+4=73+4+5=123+数组a的所有元素=18上述代码在调用add()方法时,参数列表中除了第一个参数,剩下的参数都以数组的形式传递给可变参数,因此,可以给可变参数传递多个参数,也可以直接传入一个数组。第第4节节part包Java引入包(package)的机制,提供了类的多层命名空间,解决类的命名冲突、类文件管理等问题。包可以对类进行组织和管理,使其与其他源代码库中的类分隔开,只需保证同一个包中不存在同名的类即可,以确保类名的唯一性,避免类名的重复。借助于包可以将自己定义的类与其他类库中的类分开管理。Java的基础类库就是使用包进行管理的,例如,java.lang包、java.ut
174、il包等。在不同的包中,类名可以相同,例如,java.util.Date类和java.sql.Date类,这两个类的类名都是Date,但分别属于java.util包和java.sql包,因此能够同时存在。包本节概述5.4.5.4.1 1包的声明定义包的语法格式如下:package包名;使用package关键字可以指定类所属的包,定义包需要注意以下几点:package语句必须作为Java源文件的第一条非注释性语句。一个Java源文件只能指定一个包,即只有一条package语句,不能有多条package语句。定义包以后,Java源文件中可以定义多个类,这些类将全部位于该包下。多个Java源文件可以
175、定义相同的包,这些源文件将全部位于该包下。包的声明5.4.5.4.1 1包的声明下述代码示例了包的定义packagecom;上述语句声明了一个名为com的包。在物理上,Java使用文件夹目录来组织包,任何声明了“packagecom”的类,编译后形成的字节码文件(.class)都被存储在一个com目录中。与文件目录一样,包与可以分成多级,多级的包名之间使用“.”进行分隔。例如:packagecom.zdsoft.wlw;上述语句在物理上的表现形式将是嵌套的文件目录,即“comzdsoftwlw”目录,所有声明了“packagecom.zdsoft.wlw”的类,其编译结果都被存储在wlw子目录
176、下。5.4.5.4.2 2包的导入Java中一个类可以访问其所在包中的其他所有的类,但是如果需要访问其他包中的类,则可以使用import语句导入包。Java中导入包的语法格式如下:import包名.*;/导入指定包中所有的类或import包名.类名;/导入指定包中指定的类注意,*指明导入当前包中的所有类,但不能使用“java.*”或“java.*.*”这种语句来导入以java为前缀的所有包的所有类。一个Java源文件只能有一条package语句,但可以有多条import语句,且package语句必须在import语句之前。导入包以后,可以在代码中直接访问包中的相关类。当然,也可以不使用impo
177、rt语句倒入相应的包,只需在使用的类名前直接添加完整的包名即可。例如:java.util.DatenewDate=newjava.util.Date();当程序中导入两个或多个包中同名的类后,如果直接使用类名,编译器将无法区分,此时就可以使用上述方式,在类名前面加上完全限定的包名。包的导入5.4.5.4.2 2包的导入下面代码示例包的使用。【代码5.14】PackageDemo.javapackagecom.zdsoft.wlw;publicclassPackageDemoprivateStringname;privateintage;publicPackageDemo(Stringname,
178、intage)this.name=name;this.age=age;publicvoiddisplay()System.out.println(“姓名为:”+name+“,年龄为:”+age);上述代码中使用package关键字定义了一个com.zdsoft.wlw包,则源代码中声明的相关类就属于此包,其他包中的类要想访问此包中的类则必须导入此包。如下代码所示。【代码5.15】PackageDemoExample.javapackagecom;importcom.zdsoft.wlw.PackageDemo;publicclassPackageDemoExamplepublicstaticv
179、oidmain(Stringargs)PackageDemopd=newPackageDemo(张三,20);pd.display();程序运行结果如下:姓名为:张三,年龄为:205.4.5.4.3 3Java的 常用包Java的核心类都放在java这个包及其子包下,Java扩展的许多类都放在javax包及其子包下。这些使用类就是应用程序接口。下面几个包是Java语言中的常用包。java.lang:这个包下包含了Java语言的核心类,如String、Math、System和Thread类等,使用这个包下的类无须使用import语句导入,系统会自动导入这个包下的所有类。java.util:这个包
180、下包含了Java的大量工具类/接口和集合框架类/接口,例如Arrays、List和Set等。:这个包下包含了一些Java网络编程相关的类/接口。java.io:这个包下包含了一些Java输入/输出编程相关的类/接口。java.text:这个包下包含了一些Java格式化相关的类。java.sql:这个包下包含了Java进行JDBC数据库编程相关的类/接口。java.awt:这个包下包含了抽象窗口工具集和相关类/接口,这些类主要用于构建图形用户界面(GUI)程序。java.swing:这个包下包含了Swing图形用户界面编程的相关类/接口,这些类可用于构建平台无关的GUI程序。Java的常用包5.
181、4.5.4.4 4垃圾回收机制Java语言中很多对象的数据(成员变量)都存放在堆中,对象从中分配空间,Java虚拟机的堆中存储着正在运行的应用程序所建立的所有对象,这些对象通过new等关键字建立,但是它们不需要程序代码来显示地释放。一般来说,堆中的无用对象是由垃圾回收器来负责的,尽管Java虚拟机规范并不要求特殊的垃圾回收技术,甚至根本就不需要垃圾回收,但是由于内存的有限性,虚拟机在实现的时候都有一个由垃圾回收器管理的堆。垃圾回收是一种动态存储管理技术,它自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现资源自动回收的功能。当一个对象不再被引用时,垃圾收集器会在适当的时候将其销毁,适
182、当的时候,即意味着无法预知何时垃圾收集器会进行工作。当对象被回收的时候,finalize()方法会被虚拟机自动调用,如果重写了finalize()方法,这个方法也会被自动执行。一般用户不自行调用finalize()方法。垃圾回收机制第第5节节part访问权限修饰符面向对象的分类性需要用到封装,为了实现良好的封装,需要从以下两个方面考虑:将对象的成员变量和实现细节隐藏起来,不允许外部直接访问;把方法暴露出来,让方法对成员变量进行安全访问和操作。因此,封装实际上把该隐藏的隐藏,该暴露的暴露,这些都需要通过Java访问权限修饰符来实现。Java的访问权限修饰符对类、属性和方法进行声明和控制,以便隐藏
183、类的一些实际细节,防止对封装数据未经授权的访问和不合理的操作。实现封装的关键是不让外界直接与对象属性交互,而是要通过指定的方法操作对象的属性。Java提供了四个访问权限修饰符,分别是private(私有的)、protected(受保护的)、public(公共的)和默认的。其中,默认访问符又称友好的(friendly),而friendly不是关键字,它只是一种默认访问权限修饰符的称谓而以。访问权限修饰符访问权限修饰符Java的访问权限修饰符级别由小到大如图5.4所示。访问权限修饰符这四个访问权限修饰符级别的详细介绍如下。private(当前类访问权限):如果类中的一个成员(包括属性、成员方法和构
184、造方法等)使用private访问权限修饰符来修饰,则这个成员只能在当前类的内部被访问。很显然,这个访问权限修饰符用来修饰属性最合适,使用它来修饰属性就可以把属性隐藏在该类的内部。访问权限修饰符默认的(包访问权限):如果类中的一个成员(包括属性、成员方法和构造方法等)或者一个外部类不使用任何访问权限修饰符,我们称它是包访问权限,默认修饰符控制的成员和外部类可以被相同包下的其他类访问。protected(子类访问权限):如果类中的一个成员(包括属性、成员方法和构造方法等)使用protected访问权限修饰符来修饰,那么这个成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问。在通常情况下
185、,如果使用protected来修饰一个方法,是希望其子类来重写这个方法。public(公共访问权限):这是一个最宽松的访问控制级别,如果类中的一个成员(包括属性、成员方法和构造方法等)或者一个外部类使用public访问权限修饰符,那么这个成员或者外部类就可以被所有类访问,不管访问类和被访问类是否处于同一个包中,是否具有父子继承关系。访问权限修饰符最后,我们使用表5-1来总结上述的访问权限修饰符的使用范围。通过上面关于访问权限修饰符的介绍不难发现,访问权限修饰符用于控制一个类的成员是否被其他类访问,对于局部变量而言,其作用域仅在它所在的方法内,不可能被其他类访问,因此不能使用访问权限控制符来修饰
186、。访问权限修饰符对于外部类而言,只能有两种访问权限修饰符:public和默认的,外部类不能使用private和protected来修饰,因为外部类没有处于任何类的内部,也就没有其所在类的内部、所在类的子类两个范围,因此private和protected访问权限修饰符对外部类没有任何意义。外部类使用public修饰,可以被当前项目下的所有类使用,使用默认修饰符的外部类只能被同一个包中的其他类使用。如果一个Java源文件里定义的所有类都没有使用public修饰,则这个Java源文件的文件名可以是一切合法的文件名;但如果一个Java源文件里定义了一个public修饰的类,则这个源文件的文件名必须与p
187、ublic修饰的类同名。掌握了访问权限修饰符的用法之后,下面通过示例来示范它们的具体使用方法。首先,我们创建一个项目accessController,在该项目下声明一个com包和test包,在com包中创建一个Student源文件和Test源文件,在Student源文件中创建一个public类和默认类,每个类中有四种访问权限修饰符的属性和方法。该类的源代码如下。访问权限修饰符【代码5.16】Student.javapackagecom;publicclassStudentpublicStringstuid;/学号Stringname;/姓名protectedintage;/年龄privateS
188、tringsex;/性别publicvoiddispalyStuid()/显示学号System.out.println(学生的学号为:+stuid);voiddisplayName()/显示姓名System.out.println(学生的姓名为:+name);protectedvoiddisplayAge()/显示年龄System.out.println(“学生的年龄为:”+age);privatevoiddisplaySex()/显示性别System.out.println(“学生的性别为:”+sex);访问权限修饰符classTeacherpublicStringteaid;/学号Stri
189、ngname;/姓名protectedintage;/年龄privateStringsex;/性别publicvoiddispalyTeaid()/显示学号System.out.println(“老师的教师号为:”+teaid);voiddisplayName()/显示姓名System.out.println(“老师的姓名为:”+name);protectedvoiddisplayAge()/显示年龄System.out.println(老师的年龄为:+age);privatevoiddisplaySex()/显示性别System.out.println(老师的性别为:+sex);访问权限修饰
190、符com包中的Test类是对Student源文件中的两个类进行访问,分别访问public修饰类和默认修饰类的所有属性和所有方法。程序代码如下。【代码5.17】Test.javapackagecom;publicclassTestpublicstaticvoidmain(Stringargs)/创建同一个包中的公共类和默认类对象Studentstu=newStudent();Teachertea=newTeacher();/访问public修饰符修饰类中四种修饰符修饰的属性stu.stuid=001;stu.name=张三;stu.age=20;stu.sex=男;/错误,sex为私有属性,类外
191、访问出错/访问public修饰符修饰类中四种修饰符修饰的方法stu.dispalyStuid();stu.displayName();stu.displayAge();访问权限修饰符/错误,dispalySex()为私有方法,类外访问出错stu.dispalySex();/访问默认修饰符修饰类中四种修饰符修饰的属性tea.teaid=054;tea.name=向老师;tea.age=40;/错误,sex为私有属性,类外访问出错tea.sex=男;/访问默认修饰符修饰类中四种修饰符修饰的方法tea.dispalyTeaid();tea.displayName();tea.displayAge()
192、;/错误,dispalySex()为私有方法,类外访问出错tea.dispalySex();访问权限修饰符上述代码在调试的时候报错,主要是私有修饰符的方法和属性在其他类中不能访问,注销掉出错代码,程序运行结果如下:学生的学号为:001学生的姓名为:张三学生的年龄为:20老师的教师号为:054老师的姓名为:向老师老师的年龄为:40访问权限修饰符在test包中创建ExtendsTest类,该类继承Student类,在该类中的main()方法中对创建ExtendsTest类的对象,对该类中的属性和方法进行访问。代码如下:【代码5.18】ExtendsTest.javapackagetest;impo
193、rtcom.Student;publicclassExtendsTestextendsStudentpublicstaticvoidmain(Stringargs)ExtendsTeststu=newExtendsTest();/访问public修饰符修饰类中的属性stu.stuid=“001”;/公共属性,任何地方都可以访问stu.name=“张三”;/错误,默认类型属性,不同包中的子类无法访问stu.age=20;/受保护类型属性,不同包中的子类可以访问/访问public修饰符修饰类中的方法stu.dispalyStuid();/公共方法,任何地方都可以访问stu.displayName(
194、);/错误,默认类型方法,不同包中的非子类无法访问stu.displayAge();/受保护类型方法,不同包中的子类可以访问上述代码在调试的时候报错,主要是默认修饰符的方法和属性在其他包中的类不能访问,注销掉出错代码,程序运行结果如下:学生的学号为:001学生的年龄为:20访问权限修饰符在test包中创建TestTwo类,在该类中的main()方法中对创建Student类和Teacher类的对象,对Student类中的属性和方法进行访问。代码如下:【代码5.19】TestTwo.javapackagetest;importcom.Student;importcom.Teacher;/错误,不能
195、导入其他包中默认类publicclassTestTwopublicstaticvoidmain(Stringargs)/创建同一个包中的公共类和默认类对象Studentstu=newStudent();/访问public修饰符修饰类中的属性stu.stuid=001;/公共属性,任何地方都可以访问stu.age=20;/错误,受保护类型属性,不同包中的非子类无法访问/访问public修饰符修饰类中的方法stu.dispalyStuid();/公共方法,任何地方都可以访问stu.displayAge();/错误,受保护类型方法,不同包中的非子类无法访问上述代码在调试的时候报错,主要是protec
196、ted修饰符的方法和属性在其他包中的非子类不能访问,注销掉出错代码,程序运行结果如下:学生的学号为:001第第6节节part单例类在大部分时候,我们把类的构造方法都定义成public访问权限,允许任何类自由创建该类的对象。但在某些时候,允许其他类自由创建该类的对象没有任何意义,还可能造成系统性能下降。例如,系统可能只有一个窗口管理器、一个假脱机打印设备或一个数据库引擎访问点,此时如果在系统中为这些类创建多个对象就没有太大的实际意义。如果一个类始终只能创建一个实例,则这个类被称为单例类。在一些特殊场景下,要求不允许自由创建该类的对象,而只允许为该类创建一个对象。为了避免其他类自由创建该类的对象,
197、我们可以把该类的构造方法使用private修饰,从而把该类的所有构造方法隐蔽起来。单 例类单例类根据良好封装的原则:一旦把该类的构造方法隐蔽起来,就需要提供一个public方法作为该类的访问点,用于创建该类的对象,且该方法必须使用static修饰(因为调用该方法之前还不存在对象,因此调用该方法的不可能是对象,只能是类)。除此之外,该类还必须缓存以经创建的对象,否则该类无法知道是否曾经创建过对象,也就无法保证只创建一个对象。为此该类需要使用一个成员变量来保存曾经创建的对象,因此该成员变量需要被静态方法访问,故该成员变量必须使用static修饰。下述代码示例了单例类的创建和使用。单 例类【代码5.
198、20】SingletonExample.javapackagecom;classSingleton/声明一个变量用来缓存曾经创建的对象privatestaticSingletoninstance;privateSingleton()/隐藏构造方法/声明一个创建该类对象的静态方法publicstaticSingletongetInstance()if(instance=null)/实例化一个对象,将其缓存起来instance=newSingleton();returninstance;单 例类publicclassSingletonExamplepublicstaticvoidmain(Stri
199、ngargs)/通过getInstance()方法创建该类的两个对象Singletons1=Singleton.getInstance();Singletons2=Singleton.getInstance();/比较两个对象是否相等,结果为true,表示是同一个对象。System.out.println(s1.equals(s2);上述代码中正是通过getInstance()方法提供的自定义控制,从而保证Singleton类只能产生一个实例。所以在SingletonExample类的main()方法中,看到两次产生的Singleton类对象实际是同一个对象。本章课后作业见教材JAVA程 序
200、设计本章学习目标:掌握基本类型的封装类的使用掌握Object类的使用掌握字符串类的使用掌握Scanner、Math和日期类的使用理解格式化处理的应用第六章第六章Java常用类常用类本章主要介绍Java系统提供的一些程序员在项目开发中经常用到的类和方法。掌握了这些基本类与常用方法的应用,为以后项目开发和深入学习打好基础。第第1节节part基本类型的封装类Java为其8个基本数据类型提供了对应的封装类,通过这些封装类可以把8个基本类型的值封装成对象进行使用。从JDK1.5开始,Java允许将基本类型的值直接赋值给对应的封装类对象。8个基本数据类型对应的封装类如表6-1所示。基本类型的封装类基本类型
201、的封装类基本类型的封装类除了Integer和Character写法有点特殊例外,其他的基本类型对应的封装类都是将首字母大写即可。从JDK1.5之后,Java提供了自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)功能,因此,基本类型变量和封装类之间可以直接赋值,例如:Integerobj=10;intb=obj;自动装箱和自动拆箱大大简化了基本数据类型和封装类之间的转换过程,但进行自动装箱和拆箱操作时,必须注意类型匹配,例如Integer只能和int匹配,不能跟boolean或char等其他类型匹配基本类型的封装类除此之外,封装类还可以实现基本类型变量和字符串之间的转换:基
202、本类型的封装类2、将基本类型的值转换成字符串有三种方式:直接使用一个空字符串来连接数值即可。调用封装类提供的toString()静态方法。调用String类提供的valueOf()静态方法。例如,将基本类型的值转换为字符串Strings1=+23;Strings2=Integer.toString(100);Strings3=String.valueOf(66);1、将字符串的值转换为基本类型的值有两种方式:直接利用封装类的构造方法,即Xxx(Strings)构造方法调用封装类提供的parseXxx(Strings)静态方法例如:将字符串的值转换为基本类型intnum1=newInteger(
203、10);intnum2=Integer.parseInt(123);如图6.1所示演示了基本类型变量和字符串之间的转换。基本类型的封装类下述案例示例了Java基本数据类型变量和封装类之间转换的使用,代码如下所示。【代码6.1】FengZhangExample.javapackagecom;publicclassFengZhangExamplepublicstaticvoidmain(Stringargs)/直接把一个整数值赋给Integer对象IntegerintObj=5;/直接把一个boolean值赋给一个Boolean对象BooleanboolObj=true;/Integer对象与整数
204、进行算数运算inta=intObj+10;System.out.println(a);System.out.println(boolObj);/字符串与基本类型变量之间的转换StringintStr=123;/把一个特定字符串转换成int变量intit1=Integer.parseInt(intStr);intit2=newInteger(intStr);System.out.println(it1+,+it2);基本类型的封装类StringfloatStr=4.56f;/把一个特定字符串转换成float变量floatft1=Float.parseFloat(floatStr);floatft
205、2=newFloat(floatStr);System.out.println(ft1+,+ft2);/将一个double类型变量转换为字符串Stringds1=+3.14;Stringds2=Double.toString(3.15D);Stringds3=String.valueOf(3.16d);System.out.println(ds1+,+ds2+,+ds3);/将一个boolean类型变量转换为字符串Stringbs1=+true;Stringbs2=Boolean.toString(true);Stringbs3=String.valueOf(true);System.out.
206、println(bs1+,+bs2+,+bs3);基本类型的封装类程序运行结果如下:15true123,1234.56,4.563.14,3.15,3.16true,true,true第第2节节partObject类Java基础类库提供了一些常用的核心类,包括Object、String、Math等。其中,Object对象类定义在java.lang包中,是所有类的顶级父类。在Java体系中,所有类都直接或间接地继承了Object类。因此,任何Java对象都可以调用Object类中的方法,而且任何类型的对象都可以赋给Object类型的变量。Object类提供了所有类都需要的一些方法,常用的方法及描
207、述如表6-2所示。Object类本节概述6.2.6.2.1 1equals()方法两个基本类型的变量比较是否相等时直接使用“=”运算符即可,但两个引用类型的对象比较是否相等时则有两种方式:使用“=”运算符或使用equals()方法。在比较两个对象是否相等时,“=”运算符和equals()方法是有区别的:“=”运算符比较的是两个对象地址是否相同,即引用的是同一个对象。equals()方法通常可以用于比较两个对象的内容是否相同。下述案例分别使用“=”运算符和equals()方法来判断两个对象是否相等,代码如下所示。equal()方法6.2.6.2.1 1equal s方法【代码6.2】Object
208、EqualsExample.javapackagecom;publicclassObjectEqualsExamplepublicstaticvoidmain(Stringargs)/定义4个整型类对象Integernum1=newInteger(8);Integernum2=newInteger(10);Integernum3=newInteger(8);/将num1对象赋值给num4Integernum4=num1;System.out.println(num1和自身进行比较:);/分别使用=和equals()方法对num1进行自身比较System.out.println(num1=num
209、1是+(num1=num1);System.out.println(num1.equals(num1)是+num1.equals(num1);System.out.println();System.out.println(num1和num2两个不同值的对象进行比较:);/num1和num2两个不同值的对象进行比较System.out.println(num1=num2是+(num1=num2);System.out.println(num1.equals(num2)是+num1.equals(num2);System.out.println();6.2.6.2.1 1equal s方法Syst
210、em.out.println(num1和num3两个相同值的对象进行比较:);/num1和num3两个相同值的对象进行比较/num1和num3引用指向的对象的值一样,但对象空间不一样System.out.println(num1=num3是+(num1=num3);System.out.println(“num1.equals(num3)是”+num1.equals(num3);System.out.println();System.out.println(num1和num4两个同一引用的对象进行比较:);/num2和num4引用指向同一个对象空间System.out.println(num2
211、=num4是+(num1=num4);System.out.println(num2.equals(num4)是+num1.equals(num4);6.2.6.2.1 1equal s方法上述代码中num1对象分别跟自身num1、不同值num2、相同值num3以及同一引用num4这几个对象进行比较,通过分析运行结果可以得出:使用“=”运算符将严格比较这两个变量引用是否相同,即地址是否相同,是否指向内存同一空间,只有当两个变量指向同一个内存地址即同一个对象时才返回true,否则返回false;Integer的equals()方法则比较两个对象的内容是否相同,只要两个对象的内容值相等,哪怕是两个
212、不同的对象(引用地址不同),依然会返回true。程序运行结果如下:num1和自身进行比较:num1=num1是truenum1.equals(num1)是truenum1和num2两个不同值的对象进行比较:num1=num2是falsenum1.equals(num2)是falsenum1和num3两个相同值的对象进行比较:num1=num3是falsenum1.equals(num3)是truenum1和num4两个同一引用的对象进行比较:num2=num4是truenum2.equals(num4)是true6.2.6.2.2 2toString()方法Object类的toString()
213、方法是一个非常特殊的方法,它是一个“自我描述”的方法,该方法返回当前对象的字符串表示。当使用System.out.println(obj)输出语句直接打印对象时,或字符串与对象进行连接操作时,例如:info+obj,系统都会自动调用对象的toString()方法。Object类中的toString()方法返回包含类名和散列码的字符串,具体格式如下:类名哈希代码值下述案例定义了一个Book.java类,并重写了toString()方法,代码如下所示。toString()方法6.2.6.2.2 2toString()方法packagecom;publicclassBook/属性privateStr
214、ingbookName;/书名privatedoubleprice;/价格privateStringpublisher;/出版社/默认构造方法publicBook()/重载构造方法publicBook(StringbookName,doubleprice,Stringpublisher)this.bookName=bookName;this.price=price;this.publisher=publisher;publicStringgetBookName()returnbookName;publicvoidsetBookName(StringbookName)this.bookName=
215、bookName;6.2.6.2.2 2toString()方法publicdoublegetPrice()returnprice;publicvoidsetPrice(doubleprice)this.price=price;publicStringgetPublisher()returnpublisher;publicvoidsetPublisher(Stringpublisher)this.publisher=publisher;/重写toString()方法publicStringtoString()returnthis.bookName+,¥+this.price+,+this.pu
216、blisher;上述代码重写了toString()方法,该方法将3个属性值连成一个字符串并返回。6.2.6.2.2 2toString()方法下述案例编写了一个测试类,示例了toString()方法的功能,代码如下所示。【代码6.4】BookExample.javapackagecom;publicclassBookExamplepublicstaticvoidmain(Stringargs)Bookb1=newBook(Java面向对象程序设计,38,重庆大学出版社);System.out.println(b1);Bookb2=newBook(MySql数据库程序设计,26,清华大学出版社)
217、;Strings=b1+n+b2;System.out.println(s);上述代码使用System.out.println()直接输出对象b1的信息,以及将b1和b2进行字符串连接,运行出来结果如下所示:Java面向对象程序设计,¥38.0,重庆大学出版社Java面向对象程序设计,¥38.0,重庆大学出版社MySql数据库程序设计,¥26.0,清华大学出版社6.2.6.2.2 2toString()方法将Book类中重写的toString()方法注释掉,使用Object原来默认的toString()方法,则运行结果如下所示:com.Book6fd46259com.Book6fd46259c
218、om.Book6084fa6a当类没有重写toString()方法时,系统会自动调用Object默认的toString()方法,显示的字符串格式是“类名哈希代码值”。第第3节节part字符串类字符串就是用双引号引起来的一连串的字符序列,Java提供了String、StringBuffer和StringBuilder三个类来封装字符串,并提供了一系列方法来操作字符串对象。String、StringBuffer和StringBuilder三者之间区别如下:String创建的字符串是不可变的,即当使用String创建一个字符串后,该字符串在内存中是一个不可改变的字符序列。如果改变字符串变量的值,其实
219、际是在内存中创建一个新的字符串,字符串变量将引用新创建的字符串地址,而原来的字符串在内存中依然存在且内容不变,直至Java的垃圾回收系统对其进行销毁。StringBuffer创建的字符串是可变的,当使用StringBuffer创建一个字符串后,该字符串的内容可以通过append()、insert()、setCharAt()等方法进行改变,而字符串变量所引用的地址一直不变,如果想获得StringBuffer的最终内容,可以通过调用它的toString()方法转换成一个String对象。StringBuilder是JDK1.5新增的一个类。与StringBuffer类似,也是创建一个可变的字符串,
220、不同的是StringBuffer是线程安全的,而StringBuilder没有实现线程安全,因此性能较好。通常,如果只需要创建一个内容可变的字符串对象,不涉及线程安全、同步方面的问题,应优先考虑使用StringBuilder类。字符串类本节概述6.3.6.3.1 1String类String类6.3.6.3.1 1String类6.3.6.3.1 1String类下述案例示例了String类常用方法的应用,代码如下所示。【代码6.5】StringExample.javapackagecom;publicclassStringExamplepublicstaticvoidmain(Stringa
221、rgs)Stringstr=“ImWangFeng,welcometoChongQing!”;System.out.println(字符串内容:+str);System.out.println(字符串长度:+str.length();System.out.println(截取从下标5开始的子字符串:“+str.substring(5);System.out.println(截取从下标5开始到10结束的子字符串:”+str.substring(5,10);System.out.println(转换成小写:+str.toLowerCase();System.out.println(转换成大写:+s
222、tr.toUpperCase();/验证字符串是否全是数字StringnumStr=1234567a890;booleanflag=false;6.3.6.3.1 1String类for(inti=0;i9|numStr.charAt(i)0)flag=true;break;if(flag)System.out.println(该字符串有非数字存在!);elseSystem.out.println(该字符串全是数字!);6.3.6.3.1 1String类程序运行结果如下:字符串内容:ImWangFeng,welcometoChongQing!字符串长度:34截取从下标5开始的子字符串:ang
223、Feng,welcometoChongQing!截取从下标5开始到10结束的子字符串:angFe转换成小写:imwangfeng,welcometochongqing!转换成大写:IMWANGFENG,WELCOMETOCHONGQING!该字符串有非数字存在!在Java程序中,经常会使用“+”运算符连接字符串,但不同情况下字符串连接的结果也是不同的。使用“+”运算符连接字符串时注意以下三点:字符串与字符串进行“+”连接时:第二个字符串会连接到第一个字符串之后。字符串与其他类型进行“+”连接时:因字符串在前面,所以其他类型的数据都将转换成字符串与前面的字符串进行连接。其他类型与字符串进行“+”
224、连接时:因字符串在后面,其他类型按照从左向右进行运算,最后再与字符串进行连接。6.3.6.3.2 2StringBuffer类StringBuffer类StringBuffer字符缓冲区类是一种线程安全的可变字符序列,其常用的方法如表6-4所示。6.3.6.3.2 2StringBuffer类6.3.6.3.2 2StringBuffer类下述案例示例了StringBuffer类常用方法的应用,代码如下所示。【代码6.6】StringBufferExample.javapackagecom;publicclassStringBufferExamplepublicstaticvoidmain(S
225、tringargs)StringBuffersb=newStringBuffer(“My”);System.out.println(初始长度:+sb.length();System.out.println(初始容量是:+sb.capacity();/追加字符串sb.append(java);System.out.println(追加后:+sb);/插入sb.insert(0,hello);System.out.println(插入后:+sb);/替换sb.replace(5,6,);System.out.println(替换后:+sb);/删除sb.delete(5,6);6.3.6.3.2
226、2StringBuffer类System.out.println(删除后:+sb);/反转sb.reverse();System.out.println(反转后:+sb);System.out.println(当前字符串长度:+sb.length();System.out.println(当前容量是:+sb.capacity();/改变StringBuilder的长度,将只保留前面部分sb.setLength(5);System.out.println(改变长度后:+sb);程序运行结果如下:初始长度:2初始容量是:18追加后:Myjava插入后:helloMyjava替换后:hello,My
227、java删除后:helloMyjava反转后:avajyMolleh当前字符串长度:11当前容量是:18改变长度后:avajy6.3.6.3.3 3StringBuilder类StringBuilder类StringBuilder字符串生成器类与StringBuffer类类似,也是创建可变的字符串序列,只不过没有线程安全控制,StringBuilder类常用的方法如表6-5所示。6.3.6.3.3 3StringBuilder类6.3.6.3.3 3StringBuilder类下述案例示例了StringBuilder类常用方法的应用,代码如下所示。【代码6.7】StringBuilderExa
228、mple.javapackagecom;publicclassStringBuilderExamplepublicstaticvoidmain(Stringargs)StringBuildersb=newStringBuilder(My);System.out.println(初始长度:+sb.length();System.out.println(初始容量是:+sb.capacity();/追加字符串sb.append(java);System.out.println(追加后:+sb);/插入sb.insert(0,hello);System.out.println(插入后:+sb);/替换
229、sb.replace(5,6,);System.out.println(替换后:+sb);/删除sb.delete(5,6);System.out.println(删除后:+sb);6.3.6.3.3 3StringBuilder类/反转sb.reverse();System.out.println(反转后:+sb);System.out.println(当前字符串长度:+sb.length();System.out.println(当前容量是:+sb.capacity();/改变StringBuilder的长度,将只保留前面部分sb.setLength(5);System.out.print
230、ln(改变长度后:+sb);6.3.6.3.3 3StringBuilder类程序运行结果如下:初始长度:2初始容量是:18追加后:Myjava插入后:helloMyjava替换后:hello,Myjava删除后:helloMyjava反转后:avajyMolleh当前字符串长度:11当前容量是:18改变长度后:avajy通过上述代码及运行结果可以看出,StringBuilder类除了在构造方法上与StringBuffer不同,其他方法的使用都一样。StringBuilder和StringBuffer都有两个方法:length()和capacity(),其中length()方法表示字符序列的长
231、度,而capacity()方法表示容量,通常程序无须关心其容量。第第4节节partScanner类Scanner扫描器类在java.util包中,可以获取用户从键盘输入的不同数据,以完成数据的输入操作,同时也可以对输入的数据进行验证。Scanner类常用的方法如表6-6所示。Scanner 类Scanner类下述案例示例了Scanner类常用方法的应用,代码如下所示。【代码6.8】ScannerExample.javapackagecom;importjava.util.Scanner;publicclassScannerExamplepublicstaticvoidmain(Stringar
232、gs)/创建Scanner对象,从键盘接收数据Scannersc=newScanner(System.in);System.out.print(请输入一个字符串(不带空格):);/接收字符串Strings1=sc.next();System.out.println(s1=+s1);System.out.print(请输入整数:);/接收整数inti=sc.nextInt();System.out.println(i=+i);System.out.print(请输入浮点数:);Scanner 类/接收浮点数floatf=sc.nextFloat();System.out.println(f=+f
233、);System.out.print(请输入一个字符串(带空格):);/接收字符串,默认情况下只能取出空格之前的数据Strings2=sc.next();System.out.println(s2=+s2);/设置读取的分隔符为回车sc.useDelimiter(n);/接收上次扫描剩下的空格之后的数据Strings3=sc.next();System.out.println(s3=+s3);System.out.print(请输入一个字符串(带空格):);Strings4=sc.next();System.out.println(s4=+s4);程序运行结果如下:请输入一个字符串(不带空格)
234、:xscs1=xsc请输入整数:34i=34请输入浮点数:5.6f=5.6请输入一个字符串(带空格):csvbgns2=csvs3=bgn请输入一个字符串(带空格):567879s4=567879Scanner 类通过运行结果可以看出,默认情况下,next()方法只扫描接收空格之前的内容,如果希望连空格一起接收,则可以使用useDelimiter()方法设置分隔符后再接收。第第5节节partMath类Math类包含常用的执行基本数学运算的方法,如初等指数、对数、平方根和三角函数等。Math类提供的方法都是静态的,可以直接调用,无需实例化。Math类常用的方法如表6-7所示。M ath类Math
235、类Math类除了提供大量的静态方法之外,还提供了两个静态常量:PI和E,正如其名字所暗示的,分别表示和e的值。下述案例示例了Math类中常用方法的使用,代码如下所示。【代码6.9】MathExample.javapackagecom;publicclassMathExamplepublicstaticvoidmain(Stringargs)/*-下面是三角运算-*/将弧度转换角度System.out.println(Math.toDegrees(1.57):+Math.toDegrees(1.57);/将角度转换为弧度System.out.println(Math.toRadians(90):
236、+Math.toRadians(90);/计算反余弦,返回的角度范围在0.0到pi之间。System.out.println(Math.acos(1.2):+Math.acos(1.2);/计算反正弦;返回的角度范围在-pi/2到pi/2之间。System.out.println(Math.asin(0.8):+Math.asin(0.8);/计算反正切;返回的角度范围在-pi/2到pi/2之间。System.out.println(Math.atan(2.3):+Math.atan(2.3);/计算三角余弦。System.out.println(Math.cos(1.57):+Math.co
237、s(1.57);/计算值的双曲余弦。System.out.println(Math.cosh(1.2):+Math.cosh(1.2);M ath类/计算正弦System.out.println(Math.sin(1.57):+Math.sin(1.57);/计算双曲正弦System.out.println(Math.sinh(1.2):+Math.sinh(1.2);/计算三角正切System.out.println(Math.tan(0.8):+Math.tan(0.8);/计算双曲正切System.out.println(Math.tanh(2.1):+Math.tanh(2.1);/*
238、-下面是取整运算-*/取整,返回小于目标数的最大整数。System.out.println(Math.floor(-1.2):+Math.floor(-1.2);/取整,返回大于目标数的最小整数。System.out.println(Math.ceil(1.2):+Math.ceil(1.2);/四舍五入取整System.out.println(Math.round(2.3):+Math.round(2.3);/*-下面是乘方、开方、指数运算-*/计算平方根。System.out.println(Math.sqrt(2.3):+Math.sqrt(2.3);/计算立方根。System.out.
239、println(Math.cbrt(9):+Math.cbrt(9);M ath类/返回欧拉数e的n次幂。System.out.println(Math.exp(2):+Math.exp(2);/计算乘方System.out.println(Math.pow(3,2):+Math.pow(3,2);/计算自然对数System.out.println(Math.log(12):+Math.log(12);/计算底数为10的对数。System.out.println(Math.log10(9):+Math.log10(9);/返回参数与1之和的自然对数。System.out.println(Mat
240、h.log1p(9):+Math.log1p(9);/计算绝对值。System.out.println(Math.abs(-4.5):+Math.abs(-4.5);/*-下面是大小相关的运算-*/找出最大值System.out.println(Math.max(2.3,4.5):+Math.max(2.3,4.5);/计算最小值System.out.println(Math.min(1.2,3.4):+Math.min(1.2,3.4);/返回一个伪随机数,该值大于等于0.0且小于1.0。System.out.println(Math.random():+Math.random();M at
241、h类程序运行结果如下:Math.toDegrees(1.57):89.95437383553926Math.toRadians(90):1.5707963267948966Math.acos(1.2):NaNMath.asin(0.8):0.9272952180016123Math.atan(2.3):1.1606689862534056Math.cos(1.57):7.963267107332633E-4Math.cosh(1.2):1.8106555673243747Math.sin(1.57):0.9999996829318346Math.sinh(1.2):1.509461355412
242、1725Math.tan(0.8):1.0296385570503641Math.tanh(2.1):0.9704519366134539Math.floor(-1.2):-2.0M ath类Math.ceil(1.2):2.0Math.round(2.3):2Math.sqrt(2.3):1.51657508881031Math.cbrt(9):2.080083823051904Math.exp(2):7.38905609893065Math.pow(3,2):9.0Math.log(12):2.4849066497880004Math.log10(9):0.9542425094393249
243、Math.log1p(9):2.302585092994046Math.abs(-4.5):4.5Math.max(2.3,4.5):4.5Math.min(1.2,3.4):1.2Math.random():0.4635451159005677第第6节节partDate与Calendar类6.6.6.6.1 1D at e类Date类用来表示日期和时间,该时间是一个长整型(long),精确到毫秒。其常用的方法如表6-8所示。Date类6.6.6.6.1 1D at e类下述案例示例了Date类中常用方法的使用,代码如下所示。【代码6.10】DateExample.javapackagecom
244、;importjava.util.Date;publicclassDateExamplepublicstaticvoidmain(Stringargs)/以系统当前时间实例化一个Date对象DatedateNow=newDate();/输出系统当前时间System.out.println(系统当前时间是:+dateNow.toString();/以指定值实例化一个Date对象DatedateOld=newDate(1000000000000L);/输出date1System.out.println(date1是:+dateOld.toString();/两个日期进行比较,并输出System.o
245、ut.println(after()是:+dateNow.after(dateOld);System.out.println(before()是:+dateNow.before(dateOld);System.out.println(compareTo()是:+dateNpareTo(dateOld);6.6.6.6.1 1D at e类上述代码中先使用Date类默认的、不带参数的构造方法创建一个dateNow对象,该对象封装系统当前时间;然后调用toString()方法将日期转换为字符串并输出;再使用Date类带参数的构造方法创建一个dateOld对象;然后使用after()、before(
246、)和compareTo()这三个方法进行日期比较。程序运行结果如下:系统当前时间是:WedJul1815:47:10CST2018date1是:SunSep0909:46:40CST2001after()是:truebefore()是:falsecompareTo()是:16.6.6.6.2 2Calendar类Calendar类是一个抽象类,在java.util包中。使用Calendar类的static方法getInstance()可以初始化一个日历对象.它为特定瞬间与一组YEAR、MONTH、DAY_OF_MONTH、HOUR等日历字段之间的转换,和操作日历字段提供了一些方法。瞬间可用毫秒
247、值来表示,它是距历元(即格林威治标准时间1970年1月1日的00:00:00.000,格里高利历)的偏移量。Calendar类常用方法如表6-9所示。Calendar类6.6.6.6.2 2Calendar类6.6.6.6.2 2Calendar类6.6.6.6.2 2Calendar类下述案例示例了Calendar类常用方法的使用,代码如下所示。【代码6.11】CalendarExample.javapackagecom;importjava.util.*;publicclassCalendarExamplepublicstaticvoidmain(Stringargs)Calendarca
248、lendar=Calendar.getInstance();calendar.setTime(newDate();/获取当前时间的具体值intyear=calendar.get(Calendar.YEAR);intmonth=calendar.get(Calendar.MONTH)+1;intday=calendar.get(Calendar.DAY_OF_MONTH);inthour=calendar.get(Calendar.HOUR_OF_DAY);intminute=calendar.get(Calendar.MINUTE);intsecond=calendar.get(Calenda
249、r.SECOND);System.out.print(现在的时间是:);System.out.print(+year+年+month+月+day+日);System.out.println(hour+时+minute+分+second+秒);longtime1=calendar.getTimeInMillis();6.6.6.6.2 2Calendar类/将日历翻到2015年九月一日,注意8表示九月inty=2015,m=9,d=1;calendar.set(y,m-1,d);longtime2=calendar.getTimeInMillis();/计算想个时间的天数longsubDay=(
250、time2-time1)/(1000*60*60*24);System.out.println(+newDate(time2);System.out.println(与+newDate(time1);System.out.println(相隔+subDay+天);程序运行结果如下所示:现在的时间是:2018年7月18日16时10分58秒TueSep0116:10:58CST2015与WedJul1816:10:58CST2018相隔-1051天第第7节节part格式化处理依赖Locale类,Java提供了一系列的格式其(Formatter)来完成数字、货币、日期和消息的格式化。格 式 化处理本
251、节概述6.7.6.7.1 1数 字 格式化在不同的国家,数字表示方式是不一样的,如在中国表示的“8,888.8”,而在德国却表示未“8.888,8”,因此,对数字表示将根据不同的Locale来格式化。在java.text包中提供了一个NumberFormat类,用于完成对数字、百分比进行格式化和对字符串对象进行解析。NumberFormat类提供了大量的静态方法用于获取使用指定Locale对象封装的NumberFormat实例。NumberFormat类的常用方法如表6-10所示。数字格式化6.7.6.7.1 1数 字 格式化下述案例示例了如何使用NumberFormat实现数字格式化处理,代
252、码如下所示。【代码6.12】NumberFormatExample.javapackagecom;importjava.text.NumberFormat;importjava.util.Locale;publicclassNumberFormatExamplepublicstaticvoidmain(Stringargs)/需要格式化的数据doublevalue=987654.321;/设定三个LocaleLocalecnLocale=newLocale(zh,CN);/中国,中文LocaleusLocale=newLocale(en,US);/美国,英文LocaledeLocal3=new
253、Locale(de,DE);/德国,德语NumberFormatdNf=NumberFormat.getNumberInstance();NumberFormatpNf=NumberFormat.getPercentInstance();/得到三个local对应的NumberFormat对象NumberFormatcnNf=NumberFormat.getNumberInstance(cnLocale);NumberFormatusNf=NumberFormat.getNumberInstance(usLocale);NumberFormatdeNf=NumberFormat.getNumbe
254、rInstance(deLocal3);6.7.6.7.1 1数 字 格式化/将上边的double数据格式化输出System.out.println(DefaultPercentFormat:+pNf.format(value);System.out.println(DefaultNumberFormat:+dNf.format(value);System.out.println(ChinaNumberFormat:+cnNf.format(value);System.out.println(UnitedNumberFormat:+usNf.format(value);System.out.p
255、rintln(GermanNumberFormat:+deNf.format(value);trySystem.out.println(dNf.parse(3.14).doubleValue();System.out.println(dNf.parse(3.14F).doubleValue();/下述语句抛出异常System.out.println(dNf.parse(F3.14).doubleValue();catch(Exceptione)System.out.println(e);6.7.6.7.1 1数 字 格式化上述代码中,声明了中文、英文和德语三个Locale对象,并使用相应的Nu
256、mberFormat对指定的数据格式化输出。另外,parse()方法的返回类型是Number,如果给定的数字文本格式不正确,则该方法会抛出ParseException异常。程序运行结果如下:DefaultPercentFormat:98,765,432%DefaultNumberFormat:987,654.321ChinaNumberFormat:987,654.321UnitedNumberFormat:987,654.321GermanNumberFormat:987.654,3213.143.14java.text.ParseException:Unparseablenumber:F3
257、.146.7.6.7.2 2货 币 格式化NumberFormat除了能对数字、百分比格式化外,还可以对货币数据格式化,货币格式化通常是在钱数前面加上类似于“¥”、“”的货币符号,来区分货币类型。使用NumberFormat的静态方法getCurrencyInstance()方法来获取格式器。下面案例使用NumberFormat类来实现对货币格式化处理,代码如下所示。货币格式化6.7.6.7.2 2货 币 格式化【代码6-13】CurrencyFormatExample.javapackagecom;importjava.text.NumberFormat;importjava.util.Lo
258、cale;publicclassCurrencyFormatExamplepublicstaticvoidmain(Stringargs)/需要格式化的数据doublevalue=987654.321;/设定LocaleLocalecnLocale=newLocale(zh,CN);LocaleusLocale=newLocale(en,US);/得到local对应的NumberFormat对象NumberFormatcnNf=NumberFormat.getCurrencyInstance(cnLocale);NumberFormatusNf=NumberFormat.getCurrency
259、Instance(usLocale);/将上边的double数据格式化输出System.out.println(ChinaCurrencyFormat:+cnNf.format(value);System.out.println(UnitedCurrencyFormat:+usNf.format(value);程序运行结果如下:ChinaCurrencyFormat:¥987,654.32UnitedCurrencyFormat:$987,654.32以货币格式输出数据时,会在数据前面添加相应的货币符号,并且在人民币和美元的表示中,都精确到了“分”,即小数点后只保留了两位,以确保数据有实际意义
260、。6.7.6.7.3 3日 期 格式化不同国家其日期格式也是不同的,例如,中文的日期格式为“xxxx年xx月xx日”,而英文的日期格式是“yyyy-mm-dd”。因此,对日期和时间也需要根据不同的Locale来格式化。Java语言中,日期和时间的格式化是通过DateFormat类来完成的,该类的使用方式与NumberFormat类相似。DateFormat类的常用方法如表6-11所示日期格式化6.7.6.7.3 3日 期 格式化其中,dateStyle日期样式和timeStyle时间样式,这两个参数是DateFormat中定义好的静态常量,用于控制输出日期、时间的显示形式,常用的样式控制有:D
261、ateFormat.FULL:在zh_CN的Locale下,此格式的日期格式取值类似于“2018年7月20日星期五”,时间格式取值类似于“上午09时30分12秒CST”。DateFormat.LONG:在zh_CN的Locale下,此格式的日期格式取值类似于“2018年7月20日”,时间格式取值类似于“上午09时30分12秒”。DateFormat.DEFAULT:在zh_CN的Locale下,此格式的日期格式取值类似于“2018-7-20”,时间格式取值类似于“9:30:12”。DateFormat.SHORT:在zh_CN的Locale下,此格式的日期格式取值类似于“18-7-20”,时间
262、格式取值类似于“上午9:30:”。下述案例使用了DateFormat类实现日期时间格式化处理,代码如下所示。6.7.6.7.3 3日 期 格式化【代码6.14】DateFormatExample.javapackagecom;importjava.text.DateFormat;importjava.util.Date;importjava.util.Locale;publicclassDateFormatExamplepublicstaticvoidprint(Datedate,Localelocale)/得到对应Locale对象的日期格式化对象DateFormatdf1=DateForma
263、t.getDateTimeInstance(DateFormat.FULL,DateFormat.FULL,locale);DateFormatdf2=DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG,locale);DateFormatdf3=DateFormat.getDateTimeInstance(DateFormat.DEFAULT,DateFormat.DEFAULT,locale);DateFormatdf4=DateFormat.getDateTimeInstance(DateFormat.SHORT,
264、DateFormat.SHORT,locale);/格式化日期输出System.out.println(df1.format(date);System.out.println(df2.format(date);System.out.println(df3.format(date);System.out.println(df4.format(date);6.7.6.7.3 3日 期 格式化publicstaticvoidmain(Stringargs)Datenow=newDate();LocalecnLocale=newLocale(zh,CN);LocaleusLocale=newLocal
265、e(en,US);System.out.println(中文格式:);print(now,cnLocale);System.out.println(英文格式:);print(now,usLocale);程序运行结果如下:中文格式:2018年7月20日星期五上午07时29分05秒CST2018年7月20日上午07时29分05秒2018-7-207:29:0518-7-20上午7:29英文格式:Friday,July20,20187:29:05AMCSTJuly20,20187:29:05AMCSTJul20,20187:29:05AM7/20/187:29AM6.7.6.7.3 3日 期 格式化
266、除了DateFormat类,Java还提供了更加简便的日期格式器SimpleDateFormat类,该类是DateFormat的子类,可以更加灵活地对日期和时间进行格式化。SimpleDateFormat类的使用非常简单,通过预定义的模式字符构造特定的模式串,然后根据模式串来创建SimpleDateFormat格式器对象,从而通过此格式器完成指定日期时间的格式化。例如:D表示一年中的第几天,d表示一月中的第几天,E代表星期中的第几天等,其他可以使用的模式字符串可参看Java提供的API帮助文档,表6-12列举了一部分日期模式字符。6.7.6.7.3 3日 期 格式化通过模式字符可以构建控制日期
267、、时间格式的模式串,在zh_CN的Locale下,自定义模式串及其对应的日期、时间格式示例如表6-13所示。如果需要在模式串中使用的字符(字符串)不被SimpleDateFormat解释,可以在模式串中将其用单引号括起来;SimpleDateFormat一般不用于国际化处理,而是为了以特定模式输出日期和时间,以便本地化的使用。下述案例示例如何使用SimpleDateFormat类实现日期时间格式化处理,代码如下所示。6.7.6.7.3 3日 期 格式化【代码6.15】SimpleDateFormatExample.javapackagecom;importjava.text.SimpleDat
268、eFormat;importjava.util.Date;publicclassSimpleDateFormatExamplepublicstaticvoidmain(Stringargs)Datenow=newDate();SimpleDateFormatsdf1=newSimpleDateFormat(yyyy-MM-ddHH:mm:ss);System.out.println(sdf1.format(now);SimpleDateFormatsdf2=newSimpleDateFormat(yyyy年MM月dd日HH时mm分ss秒);System.out.println(sdf2.for
269、mat(now);SimpleDateFormatsdf3=newSimpleDateFormat(现在是yyyy年MM月dd日,是今年的第D天);System.out.println(sdf3.format(now);程序运行结果如下:2018-07-2007:56:592018年07月20日07时56分59秒现在是2018年07月20日,是今年的第201天6.7.6.7.4 4消 息 格式化国际化软件需要根据用户的本地化消息输出不同的格式,即动态实现消息的格式化。java.text.MessageFormat类可以实现消息的动态处理,常用的方法如表6-14所示。消息格式化6.7.6.7.4
270、 4消 息 格式化MessageFormat类的构造方法中有一个pattern参数,该参数是一个带占位符的模式字符串,可以根据实际情况使用实际的值替换字符串中的占位符。在模式字符串中,占位符使用括起来,其语法格式如下:n,formatType,formatStyle其中:n代表占位符的索引,取值是从0开始;formatType代表格式类型,用于标识数字、日期、时间;formatStyle代表格式样式,用于具体的样式,如货币、完整日期等。常用格式类型和格式样式如表6-15所示。6.7.6.7.4 4消 息 格式化6.7.6.7.4 4消 息 格式化通常使用MessageFormat进行消息格式化
271、的步骤如下:(1)创建模式字符串,其动态变化的部分使用占位符代替,每个占位符可以重复出现多次;(2)根据模式字符串构造MessageFormat对象;(3)创建Locale对象,并调用setLocale()方法设置MessageFormat对象的语言环境;(4)创建一个对象数组,并按照占位符的索引来组织数据;(5)调用format()方法实现消息格式化,并将对象数组作为该方法的参数。下述案例示例如何使用MessageFormat类实现消息格式化处理,代码如下所示。6.7.6.7.4 4消 息 格式化【代码6.16】MessageFormatExample.javapackagecom;impo
272、rtjava.text.MessageFormat;importjava.util.Date;importjava.util.Locale;publicclassMessageFormatExample/*定义消息格式化方法msgFormat()*parampattern模式字符串*paramlocale语言环境*parammsgParams占位符参数*/publicstaticvoidmsgFormat(Stringpattern,Localelocale,ObjectmsgParams)/根据指定的pattern模式字符串构造MessageFormat对象MessageFormatform
273、atter=newMessageFormat(pattern);/formatter.applyPattern(pattern);/设置语言环境formatter.setLocale(locale);6.7.6.7.4 4消 息 格式化/根据传递的参数,对应替换模式串中的占位符System.out.println(formatter.format(msgParams);publicstaticvoidmain(Stringargs)/定义一个带占位符的模式字符串Stringpattern1=0,您好!欢迎您在1访问本系统!;/获取默认语言环境Localelocale1=Locale.getDe
274、fault();/输出国家System.out.println(locale1.getCountry();/构造模式串所需的对象数组ObjectmsgParams1=向守超,newDate();/调用msgFormat()实现消息格式化输出msgFormat(pattern1,locale1,msgParams1);/定义一个带占位符的模式字符串,对占位符进行不同的格式化Stringpattern2=0,你好!欢迎您在1,date,long访问本系统,”+现在是1,time,hh:mm:ss“;6.7.6.7.4 4消 息 格式化/调用msgFormat()实现消息格式化输出msgFormat
275、(pattern2,locale1,msgParams1);System.out.println(-);/创建一个语言环境Localelocale2=newLocale(en,US);/输出国家System.out.println(locale2.getCountry();/构造模式串所需的对象数组ObjectmsgParams2=向守超,newDate();/调用msgFormat()实现消息格式化输出msgFormat(pattern1,locale2,msgParams2);msgFormat(pattern2,locale2,msgParams2);6.7.6.7.4 4消 息 格式化
276、上述代码中,定义了一个消息格式化方法msgFormat(),该方法带三个参数,分别用于设置模式字符串、语言环境和占位符参数。在main()方法中分别定义不同的模式字符串、Locale对象以及对象数组,然后调用msgFormat()方法实现消息格式化输出。程序运行结果如下:CN向守超,您好!欢迎您在18-7-20上午8:42访问本系统!向守超,你好!欢迎您在2018年7月20日访问本系统,现在是08:42:00-US向守超,您好!欢迎您在7/20/188:42AM访问本系统!向守超,你好!欢迎您在2018年7月20日访问本系统,现在是08:42:00本章课后作业见教材JAVA程 序 设计本章学习
277、目标:第七章第七章继承与多态继承与多态了解类与类之间的关系掌握继承的概念和特点掌握方法的重写和应用掌握super关键字和final关键字的应用掌握多态向上转型的应用了解引用变量的强制类型转换了解内部类的概念、分类和基本应用第第1节节part类之间关系概述在面向对象的程序设计中,通常不会存在一个孤立的类,类和类之间总是存在一定的关系,通过这些关系,才能实现软件的既定功能。掌握类与类之间的关系对于深入理解面向对象概念具有非常重要的作用,也有利于程序员从专业的、合理的角度使用面向对象分析问题和解决问题。类之间关系概述类之间关系概述根据UML(UnifiedModelingLanguage,统一建模语
278、言)规范,类与类之间存在以下六种关系。(1)继承:一个类可以继承另外一个类,并在此基础上添加自己的特有功能。继承也称为泛化,表现的是一种共性与特性的关系。(2)实现:一个类实现接口中声明的方法,其中接口对方法进行声明,而类完成方法的定义,即实现具体功能。实现是类与接口之间常用的关系,一个类可以实现一个或多个接口中的方法。(3)依赖:在一个类的方法中操作另外一个类的对象,这种情况称为第一个类依赖于第二个类。(4)关联:在一个类中使用另外一个类的对象作为该类的成员变量,这种关系称为关联关系。关联关系体现的是两个类之间语义级别的一种强依赖关系。(5)聚合:聚合关系是关联关系的一种特例,体现的是整体与
279、部分的关系,即has-a的关系。通常表现为一个类(整体)由多个其他类的对象(部分)作为该类的成员变量,此时整体与部分之间是可以分离的,整体和部分都可以具有各自的生命周期,部分可以属于多个整体对象,与可以为多个整体对象共享。(6)组成:组成关系也是关联关系的一种特例,与聚合关系一样也是体系整体与部分的关系,但组成关系中的整体与部分是不可分离的,即contains-a的关系,这种关系比聚合更强,也称为强聚合,当整体的生命周期结束后,部分的生命周期也随之结束。类与类之间的这六种关系中,继承和实现体现了类与类之间的一种纵向关系,而其余四种则体现了类与类之间的横向关系。其中,关联、聚合和组成这三种关系更
280、多体现的是一种语义上的区别,而在代码上则是无法区分的。类之间关系概述第第2节节part继承继承是面向对象的三大特征之一,也是实现程序重复利用的重要手段。Java继承具有单继承的特点,也就是每个子类只有一个直接父类。类的继承性类似与自然界中的继承特性。例如,人类特征的遗传,子女或多或少地体现了父母的特征。不过类的继承子类除了具有父类原有的特性和功能之外,还可以增加一些新的特性和功能。例如,人类安装工作性质大致可以分为工、农、兵、学、商等,但学生又可以根据学龄分为大、中、小学生,大学生又可以根据学校不同继续细分,如图7.1所示。继承本节概述从上图可以看出,子类对象是一种特殊的父类对象,对对象(实例
281、)进行归类时,由于类型之间具有相似类似的特征,描述“某种东西是(或像)另外一种东西”这种关系时,使用继承性。7.2.7.2.1 1继 承 的特点继承的特点Java的继承通过extends关键字来实现,实现继承的类被称为子类,有的也称其为派生类,被继承的类被称为父类,有的也称其为基类或超类。父类和子类的关系,是一种一般和特殊的关系。例如水果和苹果的关系,苹果继承了水果,苹果是水果的子类,则苹果是一种特殊的水果。Java里子类继承父类的声明格式如下;【访问符】【修饰符】class子类名extends父类名属性方法7.2.7.2.1 1继 承 的特点其中,访问符、修饰符、class、子类名、大括号、
282、属性和方法与第六章类的定义完全相同,这里就不再赘述。Java使用extends作为继承的关键字,extends关键字在英文中是扩展,而不是继承。这个关键字很好地体现了子类和父类的关系:子类是对父类的扩展,子类是一种特殊的父类。下述代码示例了子类继承父类的基本格式:publicclassSubClassextendsSuperClass.上述代码通过使用extends关键字使子类SubClass继承了父类SuperClass。如果定义一个类没有使用extends关键字继承任何父类,则自动继承java.lang.Object类。Object类是所有类的顶级父类,在Java中,所有类都是直接或间接地
283、继承了Object类。下述程序代码示例了继承的创建与继承相应的特点,代码如下:7.2.7.2.1 1继 承 的特点【代码7.1】Student.javapackagecom;/创建父类classPersonprivateintage;/私有成员变量,年龄Stringname;/默认成员变量姓名staticStringid;/静态变量id编号/声明一个私有成员方法privatevoidshowAge()System.out.println(年龄为:+age);voidshowName()System.out.println(姓名为:+name);/声明一个静态成员方法publicstaticvo
284、idshowId()System.out.println(id编号为:+id);7.2.7.2.1 1继 承 的特点/创建子类publicclassStudentextendsPersonStringstuid;/学生证号voiddisplay()System.out.println(姓名为:+name+,id编号为:”+id+,学生证号为:+stuid);publicstaticvoidmain(Stringargs)Studentstu=newStudent();/为属性赋值stu.age=20;/错误,不能继承父类私有成员变量stu.name=张三;/为姓名赋值Student.id=00
285、1;/为静态成员变量id编号赋值stu.stuid=2018001;/为学生证号赋值/调用方法stu.showAge();/错误,不能继承父类私有成员方法Student.showId();stu.showName();stu.display();7.2.7.2.1 1继 承 的特点程序运行结果如下:id编号为:001姓名为:张三姓名为:张三,id编号为:001,学生证号为:2018001通过程序运行结果可以发现,Student类继承了Person类,所以它具有Person类非私有的成员变量和方法,调用Person类的属性和方法就像调用自己的属性和方法一样。7.2.7.2.1 1继 承 的特点类
286、的继承性具有如下的特点:(1)Java类继承只支持单继承。(2)子类能够继承父类的非私有成员变量和成员方法,包括类成员变量和类成员方法。(3)子类不能继承父类的构造方法。因为父类构造方法创建的是父类对象,子类必须声明自己的构造方法,创建子类自己的对象。(4)创建子类对象时,首先默认要执行父类不带参数的构造方法进行初始化。(5)子类不能删除从父类继承过来的成员。(6)子类可以增加自己的成员变量和成员方法。(7)子类可以重写继承自父类的成员变量和成员方法。(8)继承具有传递性。7.2.7.2.1 1继 承 的特点在继承过程中,子类拥有父类所定义的所有属性和方法,但父类可以通过“封装”思想隐藏某些数
287、据,只对子类提供可访问的属性和方法。实例化一个子类对象时,会先调用父类构造方法进行初始化,再调用子类自身的构造方法进行初始化,即构造方法的执行次序是父类子类。下述代码示例了在继承关系中父类和子类构造方法的调用次序。7.2.7.2.1 1继 承 的特点【代码7.2】SubClass.javapackagecom;classSuperClassintnumber;/声明一个属性publicSuperClass()this.number=1;System.out.println(调用父类不带参数的构造方法.number=+this.number);publicSuperClass(intnumber
288、)this.number=number;System.out.println(调用父类带参数的构造方法.number=+this.number);7.2.7.2.1 1继 承 的特点publicclassSubClassextendsSuperClasspublicSubClass()this.number=20;System.out.println(调用子类不带参数的构造方法.number=+this.number);publicSubClass(intnumber)this.number=number;System.out.println(调用子类带参数的构造方法.number=+this
289、.number);publicstaticvoidmain(Stringargs)SubClasss1=newSubClass();System.out.println(s1.number=+s1.number);SubClasss2=newSubClass(15);System.out.println(s2.number=+s2.number);7.2.7.2.1 1继 承 的特点程序运行结果如下:调用父类不带参数的构造方法.number=1调用子类不带参数的构造方法.number=20s1.number=20调用父类不带参数的构造方法.number=1调用子类带参数的构造方法.number
290、=15s2.number=15通过运行结果可以发现,在构造一个子类对象时,会首先调用父类不带参数的构造方法进行初始化,而后再调用子类的构造方法进行初始化。在上述代码中,如果注销掉父类不带参数的构造构造方法,会发现子类的两个构造方法都报错;如果注销掉父类带参数的构造方法,运行结果完全一样。7.2.7.2.2 2方 法 的重写方法的重写子类继承了父类,子类也是一个特殊的父类。大部分时候,子类总是以父类为基础,额外增加新的属性和方法。但有一种情况例外,就是子类需要重写父类的方法。方法的重写也是多态的一种。例如,鸟类都包含了飞翔方法,其中有一种特殊的鸟不会飞翔,那就是鸵鸟,因此它也将从鸟类继承飞翔方法
291、,但这个方法明显不适合鸵鸟,为此,鸵鸟需要重写鸟类的飞翔方法。这种子类包含与父类同名方法的现象被称为方法重写,也被称为方法覆盖(Override)。可以说子类重写了父类的方法,也可以说子类覆盖了父类的方法。下述代码演示了鸵鸟对鸟类飞翔方法的重写。7.2.7.2.2 2方 法 的重写【代码7.3】OverrideExample.javapackagecom;classBird/定义Bird类的fly()方法publicvoidfly()System.out.println(我是鸟类,我能在天空自由自在地飞翔.);classOstrichextendsBird/重写Bird类的fly()方法pub
292、licvoidfly()System.out.println(我虽然是鸟类,我却不能在天空飞翔,我只可以在陆地上奔跑.);publicclassOverrideExamplepublicstaticvoidmain(Stringargs)Ostrichos=newOstrich();/创建Ostrich的对象os.fly();/执行Ostrich对象的fly()方法7.2.7.2.2 2方 法 的重写程序运行结果如下:我虽然是鸟类,我却不能在天空飞翔,我只可以在陆地上奔跑.执行上面的程序可以看出,执行os.fly()时,执行的不再是Bird类的fly()方法,而是执行的Ostrich类的fly
293、()方法。7.2.7.2.2 2方 法 的重写方法的重写要遵循以下几点原则:(1)方法名、返回值类型、参数列表必须完全相同。(2)子类方法声明抛出的异常类应该比父类方法声明抛出的异常类更小或相等。(3)子类方法的访问权限应比父类方法的访问权限更大或相等。(4)覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法一个是实例方法。例如,下述代码将会引发编译错误。7.2.7.2.2 2方 法 的重写classFatherpublicstaticdoubleadd(inta,intb)returna+b;classSonextendsFatherpublicdoubleadd(int
294、a,intb)returna+b;7.2.7.2.2 2方 法 的重写当子类覆盖了父类方法后,子类的对象将无法访问父类中被覆盖的方法,但可以在子类方法中调用父类中被覆盖的方法。如果需要在子类方法中调用父类中被覆盖的方法,则可以使用super(被覆盖的是实例方法)关键字或者父类类名(被覆盖的是类方法)作为调用者来调用父类中被覆盖的方法。如果父类方法具有private访问权限,则该方法对其子类是隐藏的,因此子类无法重写该方法。如果子类中定义了一个与父类private方法完全相同的方法,依然不是重写,只是相当于在子类中重新定义了一个新方法。例如下面代码所示。7.2.7.2.2 2方 法 的重写cla
295、ssFatherprivatedoubleadd(inta,intb)returna+b;classSonextendsFather/此处不是方法重写,而是增加了一个新方法privatedoubleadd(inta,intb)returna+b;另外,父类方法和子类方法之间也可以发生方法重载现象,因为子类会获得父类方法,如果子类定义了一个与父类方法名相同而参数列表不同的方法,那么就形成了父类方法和子类方法的重载。7.2.7.2.3 3super关键字super关键字super是Java提供的一个关键字,super用于限定该对象调用它从父类继承得到的属性和方法。正如this一样,super也不能
296、出现在static修饰的方法中。super关键字代表父类对象,其主要用途有两种。一种是在子类的构造方法中调用父类的构造方法;另一种是在子类方法中访问父类的属性和方法。7.2.7.2.3 3super关键字1.调用父类构造方法调用父类构造方法在Java中,子类不能继承父类的构造方法,但子类构造方法里可以通过super调用父类构造方法,执行父类构造方法里的初始化代码,类似于我们前面学习的this调用同类重载的构造方法一样。在一个构造方法中调用另一个重载的构造方法使用this调用来完成,在子类构造器中调用父类构造方法用super调用来完成。super关键字调用父类构造方法的基本语法如下:super(
297、参数列表);其中,使用super调用父类构造方法必须放在子类构造方法方法体的第一行,所以this调用和super调用不会同时出现。参数列表里面参数初始化的属性,必须是从父类继承得到的属性,而不是该类自己定义的属性。下述代码示例了super调用父类构造方法的应用,代码如下所示。7.2.7.2.3 3super关键字【代码7.4】StudentTwo.javapackagecom;classPersonintage;/年龄Stringname;/姓名publicPerson(intage,Stringname)this.age=age;this.name=name;publicclassStude
298、ntTwoextendsPersonStringstuid;/学生证号publicStudentTwo(intage,Stringname,Stringstuid)super(age,name);this.stuid=stuid;7.2.7.2.3 3super关键字voiddisplay()System.out.println(姓名为:+name+,年龄为:+age+,学生证号为:+stuid);publicstaticvoidmain(Stringargs)StudentTwostu=newStudentTwo(20,张三,001);stu.display();程序运行结果如下:姓名为:张
299、三,年龄为:20,学生证号为:001从上面程序中可以看出,使用super调用和使用this调用很相似,区别只是在于super调用的是其父类的构造方法,this调用的是同一个类中重载的构造方法。7.2.7.2.3 3super关键字不管我们是否使用super调用父类构造方法,子类构造方法总会调用父类构造方法一次。子类调用父类构造方法分如下几种情况。(1)子类构造方法方法体第一行使用super显示调用父类构造方法,系统将根据super调用里传入的实参列表调用父类对应的构造方法。(2)子类构造方法方法体的第一行代码使用this显示调用本类中重载的构造方法,系统将根据this调用里传入的实参列表调用本
300、类中的另一个构造方法。执行本类中另一个构造方法时即会调用父类构造方法。(3)子类构造方法中既没有super调用,也没有this调用,系统将会在执行子类构造方法之前,隐式调用父类无参数的构造方法。不管上面哪种情况,当调用子类构造方法来初始化子类对象时,父类构造方法总会在子类构造方法之前执行。根据继承的传递性可以推出,创建任何Java对象时,最先执行的总是java.lang.Object类的构造方法。7.2.7.2.3 3super关键字2.调用父类的属性和方法调用父类的属性和方法当子类定义的属性与父类的属性同名时,这样子类从父类继承的这个属性将被隐藏。如果需要使用父类被隐藏的属性,可以使用“su
301、per.属性名”格式来引用父类的属性。当子类重写了父类的方法时,可以使用“super.方法名()”格式来调用父类的方法。下述代码示例了super关键字调用父类隐藏的属性和重写的方法,代码如下所示。7.2.7.2.3 3super关键字【代码7.5】SuperExample.javapackagecom;classPersonintage=30;/年龄staticStringname=张三;/姓名Stringid=001;/证件号码voiddisplay()System.out.println(调用父类中的display()方法。姓名为:+name+,年龄为:+age+,证件号码为:+id);7
302、.2.7.2.3 3super关键字classStudentextendsPersonintage=18;staticStringname=李四;/姓名voiddisplay()System.out.println(调用子类中的display()方法。姓名为:+name+,年龄为:+age+,证件号码为:+id);voidprint()System.out.println(父类中的年龄属性为:+super.age);System.out.println(父类中的姓名属性为:+Person.name);System.out.println(子类中的年龄属性为:+age);System.out.p
303、rintln(子类中的姓名属性为:+name);super.display();/调用父类中的display()方法display();/调用子类中的display()方法7.2.7.2.3 3super关键字publicclassSuperExamplepublicstaticvoidmain(Stringargs)Studentstu=newStudent();stu.print();程序运行结果如下:父类中的年龄属性为:30父类中的姓名属性为:张三子类中的年龄属性为:18子类中的姓名属性为:李四调用父类中的display()方法。姓名为:张三,年龄为:30,证件号码为:001调用子类中的
304、display()方法。姓名为:李四,年龄为:18,证件号码为:0017.2.7.2.3 3super关键字从上面的程序可以看出,如果子类重写了父类的方法和隐藏了父类定义的属性,如果需要调用父类被重写的方法和被隐藏的属性,我们可以通过super关键字来实现。如果子类没有隐藏父类的属性,那么在子类实例中访问该属性时,则不须使用super关键字。如果在某个方法中访问名为a的属性,则系统查找a的顺序为:(1)查找该方法中是否有名为a的局部变量。(2)查找当前类中是否包含有名为a的属性。(3)查找该类的直接父类中是否包含有名为a的属性,依次上溯到所有父类,直到java.lang.Object类,如果最
305、终没有找到该属性,则系统出现编译错误。当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存,即使子类定义了与父类中同名的实例变量,都同样分配内存。例如,A类定义了2个属性,B类定义了3个属性,C类定义了2个属性,并且A类继承了B类,B类继承了C类,那么创建A类对象时,系统将会分配2+3+2个内存空间给实例变量。7.2.7.2.4 4final关 键字final关键字final关键字表示“不可改变的,最终的”的意思,可用于修饰类、变量和方法。当final关键字修饰变量时,表示该变量一旦被初始化,就不可被改变的量,即常量;当final关键
306、字修饰方法时,表示该方法不可被子类重写,即最终方法;当final关键字修饰类时,表示该类不可被子类继承,即最终类。7.2.7.2.4 4final关 键字1.final成员变量成员变量在Java语法中规定,final修饰的成员变量必须由程序员显示地指定初始值。final修饰的类成员变量和实例成员变量能指定初始值的地方如下:(1)类成员变量必须在静态初始化块中或声明该变量时指定初始值。(2)实例成员变量必须在非静态初始化块、声明该变量时或构造方法中指定初始值。下述代码示例了final修饰的成员变量的各种具体情况。代码如下所示。7.2.7.2.4 4final关 键字【代码7.6】FinalVar
307、iableExample.javapackagecom;publicclassFinalVariableExamplefinalinta=10;/定义final成员变量时初始化值finalstaticdoubleb=9.8;/定义final类成员变量时初始化值finalStringstr;finalcharc;finalstaticbyted;/在非静态块中为final成员变量赋初始值c=a;/在静态块中为final成员变量赋初始值staticd=r;/在构造方法中为final成员变量赋初始值publicFinalVariableExample()str=String;7.2.7.2.4 4f
308、inal关 键字publicstaticvoidmain(Stringargs)FinalVariableExamplefve=newFinalVariableExample();System.out.println(a的值为:+fve.a);System.out.println(b的值为:+FinalVariableExample.b);System.out.println(c的值为:+fve.c);System.out.println(d的值为:+FinalVariableExample.d);System.out.println(str的值为:+fve.str);程序运行结果如下:a的值
309、为:10b的值为:9.8c的值为:ad的值为:114str的值为:String与普通成员变量不同的是,final成员变量必须由程序员显示初始化,系统不会对final成员变量进行隐式初始化。7.2.7.2.4 4final关 键字2.final方法方法使用final修饰的方法不能被子类重写。如果某些方法完成了关键性的、基础性的功能,不需要或不允许被子类改变,则可以将这些方法声明为final的。下述代码示例了final修饰的方法不能被重写。代码如下所示。【代码7.7】FinalMethodExample.javapackagecom;classFinalMethodpublicfinalvoidm
310、ethod()System.out.println(“final修饰的方法不能被重写,可以被继承);classFinalMethodExampleextendsFinalMethodpublicfinalvoidmethod()/错误,final方法不能被重写publicstaticvoidmain(Stringargs)FinalMethodExamplefme=newFinalMethodExample();fme.method();注销掉报错代码,程序运行结果如下:final修饰的方法不能被重写,可以被继承7.2.7.2.4 4final关 键字3.final类类使用final修饰的类不
311、能被继承。例如下述代码所示:finalclassFatherclassSonextendsFather/错误,final类不能被继承一个final类中的所有方法都被默认为final的,因此final类中的方法不必显示声明为final。其实,Java基础类库中的类都是final类,如String、Integer等,都无法被子类继承。第第3节节part多态多态性一般发生在子类和父类之间,就是同一种事物,由于条件不同,产生了不同的结果。多态性分为静态性多态和动态性多态。前面接触到的方法的重载和重写就是静态性多态。本节主要介绍的是动态性多态。Java引用变量有两个类型:一个是编译的类型,一个是运行时类
312、型。编译时类型由声明变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,则称为动态性多态。多态本节概述7.3.7.3.1 1上 转 型对象上转型对象所谓上转型对象就是一个父类类型的引用变量可以指向其子类的对象,即将子类对象赋给一个父类类型的引用变量。上转型对象能够访问到父类所有成员变量和父类中没有被子类重写的方法,还可以访问到子类重写父类的方法,而不能访问到子类新增加的成员变量和方法。下述代码示例了上转型对象的访问特性,代码所示如下。7.3.7.3.1 1上 转 型对象【代码7.8】PolymorphismExample.javapackagecom;
313、classBaseClassinta=6;publicvoidbase()System.out.println(父类的普通方法);publicvoidtest()System.out.println(父类将被子类重写的方法);7.3.7.3.1 1上 转 型对象publicclassPolymorphismExampleextendsBaseClassinta=20;/重新定义一个实例变量a,隐藏父类的实例变量intb=10;publicvoidtest()System.out.println(子类重写父类的方法);publicvoidsub()System.out.println(子类新增的
314、普通方法);7.3.7.3.1 1上 转 型对象publicstaticvoidmain(Stringargs)/编译时类型和运行时类型完全一致,不存在多态BaseClassbc=newBaseClass();System.out.println(bc.a=+bc.a);bc.base();bc.test();PolymorphismExamplesc=newPolymorphismExample();System.out.println(sc.a=+sc.a);sc.base();sc.test();/编译时类型和运行时类型不一致,多态性发生BaseClassbsc=newPolymorph
315、ismExample();System.out.println(bsc.a=+bsc.a);bsc.base();bsc.test();/下面两行代码错误,原因是运行时多态不能调用子类新增的属性和方法/bsc.sub();/System.out.println(bsc.b);程序运行结果如下:bc.a=6父类的普通方法父类将被子类重写的方法sc.a=20父类的普通方法子类重写父类的方法bsc.a=6父类的普通方法子类重写父类的方法7.3.7.3.2 2引用变量的强制类型转换引用变量的强制类型转换编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,即使它实际所引
316、用的对象确实包含该方法。如果需要让这个引用变量调用它运行时类型的方法,则必须把它强制转换成运行时类型,强制类型转换需要借助类型转换运算符。类型转换运算符是一对小括号,类型转换运算符的用法是:(type)variable,这种用法可以将variable变量转换成一个type类型的变量。这种强制类型转换不是万能的,当进行强制类型转换时需要注意:(1)基本类型之间的转换只能在数值类型之间进行,这里所说的数值类型包括整数型、字符型和浮点型。但数值类型和布尔类型之间不能进行类型转换。(2)引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编
317、译时就会出现错误。如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型是父类类型,而运行时类型是子类类型),否则将会运行时引发ClassCastException异常。下述代码示例了引用变量强制类型的转换,代码所示如下。7.3.7.3.2 2引用变量的强制类型转换【代码7.9】SchoolStudent.javapackagecom;classStudentStringname=张三;intage=20;publicvoidstudy()System.out.println(学生就要好好学习,天天向上);publicclassSchoolStudentexte
318、ndsStudentStringschool=重庆大学;publicvoidmajor()System.out.println(我的专业是计算机软件技术);7.3.7.3.2 2引用变量的强制类型转换publicstaticvoidmain(Stringargs)Studentstu=newSchoolStudent();if(stuinstanceofSchoolStudent)SchoolStudentstu2=(SchoolStudent)stu;System.out.println(姓名为:+stu2.name+,年龄为:+stu2.age+,学校为:+stu2.school);stu
319、2.major();stu2.study();程序运行结果如下:姓名为:张三,年龄为:20,学校为:重庆大学我的专业是计算机软件技术学生就要好好学习,天天向上考虑到进行强制类型转换时,可能会出现异常,因此进行引用变量强制类型转换之前先通过instanceof运算符来判断是否可以成功转换,从而避免出现ClassCastException异常,这样可以保证程序更加健壮。7.3.7.3.3 3instanceof运算符instanceof运算符instanceof运算符是一个二目运算符,左边操作数通常是一个引用类型的变量,右边操作数通常是一个类(也可以是接口),它用于判断左边的对象是否是后面的类,或
320、者其子类、实例类的实例。如果是,则返回true,否则返回false。在使用instanceof运算符时需要注意:instanceof运算符左边操作数的编译时类型要么与右边的类相同,要么具有父子继承关系,否则会引起编译错误。下述代码示例了instanceof运算符的用法,代码示例如下。7.3.7.3.3 3instanceof运算符【代码7.10】InstanceofExample.javapackagecom;publicclassInstanceofExamplepublicstaticvoidmain(Stringargs)Objecthello=hello;System.out.prin
321、tln(字符串是否是Object类的实例:+(helloinstanceofObject);System.out.println(字符串是否是String类的实例:+(helloinstanceofString);System.out.println(字符串是否是Math类的实例:+(helloinstanceofMath);System.out.println(字符串是否是Comparable接口的实例:+(helloinstanceofComparable);Stringstr=Hello;/下面代码编译错误System.out.println(字符串是否是Math类的实例:+(strin
322、stanceofMath);注销错误代码,程序运行结果如下:字符串是否是Object类的实例:true字符串是否是String类的实例:false字符串是否是Math类的实例:false字符串是否是Comparable接口的实例:true7.3.7.3.3 3instanceof运算符上述程序通过定义Objecthello=hello,这个变量的编译时类型是Object类,但实际类型是String类。因为Object类是所有类、接口的父类,因此可执行helloinstanceofMath和helloinstanceofComparable等。但如果使用Stringstr=Hello代码定义的s
323、tr变量,就不能执行strinstanceofMath,因为str的编译时类型是String类,而String类既不是Math类型,也不是Math类型的父类,所以这行代码编译就会出错。instanceof运算符的作用是在进行引用变量强制类型转换之前,首先判断前一个对象是否是后一个类的实例,是否可以转换成功,从而保证代码的健壮性。instanceof和(type)variable是Java提供的两个相关的运算符,通常先用instanceof判断一个对象是否可以强制类型转换,然后再使用(type)variable运算符进行强制类型转换,从而保证程序不会出现错误。第第4节节part内部类Java语法
324、中,允许在一个类的类体之内再定义一个类,这个定义在其他类内部的类就被称为内部类(或嵌套类),包含内部类的类也被称为外部类(或宿主类)。内部类主要有如下作用。(1)内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包的其他类访问该类。(2)内部类成员可以直接访问外部类的私有数据,因为内部类被当成外部类成员,同一个类的成员之间可以互相访问。但外部类不能访问内部类的成员。(3)匿名内部类适合用于创建那些仅需要一次使用的类。Java内部类主要分为非静态内部类、局部内部类、静态内部类和匿名内部类四种。内 部类本节概述7.4.7.4.1 1非静态内部类定义内部类非常简单,只要把一个类放在另
325、一个类内部定义即可。此处的“类内部”包括类中的任何位置,甚至在方法中也可以定义内部类(局部内部类)。大部分时候,内部类都被作为成员内部类定义,而不是作为局部内部类。成员内部类是一种与属性、成员方法、构造方法和初始化语句块相似的类成员。局部内部类和匿名内部类则不是类成员。成员内部类分为静态内部类和非静态内部类两种,使用static修饰的成员内部类是静态内部类,没有使用static修饰的成员内部类是非静态成员内部类。由于内部类作为其外部类的成员,所以可以使用任意访问权限控制符如private、protected和public等修饰。下述代码示例了非静态内部类的定义和使用,代码如下所示。非静态内部类
326、7.4.7.4.1 1非静态内部类【代码7.11】InnerClassExample1.javapackageinner;importinner.Cow.CowLeg;classCow/外部类属性privatedoubleweight;/外部类构造方法publicCow()publicCow(doubleweight)this.weight=weight;7.4.7.4.1 1非静态内部类/定义非静态内部类classCowLeg/内部类属性privatedoubleheight;privateStringcolor;/内部类构造方法publicCowLeg()publicCowLeg(doub
327、leheight,Stringcolor)this.height=height;this.color=color;/内部类成员方法publicdoublegetHeight()returnheight;publicvoidsetHeight(doubleheight)this.height=height;7.4.7.4.1 1非静态内部类publicStringgetColor()returncolor;publicvoidsetColor(Stringcolor)this.color=color;publicvoidprint()System.out.println(牛腿颜色是:+this.
328、color+,高为:+this.height);/直接访问外部类的私有成员变量System.out.println(奶牛重量为:+weight);/外部类成员方法,访问内部类publicvoiddisplay()CowLegc1=newCowLeg(1.2,黑白花);c1.print();7.4.7.4.1 1非静态内部类publicclassInnerClassExample1publicstaticvoidmain(Stringargs)Cowcow=newCow(500);cow.display();/同一个包中的类直接调用public和默认修饰符的内部类,必须用import导入CowL
329、egc1=cow.newCowLeg(1.2,黑白花);c1.print();上述代码中,在Cow类中定义了一个默认修饰符的CowLeg内部类,并在CowLeg类的方法中直接访问了Cow类的私有属性。在InnerClassExample1类的main()方法中,通过两种方式访问了内部类,一种是直接通过外部类的方法访问内部类,这种方式适合所有修饰符的内部类;另一种是通过外部类对象访问内部类,需要用import导入内部类路径,也要受到内部类访问权限修饰符的限制。程序运行结果如下:牛腿颜色是:黑白花,高为:1.2奶牛重量为:500.0牛腿颜色是:黑白花,高为:1.2奶牛重量为:500.0上述代码编译
330、后,会生成三个class文件:两个是外部类的class文件Cow.class和InnerClassExample1.class,另一个是内部类的class文件Cow$CowLeg.class。内部类的class文件形式都是“外部类名$内部类名.class”。7.4.7.4.2 2局 部 内部类在方法中定义的内部类称为局部内部类。与局部变量类似,局部内部类不能用public、private等访问修饰符和static修饰符进行声明,它的作用域被限定在声明该类的方法块中。局部内部类的优势在于:它可以对外界完全隐藏起来,除了所在的方法之外,对其他方法而言是不透明的。此外与其他内部类比较,局部内部类不仅
331、可以访问包含它的外部类的成员,还可以访问局部变量,但这些局部变量必须被声明为final。如果需要用局部内部类定义变量、创建实例或派生子类,那么都只能在局部内部类所在的方法内进行。下述代码示例了一个局部内部类的定义和使用,代码如下所示。局部内部类7.4.7.4.2 2局 部 内部类【代码7.12】localInnerClassExample.javapackageinner;publicclasslocalInnerClassExampleinta=10;publicvoidprint()System.out.println(外部类的方法);publicvoidCreateInnerClassM
332、ethod()finalintb=20;/局部内部类访问的局部变量必须为final修饰符的classLocalInnerClassintc=30;voidinnerPrint()System.out.println(局部内部类的方法);7.4.7.4.2 2局 部 内部类classSubLocalInnerClassextendsLocalInnerClassintd=40;voiddisplay()print();innerPrint();System.out.println(局部内部类子类的方法);System.out.println(a=+a+,b=+b+,c=+c+,d=+d);Sub
333、LocalInnerClasssic=newSubLocalInnerClass();sic.display();publicstaticvoidmain(Stringargs)localInnerClassExamplelocal=newlocalInnerClassExample();local.CreateInnerClassMethod();7.4.7.4.2 2局 部 内部类程序运行结果如下:外部类的方法局部内部类的方法局部内部类子类的方法a=10,b=20,c=30,d=40上述代码CreateInnerClassMethod()方法中定义了两个局部内部类LocalInnerCla
334、ss和SubLocalInnerClass。编译后会生成三个class文件:localInnerClassExample.class、localInnerClassExample$1LocalInnerClass.class和localInnerClassExample$1SubLocalInnerClass.class。局部内部类的class文件形式都是“外部类名$N内部类名.class”。需要注意的是$符合后面多了一个数字,这是因为有可能有两个以上的同名局部类(处于不同方法中),所以使用一个数字进行区分。在项目的实际开发中,很少用到局部内部类,这是因为局部内部类的作用域很小,只能在当前方法
335、中使用。7.4.7.4.3 3静 态 内部类如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用static修饰的内部类被称为类内部类,也称为静态内部类。静态内部类可以包含静态成员,也可以包含非静态成员。静态内部类是外部类的一个静态成员,因此静态内部类的成员可以直接访问外部类的静态成员,也可以通过外部类对象访问外部类的非静态成员;外部类依然不能直接静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。下述代码示例了静态内部类的定义和使用,代码所示如下。静态内
336、部类7.4.7.4.3 3静 态 内部类【代码7.13】StaticInnerClassExample.javapackageinner;publicclassStaticInnerClassExampleprivatestaticinta=20;privateintb=1;staticclassstaticInnerClassprivateintc=10;privatestaticintd=30;publicvoidprint()System.out.println(静态内部类访问外部类成员显示:a=+a+,b=+newStaticInnerClassExample().b);staticv
337、oidtest()System.out.println(静态内部类中的静态方法);7.4.7.4.3 3静 态 内部类publicvoiddisplay()System.out.println(外部类方法中调用静态内部类成员:c=+newstaticInnerClass().c+,d=+staticInnerClass.d);/访问静态内部类中的静态方法和实例方法newstaticInnerClass().print();staticInnerClass.test();publicstaticvoidmain(Stringargs)StaticInnerClassExamplesce=newS
338、taticInnerClassExample();sce.display();/通过外部类创建静态内部类对象和调用静态内部类成员StaticInnerClassExample.staticInnerClassss=newStaticInnerClassExample.staticInnerClass();ss.print();StaticInnerClassExample.staticInnerClass.test();程序运行结果如下:外部类方法中调用静态内部类成员:c=10,d=30静态内部类访问外部类成员显示:a=20,b=1静态内部类中的静态方法静态内部类访问外部类成员显示:a=20,
339、b=1静态内部类中的静态方法7.4.7.4.3 3匿 名 内部类匿名内部类就是没有类名的内部类,适合创建那种只需要一次使用的类。创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。定义匿名内部类的格式如下:new父类构造方法(实参列表)|实现接口()/匿名内部类的类体部分从上面定义可以看出,匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类或实现一个接口。下述代码示例了匿名内部类的定义格式,代码如下所示。匿名内部类7.4.7.4.3 3匿 名 内部类publicclassAnonymousExamplepublicstaticvoidmain(
340、Stringargs)System.out.println(newObject()publicStringtoString()return匿名内部类定义的基本格式使用;);上述代码的含义是创建了一个匿名类对象,该匿名类重写Object父类的toString()方法。7.4.7.4.3 3匿 名 内部类在使用匿名内部类时,要注意遵循以下几个原则:(1)匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建匿名内部类的对象。(2)匿名内部类不能有构造方法,因为匿名内部类没有类名,所以无法定义构造方法,但匿名内部类可以定义实例初始化块,通过实例初始化块类完成构造方法需要完成的事情。(3)匿名
341、内部类不能定义任何静态成员、方法和类,但非静态的方法、属性、内部类是可以的。(4)只能创建匿名内部类的一个实例,最常用的创建匿名内部类的方式是需要创建某个接口类型的对象。(5)一个匿名内部类一定跟在new的后面,创建其实现的接口或父类的对象。(6)当通过接口来创建匿名内部类时,匿名内部类也不能显示创建构造方法,因此匿名内部类只有一个隐式的无参数构造方法,故new接口名后的括号里不能传入参数值。(7)如果通过继承父类来创建匿名内部类时,匿名内部类将拥有和父类相似的构造方法,即拥有相同的形参列表。(8)当创建匿名内部类时,必须实现接口或抽象父类里的所有抽象方法,也可以重写父类中的普通方法。(9)如
342、果匿名内部类需要访问外部类的局部变量,则必须使用final修饰符来修饰外部类的局部变量。(10)匿名内部类编译以后,会产生以“外部类名$序号”为名称的.class文件,序号以1n排列,分别代码1n个匿名内部类。7.4.7.4.3 3匿 名 内部类下述代码示例了匿名内部类的定义和使用,代码所示如下。【代码7.14】AnonyInnerExample.javapackageinner;abstractclassDeviceprivateStringname;publicabstractdoublegetPrice();publicDevice()publicDevice(Stringname)th
343、is.name=name;publicStringgetName()returnname;publicclassAnonyInnerExamplepublicvoidtest(Deviced)System.out.println(购买了一本+d.getName()+,花掉了+d.getPrice()+元);7.4.7.4.3 3匿 名 内部类publicstaticvoidmain(Stringargs)AnonyInnerExampleai=newAnonyInnerExample();/调用无参数构造方法创建Device匿名实现类的对象ai.test(newDevice()/初始化块Sys
344、tem.out.println(匿名内部类的初始化块);OverridepublicdoublegetPrice()/TODOAuto-generatedmethodstubreturn38.5;publicStringgetName()return面向对象程序设计Java;);7.4.7.4.3 3匿 名 内部类/调用有参数构造方法创建Device匿名实现类的对象ai.test(newDevice(面向对象程序设计Java)OverridepublicdoublegetPrice()return38.5;);程序运行结果如下:购买了一本面向对象程序设计Java,花掉了38.5元匿名内部类的初
345、始化块购买了一本面向对象程序设计Java,花掉了38.5元第第5节节part类之间的其他关系除了继承和实现外,依赖、关联、聚合和组成也是类之间的重要关系类型。类之间的其他关系本节概述7.5.7.5.1 1依赖关系依赖关系是最常见的一种类间关系,如果在一个类的方法中操作另外一个类的对象,则称其依赖于第二个类。例如,方法的参数是某种对象类型,或者方法中的某些对象类型的局部变量,或者方法中调用了另一个类的静态方法,这些都是依赖关系。依赖关系通常都是单向的。下述代码示例了两个类之间的依赖关系,代码如下所示。依赖关系7.5.7.5.1 1依赖关系【代码7.15】RelyOnExample.javapac
346、kagerelation;classCarvoidrun(Stringcity)System.out.println(欢迎,你来到+city);staticvoidprint()System.out.print(上汽大众公司:);classPersonvoidtravel(Carcar)Car.print();car.run(重庆);publicclassRelyOnExamplepublicstaticvoidmain(Stringargs)Carcar=newCar();Personp=newPerson();p.travel(car);上述代码中,Person类的travel()方法需要
347、Car类的对象作为参数,并且在该方法中调用了Car类的静态方法和实例方法,因此,Person类依赖于Car类。程序运行结果如下:上汽大众公司:欢迎,你来到重庆7.5.7.5.2 2关联关系关联关系比依赖关系更紧密,通常体现为一个类中使用另一个类的对象作为该类的成员变量。下述代码示例了两个类之间的关联关系,代码如下所示。关联关系7.5.7.5.2 2关联关系【代码7.16】CorrelationExample.javapackagerelation;classCarvoidrun(Stringcity)System.out.println(欢迎,你来到+city);staticvoidprint
348、()System.out.print(上汽大众公司:);classPersonCarcar;publicPerson(Carcar)this.car=car;voidtravel()Car.print();car.run(重庆);publicclassCorrelationExamplepublicstaticvoidmain(Stringargs)Carcar=newCar();Personp=newPerson(car);p.travel();上述代码中,Person类中存在Car类型的成员变量,并构造方法和实例方法中都使用该成员变量,因此Person类和Car类具有关联关系。运行结果和前
349、面依赖关系运行结果一样。7.5.7.5.3 3聚合关系聚合关系是关联关系的一种特例,体现的是整体与部分的关系,通常表现为一个类(整体)由多个其他类的对象(部分)作为该类的成员变量。此时整体与部分之间是可以分离的,整体和部分都可以具有各自的生命周期。下述代码示例了两个类之间的聚合关系,代码如下所示。聚合关系7.5.7.5.3 3聚合关系【代码7.17】AggregationExample.javapackagerelation;classEmployeeStringname;publicEmployee(Stringname)this.name=name;classDepartmentEmplo
350、yeeemps;publicDepartment(Employeeemps)this.emps=emps;publicvoidshow()for(Employeeemp:emps)System.out.println(emp.name);7.5.7.5.3 3聚合关系publicclassAggregationExamplepublicstaticvoidmain(Stringargs)Employeeemps=newEmployee(“张三”),newEmployee(“李四”),newEmployee(“王五”),newEmployee(“马六”),;Departmentdept=newD
351、epartment(emps);dept.show();上述代码中,部门类Department中的Employee数组diam此部门的员工,部门和员工的聚合关系可以理解为:部门由员工组成,同一个员工也可能属于多个部门,并且部门解散后,员工是依然存在的。程序运行结果如下:张三李四王五马六7.5.7.5.4 4组成关系组成关系是比聚合关系要求更高的一种关联关系,体现的也是整体与部分的关系。但组成关系中的整体和部分是不可分离的,整体的生命周期结束后,部分的生命周期也随之结束。例如,汽车是由发动机、底盘、车身和电路设备等组成的,是整体与部分的关系,如果汽车消亡后,这些设备也随之不存在,因此属于一种组成
352、关系。下述代码示例了类之间的组成关系,代码如下所示。组成关系7.5.7.5.4 4组成关系【代码7.18】CompostionExample.javapackagerelation;/发动机类classEngine/底盘类classChassis/车身类classBodywork/电路设备classCircuitry7.5.7.5.4 4组成关系/汽车类classBusEngineengine;Chassischassis;Bodyworkbodywork;Circuitrycircuitry;publicBus(Enginee,Chassisch,Bodyworkb,Circuitryc)e
353、ngine=e;chassis=ch;bodywork=b;circuitry=c;publicclassCompostionExamplepublicstaticvoidmain(Stringargs)Enginee=newEngine();Chassisch=newChassis();Bodyworkb=newBodywork();Circuitryc=newCircuitry();Busbus=newBus(e,ch,b,c);上述代码定义了五个类,它们之间构成了一种组成关系。本章课后作业见教材JAVA程 序 设计本章学习目标:掌握抽象类的定义、应用和特点掌握接口的定义、应用和特点理解枚
354、举类的定义和应用第八章第八章抽象类、接口和枚举抽象类、接口和枚举第第1节节part抽象类在定义类时,并不是所有的类都能够完整地描述该类的行为。在某些情况下,只知道应该包含怎样的方法,但无法准确地知道如何实现这些方法时,可以使用抽象类。抽 象类本节概述抽象类是对问题领域进行分析后得出的抽象概念,是对一批看上去不同,但是本质上相同的具体概念的抽象。例如,定义一个动物类Animal,该类提供一个行动方法action(),但不同的动物的行动方式是不一样的,如牛羊是跑的,鱼儿是游的,鸟儿是飞的,此时就可以将Animal类定义成抽象类,该类既能包含action()方法,又无须提供其方法的具体实现。这种只有
355、方法头,没有方法体的方法称为抽象方法。定义抽象方法只需在普通方法上增加abstract修饰符,并把普通方法的方法体全部去掉,并在方法后增加分号即可。抽象类和抽象方法必须使用“abstract”关键字来修饰,其语法格式如下:【访问符】abstractclass类名【访问符】abstract方法名(参数列表);8.1.8.1.1 1抽 象 类 的定义抽象类的定义有抽象方法的类只能被定义为抽象类,但抽象类中可以没有抽象方法。定义抽象类和抽象方法的规则如下:(1)abstract关键字放在class前,指明该类是抽象类。(2)abstract关键字放在方法的返回值类型前,指明该方法是抽象方法。(3)抽
356、象类不能被实例化,即无法通过new关键字直接创建抽象类的实例。(4)一个抽象类中可以有多个抽象方法,也可以有实例方法。(5)抽象类可以包含成员变量、构造方法、初始化块、内部类、枚举类和方法等,但不能通过构造方法创建实例,可在子类创建实例时调用。(6)定义抽象类有三种情况:直接定义一个抽象类;继承一个抽象类,但没有完全实现父类包含的抽象方法;实现一个接口,但没有完全实现接口中包含的抽象方法。下述代码示例了抽象类和抽象方法的定义,代码如下所示。8.1.8.1.1 1抽 象 类 的定义【代码8.1】Shape.javapackagecom;publicabstractclassShapeprivat
357、eStringcolor;/初始化块System.out.println(执行抽象类中的初始化块);/构造方法publicShape()publicShape(Stringcolor)this.color=color;System.out.println(执行抽象类中的构造方法);8.1.8.1.1 1抽 象 类 的定义publicStringgetColor()returncolor;publicvoidsetColor(Stringcolor)this.color=color;/抽象方法publicabstractdoublearea();publicabstractStringgetTy
358、pe();上述代码定义了两个抽象方法:area()和getType(),所以这个Shape类只能被定义为抽象类。虽然Shape类包含了构造方法和初始化块,但不能直接通过构造方法创建对象,只有通过其子类实例化。抽象类不能实例化,只能被当成父类来继承。从语义角度上讲,抽象类是从多个具有相同特征的类中抽象出来的一个父类,具有更高层次的抽象,作为其子类的模板,从而避免子类设计的随意性。下述代码定义一个三角形类,该类继承Shape类,并实现其抽象方法,以此示例抽象类的使用。代码如下所示。8.1.8.1.2 2抽 象 类 的使用抽象类的使用【代码8.2】Triangle.javapackagecom;pu
359、blicclassTriangleextendsShapeprivatedoublea;privatedoubleb;privatedoublec;publicTriangle(Stringcolor,doublea,doubleb,doublec)super(color);this.a=a;this.b=b;this.c=c;Overridepublicdoublearea()/海伦公式计算三角形面积doublep=(a+b+c)/2;doubles=Math.sqrt(p*(p-a)*(p-b)*(p-c);returns;8.1.8.1.2 2抽 象 类 的使用Overridepubli
360、cStringgetType()if(a=b+c|b=a+c|c=a+b)return三边不能构成一个三角形;elsereturn三边能构成一个三角形;publicstaticvoidmain(Stringargs)Trianglet=newTriangle(RED,3,4,5);System.out.println(t.getType();System.out.println(三角形面积为:+t.area();程序运行结果如下:执行抽象类中的初始化块执行抽象类中的构造方法三边能构成一个三角形三角形面积为:6.08.1.8.1.2 2抽 象 类 的使用当使用abstract修饰类时,表明这个类
361、只能被继承;当使用abstract修饰方法时,表明这个方法必须由子类提供实现(即重写)。而final修饰的类不能被继承,修饰的方法不能被重写,因此,final与abstract不能同时使用。除此之外,static和abstract也不能同时使用,并且抽象方法不能定义为private访问权限。抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会大致保留抽象类的行为方式。如果编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给子类实现,这就是一种模板模式,模板模式也是十分常见且简单的设计模式之一。下面再介绍一个模板模
362、式的示例,在这个示例的抽象父类中,父类的普通方法依赖于一个抽象方法,而抽象方法则推迟到子类中提供实现。代码如下所示。8.1.8.1.3 3抽 象 类 的作用抽象类的作用【代码8.3】CarSpeedMeterExample.javapackagecom;abstractclassSpeedMeterprivatedoubleturnRate;/转速publicSpeedMeter()/返回车轮半径的方法定义为抽象方法publicabstractdoublegetRadius();publicvoidsetTurnRate(doubleturnRate)this.turnRate=turnRat
363、e;/定义计算速度的通用方法publicdoublegetSpeed()/速度(KM/H)=车轮周长*转速*3.6returnMath.round(3.6*Math.PI*2*getRadius()*turnRate);8.1.8.1.3 3抽 象 类 的作用publicclassCarSpeedMeterExampleextendsSpeedMeterOverridepublicdoublegetRadius()return0.30;publicstaticvoidmain(Stringargs)CarSpeedMeterExamplecsm=newCarSpeedMeterExample(
364、);csm.setTurnRate(10);System.out.println(车速为:+csm.getSpeed()+公里/小时);8.1.8.1.3 3抽 象 类 的作用上面程序定义了一个抽象类SpeedMeter(车速表),该类中定义了一个getSpeed()方法,该方法用于返回当前车速,而getSpeed()方法依赖于getRadius()方法的返回值。对于该抽象类来说,无法确定车轮的半径,因此getRadius()方法必须推迟到子类中来实现。在其子类CarSpeedMeterExample中,实现了父类的抽象方法,既可以创建实例对象,也可以获得当前车速。程序运行结果如下:车速为:6
365、8.0公里/小时下面是使用模板模式的一些简单规则。(1)抽象父类可以只定义需要使用的某些方法,把不能实现的部分抽象成抽象方法,留给其子类去实现。(2)父类中可能包含需要调用的其他系列方法的方法,这些被调方法既可以由父类实现,也可以由其子类实现。8.1.8.1.3 3抽 象 类 的作用第第2节节part接口接口定义了某一批类所需要遵守的公共行为规范,只规定这批类必须提供的某些方法,而不提供任何实现。接口体现的是规范和实现分离的设计哲学。让规范和实现分离正是接口的好处,让系统的个模块之间面向接口耦合,是一种松耦合的设计,从而降低各模块之间的耦合,增强系统的可扩展性和可维护性。接口本节概述8.2.8
366、.2.1 1接 口 的定义Java只支持单继承,不支持多重继承,即一个类只能继承一个父类,这一缺陷可以通过接口来弥补。Java允许一个类实现多个接口,这样使程序更加灵活、易扩展。和类定义不同,定义接口不再使用class关键字,而是使用interface关键字。接口定义的基本语法格式如下:【访问符】interface接口名/静态常量定义/抽象方法定义/默认方法、类方法、内部类等其他定义接口的定义8.2.8.2.1 1接 口 的定义其中,定义接口要遵守如下规则:(1)访问符可以是public或者默认,默认采用包权限访问控制,即在相同包中才可以访问该接口。(2)在接口体力可以包含静态常量、抽象方法、
367、内部类、内部接口以及枚举类的定义。从Java8版本开始允许接口中定义默认方法、类方法。(3)与类的默认访问符不同,接口体内定义的常量、方法等都默认为public,可以省略public关键字。(4)接口名应与类名采用相同的命名规范。(5)接口里不能包含构造方法和初始化块。下述代码示例了接口的定义规则,代码如下所示。8.2.8.2.1 1接 口 的定义【代码8.4】InterfaceDefinition.javapackagecom;publicinterfaceInterfaceDefinitionpublicfinalstaticintSIZE=0;/定义静态常量publicabstractv
368、oiddisplay();/定义抽象方法/定义默认方法,需要default修饰defaultvoidprint()System.out.println(接口中的默认方法);/定义静态方法staticvoidshow()System.out.println(接口中的类方法);/定义内部类classInnerClass/定义内部接口interfaceMyInnerInterface8.2.8.2.1 1接 口 的定义上述代码中定义了一个接口InterfaceDefinition,并在接口中声明了静态常量、抽象方法、默认方法、类方法、内部类和内部接口。其中:(1)接口中定义的成员变量如果没有声明修饰
369、符,系统会自动为其增加“publicstaticfinal”进行修饰,即接口中定义的成员变量都是静态常量。(2)接口中定义的方法只能是抽象方法、默认方法和类方法。因此,如果是定义普通方法没有声明修饰符,系统将自动增加“publicabstract”进行修饰,即接口中定义的普通方法都是抽象方法,不能有方法体。(3)从Java8开始,允许在接口中定义默认方法,默认方法必须使用“default”关键字修饰,不能使用“static”关键字修饰。因此,不能直接使用接口来调用默认方法,必须通过接口的实现类的实例对象来调用默认方法,默认方法必须有方法体。(4)从Java8开始,允许在接口中定义类方法,类方法
370、必须使用“static”关键字修饰,不能使用“default”关键字修饰,类方法必须有方法体,可以直接通过接口来调用类方法。(5)接口中定义的内部类、内部接口以及内部枚举都默认为“publicstatic”。8.2.8.2.2 2接 口 的实现接口不能用于创建实例,但接口可以用于声明引用类型变量。当使用接口来声明引用类型变量时,这个引用类型变量必须引用到其实现类的对象。除此之外,接口的主要用途就是被实现类实现。一个类可以实现一个或多个接口,实现则使用implements关键字。由于一个类可以实现多个接口,这也是为Java单继承灵活性不足所做的补充。类实现接口的语法格式如下:【访问符】【修饰符】
371、class类名implements接口名1,接口名2/类体部分接口的实现8.2.8.2.2 2接 口 的实现其中:(1)访问符、修饰符、class和类名与前面类的声明格式完全相同。(2)implements关键字用于实现接口。(3)一个类可以实现多个接口,接口之间使用逗号隔开。(4)一个类在实现一个或多个接口时,必须全部实现这些接口中定义的所有抽象方法,否则该类必须定义为抽象类。(5)一个类实现某个接口时,该类将会获得接口中定义的常量和方法等。下述代码示例了对前面声明接口的实现,代码如下所示。8.2.8.2.2 2接 口 的实现【代码8.5】MyInterface.javapackagecom
372、;publicclassMyInterfaceimplementsInterfaceDefinition/实现接口中定义的抽象方法publicvoiddisplay()System.out.println(重写接口中的抽象方法);publicstaticvoidmain(Stringargs)/实例化一个接口实现类的对象,并将其赋值给一个接口引用变量InterfaceDefinitionmyInterface=newMyInterface();/调用接口中的默认方法myInterface.print();/访问接口中的静态常量System.out.println(InterfaceDefini
373、tion.SIZE);/调用实现类中的方法(对接口中抽象方法的实现)myInterface.display();/调用接口中的类方法InterfaceDefinition.show();8.2.8.2.2 2接 口 的实现与抽象类一样,接口是一种更加抽象的类结构,因此不能对接口直接实例化,但可以声明接口变量,并用接口变量指向当前接口实现类的实例。使用接口变量指向该接口实现类的实例对象,这也是多态性的一种体现。程序运行结果如下:接口中的默认方法0重写接口中的抽象方法接口中的类方法8.2.8.2.3 3接 口 的继承接口的继承与类的继承不一样,接口完全支持多重继承,即一个接口可以有多个父接口。除此
374、之外,接口的继承与类的继承相似,当一个接口继承父接口时,该接口将获得父接口中国定义的所有方法和常量。一个接口可以继承多个接口时,多个接口跟在extends关键字之后,并用逗号隔开,接口继承语法格式如下:【访问符】interface子接口名extends父接口名1,父接口名2/子接口新增的常量和方法下述代码示例了接口的继承和实现,代码如下所示。接口的继承8.2.8.2.3 3接 口 的继承【代码8.6】InterfaceExtends.javapackagecom;interfaceMyInterfaceAintA=10;voidshowA();interfaceMyInterfaceBintB
375、=20;voidshowB();interfaceMyInterfaceCextendsMyInterfaceA,MyInterfaceBintC=30;voidshowC();8.2.8.2.3 3接 口 的继承publicclassInterfaceExtendsimplementsMyInterfaceCpublicvoidshowA()System.out.println(实现showA()方法);publicvoidshowB()System.out.println(实现showB()方法);publicvoidshowC()System.out.println(实现showC()方
376、法);publicstaticvoidmain(Stringargs)MyInterfaceCmc=newInterfaceExtends();/通过接口名直接访问接口中的静态常量System.out.println(A=+MyInterfaceC.A+,B=+MyInterfaceC.B+,C=+MyInterfaceC.C);/调用接口中的方法mc.showA();mc.showB();mc.showC();8.2.8.2.3 3接 口 的继承上述代码中,分别定义了MyInterfaceA、MyInterfaceB和MyInterfaceC三个接口,其中MyInterfaceC接口继承了M
377、yInterfaceA接口和MyInterfaceB接口,因此MyInterfaceC接口获得了MyInterfaceA接口和MyInterfaceB接口定义的常量和抽象方法。在InterfaceExtends类中实现了MyInterfaceC接口,就需要将三个接口中的抽象方法进行全部实现。程序运行结果如下:A=10,B=20,C=30实现showA()方法实现showB()方法实现showC()方法8.2.8.2.3 3接 口 的继承接口和抽象类有很多相似之处,都具有如下特征:(1)接口和抽象类都不能被实例化,都需要被其他类实现或继承。(2)接口和抽象类的引用变量都可以指向其实现类或子类的实
378、例对象。(3)接口和抽象类都可以包含抽象方法,实现接口和继承抽象类时都必须实现这些抽象方法。8.2.8.2.3 3接 口 的继承但接口与抽象类之间也存在区别,主要体现在以下几点:(1)接口体现的是一种规范,这种规范类似于总纲,是系统各模块应该遵循的标准,以便各模块之间实现耦合以及通信功能;抽象类体现的是一种模板式设计,抽象类可以被当成系统实现过程中的中间产品,该产品已实现了部分功能但不能当成最终产品,必须进一步完善,而完善可能有几种不同的方式。(2)接口中除了默认方法和类方法,不能为普通方法提供方法实现,而抽象类可以为普通方法提供方法实现。(3)接口中定义的变量默认为publicstaticf
379、inal,且必须赋值,其实现类中不能重新定义,也不能改变其值,即接口中定义的变量都是最终的静态常量;而抽象类中定义的变量与普通类一样,默认为友好的,其实现类可以重新定义也可以根据需要改变值。(4)接口中定义的方法都默认为public,而抽象类则与普通类一样,默认为友好的。(5)接口不包含构造方法,而抽象类可以包含构造方法。抽象类的构造方法不是用于创建对象,而是让其子类调用以便完成初始化操作。(6)一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,一个接口也可以有多个父接口。第第3节节part枚举在开发过程中,经常遇到一个类的实例对象是有限而且固定的情况,例如季节类,只有春
380、、夏、秋、冬四个实例对象,月份类只有12个实例对象。这种类的实例对象有限而且固定的,在Java中被称为枚举类。早期使用简单的静态常量来表示枚举,但存在不安全因素,因此,从JDK5开始新增了对枚举类的支持。定义枚举类使用enum关键字,该关键字的地位与class、interface相同。枚举类是一种特殊的类,与普通类有如下区别:枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Eunm类,而不是继承Object类,因此枚举类不能显示继承其他父类。使用enum定义非抽象的枚举类时默认会使用final修饰,因此枚举类不能派生子类。枚举类的构造方法只能使用privat
381、e访问修饰符,如果省略,则默认使用private修饰;如果强制指定访问修饰符,则只能指定为private。枚举类的所有实例必须在枚举类的类体第一行显式列出,否则该枚举永远不能产生实例。列出的枚举实例默认使用publicstaticfinal进行修饰。枚举本节概述8 8.3.3.1 1枚 举 类 的定义使用enum关键字来定义一个枚举类,语法格式如下:修饰符enum枚举类名/第一行列举枚举实例.下述代码定义一个季节枚举类,该枚举类中有4个枚举实例:春、夏、秋、冬。代码如下所示。【代码8.7】SeasonEnum.javapackagecom;publicenumSeasonEnum/在第一行列出
382、4个枚举实例:春、夏、秋、冬SPRING,SUMMER,FALL,WINTER;枚举类的定义8 8.3.3.1 1枚 举 类 的定义上述代码中SPRING、SUMMER、FALL、WINTER被称为枚举实例,其类型就是声明的SeasonEnum枚举类型,其默认使用publicstaticfinal进行修饰。枚举实例之间使用英文格式逗号“,”隔开,枚举值列举之后使用英文格式分号“;”结束。枚举一旦被定义,就可以直接使用该类型的枚举实例,枚举实例的声明和使用方式类似于基本类型,但不能使用new关键字实例化一个枚举。所有枚举类型都会包括两个预定义方法:values()和ValueOf(),其功能描述
383、如表8-1所示。8 8.3.3.1 1枚 举 类 的定义使用枚举类的某个实例的语法格式如下:枚举类.实例下述案例示例了如何对SeasonEnum枚举类进行使用,代码如下所示。【代码8.8】SeasonEnumExample.javapackagecom;publicclassSeasonEnumExamplepublicstaticvoidmain(Stringargs)System.out.println(SeasonEnum枚举类的所有实例值:);/枚举类默认有一个values方法,返回该枚举类的所有实例值for(SeasonEnums:SeasonEnum.values()System.
384、out.print(s+);System.out.println();/定义一个枚举类对象,并直接赋值SeasonEnumseason=SeasonEnum.WINTER;8 8.3.3.1 1枚 举 类 的定义/使用switch语句判断枚举值switch(season)caseSPRING:System.out.println(春暖花开,正好踏青);break;caseSUMMER:System.out.println(夏日炎炎,适合游泳);break;caseFALL:System.out.println(秋高气爽,进补及时);break;caseWINTER:System.out.pri
385、ntln(冬日雪飘,围炉赏雪);break;8 8.3.3.1 1枚 举 类 的定义对上述代码需要注意三点:调用values()方法可以返回SeasonEnum枚举类的所有实例值;定义一个枚举类型的对象时,不能使用new关键字,而是使用枚举类型的实例值直接赋值;在switch语句中直接使用枚举类型作为表达式进行判断,而case表达式中的值直接使用枚举实例值的名字,前面不能使用枚举类作为限定。程序运行结果如下:SeasonEnum枚举类的所有实例值:SPRINGSUMMERFALLWINTER冬日雪飘,围炉赏雪8.3.8.3.2 2包含属性和方法的枚举类枚举类也是一种类,具有与其他类几乎相同的特
386、性,因此可以定义枚举的属性、方法以及构造方法。但是,枚举类的构造方法只是在构造枚举实例值时被调用。每一个枚举实例都是枚举类的一个对象,因此,创建每个枚举实例时都需要调用该构造方法。下述代码对前面定义的SeasonEnum枚举类进行升级,定义的枚举类中包含属性、构造方法和普通方法,代码如下所示。包含属性和方法的枚举类8.3.8.3.2 2包含属性和方法的枚举类【代码8.9】SeasonEnum2.javapackagecom;/带构造方法的枚举类publicenumSeasonEnum2/在第一行列出4个枚举实例:春、夏、秋、冬SPRING(春),SUMMER(夏),FALL(秋),WINTER
387、(冬);/定义一个属性privateStringname;/构造方法SeasonEnum2(Stringname)this.name=name;/方法publicStringtoString()returnthis.name;8.3.8.3.2 2包含属性和方法的枚举类上述代码定义一个名为SeasonEnum2的枚举类型,该枚举类中包含一个name属性;一个带有一个参数的构造方法,用于给name属性赋值;重写toString()方法并返回name属性值。值得注意的是:在定义枚举类的构造方法时,不能定义public构造方法,枚举类构造方法访问修饰符只能缺省或使用private,缺省时默认为pri
388、vate。使用枚举时要注意两个限制:首先,枚举默认继承java.lang.Enum类,不能继承其他类;其次,枚举本身是final类,不能被继承。下述案例示例了对SeasonEnum2枚举类型的使用,代码如下所示。8.3.8.3.2 2包含属性和方法的枚举类【代码8.10】SeasonEnum2Example.javapackagecom;publicclassSeasonEnum2Examplepublicstaticvoidmain(Stringargs)System.out.println(SeasonEnum2枚举类的所有实例值:);/枚举类默认有一个values方法,返回该枚举类的所有
389、实例值for(SeasonEnum2s:SeasonEnum2.values()System.out.print(s+);System.out.println();/使用valueOf()方法获取指定的实例SeasonEnum2se=SeasonEnum2.valueOf(SUMMER);/输出seSystem.out.println(se);/调用judge()方法judge(se);System.out.println(-);/定义一个枚举类对象,并直接赋值SeasonEnum2season=SeasonEnum2.WINTER;/输出seasonSystem.out.println(se
390、ason);8.3.8.3.2 2包含属性和方法的枚举类/调用judge()方法judge(season);/判断季节并输出privatestaticvoidjudge(SeasonEnum2season)/使用switch语句判断枚举值switch(season)caseSPRING:System.out.println(春暖花开,正好踏青);break;caseSUMMER:System.out.println(夏日炎炎,适合游泳);break;caseFALL:System.out.println(秋高气爽,进补及时);break;caseWINTER:System.out.printl
391、n(冬日雪飘,围炉赏雪);break;8.3.8.3.2 2包含属性和方法的枚举类上述代码中,定义一个judge()方法用于判断季节;在main()方法中调用values()方法返回SeasonEnum2枚举类型的所有实例值并输出;调用valueOf()方法可以获取指定的实例。程序运行结果如下所示:SeasonEnum2枚举类的所有实例值:春夏秋冬夏夏日炎炎,适合游泳-冬冬日雪飘,围炉赏雪观察运行结果,可以发现此处输出的SeasonEnum2枚举实例为春、夏、秋、冬,而不是SPRING、SUMMER、FALL、WINTER。这是因为在定义SeasonEnum2枚举类时重写了toString()
392、方法,该方法的返回值为name属性值,当调用System.out.println()方法输出SeasonEnum2枚举对象时,系统会自动调用toString()方法。8.3.8.3.3 3Enum类所有枚举类都继承自java.lang.Enum,该类定义了枚举类型共用的方法。该类实现了java.lang.Serializable和java.lang.Comparable接口,Enum类常用的方法如表8-2所示。Enum类下述案例通过前面定义的枚举类,来示例Enum类中常用方法的适用于,代码如下所示。8.3.8.3.3 3Enum类【代码8.11】EnumMethodExample.javapa
393、ckagecom;publicclassEnumMethodExamplepublicstaticvoidmain(Stringargs)System.out.println(SeasonEnum枚举类的所有实例值以及顺序号:);/输出SeasonEnum类的实例值以及顺序号for(SeasonEnums:SeasonEnum.values()System.out.println(s+-+s.ordinal();System.out.println(-);/声明4个SeasonEnum对象SeasonEnums1,s2,s3,s4;/赋值s1=SeasonEnum.SPRING;s2=Seas
394、onEnum.SUMMER;s3=SeasonEnum.FALL;/调用Enum类的静态方法获取指定枚类型、指定值的枚举实例s4=Enum.valueOf(SeasonEnum.class,FALL);/等价于s4=SeasonEnum.valueOf(FALL);8.3.8.3.3 3Enum类/使用compareTo()进行比较if(pareTo(s2)0)System.out.println(s1+在+s2+之前);/使用equals()判断if(s3.equals(s4)System.out.println(s3+等于+s4);/使用=判断if(s3=s4)System.out.pri
395、ntln(s3+=+s4);System.out.println(-);System.out.println(SeasonEnum2枚举类的所有实例值以及顺序号:);/输出SeasonEnum类的实例值以及顺序号for(SeasonEnum2s:SeasonEnum2.values()System.out.println(s+-+s.ordinal();System.out.println(-);8.3.8.3.3 3Enum类/声明4个SeasonEnum对象SeasonEnum2se1,se2,se3,se4;/赋值se1=SeasonEnum2.SPRING;se2=SeasonEnum
396、2.SUMMER;se3=SeasonEnum2.FALL;/调用Enum类的静态方法获取指定枚类型、指定值的枚举实例se4=Enum.valueOf(SeasonEnum2.class,FALL);/等价于se4=SeasonEnum2.valueOf(FALL);/使用compareTo()进行比较if(pareTo(se2)0)System.out.println(se1+在+se2+之前);/使用equals()判断if(se3.equals(se4)System.out.println(se3+等于+se4);/使用=判断if(se3=se4)System.out.println(s
397、e3+=+se4);8.3.8.3.3 3Enum类上述代码中,equals()方法用于比较一个枚举实例值和任何其他对象,但只有这两个对象属性同一个枚举类型且值也相同时,二者才会相等;比较两个枚举引用是否相等时可直接使用“=”。调用Enum类的valueOf()静态方法可以获取指定枚举型、指定值的枚举实例。程序运行结果如下:SeasonEnum枚举类的所有实例值以及顺序号:SPRING-0SUMMER-1FALL-2WINTER-3-SPRING在SUMMER之前FALL等于FALLFALL=FALL-SeasonEnum2枚举类的所有实例值以及顺序号:春-0夏-1秋-2冬-3-春在夏之前秋等
398、于秋秋=秋本章课后作业见教材JAVA程 序 设计本章学习目标:理解异常的概念和异常处理机制理解Java异常的分类掌握try、catch、finally使用方法掌握throw、throws的使用方法掌握自定义异常的定义和使用方法第第九九章章异常异常第第1节节part 异常概述程序中的错误可以分为三类:语法错误、逻辑错误和运行时错误。程序经过编译和测试后,前两种错误基本可以排除,但在程序运行过程中,仍然可能发生一些预料不到的情况导致程序出错,这种在运行时出现的意外错误称为“异常”。对异常的处理机制也成为判断一种语言是否成熟的标准。好的异常处理机制会使程序员更容易写出健壮的代码,防止代码中Bug的蔓
399、延。在某些语言中,例如传统的C语言没有提供异常处理机制,程序员被迫使用多条if语句来检测所有可能导致错误的条件,这样会使代码变得非常复杂。而目前主流的编程语言,例如Java、C+等都提供了成熟的异常处理机制,可以使程序中的异常处理代码和正常的业务代码分离,一旦出现异常,很容易查到并解决,提高了程序的健壮性。异 常 概述异常概述程序发生异常时,有很多有用的信息需要保存,Java提供了丰富的异常类,当异常发生时有运行时环境自动产生相应异常类的对象保存相应异常信息,这些异常类之间有严格的继承关系。如图9.1所示列举了Java常见的异常类之间的继承关系。9.1.9.1.1 1异 常类异常类Java中的
400、异常类可以分为两种:(1)错误(Error):一般指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这些错误无法恢复或捕获,将导致应用程序中断。(2)异常(Exception):因程序编码错误或外在因素导致的问题,这些问题能够被系统捕获并进行处理,从而避免应用程序非正常中断,例如:除以0、对负数开平方根、空指针访问等。Throwable是所有异常类的父类,Error和Exception都继承此类。当程序产生Error时,因系统无法捕获Error并处理,程序员将无能为力,程序只能中断;而当发生Exception时,系统可以捕获并做出处理。9.1.9.1.1 1异 常类Exceptio
401、n异常从编程角度又可以分为以下两种类型:(1)非检查型异常:编译器不要求强制处置的异常,该异常是因编码或设计不当导致的,这种异常可以避免,RuntimeException及其所有子类都属于非检查型异常。(2)检查型异常:编译器要求及其子类必须处理的异常,该异常是程序运行时因外界因素而导致的,Exception及其子类(RuntimeException及其子类除外)都属于检查型异常。常见的异常类说明如表9-1所示9.1.9.1.1 1异 常类检查型异常体现了Java语言的严谨性,程序员必须对该类型的异常进行处理,否则程序编译不通过,无法运行。RuntimeException及其子类都是Excep
402、tion的子类,Exception是所有能够处理的异常的父类。异常是在程序执行期间产生的,会中断正常的指令流,使程序不能正常执行下去。例如,除法运算时被除数为0、数组下标越界、字符串转换为数值型等,都会产生异常,影响程序的正常运行。下述代码示例了会产生被除数为0的算术异常,程序不能正常执行完毕,最后的输出语句不能输出,代码如下所示。9.1.9.1.2 2异 常 处 理机制异常处理机制【代码9.1】ExceptionExample.javapackagecom;publicclassExceptionExamplepublicstaticvoidmain(Stringargs)System.ou
403、t.println(开始运行程序:);/产生除以0的算术异常,程序中断inti=10/0;/因执行上一句代码时程序产生异常,中断,该条语句不会执行System.out.println(异常处理机制);9.1.9.1.2 2异 常 处 理机制程序运行结果如下:开始运行程序:Exceptioninthreadmainjava.lang.ArithmeticException:/byzeroatcom.ExceptionExample1.main(ExceptionExample1.java:8)通过运行结果可以看到,程序出现异常将中断运行。提示“/byzero”的ArithmeticExcepti
404、on算术异常,最后输出的语句没有执行。为了使程序出现异常时也能正常运行下去,需要对异常进行相关的处理操作,这种操作称之为“异常处理”。Java的异常处理机制可以让程序具有良好的容错性,当程序运行过程中出现意外情况发生时,系统会自动生成一个异常对象来通知程序,程序再根据异常对象的类型进行相应的处理。Java提供的异常处理机制有两种:(1)使用trycatch捕获异常:将可能产生异常的代码放在try语句中进行隔离,如果遇到异常,程序会停止执行try块的代码,跳到catch块中进行处理。(2)使用throws声明抛出异常:当前方法不知道如何处理所出现的异常,该异常应由上一级调用者进行处理,可在定义该
405、方法时使用throws声明抛出异常。Java的异常处理机制具有以下几个优点:(1)异常处理代码和正常的业务代码分离,提高了程序的可读性,简化了程序的结构,保证了程序的健壮性。(2)将不同类型的异常进行分类,不同情况的异常对应不同的异常类,充分发挥类的可扩展性和可重用性的优势。(3)可以对程序产生的异常进行灵活处理,如果当前方法有能力处理异常,就使用trycatch捕获并处理;否则使用throws声明要抛出的异常,由该方法的上一级调用者来处理异常。9.1.9.1.2 2异 常 处 理机制第第2节节part捕获异常Java中捕获异常并处理的语句有以下几种:(1)trycatch语句(2)tryca
406、tchfinally语句(3)自动关闭资源的try语句(4)嵌套的trycatch语句(5)多异常捕获捕获异常捕获异常9.2.9.2.1 1try catch语句trycatch语句的基本语法格式如下:try/业务实现代码(可能发生异常).catch(异常类1异常对象)/异常类1的处理代码catch(异常类2异常对象)/异常类2的处理代码./可以有多个catch语句catch(异常类n异常对象)/异常类n的处理代码trycatch语句其中:(1)执行try语句中的业务代码出现异常时,系统会自动生成一个异常对象,该异常对象被提交给Java运行时环境,此过程称为“抛出异常”;(2)当Java运行时
407、环境收到异常对象时,会寻找能处理该异常对象的catch语句,即跟catch语句中的异常类型进行一一匹配,如果匹配成功,则执行相应的catch块进行处理,这个过程称为“捕获异常”;(3)try语句后可以有一条或多条catch语句,这是针对不同的异常类提供不同的异常处理方式。9.2.9.2.1 1try catch语句1.单单catch处理语句处理语句单catch处理语句只有一个catch,是最简单的捕获异常处理语句。下面代码示例了使用单catch处理语句对异常进行捕获并处理,代码所示如下。【代码9.2】SingleCatchExample.javapackagecom;publicclassSi
408、ngleCatchExamplepublicstaticvoidmain(Stringargs)intarray=1,2,3,4,5;try/产生数组下标越界异常for(inti=0;i=array.length;i+)System.out.print(arrayi+);catch(Exceptione)/输出异常信息e.printStackTrace();System.out.println(程序运行结束);上述代码中只有一条catch语句,其处理的异常类型是Exception。e是异常对象名,这里可以将每个catch块看成一个方法,e就是形参,将来的实参是运行时环境根据错误情况自动产生的异
409、常类对象,方法的调用是自动调用。而e.printStackTrace()方法将异常信息输出。9.2.9.2.1 1try catch语句所有异常对象都包含以下几个常用方法用于访问异常信息:(1)getMessage()方法:返回该异常的详细描述字符串;(2)printStackTrace()方法:将该异常的跟踪栈信息输出到标准错误输出;(3)printStackTrace(PrintStreams)方法:将该异常的跟踪栈信息输出到指定输出流;(4)getStackTrace()方法:返回该异常的跟踪栈信息。程序运行结果如下所示:12345java.lang.ArrayIndexOutOfBou
410、ndsException:5atcom.SingleCatchExample.main(SingleCatchExample.java:9)程序运行结束9.2.9.2.1 1try catch语句通过运行结果可以分析出,当执行try块中的语句产生异常时,try块中剩下的代码不会再执行,而是执行catch语句;catch语句处理结束后,程序继续向下运行。因此,最后的输出语句会被执行,“程序运行结束”字符串被输出显示,程序正常退出。单catch处理语句的执行流程如图9.2所示。9.2.9.2.1 1try catch语句2.多多catch语句语句多catch处理语句有多个catch,是最常用的、针
411、对不同异常进行捕获处理的语句。下述代码示例了使用多个catch处理语句对多种异常进行捕获并处理,代码如下所示。【代码9.3】MultiCatchExample.javapackagecom;importjava.util.Scanner;publicclassMultiCatchExamplepublicstaticvoidmain(Stringargs)Scannerscanner=newScanner(System.in);intarray=newint3;trySystem.out.println(请输入第1个数:);Stringstr=scanner.next();/从键盘获取一个字符
412、串/将不是整数数字的字符串转换成整数,会引发NumberFormatExceptionintn1=Integer.parseInt(str);System.out.println(请输入第2个数:);intn2=scanner.nextInt();/从键盘获取一个整数/两个数相除,如果n2是0,会引发ArithmeticExceptionarray1=n1/n2;9.2.9.2.1 1try catch语句/给a3赋值,数组下标越界,引发ArrayIndexOutOfBoundsException)array3=n1*n2;System.out.println(两个数的和是+(n1+n2);c
413、atch(NumberFormatExceptionex)System.out.println(数字格式化异常!);catch(ArithmeticExceptionex)System.out.println(算术异常!);catch(ArrayIndexOutOfBoundsExceptionex)System.out.println(下标越界异常!);catch(Exceptionex)System.out.println(其他未知异常!);System.out.println(程序结束!);上述代码中,try语句后跟着4条catch语句,分别针对NumberFormatException
414、、ArithmeticException、ArrayIndexOutOfBoundsException和Exception这4种类型的异常进行处理。根据输入数据的不同,其执行结果也会不同。9.2.9.2.1 1try catch语句当从键盘输入“xsc”时,将该字符串转换成整数会产生NumberFormateException异常,因此对应的异常处理会输出“数字格式化异常!”,运行结果如下所示:请输入第1个数:xsc数字格式化异常!程序结束!当输入的第一个数是10,第二个数是“0”,则会产生ArithmeticException算术异常,运行结果如下所示:请输入第1个数:10请输入第2个数:0
415、算术异常!程序结束!当输入的两个数都正确,则会执行到“array3=n1*n2”语句,因数组a的长度为3,其下标取值范围是02,所以使用“array3”会产生数组下标越界异常,运行结果如下所示:请输入第1个数:18请输入第2个数:2下标越界异常!程序结束!9.2.9.2.1 1try catch语句由程序的运行结果可以看出,执行多个catch处理语句时,当异常对象被其中的一个catch语句捕获,则剩下的catch语句将不再进行匹配。多catch处理语句的执行流程如图9.3所示。捕获异常的顺序和catch语句的顺序有关,因此安排catch语句的顺序时,首先应该捕获一些子类异常,然后再捕获父类异常
416、。9.2.9.2.2 2try.catch.finally语 句在某些时候,程序在try块中打开了一些物理资源,例如:数据库连接、网络连接以及磁盘文件读写等,针对这些物理资源、不管是否有异常发生,都必须显式回收;而在try块中一旦发生异常,可能会跳过显式回收代码直接进入catch块,导致没有正常回收资源。这种情况下就要求某些特定的代码必须被执行。Java垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存。在Java程序中,通常使用finally回收物理资源。在Java异常处理机制中,提供了finally块,可以将回收代码放入此块中,不管try块中的代码是否出现异常,
417、也不管哪一个catch块被执行,甚至在try块或catch块中执行了return语句,finally块都会被执行trycatchfinally语句9.2.9.2.2 2try.catch.finally语 句trycatchfinally语句的语法格式如下:try/业务实现代码(可能发生异常).catch(异常类1异常对象)/异常类1的处理代码catch(异常类2异常对象)/异常类2的处理代码./可以有多个catch语句catch(异常类n异常对象)/异常类n的处理代码finally/资源回收语句其中:1、try块是必需的,catch块和finally块是可选的,但catch块和finally
418、块二者至少出现其一,也可以同时出现,即有两种形式的用法:tryfinally和trycatchfinally。2、trycatchfinally是语句的顺序不能颠倒,所有的catch块必须位于try块之后,finally块必须位于所有的catch块之后。9.2.9.2.2 2try.catch.finally语 句下述代码演示trycatchfinally语句的使用,代码如下所示。【代码9.4】FinallyExample.javapackagecom;importjava.io.FileInputStream;importjava.io.IOException;publicclassFina
419、llyExamplepublicstaticvoidmain(Stringargs)FileInputStreamfis=null;try/创建一个文件输入流,读指定的文件fis=newFileInputStream(xsc.txt);catch(IOExceptionioe)System.out.println(ioe.getMessage();/return语句强制方法返回return;/使用exit来退出应用/System.exit(0);/finally9.2.9.2.2 2try.catch.finally语 句/关闭磁盘文件,回收资源if(fis!=null)tryfis.clos
420、e();catch(IOExceptionioe)ioe.printStackTrace();System.out.println(执行finally块里的资源回收!);上述代码在try块中打开一个名为“zkl.txt”的磁盘文件;在catch块中使用异常对象的getMessage()方法获取异常信息,并使用输出语句进行显示。在finally块中关闭磁盘文件,回收资源。注意在程序的catch块中处有一条return语句,该语句强制方法返回。通常程序执行到return语句时会立即结束当前方法,但现在会在返回之前先执行finally块中的代码。9.2.9.2.2 2try.catch.finall
421、y语 句运行运行结果如下所示:结果如下所示:zkl.txt(系统找不到指定的文件。)执行finally块里的资源回收!将处的return语句注释掉,取消处的代码注释,使用System.exit(0)语句退出整个应用程序。因应用程序不再执行,所以finally块中的代码也失去执行的机会,其运行结果如下所示:其运行结果如下所示:zkl.txt(系统找不到指定的文件。)9.2.9.2.2 2try.catch.finally语 句trycatchfinally语句的执行流程如图9-5所示。9.2.9.2.2 2try.catch.finally语 句在上述的代码中,关闭资源的代码放在finally语
422、句中,程序显得有些繁琐。从Java7开始,增强了try语句的功能,允许在try关键字后紧跟一对小括号,在小括号中可以声明、初始化一个或多个资源,当try语句执行结束时会自动关闭这些资源。自动关闭资源的try语句的语法格式如下:try(/声明、初始化资源代码)/业务实现代码(可能发生异常).自动关闭资源的try语句相当于包含了隐式的finally块,该finally块用于关闭前面所访问的资源。因此,自动关闭资源的try语句后面即可以没有catch块,也可以没有finally块。当然,如果程序需要,自动关闭资源的try语句后也可以带多个catch块和一个finally块。下述代码修改了前面代码程序
423、,示例了如何使用自动关闭资源的try语句,代码如下所示。9.2.9.2.2 2try.catch.finally语 句【代码9.5】AutoCloseTryExample.javapackagecom;importjava.io.FileInputStream;importjava.io.IOException;publicclassAutoCloseTryExamplepublicstaticvoidmain(Stringargs)/自动关闭资源的try语句,JDK7.0以上才支持try(FileInputStreamfis=newFileInputStream(xsc.txt)/对文件的操
424、作.catch(IOExceptione)System.out.println(e.getMessage();/包含了隐式的finally块,fis.close()关闭资源上述代码try关键字后紧跟一对小括号,在小括号中声明并初始化一个文件输入流,try语句会自动关闭该资源,这种写法既简洁又安全。程序运行结果如下:zkl.txt(系统找不到指定的文件。)9.2.9.2.3 3嵌 套 的 try.catch语句在某些时候,需要使用嵌套的trycatch语句。例如,代码块的某一部分产生一个异常,而整个代码块又有可能引起另外一个异常,此时需要将一个异常处理嵌套到另一个异常处理中。下述代码示例了嵌套的
425、trycatch语句的使用,代码如下所示。嵌套的trycatch语句9.2.9.2.3 3嵌 套 的 try.catch语句【代码9.6】NestingTryExample.javapackagecom;importjava.io.FileInputStream;importjava.io.IOException;importjava.util.Scanner;publicclassNestingTryExamplepublicstaticvoidmain(Stringargs)tryScannerscanner=newScanner(System.in);System.out.println
426、(请输入第一个数:);/从键盘获取一个字符串Stringstr=scanner.next();/将不是整数数字的字符串转换成整数,/会引发NumberFormatExceptionintn1=Integer.parseInt(str);tryFileInputStreamfis=newFileInputStream(xsc.txt);catch(IOExceptione)System.out.println(e.getMessage();9.2.9.2.3 3嵌 套 的 try.catch语句System.out.println(请输入第二个数:);/从键盘获取一个整数intn2=scanne
427、r.nextInt();System.out.println(您输入的两个数的商是:+n1/n2);catch(Exceptionex)ex.printStackTrace();System.out.println(程序到此结束!);上述代码中,在一个trycatch语句中嵌套了另外一个trycatch语句。根据输入内容的不同,其运行结果也不同。当第一个数输入“xsc”时,运行结果如下所示:运行结果如下所示:请输入第一个数:xscjava.lang.NumberFormatException:Forinputstring:xsc程序到此结束!atjava.lang.NumberFormatEx
428、ception.forInputString(UnknownSource)atjava.lang.Integer.parseInt(UnknownSource)atjava.lang.Integer.parseInt(UnknownSource)atcom.NestingTryExample.main(NestingTryExample.java:14)9.2.9.2.3 3嵌 套 的 try.catch语句当第一个数输入正确,但第二个数输入“0”时,运行结果如下所示:运行结果如下所示:请输入第一个数:10xsc.txt(系统找不到指定的文件。)请输入第二个数:0java.lang.Arith
429、meticException:/byzero程序到此结束!atcom.NestingTryExample.main(NestingTryExample.java:23)当输入两个正确的数,运行结果如下所示:运行结果如下所示:请输入第一个数:10xsc.txt(系统找不到指定的文件。)请输入第二个数:5您输入的两个数的商是:2程序到此结束!使用嵌套的trycatch语句时,如果执行内部的try块没有遇到匹配的catch块,则将检查外部的catch块。9.2.9.2.4 4多 异 常捕获在Java7以前,每个catch块只能捕获一种类型的异常。但从Java7开始,一个catch块可以捕获多种类型的
430、异常。使用一个catch块捕获多种类型的异常时的语法格式如下:try/业务实现代码(可能发生异常).catch(异常类A|异常类B.|异常类N异常对象)/多异常捕获处理代码./可以有多个catch语句多异常捕获其中:(1)捕获多种类型的异常时,多种异常类型之间使用竖杠“|”进行间隔;(2)多异常捕获时,异常变量默认是常量,因此程序不能对该异常变量重新赋值。9.2.9.2.4 4多 异 常捕获下述代码示例了如何使用多异常捕获,代码如下所示。【代码9.7】MultiExceptionExample.javapackagecom;importjava.util.Scanner;publicclass
431、MultiExceptionExamplepublicstaticvoidmain(Stringargs)tryScannerscanner=newScanner(System.in);System.out.println(请输入第一个数:);/从键盘获取一个字符串Stringstr=scanner.next();/将不是整数数字的字符串转换成整数,/会引发NumberFormatExceptionintn1=Integer.parseInt(str);System.out.println(请输入第二个数:);/从键盘获取一个整数intn2=scanner.nextInt();System.o
432、ut.println(您输入的两个数相除的结果是:+n1/n2);catch(ArrayIndexOutOfBoundsException|NumberFormatException|ArithmeticExceptione)System.out.println(程序发生了数组越界、数字格式异常、算术异常之一);9.2.9.2.4 4多 异 常捕获/捕捉多异常时,异常变量ie是默认是常量,不能重新赋值。/下面一条赋值语句错误!/e=newArithmeticException(Test);/catch(Exceptione)System.out.println(未知异常);/捕捉一个类型的异常
433、时,异常变量e可以被重新赋值。/下面一条赋值语句正确!e=newRuntimeException(Test);/上述代码中,第一个catch语句使用多异常捕获,该catch可以捕获处理ArrayIndexOutOfBoundsException、NumberFormatException和ArithmeticException三种类型的异常。多异常捕获时,异常变量默认是常量,因此程序中处的代码是错误的,而的代码是正确的。第第3节节part抛出异常Java中抛出异常可以使用throw或throws关键字:(1)使用throw抛出一个异常对象:当程序出现异常时,系统会自动抛出异常,除此之外,Jav
434、a也允许程序使用代码自行抛出异常,自行抛出异常使用throw语句完成.(2)使用throws声明抛出一个异常序列:throws只能在定义方法时使用。当定义的方法不知道如何处理所出现的异常,而该异常应由上一级调用者进行处理,可在定义该方法时使用throws声明抛出异常。抛出异常本节概述9.3.9.3.1 1throw抛出异常对象在程序中,如果需要根据业务逻辑自行抛出异常,则应该使用throw语句。throw语句抛出的不是异常类,而是一个异常实例对象,并且每次只能抛出一个异常实例对象。throw抛出异常对象的语法格式如下:throw异常对象下述代码示例了throw语句的使用,代码如下所示。thro
435、w抛出异常对象9.3.9.3.1 1throw抛出异常对象【代码9.8】ThrowExample.javapackagecom;importjava.util.Scanner;publicclassThrowExamplepublicstaticvoidmain(Stringargs)Scannerscanner=newScanner(System.in);trySystem.out.println(请输入年龄:);/从键盘获取一个整数intage=scanner.nextInt();if(age100)/抛出一个异常对象thrownewException(请输入一个合法的年龄,年龄必须在01
436、00之间);catch(Exceptionex)ex.printStackTrace();System.out.println(程序到此结束!);9.3.9.3.1 1throw抛出异常对象上述代码中,当用户输入的年龄不在0100之间时,会使用throw抛出一个异常对象。程序运行结果如下:请输入年龄:120程序到此结束!java.lang.Exception:请输入一个合法的年龄,年龄必须在0100之间atcom.ThrowExample.main(ThrowExample.java:12)9.3.9.3.2 2throw声明抛出异常序列throws声明抛出异常序列的语法格式如下:访问符方法名
437、(参数列表)throws异常类A,异常类B.,异常类N/方法体下述代码示例了throws语句的使用,代码如下所示。Throw声明抛出异常序列9.3.9.3.2 2throw声明抛出异常序列【代码9.9】ThrowsExample.javapackagecom;importjava.util.Scanner;publicclassThrowsExample/定义一个方法,该方法使用throws声明抛出异常publicvoidmyThrowsFunction()throwsNumberFormatException,ArithmeticException,ExceptionScannerscann
438、er=newScanner(System.in);System.out.println(请输入第一个数:);Stringstr=scanner.next();/从键盘获取一个字符串/将不是整数数字的字符串转换成整数,/会引发NumberFormatExceptionintn1=Integer.parseInt(str);System.out.println(请输入第二个数:);intn2=scanner.nextInt();/从键盘获取一个整数System.out.println(您输入的两个数相除的结果是:+n1/n2);publicstaticvoidmain(Stringargs)Thr
439、owsExamplete=newThrowsExample();9.3.9.3.2 2throw声明抛出异常序列try/调用带抛出异常序列的方法te.myThrowsFunction();catch(NumberFormatExceptione)e.printStackTrace();catch(ArithmeticExceptione)e.printStackTrace();catch(Exceptione)e.printStackTrace();上述代码中,在定义myThrowsFunction()方法时,该方法后面直接使用throws关键字抛出NumberFormatException、
440、ArithmeticException和Exception三种类型的异常,这三种异常类之间使用逗号“,”间隔。这表明myThrowsFunction()方法会产生异常,但该方法本身没有对异常进行捕获处理(方法体内没有异常处理语句),在调用该方法时就需要对异常进行捕获处理。9.3.9.3.2 2throw声明抛出异常序列程序运行结果如下:请输入第一个数:10请输入第二个数:xscjava.util.InputMismatchExceptionatjava.util.Scanner.throwFor(UnknownSource)atjava.util.Scanner.next(UnknownSou
441、rce)atjava.util.Scanner.nextInt(UnknownSource)atjava.util.Scanner.nextInt(UnknownSource)atcom.ThrowsExample.myThrowsFunction(ThrowsExample.java:14)atcom.ThrowsExample.main(ThrowsExample.java:20)第第4节节part自定义异常Java中定义了一系列的异常类供API或开发者使用,但在编程时可能会出现Java所定义的异常都不能说明当前异常状态的情况,此时就需要开发者编写一个自定义的异常来满足需要,而Java也提
442、供了自定义异常的机制。通常情况下,异常类直接或间接地继承于类Exception,类Exception继承于类Throwable,而类Throwable则直接继承于类Object。当只有一个对象是类Throwable或其子类的实例时,它才可以被Java虚拟机或throw语句抛出。用户自定义异常类都应该继承Exception基类或RuntimeException基类。定义异常类时通常需要提供两个构造方法:一个是无参数的构造方法;另一个是带一个字符串参数的构造方法,这个字符串将作为该异常对象的描述信息(也就是异常对象的getMessage()方法的返回值)。下述代码定义了一个自定义异常类的基本格式,
443、代码如下所示。自 定 义 异常自定义异常【代码9.10】AuctionException.javapackagecom;publicclassAuctionExceptionextendsException/无参数的构造方法publicAuctionException()/带一个字符串参数的构造方法publicAuctionException(Stringmsg)super(msg);自 定 义 异常如果需要自定义Runtime异常,只需将AuctionException.java程序中的Exception基类改为RuntimeException基类,其他地方无须修改。在大部分情况下,创建自定
444、义异常可采用与AuctionException.java相似的代码完成,只需改变AuctionException异常的类名即可,让该异常类的类名可以准确描述该异常。当除数为零的时候,会产生ArithmeticException异常,如果我们期望对于除零异常进行特殊处理,那么我们可以声明一个自定义异常,并实现自定义异常类。代码如下所示。自 定 义 异常【代码9.11】MyExceptionExample.javapackagecom;classDivZeroExcetionextendsExceptionpublicDivZeroExcetion()super(除数不能为零!);publicDi
445、vZeroExcetion(Stringmsg)super(msg);publicclassMyExceptionExamplepublicstaticdoublediv(doublex,doubley)throwsDivZeroExcetionif(y=0)thrownewDivZeroExcetion();returnx/y;自 定 义 异常publicstaticvoidmain(Stringargs)trySystem.out.println(div(3,0);catch(DivZeroExcetione)System.out.println(e.getMessage();程序运行结果
446、如下:除数不能为零!本章课后作业见教材JAVA程 序 设计本章学习目标:理解泛型的概念掌握泛型类的创建和使用理解泛型的有界类型和通配符的使用,了解泛型的限制理解Java集合框架的结构、迭代器接口掌握常用接口及实现类的使用了解集合转换第十章第十章泛型与集合泛型与集合第第1节节part泛型从JDK5.0开始,Java引入“参数化类型(parameterizedtype)”的概念,这种参数化类型称为“泛型(Generic)”。泛型是将数据类型参数化,即在编写代码时将数据类型定义成参数,这些类型参数在使用之前再进行指明。泛型提高了代码的重用性,使得程序更加灵活、安全和简洁。泛型本节概述在JDK5.0之
447、前,为了实现参数类型的任意化,都是通过Object类型来处理。但这种处理方式所带来的缺点是需要进行强制类型转换,此种强制类型转换不仅使代码臃肿,而且要求程序员必须对实际所使用的参数类型已知的情况下才能进行,否则容易引起ClassCastException异常。从JDK5.0开始,Java增加对泛型的支持。使用泛型之后就不会出现上述问题。泛型的好处是在程序编译期会对类型进行检查,捕捉类型不匹配错误,以免引起ClassCastException异常;而且泛型不需要进行强制转换,数据类型都是自动转换的。泛型经常使用在类、接口和方法的定义中,分别称为泛型类、泛型接口和泛型方法。泛型类是引用类型,在内存
448、堆中。10.1.10.1.1 1泛型定义泛型定义定义泛型类的语法格式如下:访问符class类名/类体.其中:(1)尖括号中是类型参数列表,可以由多个类型参数组成,多个类型参数之间使用“,”隔开。(2)类型参数只是占位符,一般使用大写的“T”、“U”、“V”等作为类型参数。下述代码示例了泛型类的定义,代码如下所示。classNodeprivateTdata;publicNodenext;/省略.10.1.10.1.1 1泛型定义泛型定义从Java7开始,实例化泛型类时只需给出一对尖括号“”即可,Java可以推断尖括号中的泛型信息。将两个尖括号放在一起像一个菱形,因此也被称为“菱形”语法。Java
449、7“菱形”语法实例化泛型类的格式如下:类名对象=new类名(构造方法参数列表);例如:NodemyNode=newNode();下述代码示例了一个泛型类的定义,代码如下所示。10.1.10.1.1 1泛型定义【代码10.1】Generic.javapackagecom;publicclassGenericprivateTdata;publicGeneric()publicGeneric(Tdata)this.data=data;publicTgetData()returndata;publicvoidsetData(Tdata)this.data=data;publicvoidshowData
450、Type()System.out.println(数据的类型是:+data.getClass().getName();10.1.10.1.1 1泛型定义上述代码定义了一个名为Generic的泛型类,并提供两个构造方法。私有属性data的数据类型采用泛型,可以在使用时在进行指定。showDataType()方法显示data属性的具体类型名称,其中“getClass().getName()”用于获取对象的类名。下述代码示例了泛型类的实例化,并访问相应方法,代码如下所示。【代码10.2】GenericExample.javapackagecom;publicclassGenericExamplepu
451、blicstaticvoidmain(Stringargs)Genericstr=newGeneric(字符串类型泛型类!);str.showDataType();System.out.println(str.getData();System.out.println(-);/定义泛型类的一个Double版本Genericdou=newGeneric(3.1415);dou.showDataType();System.out.println(dou.getData();10.1.10.1.1 1泛型定义上述代码使用Generic泛型类,并分别实例化为String和Double两种不同类型的对象。
452、程序运行结果如下:数据的类型是:java.lang.String欢迎使用泛型类!-数据的类型是:java.lang.Double3.141510.1.10.1.2 2通 配符通配符当使用一个泛型类时(包括声明泛型变量和创建泛型实例对象),都应该为此泛型类传入一个实参,否则编译器会提出泛型警告。假设现在定义一个方法,该方法的参数需要使用泛型,但类型参数是不确定的,此时如果考虑使用Object类型来解决,编译时则会出现错误。以之前定义的泛型类Generic为例,考虑如下代码:【代码10.3】NoWildcardExample.javapackagecom;publicclassNoWildcard
453、ExamplepublicstaticvoidmyMethod(Genericg)g.showDataType();publicstaticvoidmain(Stringargs)/参数类型是ObjectGenericgstr=newGeneric(Object);myMethod(gstr);/参数类型是IntegerGenericgint=newGeneric(12);/这里将产生一个错误myMethod(gint);/参数类型是IntegerGenericgdou=newGeneric(12.0);/这里将产生一个错误myMethod(gdou);10.1.10.1.2 2通 配符上述代
454、码中定义的myMethod()方法的参数是泛型类Generic,该方法的意图是能够处理各种类型参数,但在使用Generic类时必须指定具体的类型参数,此处在不使用通配符的情况下,只能使用“Generic”的方式。这种方式将造成main()方法中的语句编译时产生类型不匹配的错误,程序无法运行。程序中出现的这个问题,可以使用通配符解决。通配符是由“?”来表示一个未知类型,从而解决类型被限制、不能动态根据实例进行确定的缺点。下述代码使用通配符“?”重新实现上述处理过程,实现处理各种类型参数的情况,代码如下所示。10.1.10.1.2 2通 配符【代码10.4】UseWildcardExample.j
455、avapackagecom;publicclassUseWildcardExamplepublicstaticvoidmyMethod(Genericg)g.showDataType();publicstaticvoidmain(Stringargs)/参数类型是StringGenericgstr=newGeneric(Object);myMethod(gstr);/参数类型是IntegerGenericgint=newGeneric(12);myMethod(gint);/参数类型是IntegerGenericgdou=newGeneric(12.0);myMethod(gdou); 10.
456、1.10.1.2 2通 配符上述代码定义了myMethod()方法时,使用“Generic”通配符的方式作为类型参数,如此便能够处理各种类型参数,且程序编译无误,能够正常运行。程序运行结果如下:数据的类型是:java.lang.String数据的类型是:java.lang.Integer数据的类型是:java.lang.Double有界类型有界类型泛型的类型参数可以是各种类型,但有时候需要对类型参数的取值进行一定程度的限制,以便类型参数在指定范围内。针对这种情况,Java提供了“有界类型”,来限制类型参数的取值范围。有界类型分两种:(1)使用extends关键字声明类型参数的上界。(2)使用s
457、uper关键字声明类型参数的下界。10.1.10.1.3 3有界类型有界类型1.上界上界使用extends关键字可以指定类型参数的上界,限制此类型参数必须继承自指定的父类或父类本身。被指定的父类则称为类型参数的“上界(upperbound)”。类型参数的上界可以在定义泛型时进行指定,也可以在使用泛型时进行指定,其语法格式分别如下:/定义泛型时指定类型参数的上界访问符class类名/类体./使用泛型时指定类型参数的上界泛型类例如:/定义泛型时指定类型参数的上界publicclassGeneric/类体./使用泛型时指定类型参数的上界Generic上述代码限制了泛型类Generic的类型参数必须是
458、Number类及其子类,因此可以将Number类称为此类型参数的上界。Java中Number类是一个抽象类,所有数值类都继承此抽象类,即Integer、Long、Float、Double等用于数值操作的类都继承Number类。10.1.10.1.3 3有界类型下述代码示例了使用类型参数的上界,代码如下所示。【代码10.5】UpBoundGenericExample.javapackagecom;classUpBoundGenericprivateTdata;publicUpBoundGeneric()publicUpBoundGeneric(Tdata)this.data=data;publi
459、cTgetData()returndata;publicvoidsetData(Tdata)this.data=data;publicvoidshowDataType()System.out.println(数据的类型是:+data.getClass().getName();10.1.10.1.3 3有界类型publicclassUpBoundGenericExample/使用泛型Generic时指定其类型参数的上界publicstaticvoidmyMethod(Genericg)g.showDataType();publicstaticvoidmain(Stringargs)/参数类型是I
460、ntegerGenericgint=newGeneric(1);myMethod(gint);/参数类型是LongGenericglong=newGeneric(10L);myMethod(glong);/参数类型是StringGenericgstr=newGeneric(String);/产生错误/myMethod(gstr);10.1.10.1.3 3有界类型/使用已经限定参数的泛型UpBoundGenericUpBoundGenericubgint=newUpBoundGeneric(20);ubgint.showDataType();UpBoundGenericubglon=newUp
461、BoundGeneric(20L);ubglon.showDataType();/产生错误/UpBoundGenericubgstr=newUpBoundGeneric(指定上界);10.1.10.1.3 3有界类型上述代码中定义了一个泛型类UpBoundGeneric,并指定其类型参数的上界是Number类。在定义myMethod()方法时指定泛型类Generic的类型参数的上界也是Number类。在main()方法中进行使用时,当类型参数不是Number的子类时都会产生错误。因UpBoundGeneric类在定义时就已经限定了类型参数的上界,所以出现“UpBoundGeneric”就会报错
462、。Generic类在定义时并没有上界限定,而是在定义myMethod()方法使用Generic类才进行限定的,因此出现“Generic”不会报错,调用“myMethod(gstr)”时才会报错。程序运行结果如下所示:数据的类型是:java.lang.Integer数据的类型是:java.lang.Long数据的类型是:java.lang.Integer数据的类型是:java.lang.Long2.下界下界使用super关键字可以指定类型参数的下界,限制此类型参数必须是指定的类型本身或其父类,直至Object类。被指定的类则称为类型参数的“下界(lowerbound)”。类型参数的下界通常在使用
463、泛型时进行指定,其语法格式如下所示:泛型类例如:Generic上述代码限制了泛型类Generic的类型参数必须是String类本身或其父类Object,因此可以将String类称为此类型参数的下界。下述代码示例泛型类型参数下界的声明和使用,代码如下所示。10.1.10.1.3 3有界类型【代码10.6】LowBoundGenericExample.javapackagecom;publicclassLowBoundGenericExample/使用泛型Generic时指定其类型参数的下界publicstaticvoidmyMethod(Genericg)g.showDataType();pub
464、licstaticvoidmain(Stringargs)/参数类型是StringGenericgstr=newGeneric(String类本身);myMethod(gstr);/参数类型是ObjectGenericgobj=newGeneric(10);myMethod(gobj);/参数类型是IntegerGenericgint=newGeneric(10);/产生错误/myMethod(gint);10.1.10.1.3 3有界类型上述代码在定义myMethod()方法时指定泛型类Generic的类型参数的上界是String类,因此在main()方法中进行使用时,当参数类型不是Stri
465、ng类或其父类Object时,都会产生错误。程序运行结果如下:数据的类型是:java.lang.String数据的类型是:java.lang.Integer泛型中使用extends关键字限制类型参数必须是指定的类本身或其子类,而super关键字限制类型参数必须是指定的类本身或其父类。在泛型中经常使用extends关键字指定上界,而很少使用super关键字指定下界。10.1.10.1.3 3有界类型Java语言没有真正实现泛型。Java程序在编译时生成的字节码中是不包含泛型信息的,泛型的类型信息将在编译处理时被擦除掉,这个过程称为类型擦除。这种实现理念造成Java泛型本身有很多漏洞,虽然Java
466、8对类型推断进行了改进,但依然需要对泛型的使用上做一些限制,其中大多数限制都是由类型擦除和转换引起的。Java对泛型的限制如下:(1)泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。(2)同一个泛型类可以有多个版本(不同参数类型),不同版本的泛型类的实例是不兼容的,例如:“Generic”与“Generic”的实例是不兼容的。(3)定义泛型时,类型参数只是占位符,不能直接实例化,例如:“newT()”是错误的。(4)不能实例化泛型数组,除非是无上界的类型通配符,例如:“Generica=newGeneric10”是错误的,而“Generica=newGeneric10”是被允许的。
467、(5)泛型类不能继承Throwable及其子类,即泛型类不能是异常类,不能抛出也不能捕获泛型类的异常对象,例如:“classGenericExceptionextendsException”、“catch(Te)”都是错误的。10.1.10.1.4 4泛 型 的限制泛型的限制第第2节节part集合概述Java的集合类是一些常用的数据结构,例如:队列、栈、链表等。Java集合就像一种“容器”,用于存储数量不等的对象,并按照规范实现一些常用的操作和算法。程序员在使用Java的集合类时,不必考虑数据结构和算法的具体实现细节,根据需要直接使用这些集合类并调用相应的方法即可,从而提高了开发效率。集合概述
468、本节概述10.2.10.2.1 1集合框架在JDK5.0之前,Java集合会丢失容器中所有对象的数据类型,将所有对象都当成Object类型进行处理。从JDK5.0增加泛型之后,Java集合完全支持泛型,可以记住容器中对象的数据类型,从而可以编写更简洁、健壮的代码。Java所有的集合类都在java.util包下,从JDK5.0开始为了处理多线程环境下的并发安全问题,又在java.util.concurrent包下提供了一些多线程支持的集合类。Java的集合类主要由两个接口派生而出:Collection和Map,这两个接口派生出一些子接口或实现类。Collection和Map是集合框架的根接口,如
469、图10.1所示是Collection集合体系的继承树。集合框架10.2.10.2.1 1集合框架Collection接口下有3个子接口:(1)Set接口:无序、不可重复的集合;(2)Queue接口:队列集合;(3)List接口:有序、可以重复的集合。10.2.10.2.1 1集合框架如图10.2所示是Map集合体系的继承树。所有Map的实现类用于保存具有映射关系的数据,即Map保存的每项数据都是由key-value键值对组成。Map中的key用于标识集合中的每项数据,是不可重复的,可以通过key来获取Map集合中的数据项。10.2.10.2.1 1集合框架Java中的集合分为三大类:(1)Se
470、t集合:将一个对象添加到Set集合时,Set集合无法记住添加的顺序,因此Set集合中的元素不能重复,否则系统无法识别该元素,访问Set集合中的元素也只能根据元素本身进行访问;(2)List集合:与数组类似,List集合可以记住每次添加元素的顺序,因此可以根据元素的索引访问List集合中的元素,List集合中的元素可以重复且长度是可变的;(3)Map集合:每个元素都是有key/value键值对组成,可以根据每个元素的key来访问对应的value,Map集合中的key不允许重复,value可以重复。本章主要介绍常用的集合接口及其实现类,例如List、Set、Map和Queue集合接口,以及对应的实
471、现类ArrayList、HashSet、HashMap和LinkedList。10.2.10.2.2 2迭 代 器接口迭代器(Iterator)可以采用统一的方式对Collection集合中的元素进行遍历操作,开发人员无需关心Collection集合中的内容,也不必实现IEnumerable或者IEnumerator接口就能够使用foreach循环遍历集合中的部分或全部元素。Java从JDK5.0开始增加了Iterable新接口,该接口是Collection接口的父接口,因此所有实现了Iterable的集合类都是可迭代的,都支持foreach循环遍历。Iterable接口中的iterator(
472、)方法可以获取每个集合自身的迭代器Iterator。Iterator是集合的迭代器接口,定义了常见的迭代方法,用于访问、操作集合中的元素。Iterator接口中的方法如表10-1所示:迭代器接口10.2.10.2.2 2迭 代 器接口下述代码示范了Iterator接口中的方法的应用,代码如下所示。【代码10.7】IteratorExample.javapackagecom;importjava.util.Collection;importjava.util.HashSet;importjava.util.Iterator;publicclassIteratorExamplepublicstat
473、icvoidmain(Stringargs)/创建一个集合Collectioncity=newHashSet();city.add(重庆);city.add(成都);city.add(北京);city.add(上海);/直接显示集合元素:System.out.print(开始的城市有:+city+);System.out.println();10.2.10.2.2 2迭 代 器接口/获取city集合对应的迭代器Iteratorit=city.iterator();while(it.hasNext()/it.next()方法返回的数据类型是Object类型,需要强制转换Stringc=(Stri
474、ng)it.next();System.out.print(c+);if(c.equals(成都)it.remove();System.out.println();System.out.print(最后的城市有:+city+);程序运行结果如下:开始的城市有:上海,北京,重庆,成都上海北京重庆成都最后的城市有:上海,北京,重庆第第3节节part集合类10.3.10.3.1 1Collection接口Collection接口是Set、Queue和List接口的父接口,该接口中定义的方法可以操作这三个接口中的任一个集合,Collection接口中常用的方法如表10-2所示。Collection接口
475、10.3.10.3.1 1使用Collection需要注意以下几点问题:(1)add()、addAll()、remove()、removeAll()和retainAll()方法可能会引发不支持该操作的UnsupportedOperationException异常。(2)将一个不兼容的对象添加到集合中时,将产生ClassCastException异常。(3)Collection接口没有提供获取得某个元素的方法,但可以通过iterator()方法获取迭代器来遍历集合中的所有元素.(4)虽然Collection中可以存储任何Object对象,但不建议在同一个集合容器中存储不同类型的对象,建议使用泛型
476、增强集合的安全性,以免引起ClassCastException异常。下述代码示例了如何操作Collection集合里的元素,代码如下所示。Collection接口10.3.10.3.1 1【代码10.8】CollectionExample.javapackagecom;importjava.util.ArrayList;importjava.util.Collection;publicclassCollectionExamplepublicstaticvoidmain(Stringargs)/创建一个Collection对象的集合,该集合用ArrayList类实例化Collectionc=ne
477、wArrayList();/添加元素c.add(Java程序设计);c.add(12);c.add(Android程序设计);System.out.println(c集合元素的个数为:+c.size();/删除指定元素c.remove(12);System.out.println(删除后,c集合元素的个数为:+c.size();/判断集合是否包含指定元素System.out.println(“集合中是否包含”Java程序设计“字符串:”+c.contains(“Java程序设计”);Collection接口10.3.10.3.1 1/查看c集合的所有元素System.out.println(c
478、集合的元素有:+c);/清空集合中的元素c.clear();/判断集合是否为空System.out.println(c集合是否为空:+c.isEmpty();程序运行结果如下:c集合元素的个数为:3删除后,c集合元素的个数为:2集合中是否包含Java程序设计字符串:truec集合的元素有:Java程序设计,Android程序设计c集合是否为空:trueCollection接口10.3.10.3.2 2List接口及其实现类List是Collection接口的子接口,可以使用Collection接口中的全部方法。因为List是有序、可重复的集合,所以List接口中又增加一些根据索引操作集合元素的
479、方法,常用的方法如表10-3所示。List接口及其实现类10.3.10.3.2 2List接口及其实现类List集合默认按照元素添加顺序设置元素的索引,索引从0开始,例如:第一次添加的元素索引为0,第二次添加的元素索引为1,第n次添加的元素索引为n-1。当使用无效的索引时将产生IndexOutOfBoundsException异常。ArrayList和Vector是List接口的两个典型实现类,完全支持List接口的所有功能方法。ArrayList称为“数组列表”,而Vector称为“向量”,两者都是基于数组实现的列表集合,但该数组是一个动态的、长度可变的、并允许再分配的Object数组。Ar
480、rayList和Vector在用法上几乎完全相同,但由于Vector从JDK1.0开始就有了,所以Vector中提供了一些方法名很长的方法,例如:addElement()方法,该方法跟add()方法没有任何区别。10.3.10.3.2 2List接口及其实现类ArrayList和Vector虽然在用法上相似,但两者在本质上还是存在区别的:(1)ArrayList是非线程安全的,当多个线程访问同一个ArrayList集合时,如果多个线程同时修改ArrayList集合中的元素,则程序必须手动保证该集合的同步性。(2)Vector是线程安全的,程序无需手动保证该集合的同步性。正因为Vector是线程
481、安全的,所以Vector的性能要比ArrayList低。在实际应用中,即使要保证线程安全,也不推荐使用Vector,因为可以使用Collections工具类将一个ArrayList变成线程安全的。下述代码示例了ArrayList类的使用,代码如下所示。10.3.10.3.2 2List接 口 及 其 实现类【代码10.9】ArrayListExample.javapackagecom;importjava.util.ArrayList;importjava.util.Iterator;publicclassArrayListExamplepublicstaticvoidmain(Stringa
482、rgs)/使用泛型ArrayList集合ArrayListarray=newArrayList();/添加元素array.add(北京);array.add(上海);array.add(广州);array.add(重庆);array.add(深圳);System.out.print(使用foreach遍历集合:);for(Strings:array)System.out.print(s+);System.out.println();System.out.print(使用Iterator迭代器遍历集合:);10.3.10.3.2 2List接口及其实现类/获取迭代器对象Iteratorit=ar
483、ray.iterator();while(it.hasNext()System.out.print(it.next()+);/删除指定索引和指定名称的元素array.remove(0);array.remove(广州);System.out.println();System.out.print(删除后的元素有:);for(Strings:array)System.out.print(s+);程序运行结果如下:使用foreach遍历集合:北京上海广州重庆深圳使用Iterator迭代器遍历集合:北京上海广州重庆深圳删除后的元素有:上海重庆深圳10.3.10.3.3 3Set接口及其实现类Set集合
484、类似一个罐子,可以将多个元素丢进罐子里,但不能记住元素的添加顺序,因此不允许包含相同的元素。Set接口继承Collection接口,没有提供任何额外的方法,其用法与Collection一样,只是特性不同(Set中的元素不重复)。Set接口常用的实现类包括HashSet、TreeSet和EnumSet,这三个实现类各具特色:(1)HashSet是Set接口的典型实现类,大多数使用Set集合时都使用该实现类。HashSet使用Hash算法来存储集合中的元素,具有良好的存、取以及查找性。(2)TreeSet采用Tree“树”的数据结构来存储集合元素,因此可以保证集合中的元素处于排序状态。TreeSe
485、t支持两种排序方式:自然排序和定制排序,默认情况下采用自然排序。(3)EnumSet是一个专为枚举类设计的集合类,其所有元素必须是指定的枚举类型。EnumSet集合中的元素也是有序的,按照枚举值顺序进行排序Set接口及其实现类10.3.10.3.3 3Set接口及其实现类HashSet及其子类都是采用Hash算法来决定集合中元素的存储位置,并通过Hash算法来控制集合的大小。Hash表中可以存储元素的位置称为“桶(bucket)”,通常情况下,单个桶只存储一个元素,此时性能最佳,Hash算法可以根据HashCode值计算出桶的位置,并从桶中取出元素。但当发生Hash冲突时,单个桶会存储多个元素
486、,这些元素以链表的形式存储。下述代码示例了HashSet实现类的具体应用,代码如下所示。10.3.10.3.3 3Set接口及其实现类【代码10.10】HashSetExample.javapackagecom;importjava.util.HashSet;importjava.util.Iterator;publicclassHashSetExamplepublicstaticvoidmain(Stringargs)/使用泛型HashSetHashSeths=newHashSet();/向集合中添加元素hs.add(12);hs.add(3);hs.add(24);hs.add(24);h
487、s.add(5);/直接输出HashSet集合对象System.out.println(hs);/使用foreach循环遍历for(inta:hs)System.out.print(a+);System.out.println();hs.remove(3);/删除指定元素System.out.print(删除后剩下的数据:);/获取HashSet的迭代器Iteratoriterator=hs.iterator();/使用迭代器遍历while(iterator.hasNext()System.out.print(iterator.next()+);程序运行结果如下:3,5,24,12352412
488、删除后剩下的数据:52412通过运行结果可以发现,HashSet集合中的元素是无序的,且没有重复元素。10.3.10.3.3 3Set接口及其实现类下述代码示例了TreeSet实现类的使用,代码如下所示。【代码10.11】TreeSetExample.javapackagecom;importjava.util.Iterator;importjava.util.TreeSet;publicclassTreeSetExamplepublicstaticvoidmain(Stringargs)TreeSeths=newTreeSet();hs.add(上海);hs.add(重庆);hs.add(广
489、州);hs.add(成都);hs.add(重庆);System.out.println(hs);for(Stringstr:hs)System.out.print(str+);hs.remove(重庆);System.out.println();System.out.print(删除后剩下的数据:);Iteratoriterator=hs.iterator();while(iterator.hasNext()System.out.print(iterator.next()+);程序运行结果如下:上海,广州,成都,重庆上海广州成都重庆删除后剩下的数据:上海广州成都通过运行结果可以看出,TreeS
490、et集合中的元素安照字符串的内容进行排序,输出的元素都是有序的,但也不能包含重复元素。10.3.10.3.4 4Queue接口及其实现类Queue用于模拟队列这种数据结构,通常以“先进先出(FIFO)”的方式排序各个元素,即最先入队的元素最先出队。Queue接口继承Collection接口,除了Collection接口中的基本操作外,还提供了队列的插入、提取和检查操作,且每个操作都存在两种形式:一种操作失败时抛出异常;另一种操作失败时返回一个特殊值(null或false)。Queue接口中的常用方法如表10-4所示。Queue接口及其实现类10.3.10.3.4 4Queue接口及其实现类Qu
491、eue接口有一个PriorityQueue实现类。PriorityQueue类是基于优先级的无界队列,通常称为“优先级队列”。优先级队列的元素按照其自然顺序或定制排序,优先级队列不允许使用null元素,依靠自然顺序的优先级队列不允许插入不可比较的对象。下述代码示例了PriorityQueue实现类的使用,代码如下所示。Queue接口及其实现类10.3.10.3.4 4Queue接口及其实现类【代码10.12】PriorityQueueExample.javapackagecom;importjava.util.Iterator;importjava.util.PriorityQueue;pub
492、licclassPriorityQueueExamplepublicstaticvoidmain(Stringargs)PriorityQueuepq=newPriorityQueue();pq.offer(6);pq.offer(-3);pq.offer(20);pq.offer(18);System.out.println(pq);/访问队列的第一个元素System.out.println(“poll:”+pq.poll();System.out.print(“foreach遍历:”);for(Integere:pq)System.out.print(e+“”);System.out.pr
493、intln();System.out.print(“迭代器遍历:”);Iteratoriterator=pq.iterator();while(iterator.hasNext()System.out.print(iterator.next()+“”);程序运行结果如下:-3,6,20,18poll:-3foreach遍历:61820迭代器遍历:6182010.3.10.3.4 4Queue接口及其实现类除此之外,Queue还有一个Deque接口,Deque代表一个“双端队列”,双端队列可以同时从两端来添加、删除元素。Deque接口中定义在双端队列两端插入、移除和检查元素的方法,其常用方法如表
494、10-5所示。10.3.10.3.4 4Queue接口及其实现类Java为Deque提供了ArrayDeque和LinkedList两个实现类。ArrayDeque称为“数组双端队列”,是Deque接口的实现类,其特点如下:(1)ArrayDeque没有容量限制,可以根据需要增加容量;(2)ArrayDeque不是基于线程安全的,在没有外部代码同步时,不支持多个线程的并发访问;(3)ArrayDeque禁止添加null元素;(4)ArrayDeque在用作堆栈时快于Stack,在用作队列时快于LinkedList。下述代码示例了ArrayDeque实现类的使用,代码如下所示。10.3.10.3
495、.4 4Queue接口及其实现类【代码10.13】ArrayDequeExample.javapackagecom;importjava.util.*;publicclassArrayDequeExamplepublicstaticvoidmain(Stringargs)/使用泛型ArrayDeque集合ArrayDequequeue=newArrayDeque();/在队尾添加元素queue.offer(上海);/在队头添加元素queue.push(北京);/在队头添加元素queue.offerFirst(南京);/在队尾添加元素queue.offerLast(重庆);System.out.
496、print(直接输出ArrayDeque集合对象:+queue);System.out.println();System.out.println(peek访问队列头部的元素:+queue.peek();System.out.println(peek访问后的队列元素:+queue);/System.out.println(-);10.3.10.3.4 4Queue接口及其实现类/poll出第一个元素System.out.println(poll出第一个元素为:+queue.poll();System.out.println(poll访问后的队列元素:+queue);System.out.prin
497、tln(foreach遍历:);/使用foreach循环遍历for(Stringstr:queue)System.out.print(str+);System.out.println();System.out.println(迭代器遍历:);/获取ArrayDeque的迭代器Iteratoriterator=queue.iterator();/使用迭代器遍历while(iterator.hasNext()System.out.print(iterator.next()+);10.3.10.3.4 4Queue接口及其实现类程序运行结果如下:直接输出ArrayDeque集合对象:南京,北京,上海
498、,重庆peek访问队列头部的元素:南京peek访问后的队列元素:南京,北京,上海,重庆poll出第一个元素为:南京poll访问后的队列元素:北京,上海,重庆foreach遍历:北京上海重庆迭代器遍历:北京上海重庆10.3.10.3.4 4Map接口及其实现类Map接口是集合框架的另一个根接口,与Collection接口并列。Map是以key/value键值对映射关系存储的集合。Map接口中的常用方法如表10-6所示。Map接口及其实现类10.3.10.3.4 4Map接口及其实现类HashMap和TreeMap是Map体系中两个常用实现类,其特点如下:(1)HashMap是基于哈希算法的Map
499、接口的实现类,该实现类提供所有映射操作,并允许使用null键和null值,但不能保证映射的顺序,即是无序的映射集合。(2)TreeMap是基于“树”结构来存储的Map接口实现类,可以根据其键的自然顺序进行排序,或定制排序方式。下述代码演示了HashMap类的使用,代码如下所示。10.3.10.3.4 4Map接口及其实现类【代码10.14】HashMapExample.javapackagecom;importjava.util.HashMap;publicclassHashMapExamplepublicstaticvoidmain(Stringargs)/使用泛型HashMap集合Hash
500、Maphm=newHashMap();/添加数据,key-value键值对形式hm.put(1,“北京”);hm.put(2,“上海”);hm.put(3,“武汉”);hm.put(4,“重庆”);hm.put(5,“成都”);hm.put(null,null);/根据key获取valueSystem.out.println(hm.get(1);System.out.println(hm.get(3);System.out.println(hm.get(5);System.out.println(hm.get(null);/根据key删除hm.remove(1);/key为1的元素已经删除,返
501、回nullSystem.out.println(hm.get(1);上述代码允许向HashMap中添加null键和null值,当使用get()方法获取元素时,没用指定的键时会返回null。程序运行结果如下:北京武汉成都nullnull10.3.10.3.4 4Map接口及其实现类下述代码演示了TreeMap类的使用,代码如下所示。【代码10.15】TreeMapExample.javapackagecom;importjava.util.TreeMap;publicclassTreeMapExamplepublicstaticvoidmain(Stringargs)/使用泛型TreeMap集合
502、TreeMaptm=newTreeMap();/添加数据,key-value键值对形式tm.put(1,北京);tm.put(2,上海);tm.put(3,武汉);tm.put(4,重庆);tm.put(5,成都);/tm.put(null,null);不允许null键和null值/根据key获取valueSystem.out.println(tm.get(1);System.out.println(tm.get(3);System.out.println(tm.get(5);/System.out.println(tm.get(null);不允许null键/根据key删除tm.remove(
503、1);/key为1的元素已经删除,返回nullSystem.out.println(tm.get(1);上述代码在使用TreeMap时,不允许使用null键和null值,当使用get()方法获取元素时,没用指定的键时会返回null。程序运行结果如下:北京武汉成都null第第4节节part集合转换Java集合框架有两大体系:Collection和Map,两者虽然从本质上是不同的,各自具有自身的特性,但可以将Map集合转换为Collection集合。将Map集合转换为Collection集合有三个方法:(1)entrySet():返回一个包含了Map中元素的集合,每个元素都包括键和值;(2)key
504、Set():返回Map中所有键的集合;(3)values():返回Map中所有值的集合。下述代码演示了如何将Map集合转换为Collection集合,代码如下所示。集合转换集合转换【代码10.16】MapChangeCollectionExample.javapackagecom;importjava.util.Collection;importjava.util.HashMap;importjava.util.Map.Entry;importjava.util.Set;publicclassMapChangeCollectionExamplepublicstaticvoidmain(Stri
505、ngargs)/使用泛型HashMap集合HashMaphm=newHashMap();/添加数据,key-value键值对形式hm.put(1,北京);hm.put(2,上海);hm.put(3,武汉);hm.put(4,重庆);hm.put(5,成都);hm.put(null,null);/使用entrySet()方法获取Entry键值对集合SetEntryset=hm.entrySet();集合转换System.out.println(所有Entry:);for(Entryentry:set)System.out.println(entry.getKey()+:+entry.getVal
506、ue();System.out.println(-);/使用keySet()方法获取所有键的集合SetkeySet=hm.keySet();System.out.println(所有key:);for(Integerkey:keySet)System.out.println(key);System.out.println(-);/使用values()方法获取所有值的集合CollectionvalueSet=hm.values();System.out.println(所有value:);for(Stringvalue:valueSet)System.out.println(value);集合转
507、换上述代码分别使用entrySet()方法获取Entry键值对集合,使用keySet()方法获取所有键的集合,使用values()方法获取所有值的集合,并使用foreach循环对集合遍历输出。程序运行结果如下:集合转换所有Entry:null:null1:北京2:上海3:武汉4:重庆5:成都-所有key:null12345-所有value:null北京上海武汉重庆成都本章课后作业见教材JAVA程 序 设计本章学习目标:掌握File类的使用掌握IO流的分类和体系结构掌握字符流和字节流的使用了解对象流和过滤流的使用了解NIO的特点并掌握Buffer和Channel的使用第十一章第十一章输入输入/输
508、出流输出流第第1节节partI/O流概述流是一组有序的数据序列,是实现数据输入(Input)和输出(Output)的基础,可以对数据实现读/写操作。流(Stream)的优势在于使用统一的方式对数据进行操作或传递,简化了代码操作。按照不同的分类方式,可以将流分为不同的类型。按照流的流向来分,可以将流分为输入流和输出流。输入流是只能读取数据,而不能写入数据的流;输出流是只能写入数据,而不能读取数据的流,如图11.1所示。I /O流概述I/O流概述按照流所操作的基本数据单元来分,可以将流分为字节流和字符流。字节流所操作的基本数据单元是8位的字节(byte),无论是输入还是输出,都是直接对字节进行处理
509、;字符流所操作的基本数据单元是16位的字符(Unicode),无论是输入还是输出,都是直接对字符进行处理。按照流的角色来分,可以将流分为节点流和处理流。节点流用于从或向一个特定的IO设备(如磁盘、网络等)读/写数据的流,也称为低级流,通常是指文件、内存或管道;处理流用于对一个已经存在的流进行连接或封装,通过封装后的流来实现数据的读/写功能,也称为高级流或包装流。I /O流概述Java的IO流都是由4个抽象基类派生的,如图11.2所示。I /O流概述其中:InputStream/Reader是所有输入流的基类,用于实现数据的读操作,前者是字节输入流,后者是字符输入流,只是处理的数据基本单位不同;
510、OutputStream/Writer是所有输出流的基类,用于实现数据的写操作,前者是字节输出流,后者是字符输出流,只是处理的数据基本单位不同。Java的IO流体系比较复杂,共涉及40多个类,这些类彼此之间相互联系,与有一定的规律。Java按照功能将IO流分成了许多类,而每个类中又分别提供了字节输入流、字节输出流、字符输入流和字符输出流。当然有些流没有提供字节流,有些流没有提供字符流。Java的IO流体系按照功能分类,常用的流如表11-1所示。其中,访问文件、数组、管道和字符串的流都是节点流,必须直接与指定的物理节点关联。I /O流概述由于计算机中所有的数据都是以二进制的形式进行组织,而字节流
511、可以处理所有二进制文件,所以通常字节流的功能比字符流的功能更强大。但是,如果使用字节流处理文本文件时,则需要使用合适的方式将字节转换成字符,从而增加了编程的复杂度。因此,在使用IO流时注意一个规则:如果进行输入输出的内容是文本内容,则使用字符流;如果输入输出的内容是二进制内容,则使用字节流。I /O流概述第第2节节partFile类F i l e类File类是java.io包下代表与平台无关的文件和目录,也就是说,如果希望在程序中操作文件和目录,都可以通过File类来完成。File类中的方法可以实现文件和目录的创建、删除和重命名等,但File类不能访问文件内容本身。如果需要访问文件内容本身,则
512、需要使用输入/输出流。File类可以使用文件路径字符串来创建File实例,该文件路径字符串既可以是绝对路径,也可以是相对路径。绝对路径是从根目录开始,对文件进行完整描述,例如“D:dataxsc.txt”;相对路径是以当前目录为参照,对文件进行描述,例如“dataxsc.txt”。File类F i l e类File类常用方法及功能如表11-2所示。F i l e类下述案例示例了文件的创建以及对文件进行操作和管理的应用,代码如下所示。【代码11.1】FileExample.javapackagecom;importjava.io.File;importjava.io.IOException;pu
513、blicclassFileExamplepublicstaticvoidmain(Stringargs)FilenewFile=newFile(D:data);System.out.println(创建目录:+newFile.mkdir();/以当前路径来创建一个File对象,Filefile=newFile(D:data,xsc.txt);tryfile.createNewFile();/创建文件catch(IOExceptione)e.printStackTrace();F i l e类if(file.exists()System.out.println(该文件已存在);System.ou
514、t.println(文件名称为:+file.getName();System.out.println(文件绝对路径为:+file.getAbsolutePath();System.out.println(文件上一级路径为:+file.getParent();/file.delete();/删除该文件/使用list()方法来列出当前路径下的所有文件和路径StringfileList=newFile.list();System.out.println(=当前路径下所有文件和路径如下=);for(StringfileName:fileList)System.out.println(fileName)
515、;F i l e类程序运行结果如下:创建目录:true该文件已存在文件名称为:xsc.txt文件绝对路径为:D:dataxsc.txt文件上一级路径为:D:data=当前路径下所有文件和路径如下=xsc.txt注意:在Windows操作系统下,路径的分隔符使用反斜杠“”,而Java程序中的单反斜杠表示转义字符,所以路径分割符需要使用双反斜杠。第第3节节part字节流字节流所处理的数据基本单元是字节,其输入/输出操作都是在字节的基础上进行。字节流的两个抽象基类是InputStream和OutputStream,其他字节流都是由这两个抽象类派生的。字 节流本节概述11.3.11.3.1 1Inpu
516、tStreamInputStream是字节输入流,使用InputStream可以从数据源以字节为单位进行读取数据。InputStream类中的常用方法如表11-3所示。InputStream11.3.11.3.1 1InputStreamInputStream类是抽象类,不能直接实例化,因此使用其子类来完成具体功能。InputStream类及其子类的关系如图11.3所示。11.3.11.3.1 1InputStreamInputStream常见子类及其功能描述如表11-4所示。下述案例示例了FileInputStream读文件内容,代码如下所示。11.3.11.3.1 1InputStream
517、【代码11.2】FileInputStreamExample.javapackagecom;importjava.io.FileInputStream;importjava.io.IOException;publicclassFileInputStreamExamplepublicstaticvoidmain(Stringargs)/声明文件字节输入流FileInputStreamfis=null;try/实例化文件字节输入流fis=newFileInputStream(D:dataxsc.txt);/创建一个长度为1024的字节数组作为缓冲区bytebbuf=newbyte1024;/用于保
518、存实际读取的字节数inthasRead=0;/使用循环重复读文件中的数据while(hasRead=fis.read(bbuf)0)/将缓冲区中的数据转换成字符串输出System.out.print(newString(bbuf,0,hasRead);11.3.11.3.1 1InputStreamcatch(IOExceptione)e.printStackTrace();finallytry/关闭文件输入流fis.close();catch(IOExceptione)e.printStackTrace();上述代码使用FileInputStream中的read()方法循环从文件中读取数据并
519、输出。在读文件操作时,可能会发生IOException,因此需要将代码放在trycatch异常处理语句中。最后在finally块中,使用close()方法将文件输入流关闭,从而释放资源。11.3.11.3.2 2OutputStreamOutputStream是字节输出流,使用OutputStream可以往数据源以字节为单位写入数据。OutputStream常用方法如表11-5所示。OutputStream11.3.11.3.2 2OutputStreamOutputStream类和InputStream类一样,都是抽象类,不能直接实例化,因此也是使用其子类来完成具体功能。OutputStre
520、am类及其子类的关系如图11.4所示。11.3.11.3.2 2OutputStreamOutputStream常见子类及其功能描述如表11-4所示。下述案例示例了使用FileOutputStream类将用户输入写到指定文件中。代码如下所示。11.3.11.3.2 2OutputStream【代码11.3】FileOutputStreamExample.javapackagecom;importjava.io.FileOutputStream;importjava.io.IOException;importjava.util.Scanner;publicclassFileOutputStrea
521、mExamplepublicstaticvoidmain(Stringargs)/建立一个从键盘接收数据的扫描器Scannerscanner=newScanner(System.in);/声明文件字节输出流FileOutputStreamfos=null;try/实例化文件字节输出流fos=newFileOutputStream(D:dataxsc.txt);System.out.println(请输入内容:);Stringstr=scanner.nextLine();/将数据写入文件中fos.write(str.getBytes();System.out.println(已保存!);catc
522、h(IOExceptione)11.3.11.3.2 2OutputStreame.printStackTrace();finallytry/关闭文件输出流fos.close();scanner.close();catch(IOExceptione)e.printStackTrace();上述代码使用FileOutputStream的write()方法将用户从键盘输入的字符串保存到文件中,因write()方法不能直接写字符串,所以要先使用getBytes()方法将字符串转换成字节数组后在写到文件中。最后在finally块中,使用close()方法将文件输出流关闭并释放资源。11.3.11.3.
523、2 2OutputStream程序运行结果如下所示:请输入内容:我喜欢输入输出流内容的学习!已保存!上面运行结果中用户输入的“我喜欢输入输出流内容的学习!”,将保存到“D:dataxsc.txt”文件中。如果D盘不存在D:dataxsc.txt文件,程序会先创建一个,再将输入内容写入文件;如果该文件已存在,则先清空原来文件中的内容,再写入新的内容。第第4节节part字符流字符流所处理的数据基本单元是字符,其输入/输出操作都是在字符的基础上进行。Java语言中的字符采用Unicode字符编码,每个字符占两个字节空间,而文本文件有可能采用其他类型的编码,如GBK和UTF-8编码方式,因此在Java
524、程序中使用字符流进行操作时,需要注意字符编码方式之间的转换。字符流的两个抽象基类是Reader和Writer,其他字符流都是由这两个抽象类派生的。字 符流本节概述11.4.11.4.1 1ReaderReader是字符输入流,用于从数据源以字符为单位进行读取数据。Reader类中常用方法及功能描述如表11.5所示。Reader11.4.11.4.1 1ReaderReader类是抽象类,不能直接实例化,因此使用其子类来完成具体功能。Reader类及其子类的关系如图11.5所示。11.4.11.4.1 1ReaderReader类常用子类及其功能描述如表11.6所示。下述案例示例了使用FileR
525、eader和BufferedReader读取文件的内容并输出,代码如下所示。11.4.11.4.1 1Reader【代码11.4】ReaderExample.javapackagecom;importjava.io.BufferedReader;importjava.io.FileReader;publicclassReaderExamplepublicstaticvoidmain(Stringargs)/声明一个BufferedReader流的对象BufferedReaderbr=null;try/实例化BufferedReader流,连接FileReader流用于读文件br=newBuff
526、eredReader(newFileReader(D:dataxsc.txt);Stringresult=null;/循环读文件,一次读一行while(result=br.readLine()!=null)/输出System.out.println(result);catch(Exceptione)e.printStackTrace();finally11.4.11.4.1 1Readertry/关闭缓冲流br.close();catch(Exceptionex)ex.printStackTrace();上述代码中,首先声明了一个BufferedReader类型的对象,在实例化该缓冲字符流时创
527、建一个FileReader流作为BufferedReader构造方法的参数,如此两个流就连接在一起,可以对文件进行操作;然后使用BufferedReader的readLine()方法一次读取文件中的一行内容,循环读文件并显示。BufferedReader类中readLine()方法是按行读取,当读取到流的末尾时返回null,所以可以根据返回值是否为null来判断文件是否读取完毕。11.4.11.4.2 2WriterReader是字符输入流,用于从数据源以字符为单位进行读取数据。Reader类中常用方法及功能描述如表11.5所示。Writer11.4.11.4.2 2WriterWriter类
528、是抽象类,不能直接实例化,因此使用其子类来完成具体功能。Writer类及其子类的关系如图11.6所示。下述案例示例了使用FileWriter将用户输入的数据写入指定的文件中,代码如下所示。11.4.11.4.2 2Writer【代码11.5】WriterExample.javapackagecom;importjava.io.FileWriter;importjava.io.IOException;importjava.util.Scanner;publicclassWriterExamplepublicstaticvoidmain(Stringargs)/建立一个从键盘接收数据的扫描器Sca
529、nnerscanner=newScanner(System.in);/声明文件字符输出流FileWriterfw=null;try/实例化文件字符输出流fw=newFileWriter(D:dataxsc.txt);System.out.println(请输入内容:);Stringstr=scanner.nextLine();/将数据写入文件中fw.write(str);System.out.println(已保存!);catch(IOExceptione)11.4.11.4.2 2Writere.printStackTrace();finallytry/关闭文件字符输出流fw.close()
530、;scanner.close();catch(IOExceptione)e.printStackTrace();程序运行结果如下所示:请输入内容:我们努力学习FileWriter类的应用!已保存!第第5节节part过滤器和转换器11.5.11.5.1 1过滤流过滤流用于对一个已有的流进行连接和封装处理,以更加便利的方式对数据进行读/写操作。过滤流又分为过滤输入流和过滤输出流。FilterInputStream为过滤输入流,该类的子类如图11.7所示。过滤流11.5.11.5.1 1过滤流FilterInputStream各个子类的功能描述如表11-8所示。下述案例示例了BufferedInpu
531、tStream类的使用,代码如下所示。11.5.11.5.1 1过滤流【代码11.6】BufferedInputStreamExample.javapackagecom;importjava.io.BufferedInputStream;importjava.io.FileInputStream;publicclassBufferedInputStreamExamplepublicstaticvoidmain(Stringargs)/定义一个BufferedInputStream类型的变量BufferedInputStreambi=null;try/利用FileInputStream对象创建一
532、个输入缓冲流bi=newBufferedInputStream(newFileInputStream(D:dataxsc.txt);intresult=0;/循环读数据while(result=bi.read()!=-1)/输出System.out.print(char)result);catch(Exceptione)e.printStackTrace();finally11.5.11.5.1 1过滤流try/关闭缓冲流bi.close();catch(Exceptionex)ex.printStackTrace();11.5.11.5.1 1过滤流FilterOutputStream类为过
533、滤输出流,其子类的层次关系如图11.8所示。FilterOutputStream类各个子类的功能如表11-9所示。11.5.11.5.1 1过滤流下述案例演示了PrintStream打印流的使用,代码如下所示。【代码11.7】PrintStreamExample.javapackagecom;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.PrintStream;publicclassPrintStreamExamplepublicstaticvoidmain(Stringargs)Stringlin
534、e=使用PrintStream过滤流写数据;tryPrintStreamps=newPrintStream(newFileOutputStream(D:dataxsc.txt);/使用PrintStream打印一个字符串ps.println(line);catch(IOExceptione)e.printStackTrace();上述代码创建一个PrintStream打印流,并与FileOutputStream连接,往文件中写入数据。11.5.11.5.2 2转 换流Java的IO流体系中提供了两个转换流:InputStreamReader:将字节输入流转换成字符输入流。OutputStrea
535、mWriter:将字符输出流转换成字节输出流。下述案例示例了转换流的使用,以InputStreamReader为例,将键盘System.in输入的字节流转换成字符流。代码如下所示。转换流11.5.11.5.2 2转 换流【代码11.8】InputStreamReaderExample.javapackagecom;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;publicclassInputStreamReaderExamplepublicstaticvoidmain
536、(Stringargs)try(/将Sytem.in标准输入流InputStream字节流转换成Reader字符流InputStreamReaderreader=newInputStreamReader(System.in);/将普通Reader包装成BufferedReaderBufferedReaderbr=newBufferedReader(reader)Stringline=null;/采用循环方式来一行一行的读取while(line=br.readLine()!=null)/如果读取的字符串为退出,程序退出if(line.equals(退出)System.exit(0);11.5.1
537、1.5.2 2转 换流/打印读取的内容System.out.println(输入内容为:+line);catch(IOExceptione)e.printStackTrace();上述代码使用InputStreamReader转换流将System.in标准输入流InputStream字节流转换成Reader字符流,再将普通Reader包装成BufferedReader。BufferedReader流具有缓冲功能,一次可以读一行文本。第第6节节part对象流在Java中,使用对象流可实现对象的序列化和反序列化操作。对 象流本节概述11.6.11.6.1 1对象序列化与反序列化对象的序列化(Ser
538、ialize)是指将对象数据写到一个输出流中的过程;而对象的的反序列化是指从一个输入流中读取一个对象。将对象序列化后会转换成与平台无关的二进制字节流,从而允许将二进制字节流持久地保存在磁盘上,或通过网络将二进制字节流传输到另一个网络节点;其他程序从磁盘或网络中获取这种二进制字节流,并将其反序列化后恢复成原来的Java对象。对象序列化与反序列化11.6.11.6.1 1对象序列化与反序列化对象序列化具有以下两个特点:对象序列化可以在分布式应用中进行使用,分布式应用需要跨平台、跨网络,因此要求所有传递的参数、返回值都必须实现序列化;对象序列化不仅可以保存一个对象的数据,而且通过循环可以保存每个对象
539、的数据。对象序列化和反序列化过程如图11.9所示。11.6.11.6.1 1对象序列化与反序列化在Java中,如果需要将某个对象保存到磁盘或通过网络传输,则该对象必须是可以序列化的(serializable)。一个类的对象是可序列化的,则该类必须实现java.lang包下的Serializable接口或Externalizable接口。Java中的很多类已经实现了Serializable接口,该接口只是一个标志接口,接口中没有任何的方法。实现Serializable接口时无须实现任何方法,它只是用于表明该类的实例对象是可以序列化的。只有实现Serializable接口的对象才可以利用序列化工具
540、保存和复原。11.6.11.6.2 2O b j e c t I n p u t S t r e a m和O b j e c t O u t p u t S t r e a mObjectOutputStream是OutputStream的子类,该类也实现了ObjectOutput接口,其中ObjectOutput接口支持对象序列化。该类的一个构造方法如下:ObjectOutputStream(OutputStreamoutStream)throwsIOException其中参数outStream是被写入序列化对象的输出流。ObjectOutputStream类的常用方法及其功能如表11-10
541、所示。ObjectInputStream和ObjectOutputStream11.6.11.6.2 2O b j e c t I n p u t S t r e a m和O b j e c t O u t p u t S t r e a m下述案例定义了一个可以序列化的实体类,代码如下所示。【代码11.9】Person.javapackagecom;importjava.io.Serializable;publicclassPersonimplementsSerializableStringname;intage;Stringaddress;publicPerson(Stringname,i
542、ntage,Stringaddress)this.name=name;this.age=age;this.address=address;11.6.11.6.2 2O b j e c t I n p u t S t r e a m和O b j e c t O u t p u t S t r e a m下述案例使用ObjectOutputStream类示例了对对象的序列化过程,代码如下所示。【代码11.10】ObjectOutputStreamExample.javapackagecom;importjava.io.FileOutputStream;importjava.io.ObjectOut
543、putStream;publicclassObjectOutputStreamExamplepublicstaticvoidmain(Stringargs)/创建一个ObjectOutputStream对象输出流try(ObjectOutputStreamobs=newObjectOutputStream(newFileOutputStream(D:dataxsc.txt)/创建一个Person类型的对象Personperson=newPerson(李飞,20,重庆);/把对象写入到文件中obs.writeObject(person);obs.flush();System.out.printl
544、n(序列化完毕!);catch(Exceptionex)ex.printStackTrace();11.6.11.6.2 2O b j e c t I n p u t S t r e a m和O b j e c t O u t p u t S t r e a m上述代码中,首先创建了一个ObjectOutputStream类型的对象,其中创建一个FileOutputStream类型的对象作为ObjectOutputStream构造方法的参数,然后创建了一个Person类型的对象,在利用ObjectOutputStream对象的writeObject()方法将对象写入到文件中。ObjectInp
545、utStream是InputStream的子类,该类也实现了ObjectInput接口,其中ObjectInput接口支持对象序列化。该类的一个构造方法如下:ObjectInputStream(InputStreaminputStream)throwsIOException其中inputStream参数是读取序列化对象的输入流。ObjectInputStream类的常用方法及其功能如表11-11所示。11.6.11.6.2 2O b j e c t I n p u t S t r e a m和O b j e c t O u t p u t S t r e a m下述案例使用ObjectInpu
546、tStream类示例了反序列化对象的过程,代码如下所示。【代码11.11】ObjectInputStreamExamplejavapackagecom;importjava.io.FileInputStream;importjava.io.ObjectInputStream;publicclassObjectInputStreamExamplepublicstaticvoidmain(Stringargs)/创建一个ObjectInputStream对象输入流try(ObjectInputStreamois=newObjectInputStream(newFileInputStream(D:d
547、ataxsc.txt)/从ObjectInputStream对象输入流中读取一个对象,并强制转换成Person对象Personperson=(Person)ois.readObject();System.out.println(反序列化完毕!读出的对象信息如下:);System.out.println(姓名:+person.name);System.out.println(年龄:+person.age);System.out.println(地址:+person.address);catch(Exceptionex)ex.printStackTrace();11.6.11.6.2 2O b j
548、 e c t I n p u t S t r e a m和O b j e c t O u t p u t S t r e a m上述代码首先创建了一个ObjectInputStream类型的对象输入流,并连接一个FileInputStream文件输入流,对文件“D:dataxsc.txt”进行读取操作;再利用readObject()方法从文件中读取一个对象并强制转换成Person对象;最后将读取的Person对象信息输出。第第6节节partNIOJava传统的IO流都是通过字节的移动进行处理的,即使不是直接去处理字节流,但其底层的实现还是依赖于字节处理。因此,Java传统的IO流一次只能处理一
549、个字节,从而造成系统的效率不高。从JDK1.4开始,Java提供了一系列改进功能的IO流,这些新的功能IO流被统称为NIO(NewIO,新IO)。NIO新增的类都放在java.nio包及子包下,这些新增的类对原来java.io包中的很多类都进行了改写,以满足NIO的功能需要。N IO本节概述11.7.11.7.1 1N I O概述NIO和传统的IO有相同的目的,都是用于数据的输入/输出,但NIO采用了内存映射文件这种与原来不同的方式来处理输入/输出操作。NIO将文件或文件的一段区域映射到内存中,这样可以像访问内存一样来访问文件了。Java中与NIO相关的有以下几个包:java.nio包:主要包
550、含各种与Buffer(缓冲)相关的类;java.nio.channels包:主要包含与Channel(通道)和Selector相关的类;java.nio.charset包:主要包含与字符集相关的类;java.nio.channels.spi包:主要包含与Channel相关的服务提供者编程接口;java.nio.charset.spi包:主要包含与字符集相关的服务提供者编程接口。NIO概述11.7.11.7.1 1N I O概述Buffer和Channel是NIO中两个核心对象:Buffer可以被理解成一个容器,其本质是一个数组,往Channel中发送或读取的对象都必须先放到Buffer中。Ch
551、annel是对传统的IO系统的模拟,在NIO系统中所有数据都需要经过通道传输。Channel与传统的InputStream、OutputStream最大的区别是提供一个map()方法,通过该方法可以直接将一块数据映射到内存中。传统的IO是面向流的处理,而NIO则是面向块的处理。除了Buffer和Channel之外,NIO还提供了用于将Unicode字符串映射成字节序列以及映射操作的Charset类,也提供了支持非阻塞方式的Selector类。11.7.11.7.2 2BufferNIO和传统的IO有相同的目的,都是用于数据的输入/输出,但NIO采用了内存映射文件这种与原来不同的方式来处理输入/
552、输出操作。NIO将文件或文件的一段区域映射到内存中,这样可以像访问内存一样来访问文件了。Java中与NIO相关的有以下几个包:java.nio包:主要包含各种与Buffer(缓冲)相关的类;java.nio.channels包:主要包含与Channel(通道)和Selector相关的类;java.nio.charset包:主要包含与字符集相关的类;java.nio.channels.spi包:主要包含与Channel相关的服务提供者编程接口;java.nio.charset.spi包:主要包含与字符集相关的服务提供者编程接口。Buffer11.7.11.7.2 2BufferBuffer是一个
553、抽象类,其最常使用的子类是ByteBuffer,用于在底层字节数组上进行get/set操作。除了布尔类型之外,其他基本数据类型都有对应的Buffer类,例如,CharBuffer、ShortBuffer、IntBuffer等。这些Buffer类都没有提供构造方法,而是通过下面一个静态方法来获得一个Buffer对象:StaticXxxBufferallocate(intcapacity):/创建一个指定容量的XxxBuffer对象通常使用最多的是ByteBuffer和CharBuffer,而其他Buffer则很少使用。其中,ByteBuffer类还有一个名为“MappedByteBuffer”的
554、子类,用于表示Channel将磁盘文件的部分或全部内容映射到内存中所得到的结果,通常MappedByteBuffer对象由Channel的map()方法返回。使用Buffer类时涉及到容量(Capacity)、界限(limit)和位置(position)三个概念。其中容量是该Buffer的最大数据容量,创建后不能改变;界限是第一个不应该被读出或者写入的缓冲区位置索引,位于limit后的数据既不能被读,也不能被写;位置是用于指明下一个可以被读出或者写入的缓冲区位置索引。11.7.11.7.2 2BufferBuffer还支持一个可选的标记mark,Buffer允许直接将position定位到该m
555、ark处。Capacity、limit、position和mark这些值之间满足以下关系:0=mark=position=limit=capacityBuffer读入数据后如图11.10所示。11.7.11.7.2 2BufferBuffer常用的方法如表11-12所示。11.7.11.7.2 2Buffer下述案例示例了Buffer的使用,代码如下所示。【代码11.12】NIOBufferExample.javapackagecom;importjava.nio.CharBuffer;publicclassNIOBufferExamplepublicstaticvoidmain(String
556、args)/创建一个容量为8的CharBuffer缓冲区对象CharBufferbuff=CharBuffer.allocate(8);/获取缓冲区的容量、界限和位置System.out.println(capacity:+buff.capacity();System.out.println(limit:+buff.limit();System.out.println(position:+buff.position();/使用put()方法向CharBuffer对象中放入元素buff.put(a);buff.put(b);buff.put(c);System.out.println(加入三个元
557、素后,position=+buff.position();/调用flip()方法反转缓冲区buff.flip();/System.out.println(执行flip()后,limit=+buff.limit();System.out.println(position=+buff.position();11.7.11.7.2 2Buffer/取出第一个元素System.out.println(第一个元素(position=0):+buff.get();System.out.println(取出一个元素后,position=+buff.position();/调用clear()方法清除缓冲区bu
558、ff.clear();System.out.println(执行clear()后,limit=+buff.limit();System.out.println(执行clear()后,position=+buff.position();/根据索引获取数据System.out.println(执行clear()后,缓冲区内容并没有被清除:+第三个元素为:+buff.get(2);System.out.println(执行绝对读取后,position=+buff.position();11.7.11.7.2 2Buffer程序运行结果如下:capacity:8limit:8position:0加入三
559、个元素后,position=3执行flip()后,limit=3position=0第一个元素(position=0):a取出一个元素后,position=1执行clear()后,limit=8执行clear()后,position=0执行clear()后,缓冲区内容并没有被清除:第三个元素为:c执行绝对读取后,position=011.7.11.7.3 3C hann e lChannel与传统的IO流类似,但主要有两点区别:Channel类可以直接将指定文件的部分或全部直接映射成Buffer;程序不能直接访问Channel中的数据,Channel只能与Buffer进行交互。Channel是
560、接口,其实现类包括:DatagramChannel、FileChannel、Pipe.SinkChannel、Pipe.SourceChannel、SelectableChannel、ServerSocketChannel、SocketChannel等。所有的Channel对象都不是通过构造方法直接创建,而是通过传统的节点InputStream或OutputStream的getChannel()方法来获取对应的Channel对象,不同的节点流所获取的Channel也是不一样的。例如,FileInputStream和FileOutputStream的getChannel()方法返回的是FileC
561、hannel,而PipeInputStream和PipeOutputStream的getChannel()方法返回的是Pipe.SourceChannel。Channel11.7.11.7.3 3C hann e l在本章节中,以FileChannel为例,介绍如何使用Channel进行数据访问。FileChannel类中常用的方法如表11-13所示。11.7.11.7.3 3C hann e l下述案例示例将FileChannel的所有数据映射成ByteBuffer,代码如下所示。【代码11.13】NIOFileChannelExample.javapackagecom;importjava
562、.io.*;importjava.nio.*;importjava.nio.channels.FileChannel;importjava.nio.charset.*;publicclassNIOFileChannelExamplepublicstaticvoidmain(Stringargs)Filef=newFile(D:dataxsc.txt);try(/创建FileInputStream,以该文件输入流创建FileChannelFileChannelinChannel=newFileInputStream(f).getChannel();/以文件输出流创建FileBuffer,用以控制
563、输出FileChanneloutChannel=newFileOutputStream(D:channel.txt).getChannel()/用map()方法经FileChannel中的所有数据映射成ByteBufferMappedByteBufferbuffer=inChannel.map(FileChannel.MapMode.READ_ONLY,0,f.length();/使用GBK的字符集来创建解码器Charsetcharset=Charset.forName(GBK);11.7.11.7.3 3C hann e l/直接将buffer里的数据全部输出outChannel.write
564、(buffer);/再次调用buffer的clear()方法,复原limit、position的位置buffer.clear();/创建解码器(CharsetDecoder)对象CharsetDecoderdecoder=charset.newDecoder();/使用解码器将ByteBuffer转换成CharBufferCharBuffercharBuffer=decoder.decode(buffer);/CharBuffer的toString方法可以获取对应的字符串System.out.println(charBuffer);catch(IOExceptionex)ex.printSta
565、ckTrace();上面程序中分别使用FileInputStream和FileOutputStream来获取FileChannel,虽然FileChannel既可以读取也可以写入,但FileInputStream获取的FileChannel只能读,而FileOutputStream获取的FileChannel则只能写。程序后面部分为了能将文件里的内容打印出来,使用了Charset类和CharsetDecoder类将ByteBuffer转换成CharBuffer。11.7.11.7.4 4字符集和C h a r s e t我们在计算机里看到的各种文件、数据和图片都只是一种表面现象,其实所有文件在
566、计算机底层都是以二进制文件形式保存,即全部是字节码。当需要保存文件时,程序必须先把文件中的每个字符翻译成二进制序列;当需要读取文件时,程序必须把二进制序列转换为一个个的字符。这个过程涉及到两个概念:编码(Encode)和解码(Decode),通常而言,把明文的字符序列转换成计算机理解的二进制序列,称为编码,把二进制序列转换成普通人能看懂的明文字符串称为解码。Java默认使用Unicode字符集,但很多操作系统并不使用Unicode字符集,那么当从系统中读取数据到Java程序中时,就可能出现乱码现象。字符集和Charset11.7.11.7.4 4字符集和C h a r s e tJDK1.4提
567、供了Charset来处理字节序列和字符序列(字符串)之间的转换关系,该类包含了用于创建解码器和编码器的方法,还提供了获取Charset所支持字符集的方法。Charset类常用方法如表11-14所示。11.7.11.7.4 4字符集和C h a r s e t下述案例示例了Charset类及字符集的使用,代码如下所示。【代码11.14】CharsetExample.javapackagecom;importjava.nio.ByteBuffer;importjava.nio.CharBuffer;importjava.nio.charset.CharacterCodingException;im
568、portjava.nio.charset.Charset;importjava.nio.charset.CharsetDecoder;importjava.nio.charset.CharsetEncoder;importjava.util.SortedMap;publicclassCharsetExamplepublicstaticvoidmain(Stringargs)throwsCharacterCodingException/获取Java支持的全部字符集,并遍历出来SortedMapmap=Charset.availableCharsets();for(Stringstr:map.ke
569、ySet()System.out.println(str+-+map.get(str);/创建简体中文对应的CharsetCharsetcn=Charset.forName(GBK);11.7.11.7.4 4字符集和C h a r s e t/获取cn对象对应的编码器和解码器CharsetEncodercne=cn.newEncoder();CharsetDecodercnd=cn.newDecoder();/创建一个对象,并放入元素CharBuffercnb=CharBuffer.allocate(8);cnb.put(我);cnb.put(爱);cnb.put(北);cnb.put(京)
570、;cnb.flip();/将CharBuffer中的字符序列转换成字节序列ByteBufferbuff=cne.encode(cnb);for(inti=0;iBig5Big5-HKSCS-Big5-HKSCSEUC-JP-EUC-JPEUC-KR-EUC-KRGB18030-GB18030GB2312-GB2312GBK-GBK.-50-46-80-82-79-79-66-87我爱北京11.7.11.7.5 5文件锁文件锁在操作系统中是很平常的事情,如果多个运行的程序需要并发修改同一个文件时,程序之间就需要某种机制来进行通信。文件锁可以有效地阻止多个进程并发修改同一个文件,所以现在的大部分操
571、作系统都提供了文件锁的功能。文件锁控制文件的全部或部分字节的访问,但文件锁在不同的操作系统中差别较大,所以早期的JDK版本并未提供文件锁的支持。从JDK1.4的NIO开始,Java开始提供文件锁的支持。文件锁11.7.11.7.5 5文件锁在NIO中,Java提供了FileLock类来支持文件锁定功能,在FileChannel类中提供了Lock()/tryLock()方法来获得文件锁对象,从而锁定文件。FileChannel类常用的方法如表11-15所示。11.7.11.7.5 5文件锁当参数shared为true时,表明该锁是一个共享锁,它将允许多个进程来读取该文件,但阻止其他进程获得对该文
572、件的排他锁。当shared为false时,表明该锁是一个排他锁,它将锁住对该文件的读写。程序可以通过FileLock类的isShared()方法来判断它获得的锁是否为共享锁。处理完文件后通过FileLock了的release()方法来释放文件锁。Lock()方法和tryLoc()方法的区别在于:当lock()试图锁定某个文件时,如果无法得到文件锁,程序将一直阻塞;而tryLoc()是尝试锁定文件,它将直接返回而不是阻塞,如果获得了文件锁,该方法则返回该文件锁,否则将返回null。下面案例示例了FileLock锁定文件的使用,代码如下所示。11.7.11.7.5 5文件锁【代码11.15】Fil
573、eLockExample.javapackagecom;importjava.io.FileOutputStream;importjava.nio.channels.FileChannel;importjava.nio.channels.FileLock;publicclassFileLockExamplepublicstaticvoidmain(Stringargs)try/使用FileOutputStream获取FileChannel对象FileChannelfc=newFileOutputStream(D:dataxsc.txt).getChannel();/使用非阻塞方式对指定文件加锁
574、FileLockfl=fc.tryLock();/程序暂停1秒Thread.sleep(1000);/释放锁fl.release();catch(Exceptione)e.printStackTrace();11.7.11.7.5 5文件锁关于文件锁还需要指出如下几点:在某些平台上,文件锁仅仅是建议性的,并不是强制性的。这意味着即使一个程序不能获得文件锁,它也可以对文件进行读写。在某些平台上,不能同步地锁定一个文件并把它映射到内存中。文件锁是由Java虚拟机所持有的,如果两个Java程序使用同一个Java虚拟机运行,则它们不能对同一个文件进行加锁。在某些平台上关闭FileChannel时,会释
575、放Java虚拟机在该文件上的所有锁,因此应该避免对同一个被锁定的文件打开多个FileChannel。11.7.11.7.6 6NI O.2Java7对原有的NIO进行重大改进,称为“NIO.2”。NIO.2主要有以下两方面的改进:提供了全面的文件IO和文件系统访问支持,新增了java.nio.file包及其子包;新增基于异步的Channel的IO,在java.nio.channels包下增加了多个以Asynchronous开头的Channel接口和类。NIO.211.7.11.7.6 6NI O.2NIO.2为了弥补原先File类的不足,引入了一个Path接口,该接口代表一个平台无关的文件路径
576、。另外,NIO.2还提供了Files和Paths两个工具类。Paths工具类提供了get()静态方法来创建Path实例对象,如表11-16所示。11.7.11.7.6 6NI O.2下面案例简单示例了Path接口的功能和用法,代码如下所示。【代码11.16】NIO2PathExample.javapackagecom;importjava.nio.file.Path;importjava.nio.file.Paths;publicclassNIO2PathExamplepublicstaticvoidmain(Stringargs)/以当前路径来创建Path对象Pathpath=Paths.g
577、et(.);System.out.println(path里包含的路径数量:+path.getNameCount();System.out.println(path的根路径:+path.getRoot();/获取path对应的绝对路径。PathabsolutePath=path.toAbsolutePath();System.out.println(absolutePath);/获取绝对路径的根路径System.out.println(absolutePath的根路径:+absolutePath.getRoot();/获取绝对路径所包含的路径数量System.out.println(absol
578、utePath里包含的路径数量:+absolutePath.getNameCount();System.out.println(absolutePath.getName(1);11.7.11.7.6 6NI O.2/以多个String来构建Path对象Pathpath2=Paths.get(D:,workspace);System.out.println(path2);上述代码中使用Paths类的get()方法获得Path对象,然后打印Path对象中的路径数量、根路径以及绝对路径等信息;Paths类还提供了get(Stringfirst,String.more)方法来获取对象,Paths会将给
579、定的多个字符连缀成路径,比如Paths.get(D:,workspace)就返回D:workspace路径。本章课后作业见教材JAVA程 序 设计本章学习目标:掌握线程创建的过程掌握线程的生命周期掌了解线程同步机制以及线程通信了解线程的优先级掌握线程的同步与死锁第第十二十二章章多线程多线程第第1节节part线程概述线程(Thread)在多任务处理应用程序中起着至关重要的作用。之前所接触的应用程序都是采用单线程处理模式。单线程在某些功能方面会受到限制,无法同时处理多个互不干扰的任务,只有一个顺序执行流;而多线程是同时有多个线程并发执行,同时完成多个任务,具有多个顺序执行流,且执行流之间互不干扰。
580、Java语言多多线程提供了非常优秀的支持,在程序中可以通过简便的方式创建多线程。线程概述本节概述在操作系统中,每个独立运行的程序就是一个进程(Process),当一个程序进入内存运行时,即变成一个进程。进程是操作系统进行资源分配和调度的一个独立单位,是具有独立功能且处于运行过程中的程序。在Windows操作系统中,右击任务栏,选择“启动任务管理器”菜单命令,可以打开“Windows任务管理器”窗口,该窗口中的“进程”选项卡中显示系统当前正在运行的进程,如图12.1所示。12.1.12.1.1 1线 程 和进程线程和进程进程具有如下三个特征:(1)独立性:进程是操作系统中独立存在的实体,拥有自己
581、独立的资源,每个进行都拥有自己私有的地址空间,其他进程不可以直接访问该地址空间,除非进程本身允许的情况下才能进行访问。(2)动态性:程序只是一个静态的指令集合,只有当程序进入内存运行时,才变成一个进程。进程是一个正在内存中运行的、动态的指令集合,进程具有自己的生命周期和各种不同状态。(3)并发性:多个进程可以在单个处理器上并发执行,多个进程之间互不影响。12.1.12.1.1 1线 程 和进程目前的操作系统都支持多线程的并发,但在具体的实现细节上会采用不同的策略。对于一个CPU而言,在某一时间点只能执行一个进程,CPU会不断在多个进程之间来回轮换执行。并发性(concurrency)和并行性(
582、parallel)是两个相似但又不同的概念:并发是指多个事件在同一时间间隔内发生,其实质是在一个CPU上同时运行多个进程,CPU要在多个进程之间切换。并发不是真正的同时发生,而是对有限物理资源进行共享以便提高效率。并行是指多个事件在同一时刻发生,其实质是多个进程同一时刻可在不同的CPU上同时执行,每个CPU运行一个进程。12.1.12.1.1 1线 程 和进程并发就像一个人喂两个孩子吃饭,轮换着每人喂一口,表面上两个孩子都在吃饭;而并行就是两个人喂两个孩子吃饭,两个孩子也同时在吃饭。并发和并行之间的区别如图12.2所示。12.1.12.1.1 1线 程 和进程线程是进程的组成部分,一个线程必须
583、在一个进程之内,而一个进程可以拥有多个线程,一个进程中至少有一个线程。线程是最小的处理单位,线程可以拥有自己的堆栈、计数器和局部变量,当不能拥有系统资源,多个线程共享其所在进程的系统资源。线程可以完成一定的任务,使用多线程可以在一个程序中同时完成多个任务,在更低的层次中引入多任务处理。多线程在多CPU的计算机中可以实现真正物理上的同时执行;而对于单CPU的计算机实现的只是逻辑上的同时执行,在每个时刻,真正执行的只有一个线程,由操作系统进行线程管理调度,但由于CPU的速度很快,让人感到像是多个线程在同时执行。12.1.12.1.1 1线 程 和进程多线程扩展了多进程的概念,使得同一个进程可以同时
584、并发处理多个任务。因此,线程也被称作轻量级进程。多进程与多线程是多任务的两种类型,两者之间的主要区别如下:(1)进程之间的数据块是相互独立的,彼此互不影响,进程之间需要通过信号、管道等进行交互。(2)多线程之间的数据块可以共享,一个进程中的多个线程可以共享程序段、数据段等资源。多线程比多进程更便于资源共享,同时Java提供的同步机制还可以解决线程之间的数据完整性问题,使得多线程设计更易发挥作用。多线程编程的优点如下:(1)多线程之间共享内存,节约系统资源成本;(2)充分利用CPU,执行并发任务效率高;(3)Java内置多线程功能支持,简化编程模型(4)GUI应用通过启动单独线程收集用户界面事件
585、,简化异步事件处理,使GUI界面的交互性更好。12.1.12.1.1 1线 程 和进程Java线程模型提供线程所必需的功能支持,基本的Java线程模型有Thread类、Runnable接口、Callable接口和Future接口等,这些线程模型都是面向对象的。Thread类将线程所必需的功能进行封装,其常用的方法如表12-1所示。12.1.12.1.2 2Java线程模型Java线程模型Thread类的run()方法是线程中最重要的方法,该方法用于执行线程要完成的任务;当创建一个线程时,要完成自己的任务,则需要重写run()方法。此外,Thread类还提供了start()方法,该方法用于负责线
586、程的启动;当调用start()方法成功地启动线程后,系统会自动调用Thread类的run()方法来执行线程。因此,任何继承Thread类的线程都可以通过start()方法来启动。Runnable接口用于标识某个Java类可否作为线程类,该接口只有一个抽象方法run(),即线程中最重要的执行体,用于执行线程中所要完成的任务。Runnable接口定义在java.lang包中,定义代码如下所示。packagejava.lang;publicinterfaceRunnablepublicabstractvoidrun();12.1.12.1.2 2Java线程模型Callable接口是Java5新增的
587、接口,该接口中提供一个call()方法作为线程的执行体。call()方法比run()方法功能更强大,call()方法可以有返回值,也可以声明抛出异常。Callable接口定义在java.util.concurrent包中,定义代码如下所示。packagejava.util.concurrent;publicinterfaceCallableVcall()throwsException;12.1.12.1.2 2Java线程模型Future接口用来接收Callable接口中call()方法的返回值。Future接口提供一些方法用于控制与其关联的Callable任务。Future接口提供的方法如表
588、12-2所示。12.1.12.1.2 2Java线程模型Callable接口有泛型限制,该接口中的泛型形参类型与call()方法返回值的类型相同;而且Callable接口是函数式接口,因此从Java8开始可以使用Lambda表达式创建Callable对象。每个能够独立运行的程序就是一个进程,每个进程至少包含一个线程,即主线程。在Java语言中,每个能够独立运行的Java程序都至少有一个主线程,且在程序启动时,JVM会自动创建一个主线程来执行该程序中的main()方法。因此,主线程有以下两个特点:(1)一个进程肯定包含一个主线程(2)主线程用来执行main()方法下述程序在main()方法中,调
589、用Thread类的静态方法currentThread()来获取主线程,代码如下所示。12.1.12.1.3 3主 线程主线程【代码12.1】MainThreadExample.javapackagecom;publicclassMainThreadExamplepublicstaticvoidmain(Stringargs)/调用Thread类的currentThread()获取当前线程Threadt=Thread.currentThread();/设置线程名t.setName(MyThread);System.out.println(主线程是:+t);System.out.println(线
590、程名:+t.getName();System.out.println(线程ID:+t.getId();12.1.12.1.3 3主 线程上述代码中,通过Thread.currentThread()静态方法来获取当前线程对象,由于是在main()方法中,所以获取的线程是主线程。调用setName()方法可以设置线程名,调用getId()方法可以获取线程的Id号,调用getName()方法可以获取线程的名字。程序运行结果如下:主线程是:ThreadMyThread,5,main线程名:MyThread线程ID:112.1.12.1.3 3主 线程第第2节节part线程的创建和启动基于Java线程模
591、型,创建线程的方式有三种:(1)第一种方式是继承Thread类,重写Thread类中的run()方法,直接创建线程。(2)第二种方式是实现Runnable接口,再通过Thread类和Runnable的实现类间接创建一个线程。(3)第三种方式是使用Callable接口或Future接口间接创建线程。上述三种方式从本质上是一致的,最终都是通过Thread类来建立线程。提供Runnable、Callable和Future接口模型是由于Java不支持多继承,如果一个线程类继承了Thread类,则不能再继承其他的类,因此可以通过实现接口的方式间接创建线程。采用Runnable、Callable和Futu
592、re接口的方式创建线程时,线程类还可以继承其他类,且多个线程之间可以共享一个target目标对象,适合多个相同线程处理同一个资源的情况,从而可以将CPU、代码和数据分开,形成清晰的数据模型。线程的启动和创建本节概述12.2.12.2.1 1继承Thread类通过继承Thread类来创建并启动线程的步骤如下:(1)定义一个子类继承Thread类,并重写run()方法。(2)创建子类的实例,即实例化线程对象。(3)调用线程对象的start()方法启动该线程。Thread类的start()方法将调用run()方法,该方法用于启动线程并运行。因此start()方法不能多次调用,当多次调用td.star
593、t()方法时会抛出一个IllegalThreadStateException异常。下述案例示例通过继承Thread类来创建并启动线程的步骤,代码如下所示。继承Thread类继承Thread类【代码12.2】ThreadExample.javapackagecom;/继承Thread类publicclassThreadExampleextendsThread/重写run()方法publicvoidrun()for(inti=0;i10;i+)/继承Thread类时,直接使用this即可获取当前线程对象/调用getName()方法返回当前线程的名字System.out.println(this.g
594、etName()+:+i);12.2.12.2.1 1继承Thread类publicstaticvoidmain(Stringargs)/创建线程对象ThreadExampletd=newThreadExample();/调用start()方法启动线程td.start();/主线程任务for(inti=1100;i1110;i+)/使用Thread.currentThread().getName()获取主线程名字System.out.println(Thread.currentThread().getName()+:+i);12.2.12.2.1 1继承Thread类因为线程在CPU中的执行是
595、由操作系统所控制,执行次序是不确定的,除非使用同步机制强制按特定的顺序执行,所以程序代码运行的结果会因调度次序不同而不同。程序执行结果可能如下:main:1101Thread-0:1main:1102Thread-0:2main:1103.在创建td线程对象时并未指定该线程的名字,因此所输出的线程名是系统的默认值“Thread-0”。对于输出结果,不同机器所执行的结果可能不同,在同一机器上多次运行同一个程序也可能生成不同结果。12.2.12.2.1 112.2.12.2.2 2实 现 Runable接口创建线程的第二种方式是实现Runnable接口。Runnable接口中只有一个run()方法
596、,一个类实现Runnable接口后,并不代表该类是个“线程”类,不能直接启动线程,必须通过Thread类的实例来创建并启动线程。通过Runnable接口创建并启动线程的步骤如下:(1)定义一个类实现Runnable接口,并实现该接口中的run()方法;(2)创建一个Thread类的实例,将Runnable接口的实现类所创建的对象作为参数传入Thread类的构造方法中;(3)调用Thread对象的start()方法启动该线程。下述案例示例通过实现Runnable接口创建并启动线程的步骤,代码如下所示。实现Runable接口实 现 Runable接口【代码12.3】RunnableExamble.
597、javapackagecom;/实现Runnable接口publicclassRunnableExambleimplementsRunnable/重写run()方法publicvoidrun()/获取当前线程的名字for(inti=0;i10;i+)/实现Runnable接口时,只能使用Thread.currentThread()获取当前线程对象/再调用getName()方法返回当前线程的名字System.out.println(Thread.currentThread().getName()+:+i);12.2.12.2.2 2实 现 Runable接口publicstaticvoidmai
598、n(Stringargs)/创建一个Thread类的实例,其参数是RunnableExamble类的对象Threadtd=newThread(newRunnableExamble();/调用start()方法启动线程td.start();/主线程任务for(inti=1100;i1110;i+)/使用Thread.currentThread().getName()获取主线程名字System.out.println(Thread.currentThread().getName()+:+i);12.2.12.2.2 2实 现 Runable接口上述代码定义了一个RunnableExamble类,该
599、类实现了Runnable接口,并实现run()方法,这样的类可以称为线程任务类。直接调用Thread类或Runnable接口所创建的对象的run()方法是无法启动线程的,必须通过Thread的start()方法才能启动线程。程序执行的结果可能如下:main:1100Thread-0:0Thread-0:1Thread-0:2.12.2.12.2.2 212.2.12.2.3 3使用 C al l abl e和F u t u r e接口创建线程的第三种方式是使用Callable和Future接口。Callable接口提供一个call()方法作为线程的执行体,该方法的返回值使用Future接口来代
600、表。从Java5开始,为Future接口提供一个FutureTask实现类,该类同时实现了Future和Runnable两个接口,因此可以作为Thread类的target参数。使用Callable和Future接口的最大优势在于可以在线程执行完成之后获得执行结果。使用Callable和Future接口创建并启动线程的步骤如下:(1)创建Callable接口的实现类,并实现call()方法,该方法将作为线程的执行体,并具有返回值;然后创建Callable实现类的实例。(2)使用FutureTask类来包装Callable对象,在FutureTask对象中封装了Callable对象的call()方
601、法的返回值。(3)使用FutureTask对象作为Thread对象的target,创建并启动新线程。(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。下述案例示例通过Callable和Future接口创建并启动线程的步骤,代码如下所示。使用Callable和Future接口使用 C al l abl e和F u t u r e接口【代码12.4】CallableFutureExample.javapackagecom;importjava.util.concurrent.Callable;importjava.util.concurrent.FutureTask
602、;/创建Callable接口的实现类classTaskimplementsCallable/实现call()方法,作为线程执行体publicIntegercall()throwsExceptioninti=0;for(;i10;i+)System.out.println(Thread.currentThread().getName()+:+i);/call()方法可以有返回值returni;12.2.12.2.3 3使用 C al l abl e和F u t u r e接口publicclassCallableFutureExamplepublicstaticvoidmain(Stringar
603、gs)/使用FutureTask类包装Callable实现类的实例FutureTasktask=newFutureTask(newTask();/创建线程,使用FutureTask对象task作为Thread对象的target,/并调用start()方法启动线程Threadtd=newThread(task,子线程);td.start();/调用FutureTask对象task的get()方法获取子线程执行结束后的返回值trySystem.out.println(子线程返回值:+task.get();catch(Exceptione)e.printStackTrace();/主线程任务for(
604、inti=1100;i1110;i+)/使用Thread.currentThread().getName()获取主线程名字System.out.println(Thread.currentThread().getName()+:+i);12.2.12.2.3 3使用 C al l abl e和F u t u r e接口上述代码先定义一个Task类,该类实现Callable接口并重写call()方法,call()的返回值为整型,因此Callable接口中对应的泛型限制为Integer,即Callable。在main()方法中,先创建FutureTask类的对象task,该对象包装Task类;再创
605、建Thread对象并启动线程;最后调用FutureTask对象task的get()方法获取子线程执行结束后的返回值。整个程序所实现的功能与前两种方式一样,只是增加了子线程返回值。程序运行结果如下:子线程:0子线程:1子线程:2.子线程返回值:子线程返回值:10main:1100main:110112.2.12.2.3 3使用 C al l abl e和F u t u r e接口从Java8开始,可以直接使用Lambda表达式创建Callable对象,下述案例示例通过Lambda表达式创建Callable对象,代码如下所示。【代码12.5】LambdaCallableExample.javapa
606、ckagecom;importjava.util.concurrent.Callable;importjava.util.concurrent.FutureTask;publicclassLambdaCallableExamplepublicstaticvoidmain(Stringargs)/使用Lambda表达式创建Callable对象/使用FutureTask类包装Callable对象FutureTasktask=newFutureTask(Callable)()-inti=0;for(;i10;i+)System.out.println(Thread.currentThread().g
607、etName()+:+i);/call()方法可以有返回值方法可以有返回值returni;);12.2.12.2.3 3使用 C al l abl e和F u t u r e接口/创建线程,使用FutureTask对象task作为Thread对象的target,/并调用start()方法启动线程Threadtd=newThread(task,子线程);td.start();/调用FutureTask对象task的get()方法获取子线程执行结束后的返回值trySystem.out.println(子线程返回值:+task.get();catch(Exceptione)e.printStackT
608、race();/主线程任务for(inti=1100;i1110;i+)/使用Thread.currentThread().getName()获取主线程名字System.out.println(Thread.currentThread().getName()+:+i);上述代码加粗部分就是Lambda表达式,可以直接使用Lambda表达式创建Callable对象,而无须先创建Callable实现类,但Lambda表达式必须在jdk1.8版本后才可以运行。在JavaAPI中,定义的FutureTask类实际上直接实现RunnableFuture接口,而RunnableFuture接口继承Runn
609、able和Future两个接口,因此FutureTask类即实现了Runnable接口,又实现了Future接口。12.2.12.2.3 3第第3节节part线程的生命周期线程具有生命周期,当线程被创建并启动后,不会立即进入执行状态,也不会一直处于执行状态。在线程的生命周期中,要经过5种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。线程状态之间的转换如图12-3所示。线程的生命周期本节介绍12.3.12.3.1 1新 建 和 就 绪状态当程序使用new关键字创建一个线程之后,该线程就处于新建状态,此时与其他Java对象一样,仅
610、由JVM为其分配内存并初始化。新建状态的线程没有表现出任何动态特征,程序也不会执行线程的执行体。当线程对象调用start()方法之后,线程就处于就绪状态,相当于“等待执行”。此时,调度程序就可以把CPU分配给该线程,JVM会为线程创建方法调用栈和程序计数器。处于就绪状态的线程并没有开始运行,只是表示该线程准备就绪等待执行。注意只能对新建状态的线程调用start()方法,即new完一个线程后,只能调用一次start()方法,否则将引发IllegalThreadStateException异常。下述案例示例了新建线程重复调用start()方法引发异常,代码如下所示。新建和就绪状态新 建 和 就 绪
611、状态【代码12.6】IllegalThreadExample.javapackagecom;publicclassIllegalThreadExamplepublicstaticvoidmain(Stringargs)/创建线程Threadt=newThread(newRunnable()publicvoidrun()for(inti=0;i10;i+)System.out.print(i+););t.start();t.start();上述代码三次调用start()方法,多次启动线程,因此会引发IllegalThreadStateException异常。运行结果可能如下所示:Exceptio
612、ninthreadmainjava.lang.IllegalThreadStateException0123456789atjava.lang.Thread.start(UnknownSource)atcom.IllegalThreadExample.main(IllegalThreadExample.java:12)12.3.12.3.1 112.3.12.3.2 2运行和阻塞状态处于就绪状态的线程获得CPU后,开始执行run()方法的线程执行体,此时该线程处于运行状态。如果计算机的CPU是单核的,则在任何时刻只有一个线程处于运行状态。一个线程开始运行后,不可能一直处于运行状态。线程在运行过
613、程中需要被中断,目的是使其他线程获得执行的机会,线程调度的细节取决于底层平台所采用的策略。目前UNIX系统采用的是时间片算法策略,Windows系统采用的则是抢占式策略,另外一种小型设备(手机)则可能采用协作式调度策略。对于采用抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务;当该时间段用完后,系统就会剥夺该线程所占用的资源,让其他线程获得执行的机会。在选择下一个线程时,系统会考虑线程的优先级。运行和阻塞状态运行和阻塞状态当线程出现以下情况时,会进入阻塞状态:(1)调用sleep()方法,主动放弃所占用的处理器资源;(2)调用了一个阻塞式IO方法,在该方法返回之前,该线程被
614、阻塞;(3)线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有;(4)执行条件还未满足,调用wait()方法使线程进入等待状态,等待其他线程的通知;(5)程序调用了线程的suspend()方法将该线程挂起,但该方法容易导致死锁,因此应该尽量避免使用。12.3.12.3.2 2运行和阻塞状态正在执行的线程被阻塞之后,其他线程就可以获得执行的机会,被阻塞的线程会在合适的时机重新进入就绪状态,等待线程调度器再次调度。当线程出现如下几种情况时,线程可以解除阻塞进入就绪状态:(1)调用sleep()方法的线程经过了指定的时间;(2)线程调用的阻塞式IO方法以经返回;(3)线程成功地获得了同步监
615、视器;(4)线程处于等待状态,其他线程调用notify()或notifyAll()方法发出了一个通知时,则线程回到就绪状态;(5)处于挂起状态的线程被调用了resume()恢复方法。12.3.12.3.2 2运行和阻塞状态在线程运行的过程中,可以通过sleep()方法使线程暂时停止运行,进入休眠状态。在使用sleep()方法时需要注意以下两点:(1)sleep()方法的参数是以毫秒为基本单位,例如sleep(2000)则休眠2秒钟;(2)sleep()方法声明了InterruptedException异常,因此调用sleep()方法时要么放在trycatch语句中捕获该异常并处理,要么在方法后
616、使用throws显式声明抛出该异常。可以通过Thread类的isAlive()方法来判断线程是否处于运行状态。当线程处于就绪、运行和阻塞三种状态时,isAlive()方法的返回值为true;当线程处于新建、死亡两种状态时,isAlive()方法的返回值为false。下述案例示例了线程的创建、运行和死亡三个状态,代码如下所示。12.3.12.3.2 2运行和阻塞状态【代码12.7】ThreadLifeExample.javapackagecom;publicclassThreadLifeExampleextendsThreadpublicvoidrun()intsum=0;for(inti=0;
617、i=100;i+)sum+=i;System.out.println(sum=+sum);publicstaticvoidmain(Stringargs)throwsInterruptedExceptionThreadLifeExampletle=newThreadLifeExample();System.out.println(新建状态isAlive():+tle.isAlive();tle.start();System.out.println(运行状态isAlive():+tle.isAlive();Thread.sleep(2000);System.out.println(线程结束isA
618、live():+tle.isAlive();12.3.12.3.2 2运行和阻塞状态程序运行结果如下:新建状态isAlive():false运行状态isAlive():truesum=5050线程结束isAlive():false注意:线程调用wait()方法进入等待状态后,需其他线程调用notify()或notifyAll()方法发出通知才能进入就绪状态。使用suspend()和resume()方法可以挂起和唤醒线程,但这两个方法可能会导致不安全因素。如果对某个线程调用interrupt()方法发出中断请求,则该线程会根据线程状态抛出InterruptedException异常,对异常进行处
619、理时可以再次调度该线程。12.3.12.3.2 2死亡状态线程结束后就处于死亡状态,结束线程有以下三种方式:(1)线程执行完成run()或call()方法,线程正常结束;(2)线程抛出一个未捕获的Exception或Error;(3)调用stop()方法直接停止线程,该方法容易导致死锁,通常不推荐使用。死亡状态12.3.12.3.2 2死亡状态主线程结束时,其他子线程不受任何影响,并不会随主线程的结束而结束。一旦子线程启动起来,子线程就拥有和主线程相同的地位,子线程不会受主线程的影响。为了测试某个线程是否死亡,可以通过线程对象的isAlive()方法来获得线程状态,当方法返回值为false时,
620、线程处于死亡或新建状态。不要试图对一个已经死亡的线程调用start()方法使其重新启动,线程死亡就是死亡,该线程不可再次作为线程执行。Thread类中的join()方法可以让一个线程等待另一个线程完成后,继续执行原线程中的任务。当在某个程序执行流中调用其他线程的join()方法时,当前线程将被阻塞,直到另一个线程执行完为止。join()方法通常由使用线程的程序调用,当其他线程都执行结束后,再调用主线程进一步操作。下述案例示例了join()方法的使用,代码如下所示。12.3.12.3.2 2死亡状态【代码12.8】JoinExample.javapackagecom;classJoinThrea
621、dextendsThreadpublicJoinThread()super();publicJoinThread(Stringstr)super(str);publicvoidrun()for(inti=0;i10;i+)System.out.println(this.getName()+:+i);12.3.12.3.2 2死亡状态publicclassJoinExamplepublicstaticvoidmain(Stringargs)/创建子线程JoinThreadt1=newJoinThread(被Join的子线程);/启动子线程t1.start();/等待子线程执行完毕tryt1.jo
622、in();catch(InterruptedExceptione)/TODOAuto-generatedcatchblocke.printStackTrace();/输出主线程名System.out.println(主线程名为:+Thread.currentThread().getName();/子线程已经处于死亡状态,其isAlive()方法返回值为falseSystem.out.println(子线程死亡状态isAlive():+t1.isAlive();/再次启动子线程,抛出异常t1.start();12.3.12.3.2 2死亡状态上述代码中开始调用了线程的join()方法,最后又对死
623、亡状态的线程再次调用start()方法,运行结果如下所示:.被Join的子线程:8被Join的子线程:9主线程名为:main子线程死亡状态isAlive():falseExceptioninthreadmainjava.lang.IllegalThreadStateExceptionatjava.lang.Thread.start(UnknownSource)atcom.JoinExample.main(JoinExample.java:33)在上述代码中,注销掉join()方法的调用和对死亡状态线程的start()方法的再次调用,运行结果可能如下:主线程名为:main被Join的子线程:0被
624、Join的子线程:1子线程死亡状态isAlive():true被Join的子线程:2被Join的子线程:312.3.12.3.2 2第第4节节part线程的优先级每个线程执行时都具有一定的优先级,线程的优先级代表该线程的重要程度。当有多个线程同时处于可执行状态并等待获得CPU处理器时,系统将根据各个线程的优先级来调度各线程,优先级越高的线程获得CPU时间的机会越多,而优先级低的线程则获得较少的执行机会。每个线程都有默认的优先级,其优先级都与创建该线程的父线程的优先级相同。在默认情况下,主线程具有普通优先级,由主线程创建的子线程也具有普通优先级。Thread类提供三个静态常量来标识线程的优先级:
625、(1)MAX_PRIORITY:最高优先级,其值为10;(2)NORM_PRIORITY:普通优先级,其值为5;(3)MIN_PRIORITY:最低优先级,其值为1。线 程 的 优先级线程的优先级Thread类提供了setPriority()方法来对线程的优先级进行设置,而getPriority()方法来获取线程的优先级。setPriority()方法的参数是一个整数(110),也可以使用Thread类提供的三个优先级静态常量。线程的优先级高度依赖于操作系统,并不是所有的操作系统都支持Java的10个优先级,例如Windows2000仅提供7个优先级。因此,尽量避免直接使用整数给线程指定优先级
626、,提倡使用MAX_PRIORITY、NORM_PRIORITY和MIN_PRIORITY三个优先级静态常量。另外,优先级并不能保证线程的执行次序,因此应避免使用线程优先级作为构建任务执行顺序的标准。下述案例示例了线程优先级的设置及使用,代码如下所示。线 程 的 优先级【代码12.9】PriorityExample.javapackagecom;classMyPriorityThreadextendsThreadpublicMyPriorityThread()super();publicMyPriorityThread(Stringname)super(name);publicvoidrun()
627、for(inti=0;i10;i+)System.out.println(this.getName()+,其优先级是:+this.getPriority()+,循环变量的值为:+i);线 程 的 优先级publicclassPriorityExamplepublicstaticvoidmain(Stringargs)/输出主线程的优先级System.out.println(主线程的优先级:+Thread.currentThread().getPriority();/创建子线程,并设置不同优先级MyPriorityThreadt1=newMyPriorityThread(高级);t1.setPr
628、iority(Thread.MAX_PRIORITY);MyPriorityThreadt2=newMyPriorityThread(普通);t2.setPriority(Thread.NORM_PRIORITY);MyPriorityThreadt3=newMyPriorityThread(低级);t3.setPriority(Thread.MIN_PRIORITY);MyPriorityThreadt4=newMyPriorityThread(指定值级);t4.setPriority(4);/启动所有子线程t1.start();t2.start();t3.start();t4.start(
629、);线 程 的 优先级程序运行执行结果可能如下:主线程的优先级:5普通,其优先级是:5,循环变量的值为:0高级,其优先级是:10,循环变量的值为:0高级,其优先级是:10,循环变量的值为:1高级,其优先级是:10,循环变量的值为:2高级,其优先级是:10,循环变量的值为:3普通,其优先级是:5,循环变量的值为:1高级,其优先级是:10,循环变量的值为:4指定值,其优先级是:4,循环变量的值为:0.通过运行结果可以看出,优先级越高的线程提前获得执行机会就越多。线 程 的 优先级第第5节节part线程的同步多线程访问同一资源数据时,很容易出现线程安全问题。以多窗口出售车票为例,一旦多线程并发访问,
630、就可能出现问题,造成一票多售的现象。在Java中,提供了线程同步的概念以保证某个资源在某一时刻只能由一个线程访问,以此保证共享数据的一致性。Java使用监控器(也称对象锁)实现同步。每个对象都有一个监控器,使用监控器可以保证一次只允许一个线程执行对象的同步语句。即在对象的同步语句执行完毕前,其他试图执行当前对象的同步语句的线程都将处于阻塞状态,只有线程在当前对象的同步语句执行完毕后,监控器才会释放对象锁,并让优先级最高的阻塞线程处理同步语句。线程同步通常采用三种方式:同步代码块、同步方法和同步锁。线 程 的同步本节介绍12.5.12.5.1 1同步代码块使用同步代码块实现同步功能,只需将对实例
631、的访问语句放入一个同步块中,其语法格式如下:synchronized(object)/需要同步的代码块其中:synchronized是同步关键字;object是同步监视器,其数据类型不能是基本数据类型。线程开始执行同步代码之前,必须先获得同步监视器的锁定,并且,任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。下述案例示例了同步代码块的声明和使用,代码如下所示。同步代码块同步代码块【代码12.10】SynBlockExample.javapackagecom;/银行帐户类classBankAccount/银行账号privateStrin
632、gbankNo;/银行余额privatedoublebalance;/构造方法publicBankAccount(StringbankNo,doublebalance)this.bankNo=bankNo;this.balance=balance;publicStringgetBankNo()returnbankNo;publicvoidsetBankNo(StringbankNo)this.bankNo=bankNo;publicdoublegetBalance()returnbalance;publicvoidsetBalance(doublebalance)this.balance=ba
633、lance;12.5.12.5.1 1同步代码块publicclassSynBlockExampleextendsThread/银行账户privateBankAccountaccount;/操作金额,正数为存钱,负数为取钱privatedoublemoney;publicSynBlockExample(Stringname,BankAccountaccount,doublemoney)super(name);this.account=account;this.money=money;/线程任务publicvoidrun()synchronized(this.account)/获取目账户的金额d
634、oubled=this.account.getBalance();/如果操作的金额money0,则代表取钱操作,/同时判断账户金额是否低于取钱金额if(money0&d-money)System.out.println(this.getName()+操作失败,余额不足!);return;else12.5.12.5.1 1同步代码块/对账户金额进行操作d+=money;System.out.println(this.getName()+操作成功,目前账户余额为:+d);try/休眠10毫秒Thread.sleep(10);catch(InterruptedExceptione)e.printSt
635、ackTrace();/修改账户金额this.account.setBalance(d);publicstaticvoidmain(Stringargs)/创建一个银行账户实例BankAccountmyAccount=newBankAccount(101,5000);12.5.12.5.1 1同步代码块/创建多个线程,对账户进行存取钱操作SynBlockExamplet1=newSynBlockExample(T1,myAccount,-3000);SynBlockExamplet2=newSynBlockExample(T2,myAccount,-3000);SynBlockExamplet
636、3=newSynBlockExample(T3,myAccount,1000);/启动线程t1.start();t2.start();t3.start();/等待所有子线程完成tryt1.join();t2.join();t3.join();catch(InterruptedExceptione)e.printStackTrace();/输出账户信息System.out.println(账号:+myAccount.getBankNo()+,余额:+myAccount.getBalance();12.5.12.5.1 1同步代码块上述代码在run()方法中,使用“synchronized(thi
637、s.account)”对银行账户的操作代码进行同步,保证某一时刻只能有一个线程访问该账户,只有里面的代码执行完毕,才释放对该账户的锁定。程序运行结果如下所示:T1操作成功,目前账户余额为:2000.0T2操作失败,余额不足!T3操作成功,目前账户余额为:3000.0账号:101,余额:3000.012.5.12.5.1 112.5.12.5.2 2同步方法同步方法是使用synchronized关键字修饰的方法,其声明的语法格式如下:访问修饰符synchronized返回类型方法名(参数列表)/方法体其中:synchronized关键字修饰的实例方法无须显式地指定同步监视器,同步方法的同步监视器
638、是this,即该方法所属的对象。一旦一个线程进入一个实例的任何同步方法,其他线程将不能进入该实例的所有同步方法,但该实例的非同步方法仍然能够被调用。使用同步方法可以非常方便地实现线程安全,一个具有同步方法的类被称为“线程安全的类”,该类的对象可以被多个线程安全地访问,且每个线程调用该对象的方法后都将得到正确的结果。下述案例示例了同步方法的声明和使用,代码如下所示。同步方法同步方法【代码12.11】SynMethodExample.javapackagecom;/增加有同步方法的银行帐户类classSynMethod/银行账号privateStringbankNo;/银行余额privatedou
639、blebalance;/构造方法publicSynMethod(StringbankNo,doublebalance)this.bankNo=bankNo;this.balance=balance;/同步方法,存取钱操作publicsynchronizedvoidaccess(doublemoney)/如果操作的金额money0,则代表取钱操作,/同时判断账户金额是否低于取钱金额if(money0&balance-money)System.out.println(Thread.currentThread().getName()+操作失败,余额不足!);return;/返回else12.5.12
640、.5.2 2同步方法/对账户金额进行操作balance+=money;System.out.println(Thread.currentThread().getName()+操作成功,目前账户余额为:+balance);try/休眠1毫秒Thread.sleep(1);catch(InterruptedExceptione)e.printStackTrace();publicStringgetBankNo()returnbankNo;publicdoublegetBalance()returnbalance;12.5.12.5.2 2同步方法publicclassSynMethodExampl
641、eextendsThread/银行账户privateSynMethodaccount;/操作金额,正数为存钱,负数为取钱privatedoublemoney;publicSynMethodExample(Stringname,SynMethodaccount,doublemoney)super(name);this.account=account;this.money=money;/线程任务publicvoidrun()/调用account对象的同步方法this.account.access(money);publicstaticvoidmain(Stringargs)/创建一个银行账户实例S
642、ynMethodmyAccount=newSynMethod(1001,5000);/创建多个线程,对账户进行存取钱操作SynMethodExamplet1=newSynMethodExample(T1,myAccount,-3000);12.5.12.5.2 2同步方法SynMethodExamplet2=newSynMethodExample(T2,myAccount,-3000);SynMethodExamplet3=newSynMethodExample(T3,myAccount,1000);/启动线程t1.start();t2.start();t3.start();/等待所有子线程完
643、成tryt1.join();t2.join();t3.join();catch(InterruptedExceptione)e.printStackTrace();/输出账户信息System.out.println(账号:+myAccount.getBankNo()+,余额:+myAccount.getBalance();12.5.12.5.2 2同步方法程序运行结果如下:T1操作成功,目前账户余额为:2000.0T2操作失败,余额不足!T3操作成功,目前账户余额为:3000.0账号:1001,余额:3000.0注意:synchronized锁定的是对象,而不是方法或代码块;synchroni
644、zed也可以修饰类,当用synchronized修饰类时,表示这个类的所有方法都是synchronized的。12.5.12.5.2 212.5.12.5.3 3同 步 锁同步锁Lock是一种更强大的线程同步机制,通过显式定义同步锁对象来实现线程同步。同步锁提供了比同步代码块、同步方法更广泛的锁定操作,实现更灵活。Lock是控制多个线程对共享资源进行访问的工具,能够对共享资源进行独占访问。每次只能有一个线程对Lock对象加锁,线程访问共享资源之前需要先获得Lock对象。某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁)。Lock和ReadWriteLock是Java5提供
645、的关于锁的两个根接口,并为Lock提供了ReentrantLock(可重入锁)实现类,为ReadWriteLock提供了ReentrantReadWriteLock实现类。从Java8开始,又新增了StampedeLock类,可以替代传统的ReentrantReadWriteLock类。同步锁同 步 锁ReentrantLock类是常用的可重入同步锁,该类对象可以显式地加锁、释放锁。使用ReentrantLock类的步骤如下:(1)定义一个ReentrantLock锁对象,该对象是final常量;privatefinalReentrantLocklock=newReentrantLock();
646、(2)在需要保证线程安全的代码之前增加“加锁”操作;lock.lock();(3)在执行完线程安全的代码后“释放锁”。lock.unlock();12.5.12.5.3 3同 步 锁下述代码示例了使用ReentrantLock锁的基本步骤:/1.定义锁对象privatefinalReentrantLocklock=newReentrantLock();./定义需要保证线程安全的方法publicvoidmyMethod()/2.加锁lock.lock();try/需要保证线程安全的代码.finally/3.释放锁lock.unlock();其中:加锁和释放锁都需要放在线程安全的方法中;lock.
647、unlock()放在finally语句中,不管发生异常与否,都需要释放锁。12.5.12.5.3 3同 步 锁下述案例示例了ReentrantLock同步锁的使用,代码如下所示。【代码12.12】SynLockExample.javapackagecom;importjava.util.concurrent.locks.ReentrantLock;classSynLockprivateStringbankNo;/银行账号privatedoublebalance;/银行余额/定义锁对象privatefinalReentrantLocklock=newReentrantLock();/构造方法pu
648、blicSynLock(StringbankNo,doublebalance)this.bankNo=bankNo;this.balance=balance;/存取钱操作publicvoidaccess(doublemoney)/加锁lock.lock();try/如果操作的金额money0,则代表取钱操作,12.5.12.5.3 3同 步 锁/同时判断账户金额是否低于取钱金额if(money0&balance-money)System.out.println(Thread.currentThread().getName()+操作失败,余额不足!);/返回return;else/对账户金额进行
649、操作balance+=money;System.out.println(Thread.currentThread().getName()+操作成功,目前账户余额为:+balance);try/休眠1毫秒Thread.sleep(1);catch(InterruptedExceptione)e.printStackTrace();finally12.5.12.5.3 3同 步 锁/释放锁lock.unlock();publicStringgetBankNo()returnbankNo;publicdoublegetBalance()returnbalance;12.5.12.5.3 3同 步 锁
650、/使用同步锁的类publicclassSynLockExampleextendsThread/银行账户privateSynLockaccount;/操作金额,正数为存钱,负数为取钱privatedoublemoney;publicSynLockExample(Stringname,SynLockaccount,doublemoney)super(name);this.account=account;this.money=money;/线程任务publicvoidrun()/调用account对象的access()方法this.account.access(money);publicstatic
651、voidmain(Stringargs)/创建一个银行账户实例SynLockmyAccount=newSynLock(1001,5000);/创建多个线程,对账户进行存取钱操作12.5.12.5.3 3同 步 锁SynLockExamplet1=newSynLockExample(T1,myAccount,-3000);SynLockExamplet2=newSynLockExample(T2,myAccount,-3000);SynLockExamplet3=newSynLockExample(T3,myAccount,1000);/启动线程t1.start();t2.start();t3.
652、start();/等待所有子线程完成tryt1.join();t2.join();t3.join();catch(InterruptedExceptione)e.printStackTrace();/输出账户信息System.out.println(账号:+myAccount.getBankNo()+,余额:+myAccount.getBalance();12.5.12.5.3 3同 步 锁程序运行结果如下:T1操作成功,目前账户余额为:2000.0T2操作失败,余额不足!T3操作成功,目前账户余额为:3000.0账号:1001,余额:3000.012.5.12.5.3 3第第6节节part线
653、程通信线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行。在Java中提供了一些机制来保证线程之间的协调运行,这就是所谓的线程通信。线程通信可以使用Object类中定义的wait()、notify()和notifyAll()方法,使线程之间相互进行事件通知。在执行这些方法时,必须同时拥有相关对象的锁。(1)wait()方法:让当前线程等待,并释放对象锁,直到其他线程调用该监视器的notify()或notifyAll()方法来唤醒该线程。wait()方法也可以带一个参数,用于指明等待的时间,使用这种方式不需要notify()或notifyAll()方法来唤醒。wa
654、it()方法只能在同步方法中调用。(2)notify()方法:唤醒在此同步监视器上等待的单个线程,解除该线程的阻塞状态。(3)notifyAll()方法:唤醒在此同步监视器上等待的所有线程,唤醒次序完全由系统来控制。notify()方法和notifyAll()方法,只能在同步方法或同步块中使用。wait()方法区别于sleep()方法的是:wait()方法调用时会释放对象锁,而sleep()方法不会。下述案例通过生产/消费模型示例了线程通信机制的使用,代码如下所示。线程通信线程通信【代码12.13】WaitNotifyExample.javapackagecom;/产品类classProduc
655、tintn;/为true时表示有值可取,为false时表示需要放入新值booleanvalueSet=false;/生产方法synchronizedvoidput(intn)/如果没有值,等待线程取值if(valueSet)trywait();catch(Exceptione)this.n=n;/将valueSet设置为true,表示值已放入valueSet=true;System.out.println(Thread.currentThread().getName()+-生产:+n);线程通信/通知等待线程,进行取值操作notify();/消费方法synchronizedvoidget()/
656、如果没有值,等待新值放入if(!valueSet)trywait();catch(Exceptione)System.out.println(Thread.currentThread().getName()+-消费:+n);/将valueSet设置为false,表示值已取valueSet=false;/通知等待线程,放入新值notify();线程通信/生产者类classProducerimplementsRunnableProductproduct;Producer(Productproduct)this.product=product;newThread(this,Producer).sta
657、rt();publicvoidrun()intk=0;/生产10次for(inti=0;i10;i+)product.put(k+);线程通信/消费者类classConsumerimplementsRunnableProductproduct;Consumer(Productproduct)this.product=product;newThread(this,Consumer).start();publicvoidrun()/消费10次for(inti=0;i10;i+)product.get();线程通信publicclassWaitNotifyExamplepublicstaticvoi
658、dmain(Stringargs)/实例化一个产品对象,生产者和消费者共享该实例Productproduct=newProduct();/指定生产线程Producerproducer=newProducer(product);/指定消费线程Consumerconsumer=newConsumer(product);线程通信上述代码描述了典型的生产/消费模型,其中Product类是资源类,用于为生产者和消费者提供资源;Producer类是生产者,产生队列输入;Consumer类是消费者,从队列中取值。在定义Product类时,使用synchronized修饰put()和get()方法,确保当前实
659、例在某一时刻只有一种状态:要么生产,要么消费;在put()和get()方法内部,通过信号量valueSet的取值,利用wait()和notify()方法的配合实现线程间的通信,确保生产和消费的相互依赖关系。在main()方法中,创建一个Product类型的实例,并将该实例传入生产线程和消费线程中,使两国线程在产生“资源竞争”的情况下,保存良好的生产消费关系。程序执行结果如下:Producer-生产:0Consumer-消费:0Producer-生产:1Consumer-消费:1.Producer-生产:9Consumer-消费:9线程通信第第7节节partTimer定时器Java提供了Time
660、r定时器类,用于执行规划好的任务或循环任务,即每隔一定的时间执行特定的任务。使用java.util.Timer类非常容易,具体步骤如下:(1)定义一个类继承TimerTask。TimerTask类中有一个run()方法,用于定义Timer所要执行的任务代码。(2)创建Timer对象,通常使用不带参数的构造方法Timer()直接实例化。(3)调用Timer对象的schedule()方法安排任务,传递一个TimerTask对象作为参数,即第(1)步中定义类的实例。(4)为了取消一个规划好的任务,则调用Timer对象的cancel()方法。Timer定 时器Timer定时器Timer类的schedu
661、le()方法常用以下几种重载方式:(1)schedule(TimerTasktask,Datetime),在指定的时间执行特定任务;(2)schedule(TimerTasktask,DatefirstTime,longperiod),第一次到达指定时间firstTime时执行特定任务,并且每隔period参数指定的时间(毫秒)重复执行该任务;(3)schedule(TimerTasktask,longdelay,longperiod),延迟delay参数所指定的时间(毫秒)后,第一次执行特定任务,并且每隔period参数指定的时间(毫秒)重复执行该任务。下面案例示例了Timer定时器类的使用
662、,代码如下所示。Timer定 时器【代码12.14】TimerExample.javapackagecom;importjava.util.Timer;importjava.util.TimerTask;publicclassTimerExample/声明一个TimerTimert;publicTimerExample()/2.实例化Timer对象t=newTimer();/3.调用schedule()方法,执行任务t.schedule(newMyTask(),0,1000);/1.定义一个内部类,继承TimerTaskclassMyTaskextendsTimerTask/任务方法publi
663、cvoidrun()System.out.println(Timer定时器的使用);Timer定 时器publicstaticvoidmain(Stringargs)TimerExamplef=newTimerExample();程序运行每一秒钟打印一句“Timer定时器的使用”,结果如下:Timer定时器的使用Timer定时器的使用本章课后作业见教材JAVA程 序 设计本章学习目标:掌握JAVA容器类JFrame和JPanel的使用掌握AWT和Swing常用布局的使用了解JAVA事件处理机制掌握常用可视化组件的使用第十三章第十三章SwingUI设计设计第第1节节partWindowBuild
664、er插件WindowBuilder是一款基于Eclipse平台的插件,具备SWT/JFACE、Swing和GWT三大功能,可以对JavaGUI进行双向设计。WindowBuilder是一款不可多得的Java体系中的“所见即所得”开发工具。WindowBuilder插件本节概述WindowBuilder插件是基于Eclipse的,安装前需要JDK开发环境和Eclipse开发工具。在Eclipse官方网站提供了WindowBuilder插件的下载即安装说明,地址如下:http:/www.eclipse.org/WindowBuilder/download.php目前WindowBuilder插件支
665、持Eclipse的Juno、Kepler、Luna和Mars版本,如图13.1所示,每个版本又分为发行版(ReleaseVersion)和整合版(IntegrationVersion)。13.1.13.1.1 1WindowBuilder插件安装WindowBuilder插件安装在Eclipse中安装WindowBuilder插件有以下两种方式进行安装。(1)在线安装:在图13.1中,单击表格中的ReleaseVersionUpdateSite4.4(Luna)所对应的link,进入在线安装界面,浏览器地址栏中的地址即为在线安装地址。(2)离线安装:点击ReleaseVersionZipped
666、UpdateSite4.4(Luna)所对应的link(MD5Hash),下载WindowBuilder插件的离线安装包。WindowBuilder插件安装13.1.13.1.1 11.在线安装方式在线安装方式通过在线方式安装Eclipse的WindowBuilder插件的步骤如下所示:【步骤一】代开Eclipse集成开发工具,在Help菜单中选择InstallNewSoftCWare命令,如图13.2所示。进入安装界面,如图13.3所示。WindowBuilder插件安装13.1.13.1.1 1【步骤二】单击Add按钮,打开站点添加界面,如图13.4所示,添加WindowBuilder插件
667、的在线安装地址为:http:/download.eclipse.org/windowbuilder/WB/release/R201506241200-1/4.4/WindowBuilder插件安装13.1.13.1.1 1【步骤三】单击OK按钮,返回安装主界面,如图13.5所示。选择SwingDesigner、SWTDesigner和WindowBuilderEngine选项后,单击Next按钮,进入安装细节界面,如图13.6所示。WindowBuilder插件安装13.1.13.1.1 1【步骤四】单击Next按钮,进入协议许可界面,如图13.7所示。单击Finish按钮,进入WindowB
668、uilder插件安装界面,如图13.8所示。安装完成后,重新启动Eclipse开发工具即可。WindowBuilder插件安装13.1.13.1.1 12.离线安装方式离线安装方式下载WindowBuilder插件的离线安装包后,可以通过离线方式进行安装,具体步骤与在线安装相同,只是在步骤一单击Add按钮后,进入本地资源界面,与步骤二界面相同。其中Local按钮用于选取本地文件夹,Archive按钮用于选取本地jar或zip类型的压缩文件。输入本地资源名称,选取下载本地的离线包后,单击OK按钮返回安装主界面。WindowBuilder插件安装13.1.13.1.1 1在Eclipse中的Jav
669、a项目中,单击FileNewOther菜单命令,通过向导方式创建一个JFrame窗体,如图13.9所示。选择JFrame选项,单击Next按钮进入常见JFrame对话框。13.1.13.1.2 2WindowBuilder插件的使用过程WindowBuilder插件的使用过程在创建JFrame对话框中,输入类名LoginFrame,单击Finish按钮即完成JFrame窗体的创建,如图13.10所示。WindowBuilder插件的使用过程13.1.13.1.2 2在代码编辑窗口,如图13.11所示,单击左下角的Source和Design选项卡(或按F12快捷键)可以在源代码和设计界面之间进行
670、切换。WindowBuilder插件的使用过程13.1.13.1.2 2源代码窗口可以直接编写Java代码;而界面设计窗口可以通过拖拽控件实现窗体的设计;界面设计窗口主要有结构窗口、属性窗口、工具窗口、控件窗口和设计窗口五部分组成,如图13.12所示。WindowBuilder插件的使用过程13.1.13.1.2 2在结构窗口中,可以将当前JFrame窗体中的控件以树状结构显示出来;当选取某一控件时,设计窗口中相应的元素处于被选中状态。控件窗口中包含System、Containers、Layouts、Strust&Sping、Components、SwingActions、Menu、AWTCo
671、mponents和JGoodies等组件,通过拖拽的方式可以快速添加到设计窗口中。当在设计窗口中选取某一控件时,属性窗口相应地发生改变,通过可视化界面可以快速设置该控件的相关属性。在属性窗口中,单击事件切换按钮可以在属性列表和事件列表之间进行切换。在设计界面中先选中某一控件,再在属性窗口的事件列表中找到所需的事件,通过双击的方式可以为该控件添加相应的事件处理。WindowBuilder插件的使用过程13.1.13.1.2 2第第2节节partGUI概述用户喜欢功能丰富、操作简单且直观的应用程序。为了提高用户体验度,使系统的交互性和操作性更好,大多数应用程序都采用图形用户界面(Graphical
672、UserInterface,GUI))的形式。Java中提供了AWT(AbstractWindowToolkit,抽象窗口工具包)和Swing来实现GUI图形用户界面编程。GUI概述本节概述13.2.13.2.1 1A W T和S w i n g在JDK1.0发布时,Sun公司提供了一套基本的GUI类库,这套基本类库被称为AWT。AWT为Java程序提供了基本的图形组件,实现一些基本的功能,并希望在所有平台上都能运行。使用AWT提供的组件所构建的GUI应用程序具有以下几个问题:(1)使用AWT做出的图形用户界面在所有的平台上都显得很丑陋,功能也非常有限;(2)运行在不同的平台上,呈现不同的外观
673、效果,为保证界面的一致性和可预见性,程序员需要在不同平台上进行测试;(3)AWT为了迎合所有主流操作系统的界面设计,AWT组件只能使用这些操作系统上图形界面组件的交集,所以不能使用特定操作系统上复杂的图形界面组件,最多只能使用四种字体;(4)编程模式非常笨拙,并且是非面向对象的编程模式。AWT和SwingA W T和S w i n g在1996年,Netscape公司开发了一套工作方式完全不同的GUI库,被称为IFC(InternetFoundationClass)。IFC除了窗口本身需要借助操作系统的窗口来实现,其他组件都是绘制在空白窗口中。IFC能够真正地实现各平台界面的一致性,Sun公司
674、与Netscape公司合作完善了这种方案,并创建了一套新的用户界面库,并命名为Swing。Swing组件完全采用Java语言编程,不再需要使用那些平台所用的复杂的GUI功能,因此,使用Swing构建的GUI应用程序在不同平台上运行时,所显示的外观效果完全相同。AWT、Swing、2DAPI、辅助功能API以及拖放API共同组成了JFC(JavaFoundationClass,Java基础类库),其中Swing全面替代了Java1.0中的AWT组件,但保留了Java1.1中的AWT事件模型。总体上,Swing替代了绝大部分AWT组件,但并没有完全替代AWT,而是在AWT的基础之上,进行了有力的补
675、充和加强。13.2.13.2.1 1A W T和S w i n g使用Swing组件进行GUI编程的优势有以下几点:(1)Swing用户界面组件丰富,使用便捷;(2)Swing组件对底层平台的依赖少,与平台相关的Bug也很少;(3)能够保证不同平台上用户一致的感观效果;(4)Swing组件采用MVC(Model-View-Controller,即模型-视图-控制器)设计模式,其中模型用于维护组件的各种状态,视图是组件的可视化表现。控制器用于控制对于各种事件,组件做出怎样的响应。13.2.13.2.1 1Swing组 件层次大部分Swing组件都是JComponent抽象类的直接或间接子类,在J
676、Component抽象类中定义了所有子类组件的通用方法。JComponent类位于javax.swing包中,javax包是一个Java扩展包。要有效地使用GUI组件,必须理解javax.swing和java.awt包中组件之间的继承层次,尤其是理解Component类、Container类和JComponent类,其中声明了大多数Swing组件的通用特性。Swing中的JComponent类是AWT中java.awt.Container类的子类,也是Swing和AWT的联系之一。JComponent类的继承层次如图13.13所示:JComponent类是Container的子类;Contai
677、ner类是Component类的子类;而Component类又是Object类的子类。Swing组件层次13.2.13.2.1 1Swing组 件层次绝大部分的Swing组件位于javax.swing包中,且继承Container类。Swing组件按功能进行划分,可以分为以下几类:顶层容器:JFrame、JApplet、JDialog和JWindow。中间容器:JPanel、JScrollPane、JSplitPane和JToolBar等。特殊容器:在用户界面上具有特殊作用的中间容器,如JInternalFrame、JRootPane、JLayeredPane和JDestopPane等。基本组
678、件:实现人机交互的组件,如JButton、JComboBox、JList、JMenu和JSlider等。特殊对话框组件:直接产生特殊对话框的组件,如JColorChooser和JFileChooser等。不可编辑信息的显示组件:向用户显示不可编辑信息的组件,如JLable、JProgressBar和JToolTip等。可编辑信息的显示组件:向用户显示能被编辑的格式化信息的组件,如JTextField、JTextArea和JTable等。13.2.13.2.1 1第第3节节part容器与布局容器用来存放其他组件,而容器本身也是一个组件,属于Component的子类。布局管理器用来管理组件在容器中
679、的布局格式,当容器中容纳多个组件时,可以使用布局管理器对这些组件进行排列和分布。容 器 与布局本节概述13.3.13.3.1 1JFrame顶 级容器JFrame(窗口框架)是可以独立存在的顶级窗口容器,能够包含其他子容器,但不能被其他容器所包含。JFrame类常用的构造方法有以下两种:JFrame():不带参数的构造方法,该方法用于创建一个初始不可见的新窗体。JFrame(Stringtitle):带一个字符串参数的构造方法,该方法用于创建一个初始不可见的新窗体,且窗口的标题有字符串参数指定。JFrame类常用的方法及功能如表13-1所示。Jframe顶级容器JPanel中 间容器JPane
680、l(面板)是一种中间容器,中间容器与顶级容器不同,不能独立存在,必须放在其他容器中。JPanel中间容器的意义在于为其他组件提供空间。在使用JPanel时,通常先将其他组件添加到JPanel中间容器中,再将JPanel中间容器添加到JFrame顶级容器中。JPanel类常用的构造方法有以下两种:JPanel():不带参数的构造方法,该方法用于创建一个默认为流布局(FlowLayout)的面板。JPanel(LayoutManagerlayout):带参数的构造方法,参数是一个布局管理器,用于创建一个指定布局的面板。JPanel类的常用方法即功能如表13-2所示。Jpanel中间容器13.3.1
681、3.3.2 213.3.13.3.3 3BorderL ayout边界布局BorderLayout边界布局允许将组件有选择地放置到容器的东部、南部、西部、北部、中部这五个区域,如图13.14所示。BorderLayout边界布局BorderLayout类的构造方法如下:BorderLayout():不带参数的构造方法,用于创建一个无间距的边界布局管理器对象。BorderLayout(inthgap,intvgap):带参数的构造方法,用于创建一个指定水平、垂直间距的边界布局管理器。BorderLayout类提供了五个静态常量,用于指定边界布局管理中的五个区域:BorderLayout.EAST
682、指定东部位置;指定东部位置;BorderLayout.WEST指定西部位置;BorderLayout.SOUTH指定南部位置;BorderLayout.NORTH指定北部位置;BorderLayout.CENTER指定中部位置,该位置属于默认位置。BorderL ayout边界布局一个容器使用BorderLayout边界布局后,向容器中添加组件时,需要使用带两个参数的add()方法,将指定组件添加到此容器的给定位置上。基本语法如下:publicComponentadd(Componentcomp,intindex);例如:p.add(newJButton(“西部”),BorderLayout.
683、WEST);当使用BorderLayout布局时,需要注意以下两点:当向使用BorderLayout布局的容器中添加组件时,需要指定组件所放置的区域位置,如果没有指定则默认放置到布局的中央位置。通常一个区域位置只能添加一个组件,如果同一个区域中添加多个组件,则后放入的组件将会覆盖先放入的组件。BorderLayout边界布局是窗体(JFrame)的默认布局。当容器采用边界布局时,改变窗体的大小,可以发现东西南北四个位置上的组件长度进行拉伸,而中间位置的组件进行扩展。13.3.13.3.3 313.3.13.3.4 4FlowL ayout流布局FlowLayout流布局是将容器中的组件按照从中
684、间到两边的顺序,流动地排列和分布,直到上方的空间被占满,才移到下一行,继续从中间到两边流动排列。FlowLayout类的构造方法有如下三个:FlowLayout():不带参数的构造方法,使用默认对齐方式(中间对齐)和默认间距(水平、垂直间距都为5像素)创建一个新的流布局管理器。FlowLayout(intalign):带有对齐方式参数的构造方法,用于创建一个指定对齐,默认间距为5像素的流布局管理器。FlowLayout(intalign,inthgap,intvgap):带有对齐方式、水平间距、垂直间距参数的构造方法,用于创建一个指定对齐方式、水平间距、垂直间距的流布局管理器。FlowLayo
685、ut类提供了三个静态常量,用于指明布局的对齐方式,这三个常量分别是:FlowLayout.CENTER为居中对齐,也是默认对齐方式;FlowLayout.LEFT为左对齐方式;FlowLayout.RIGHT为右对齐方式。FlowLayout流布局13.3.13.3.5 5GridL ayout网 格布局GridLayout网格布局就像表格一样,将容器按照行和列分割成单元格,每个单元格所占的区域大小都一样。当向GridLayout布局的容器中添加组件时,默认是按照从左到右、从上到下的顺序,依次将组件添加到每个网格中。与FlowLayout不同,放置在GridLayout布局中的各组件的大小由所
686、处区域来决定,即每个组件将自动占满整个区域。GridLayout类提供了两个构造方法如下:GridLayout(introws,intcols):用于创建一个指定行数和列数的网格布局管理器。GridLayout(introws,intcols,inthgap,intvgap):用于创建一个指定行数、列数、水平间距和垂直间距的网格布局管理器。GridLayout网格布局13.3.13.3.6 6CardLayout卡 片布局CardLayout卡片布局将加入到容器中的组件看成一叠卡片,每次只能看见最上面的组件。因此,CardLayout卡片布局是以时间而非空间来管理容器中的组件。CardLayo
687、ut类提供了两个构造方法如下:CardLayout():不带参数的构造方法,用于创建一个默认间距为0的新卡片布局管理器。CardLayout(inthgap,intvgap):带参数的构造方法,用于创建一个指定水平和垂直间距的卡片布局管理器。CardLayout类中用于控制组件可见5个常用方法如表13-3所示。CardLayout卡片布局一个容器使用CardLayout卡片布局后,当向容器中添加组件时,需要使用带两个参数的add()方法,给组件指定一个名称并将其添加到容器中。13.3.13.3.7 7NULL空布局在实际开发过程中,用户界面比较复杂,而且要求美观,单一使用一种布局管理器很难满足
688、要求。此时,可以采用NULL空布局。空布局是指容器不采用任何布局,而是通过每个组件的绝对定位进行布局。使用空布局的步骤如下:(1)将容器中的布局管理器设置为null(空),即容器中不采用任何布局。例如:/设置面板对象的布局为空p.setLayout(null);(2)调用setBounds()设置组件的绝对位置坐标及大小,或使用setLocation()方法和setSize()方法分别设置组件的坐标和大小。例如:/设置按钮x轴坐标为30,y轴坐标为60,宽度40,高度25(像素)btn.setBounds(30,60,40,25);(3)将组件添加到容器中。例如:/将按钮添加到面板中p.add
689、(b);NULL空布局NULL空布局【代码13.1】NullLayoutExample.javapackagelayout;importjava.awt.EventQueue;importjavax.swing.*;importjavax.swing.border.EmptyBorder;publicclassNullLayoutExampleextendsJFrame/创建面板对象privateJPanelcontentPane;publicstaticvoidmain(Stringargs)EventQueue.invokeLater(newRunnable()publicvoidrun(
690、)tryNullLayoutExampleframe=newNullLayoutExample();frame.setVisible(true);/设置窗口可见性catch(Exceptione)e.printStackTrace(););13.3.13.3.7 7NULL空布局publicNullLayoutExample()/设定窗口默认关闭方式为退出应用程序setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);/设置窗口坐标和大小setBounds(100,100,450,300);contentPane=newJPanel();/设置面板边界co
691、ntentPane.setBorder(newEmptyBorder(5,5,5,5);setContentPane(contentPane);/设置面板布局contentPane.setLayout(null);JButtonbtn_OK=newJButton(确认);/这种按钮的绝对位置btn_OK.setBounds(68,119,93,23);/添加按钮到面板中contentPane.add(btn_OK);JButtonbtn_Cancel=newJButton(取消);btn_Cancel.setBounds(214,119,93,23);contentPane.add(btn_C
692、ancel);13.3.13.3.7 7NULL空布局程序运行结果如图13.15所示。NULL空布局一般用于组件之间位置相对固定,并且窗口不允许随便变换大小的情况,否则当窗口大小发生变化,因所有组件都使用绝对位置定位,而产生组件整体“偏移”的情况。第第4节节part基本组件GUI图形界面时由一些基本的组件在布局管理器的统一控制下组合而成的,常用的基本组件包括图标、按钮、标签、文本组件、列表框、单选按钮、复选框和组合框等。基 本 组件本节概述13.4.13.4.1 1Icon图标Icon是一个图标接口,用于加载图片。ImageIcon类是Icon接口的一个实现类,用于加载指定的图片文件,通常价值
693、的图片文件为gif、jpg、png等格式。ImageIcon类常用的构造方法如下:ImageIcon():创建一个未初始化的图标对象。ImageIcon(Imageimage):根据图像创建图标对象。ImageIcon(Stringfilename):根据指定的图片文件创建图标对象。ImageIcon类常用的方法如表13-4所示。Icon图标Icon图标在Eclipse项目中,当使用到图片文件时,通常先将图片文件复制到自定义的一个文件目录中,如图13.16所示,将图片复制到images目录下。下述案例示例了ImageIcon类的使用,代码如下所示。13.4.13.4.1 1Icon图标【代码1
694、3.2】IconExample.java.publicvoidpaint(Graphicsg)/创建ImageIcon图标ImageIconxscIcon=newImageIcon(“imagesxsc.PNG”);/在窗体中画图标g.drawImage(xscIcon.getImage(),20,40,this);/显示图标的宽度和高度g.drawString(“图标宽:”+xscIcon.getIconWidth()+“px,高:”+xscIcon.getIconHeight()+“px”,20,320);上述程序中对JFrame窗口的paint()方法进行重写,实现在窗口中绘制图片和字符
695、串。图片文件名中包含路径,其中“”是转义字符,代表“”。程序运行结果如图13.17所示。13.4.13.4.1 113.4.13.4.2 2JB utton按钮JButton类提供一个可接受单击操作的按钮功能,单击按钮会使其处于“下压”形状,松开后按钮又会恢复原状。在按钮中可以显示字符串、图标或两者同时显示。JButton类的构造方法如下:JButton(Stringstr):用于创建一个指定文本的按钮对象。JButton(Iconicon):用于创建一个指定图标的按钮对象。JButton(Stringstr,Iconicon):该构造方法带有字符串和图标两个参数,用于创建一个指定文本和图标的
696、按钮对象。JButton类常用的方法如表13-5所示。JButton按钮13.4.13.4.3 3Jlabel标签JLabel标签具有标识和提示的作用,可以显示文字或图标。标签没有边界,也不会响应用户操作,即单击标签是没有反应的。在GUI编程中,标签通常放在文本框、文本框、组合框等不带标签的组件前,对用户进行提示。JLabel类的构造方法如下:JLabel(Stringtext):用于创建一个指定文本的标签对象。JLabel(Iconicon):用于创建一个指定图标的标签对象。JLabel(Stringtext,Iconicon,inthorizontalAlignment):用于创建一个指定
697、文本、图标和对齐方式的标签对象。JLabel类的常用方法如表13-6所示。Jlabel标签13.4.13.4.4 4文本组件文本组件可以接收用户输入的文本内容。Swing常用的文本组件有以下三种:JTextField:文本框,该组件只能接收单行的文本输入。JTextArea:文本域,该组件可以接收多行的文本输入。JPasswordField:密码框,不显示原始字符,用于接收用户输入的密码。JTextField类常用的构造方法及其常用方法如表13-7所示。文本组件文本组件JTextArea文本域组件可以编辑多行多列文本,且具有换行能力。JTextArea类常用的构造方法及其常用方法如表13-8所
698、示。13.4.13.4.4 4文本组件JPasswordField是JTextField类的子类,允许编辑单行文本,密码框用于接收用户输入的密码,但不显示原始字符,而是以特殊符号(掩码)形式显示。JPasswordField类常用的构造方法及其常用方法如表13-9所示。13.4.13.4.4 413.4.13.4.5 5JComboB ox组合框JComboBox组合框是一个文本框和下拉列表的组合,用户可以从下拉列表选项中选择一个选项。JComboBox类常用的构造方法如下:JComboBox():不带参数的构造方法,用于创建一个没有选项的组合框。JComboBox(ObjectlistDat
699、a):构造方法的参数是对象数组,用于创建一个选项列表为对象数组中的元素的组合框。JComboBox(VectorlistData):构造方法的参数是泛型向量,用于创建一个选项列表为向量集合中的元素的组合框。JComboxBox组合框JComboB ox组合框JComboBox类常用的构造方法及其常用方法如表13-10所示。13.4.13.4.5 513.4.13.4.6 6JList列 表框JList列表框中的选项以列表的形式都显示出来,用户在列表框中可以选择一个或多个选项(按住Ctrl键才能实现多选)。JList类常用的构造方法如下:JList():不带参数的构造方法,用于创建一个没有选项的
700、列表框。JList(ObjectlistData):参数是对象数组的构造方法,用于创建一个选项列表为对象数组中的元素的列表框。JList(VectorlistData):参数是泛型向量的构造方法,用于创建一个选项列表为向量集合中的元素的列表框。Jlist列表框JList列 表框JList类常用的常用方法如表13-11所示。13.4.13.4.6 613.4.13.4.7 7JRadioButton单 选按钮JRadioButton单选按钮可被选择或被取消选择。JRadioButton类常用的构造方法如下:JRadioButton(Stringstr):用于创建一个具有指定文本的单选按钮。JRa
701、dioButton(Stringstr,booleanstate):创建一个具有指定文本和选择状态的单选按钮,当选择状态为true时,表示单选按钮被选中,状态未false时表示未被选中。JRadioButton类常用的常用方法如表13-12所示。JRadioButton单选按钮JRadioButton单 选按钮单选按钮一般成组出现,且需与ButtonGroup按钮组配合使用后,才能实现单选规则,即一次只能选择按钮组中的一个按钮。因此,使用单选按钮要经过以下两个步骤:(1)先实例化所有的JRadioButton单选按钮对象;(2)创建一个ButtonGroup按钮组对象,并用其add()方法将所
702、有的单选按钮对象添加到该组中,实现单选规则。例如:/创建单选按钮JRadioButtonrbMale=newJRadioButton(男,true);JRadioButtonrbFemale=newJRadioButton(女);/创建按钮组ButtonGroupbg=newButtonGroup();/将rb1和rb2两个单选按钮添加到按钮组中,这两个单选按钮只能选中其一bg.add(rbMale);bg.add(rbFemale);13.4.13.4.7 713.4.13.4.8 8JCheckBox复 选框JCheckBox复选框可以控制选项的开启或关闭,在复选框上单击时,而言改变复选框
703、的状态,复选框可以被单独使用或作为一组使用。JCheckBox类常用的构造方法如下:JCheckBox(Stringstr):创建一个带文本的、最初未被选定的复选框。JCheckBox(Stringstr,booleanstate):创建一个带文本的复选框,并指定其最初是否处于选定状态。JCheckBox类常用的方法如表13-13所示。JCheckBox复选框13.4.13.4.9 9用 户 注 册界面JCheckBox复选框可以控制选项的开启或关闭,在复选框上单击时,而言改变复选框的状态,复选框可以被单独使用或作为一组使用。JCheckBox类常用的构造方法如下:JCheckBox(Stri
704、ngstr):创建一个带文本的、最初未被选定的复选框。JCheckBox(Stringstr,booleanstate):创建一个带文本的复选框,并指定其最初是否处于选定状态。JCheckBox类常用的方法如表13-13所示。用户注册界面用 户 注 册界面下述案例实现了用户注册界面的设计和信息的获取,代码如下所示。【代码13.3】RegisterFrame.javapackagecom;importjava.awt.EventQueue;importjavax.swing.*;importjavax.swing.ButtonGroup;importjavax.swing.border.Empt
705、yBorder;importjava.awt.event.ActionListener;importjava.awt.event.ActionEvent;importjava.awt.Color;importjava.awt.Font;publicclassRegisterFrameextendsJFrameprivateJPanelcontentPane;privateJTextFieldnameText;privateJPasswordFieldpassTest;privateJPasswordFieldrePassText;privateJTextFieldidText;13.4.13.
706、4.9 9用 户 注 册界面publicstaticvoidmain(Stringargs)EventQueue.invokeLater(newRunnable()publicvoidrun()tryRegisterFrameframe=newRegisterFrame();frame.setVisible(true);catch(Exceptione)e.printStackTrace(););13.4.13.4.9 9用 户 注 册界面publicRegisterFrame()setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setBounds(
707、100,100,510,475);contentPane=newJPanel();contentPane.setBorder(newEmptyBorder(5,5,5,5);setContentPane(contentPane);contentPane.setLayout(null);JLabellabel_8=newJLabel(用户注册界面设计);label_8.setHorizontalAlignment(SwingConstants.CENTER);label_8.setForeground(Color.RED);label_8.setFont(newFont(宋体,Font.PLAI
708、N,18);label_8.setBounds(146,10,177,23);contentPane.add(label_8);JLabelnamelabel=newJLabel(用户名:);namelabel.setBounds(40,57,54,15);contentPane.add(namelabel);13.4.13.4.9 9用 户 注 册界面nameText=newJTextField();nameText.setBounds(100,54,100,21);contentPane.add(nameText);nameText.setColumns(10);JLabelidlabel
709、=newJLabel(学号:);idlabel.setBounds(242,57,54,15);contentPane.add(idlabel);idText=newJTextField();idText.setBounds(311,54,114,21);contentPane.add(idText);idText.setColumns(10);JLabellabel_6=newJLabel(密码:);label_6.setBounds(40,96,54,15);contentPane.add(label_6);13.4.13.4.9 9用 户 注 册界面passTest=newJPasswo
710、rdField();passTest.setBounds(100,93,100,21);contentPane.add(passTest);JLabellabel_7=newJLabel(确认密码:);label_7.setBounds(230,99,64,15);contentPane.add(label_7);rePassText=newJPasswordField();rePassText.setBounds(311,93,114,21);contentPane.add(rePassText);JLabelsexlabel=newJLabel(性别:);sexlabel.setBound
711、s(40,124,54,15);contentPane.add(sexlabel);finalJRadioButtonmanbtn=newJRadioButton(男);manbtn.setBounds(140,120,45,23);contentPane.add(manbtn);13.4.13.4.9 9用 户 注 册界面JRadioButtonwomanbtn=newJRadioButton(女);womanbtn.setSelected(true);womanbtn.setBounds(230,124,60,23);contentPane.add(womanbtn);ButtonGrou
712、pbg=newButtonGroup();bg.add(womanbtn);bg.add(manbtn);JLabellabel_3=newJLabel(爱好:);label_3.setBounds(40,164,54,15);contentPane.add(label_3);finalJCheckBoxcheckBox=newJCheckBox(篮球);checkBox.setBounds(140,160,60,23);contentPane.add(checkBox);finalJCheckBoxcheckBox_1=newJCheckBox(足球);checkBox_1.setSelec
713、ted(true);checkBox_1.setBounds(202,160,60,23);contentPane.add(checkBox_1);13.4.13.4.9 9用 户 注 册界面finalJCheckBoxnetcheckBox=newJCheckBox(上网);netcheckBox.setBounds(276,160,60,23);contentPane.add(netcheckBox);finalJCheckBoxlvcheckBox=newJCheckBox(旅游);lvcheckBox.setBounds(342,160,60,23);contentPane.add(l
714、vcheckBox);JLabellabel_4=newJLabel(个人简历:);label_4.setBounds(40,215,66,15);contentPane.add(label_4);finalJTextAreatextArea=newJTextArea();textArea.setBounds(129,199,292,53);contentPane.add(textArea);JLabellabel_1=newJLabel(喜欢的职业:);label_1.setBounds(41,268,84,38);contentPane.add(label_1);13.4.13.4.9 9
715、用 户 注 册界面finalJListlist=newJList();list.setModel(newAbstractListModel()Stringvalues=newString公务员,教师,医生,律师;publicintgetSize()returnvalues.length;publicObjectgetElementAt(intindex)returnvaluesindex;);list.setSelectedIndex(0);list.setBounds(140,268,110,75);contentPane.add(list);JLabellabel=newJLabel(学历
716、:);label.setBounds(40,365,54,15);contentPane.add(label);13.4.13.4.9 9用 户 注 册界面finalJComboBoxcomboBox=newJComboBox();comboBox.setModel(newDefaultComboBoxModel(newString博士,硕士,学士);comboBox.setSelectedIndex(0);comboBox.setBounds(140,362,96,21);contentPane.add(comboBox);JButtonbutton=newJButton(注册);butto
717、n.addActionListener(newActionListener()publicvoidactionPerformed(ActionEventarg0)/获取用户名和学号Stringname=nameText.getText().trim();Stringid=idText.getText().trim();if(name.equals()|id.equals()JOptionPane.showMessageDialog(null,用户名和学号不能为空);return;13.4.13.4.9 9用 户 注 册界面System.out.println(姓名:+name+n学号:+id)
718、;/获取密码Stringpass=newString(passTest.getPassword();Stringpass1=newString(rePassText.getPassword();if(!pass.equals(pass1)JOptionPane.showMessageDialog(null,两次密码不一致);return;System.out.println(密码:+pass);/获取性别和爱好Stringsex=manbtn.isSelected()?男:女;Stringlike=checkBox.isSelected()?篮球:;like+=checkBox_1.isSel
719、ected()?足球:;like+=netcheckBox.isSelected()?上网:;like+=lvcheckBox.isSelected()?旅游:;System.out.println(性别:+sex+n爱好:+like);/获取个人简历Stringjianli=textArea.getText().trim();System.out.println(个人简历:+jianli);13.4.13.4.9 9用 户 注 册界面/获取喜欢的职业Stringjob=;Objectzhiye=list.getSelectedValues();for(Objectstr:zhiye)job+
720、=str+;System.out.println(喜欢的职业:+job);/获取学历Stringxueli=(String)comboBox.getSelectedItem();System.out.println(学历:+xueli););button.setBounds(75,404,93,23);contentPane.add(button);13.4.13.4.9 9用 户 注 册界面JButtonbtnNewButton=newJButton(重置);btnNewButton.addActionListener(newActionListener()publicvoidactionP
721、erformed(ActionEventarg0)/对文本组件进行清空nameText.setText();idText.setText();passTest.setText();rePassText.setText();textArea.setText(););btnNewButton.setBounds(261,404,93,23);contentPane.add(btnNewButton);13.4.13.4.9 9用 户 注 册界面上述代码中既验证了用户名和学号不能为空,也验证了密码和确认密码的一致性。运行结果界面如图13.18所示。13.4.13.4.9 9用 户 注 册界面在注册界
722、面中输入信息,点击“注册”按钮,获取用户信息如下所示:姓名:向守超学号:00054密码:111性别:男爱好:篮球足球个人简历:2000年开始从事软件技术开发喜欢的职业:公务员教师学历:硕士13.4.13.4.9 9第第5节节part事件处理前面介绍了布局和基本组件,从而可以得到不同的图形界面,但这些界面还不能响应用户的任何操作。如果要实现用户界面的交互,必须通过事件处理。事件处理是指在事件驱动机制中,应用程序为响应事件而执行的一系列操作。事件处理本节概述13.5.13.5.1 1Java事件处理机制在图形用户界面中,当用户使用鼠标单击按钮、在列表框进行选择或者单击窗口右上角的“”关闭按钮时,都
723、会触发一个相应的事件。在Java事件处理体系结构中,主要涉及三种对象。事件(Event):在Event对象中封装了GUI组件所发生的特定事情,通常由用户的一次操作产生,而不是通过new运算符创建。事件包括键盘事件、鼠标事件等。Event对象一般作为事件处理方法的参数,以便事件处理程序从中获取GUI组件上所发生的事件相关信息。事件源(EventSource):事件发生的场所,通常就是各个GUI组件,例如窗口、按钮、菜单等。事件监听器(EventListener):负责监听事件源所产生的事件,并对事件做出响应处理。事件监听器对象需要实现监听接口Listener中所定义的事件处理方法;当事件触发时,
724、直接调用该事件对应的处理方法对此事件进行响应和处理。Java事件处理机制Java事件处理机制Java的事件处理机制如图13.19所示。在Java程序中,实现事件处理需要以下三个步骤:(1)创建监听类,实现监听接口并重写监听接口中的事件处理方法;(2)创建监听对象,即实例化上一步中所创建的监听类的对象;(3)注册监听对象,调用组件的addXXXListener()方法,将监听对象注册到相应组件上,以便监听对事件源所触发的事件。13.5.13.5.1 1Java事件处理机制此处需要注意监听类、事件处理方法和监听对象之间的区别与联系。监听类:是一个自定义的实现监听接口的类,监听类可以实现一个或多个监
725、听接口。classMyListenerimplementsActionListener.事件处理方法:即监听接口中已经定义好的相应的事件处理方法,该方法是抽象方法,需要在创建监听类时重写接口中的事件处理方法,并将处理事件的业务代码放入到方法中。classMyListenerimplementsActionListener/重写ActionListener接口中的事件处理方法actionPerformed()publicvoidactionPerformed(ActionEvente).监听对象:就是监听类的一个实例对象,该对象具有监听功能,前提是先将监听对象注册到事件源组件上,当操作该组件产生
726、事件时,该事件将会被此监听对象捕获并调用相应的事件方法进行处理。/创建一个监听对象MyListenerlistener=newMyListener();/注册监听button.addActionListener(listener);13.5.13.5.1 113.5.13.5.2 2事 件 和 事 件 监听器事件用于封装事件处理所必需的基本信息,包括事件源、事件信息等。AWT中提供了丰富的事件类,用于封装不同组件上所发生的特定操作。所有AWT的事件类都是AWTEvent类的子类,而AWTEvent类又是EventObject类的子类。AWT事件分为低级事件和高级事件两大类。低级事件是指基于特定
727、动作的事件,比如鼠标进入、单击、拖放等动作,组件获得焦点、失去焦点时所触发的焦点事件;高级事件是基于语义的事件,它可以不和特定的动作相关联,而依赖于触发此事件的类,比如单击按钮和菜单、滑动滑动条、选中单选按钮等。事件和事件监听器事 件 和 事 件 监听器常见的AWT事件类如表13-14所示。13.5.13.5.2 2事 件 和 事 件 监听器13.5.13.5.2 2事 件 和 事 件 监听器对不同的事件需要使用不同的监听器进行监听,不同的监听器需要实现不同的监听接口。监听接口中定义了抽象的事件处理方法,这些方法能够针对不同的操作进行不同的处理。在程序中,通常使用监听类来实现监听接口中的事件处
728、理方法,AWT提供了大量的监听接口,用于实现不同类型的事件监听器,常用的监听接口及说明如表13-15所示。13.5.13.5.2 2事 件 和 事 件 监听器监听接口与事件一样,通常都定义在java.awt.event包中,该包提供了不同类型的事件类和监听接口。13.5.13.5.2 2第第6节节part标准对话框对话框属于特殊组件,与窗口一样是一种可以独立存在的顶级容器。对话框通常依赖于其他窗口,即有一个父窗口。Swing提供了JOptionPane标准对话框组件,用于显示消息或获取信息。JOptionPane类主要提供了四个静态方法用于显示不同类型的对话框,如表13-16所示。标准对话框本
729、节概述消 息 对话框例如:JOptionPane.showMessageDialog(null,您输入的数据不正确,请重新输入!,错误提示,JOptionPane.ERROR_MESSAGE);运行结果如图13.20所示:13.6.13.6.1 113.6.13.6.1 1消 息 对话框JOptionPane.showMessageDialog()静态方法用于显示消息对话框,该方法有以下几种常用的重载方法:voidshowMessageDialog(ComponentparentComponent,Objectmessage):显示一个指定信息的消息对话框,该对话框的标题为“message”。
730、voidshowMessageDialog(ComponentparentComponent,Objectmessage,Stringtitle,intmessageType):显示一个指定信息、标题和消息类型的消息对话框。voidshowMessageDialog(ComponentparentComponent,Objectmessage,Stringtitle,intmessageType,Iconicon):显示一个指定信息、标题、消息类型和图标的消息对话框。消息对话框消 息 对话框关于showMessageDialog()方法所使用到的参数说明如下:parentComponent参数
731、:用于指定对话框的父组件,如果为null,则对话框将显示在屏幕中央,否则根据父组件所在窗体来确定位置。message参数:用于指定对话框中所显示的信息内容。title参数:用于指定对话框的标题。messageType参数:用于指定对话框的消息类型。对话框左边显示的图标取决于对话框的消息类型,不同的消息类型显示不同的图标。在JOptionPane中提供了五种消息类型:ERROR_MESSAGE(错误)、INFORMATION_MESSAGE(通知)、WARNING_MESSAGE(警告)、QUESTION_MESSAGE(疑问)、PLAIN_MESSAGE(普通)。icon参数:用于指定对话框所
732、显示的图标。13.6.13.6.1 113.6.13.6.2 2输 入 对话框JOptionPane.showInputDialog()静态方法用于显示输入对话框,该方法有以下几种常用的重载方法:StringshowInputDialog(Objectmessage):显示一个指定提示信息的输入对话框。StringshowInputDialog(ComponentparentComponent,Objectmessage):显示一个指定父组件、提示信息的输入对话框。StringshowInputDialog(ComponentparentComponent,Objectmessage,Stri
733、ngtitle,intmessageType):显示一个指定父组件、提示信息、标题以及消息类型的输入对话框。例如:JOptionPane.showInputDialog(null,请输入一个数字:);运行结果如图13.21所示:输入对话框13.6.13.6.3 3确 认 对话框JOptionPane.showConfirmDialog()静态方法用于显示确认对话框,该方法有以下几种常用的重载方法:intshowConfirmDialog(Componentcomponent,Objectmessage):显示一个指定父组件、提示信息、选项类型为YES_NO_CANCEL_OPTION、标题为“
734、选择一个选项”的确认对话框。intshowConfirmDialog(Componentcomponent,Objectmessage,Stringtitle,intoptionType):显示一个指定父组件、提示信息、标题和选项类型的确认对话框。intshowConfirmDialog(Componentcomponent,Objectmessage,Stringtitle,intoptionType,intmessageType):显示一个指定父组件、提示信息、标题、选项类型和消息图标类型的确认对话框。确认对话框确 认 对话框其中,optionType参数代表选项类型,用于设置对话框中所提
735、供的按钮选项。在JOptionPane类中提供了四种选项类型的静态变量:DEFAULT_OPTION:默认选项YES_NO_OPTION:Yes和No选项YES_NO_CANCEL_OPTION:Yes、No和CANCEL选项OK_CANCEL_OPTION:Ok和Cancel选项例如:JOptionPane.showConfirmDialog(null,您确定要删除吗?,删除,JOptionPane.YES_NO_OPTION,JOptionPane.QUESTION_MESSAGE);运行结果如图13.22所示:13.6.13.6.3 313.6.13.6.4 4选 项 对话框JOptio
736、nPane.showOptionDialog()静态方法用于显示选项对话框,该方法的参数是固定的,具体如下:intshowOptionDialog(ComponentparentComponent,Objectmessage,Stringtitle,intoptionType,intmessageType,Iconicon,Objectoptions,ObjectinitialValue):其功能是创建一个指定各参数的选项对话框,其中选项数有optionType参数确定,初始选择由initialValue参数确定。选项对话框选 项 对话框例如:Objectoptions=红,橙,黄,绿;JOp
737、tionPane.showOptionDialog(null,请选择一种你喜欢的颜色:,选择颜色,JOptionPane.DEFAULT_OPTION,JOptionPane.QUESTION_MESSAGE,null,options,options0);运行结果如图13.23所示:13.6.13.6.4 4第第7节节part菜单菜单是常见的GUI组件,且占用空间少、使用方便。创建菜单组件时只需要将菜单栏、菜单和菜单项组合在一起即可。Swing中的菜单由如下几个类组合而成:JMenuBar:菜单栏,菜单容器;JMenu:菜单,菜单项的容器;JPopupMenu:弹出式菜单,单击鼠标右键可以弹出
738、的上下文菜单;JMenuItem:菜单项,菜单系统中最基本的组件。常用的菜单有两种样式:下拉式菜单:由JMenuBar、JMenu和JMenuItem组合而成的下拉式菜单;弹出式菜单:由JPopupMenu和JMenuItem组合而成的右键弹出式菜单。菜单本节概述13.7.13.7.1 1下 拉 式菜单下拉式菜单是常用的菜单样式,由JMenuBar菜单栏、JMenu菜单和JMenuItem菜单项组合而成,先将JMenuItem添加到JMenu中,再将JMenu添加到JMenuBar中。菜单允许嵌套,即一个菜单中不仅可以添加菜单项,还可以添加另外一个菜单对象,从而形成多级菜单。1.JMenuBa
739、r菜单栏菜单栏菜单栏是一个水平栏,用来管理菜单,可以位于GUI容器的任何位置,但通常放置在顶级窗口的顶部。Swing中的菜单栏是通过使用JMenuBar类来创建,创建一个JMenuBar对象后,再通过JFrame类的setJMenuBar()方法将菜单栏对象添加到窗口的顶部。例如:/创建菜单栏对象JMenuBarmenuBar=newJMenuBar();/添加菜单栏对象到窗口frame.setJMenuBar(menuBar);下拉式菜单下 拉 式菜单2.JMenu菜单菜单菜单用来整合管理菜单项,组成一个下拉列表形式的菜单,使用JMenu类可以创建一个菜单对象,其常用的构造方法如下:JMen
740、u():创建一个新的、无文本的菜单对象。JMenu(Stringstr):创建一个新的、指定文本的菜单对象,是常用的构造方法。JMenu(Stringstr,booleanbool):创建一个新的、指定文本、是否分离式的菜单对象。例如:/菜单的文本为“新建”JMenumenuFile=newJMenu(新建);13.7.13.7.1 1下 拉 式菜单JMenu类常用的方法如表13-17所示。13.7.13.7.1 1下 拉 式菜单3.JMenuItem菜单项菜单项菜单项是菜单系统中最基本的组件,其实质是位于菜单列表中的按钮。当用户选择菜单项时,则执行与菜单项所关联的操作。使用JMenuItem
741、类可以创建一个菜单选项对象,菜单项对象可以添加到菜单中。JMenuItem类常用的构造方法如下:JMenuItem():创建一个新的、无文本和图标的菜单项;JMenuItem(Iconicon):创建一个新的、指定图标的菜单项;JMenuItem(Stringtext):创建一个新的、指定文本的菜单项;JMenuItem(Stringtext,Iconicon):创建一个新的、指定文本和图标的菜单项。例如:/菜单项的文本为“退出”JMenuItemmenuFile=newJMenuItem(退出);13.7.13.7.1 1下 拉 式菜单JMenuItem类常用的方法如表13-18所示。使用J
742、MenuBar、JMenu和JMenuItem实现下拉式菜单的步骤:(1)创建一个JMenuBar菜单栏对象,调用顶级窗口的setJMenuBar()方法将其添加到窗体顶部;(2)创建若干JMenu菜单对象,调用JMenuBar的add()方法将菜单添加到菜单栏中;(3)创建若干个JMenuItem菜单项,调用JMenu的add()方法将菜单项添加到菜单中。下述案例示例了使用JMenuBar、JMenu和JMenuItem实现下拉式菜单,代码如下所示。13.7.13.7.1 1下 拉 式菜单【代码13.4】JMenuExample.javapackagecom;importjavax.swin
743、g.*;publicclassJMenuExampleextendsJFrameprivateJPanelp;/声明菜单栏privateJMenuBarmenuBar;/声明菜单privateJMenumenuFile,menuEdit,menuHelp,menuNew;/声明菜单项privateJMenuItemmiSave,miExit,miCopy,miC,miJava,miOther;publicJMenuExample()super(下拉菜单);p=newJPanel();/创建菜单栏对象menuBar=newJMenuBar();/将菜单栏设置到窗体中this.setJMenuBa
744、r(menuBar);13.7.13.7.1 1下 拉 式菜单/创建菜单menuFile=newJMenu(文件);menuEdit=newJMenu(编辑);menuHelp=newJMenu(帮助);menuNew=newJMenu(新建);/将菜单添加到菜单栏menuBar.add(menuFile);menuBar.add(menuEdit);menuBar.add(menuHelp);/将新建菜单添加到文件菜单中menuFile.add(menuNew);/在菜单中添加分隔线menuFile.addSeparator();/创建菜单选项miSave=newJMenuItem(保存);
745、miExit=newJMenuItem(退出);miCopy=newJMenuItem(复制);miC=newJMenuItem(类);miJava=newJMenuItem(Java项目);miOther=newJMenuItem(其他.);13.7.13.7.1 1下 拉 式菜单/将菜单项添加到菜单中menuFile.add(miSave);menuFile.add(miExit);menuEdit.add(miCopy);menuNew.add(miC);menuNew.add(miJava);menuNew.add(miOther);/将面板添加到窗体this.add(p);/设定窗口
746、大小this.setSize(400,200);/设定窗口左上角坐标(X轴200像素,Y轴100像素)this.setLocation(200,100);/设定窗口默认关闭方式为退出应用程序this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);/设置窗口可视(显示)this.setVisible(true);publicstaticvoidmain(Stringargs)newJMenuExample();程序运行结果如图13.24所示:13.7.13.7.1 113.7.13.7.2 2弹 出 式菜单弹出式菜单不是固定在菜单栏中,而是在GUI
747、界面的任意位置点击鼠标右键时所弹出的一种菜单。JPopupMenu类常用的构造方法如下:JPopupMenu():创建一个默认无文本的菜单对象。JPopupMenu(Stringlabel):创建一个指定文本的菜单对象。JPopupMenu类常用的方法及功能如表13-19所示。弹出式菜单弹 出 式菜单下述案例示例了弹出式菜单的创建,代码如下所示。【代码13.5】JPopupMenuExample.javapackagecom;importjava.awt.event.*;importjavax.swing.*;publicclassJPopupMenuExampleextendsJFramep
748、rivateJPanelp;/声明弹出菜单privateJPopupMenupopMenu;/声明菜单选项privateJMenuItemmiSave,miCopy,miCut;publicJPopupMenuExample()super(弹出式菜单);p=newJPanel();/创建弹出菜单对象popMenu=newJPopupMenu();/创建菜单选项miSave=newJMenuItem(保存);miCopy=newJMenuItem(复制);miCut=newJMenuItem(剪切);13.7.13.7.2 2弹 出 式菜单/将菜单选项添加到菜单中popMenu.add(miSa
749、ve);popMenu.addSeparator();popMenu.add(miCopy);popMenu.add(miCut);/注册鼠标监听p.addMouseListener(newMouseAdapter()/重写鼠标点击事件处理方法publicvoidmouseClicked(MouseEvente)/如果点击鼠标右键if(e.getButton()=MouseEvent.BUTTON3)intx=e.getX();inty=e.getY();/在面板鼠标所在位置显示弹出菜单popMenu.show(p,x,y););this.add(p);this.setBounds(200,1
750、00,400,200);13.7.13.7.2 2弹 出 式菜单this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.setVisible(true);publicstaticvoidmain(Stringargs)newJPopupMenuExample();运行结果如图13.25所示。13.7.13.7.2 2第第8节节part表格与树表格和树是GUI程序中常见的组件。表格是由多行和多列组成的一个二维显示区。树是由一系列具有父子关系的节点组成,每个节点既可以是上一级节点的子节点,与可以是下一级节点的父节点。表 格 与树本节概述13
751、.8.13.8.1 1表格Swing中对表格提供了支持,使用JTable类及其相关类可以轻松创建一个二维表格,还可以对表格定制外观和编辑特性。下面对JTable类及其相关接口进行比较详细介绍。1.JTable类类JTable类用于创建一个表格对象,显示和编辑常规二维单元表。JTable类的构造方法如下:JTable():创建一个默认模型的表格对象。JTable(intnumRows,intnumColumns):创建一个指定行数和列数的默认表格。JTable(ObjectrowData,ObjectcolumnNames):创建一个具有指定列名和二维数组数据的默认表格。JTable(Table
752、Modeldm):创建一个指定表格模型的表格对象。JTable(TableModeldm,TableColumnModelcm):创建一个指定表格模型和列模型的表格对象。JTable(VectorrowData,VectorcolumnNames):创建一个指定列名并以Vector为输入来源的数据表格。表格表格JTable类的常用方法如表13-20所示。13.8.13.8.1 1表格2.TableModel接口接口在创建一个指定表格模型的JTable对象时,需要使用TableModel类型的参数来指定表格模型。TableModel表格模型是一个接口,此接口定义在javax.swing.tabl
753、e包中。TableModel接口中定义许多表格操作的方法,如表13-21所示。通过直接实现TableModel接口来创建表格是非常繁琐的,因此Java提供了实现TableModel接口的两个类:AbstractTableModel类:是一个抽象类,其中实现TableModel接口中的大部分方法,通过AbstractTableModel类可以灵活地构造出自己所需的表格模式。DefaultTableModel类:是一个默认的表格模式类,该类继承AbstractTableModel抽象类。13.8.13.8.1 1表格3.TableColumnModel接口接口在创建一个指定表格列模型的JTable
754、对象时,需要使用TableColumnModel类型的参数来指定表格的列模型。TableColumnModel接口中提供了有关表格列模型的方法,如表13-22所示。13.8.13.8.1 1表格TableColumnModel接口通常不需要直接实现,而是通过调用JTable对象中的getColumnModel()方法来获取TableColumnModel对象,再使用该对象对表格的列进行设置。例如使用表格列模型获取选中的列代码如下:/获取表格列模型TableColumnModelcolumnModel=table.getColumnModel();/获取选中的表格列TableColumncolu
755、mn=columnModel.getColumn(table.getSelectedColumn();13.8.13.8.1 1表格4.ListSelectionModel接口接口JTable使用ListSelectionModel来表示表格的选择状态,程序可以通过ListSelectionModel来控制表格的选择模式。ListSelectionModel接口提供了以下三种不同的选择模式:ListSelectionModel.SINGLE_SELECTION:单一选择模式,只能选择单个表格单元。ListSelectionModel.SINGLE_INTERVAL_SELECTION:连续区间
756、选择模式,用于选择单个连续区域,在选择多个单元格时单元格之间必须是连续的(通过SHIFT辅助键的帮助来选择连续区域)ListSelectionModel.MULTIPLE_INTERVAL_SELECTION:多重选择模式,没有任何限制,可以选择任意表格单元(通过SHIFT辅助键的帮助来选择多个单元格),该模式时默认的选择模式。13.8.13.8.1 1表格ListSelectionModel接口通常不需要直接实现,而是通过调用JTable对象的getSelectionModel()方法来获取ListSelectionModel对象,然后通过调用setSelectionModel()方法来设置
757、表格的选择模式。当用户选择表格内的数据时会产生ListSelectionEvent事件,要处理此类事件就必须实现ListSelectionListener监听接口。该接口中定义了一个事件处理方法:voidvalueChanged(ListSelectionEvente)其功能是当所选取的单元格数据发生改变时,将自动调用该方法来处理ListSelectionListener事件。下述案例示例了JTable类的简单应用,代码如下所示。13.8.13.8.1 1表格【代码13.6】JTableExample.javapackagecom;importjava.awt.BorderLayout;imp
758、ortjavax.swing.*;publicclassJTableExampleextendsJFrame/声明滚动面板privateJScrollPanespTable;/声明表格privateJTabletable;StringcolumnName=姓名,学号,课程名称;StringtableValues=张三,001,计算机应用,李四,002,Java程序设计,王五,003,WEB程序设计;publicJTableExample()super(使用数组创建表格);/设定窗口性质this.setSize(400,200);this.setLocation(200,100);this.se
759、tDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.setVisible(true);13.8.13.8.1 1表格/创建表格table=newJTable(tableValues,columnName);/设置表格选择模式为单一选择table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);/创建一个滚动面板,包含表格spTable=newJScrollPane(table);/将滚动面板添加到窗体中央this.add(spTable,BorderLayout.CENTER);publ
760、icstaticvoidmain(Stringargs)newJTableExample();运行结果如图13.26所示。13.8.13.8.1 113.8.13.8.2 2树Swing中对树的节点提供了支持,使用JTree类及其相关类可以轻松创建一个树及其节点。根据节点中是否包含子节点,可以将树的节点分为普通节点和叶子节点两类。下面对JTree类及其相关接口进行比较详细介绍。1.JTree类类JTree类用来创建树目录组件,是一个将分层数据集显示为轮廓的组件。树中的节点可以展开,也可以折叠。当展开普通节点时,将显示其子节点;当折叠节点时,将其子节点隐藏。JTree类常用构造方法及其他方法如表
761、13-23所示。树树2.TreeModel树模型树模型TreeModel是树的模型接口,可以触发相关的树事件,处理树可能产生的一些变动。TreeModel接口中常用的方法如表13-24所示。13.8.13.8.2 2树通过实现TreeModel接口中的八种方法,可以构造出用户所需JTree树,但这种方式相对比较繁琐。Java提供了一个DefaultTreeModel默认模式类,该类实现了TreeModel接口,并提供了许多实用的方法,能够方便快捷地构造出JTree树。DefaultTreeModel类的构造方法如下:DefaultTreeModel(TreeNoderoot):创建一个Defa
762、ultTreeModel对象,并指定根节点。DefaultTreeModel(TreeNoderoot,BooleanasksAllowsChildren):创建一个指定根节点的,是否具有子节点的DefaultTreeModel对象。13.8.13.8.2 2树3.TreeNode树节点树节点TreeNode接口用于表示树节点,该接口提供树的相关节点的操作方法,如表13-25所示。13.8.13.8.2 2树DefaultMutableTreeNode类是一个实现TreeNode和MutableTreeNode接口的类,该类中提供了许多实用的方法,并增加了一些关于节点的处理方式。Default
763、MutableTreeNode类的常用方法如表13-26所示。13.8.13.8.2 2树4.树事件树事件树事件是当对树进行操作时所触发的事件,其类型有两种:TreeModelEvent事件和TreeSelectionEvent事件。当树的结构改变时,例如,改变节点值、新增节点、删除节点等,都会触发TreeModelEvent事件,处理TreeModelEvent事件的监听接口是TreeModelListener;当在JTree树中选择任何一个节点时,都会触发TreeSelectionEvent事件,处理TreeSelectionEvent事件的监听接口是TreeSelectionListen
764、er。处理树事件的处理方法如表13-27所示。13.8.13.8.2 2树下述案例示例了JTree的使用,代码如下所示。【代码13.7】JTreeExample.javapackagecom;importjava.awt.GridLayout;importjavax.swing.*;importjavax.swing.event.*;importjavax.swing.tree.*;publicclassJTreeExampleextendsJFrameprivateDefaultMutableTreeNoderoot;privateDefaultTreeModelmodel;privateJ
765、Treetree;privateJTextAreatextArea;privateJPanelp;publicJTreeExample()super(JTree树示例);/实例化树的根节点root=makeSampleTree();/实例化的树模型model=newDefaultTreeModel(root);13.8.13.8.2 2树/实例化一棵树tree=newJTree(model);/设置树的选择模式是单一节点的选择模式(一次只能选中一个节点)tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TRE
766、E_SELECTION);/注册树的监听对象,监听选择不同的树节点tree.addTreeSelectionListener(newTreeSelectionListener()/重写树的选择事件处理方法publicvoidvalueChanged(TreeSelectionEventevent)/获取选中节点的路径TreePathpath=tree.getSelectionPath();if(path=null)return;/获取选中的节点对象DefaultMutableTreeNodeselectedNode=(DefaultMutableTreeNode)path.getLastPat
767、hComponent();/获取选中节点的内容,并显示到文本域中textArea.setText(selectedNode.getUserObject().toString(););13.8.13.8.2 2树/实例化一个面板对象,布局是1行2列p=newJPanel(newGridLayout(1,2);/在面板的左侧放置树p.add(newJScrollPane(tree);textArea=newJTextArea();/面板右侧放置文本域p.add(newJScrollPane(textArea);/将面板添加到窗体this.add(p);/设定窗口大小this.setSize(400
768、,200);/设定窗口左上角坐标(X轴200像素,Y轴100像素)this.setLocation(200,100);/设定窗口默认关闭方式为退出应用程序this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);/设置窗口可视(显示)this.setVisible(true);/创建一棵树对象的方法publicDefaultMutableTreeNodemakeSampleTree()13.8.13.8.2 2树/实例化树节点,并将节点添加到相应节点中DefaultMutableTreeNoderoot=newDefaultMutableTreeN
769、ode(重庆工程学院);DefaultMutableTreeNodecomp=newDefaultMutableTreeNode(物联网学院);root.add(comp);DefaultMutableTreeNodedpart=newDefaultMutableTreeNode(物联网系);comp.add(dpart);DefaultMutableTreeNodeemp=newDefaultMutableTreeNode(赵二);dpart.add(emp);emp=newDefaultMutableTreeNode(张三);dpart.add(emp);dpart=newDefaultM
770、utableTreeNode(网络系);comp.add(dpart);emp=newDefaultMutableTreeNode(李四);dpart.add(emp);returnroot;publicstaticvoidmain(Stringargs)newJTreeExample();运行结果如图13.27所示:13.8.13.8.2 2本章课后作业见教材JAVA程 序 设计本章学习目标:了解JDBC概念以及驱动类型掌握使用JDBC连接MySQL数据库的基本步骤掌握数据库环境的搭建掌握使用JDBC访问数据库的步骤掌握使用JavaAPI操作数据库掌握数据库事务的使用第十四章第十四章JDBC
771、与与MySQL编程编程第第1节节partJDBC基础通过使用JDBC,Java程序可以轻松地操作各种主流数据库,例如,Oracle、MSSQLServer、MySQL等。由于Java语言本身的跨平台性,所以使用JDBC编写的程序不仅可以实现跨数据库,还具有跨平台性和可移植性。使用JDBC访问数据库具有操作简单、获取方便且安全可靠等优势。JDBC基础本节概述JDBC(JavaDatabaseConnectivity,Java数据库连接)是一种执行SQL语句的JavaAPI。程序可以通过JDBCAPI连接到关系数据库,并使用SQL结构化语言来完成对数据库的增、删、改、查等操作。与其他数据库编程语言
772、相比,JDBC为数据开发者提供了标准的API,使用JDBC开发的数据库应用程序可以访问不同的数据库,并在不同平台上运行,既可以在Windows平台上运行,也可以在UNIX平台上运行。JDBC程序访问不同的数据库时,需要数据库厂商提供相应的驱动程序。通过JDBC驱动程序的转换,使得相同的代码在访问不同的数据库时运行良好。JDBC驱动示意图如图14.1所示。14.1.14.1.1 1JDBC简介JDBC简介JDBC应用程序可以对数据库进行访问和操作,JDBC访问数据库时主要完成以下工作:建立与数据库的连接;执行SQL语句;获取执行结果。数据库驱动程序是JDBC程序和数据库之间的转换层,数据库驱动程
773、序负责将JDBC调用映射成特定的数据库调用,JDBC访问示意图如图14.2所示。14.1.14.1.2 2JDBC驱动JDBC驱动当今市场上主流数据库都提供了JDBC驱动程序,甚至一些流行的数据库还提供了多种不同版本的JDBC驱动程序。JDBC驱动程序有以下4种类型:JDBC-ODBC桥:是最早实现的JDBC驱动程序,主要目的是快速推广JDBC。ODBC(OpenDatabaseConnectivity,开放数据库连接)是通过一组通用的API访问不同的数据库管理系统,也需要各数据库厂商提供相应的驱动程序,而ODBC则对这些驱动程序进行管理。JDBC-ODBC桥驱动是将JDBCAPI映射到ODB
774、CAPI,驱动速度很慢,只适用于访问没有其他JDBC驱动的数据库。由于Java语言的广泛应用,所有数据库厂商都提供了JDBC驱动,因此在Java8中不再支持JDBC-ODBC数据访问方式。JDBC驱动14.1.14.1.2 2本地API驱动:直接将JDBCAPI映射成数据库特定的客户端API,包含特定的数据库本地代码,用于访问特定数据库的客户端。本地API驱动比起JDBC-ODBC桥执行效率要高,但是仍然需要在客户端加载数据库厂商提供的代码库,不适合基于网络的应用。本地API驱动虽然速度有所提升,但相对后面两种JDBC驱动还是不够高。网络协议驱动:将JDBC调用翻译成中间件供应商的协议,然后再
775、由中间件服务器翻译成数据库访问协议。网络协议驱动是基于服务器的,不需要在客户端加载数据库厂商提供的代码库,且执行效率比较好,便于维护和升级。本地协议驱动:是纯Java编写的,可以直接连接到数据库。本地协议驱动不需要将JDBC的调用传给ODBC,或本地数据库接口,或中间层服务器,因此执行效率非常高;而且根本不需要在客户端或服务器装载任何软件或驱动。本地协议驱动是智能的,能够知道数据库使用的底层协议,是目前最流行的JDBC驱动。通常JDBC访问数据库时建议使用第4种本地协议驱动,该驱动使用纯Java编写,且避开了本地代码,减少了应用开发的复杂性,降低了产生冲突和出错的可能。JDBC驱动14.1.1
776、4.1.2 2JDBCAPI提供了一组用于与数据库进行通信的接口和类,这些接口和类都定义在java.sql包中,常用的接口和类如表14-1所示。14.1.14.1.3 3J D B C APIJDBCAPI需要注意的是:使用JDBCAPI中的类或接口访问数据库时,容易引发SQLException异常,SQLException异常类是检查型异常,需要放在trycatch语句中进行异常处理,SQLException是JDBC中其他异常类型的基础。1.DriverManager类类DriverManager是数据库驱动管理类,用于管理一组JDBC驱动程序的基本服务。应用程序和数据库之间可以通过Dri
777、verManager建立连接,其常用的静态方法如表14-2所示。J D B C API14.1.14.1.3 32.Connection接口接口Connection接口用于连接数据库,每个Connection对象代表一个数据库连接会话,要想访问数据库,必须先获得数据库连接。一个应用程序可与单个数据库建立一个或多个连接,也可以与多个数据库建立连接。通过DriverManager类的getConnection()方法可以返回一个Connection对象,该对象中提供了创建SQL语句的方法,以完成基本的SQL操作,同时为数据库事务提供了提交和回滚的方法。Connection接口中常用的方法如表14-
778、3所示。J D B C API14.1.14.1.3 33.Statement接口接口Statement接口一般用于执行SQL语句。在JDBC中要执行SQL查询语句的方式有一般查询(Statement)、参数查询(PreparedStatement)和存储过程(CallableStatement)三种方式。Connection接口中提供的createStatement()、prepareStatement()和prepareCall()方法分别返回一个Statement对象,PreparedStatement对象和CallableStatement对象。Statement、PreparedSt
779、atement和CallableStatement三个接口具有继承关系,其中PreparedStatement是Statement的子接口,而CallableStatement又是PreparedStatement的子接口。Statement接口的主要功能是将SQL语句传送给数据库,并返回SQL语句的执行结果。Statement提交的SQL语句是静态的,不需要接收任何参数,SQL语句可以包含以下三种类型的语句:SELECT查询语句;DML语句,如INSERT、UPDATE或DELETE;DDL语句,如CREATETABLE和DROPTABLE。J D B C API14.1.14.1.3 3S
780、tatement接口中常用的方法及功能如表14-4所示。J D B C API需要注意的是:closeOnCompletion()和isCloseOnCompletion()方法是从Java7开始新增的方法,executeLargeUpdate()方法是从Java8开始新增的方法,在开发过程中使用这几个方法时需要注意JDK的版本。考虑到目前应用程序所处理的数据量越来越大,使用executeLargeUpdate()方法具有更好的适应性,但目前有的数据库驱动暂不支持该方法,例如MySQL驱动。14.1.14.1.3 34.ResultSet接口接口ResultSet接口用于封装结果集对象,该对象
781、包含访问查询结果的方法。使用Statement中的executeQuery()方法可以返回一个ResultSet结果集的对象,该对象封装了所有符合查询条件的记录。ResultSet具有指向当前数据行的游标,并提供了许多方法来操作结果集中的游标,同时还提供了一套getXXX()方法对结果集中的数据进行访问,这些方法可以通过列索引或列名获得数据。ResultSet接口中常用的方法如表14-5所示。J D B C API14.1.14.1.3 3ResultSet对象具有指向当前数据行的游标。最初游标位于第一行之前,每调用一次next()方法,游标会自动向下移一行,从而可以从上到下依次获取所有数据行
782、。getXXX()方法用于对游标所指向的行的数据进行访问。在使用getXXX()方法取值时,数据库的字段数据类型要与Java的数据类型相匹配,例如,数据库中的整数字段对应Java数据类型中的int类型,此时使用getInt()方法来读取该字段中的数据。常用的SQL数据类型和Java数据类型之间的对应关系如表14-6所示。J D B C API14.1.14.1.3 3第第2节节part数据库环境搭建14.2.14.2.1 1创 建 数 据库表本章JDBC数据库访问基于MySQL数据库,因此所有的代码及环境都是基于MySQL数据库的。在进行数据库访问操作之前,需要先创建数据库和表并录入测试数据。
783、在root用户下创建student数据库,并在该库下创建t_user表,并添加测试数据,其SQL代码如下所示。创建数据库表创 建 数 据库表【代码14.1】student.sqlCREATEDATABASEstudent;CREATETABLEt_user(Idint(11)NOTNULLAUTO_INCREMENT,sidvarchar(20)DEFAULTNULL,namevarchar(20)DEFAULTNULL,passwordvarchar(20)DEFAULTNULL,sexvarchar(20)DEFAULTNULL,majorvarchar(20)DEFAULTNULL,ho
784、bbyvarchar(20)DEFAULTNULL,PRIMARYKEY(Id);#添加测试数据INSERTINTOt_userVALUES(19,159110909,向守超,111,男,物联网工程,篮球足球),(20,159110901,张恒,123,男,物联网工程,篮球足球);创建完库student、表t_user和添加完数据以后,在MySQL-Front图形化界面工具中打开,其表中的数据如图14.3所示。14.2.14.2.2 2设 置 MySQL驱动类Java项目在访问MySQL数据库时,需要在项目中设置MySQL驱动类路径,即将MySQL数据库所提供的JDBC驱动程序(mysql-c
785、onnector-java-5.1.12-bin0)导入到工程中。mysql-connector-java-5.1.12-bin.jar驱动文件可在网络上直接下载,当然也可以下载其他的版本。配置MySQL数据库驱动程序有两种方法:一种方法是将驱动程序配置到CLASSPATH中,与配置JDK的环境变量类似,这种方法的配置将对本机中所有创建的项目起作用,但程序员一般不用这种方法;第二种方法是在基础开发工具Eclipse中选中项目,右键单击,在弹出的快捷菜单中选择“PropertiesJavaBuildPathlibrariesAddExternalJARs”命令,在弹出的对话框中,选择mysql-
786、connector-java-5.1.12-bin.jar文件。如图14.4所示。设置MySQL驱动类设 置 MySQL驱动类设置完MySQL数据库驱动类路径之后,项目的目录如图14.5所,ReferencedLibraries文件夹中的mysql-connector-java-5.1.12-bin.jar表示对该jar包的引用。14.2.14.2.2 2第第3节节part数据库访问使用JDBC访问数据库的步骤:加载数据库驱动;建立数据连接;创建Statement对象;执行SQL语句;访问结果集;数 据 库访问本节概述14.3.14.3.1 1加 载 数 据 库驱动通常使用Class类的for
787、Name()静态方法来加载数据库的驱动,其语法格式如下所示:/加载驱动Class.forName(数据库驱动类名);例如:Class.forName(com.mysql.jdbc.Driver);/加载MySQL驱动需要注意的是:不同的数据库其数据库驱动类是不同的,例如:Oracle数据库的驱动类是oracle.jdbc.driver.OracleDriver,而MySQL的数据库驱动类是com.mysql.jdbc.Driver。数据库厂商在提供数据库驱动(通常是一个或几个jar文件)时,会有相应的文档说明。加载数据库驱动14.3.14.3.2 2建立数据库连接在使用JDBC操作数据库之前,
788、需要先创建一个数据库连接,使用DriverManager类的getConnection()静态方法来获取数据库连接对象,其语法格式如下所示:DriverManager.getConnection(Stringurl,Stringuser,Stringpass);其中,getConnection()方法有三个参数,具体如下:url:数据库连接字符串,遵循的格式是“jdbc:驱动:其他”,不同的数据库连接的URL也有所不同。user:连接数据库的用户名;pass:密码。建立数据库连接14.3.14.3.2 2建立数据库连接例如,访问MySQL数据库的URL连接字符串jdbc:mysql:/127.
789、0.0.1:3306/student在上面的URL连接字符串中:jdbc:mysql是协议名称“127.0.0.1”是本机服务器IP地址,也可以使用“localhost”;“3306”是MySQL数据库的端口号;“student”是数据库实例名。例如:获取MySQL数据库连接对象Class.forName(com.mysql.jdbc.Driver);Connectionconn=DriverManager.getConnection(jdbc:mysql:/127.0.0.1:3306/student,/URL连接字符串root,/用户名root);/密码14.3.14.3.3 3创建Sta
790、tement对象对数据库进行操作或访问时,需要使用SQL语句。在Java语言中,SQL语句是通过Statement对象进行封装后,发送给数据库。Statement对象不是通过Statement类直接创建的,而是通过Connection对象所提供的方法来创建各种Statement对象。通过Connection对象来获得Statement的方法有以下三种:createStatement()方法:创建一个基本的Statement对象;prepareStatement(Stringsql)方法:根据参数化的SQL语句创建一个预编译的PreparedStatement对象;prepareCall(Str
791、ingsql)方法:根据SQL语句来创建一个CallableStatement对象,用于调用数据库的存储过程。例如:/创建Statment对象Statementstmt=conn.createStatement();创建Statement对象14.3.14.3.4 4执 行 SQL语句获取Statement对象之后,就可以调用该对象的不同方法来执行SQL语句。所有的Statement都有三种执行SQL语句的方法,具体使用哪一种方法由SQL语句产生的结果来决定:executeQuery()方法:只能执行查询语句,例如SELECT语句,用于产生单个结果集;executeUpdate()和execu
792、teLargeUpdate()方法:用于执行DML和DDL语句,执行DML(INSERT、UPDATE或DELETE语句)时返回受SQL语句所影响的行数(整数值),而执行DDL语句(CREATETABLE、DROPTABLE等)返回值总为0;execute()方法:可以执行任何SQL语句,此方法比较特殊,也比较麻烦,返回结果为多个结果集、多个更新计数或二者的组合。通常不建议使用该方法,只有在不知道执行SQL语句会产生什么结果或可能有多种类型结果的情况下才会使用。如果SQL语句运行后能产生结果集,Statement对象则将结果集封装成ResultSet对象并返回。下述代码调用Statement对
793、象的executeQuery()方法来执行SQL查询语句,并返回一个ResultSet结果集对象。例如:执行SQL查询语句并返回结果集ResultSetrs=smt.executeQuery(SELECT*FROMt_user);执行SQL语句14.3.14.3.5 5访问结果集SQL的查询结果使用ResultSet封装,ResultSet结果集包含了满足SQL查询条件的所有的行,使用getXXX()方法对结果集中的数据进行访问。当使用getXXX()方法访问结果集中的数据时,可通过列索引或列名来获取游标所指行中的列数据,其语法格式如下;getXXX(列索引)或getXXX(“列名”)例如:循
794、环输出结果集中第三列数据。while(rs.next()System.out.println(rs.getString(3);或while(rs.next()System.out.println(rs.getString(name);访问结果集访问结果集需要注意的是:在使用getXXX()方法来获得数据库表中的对应字段的数据时,尽可能使用序列号参数,这样可以提高效率。除Blob类型外,其他任意类型的字段都可以通过getString()方法来获取,因为所有数据类型都可以自动转换成字符串。当数据库操作执行完毕或退出应用前,需将数据库访问过程中建立的对象按顺序关闭,防止系统资源浪费。关闭的次序是:关
795、闭结果集:rs.close();关闭Statement对象:stmt.close();关闭连接:conn.close();下述案例用于示例访问数据库的一般步骤,代码如下所示。14.3.14.3.5 5访问结果集【代码14.2】ConnectionExample.javapackagecom;importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.ResultSet;importjava.sql.SQLException;importjava.sql.Statement;publicclassConnection
796、Examplepublicstaticvoidmain(Stringargs)try/加载驱动Class.forName(com.mysql.jdbc.Driver);/建立数据库连接Connectionconn=DriverManager.getConnection(jdbc:mysql:/127.0.0.1:3306/student,root,root);System.out.println(连接成功!);/创建Statment对象Statementstmt=conn.createStatement();/获取查询结果集ResultSetrs=stmt.executeQuery(SELEC
797、T*FROMt_user);14.3.14.3.5 5访问结果集System.out.println(查询成功!);/访问结果集中的数据while(rs.next()System.out.println(rs.getString(1)+rs.getString(name);/关闭结果集rs.close();/关闭载体stmt.close();/关闭连接conn.close();catch(ClassNotFoundExceptione)e.printStackTrace();catch(SQLExceptione)e.printStackTrace();14.3.14.3.5 5访问结果集上述
798、代码按照访问数据库的一般步骤编写。通过Class.forName()方法加载MySQL数据库驱动;调用DriverManager.getConnection()方法来建立MySQL数据库连接,在获取连接时需要指明数据库连接的URL、用户名和密码;通过连接对象的createStatement()方法来获取Statement对象,调用Statement对象的executeQuery()方法执行SQL语句;调用ResultSet结果集对象的next()方法将游标移动到下一条记录,再通过getXXX()方法来获取指定列中的数据;最后调用close()方法关闭所有创建的对象。程序运行结果如下所示:连接成
799、功!查询成功!19向守超20张恒14.3.14.3.5 5第第4节节part操作数据库JDBC不仅可以执行数据库查询,还可以执行DDL、DML等SQL语句,以便最大限度地操作数据库。操作数据库本节概述14.4.14.4.1 1execute()方法Statement接口的execute()方法几乎可以执行任何SQL语句,如果不清楚SQL语句的类型,则只能通过使用execute()方法来执行SQL语句。使用execute()方法执行SQL语句的返回值是boolean值,表明执行该SQL语句是否返回了ResultSet对象:当返回值为true时,可以使用Statement的getResultSet
800、()方法,来获取execute()方法执行SQL查询语句所返回的ResultSet对象;当返回值为false时,可以使用getUpdateCount()方法,来获取execute()方法执行DML语句所影响的行数。下述案例示例了Statement对象的execute()方法的使用,代码如下所示。execute()方法【代码14.3】ExecuteExample.javapackagecom;importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.ResultSet;importjava.sql.Statemen
801、t;publicclassExecuteExampleprivateStringdriver=com.mysql.jdbc.Driver;privateStringurl=jdbc:mysql:/127.0.0.1:3306/student;privateStringuser=root;privateStringpass=root;publicvoidexecuteSql(Stringsql)throwsException/加载驱动Class.forName(driver);try(/获取数据库连接Connectionconn=DriverManager.getConnection(url,u
802、ser,pass);/使用Connection来创建一个Statement对象Statementstmt=conn.createStatement()execute()方法14.4.14.4.1 1/执行SQL,返回boolean值表示是否包含ResultSetbooleanhasResultSet=stmt.execute(sql);/如果执行后有ResultSet结果集if(hasResultSet)try(/获取结果集ResultSetrs=stmt.getResultSet()/迭代输出ResultSet对象while(rs.next()/依次输出第1列的值System.out.pri
803、nt(rs.getString(1)+t);System.out.println();elseSystem.out.println(该SQL语句影响的记录有+stmt.getUpdateCount()+条);execute()方法14.4.14.4.1 1publicstaticvoidmain(Stringargs)throwsExceptionExecuteExampleexecuteObj=newExecuteExample();System.out.println(-执行建表的DDL语句-);executeObj.executeSql(createtablemy_test+(test_
804、idintprimarykey,test_namevarchar(25);System.out.println(-执行插入数据的DML语句-);executeObj.executeSql(insertintomy_test(test_id,test_name)+selectid,namefromt_user);System.out.println(-执行查询数据的查询语句-);executeObj.executeSql(selecttest_namefrommy_test);System.out.println(-执行删除表的DDL语句-);executeObj.executeSql(drop
805、tablemy_test);execute()方法14.4.14.4.1 1上述代码先定义了一个executeSql()方法,用于执行不同的SQL语句,当执行结果有ResultSet结果集时,则循环输出结果集中第3列的信息;否则输出该SQL语句所影响的记录条数。在main()方法中,调用executeSql()方法,分别执行建表、插入、查询和删除表四个SQL语句。程序运行结果如下:-执行建表的DDL语句-该SQL语句影响的记录有0条-执行插入数据的DML语句-该SQL语句影响的记录有2条-执行查询数据的查询语句-向守超张恒-执行删除表的DDL语句-该SQL语句影响的记录有0条需要注意的是:使用
806、Statement执行DDL和DML语句的步骤与执行普通查询语句的步骤基本相似。区别在于执行DDL语句后返回值为0,而执行了DML语句后返回值为受影响的行数。execute()方法14.4.14.4.1 114.4.14.4.2 2executeUpdate()方法executeUpdate()和executeLargeUpdate()方法用于执行DDL和DML语句,其中executeLargeUpdate()方法是Java8新增的方法,是增强版的executeUpdate()方法。executeLargeUpdate()方法的返回值类型为long,当DML语句影响的记录超过Integer.M
807、AX_VALUE时,建议使用该方法。下述案例示例了Statement对象的executeUpdate()方法的使用,目前MySQL数据库驱动暂不支持executeLargeUpdate()方法功能。代码如下所示。executeUpdate()方法executeUpdate()方法【代码14.4】ExecuteUpdateExample.javapackagecom;importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.Statement;publicclassExecuteUpdateExamplepriva
808、teStringdriver=com.mysql.jdbc.Driver;privateStringurl=jdbc:mysql:/127.0.0.1:3306/student;privateStringuser=root;privateStringpass=root;publicvoidcreateTable(Stringsql)throwsException/加载驱动Class.forName(driver);try(/获取数据库连接Connectionconn=DriverManager.getConnection(url,user,pass);/使用Connection来创建一个Sta
809、tment对象Statementstmt=conn.createStatement()/执行DDL,创建数据表stmt.executeUpdate(sql);14.4.14.4.2 2executeUpdate()方法publiclonginsertData(Stringsql)throwsException/加载驱动Class.forName(driver);try(/获取数据库连接Connectionconn=DriverManager.getConnection(url,user,pass);/使用Connection来创建一个Statment对象Statementstmt=conn.c
810、reateStatement()/执行DML,返回受影响的记录条数returnstmt.executeUpdate(sql);publicstaticvoidmain(Stringargs)throwsExceptionExecuteUpdateExampleelud=newExecuteUpdateExample();elud.createTable(createtablemy_test1+(test_idintprimarykey,test_namevarchar(25);System.out.println(-建表成功-);longresult=elud.insertData(inser
811、tintomy_test1(test_id,test_name)selectid,namefromt_user);System.out.println(-系统中共有+result+条记录受影响-);14.4.14.4.2 2executeUpdate()方法上述代码定义了createTable()方法来创建表,insertData()方法用于插入数据,不管是执行DDL语句还是执行DML语句,最终都是通过调用Statement对象的executeUpdate()方法来实现的。运行该程序,结果如下所示:-建表成功-系统中共有2条记录受影响-14.4.14.4.2 214.4.14.4.3 3Pre
812、paredStatement接口PreparedStatement接口继承Statement接口,该接口具有以下两个特点:PreparedStatement对象中所包含的SQL语句将进行预编译,当需要多次执行同一条SQL语句时,直接执行预先编译好的语句,其执行速度比Statement对象快;PreparedStatement可用于执行动态的SQL语句,即在SQL语句中提供参数,大大提高了程序的灵活性和执行效率。动态SQL语句使用“?”作为动态参数的占位符,示例如下所示。例如:参数化的动态SQL语句,创建PreparedStatement对象StringinsertSql=INSERTINTOu
813、serdetails(sid,name,password,sex)VALUES(?,?,?,?);PreparedStatementpstmt=conn.prepareStatement(insertSql);PreparedStatement接口PreparedStatement接口在执行带参数的SQL语句前,必须对“?”占位符参数进行赋值。PreparedStatement接口中提供了大量的setXXX()方法,通过占位符的索引完成对输入参数的赋值,根据参数的类型来选择对应的setXXX()方法,PreparedStatement接口中提供的常用setXXX()方法如表14-7所示。14.
814、4.14.4.3 3PreparedStatement接口下述案例示例了PreparedStatement的使用,代码如下所示。【代码14.5】PreparedStatementExample.javapackagecom;importjava.sql.Connection;importjava.sql.DriverManager;importjava.sql.PreparedStatement;importjava.sql.SQLException;publicclassPreparedStatementExamplepublicstaticvoidmain(Stringargs)try/加
815、载oracle驱动Class.forName(com.mysql.jdbc.Driver);/建立数据库连接Connectionconn=DriverManager.getConnection(jdbc:mysql:/127.0.0.1:3306/student,root,root);/定义带参数的sql语句StringinsertSql=INSERTINTOt_user(sid,name,password,sex)+VALUES(?,?,?,?);/创建PreparedStatement对象PreparedStatementpstmt=conn.prepareStatement(insert
816、Sql);14.4.14.4.3 3PreparedStatement接口/使用setXXX()方法对参数赋值pstmt.setInt(1,7);pstmt.setString(2,Tom);pstmt.setString(3,123456);pstmt.setByte(4,(byte)1);/执行intresult=pstmt.executeUpdate();System.out.println(插入+result+行!);/关闭载体pstmt.close();/关闭连接conn.close();catch(ClassNotFoundExceptione)e.printStackTrace(
817、);catch(SQLExceptione)e.printStackTrace();14.4.14.4.3 3PreparedStatement接口上述代码先定义一个带参数的SQL语句;再使用该语句来创建一个PreparedStatement对象;然后调用PreparedStatement对象的setXXX()方法对参数进行赋值,并调用PreparedStatement对象的executeUpdate()方法来执行SQL语句。运行该程序,结果如下所示:插入1行!14.4.14.4.3 3第第5节节part事务处理事务是保证底层数据完整的重要手段,对于任何数据库都是非常重要的。事务是由一步或几步
818、数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行。事务具有ACID四个特性:原子性(Atomicity):事务是应用中的最小执行单位,就如原子是自然界的最小颗粒一样,具有不可再分的特性。事务中的全部操作要么全部完成,要么都不执行。一致性(Consistency):事务执行之前和执行之后,数据库都必须处于一致性状态,即从执行前的一个一致状态变为另一个一致性的状态。隔离性(Isolation):各个事务的执行互不干扰,任意一个事务的内部操作对其他并发事务都是隔离的,即并发执行的事务之间不能看到对方的中间状态,并发事务之间是互不影响的。持久性(Durability):事务一
819、旦提交,对数据库所做的任何改变都永久地记录到存储器中,即保存到物理数据库中,不被丢失。事务处理事务处理事务处理过程中会涉及到事务的提交、中止和回滚三个概念。“事务提交”是指成功执行完毕事务,事务提交又分显示提交和自动提交两种;“事务中止”是指未能成功完成事务,执行中断;“事务回滚”对于中止事务所造成的变更需要进行撤销处理,即事务所做的修改全部失效,数据库返回到事务执行前的状态,事务回滚也有显示回滚和自动回滚两种。JDBC对事务操作提供了支持,其事务支持由Connection提供。JDBC的事务操作步骤如下:开启事务;执行任意多条DML语句;执行成功,则提交事务;执行失败,则回滚事务;Conne
820、ction在默认情况下会自动提交,即事务是关闭的。此种情况下,一条SQL语句更新成功后,系统会立即调用commit()方法提交到数据库,而无法对其进行rollback回滚操作。事务处理使用Connection对象的setAutoCommit()方法可开启或者关闭自动提交模式,其参数是一个布尔类型,如果参数为false,表示关闭自动提交;如果参数为true(默认),则表示打开自动提交。因此,在JDBC中,开启事务时需要调用Connection对象的setAutoCommit(false)来关闭自动提交,示例代码如下:conn.setAutoCommit(false);需要了解的是:使用Conne
821、ction对象的getAutoCommit()方法能够获取该连接的自动提交状态,可以使用该方法来检查自动提交方式是否打开。当所有的SQL语句都执行成功后,调用Connection的commit()方法来提交事务,代码如下所示:mit();如果任意一条SQL语句执行失败,则调用Connection的rollback()方法来回滚事务,代码如下所示:conn.rollback();需要注意的是:实际上,当程序遇到一个未处理的SQLException异常时,系统会非正常退出,事务也会自动回滚;但如果程序捕获该异常,则需要在异常处理块中显式地调用Connection的rollback()方法进行事务回
822、滚。事务处理下述案例示例了JDBC的事务处理过程,代码如下所示。【代码14.6】TransactionExample.javapackagecom;importjava.sql.*;publicclassTransactionExamplestaticConnectionconn;publicstaticvoidmain(Stringargs)Stringdriver=com.mysql.jdbc.Driver;Stringurl=jdbc:mysql:/127.0.0.1:3306/student;Stringuser=root;Stringpass=root;tryClass.forNam
823、e(driver);conn=DriverManager.getConnection(url,user,pass);/使用Connection来创建一个Statement对象Statementstmt=conn.createStatement();booleanautoCommit=conn.getAutoCommit();System.out.println(事务自动提交状态:+autoCommit);if(autoCommit)/关闭自动提交,开启事务conn.setAutoCommit(false);事务处理/多条DML批处理语句stmt.executeUpdate(INSERTINTO
824、t_user(id,name,password,sex)+VALUES(10,张四,123456,男);stmt.executeUpdate(INSERTINTOt_user(id,name,password,sex)+VALUES(11,刘牛,123456,女);/由于主键约束,下述语句将抛出异常stmt.executeUpdate(INSERTINTOt_user(id,name,password,sex)+VALUES(11,杨八,123456,男);/如果顺利执行则在此提交mit();/恢复原有事务提交状态conn.setAutoCommit(autoCommit);catch(Exc
825、eptione)/出现异常if(conn!=null)try/回滚conn.rollback();catch(SQLExceptionse)事务处理se.printStackTrace();e.printStackTrace();事务处理上述代码在执行多条DML批处理语句时,由于主键限制,将会在插入第三个用户时抛出主键约束异常,从而使程序转到catch语句中,通过调用rollback()方法回滚事务,撤销前面所有的操作。如果将插入第三个用户的语句注释掉,则程序会正常执行,将两条数据插入到表中。本章课后作业见教材JAVA程 序 设计本章学习目标:了解Java网络相关的API掌握Socket类及其
826、方法的使用掌握ServerSocket类的使用第十五章第十五章网络编程网络编程第第1节节partJava网络API最初,Java就是作为网络编程语言而出现的,其本身就对网络通信提供了支持,允许使用网络上的各种资源和数据,与服务器建立各种传输通道,实现数据的传输,使网络编程实现起来变得简单。Java中有关网络方面的功能都定义在包中,该包下的URL和URLConnection等类提供了以程序的方式来访问Web服务,而URLDecoder和URLEncoder则提供了普通字符串和application/x-www-form-urlencodeMIME字符串相互转换的静态方法。Java网 络API本节
827、概述Java提供InetAddress类来封装IP地址或域名,InetAddress类有两个子类:Inet4Address类和Inet6Address类,分别用于封装4个字节的IP地址和6个字节的IP地址。InetAddress内部对地址数字进行隐藏,用户不需要了解实现地址的细节,只需了解如何调用相应的方法即可。InetAddress类无构造方法,因此不能直接创建其对象,而是通过该类的静态方法创建一个InetAddress对象或InetAddress数组。InetAddress类常用方法如表15-1所示。表15-1InetAddress类常用方法15.1.15.1.1 1InetAddress
828、类InetAddress类下述案例示例了InetAddress类的使用,代码如下所示。【代码15.1】InetAddressExample.javapackagecom;importjava.io.IOException;.InetAddress;.UnknownHostException;publicclassInetAddressExamplepublicstaticvoidmain(Stringargs)try/获取本机地址信息InetAddresslocalIp=InetAddress.getLocalHost();System.out.println(localIp.getCanon
829、icalHostName()=+localIp.getCanonicalHostName();System.out.println(localIp.getHostAddress()=+localIp.getHostAddress();System.out.println(localIp.getHostName()=+localIp.getHostName();System.out.println(localIp.toString()=+localIp.toString();System.out.println(localIp.isReachable(5000)=“+localIp.isReac
830、hable(5000);System.out.println(-);InetAddress类15.1.15.1.1 1/获取指定域名地址信息InetAddressbaiduIp=InetAddress.getByName();System.out.println(baiduIp.getCanonicalHostName()=+baiduIp.getCanonicalHostName();System.out.println(baiduIp.getHostAddress()=+baiduIp.getHostAddress();System.out.println(baiduIp.getHostN
831、ame()=+baiduIp.getHostName();System.out.println(baiduIp.toString()=+baiduIp.toString();System.out.println(baiduIp.isReachable(5000)=+baiduIp.isReachable(5000);System.out.println(-);/获取指定原始IP地址信息InetAddressip=InetAddress.getByAddress(newbyte127,0,0,1);/InetAddressip=InetAddress.getByName(127.0.0.1);S
832、ystem.out.println(ip.getCanonicalHostName()=“+ip.getCanonicalHostName();System.out.println(ip.getHostAddress()=“+ip.getHostAddress();System.out.println(ip.getHostName()=“+ip.getHostName();System.out.println(ip.toString()=+ip.toString();InetAddress类15.1.15.1.1 1System.out.println(ip.isReachable(5000)
833、=“+ip.isReachable(5000);catch(UnknownHostExceptione)e.printStackTrace();catch(Exceptione)e.printStackTrace();InetAddress类上述代码分别获取本机、指定域名以及指定IP地址的InetAddress对象。其中,调用getLocalHost()可以获取本机InetAddress对象;调用getByName()可以获取指定域名的InetAddress对象;调用getByAddress()可以获取指定IP地址的InetAddress对象,该方法的参数使用字节数组存放IP地址。也可以直接通
834、过getByName()获取指定IP地址的InetAddress对象,此时,IP地址作为字符串即可。15.1.15.1.1 1程序运行结果如下所示:localIp.getCanonicalHostName()=192.168.0.101localIp.getHostAddress()=192.168.0.101localIp.getHostName()=shouchao-PClocalIp.toString()=shouchao-PC/192.168.0.101localIp.isReachable(5000)=true-baiduIp.getCanonicalHostName()=180.9
835、7.33.108baiduIp.getHostAddress()=180.97.33.108baiduIp.getHostName()=baiduIp.toString()= 1URL(UniformResourceLocator,统一资源定位器)表示互联网上某一资源的地址。资源可以是简单的文件或目录,也可以是对更为复杂对象的引用,例如对数据库或搜索引擎的查询。URL是最为直观的一种网络定位方法,符合人们的语言习惯,且容易记忆。在通常情况下,URL可以由协议名、主机、端口和资源四个部分组成,其语法格式如下所示:protocol:/host:port/resourceName其中:protoco
836、l是协议名,指明获取资源所使用的传输协议,例如http、ftp等,并使用冒号“:”与其他部分进行隔离;host是主机名,指定获取资源的域名,此部分由左边的双斜线“/”和右边的单斜线“/”或可选冒号“:”限制;port是端口,指定服务的端口号,是一个可选参数,由主机名左边的冒号“:”和右边的斜线“/”限制;resourceName是资源名,指定访问的文件名或目录。例如:URL地址http:/127.0.0.1:8080/student/index.jsp15.1.15.1.2 2U R L类URL类为了方便处理,Java将URL封装成URL类,通过URL对象记录下完整的URL信息。URL类常用方
837、法及功能如表15-2所示。15.1.15.1.2 2U R L类下述案例示例了根据指定的路径构造URL对象,并获取当前URL对象的相关属性。代码如下所示。【代码15.2】URLExample.javapackagecom;.MalformedURLException;.URL;publicclassURLExamplepublicstaticvoidmain(Stringargs)tryURLmybook=newURL(http:/127.0.0.1:8080/student/index.jsp);System.out.println(协议protocol=+mybook.getProtoco
838、l();System.out.println(主机host=+mybook.getHost();System.out.println(端口port=+mybook.getPort();System.out.println(文件filename=+mybook.getFile();System.out.println(锚ref=+mybook.getRef();System.out.println(查询信息query=+mybook.getQuery();System.out.println(路径path=+mybook.getPath();catch(MalformedURLException
839、e)e.printStackTrace();程序运行结果如下所示:协议protocol=http主机host=127.0.0.1:8080端口port=-1文件filename=/student/index.jsp锚ref=null查询信息query=null路径path=/student/index.jsp15.1.15.1.2 2U R L类URLConnection代表与URL指定的数据源的动态连接,该类提供一些比URL类更强大的服务器交互控制的方法,允许使用POST或PUT和其他HTTP请求方法将数据送回服务器。URLConnection是一个抽象类,其常用方法如表15-3所示。15.
840、1.15.1.3 3URLConnection类URLConnection类下述案例示例了使用URLConnection类读取网络资源信息并打印,代码如下所示。【代码15.3】URLConnectionExample.javapackagecom;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;.MalformedURLException;.URL;.URLConnection;publicclassURLConnectionExamplepublicstaticvoi
841、dmain(Stringargs)try/构建一URL对象URLmybook=newURL(https:/ 3URLConnection类StringinputLine;/循环读取并打印数据while(inputLine=br.readLine()!=null)System.out.println(inputLine);/关闭输入流br.close();catch(MalformedURLExceptione)e.printStackTrace();catch(IOExceptione)e.printStackTrace();上述代码的运行结果是输出指定网页页面的源代码,此处不显示输出效果,读
842、者可以自己调试程序查看。15.1.15.1.3 3URLConnection类当URL地址中包含非西欧字符时,系统会将这些非西欧字符转换成特殊编码(如“%XX”格式),此种编码称为application/x-www-form-urlencodedMIME。在编程过程中如果涉及到普通字符串和application/x-www-form-urlencodedMIME字符串之间相互转换时,就需要使用URLDecoder和URLEncoder两个工具类。URLDecoder工具类提供了一个decode(Strings,Stringenc)静态方法,该方法将application/x-www-form-
843、urlencodedMIME字符串转换成普通字符串;URLEncoder工具类提供了一个encode(Strings,Stringenc)静态方法,该方法与decode()方法正好相反,能够将普通的字符串转换成application/x-www-form-urlencodedMIME字符串。15.1.15.1.4 4U R L D e c o d e r和U R L E n c o d e r类URLDecoder和URLEcoder类下述案例示例了URLDecoder和URLEncoder两个工具类的使用,代码如下所示。【代码15.4】URLDecoderExample.javapackag
844、ecom;importjava.io.UnsupportedEncodingException;.URLDecoder;.URLEncoder;publicclassURLDecoderExamplepublicstaticvoidmain(Stringargs)try/将普通字符串转换成application/x-www-form-urlencoded字符串StringurlStr=URLEncoder.encode(面向对象程序设计Java,GBK);System.out.println(urlStr);/将application/x-www-form-urlencoded字符串转换成普通
845、字符串StringkeyWord=URLDecoder.decode(%C3%E6%CF%F2%B6%D4%CF%F3%B3%CC%D0%F2%C9%E8%BC%C6Java,GBK);System.out.println(keyWord);catch(UnsupportedEncodingExceptione)e.printStackTrace();程序运行结果如下:%C3%E6%CF%F2%B6%D4%CF%F3%B3%CC%D0%F2%C9%E8%BC%C6Java面向对象程序设计Java15.1.15.1.4 4U R L D e c o d e r和U R L E n c o d e
846、 r类第第2节节part基于TCP的网路编程TCP/IP通信协议是一种可靠的、双向的、持续的、点对点的网络协议。使用TCP/IP协议进行通信时,会在通信的两端各建立一个Socket(套接字),从而在通信的两端之间形成网络虚拟链路,其通信原理如图15.1所示。基 于 TCP的网路编程本节概述Java对基于TCP的网络通信提供了封装,使用Socket对象封装了两端的通信端口。Socket对象屏蔽了网络的底层细节,例如媒体类型、信息包的大小、网络地址、信息的重发等。Socket允许应用程序将网络连接当成一个IO流,既可以向流中写数据,也可以从流中读取数据。一个Socket对象可以用来建立Java的I
847、O系统到Internet上的任何机器(包括本机)的程序连接。包中提供了网络编程所需的类,其中基于TCP协议的网络编程主要使用下面两种Socket:ServerSocket:是服务器套接字,用于监听并接收来自客户端的Socket连接;Socket:是客户端套接字,用于实现两台计算机之间的通信。基 于 TCP的网路编程15.2.15.2.1 1Socket类使用Socket套接字可以较为方便地在网络上传递数据,从而实现两台计算机之间的通信。通常客户端使用Socket来连接指定的服务器,Socket的两个常用构造方法如下:Socket(InetAddress|Stringhost,intport):
848、创建连接到指定远程主机和端口号的Socket对象,该构造方法没有指定本地地址和本地端口号,默认使用本地主机IP地址和系统动态分配的端口;Socket(InetAddress|Stringhost,intport,InetAddresslocalAddr,intlocalPort):创建连接到指定远程主机和端口号的Socket对象,并指定本地IP地址和本地端口号,适用于本地主机有多个IP地址的情况。需要注意的是:上述两个Socket构造方法都声明抛出IOException异常,因此在创建Socket对象必须捕获或抛出异常。端口号建议采用注册端口(范围是102449151之间的数),通常应用程序使
849、用该范围内的端口,以防止发生冲突。Socket类例如:创建Socket对象trySockets=newSocket(192.168.1.128,9999);./Socket通信catch(IOExceptione)e.printStackTrace();除了构造方法,Socket类常用的其他方法如表15-4所示。15.2.15.2.1 1Socket类通常使用Socket进行网络通信的具体步骤如下:根据指定IP地址和端口号创建一个Socket对象;调用getInputStream()方法或getOutputStream()方法打开连接到Socket的输入/输出流;客户端与服务器根据协议进行交互
850、,直到关闭连接;关闭客户端的Socket。下述案例示例了创建客户端Socket的过程,代码如下所示。15.2.15.2.1 1Socket类【代码15.5】ClientSocketExample.javapackagecom;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.PrintStream;.Socket;.UnknownHostException;publicclassClientSocketExamplepublicstaticvo
851、idmain(Stringargs)try/创建连接到本机、端口为9999的Socket对象Socketsocket=newSocket(127.0.0.1,9999);/将Socket对应的输出流包装成PrintStreamPrintStreamps=newPrintStream(socket.getOutputStream();/往服务器发送信息ps.println(我喜欢Java);ps.flush();/将Socket对应的输入流包装成BufferedReaderBufferedReaderbr=newBufferedReader(newInputStreamReader(socket
852、.getInputStream();15.2.15.2.1 1Socket类/读服务器返回的信息并显示Stringline=br.readLine();System.out.println(来自服务器的数据:+line);/关闭br.close();ps.close();socket.close();catch(UnknownHostExceptione)e.printStackTrace();catch(IOExceptione)e.printStackTrace();上述代码先创建了一个连接到本机、端口为9999的Socket对象;再使用getOutputStream()获取Socket对
853、象的输出流,用于往服务器发送信息;然后使用getInputStream()获取Socket对象的输入流,读取服务器返回的数据;最后关闭输入/输出流和Socket连接,释放所有的资源。15.2.15.2.1 1Socket类15.2.15.2.2 2ServletSocket类ServerSocket是服务器套接字,运行在服务器端,通过指定端口主动监听来自客户端的Socket连接。当客户端发送Socket请求并与服务器端建立连接时,服务器将验证并接收客户端的Socket,从而建立客户端与服务器之间的网络虚拟链路;一旦两端的实体之间建立了虚拟链路,就可以相互传送数据。ServerSocket类常用
854、的构造方法如下:ServerSocket(intport):根据指定端口来创建一个ServerSocket对象;ServerSocket(intport,intbacklog):创建一个ServerSocket对象,指定端口和连接队列长度,此时增加一个用来改变连接队列长度的参数backlog;ServerSocket(intport,intbacklog,InetAddresslocalAddr):创建一个ServerSocket对象,指定端口、连接队列长度和IP地址;当机器拥有多个IP地址时,才允许使用localAddr参数指定具体的IP地址。ServletSocket类需要注意的是:Ser
855、verSocket类的构造方法都声明抛出IOException异常,因此在创建ServerSocket对象必须捕获或抛出异常。另外,在选择端口号时,建议选择注册端口(范围是102449151的数),通常应用程序使用这个范围内的端口,以防止发生冲突。下面几行代码示例了创建一个ServerSocket对象:tryServerSocketserver=newServerSocket(9999);catch(IOExceptione)e.printStackTrace();ServerSocket类常用的其他方法如表15-5所示。15.2.15.2.2 2ServletSocket类通常使用Serve
856、rSocket进行网络通信的具体步骤如下:根据指定的端口号来实例化一个ServerSocket对象;调用ServerSocket对象的accept()方法接收客户端发送的Socket对象;调用Socket对象的getInputStream()/getOutputStream()方法来建立与客户端进行交互的IO流;服务器与客户端根据一定的协议交互,直到关闭连接;关闭服务器端的Socket;回到第2步,继续监听下一次客户端发送的Socket请求连接。下述案例示例了创建服务器端ServerSocket的过程,代码如下所示。15.2.15.2.2 2ServletSocket类【15.6】Server
857、SocketExample.javapackagecom;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.PrintStream;.ServerSocket;.Socket;publicclassServerSocketExampleextendsThread/声明一个ServerSocketServerSocketserver;/计数intnum=0;publicServerSocketExample()/创建ServerSocket,用于
858、监听9999端口是否有客户端的Sockettryserver=newServerSocket(9999);catch(IOExceptione)e.printStackTrace();15.2.15.2.2 2ServletSocket类/启动当前线程,即执行run()方法this.start();System.out.println(服务器启动.);publicvoidrun()while(this.isAlive()try/接收客户端的SocketSocketsocket=server.accept();/将Socket对应的输入流包装成BufferedReaderBufferedRead
859、erbr=newBufferedReader(newInputStreamReader(socket.getInputStream();/读客户端发送的信息并显示Stringline=br.readLine();System.out.println(line);/将Socket对应的输出流包装成PrintStreamPrintStreamps=newPrintStream(socket.getOutputStream();/往客户端发送信息ps.println(您是第+(+num)+个访问服务器的用户!);ps.flush();15.2.15.2.2 2ServletSocket类/关闭br.
860、close();ps.close();socket.close();catch(IOExceptione)/TODOAuto-generatedcatchblocke.printStackTrace();publicstaticvoidmain(Stringargs)newServerSocketExample();15.2.15.2.2 2ServletSocket类上述代码服务器端是一个多线程应用程序,能为多个客户提供服务。在ServerSocketExample()构造方法中,先创建一个用于监听9999端口的ServerSocket对象,再调用this.start()方法启动线程。在线程
861、的run()方法中,先调用ServerSocket对象的accept()方法来接收客户端发送的Socket对象;再使用getInputStream()获取Socket对象的输入流,用于读取客户端发送的数据信息;然后使用getOutputStream()获取Socket对象的输出流,往客户端发送信息;最后关闭输入、输出流和Socket,释放所有资源。前面编写的客户端程序ClientSocketExample与服务器端程序ServerSocketExample能够形成网络通信,运行时先运行服务器端ServerSocketExample应用程序,服务器端先显示如下提示:服务器启动.然后,运行客户端C
862、lientSocketExample应用程序,此时服务器端又会增加打印一条信息:我喜欢Java客户端应用程序会显示:来自服务器的数据:您是第1个访问服务器的用户!15.2.15.2.2 2ServletSocket类一般服务器和客户端之间,使用Socket进行基于C/S架构的网络通信,程序设计的过程如下:服务器端通过某个端口监听是否有客户端发送Socket连接请求;客户端向服务器端发出一个Socket连接请求;服务器端调用accept()接收客户端Socket并建立连接;通过调用Socket对象的getInputStream()/getOutputStream()方法进行IO流操作,服务器与客
863、户端之间进行信息交互;关闭服务器端和客户端的Socket。15.2.15.2.2 2ServletSocket类15.2.15.2.3 3聊天室基于TCP网络编程的典型应用就是聊天室,下述内容使用Socket和ServerSocket实现多人聊天的聊天室程序。聊天室程序是基于C/S架构,分客户端代码和服务器端代码。其中,客户端是一个窗口应用程序,代码如下。聊天室【代码15.7】ChatClient.javapackagecom;importjava.awt.BorderLayout;importjava.awt.event.ActionEvent;importjava.awt.event.Ac
864、tionListener;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.PrintWriter;.Socket;.UnknownHostException;importjavax.swing.*;/聊天室客户端publicclassChatClientextendsJFrameSocketsocket;PrintWriterpWriter;BufferedReaderbReader;JPanelpanel;JScrollPanesPane;
865、JTextAreatxtContent;15.2.15.2.3 3聊天室JLabellblName,lblSend;JTextFieldtxtName,txtSend;JButtonbtnSend;publicChatClient()super(聊天室);txtContent=newJTextArea();/设置文本域只读txtContent.setEditable(false);sPane=newJScrollPane(txtContent);lblName=newJLabel(昵称:);txtName=newJTextField(5);lblSend=newJLabel(发言:);txtS
866、end=newJTextField(20);btnSend=newJButton(发送);panel=newJPanel();panel.add(lblName);panel.add(txtName);panel.add(lblSend);panel.add(txtSend);panel.add(btnSend);15.2.15.2.3 3聊天室this.add(panel,BorderLayout.SOUTH);this.add(sPane);this.setSize(500,300);this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);t
867、ry/创建一个套接字socket=newSocket(127.0.0.1,9999);/创建一个往套接字中写数据的管道,即输出流,给服务器发送信息pWriter=newPrintWriter(socket.getOutputStream();/创建一个从套接字读数据的管道,即输入流,读服务器的返回信息bReader=newBufferedReader(newInputStreamReader(socket.getInputStream();catch(UnknownHostExceptione)e.printStackTrace();catch(IOExceptione)e.printStac
868、kTrace();/注册监听btnSend.addActionListener(newActionListener()publicvoidactionPerformed(ActionEvente)15.2.15.2.3 3聊天室/获取用户输入的文本StringstrName=txtName.getText();StringstrMsg=txtSend.getText();if(!strMsg.equals()/通过输出流将数据发送给服务器pWriter.println(strName+说:+strMsg);pWriter.flush();/清空文本框txtSend.setText(););/启
869、动线程newGetMsgFromServer().start();/接收服务器的返回信息的线程classGetMsgFromServerextendsThreadpublicvoidrun()while(this.isAlive()try15.2.15.2.3 3聊天室StringstrMsg=bReader.readLine();if(strMsg!=null)/在文本域中显示聊天信息txtContent.append(strMsg+n);Thread.sleep(50);catch(Exceptione)e.printStackTrace();publicstaticvoidmain(St
870、ringargs)/创建聊天室客户端窗口实例,并显示newChatClient().setVisible(true);15.2.15.2.3 3聊天室上述代码在构造方法中先创建客户端图形界面,并创建一个Socket对象连接服务器,然后获取Socket对象的输入流和输出流,用于与服务器进行信息交互,输出流可以给服务器发送信息,输入流可以读取服务器的返回信息。再对“发送”按钮添加监听事件处理,当用户单击“发送”按钮时,将用户在文本框中输入的数据通过输出流写到Socket中,实现将信息发送给服务器。GetMsgFromServer是一个用于不断循环接收服务器的返回信息的线程,只要接收到服务器的信息,
871、就将该信息在窗口的文本域中显示。注意在构造方法的最后创建一个GetMsgFromServer线程实例并启动。聊天室的服务器端代码如下所示。15.2.15.2.3 3聊天室【代码15.8】ChatServer.javapackagecom;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStreamReader;importjava.io.PrintWriter;.ServerSocket;.Socket;importjava.text.SimpleDateFormat;importjava.ut
872、il.ArrayList;importjava.util.Date;importjava.util.LinkedList;/聊天室服务器端publicclassChatServer/声明服务器端套接字ServerSocketServerSocketserverSocket;/输入流列表集合ArrayListbReaders=newArrayList();/输出流列表集合ArrayListpWriters=newArrayList();15.2.15.2.3 3聊天室/聊天信息链表集合LinkedListmsgList=newLinkedList();publicChatServer()try/
873、创建服务器端套接字ServerSocket,在28888端口监听serverSocket=newServerSocket(9999);catch(IOExceptione)e.printStackTrace();/创建接收客户端Socket的线程实例,并启动newAcceptSocketThread().start();/创建给客户端发送信息的线程实例,并启动newSendMsgToClient().start();System.out.println(服务器已启动.);/接收客户端Socket套接字线程classAcceptSocketThreadextendsThreadpublicvoi
874、drun()while(this.isAlive()try15.2.15.2.3 3聊天室/接收一个客户端Socket对象Socketsocket=serverSocket.accept();/建立该客户端的通信管道if(socket!=null)/获取Socket对象的输入流BufferedReaderbReader=newBufferedReader(newInputStreamReader(socket.getInputStream();/将输入流添加到输入流列表集合中bReaders.add(bReader);/开启一个线程接收该客户端的聊天信息newGetMsgFromClient(
875、bReader).start();/获取Socket对象的输出流,并添加到输入出流列表集合中pWriters.add(newPrintWriter(socket.getOutputStream();catch(IOExceptione)e.printStackTrace();15.2.15.2.3 3聊天室/接收客户端的聊天信息的线程classGetMsgFromClientextendsThreadBufferedReaderbReader;publicGetMsgFromClient(BufferedReaderbReader)this.bReader=bReader;publicvoid
876、run()while(this.isAlive()try/从输入流中读一行信息StringstrMsg=bReader.readLine();if(strMsg!=null)/SimpleDateFormat日期格式化类,指定日期格式为/年-月-日时:分:秒,例如2015-11-0613:50:26SimpleDateFormatdateFormat=newSimpleDateFormat(yyyy-MM-ddHH:mm:ss);/获取当前系统时间,并使用日期格式化类格式化为指定格式的字符串StringstrTime=dateFormat.format(newDate();/将时间和信息添加到
877、信息链表集合中msgList.addFirst(n+strMsg);catch(Exceptione)15.2.15.2.3 3聊天室e.printStackTrace();15.2.15.2.3 3聊天室/给所有客户发送聊天信息的线程classSendMsgToClientextendsThreadpublicvoidrun()while(this.isAlive()try/如果信息链表集合不空(还有聊天信息未发送)if(!msgList.isEmpty()/取信息链表集合中的最后一条,并移除Stringmsg=msgList.removeLast();/对输出流列表集合进行遍历,循环发送信
878、息给所有客户端for(inti=0;ipWriters.size();i+)pWriters.get(i).println(msg);pWriters.get(i).flush();catch(Exceptione)e.printStackTrace();15.2.15.2.3 3聊天室publicstaticvoidmain(Stringargs)newChatServer();在上述代码中,聊天室的服务器是基于多个线程的应用程序,其中:创建的AcceptSocketThread线程用于循环接收客户端发来的Socket连接,每当接收到一个客户端的Socket对象时,就建立服务器与该客户端的通
879、信管道,即将该Socket对象的输入流和输出流保存到ArrayList列表集合中;创建的GetMsgFromClient线程用于接收客户端发来的聊天信息,并将信息保存到LinkedList链表集合中。创建的SendMsgToClient线程用于将LinkedList链表集合中的聊天信息循环发给所有客户端。15.2.15.2.3 3聊天室在聊天室服务器端的构造方法ChatServer()中,先后实例化AcceptSocketThread和GetMsgFromClient两个线程对象并启动;在AcceptSocketThread线程执行过程中,每当接收一个Socket对象时,则说明新开启一个客户端
880、,此时要建立与客户端的通信管道,并实例化一个GetMsgFromClient线程对象来接收客户端的聊天信息。通过服务器端的多线程实现多个人聊天的功能,是客户端都能看到大家发送的所有聊天信息。15.2.15.2.3 3聊天室运行测试时,依然是先运行服务器端,服务器端在控制台输出如下提示:服务器已启动然后运行两个客户端,如图15.2所示,在各个客户端分别发送聊天信息,窗口中显示聊天室所有人的所有对话内容。在局域网环境中,需要指定其中的一台计算机作为服务器并运行服务器端应用程序;修改客户端程序,将创建Socket的本机IP“127.0.0.1”改为服务器的真正IP地址,然后在其他不同的计算机上运行客户端应用程序,可以更好地测试该聊天室应用程序。15.2.15.2.3 3聊天室本章课后作业见教材