java入门到精通
2023-06-09 01:19:08 15 举报
AI智能生成
java
作者其他创作
大纲/内容
初始java
java简介
java的版本
javaSE
javaEE
怎么学好java
明确自己的学习目标和大的方向,选择并锁定一门语言,然后按照自己的方向努力学习,认真研究。
不要急躁。遇到技术问题,必须冷静对待,不要让自己思维混乱。保持清醒的头脑才能分析和解决各种问题。可以尝试用听歌、散步等方式来放松自己。
多阅读别人的源代码。不但要看懂他人的程序代码,还要分析他人的编程思想和设计模式,并化为己用。
Java APL文档
API的全称是Application Programming Interface,即应用程序编程接口,主要包括类的继承结构、成员变量、成员方法、构造方法、静态成员的描述信息和详细说明等内容。读者朋友可以在https://docs.oracle.com/en/java/javase/11/docs/api/index.html中找到JDK 11的API文档,页面效果如图1.2所示。
java语言的特性
简单
语法规则和C++类似。从某种意义上讲,Java语言是由C和C++语言转变而来的,所以C/C++程序设计人员可以很容易地掌握Java语言的语法。
Java语言对C++进行了简化和提高。例如,Java使用接口取代了多重继承,并取消了指针,因为指针和多重继承通常使程序变得复杂。Java语言还通过垃圾自动收集,大大简化了程序设计人员的资源释放管理工作。
Java提供了丰富的类库、API文档以及第三方开发包,另外还有大量基于Java的开源项目。JDK(Java开发者工具箱)已经开放源代码,读者可以通过分析项目的源代码,提高自己的编程水平。
面向对象
面向对象是Java语言的基础,也是Java语言的重要特性,它本身就是一种纯面向对象的程序设计语言。Java提倡万物皆对象,语法中不能在类外面定义单独的数据和函数,也就是说,Java语言最外部的数据类型是对象,所有的元素都要通过类和对象来访问。
分布性
Java的分布性包括操作分布和数据分布,其中操作分布是指在多个不同的主机上布置相关操作,而数据分布是将数据分别存放在多个不同的主机上,这些主机是网络中的不同成员。Java可以凭借URL(统一资源定位符)对象访问网络对象,访问方式与访问本地系统相同。
可移植性
Java程序具有与体系结构无关的特性,可以非常方便地移植到网络上的不同计算机中。同时,Java的类库也实现了针对不同平台的接口,使得这些类库也可以被移植。
解释型
运行Java程序需要解释器。任何移植了Java解释器的计算机或其他设备都可以用Java字节码进行解释执行。字节码独立于平台,它本身携带了许多编译时的信息,使得连接过程更加简单,开发过程更加迅速,更具探索性。
安全性
Java语言取消了类C语言中的指针和内存释放等语法,有效地避免了用户对内存的非法操作。Java程序代码要经过代码校验、指针校验等很多测试步骤才能够运行,所以未经允许的Java程序不可能运行,也不可能出现损害系统平台的行为,而且使用Java可以编写出防病毒和防修改的系统。
健壮性
Java语言的设计目标之一,是能编写出多方面、可靠的应用程序。因此,Java会检查程序在编译和运行时的错误,并消除错误。类型检查能帮助用户检查出许多在开发早期出现的错误,集成开发工具(如Eclipse、NetBeans)的出现也使得编译和运行Java程序更加容易。
多线程
Java语言支持多线程机制,能够使应用程序在同一时间并行执行多项任务,而且相应的同步机制可以保证不同线程能够正确地共享数据。使用多线程,可以带来更好的交互能力和实时行为。
高性能
Java编译后的字节码是在解释器中运行的,所以它的速度较多数交互式应用程序提高了很多。另外,字节码可以在程序运行时被翻译成特定平台的机器指令,从而进一步提高运行速度。
动态
Java在很多方面比C和C++更能够适应发展的环境,可以动态调整库中方法和增加变量,而客户端却不需要任何更改。在Java中进行动态调整是非常简单和直接的。
搭建java环境
下载JDK
打开浏览器,输入网址http://jdk.java.net,在页面下方的Reference implementations栏右侧单击如图1.3所示的“11”超链接。
在Open JDK 11的详情页中,单击Windows/x64 Java Development Kit超链接,即可下载Open JDK 11的zip安装包,操作如图1.4所示。
在Windws 10系统配置JDK环境
解压缩
下载完Open JDK 11的zip格式压缩包后,将压缩包中的所有文件解压到计算机硬盘中,例如解压到D盘根目录的Java文件夹中,效果如图1.5所示。
配置环境变量
在桌面上的“此电脑”图标上单击鼠标右键,在弹出的快捷菜单中选择“属性”命令,接着在弹出的窗体左侧单击“高级系统设置”超链接,如图1.6所示。
单击完“高级系统设置”超链接后将打开如图1.7所示的“系统属性”对话框。单击“环境变量”按钮,将弹出如图1.8所示的“环境变量”对话框。先选中“系统变量”栏中的Path变量,再单击下方的“编辑”按钮。
单击完“编辑”按钮之后会打开如图1.9所示的“编辑环境变量”对话框。首先单击右侧的“新建”按钮,列表中会出现一个空的环境变量,然后将解压完成的JDK根目录路径下的bin路径填充到这个空环境变量中,最后单击下方的“确定”按钮。
逐个单击对话框中的“确定”按钮,依次退出上述对话框后,即可完成在Windows 10下配置JDK环境变量的相关操作。JDK环境变量配置完成后,需确认其是否配置准确。在Windows 10下测试JDK环境需要先单击桌面左下角的[插图]图标(在Windows 7系统下单击[插图]图标),在下方搜索框中输入“cmd”,如图1.10所示,然后按Enter键启动“命令提示符”对话框。
在“命令提示符”对话框中输入“java -version”命令,按Enter键,将显示如图1.11所示的JDK版本信息。如果显示当前JDK版本号、位数等信息,则说明JDK环境已搭建成功。如果显示“XXX不是内部或外部命令……”,则说明搭建失败,请重新检查在环境变量中填写的路径是否正确。
熟悉Eclipse开发工具
熟悉Eelipse
虽然使用记事本和JDK编译工具也可以编写Java程序,但在项目开发过程中必须使用大型集成开发工具(IDE)来编写Java程序。使用IDE工具可以避免编码错误,更好地管理项目结构,且其代码辅助功能可帮助开发者快速输入程序代码。本节就来介绍Eclipse开发工具,包括其下载、安装、配置与启动、菜单栏、工具栏以及各种视图的作用等。
Eclipse简介
Eclipse由IBM公司投资4000万美元开发而成,它基于Java语言编写,是目前最流行的Java集成开发工具之一。Eclipse所有代码都是开源的,可扩展,其后续开发由Eclipse联盟负责。Eclipse为编程人员提供了一流的Java程序开发环境,它的平台体系结构是在插件概念的基础上构建的,插件是Eclipse平台最具特色的特征之一,也是其区别于其他开发工具的特征之一。
下载Eclipse
1.打开浏览器,在地址栏中输入https://www.eclipse.org/downloads/后,按Enter键访问Eclipse的官网首页,然后单击如图2.1所示的Download Packages超链接。
子主题
2.进入Eclipse IDE Downloads页面,在Eclipse IDE for Java Developers下载列表中,单击右侧的Windows 64-bit(或32-bit)超链接,如图2.2所示。
子主题
3.Eclipse服务器会根据客户端所在的地理位置,分配合理的下载镜像站点,如图2.3所示。建议使用默认镜像地址,这里直接单击页面中的Download按钮即可。
子主题
4.单击Download按钮之后,若5秒后仍未开始下载任务,可单击如图2.4所示中的click here超链接,重新开启下载任务。
子主题
安装Eclipse的中文语言包
从网站中下载的Eclipse安装文件是一个压缩包,将其解压缩到指定的文件夹,然后运行文件夹中的Eclipse.exe文件,即可启动Eclipse开发工具。但是在启动Eclipse之前需要安装中文语言包,以降低读者的学习难度。
1。在浏览器的地址栏中输入https://www.eclipse.org/babel/downloads.php,在下载页面的Babel Language Packs Zips选项下选择对应Eclipse版本的超链接下载语言包。例如,本书使用的Eclipse版本为2020-12,所以单击该2020-12超链接,如图2.5所示。
2.在弹出的下载列表页面中,在Language: Chinese(Simplified)列表下选择并单击如图2.6所示的BabelLanguagePack-eclipse-zh_X.X.X超链接。X.X.X为汉化包的版本号,因为官方会频繁更新汉化包,但文件的前缀不会改变,所以读者只要下载前缀为BabelLanguagePack-eclipse-zh_的zip文件即可。
3.单击超链接后,Eclipse服务器会根据客户端所在的地理位置,分配合理的下载镜像站点,如图2.7所示。读者只需单击Download按钮,即可下载汉化包。
4.将下载完成的汉化包解压缩,解压后生成的eclipse文件夹下有两个子文件夹:features文件夹和plugins文件夹。将这两个子文件夹覆盖到Eclipse程序的根目录下,如图2.8所示。重启Eclipse之后就可以看到汉化效果。
Eclipse的配置与启动
配置好Eclipse的汉化语言包后,就可以启动Eclipse了。在Eclipse的安装文件夹中运行eclipse.exe文件,即开始启动Eclipse,将弹出“Eclipse启动程序”对话框,该对话框用于设置Eclipse的工作空间(用于保存Eclipse建立的程序项目和相关设置)。本书的开发环境统一设置工作空间为Eclipse安装位置的workspace文件夹,在“Eclipse启动程序”对话框的“工作空间”文本框中输入“.\eclipse-workspace”,单击“启动”按钮,即可启动Eclipse,如图2.9所示。
Eclipse首次启动时,会显示Eclipse欢迎界面,如图2.10所示。单击标题栏上的×按钮,即可关闭该界面。
Eclipse工作台
Eclipse的“欢迎”界面中,单击“工作台”(Workbench)按钮或关闭欢迎界面,将显示Eclipse的工作台,它是程序开发人员开发程序的主要场所。Eclipse还可以将各种插件无缝地集成到工作台中,也可以在工作台中开发各种插件。Eclipse工作台主要包括标题栏、菜单栏、工具栏、编辑器、透视图和相关的视图等,如图2.11所示。在接下来的章节中将介绍Eclipse的透视图、视图、菜单栏与工具栏,并介绍常用视图。
透视图与视图
透视图
透视图是Eclipse工作台提供的附加组织层,它实现多个视图的布局和可用操作的集合,并为这个集合定义一个名称,起到一个组织的作用。例如,Eclipse提供的Java透视图组织了与Java程序设计有关的视图和操作的集合,而“调试”透视图负责组织与程序调试有关的视图和操作集。在Eclipse的Java开发环境中提供了几种常用的透视图,如Java透视图、“资源”透视图、“调试”透视图、“小组同步”透视图等。不同的透视图之间可以进行切换,但是同一时刻只能使用一个透视图。
视图
视图多用于浏览信息的层次结构和显示活动编辑器的属性。例如,“控制台”视图用于显示程序运行时的输出信息和异常错误,而“包资源管理器”视图可以浏览项目的文件组织结构。视图可以单独出现,也可以与其他视图以选项卡样式叠加在一起。视图有自己独立的菜单和工具栏,并且可以通过拖动改变布局位置。
菜单栏
Eclipse的菜单栏包含了Eclipse的基本命令,在使用不同的编辑器时,还会动态地添加有关该编辑器的菜单。基本的菜单栏中除了常用的“文件”“编辑”“窗口”“帮助”等菜单,还提供了一些功能菜单,如“源代码”和“重构”等,如图2.12所示。
不同菜单中包含着不同的命令,这些命令用于完成最终的操作,如文件的打开与保存、代码的格式化、程序的运行与分步调试等。各菜单所包含的命令如图2.13所示。
子主题
文件”菜单
文件”菜单中包含“新建”“保存”“关闭”“打印”“切换工作空间”“属性”等命令。“文件”菜单中包含的内容虽多,但也有常用和不常用之分。例如,如果不常使用打印功能,那么“文件”菜单中的“打印”命令会很少用到。“文件”菜单中的常用命令如表2.1所示。
“编辑”菜单
“编辑”菜单用于辅助程序代码设计工作,除常用的“剪切”“复制”“粘贴”命令外,还提供了“快速修正”“将选择范围扩展到”“内容辅助”等高级命令。“编辑”菜单中的常用命令如表2.2所示。
“源代码”菜单中包含的命令都是和代码编写相关的命令,主要用于辅助编程。“源代码”菜单的常用命令如表2.3所示。
“重构”菜单
“重构”菜单是Eclipse最关键的菜单,主要包括项目重构的相关命令,应该重点掌握。“重构”菜单中的常用命令如表2.4所示。
工具栏
主工具栏
主工具栏就是位于Eclipse菜单栏下方的工具栏,其内容将根据不同的透视图和不同类型的编辑器显示相关工具按钮,如图2.14所示。
视图工具栏
Eclipse界面中包含多种视图,这些视图有不同的用途(有关视图的概念已在2.1.6节中讲述),可以根据视图的功能需求在视图的标题栏位置添加相应的视图工具栏。例如,“控制台”视图用于输出程序运行中的输出结果和运行时异常信息,其工具栏如图2.15所示。
透视图工具栏
透视图工具栏主要包括切换已经打开的不同透视图的缩略按钮以及打开其他视图的按钮。在相应的工具按钮上右击会弹出透视图的管理菜单,实现透视图的定制、关闭、复位、布局位置、是否显示文本等操作,如图2.16所示。
“包资源管理器”视图
包资源管理器”视图用于浏览项目结构中的Java元素,包括包、类、类库的引用等,但最主要的用途还是操作项目中的源代码文件。“包资源管理器”视图的界面如图2.17所示。
“控制台”视图
“控制台”视图用于显示程序运行的输出结果和异常信息(Runtime Exception)。在学习Swing程序设计之前,必须使用控制台实现与程序的交互。例如,为方便调试某个方法,该视图在方法执行前后会分别输出“方法开始”和“方法结束”信息。“控制台”视图的界面如图2.18所示。
使用Eclipse
创建Java项目
选择“文件”/“新建”/“[插图]项目”命令,打开“新建项目”对话框,该对话框包含创建项目的向导,在向导中选择“[插图]Java项目”节点,单击“下一步”按钮。
弹出“新建Java项目”对话框,在“项目名”文本框中输入“MyProject”,在“项目布局”栏中选中“为源文件和类文件创建单独的文件夹”单选按钮,如图2.19所示,然后单击“完成”按钮。
此时将弹出如图2.20所示的新建模块化声明文件对话框。因为新建的模块化声明文件会影响Java项目的运行,因此这里单击Don’t Create按钮。至此,已完成Java项目的新建操作。
创建Java类文件
在“源文件夹”文本框中输入项目源程序文件夹的位置。通常向导会自动填写该文本框,没有特殊情况,不需要修改。
在“包”文本框中输入类文件的包名,这里暂时默认为空,不输入任何信息,这样就会使用Java工程的默认包。
)在“名称”文本框中输入新建类的名称,如HelloJava。
选中public static void main(String[] args)复选框,向导在创建类文件时,会自动为该类添加main()方法,使该类成为可以运行的主类。
使用编辑器编写程序代码
在使用向导创建Java类文件之后,会自动打开Java编辑器编辑新创建的Java类文件。除此之外,打开Java编辑器最常用的方法是在“包资源管理器”视图中双击Java源文件或在Java源文件处右击并在弹出的快捷菜单中选择“打开方式”/“Java编辑器”命令。Java编辑器的界面如图2.22所示。
Eclipse的强大之处并不在于编辑器能突出显示Java语法,而在于它强大的代码辅助功能。在编写Java程序代码时,可以使用Ctrl+Alt+/快捷键自动补全Java关键字,也可以使用Alt+/快捷键启动Eclipse代码辅助菜单。在使用向导创建HelloJava类之后,向导会自动构建HelloJava类结构的部分代码,并建立main()方法,程序开发人员需要做的就是将代码补全,为程序添加相应的业务逻辑。本程序的完整代码如图2.23所示。
运行Java程序
elloJava类包含main()方法,它是一个可以运行的主类。例如,在Eclipse中运行HelloJava程序,可以在“包资源管理器”视图的HelloJava文件处右击,在弹出的快捷菜单中选择“运行方式”/“[插图]Java应用程序”命令运行该程序。程序运行结果如图2.26所示。
程序调试
断点
设置断点是程序调试中必不可少的手段,Java调试器每次遇到程序断点时都会将当前线程挂起,即暂停当前程序的运行。可以在Java编辑器中显示代码行号的位置双击添加或删除当前行的断点,或者在当前行号的位置右击,在弹出的快捷菜单中选择“切换断点”命令实现断点的添加与删除,如图2.27所示。
以调试方式运行Java程序
要在Eclipse中调试HelloJava程序,可以在“包资源管理器”视图中的HelloJava文件处右击,在弹出的快捷菜单中选择“调试方式”/“[插图]Java应用程序”命令。图2.27中在第8行代码处设置了断点,调试器将在该断点处挂起当前线程,使程序暂停,如图2.28所示。
程序调试
程序执行到断点被暂停后,可以通过“调试”视图工具栏上的按钮执行相应的调试操作,如运行、停止等。“调试”视图如图2.29所示。下面对“调试”视图中的两个关键操作进行简要介绍:[插图] 单步跳过。在“调试”视图的工具栏中单击[插图]按钮或按F6键,将执行单步跳过操作,即运行单独的一行程序代码,但是不进入调用方法的内部,然后跳到下一个可执行点并暂挂线程。
java语言基础
Java主类结构
包声明
一个Java应用程序是由若干个类组成的。在例3.1中就是一个类名为Frist的类,语句package Number为声明该类所在的包,package为包的关键字(关于包的详细讲解可参见第11章)
声明成员变量和局部变量
通常将类的属性称为类的全局变量(成员变量),将方法中的属性称为局部变量。全局变量声明在类体中,局部变量声明在方法体中。全局变量和局部变量都有各自的应用范围。在例3.1中,s1是成员变量,s2是局部变量。
编写主方法
main()方法是类体中的主方法。该方法从“{”开始,至“}”结束。public、static和void分别是main()方法的权限修饰符、静态修饰符和返回值修饰符,Java程序中的main()方法必须声明为public static void。String[] args是一个字符串类型的数组,它是main()方法的参数(以后章节中将作详细的讲解)。main()方法是程序开始执行的位置。
导入API类库
在Java语言中可以通过import关键字导入相关的类。在JDK的API中(应用程序接口)提供了130多个包,如java.awt、java.io等。可以通过JDK的API文档来查看这些包中的类,把握类的继承结构、类的应用、成员变量表、构造方法表等,并对每个变量的使用目的进行了解,API文档是程序开发人员不可或缺的工具。
基础数据类型
整数类型
整数类型简称整型,用来存储整数数值,即没有小数部分的数值。可以是正数,也可以是负数。整型数据根据它所占内存大小的不同,可分为byte、short、int和long 4种类型。它们具有不同的取值范围
int型
int型是Java整型值的默认数据类型。当对多个尚未定义数据类型的整数做运算时,运算的结果将默认为int类型
byte型
byte型的定义方式与int相同。定义byte类型变量
short型
short型的定义方式与int相同。定义short类型变量
long型
由于long型的取值范围比int型大,且属于高精度数据类型,所以在赋值时要和int型做出区分,需要在整数后加L或者l(小写的L)。定义long类型变量
浮点类型
浮点类型简称浮点型,用来存储含有小数部分的数值。Java语言中浮点类型分为单精度浮点类型(float)和双精度浮点类型(double),它们具有不同的取值范围
在默认情况下小数都被看作double型,若想使用float型小数,则需要在小数后面添加F或f。另外,可以使用后缀d或D来明确表明这是一个double类型数据,但加不加d或D并没有硬性规定。而定义float型变量时,如果不加F或f,系统会认为是double类型数据,进而出错。定义浮点类型变量
创建BMIexponent类,声明double型变量height来记录身高,单位为米;声明int型变量weight记录体重,单位为千克;根据BMI=体重/(身高×身高)计算BMI指数。
字符类型
char型
字符类型(char)用于存储单个字符,占用16位(两个字节)的内存空间。在定义字符型变量时,要以单引号表示,如's'表示一个字符。但是"s"则表示一个字符串,虽然只有一个字符,但由于使用双引号,它仍然表示字符串,而不是字符。使用char关键字可定义字符变量
转义字符
转义字符是一种特殊的字符变量,它以反斜杠“\”开头,后跟一个或多个字符。转义字符具有特定的含义,不同于字符原有的意义,故称“转义”。例如,printf函数的格式串中用到的“\n”就是一个转义字符,意思是“回车换行”
布尔类型
布尔类型又称逻辑类型,简称布尔型,通过关键字boolean来定义布尔类型变量。布尔类型只有true和false两个值,分别代表布尔逻辑中的“真”和“假”。布尔值不能与整数类型进行转换。布尔类型通常被用在流程控制中,作为判断条件。定义布尔类型变量
变量与常量
标识符和关键字
标识符
标识符可以简单地理解为一个名字,是用来标识类名、变量名、方法名、数组名、文件名的有效字符序列。Java语言规定标识符由任意顺序的字母、下画线(_)、美元符号($)和数字组成,并且第一个字符不能是数字。标识符不能是Java中的关键字(保留字)。
在Java语言中,标识符中的字母是严格区分大小写的,如good和Good是不同的两个标识符。Java语言使用Unicode标准字符集,最多可以标识65535个字符。因此,Java语言中的字母不仅包括通常的拉丁文字a、b、c等,还包括汉语、日语以及其他许多语言中的文字。
关键字
关键字又称保留字,是Java语言中已经被赋予特定意义的一些单词,不可以把这些单词作为标识符来使用。3.2节介绍数据类型时提到的int、boolean等都是关键字
声明变量
变量的使用是程序设计中一个十分重要的环节。声明变量就是要告诉编译器(compiler)这个变量的数据类型,这样编译器才知道需要配置多少空间给它,以及它能存放什么样的数据。在程序运行过程中,空间内的值是变化的,这个内存空间就称为变量。为了便于操作,给这个空间取个名字,称为变量名。变量的命名必须是合法的标识符。内存空间内的值就是变量值。在声明变量时可以是没有赋值,也可以是直接赋给初值。
编写以上程序代码,究竟会产生什么样的效果呢?要了解这个问题,就需要对变量的内存配置有一定的认识。用图解的方式将上述程序代码在内存中的状况表现出来
对于变量的命名并不是随意的,应遵循以下几条规则: 变量名必须是一个有效的标识符 变量名不可以使用Java中的关键字。 变量名不能重复。 应选择有意义的单词作为变量名。
声明常量
在程序运行过程中一直不会改变的量称为常量(constant),通常也被称为“final变量”。常量在整个程序中只能被赋值一次。在为所有的对象共享值时,常量是非常有用的。
常量名通常使用大写字母,但这并不是必需的。很多Java程序员使用大写字母表示常量,是为了清楚地表明正在使用常量。
当变量被final关键字修饰时,该变量就变成了常量,必须在定义时就设定它的初值,否则将会产生编译错误。从下面的实例中可看出变量与常量的区别。
在项目中创建类Part,在类体中创建变量age与常量PI。在主方法中分别将变量与常量赋值,通过输出信息可测试变量与常量的有效范围。
从这个结果可以看到,Part类运行后发生了错误,异常日志中记载Part类出现编译问题,此编译问题正是常量number被二次赋值。
变量的有效范围
成员变量
在类体中所声明的变量被称为成员变量,成员变量在整个类中都有效。类的成员变量又可分为两种,即静态变量和实例变量。
其中,x为实例变量,y为静态变量(也称类变量)。如果在成员变量的类型前面加上关键字static,这样的成员变量称为静态变量。静态变量的有效范围可以跨类,甚至可达到整个应用程序之内。对于静态变量,除了能在声明它的类内存取,还能直接以“类名.静态变量”的方式在其他类内使用。
局部变量
在类的方法体中声明的变量(方法内部定义,“{”与“}”之间的代码中声明的变量)称为局部变量。局部变量只在当前代码块中有效,也就是只能在“{”与“}”之内使用。在类的方法中声明的变量,包括方法的参数,都属于局部变量。局部变量只在当前定义的方法内有效,不能用于类的其他方法中。局部变量的生命周期取决于方法,当方法被调用时,Java虚拟机会为方法中的局部变量分配内存空间,当该方法的调用结束后,则会释放方法中局部变量占用的内存空间,局部变量也将会被销毁。局部变量可与成员变量的名字相同,此时成员变量将被隐藏,即这个成员变量在此方法中暂时失效。
运算符
赋值运算符
赋值运算符以符号“=”表示,它是一个二元运算符(对两个操作数作处理),其功能是将右方操作数所含的值赋给左方的操作数
该表达式是将100赋值给变量a。左方的操作数必须是一个变量,而右边的操作数则可以是任何表达式,包括变量(如a、number)、常量(如123、'book')、有效的表达式(如45 * 12)。由于赋值运算符“=”处理时会先取得右方表达式处理后的结果,因此一个表达式中若含有两个以上的“=”运算符,会从最右方的“=”开始处理。
算术运算符
Java中的算术运算符主要有+(加)、-(减)、*(乘)、/(除)、%(求余),它们都是二元运算符。
其中,“+”和“-”运算符还可以作为数值的正负符号,如+5、-7。
自增和自减运算符
自增、自减运算符是单目运算符,可以放在操作元之前,也可以放在操作元之后。操作元必须是一个整型或浮点型变量。自增、自减运算符的作用是使变量的值增1或减1。放在操作元前面的自增、自减运算符,会先将变量的值加1(减1),然后再使该变量参与表达式的运算。放在操作元后面的自增、自减运算符,会先使变量参与表达式的运算,然后再将该变量加1(减1)。
比较运算符
比较运算符属于二元运算符,用于程序中的变量之间、变量和自变量之间以及其他类型的信息之间的比较。比较运算符的运算结果是boolean型。当运算符对应的关系成立时,运算结果为true,否则为false。所有比较运算符通常作为判断的依据用在条件语句中。比较运算符共有6个
在项目中创建类Compare,在主方法中创建整型变量,使用比较运算符对变量进行比较运算,并将运算后的结果输出。
逻辑运算符
返回类型为布尔型的表达式,如比较运算符,可以被组合在一起构成一个更复杂的表达式。这是通过逻辑运算符来实现的。逻辑运算符包括&(&&)(逻辑与)、‖(逻辑或)、!(逻辑非)。逻辑运算符的操作元必须是boolean型数据。在逻辑运算符中,除了“!”是一元运算符,其他都是二元运算符。表3.7给出了逻辑运算符的用法和含义。
结果为boolean型的变量或表达式可以通过逻辑运算符组合为逻辑表达式。
逻辑运算符“&&”与“&”都表示“逻辑与”,那么它们之间的区别在哪里呢?从表3.8可以看出,当两个表达式都为true时,“逻辑与”的结果才会是true。使用逻辑运算符“&”会判断两个表达式;而逻辑运算符“&&”则是针对boolean类型的类进行判断,当第一个表达式为false时则不去判断第二个表达式,直接输出结果,从而节省计算机判断的次数。通常将这种在逻辑表达式中从左端的表达式可推断出整个表达式的值的情况称为“短路”,而那些始终需要执行逻辑运算符两边的表达式才能推断出整个表达式的值的情况称为“非短路”。“&&”属于“短路”运算符,而“&”属于“非短路”运算符。
位运算符
位运算符除“按位与”和“按位或”运算符外,其他只能用于处理整数的操作数,包括byte、short、char、int和long等数据类型。位运算是完全针对位方面的操作。整型数据在内存中以二进制的形式表示,如int型变量7的二进制表示是00000000 00000000 00000000 00000111。左边最高位是符号位,最高位是0表示正数,若为1则表示负数。负数采用补码表示,如-8的二进制表示为111111111 111111111 1111111 11111000。这样就可以对整型数据进行按位运算。
“按位与”运算
“按位与”运算的运算符为“&”,为双目运算符。“按位与”运算的运算法则是:如果两个整型数据a、b对应位都是1,则结果位才是1,否则为0。如果两个操作数的精度不同,则结果的精度与精度高的操作数相同
“按位或”运算
“按位或”运算的运算符为“|”,为双目运算符。“按位或”运算的运算法则是:如果两个操作数对应位都是0,则结果位才是0,否则为1。如果两个操作数的精度不同,则结果的精度与精度高的操作数相同
“按位取反”运算
“按位取反”运算也称“按位非”运算,运算符为“~”,为单目运算符。“按位取反”就是将操作数二进制中的1修改为0,0修改为1
“按位异或”运算
“按位异或”运算的运算符是“^”,为双目运算符。“按位异或”运算的运算法则是:当两个操作数的二进制表示相同(同时为0或同时为1)时,结果为0,否则为1。若两个操作数的精度不同,则结果的精度与精度高的操作数相同
移位操作
除了上述运算符,还可以对数据按二进制位进行移位操作。Java中的移位运算符有以下3种:[插图] <<:左移。[插图] >>:右移。[插图] >>>:无符号右移。左移就是将运算符左边的操作数的二进制数据,按照运算符右边操作数指定的位数向左移动,右边移空的部分补0。右移则复杂一些。当使用“>>”符号时,如果最高位是0,右移空的位就填入0;如果最高位是1,右移空的位就填入1
三元运算符
三元运算符的运算规则为:若条件式的值为true,则整个表达式取值1,否则取值2
上述程序表达式“20 < 45”的运算结果返回真,那么boolean型变量b取值为true。相反,如果表达式的运算结果返回为假,则boolean型变量b取值为false。
运算符优先级
a中的表达式就是使用运算符连接起来的符合Java规则的式子。运算符的优先级决定了表达式中运算执行的先后顺序。通常,优先级由高到低的顺序依次是:[插图] 增量和减量运算。[插图] 算术运算。[插图] 比较运算。[插图] 逻辑运算。[插图] 赋值运算。如果两个运算有相同的优先级,那么左边的表达式要比右边的表达式先被处理。
数据类型转换
隐式类型转换
从低级类型向高级类型的转换,系统将自动执行,程序员无须进行任何操作。这种类型的转换称为隐式转换。下列基本数据类型会涉及数据转换,不包括逻辑类型和字符类型。这些类型按精度从低到高排列的顺序为byte < short < int < long <float < double。
隐式转换也要遵循一定的规则,来解决在什么情况下将哪种类型的数据转换成另一种类型的数据
显式类型转换
当把高精度的变量的值赋给低精度的变量时,必须使用显式类型转换运算(又称强制类型转换)
执行显式类型转换时,可能会导致精度损失。除boolean类型外,其他基本类型都能以显式类型转换的方法实现转换。
代码注释与编码规范
代码注释
通过在程序代码中添加注释可提高程序的可读性。注释中包含了程序的信息,可以帮助程序员更好地阅读和理解程序。在Java源程序文件的任意位置都可添加注释语句。注释中的文字Java编译器不进行编译,所有代码中的注释文字对程序不产生任何影响。Java语言提供了3种添加注释的方法,分别为单行注释、多行注释和文档注释。
单行注释
“//”为单行注释标记,从符号“//”开始直到换行为止的所有内容均作为注释而被编译器忽略。
多行注释
“/* */”为多行注释标记,符号“/*”与“*/”之间的所有内容均为注释内容。注释中的内容可以换行。
文档注释
“/** */”为文档注释标记。符号“/**”与“*/”之间的内容均为文档注释内容。当文档注释出现在声明(如类的声明、类的成员变量的声明、类的成员方法的声明等)之前时,会被Javadoc文档工具读取作为Javadoc文档内容。除注释标记不同外,文档注释的格式与多行注释的格式相同。对于初学者而言,文档注释并不是很重要,了解即可。
编码规范
在学习开发的过程中要养成良好的编码习惯,规整的代码格式会为程序日后的维护工作提供极大的便利。在此对编码规则作了以下总结,供读者学习:[插图] 每条语句尽量单独占一行,每条语句都要以分号结束。
在声明变量时,尽量使每个变量单独占一行,即使有多个数据类型相同的变量,也应将其各自放置在单独的一行上,这样有助于添加注释。对于局部变量,应在声明的同时对其赋予初始值。
在Java代码中,空格仅提供分隔使用,无其他含义,开发者应控制好空格的数量,不要写过多的无用空格
为了方便日后的维护,不要使用技术性很高、难懂、易混淆的语句。因为程序的开发者与维护者可能不是同一个人,所以应尽量使用简洁、清晰的代码编写程序需要的功能。
流程控制
复合语句
与C语言及其他语言相同,Java语言的复合语句是以整个块区为单位的语句,所以又称块语句。复合语句由开括号“{”开始,闭括号“}”结束。在前面的学习中我们已经接触到了这种复合语句。例如,在定义一个类或方法时,类体就是以“{”与“}”作为开始与结束的标记,方法体同样也是以“{”与“}”作为标记。复合语句中的每个语句都是从上到下被执行。复合语句以整个块为单位,能够用在任何一个单独语句可以使用的地方,并且在复合语句中还可以嵌套复合语句。例如,下面这段代码,在主方法中定义了复合语句块,复合语句块中还可以包含另一复合语句块。
条件语句
条件语句可根据不同的条件执行不同的语句。条件语句包括if条件语句与switch多分支语句。本节将向读者讲解条件语句的用法。
if条件语句
if条件语句是一个重要的编程语句,用于告诉程序在某个条件成立的情况下执行某段语句,而在另一种情况下执行另外的语句。使用if条件语句,可选择是否要执行紧跟在条件之后的那个语句。关键字if之后是作为条件的“布尔表达式”。如果该表达式返回的结果为true,则执行其后的语句;如果为false,则不执行if条件之后的语句。if条件语句可分为简单的if条件语句、if…else语句和if…else if多分支语句。
简单的if条件语句
布尔表达式:必要参数,表示最后返回的结果必须是一个布尔值。它可以是一个单纯的布尔变量或常量,也可以是使用关系或布尔运算符的表达式。
语句序列:可选参数。可以是一条或多条语句,当表达式的值为true时执行这些语句。若语句序列中仅有一条语句,则可以省略条件语句中的“{ }”。
if…else语句
if…else语句是条件语句中最常用的一种形式,它会针对某种条件有选择地做出处理。通常表现为“如果满足某种条件,就进行某种处理,否则就进行另一种处理”
[插图]if后面“()”内的表达式的值必须是boolean型的。如果表达式的值为true,则执行紧跟if语句的复合语句;如果表达式的值为false,则执行else后面的复合语句
if…else if多分支语句
if…else if多分支语句用于针对某一事件的多种情况进行处理。通常表现为“如果满足某种条件,就进行某种处理,否则如果满足另一种条件则执行另一种处理”。
switch多分支语句
在编程中,一个常见的问题就是检测一个变量是否符合某个条件,如果不符合,再用另一个值来检测,依此类推。当然,这种问题使用if条件语句也可以完成。
switch语句中表达式的值必须是整型、字符型、字符串类型或枚举类型,常量值1~n的数据类型必须与表达式的值的类型相同。switch语句首先计算表达式的值,如果表达式的计算结果和某个case后面的常量值相同,则执行该case语句后的若干个语句直到遇到break语句为止。此时,如果该case语句中没有break语句,将继续执行后面case中的若干个语句,直到遇到break语句为止。若没有一个常量的值与表达式的值相同,则执行default后面的语句。default语句为可选的,如果它不存在,且switch语句中表达式的值不与任何case的常量值相同,switch语句则不做任何处理。
循环语句
循环语句就是在满足一定条件的情况下反复执行某一个操作的语句。Java中提供了3种常用的循环语句,分别是while循环语句、do…while循环语句和for循环语句,下面分别进行讲解。
while循环语句
while语句也称条件判断语句,它的循环方式为利用一个条件来控制是否要继续反复执行这个语句。
当条件表达式的返回值为真时,则执行“{}”中的语句,当执行完“{}”中的语句后,重新判断条件表达式的返回值,直到表达式返回的结果为假时,退出循环。
do…while循环语句
do…while循环语句与while循环语句类似,它们之间的区别是while语句为先判断条件是否成立再执行循环体,而do…while循环语句则先执行一次循环后,再判断条件是否成立。也就是说,do…while循环语句“{}”中的程序段至少要被执行一次
for循环语句
for语句
传统的for语句中有3个表达式
表达式1:初始化表达式,负责完成变量的初始化。
表达式2:循环条件表达式,值为boolean型的表达式,指定循环条件。等同于while循环里的表达式。
表达式3:每次循环结束后执行的语句,通常用来改变循环条件。
foreach语句
foreach语句是for语句的特殊简化版本,不能完全取代for语句,但任何foreach语句都可以改写为for语句版本。foreach并不是一个关键字,习惯上将这种特殊的for语句格式称为foreach语句。foreach语句在遍历数组等方面为程序员提供了很大的方便(本书将在第5章对数组进行详细的介绍)。
foreach语句中的元素变量x,不必对其进行初始化。下面通过简单的例子来介绍foreach语句是如何遍历一维数组的。
循环控制
循环控制包含两方面的内容,一方面是控制循环变量的变化方式,另一方面是控制循环的跳转。控制循环的跳转需要用到break和continue两个关键字,这两条跳转语句的跳转效果不同,break语句是中断循环,continue语句是执行下一次循环。
break语句
使用break语句可以跳出switch结构。在循环结构中,同样也可用break语句跳出当前循环体,从而中断当前循环。
continue语句
continue语句是针对break语句的补充。continue不是立即跳出循环体,而是跳过本次循环,回到循环的条件测试部分,重新开始执行循环。在for循环语句中遇到continue后,首先执行循环的增量部分,然后进行条件测试。在while和do…while循环中,continue语句使控制直接回到条件测试部分。
创建ContinueTest类,编写一个for循环从1循环至20,如果当前循环的次数为偶数,则使用continue语句跳过循环。
标签名:任意标识符。
循环体:任意循环语句。
continue标签名:continue跳出指定的循环体,此循环体的标签名必须与continue的标签名一致。
数组
数组概述
数组是具有相同数据类型的一组数据的集合。例如,球类的集合——足球、篮球、羽毛球等;电器集合——电视机、洗衣机、电风扇等。在程序设计中,可以将这些集合称为数组。数组中的每个元素具有相同的数据类型。在Java中同样将数组看作一个对象,虽然基本数据类型不是对象,但由基本数据类型组成的数组却是对象。在程序设计中引入数组可以更有效地管理和处理数据。可根据数组的维数将数组分为一维数组、二维数组……
一维数组
一维数组实质上是一组相同类型数据的线性集合,当在程序中需要处理一组数据,或者传递一组数据时,可以应用这种类型的数组。本节将讲解一维数组的创建、初始化及使用。
创建一维数组
数组元素类型决定了数组的数据类型,它可以是Java中任意的数据类型,包括简单类型和组合类型。数组名字为一个合法的标识符,符号“[ ]”指明该变量是一个数组类型变量。单个“[ ]”表示要创建的数组是一个一维数组。
声明数组后,还不能立即访问它的任何元素,因为声明数组只是给出了数组名字和元素的数据类型,要想真正使用数组,还要为它分配内存空间。在为数组分配内存空间时必须指明数组的长度。
声明的同时为数组分配内存
这种创建数组的方法是将数组的声明和内存的分配合在一起执行。
上面的代码创建数组month,并指定了数组长度为12。这种创建数组的方法也是Java程序编写过程中普遍的做法。
初始化一维数组
从中可以看出,数组的初始化就是包括在大括号之内用逗号分开的表达式列表。用逗号(,)分割数组中的各个元素,系统自动为数组分配一定的空间。用第一种初始化方式,将创建5个元素的数组,其元素依次为1、2、3、5、25。第二种初始化方式,会创建4个元素的数组,其元素依次为34、23、12、6。
使用一维数组
在项目中创建GetDay类,在主方法中创建并初始化一个用于存储1~12月每个月份天数的int型数组,在控制台上输出1~12月每个月份的天数
二维数组
如果一维数组中的各个元素仍然是一个数组,那么它就是一个二维数组。二维数组常用于表示表,表中的信息以行和列的形式组织,第一个下标代表元素所在的行,第二个下标代表元素所在的列。
创建二维数组
同一维数组一样,二维数组在声明时也没有分配内存空间,同样要使用new关键字来分配内存,然后才可以访问每个元素。对于高维数组,有两种为数组分配内存的方式。
第二种内存分配方式是分别为每一维分配内存
上述代码创建了二维数组a,但是只声明了a第一维的长度,也就是“行数”,第二维的长度也就是“列数”,则是为每一行单独声明的,因此创建的数组a是“不定长数组”
声明的同时为数组分配内存
第二种创建方式与第一种实现的功能相同,只不过声明与赋值合并到同一行代码中。例如,创建一个2行4列的二维数组
初始化二维数组
type:数组数据类型。
arrayname:数组名称,一个合法的标识符。
alue:二维数组中各元素,都代表一个一维数组。
初始化二维数组后,要明确数组的下标都是从0开始。例如,上面的代码中myarr[1][1]的值为10。
使用二维数组
二维数组在实际应用中用得非常广泛。下面的实例就是使用二维数组输出一个3行4列且所有元素都是0的矩阵。
在项目中创建Matrix类,在主方法中编写代码实现输出一个3行4列且所有元素都为0的矩阵
数组的基本操作
遍历数组
遍历数组就是获取数组中的每个元素。通常遍历数组都是使用for循环来实现。遍历一维数组很简单,也很好理解,下面详细介绍遍历二维数组的方法。遍历二维数组需使用双层for循环,通过数组的length属性可获得数组的长度。
在项目中创建Tautog类,在主方法中定义二维数组,使用foreach语句遍历二维数组
填充替换数组元素
数组中的元素定义完成后,可通过Arrays类的静态方法fill()来对数组中的元素进行替换。该方法通过各种重载形式可完成对任意类型的数组元素的替换。fill()方法有两种参数类型,下面以int型数组为例讲解fill()方法的使用方法。
fill(int[] a,int value)
a:要进行元素替换的数组。
value:要存储数组中所有元素的值。
fill(int[] a,int fromIndex,int toIndex,int value)
a:要进行填充的数组。
fromIndex:要使用指定值填充的第一个元素的索引(包括)。
toIndex:要使用指定值填充的最后一个元素的索引(不包括)。
value:要分配给数组指定范围中的每个元素的值。
查询数组
Arrays类的binarySearch()方法,可使用二分搜索法来搜索指定数组,以获得指定对象。该方法返回要搜索元素的索引值。binarySearch()方法提供了多种重载形式,用于满足各种类型数组的查找需要。binarySearch()方法有两种参数类型。
1.binarySearch(Object[] a, int fromIndex, int toIndex, Object key)
a:要搜索的数组。[插图] fromIndex:要搜索的第一个元素的索引(包含fromIndex)。[插图] toIndex:要搜索的最后一个元素的索引(不包含toIndex)。[插图] key:要搜索的值。
2.binarySearch(Object[],int fromIndex,int toIndex,Object key)2.binarySearch(Object[],int fromIndex,int toIndex,Object key)
[插图] a:要进行检索的数组。[插图] fromIndex:指定范围的开始处索引(包含)。[插图] toIndex:指定范围的结束处索引(不包含)。[插图] key:要搜索的元素。
在使用该方法前,同样要对数组进行排序,这样才能获得准确的索引值。如果要搜索的元素key在指定的范围内,则返回搜索键的索引;否则返回-1或“-”(插入点)。如果范围中的所有元素都小于指定的键,则插入点为toIndex(注意,这保证了当且仅当此键被找到时,返回的值将大于等于0)。
数组排序算法
冒泡排序
在程序设计中,经常需要将一组数列进行排序,这样更加方便统计与查询。程序常用的排序方法有冒泡排序、选择排序和反转排序等。本节将讲解冒泡排序方法,它以简洁的思想与实现方法而备受开发人员青睐,是广大学习者最先接触的一种排序算法。冒泡排序是最常用的数组排序算法之一,它排序数组元素的过程总是将较小的数往前放、较大的数往后放,类似水中气泡往上升的动作,所以称为冒泡排序。
基本思想
冒泡排序的基本思想是对比相邻的元素值,如果满足条件就交换元素值,把较小的元素移动到数组前面,把较大的元素移动到数组后面(也就是交换两个元素的位置),这样较小的元素就像气泡一样从底部上升到顶部。
算法示例
冒泡算法由双层循环实现,其中外层循环用于控制排序轮数,一般为要排序的数组长度减1次,因为最后一次循环只剩下一个数组元素,不需要对比,同时数组已经完成排序了。而内层循环主要用于对比数组中每个邻近元素的大小,以确定是否交换位置,对比和交换次数随排序轮数而减少。
算法实现
在项目中创建BubbleSort类,这个类的代码将对一个int型的一维数组中的元素进行冒泡排序。
从实例的运行结果来看,数组中的元素已经按从小到大的顺序排列好了。冒泡排序的主要思想就是:把相邻两个元素进行比较,如满足一定条件则进行交换(如判断大小或日期前后等),每次循环都将最大(或最小)的元素排在最后,下一次循环是对数组中其他的元素进行类似操作。
直接选择排序
1.基本思想
直接选择排序的基本思想是将指定排序位置元素与其他数组元素分别对比,如果满足条件就交换元素值。注意这里与冒泡排序的区别,不是交换相邻元素,而是把满足条件的元素与指定的排序位置元素交换(如从最后一个元素开始排序),这样排序好的位置逐渐扩大,直至整个数组都变成已排序好的格式。这就好比有一个小学生,从包含数字1~10的乱序的数字堆中分别选择合适的数字,组成一个1~10的排序,而这个学生首先从数字堆中选出1,放在第一位,然后选出2(注意这时数字堆中已经没有1了)放在第二位,依此类推,直到其找到数字9,放到8的后面,最后剩下10,就不用选择了,直接放到最后就可以。与冒泡排序相比,直接选择排序的交换次数要少很多,所以速度会快些。
2.算法示例
每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序地放在已排好序的数列的最后,直到全部待排序的数据元素排完。
3.算法实现
在项目中创建SelectSort类,这个类的代码将对一个int型的一维数组中的元素进行直接选择排序。
反转排序
1.基本思想
反转排序的基本思想比较简单,也很好理解,其实现思路就是把数组最后一个元素与第一个元素替换,倒数第二个元素与第二个元素替换,依此类推,直到把所有数组元素反转替换。
2.算法示例
反转排序是对数组两边的元素进行替换,所以只需要循环数组长度的半数次,如数组长度为7,那么for循环只需要循环3次。
3.算法实现
在项目中创建ReverseSort类,这个类的代码将对一个int型的一维数组中的元素进行反转排序
类和对象
面向对象概述
在程序开发初期,人们使用结构化开发语言。随着软件的规模越来越庞大,结构化语言的弊端也逐渐暴露出来,开发周期越来越长,产品的质量也不尽如人意。这时人们开始将另一种开发思想引入程序中,即面向对象的开发思想。面向对象思想是人类最自然的一种思考方式,它将所有预处理的问题抽象为对象,通过了解这些对象具有哪些相应的属性以及展示这些对象的行为,以解决这些对象面临的一些实际问题。在程序开发中引入面向对象设计的概念,其实质上就是对现实世界中的对象
对象
现实世界中,随处可见的一种事物就是对象。对象是事物存在的实体,如人、书桌、计算机、高楼大厦等。人类解决问题的方式总是将复杂的事物简单化,于是就会思考这些对象都是由哪些部分组成的。通常都会将对象划分为两个部分,即静态部分与动态部分。顾名思义,静态部分就是不能动的部分,这个部分被称为“属性”,任何对象都会具备其自身属性,如一个人,其属性包括高矮、胖瘦、性别、年龄等。动态部分即对象可执行的动作,这部分称为“行为”,也是一个值得探讨的部分,同样对于一个人,其可以哭泣、微笑、说话、行走,这些都是这个人具备的行为。人类通过探讨对象的属性和观察对象的行为来了解对象。
在计算机的世界中,面向对象程序设计的思想要以对象来思考问题,首先要将现实世界的实体抽象为对象,然后考虑这个对象具备的属性和行为。例如,现在面临一只大雁要从北方飞往南方这样一个实际问题,试着以面向对象的思想来解决这一实际问题。步骤如下:
1)从这一问题中抽象出对象,这里抽象出的对象为大雁。(2)识别这个对象的属性。对象具备的属性都是静态属性,如大雁有一对翅膀、黑色的羽毛等。这些属性如图6.1所示。(3)识别这个对象的动态行为,即这只大雁可以进行的动作,如飞行、觅食等,这些行为都是这个对象基于其属性而具有的动作。这些行为如图6.2所示。(4)识别出这个对象的属性和行为后,这个对象就被定义完成了。然后可以根据这只大雁具有的特性制定这只大雁从北方飞向南方的具体方案,以解决问题。究其本质,所有的大雁都具有以上属性和行为,可以将这些属性和行为封装起来,构成一个类,以描述大雁这类动物。由此可见,类实质上就是封装对象属性和行为的载体,而对象则是类抽象出来的一个实例
类
类就是同一类事物的统称,如果将现实世界中的一个事物抽象成对象,类就是这类对象的统称,如鸟类、家禽类、人类等。类是构造对象时所依赖的规范,如一只鸟有一对翅膀,它可以用这对翅膀飞行,而基本上所有的鸟都具有“有翅膀”这个特性和飞行的技能,这样具有相同特性和行为的一类事物就称为类,类的思想就是这样产生的。在图6.3中已经描述过类与对象之间的关系,对象就是符合某个类的定义所产生出来的实例。更为恰当的描述是,类是世间事物的抽象称呼,而对象则是这个事物对应的实体。如果面临实际问题,通常需要实例化类对象来解决。例如,解决大雁南飞的问题,这里只能拿这只大雁来处理这个问题,而不能拿大雁类或鸟类来解决。
封装
面向对象程序设计具有以下特点:封装性、继承性和多态性。封装是面向对象编程的核心思想。将对象的属性和行为封装起来,其载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。例如,用户使用计算机时,只需要使用手指敲击键盘就可以实现一些功能,无须知道计算机内部是如何工作的。即使知道计算机的工作原理,但在使用计算机时也并不完全依赖于计算机工作原理等细节。采用封装的思想保证了类内部数据结构的完整性,使用类的用户不能轻易地直接操作类的数据结构,只能执行类允许公开的数据。这样就避免了外部操作对内部数据的影响,提高了程序的可维护性。
继承
类与类之间同样具有关系,这种关系被称为关联。关联主要描述两个类之间的一般二元关系。例如,一个百货公司类与销售员类就是一个关联,学生类与教师类也是一个关联。两个类之间的关系有很多种,继承是关联中的一种。当处理一个问题时,可以将一些有用的类保留下来,在遇到同样的问题时拿来复用。假如这时需要解决信鸽送信的问题,我们很自然就会想到图6.4所示的鸟类。由于鸽子属于鸟类,具有与鸟类相同的属性和行为,便可以在创建信鸽类时将鸟类拿来复用,并且保留鸟类具有的属性和行为。不过,并不是所有的鸟都有送信的习惯,因此还需要再添加一些信鸽具有的独特属性及行为。鸽子类保留了鸟类的属性和行为,这样就节省了定义鸟和鸽子共同具有的属性和行为的时间,这就是继承的基本思想。设计软件时,使用继承思想可以缩短软件开发的周期,复用那些已经定义好的类可以提高系统性能,减少系统在使用过程中出现错误的概率。
继承性主要利用特定对象之间的共有属性。例如,平行四边形是四边形,正方形、矩形也都是四边形,平行四边形类与四边形类具有共同特性,就是拥有4个边,可以将平行四边形类看作四边形类的延伸,平行四边形类复用了四边形类的属性和行为,同时添加了平行四边形独有的属性和行为,如平行四边形的对边平行且相等。这里可以将平行四边形类看作是从四边形类中继承的。在Java语言中,将类似于平行四边形类的类称为子类,将类似于四边形类的类称为父类或超类。值得注意的是,可以说平行四边形是特殊的四边形,但不能说四边形是平行四边形,也就是说子类的实例都是父类的实例,但不能说父类的实例是子类的实例。
多态
在6.1.4节中介绍了继承、父类和子类,其实将父类对象应用于子类的特征就是多态。依然以图形类来说明多态,每个图形都拥有绘制自己的能力,这个能力可以看作是该类具有的行为,如果将子类的对象统一看作是父类的实例对象,这样当绘制图形时,简单地调用父类也就是图形类绘制图形的方法即可绘制任何图形,这就是多态最基本的思想。
多态性允许以统一的风格编写程序,以处理种类繁多的已存在的类及相关类。该统一风格可以由父类来实现,根据父类统一风格的处理,可以实例化子类的对象。由于整个事件的处理都只依赖于父类的方法,所以日后只要维护和调整父类的方法即可,这样就降低了维护的难度,节省了时间。
提到多态,就不得不提抽象类和接口,因为多态的实现并不依赖于具体类,而是依赖于抽象类和接口。再回到绘制图形的实例上来。图形类作为所有图形的父类,具有绘制图形的能力,这个方法可以称为“绘制图形”,但如果要执行这个“绘制图形”的命令,没有人知道应该画什么样的图形,并且如果要在图形类中抽象出一个图形对象,没有人能说清这个图形究竟是什么图形,所以使用“抽象”这个词来描述图形类比较恰当。在Java语言中,称这样的类为抽象类,抽象类不能实例化对象。在多态的机制中,父类通常会被定义为抽象类,在抽象类中给出一个方法的标准,而不给出实现的具体流程。实质上这个方法也是抽象的,如图形类中的“绘制图形”方法只提供一个可以绘制图形的标准,并没有提供具体绘制图形的流程,因为没有人知道究竟需要绘制什么形状的图形。
在多态的机制中,比抽象类更方便的方式是将抽象类定义为接口。由抽象方法组成的集合就是接口。接口的概念在现实中也极为常见,如从不同的五金商店买来螺丝帽和螺丝钉,螺丝帽很轻松地就可以拧在螺丝钉上,可能螺丝帽和螺丝钉的厂家不同,但这两个物品可以轻易地组合在一起,这是因为生产螺丝帽和螺丝钉的厂家都遵循着统一的标准,这个统一的标准在Java中就是接口。依然拿“绘制图形”来说明,可以将“绘制图形”作为一个接口的抽象方法,然后使图形类实现这个接口,同时实现“绘制图形”这个抽象方法,当三角形类需要绘制时,就可以继承图形类,重写其中的“绘制图形”方法,并改写这个方法为“绘制三角形”,这样就可以通过这个标准绘制不同的图形。
成员变量
成员方法
在Java语言中,使用成员方法对应于类对象的行为。以Book类为例,它包含getName()和setName()两个方法,这两个成员方法分别为获取图书名称和设置图书名称的方法。
权限修饰符
Java中的权限修饰符主要包括private、public和protected,这些修饰符控制着对类和类的成员变量以及成员方法的访问。如果一个类的成员变量或成员方法被修饰为private,则该成员变量只能在本类中被使用,在子类中是不可见的,并且对其他包的类也是不可见的。如果将类的成员变量和成员方法的访问权限设置为public,那么除了可以在本类使用这些数据,还可以在子类和其他包的类中使用。如果一个类的访问权限被设置为private,这个类将隐藏其内的所有数据,以免用户直接访问它。如果需要使类中的数据被子类或其他包中的类使用,可以将这个类设置为public访问权限。如果一个类使用protected修饰符,那么只有本包内的该类的子类或其他类可以访问此类中的成员变量和成员方法。
局部变量
在6.2.2节中已经讲述过成员方法,如果在成员方法内定义一个变量,那么这个变量被称为局部变量。实际上,方法中的形参也可作为一个局部变量。例如,在例6.1的Book类中定义setName(String name)方法,String name这个形参就被看作是局部变量。局部变量是在方法被执行时创建,在方法执行结束时被销毁。局部变量在使用时必须进行赋值操作或被初始化,否则会出现编译错误。
局部变量的有效范围
可以将局部变量的有效范围称为变量的作用域,局部变量的有效范围从该变量的声明开始到该变量的结束为止
在相互不嵌套的作用域中可以同时声明两个名称和类型相同的局部变量
但是在相互嵌套的区域中不可以这样声明,如果将局部变量id在方法体的for循环中再次定义,编译器将会报错
this关键字
his关键字用于表示本类当前的对象,当前对象不是某个new出来的实体对象,而是当前正在编辑的类。this关键字只能在本类中使用。
在上述代码中可以看到,成员变量与setName()方法中的形式参数的名称相同,都为name,那么该如何在类中区分使用的是哪一个变量呢?在Java语言中,规定使用this关键字来代表本类对象的引用,this关键字被隐式地用于引用对象的成员变量和方法,如在上述代码中,this.name指的就是Book类中的name成员变量,而this.name = name语句中的第二个name则指的是形参name。实质上,setName()方法实现的功能就是将形参name的值赋予成员变量name。
类的构造方法
在类中,除成员方法外,还存在一种特殊类型的方法,那就是构造方法。构造方法是一个与类同名的方法,对象的创建就是通过构造方法完成的。每当类实例化一个对象时,类都会自动调用构造方法。
构造方法的特点如下:
构造方法没有返回值。
构造方法的名称要与本类的名称相同。
构造方法的定义语法格式如下:
public:构造方法修饰符。
Book:构造方法的名称。
静态变量和静态方法
在介绍静态变量和静态方法之前,首先需要介绍static关键字,因为由static修饰的变量和方法被称为静态变量和静态方法。有时,在处理问题时会需要两个类在同一个内存区域共享一个数据。例如,在球类中使用圆周率PI这个值,可能除了本类需要这个值,在另外一个圆类中也需要使用。这时没有必要在两个类中同时创建PI,因为这样系统会将这两个不在同一个类中定义的静态值分配到不同的内存空间中
创建StaticDemo类,在类中使用static关键字定义一个属性和一个方法,并在主方法中调用。
最后总结以下使用static关键字要注意的几点:
在静态方法中不可以使用this关键字。
在静态方法中不可以直接调用非静态方法。
局部变量不可以使用static关键字声明。
主方法必须用static声明。
只有内部类可以使用static关键字声明。
类的主方法
主方法是类的入口点,它定义了程序从何处开始。主方法提供对程序流向的控制,Java编译器通过主方法来执行程序。
在主方法的定义中可以看到其具有以下特性:
主方法是静态的,所以如要直接在主方法中调用其他方法,则该方法必须也是静态的。
主方法没有返回值。
主方法的形参为数组。其中,args[0]~args[n]分别代表程序的第一个参数到第n个参数,可以使用args.length获取参数的个数。
运行代码前,先要在Eclipse中设置运行参数,步骤如下:
(1)在Eclipse中的MainDem.java文件上单击鼠标右键,在弹出的快捷菜单中选择“运行方式”/“运行配置”(英文版为Run As / Run Configrations),弹出“运行配置”(Run Configrations)对话框。
(2)在“运行配置”对话框中选择“自变量”(Arguments)选项卡,在“项目”(Program arguments)文本框中输入相应的参数,每个参数间按Enter键隔开。具体设置如图6.11所示。
3)单击“运行”按钮,查看控制台运行结果
对象
Java是一门面向对象的程序设计语言,对象是由类实例化而来的,所有问题都通过对象来处理。对象可以通过操作类的属性和方法来解决相应的问题,所以了解对象的产生、操作和消亡是十分必要的。本节就来讲解对象在Java语言中的应用。
对象的创建
在6.1节中曾经讲解过对象,对象可以认为是在一类事物中抽象出某一个特例,可以通过这个特例来处理这类事物出现的问题。在Java语言中,通过new操作符来创建对象。前文在讲解构造方法时介绍过,每实例化一个对象就会自动调用一次构造方法,实质上这个过程就是创建对象的过程。准确地说,可以在Java语言中使用new操作符调用构造方法创建对象。
每个对象都是相互独立的,在内存中占据独立的内存地址,并且每个对象都具有自己的生命周期,当一个对象的生命周期结束时,对象就变成垃圾,由Java虚拟机自带的垃圾回收机制处理,不能再被使用
访问对象的属性和行为
用户使用new操作符创建一个对象后,可以使用“对象.类成员”来获取对象的属性和行为。前文已经提到过,对象的属性和行为在类中是通过类的成员变量和成员方法的形式来表示的,所以当对象获取类成员时,也相应地获取了对象的属性和行为。
对象的引用
在Java语言中,尽管一切都可以看作对象,但真正的操作标识符实质上是一个引用,那么引用在Java中是如何体现的呢?语法如下:
实际上真正的对象是“new People()”这段代码。为了方便开发者保存、调用对象,于是创建了一个People类型、名叫tom的引用变量。实际上,tom只是一段内存地址,用于标记“new People()”对象在内存中的位置。因为内存地址又长又乱,很难让人记住,所以Java语言利用引用变量帮开发者标记内存地址。开发者只要记住引用变量的名字,就能够在内存里找到对象数据。简单来说,tom是new People()的“代理人”。既然tom是new People()的“代理人”,那么“new People()”对象能做的事,tom也能做
对象的销毁
每个对象都有生命周期,当对象的生命周期结束时,分配给该对象的内存地址需要被回收。在其他语言中,需要用户手动回收废弃的对象。Java拥有一套完整的垃圾回收机制,用户不必担心废弃的对象占用内存,垃圾回收器会自动回收无用却占用内存的资源。在学习垃圾回收机制之前,读者首先需要了解何种对象会被Java虚拟机视为“垃圾”。主要包括以下两种情况:
对象引用超过其作用范围,这个对象将被视为垃圾
虽然Java的垃圾回收机制已经很完善,但垃圾回收器只能回收那些由new操作符创建的对象。某些对象不是通过new操作符在内存中获取存储空间的,这种对象无法被垃圾回收机制所识别。在Java中,提供了一个finalize()方法,这个方法是Object类的方法,它被声明为protected,用户可以在自己的类中定义这个方法。如果用户在类中定义了finalize()方法,在垃圾回收时会首先调用该方法,在下一次垃圾回收动作发生时,才真正回收被对象占用的内存。
由于垃圾回收不受人为控制,具体执行时间也不确定,所以finalize()方法也就无法执行。为此,Java提供了System.gc()方法来强制启动垃圾回收器,这与给120打电话通知医院来救护病人的道理一样,主动告知垃圾回收器来进行清理。
继承.多态.抽象类与接口
类的继承
继承在面向对象开发思想中是一个非常重要的概念,它使整个程序架构具有一定的弹性。在程序中复用一些已经定义完善的类,不仅可以减少软件开发周期,也可以提高软件的可维护性和可扩展性。本节将详细讲解类的继承。
在Java语言中,一个类继承另一个类需要使用关键字extends,关键字extends的使用方法如下:
因为Java只支持单继承,即一个类只能有一个父类,所以类似下面的代码是错误的:
子类在继承父类之后,创建子类对象的同时也会调用父类的构造方法。
Object类
在开始学习使用class关键字定义类时,就应用到了继承原理,因为在Java中所有的类都直接或间接继承了java.lang.Object类。Object类是比较特殊的类,它是所有类的父类,是Java类层中的最高层类。用户创建一个类时,除非已经指定要从其他类继承,否则它就是从java.lang.Object类继承而来的。Java中的每个类都源于java.lang.Object类,如String类、Integer类等都是继承于Object类。除此之外,自定义的类也都继承于Object类。由于所有类都是Object类的子类,所以在定义类时可省略extends Object在Object类中,主要包括clone()、finalize()、equals()、toString()等方法,其中常用的两个方法为equals()和toString()方法。由于所有的类都是Object类的子类,所以任何类都可以重写Object类中的方法。
getClass()方法
getClass()方法是Object类定义的方法,它会返回对象执行时的Class实例,然后使用此实例调用getName()方法可以取得类的名称
可以将getClass()方法与toString()方法联合使用。
toString()方法
toString()方法的功能是将一个对象返回为字符串形式,它会返回一个String实例。在实际的应用中通常重写toString()方法,为对象提供一个特定的输出模式。当这个类转换为字符串或与字符串连接时,将自动调用重写的toString()方法。
创建Student类,重写toString()方法,使该类的对象可以自定义输出自己的姓名和年龄。
运行结果如下:
equals()方法
在Java语言中,有两种比较对象的方式,分别为“==”运算符与equals()方法。两者的区别在于:“==”比较的是两个对象引用内存地址是否相等,而equals()方法比较的是两个对象的实际内容。
为People类创建身份证号和姓名两个属性,重写equals()方法,仅以身份证号作为区分条件。创建n个People对象,用equals()方法和“==”运算符来判断是否存在多个对象代表同一个人。
对象类型的转换
对象类型的转换在Java编程中经常遇到,主要包括向上转型与向下转型操作。本节将详细讲解对象类型转换的内容。
向上转型
向上转型可以被理解为将子类类型的对象转换为父类类型的对象,即把子类类型的对象直接赋值给父类类型的对象,进而实现按照父类描述子类的效果。
例如,四边形类是平行四边形类的父类,用Quadrangle表示四边形类、用Parallelogram表示平行四边形类;在Parallelogram类中,定义一个值为4的表示底边长度的变量edges;在Parallelogram类的主方法中,运用向上转型,把平行四边形类(Parallelogram)类型的对象直接赋值给四边形类(Quadrangle)类型的对象。人为强制四边形类(Quadrangle)类型的对象可以调用变量edges,并将edges的值修改为6,Eclipse能通过编译么?Eclipse的效果图如图7.3所示,从图中可以看出,Eclipse在相关代码处显示波浪线等错误标志,说明代码有误。
综上所述,在运用向上转型的过程中,父类的对象无法调用子类独有的属性或者方法。
向下转型
向下转型可以被理解为将父类类型的对象转换为子类类型的对象。但是,运用向下转型,如果把一个较抽象的类的对象转换为一个较具体的类的对象,这样的转型通常会出现错误。例如,可以说某只鸽子是一只鸟,却不能说某只鸟是一只鸽子。因为鸽子是具体的,鸟是抽象的。一只鸟除了可能是鸽子,还有可能是老鹰、麻雀等。因此,向下转型是不安全的。
使用instanceof关键字判断对象类型
当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生ClassCastException异常,所以在执行向下转型之前需要养成一个良好的习惯,就是判断父类对象是否为子类对象的实例。这个判断通常使用instanceof关键字来完成。可以使用instanceof关键字判断是否一个类实现了某个接口(接口会在第7.8节中进行讲解),也可以用它来判断一个实例对象是否属于一个类。
instanceof的语法格式如下:
myobject:某类的对象引用。
ExampleClass:某个类。
使用instanceof关键字的表达式返回值为布尔值。如果返回值为true,说明myobject对象为ExampleClass的实例对象;如果返回值为false,说明myobject对象不是ExampleClass的实例对象。
创建Quadrangle四边形类、Square正方形类和Circular圆形类。其中,Square类继承Quadrangle类,在主方法中分别创建这些类的对象,然后使用instanceof关键字判断它们的类型并输出结果。
方法的重载
在第6章中我们曾学习过构造方法,知道构造方法的名称由类名决定,所以构造方法只有一个名称。如果希望以不同的方式来实例化对象,就需要使用多个构造方法来完成。由于这些构造方法都需要根据类名进行命名,为了让方法名相同而形参不同的构造方法同时存在,必须用到方法重载。虽然方法重载起源于构造方法,但它也可以应用到其他方法中。本节将讲述方法的重载。
在本实例中分别定义了5个方法,在这5个方法中,前两个方法的参数类型不同,并且方法的返回值类型也不同,所以这两个方法构成重载关系;前两个方法与第3个方法相比,第3个方法的参数个数少于前两个方法,所以这3个方法也构成了重载关系;最后两个方法相比,发现除了参数的出现顺序不同,其他都相同,同样构成了重载关系。图7.6表明了所有可以构成了重载的条件。
根据图7.6所示的构成方法重载的条件,可以总结出编译器是利用方法名、方法各参数类型和参数的个数、参数的顺序来确定类中的方法是否唯一。方法的重载使得方法以统一的名称被管理,使程序代码更有条理。
final关键字
final被译为“最后的”“最终的”,final是Java语言中的一个关键字,凡是被final关键字修饰过的内容都是不可改变的。本节将讲解final关键字的3种使用场景。
final变量
final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值。通常,由final定义的变量为常量。
当在程序中使用到PI这个常量时,它的值就是3.14。如果在程序中再次对定义为final的常量赋值,编译器将不会接受。final关键字定义的变量必须在声明时对其进行赋值操作。final除了可以修饰基本数据类型的常量,还可以修饰对象引用。由于数组也可以被看作一个对象来引用,所以final可以修饰数组。一旦一个对象引用被修饰为final后,它就只能恒定指向一个对象,无法将其改变以指向另一个对象。一个既是static又是final的字段只占据一段不能改变的存储空间。为了深入了解final关键字,来看下面的实例。
final方法
将方法定义为final类型,可以防止子类修改父类的定义与实现方式,同时定义为final的方法的执行效率要高于非final方法。在修饰权限中曾经提到过private修饰符,如果一个父类的某个方法被设置为private,子类将无法访问该方法,自然无法覆盖该方法。也就是说,一个定义为private的方法隐式被指定为final类型,因此无须将一个定义为private的方法再定义为final类型。
final类
定义为final的类不能被继承。如果希望一个类不被任何类继承,并且不允许其他人对这个类进行任何改动,可以将这个类设置为final类。
如果将某个类设置为final类,则该类中的所有方法都被隐式设置为final方法,但是final类中的成员变量可以被定义为final或非final形式。
多态
利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。在7.3节中已经学习过子类对象可以作为父类的对象实例使用,这种将子类对象视为父类对象的做法称为“向上转型”。
假如现在要编写一个绘制图形的方法draw(),如果传入正方形对象就绘制正方形,如果传入圆形对象就绘制圆形,这种场景可以使用重载来实现,定义如下:
但是这种写法有个问题:正方形和圆形都是图形,这场景细分的重载方式不仅增加了代码量,还降低了“易用度”。如果定义一个图形类,让它处理所有继承该类的对象,根据“向上转型”原则可以使每个继承图形类的对象作为draw()方法的参数,然后在draw()方法中做一些限定就可以根据不同图形类对象绘制相应的图形。这样处理能够很好地解决代码冗余问题,同时程序也易于维护。
抽象类与接口
在解决实际问题时,一般将父类定义为抽象类,需要使用这个父类进行继承与多态处理。回想继承和多态原理,继承树中越是在上方的类越抽象,如鸽子类继承鸟类、鸟类继承动物类等。在多态机制中,并不需要将父类初始化为对象,我们需要的只是子类对象,所以在Java语言中设置抽象类不可以实例化为对象。
使用abstract关键字定义的类称为抽象类,而使用这个关键字定义的方法称为抽象方法。抽象方法没有方法体,这个方法本身没有任何意义,除非它被重写,而承载这个抽象方法的抽象类必须被继承,实际上抽象类除了被继承没有任何意义。定义抽象类的语法如下:
反过来讲,如果声明一个抽象方法,就必须将承载这个抽象方法的类定义为抽象类,不能在非抽象类中获取抽象方法。换句话说,只要类中有一个抽象方法,此类就被标记为抽象类。抽象类被继承后需要实现其中所有的抽象方法,也就是保证以相同的方法名称、参数列表和返回值类型创建出非抽象方法,当然也可以是抽象方法。图7.10说明了抽象类的继承关系。
从图7.10中可以看出,继承抽象类的所有子类需要将抽象类中的抽象方法进行覆盖。这样在多态机制中,就可以将父类修改为抽象类,将draw()方法设置为抽象方法,然后每个子类都重写这个方法来处理。但这又会出现我们刚探讨多态时讨论的问题,程序中会有太多冗余的代码,同时这样的父类局限性很大,也许某个不需要draw()方法的子类也不得不重写draw()方法。如果将draw()方法放置在另外一个类中,让那些需要draw()方法的类继承该类,不需要draw()方法的类继承图形类,又会产生新的问题:所有的子类都需要继承图形类,因为这些类是从图形类中导出的,同时某些类还需要draw()方法,而Java中规定类不能同时继承多个父类。为了应对这种问题,接口的概念便出现了。
接口
接口是抽象类的延伸,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体。对于7.8.1节中遗留的问题,可以将draw()方法封装到一个接口中,使需要draw()方法的类实现这个接口,同时也继承图形类,这就是接口存在的必要性。在图7.11中,描述了各个子类继承图形类后使用接口的关系。
接口使用interface关键字进行定义,其语法如下:
public:接口可以像类一样被权限修饰符修饰,但public关键字仅限用于接口在与其同名的文件中被定义。
interface:定义接口关键字。
[插图] Paintable:接口名称。
将图形对象的绘图方法剥离出来,作为Paintable可绘制接口中的抽象方法。创建四边形类作为平行四边形类和正方形类的父类,同时让这两个子类实现Paintable接口。创建圆形类实现Paintable接口,但不继承四边形类。
运行结果如下:
从这个结果可以看出,“绘制”这个行为可不是四边形独有的,圆形也能绘制,所以draw()方法被独立封装在了可绘制接口中。正方形类与平行四边形类分别继承了四边形类并实现了可绘制接口,所以正方形类与平行四边形类既可以调用绘制方法,又可以调用四边形提供的doAnything()方法。但是,圆形不属于四边形,且可以绘制,所以最后圆形对象只调用了draw()方法。
包和内部类
java类包
在Java中每定义好一个类,通过Java编译器进行编译之后,都会生成一个扩展名为.class的文件。当程序的规模逐渐扩大时,就很容易发生类名称冲突的现象。JDK API中提供了成千上万具有各种功能的类,系统又是如何管理的呢?Java中提供了一种管理类文件的机制,就是类包。
类名冲突
Java中每个接口或类都来自不同的类包,无论是Java API中的类与接口还是自定义的类与接口,都需要隶属于某一个类包,这个类包包含了一些类和接口。如果没有包的存在,管理程序中的类名称将是一件非常麻烦的事情。如果程序只由一个类组成,自然不会出现类名重叠的问题,但是随着程序的类的数量增多,难免会出现这一问题。例如,在程序中定义一个Login类,因业务需要,还要定义一个名称为Login的类,但是这两个类所实现的功能完全不同,于是问题就产生了——编译器不会允许存在同名的类文件。解决这类问题的办法是将这两个类放置在不同的类包中。
完整的类路径
编写Java程序经常用到String类(第10章会详细讲解),其实String类并不是它的完整名称,就如同一个人需要有名有姓一样,String类的完整名称如图8.1所示。可以看出,一个完整的类名需要包名与类名的组合,每个类都隶属于一个类包,只要保证同一类包中的类不同名,就可以有效地避免同名类冲突的情况。例如,一个程序中同时使用到java.util.Date类与java.sql.Date类,如果在程序中不指定完整的类路径,编译器不会知道这段代码使用的是java.util类包中的Date类还是java.sql类包中的Date类,所以需要在指定代码中给出完整的类路径。
创建包
(1)在项目的src节点上右击,选择“新建”/“包”命令(英文为New /Package)。
(2)弹出“新建Java包”(New Java Package)对话框,在“名称”(Name)文本框中输入新建的包名,如com.mr,然后单击“完成”(Finish)按钮,如图8.2所示。
3)在Eclipse中创建类时,可以在新建立的包上右击,选择“新建”(New)命令,这样新建的类会默认保存在该包中。另外也可以在New Java Class对话框中指定新建类所在的包。
在类中指定包名时,需要将package表达式放置在程序的第一行,它必须是文件中的第一行非注释代码。使用package关键字为类指定包名之后,包名将会成为类名中的一部分,预示着这个类必须指定全名。例如,在使用位于com.mr包下的Dog.java类时,需要使用形如com.mr.Dog这样的表达式。
导入包
.使用import关键字导入包
如果某个类中需要使用Math类,那么如何告知编译器当前应该使用哪一个包中的Math类,是java.lang.Math类还是com.mr.Math类?这是一个令人困扰的问题。此时,可以使用Java中的import关键字指定。例如,如果在程序中使用import关键字导入com.mr.Math类,在程序中使用Math类时就会自动选择com.mr.Math类。
在使用import关键字时,可以指定类的完整描述,如果为了使用包中更多的类,可以在使用import关键字指定时在包指定后加上*,这表示可以在程序中使用该包中的所有类。
使用import导入静态成员
import关键字除导入包外,还可以导入静态成员,这是JDK 5.0以上版本提供的功能。导入静态成员可以使编程更为方便。
从本实例中可以看出,分别使用import static导入了java.lang.Math类中的静态成员方法max()和java.lang.System类中的out成员变量,这时就可以在程序中直接引用这些静态成员,如在主方法中直接使用out.println()表达式以及max()方法。
内部类
成员内部类
成员内部类简介
在一个类中使用内部类,可以在内部类中直接存取其所在类的私有成员变量
在成员内部类中可以随意使用外部类的成员方法及成员变量,尽管这些类成员被修饰为private。图8.3充分说明了内部类的使用,尽管成员变量i以及成员方法g()都在外部类中被修饰为private,但在成员内部类中可以直接使用。内部类的实例一定要绑定在外部类的实例上,如果从外部类中初始化一个内部类对象,那么内部类对象就会绑定在外部类对象上。内部类初始化方式与其他类的初始化方式相同,都是使用new关键字。下面来看一个实例。
首先创建Car类,Car类中有私有属性brand和start()方法,然后在Car类的内部创建Engine类,Engine类中有私有属性model和ignite()方法,最后打印出“启动大众朗行,发动机EA211点火”。
使用this关键字获取内部类与外部类的引用
如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用this关键字。
在项目中创建TheSameName类,在类中定义成员变量x,再定义一个内部类Inner,在内部类中也创建x变量,并在内部类的doit()方法中定义一个局部变量x。
在类中,如果遇到内部类与外部类的成员变量重名的情况,可以使用this关键字进行处理。例如,在内部类中使用this.x语句可以调用内部类的成员变量x,而使用TheSameName.this.x语句可以调用外部类的成员变量x,即使用外部类名称后跟一个点操作符和this关键字便可获取外部类的一个引用。
匿名内部类
匿名类是只在创建对象时才会编写类体的一种写法。匿名类的特点是“现用现写”,其语法如下:
创建一个抽象的狗类,类中有一个颜色属性和两个抽象方法,在测试类的主方法中创建抽象类对象,并用匿名内部类实现该对象的抽象方法。
从这个结果可以看出,本来无法创建对象的抽象类竟然也可以出现在new关键字的右侧。为何叫匿名内部类?就是因为实例中Dog抽象类的实现类没有名称。创建出的对象maomao既不是金毛犬,也不是哈士奇犬,在程序中maomao只能解读为“一只具体的无名之狗”。使用匿名类时应该遵循以下原则:
匿名类不能写构造方法。
匿名类不能定义静态的成员。
如果匿名类创建的对象没有赋值给任何引用变量,会导致该对象用完一次就会被Java虚拟机销毁。
异常处理
异常概述
在程序中,异常可能由程序员没有预料到的各种情况产生,也可能由超出了程序员可控范围的环境因素产生,如用户的坏数据、试图打开一个根本不存在的文件等。在Java中,这种在程序运行时可能出现的一些错误称为异常。异常是一个在程序执行期间发生的事件,它中断了正在执行的程序的正常指令流。
有许多异常的例子,如数组溢出等。Java语言是一门面向对象的编程语言,因此异常在Java语言中也是作为类的实例的形式出现的。当某一方法中发生错误时,这个方法会创建一个对象,并且把它传递给正在运行的系统。这个对象就是异常对象。通过异常处理机制,可以将非正常情况下的处理代码与程序的主逻辑分离,即在编写代码主流程的同时在其他地方处理异常。
异常的抛出与捕捉
为了保证程序有效地执行,需要对抛出的异常进行相应的处理。在Java中,如果某个方法抛出异常,既可以在当前方法中进行捕捉,而后处理该异常,也可以将异常向上抛出,交由方法调用者来处理。本节将讲解Java中捕获异常的方法。
抛出异常
异常抛出后,如果不做任何处理,程序就会被终止。例如,将一个字符串转换为整型,可以通过Integer类的parseInt()方法来实现。但如果该字符串不是数字形式,parseInt()方法就会抛出异常,程序将在出现异常的位置终止,不再执行下面的语句。
捕捉异常
Java语言的异常捕获结构由try、catch和finally 3部分组成。其中,try语句块存放的是可能发生异常的Java语句;catch语句块在try语句块之后,用来激发被捕获的异常;finally语句块是异常处理结构的最后执行部分,无论try语句块中的代码如何退出,都将执行finally语句块。
try-catch语句块
在项目中创建Take类,在主方法中使用try-catch语句块将可能出现的异常语句进行异常处理。实例代码如下:
运行结果如图9.3所示
从图9.3中可以看出,程序仍然输出最后的提示信息,没有因为异常而终止。在例9.3中,将可能出现异常的代码用try-catch语句块进行处理,当try语句块中的语句发生异常时,程序就会跳转到catch语句块中执行,执行完catch语句块中的程序代码后,将继续执行catch语句块后的其他代码,而不会执行try语句块中发生异常语句后面的代码。由此可知,Java的异常处理是结构化的,不会因为一个异常影响整个程序的执行。
finally语句块
完整的异常处理语句一定要包含finally语句,无论程序中有无异常发生,并且无论之前的try-catch语句块是否顺利执行完毕,都会执行finally语句。但是,在以下4种特殊情况下,finally块不会被执行:
在finally语句块中发生了异常。
在前面的代码中使用了System.exit()退出程序。
程序所在的线程死亡。
关闭CPU。
java常见的异常类
在Java中,提供了一些异常类用来描述经常发生的异常。其中,有的需要程序员进行捕获处理或声明抛出,有的是由Java虚拟机自动进行捕获处理。Java中常见的异常类如表9.1所示。
自定义异常
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户只需继承Exception类即可自定义异常类。在程序中使用自定义异常类,大体可分为以下几个步骤:
(1)创建自定义异常类。(2)在方法中通过throw关键字抛出异常对象。(3)如果在当前抛出异常的方法中处理异常,可以使用try-catch语句块捕获并处理,否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。(4)在出现异常的方法的调用者中捕获并处理异常。
在方法中抛出异常
若某个方法可能会发生异常,但不想在当前方法中处理这个异常,则可以使用throws、throw关键字在方法中抛出异常。
使用throws关键字抛出异常
throws关键字通常被应用在声明方法时,用来指定方法可能抛出的异常。多个异常可使用逗号分隔。
在项目中创建Shoot类,在该类中创建方法pop(),在该方法中抛出NegativeArraySizeException异常,在主方法中调用该方法,并实现异常处理。实例代码如下:
使用throws关键字将异常抛给上一级后,如果不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的代
使用throw关键字抛出异常
throw关键字通常用于方法体中,并且抛出一个异常对象。程序在执行到throw语句时立即终止,它后面的语句都不执行。通过throw抛出异常后,如果想在上一级代码中捕获并处理异常,则需要在抛出异常的方法中使用throws关键字在方法的声明中指明要抛出的异常;如果要捕捉throw抛出的异常,则必须使用try-catch语句块
运行时异常
untimeException异常是程序运行过程中抛出的异常。Java类库的每个包中都定义了异常类,所有这些类都是Throwable类的子类。Throwable类派生了两个子类,分别是Exception类和Error类。Error类及其子类用来描述Java运行系统中的内部错误以及资源耗尽的错误,这类错误比较严重。Exception类称为非致命性类,可以通过捕捉处理使程序继续执行。Exception类又根据错误发生的原因分为RuntimeException异常和除RuntimeException之外的异常,如图9.8所示
Java中提供了常见的RuntimeException异常,这些异常可通过try-catch语句捕获,如表9.2所示。
异常的使用原则
Java异常强制用户去考虑程序的强健性和安全性。异常处理不应用来控制程序的正常流程,其主要作用是捕获程序在运行时发生的异常并进行相应的处理。编写代码处理某个方法可能出现的异常时,可遵循以下几条原则:
在当前方法声明中使用try-catch语句捕获异常。
一个方法被覆盖时,覆盖它的方法必须抛出相同的异常或异常的子类。
如果父类抛出多个异常,则覆盖方法必须抛出那些异常的一个子集,不能抛出新异常。
字符串
String类
单个字符可以用char类型保存,多个字符组成的文本就需要保存在String对象中。String通常被称为字符串,一个String对象最多可以保存(232-1)个字节(占用4GB空间大小)的文本内容。本节将详细讲解String类的使用方法。
声明字符串
在Java语言中,字符串必须包含在一对双引号(" ")之内。例如:
以上这些都是字符串常量,字符串常量可以是系统能够显示的任何文字信息,甚至可以是单个字符。
可以通过以下语法格式来声明字符串变量:
String:指定该变量为字符串类型。
str:任意有效的标识符,表示字符串变量的名称。声明字符串变量s,代码如下:
创建字符串
该方法用一个字符数组a创建String对象,代码如下:
String(char a[], int offset, int length)
该方法提取字符数组a中的一部分创建一个字符串对象。参数offset表示开始截取字符串的位置,length表示截取字符串的长度。代码如下
String(char[] value)
该构造方法可分配一个新的String对象,使其表示字符数组参数中所有元素连接的结果。代码如下:
除通过以上几种使用String类的构造方法来创建字符串变量外,还可通过将字符串常量的引用赋值给一个字符串变量来创建字符串。代码如下:
连接字符串
对于已声明的字符串,可以对其进行相应的操作,连接字符串就是字符操作中较简单的一种。可以对多个字符串进行连接,也可使字符串与其他数据类型进行连接。
连接多个字符串
使用“+”运算符可实现连接多个字符串的功能。“+”运算符可以连接多个String对象并产生一个新的String对象。
在项目中创建Join类,在主方法中创建两个String型变量,它们的值分别是“春色绿千里”和“马蹄香万家”,使用“+”运算符连接这两个String型变量和“\n”,在控制台上输出连接后的字符串。实例代码如下:
连接其他数据类型
字符串也可同其他基本数据类型进行连接。如果将字符串同其他数据类型数据进行连接,会将其他数据类型的数据直接转换成字符串。
获取字符串信息
字符串作为对象,可通过相应方法获取字符串的有效信息,如获取某字符串的长度、某个索引位置的字符等。本节将讲解几种获取字符串的相关信息的方法。
获取字符串长度
使用String类的length()方法可获取声明的字符串对象的长度。语法如下:
其中,str为字符串对象。获取字符串长度,代码如下:
上段代码是将字符串str的长度赋值给int型变量size,此时变量size的值为15,这表示length()方法返回的字符串的长度(包括字符串中的空格)。
字符串查找
String类提供了两种查找字符串的方法,即indexOf()与lastIndexOf()方法。这两种方法都允许在字符串中搜索指定条件的字符或字符串。indexOf()方法返回的是搜索的字符或字符串首次出现的位置,lastIndexOf()方法返回的是搜索的字符或字符串最后一次出现的位置。
indexOf(String s)
该方法用于返回参数字符串s在指定字符串中首次出现的索引位置。当调用String类的indexOf()方法时,会从当前字符串的开始位置搜索s的位置。如果没有检索到字符串s,该方法的返回值是-1。语法如下:
str:任意字符串对象。
substr:要搜索的字符串。
查找字符a在字符串str中的索引位置,代码如下:
理解字符串的索引位置,要对字符串的下标有所了解。在Java语言中,String对象是用数组表示的。字符串的下标是0~length()-1。因此,字符串“We are students”的下标如图10.5所示。
lastIndexOf(String str)
该方法用于返回指定字符串最后一次出现的索引位置。当调用String类的lastIndexOf()方法时,会从当前字符串的开始位置检索参数字符串str,并将最后一次出现str的索引位置返回。如果没有检索到字符串str,该方法返回-1。语法如下:
str:任意字符串对象。substr:要搜索的字符串。
在项目中创建Text类,在主方法中创建String对象,先使用lastIndexOf()方法查看字符串str中空字符串的位置,再输出这个字符串的长度,查看这两个结果是否相同。实例代码如下:
运行结果如图10.6所示。
获取指定索引位置的字符
使用charAt()方法可将指定索引处的字符返回。语法如下:
str:任意字符串。index:整型值,用于指定要返回字符的下标。
在项目中创建Ref类,在主方法中创建String对象,使用charAt()方法查看字符串str中索引位置是6的字符。实例代码如下:
运行结果如图10.7所示。
字符串操作
String类中包含了很多方法,允许程序员对字符串进行操作来满足实际编程中的需要。本节将讲解几种常见的字符串操作。
获取子字符串
通过String类的substring()方法可对字符串进行截取。substring()方法被两种不同的重载形式,来满足不同的需要。这些形式的共同点就是都利用字符串的下标进行截取,且应明确字符串下标是从0开始的。
substring(int beginIndex)
该方法返回的是从指定的索引位置开始截取直到该字符串结尾的子串。语法如下:
其中,beginIndex指定从某一索引处开始截取字符串。截取字符串,代码如下:
substring(int beginIndex, int endIndex)
该方法返回的是从字符串某一索引位置开始截取至某一索引位置结束的子串。语法如下:
beginIndex:开始截取子字符串的索引位置。endIndex:子字符串在整个字符串中的结束位置。
在项目中创建Subs类,在主方法中创建String对象,实现使用substring()方法对字符串进行截取,并将截取后形成的新串输出。实例代码如下:
运行结果如图10.9所示。
去除空格
trim()方法返回字符串的副本,忽略前导空格和尾部空格。语法如下:
其中,str为任意字符串对象。
在项目中创建Blak类,在主方法中创建String对象,在控制台上输出字符串原来的长度和去掉首、尾空格后的长度。实例代码如下:
运行结果如图10.10所示。
字符串替换
replace()方法可实现将指定的字符或字符串替换成新的字符或字符串。
replace()方法返回的结果是一个新的字符串。如果字符或字符串oldChar没有出现在该对象表达式中的字符串序列中,则将原字符串返回。
判断字符串的开始与结尾
startsWith()方法与endsWith()方法分别用于判断字符串是否以指定的内容开始或结束。这两个方法的返回值都为boolean类型。
startsWith()方法
该方法用于判断当前字符串对象的前缀是否为参数指定的字符串。语法如下:
其中,prefix是指作为前缀的字符串。
endsWith()方法
该方法用于判断当前字符串是否为以给定的子字符串结束。语法如下:
其中,suffix是指作为后缀的字符串。
判断字符串是否相等
对字符串对象进行比较不能简单地使用比较运算符“==”,因为比较运算符比较的是两个字符串的地址是否相同。即使两个字符串的内容相同,两个对象的内存地址也是不同的,使用比较运算符仍然会返回false。使用比较运算符比较两个字符串
equals()方法
如果两个字符串具有相同的字符和长度,则使用equals()方法进行比较时,返回true。否则,返回false。语法如下:
其中,str、otherstr是要比较的两个字符串对象。
equalsIgnoreCase()方法
使用equals()方法对字符串进行比较时是区分大小写的,而使用equalsIgnoreCase()方法是在忽略了大小写的情况下比较两个字符串是否相等,返回结果仍为boolean类型。语法如下:
其中,str、otherstr是要比较的两个字符串对象。通过下面的例子可以看出equals()方法和equalsIgnoreCase()方法的区别。
按字典顺序比较两个字符串
compareTo()方法为按字典顺序比较两个字符串,该比较基于字符串中各个字符的Unicode值,按字典顺序将String对象表示的字符序列与参数字符串所表示的字符序列进行比较。如果按字典顺序此String对象位于参数字符串之前,则比较结果为一个负整数;如果按字典顺序此String对象位于参数字符串之后,则比较结果为一个正整数;如果这两个字符串相等,则结果为0。语法如下:
其中,str、otherstr是要比较的两个字符串对象。
字母大小写转换
String类的toLowerCase()方法可将字符串中的所有大写字母改写为小写字母,而toUpperCase()方法可将字符串中的所有小写字母改写为大写字母。
toLowerCase()方法
该方法将字符串中的所有大写字母转换为小写。如果字符串中没有应该被转换的字符,则将原字符串返回;否则将返回一个新的字符串,将原字符串中每个大写字母都转换成小写,字符串长度不变。语法如下:
其中,str是要进行转换的字符串。
toUpperCase()方法
该方法将字符串中所有的小写字母转换为大写。如果字符串中没有应该被转换的字符,则将原字符串返回;否则返回一个新字符串,将原字符串中每个小写字母都转换成大写,字符串长度不变。语法如下:
其中,str是要进行转换的字符串。
字符串分割
使用split()方法可以使字符串按指定的分割字符或字符串进行分割,并将分割后的结果存放在字符串数组中。split()方法提供了以下两种字符串分割形式。
split(String sign)
该方法可根据给定的分割符对字符串进行拆分。语法如下:
其中,sign为分割字符串的分割符,也可以使用正则表达式。
split(String sign,int limit)
该方法可根据给定的分割符对字符串进行拆分,并限定拆分的次数。语法如下:
sign:分割字符串的分割符,也可以使用正则表达式。limit:限制的分割次数。
格式化字符串
String类的静态format()方法用于创建格式化的字符串。format()方法有两种重载形式。
format(String format,Object...args)
该方法使用指定的格式字符串和参数返回一个格式化字符串,格式化后的新字符串使用本地默认的语言环境。语法如下:
format:格式字符串。
args:格式字符串中由格式说明符引用的参数。如果还有格式说明符以外的参数,则忽略这些额外的参数。此参数的数目是可变的,可以为0。
format(Local l,String format,Object...args)
该方法使用指定的语言环境、格式字符串和参数返回一个格式化字符串,格式化后的新字符串使用其指定的语言环境。语法如下:
l:格式化过程中要应用的语言环境。如果l为null,则不进行本地化。
format:格式字符串。
args:格式字符串中由格式说明符引用的参数。如果还有格式说明符以外的参数,则忽略这些额外的参数。此参数的数目是可变的,可以为0。
日期和时间字符串格式化
在应用程序设计中,经常需要显示日期和时间。如果想输出满意的日期和时间格式,一般需要编写大量的代码、经过各种算法才能实现。format()方法通过给定的特殊转换符作为参数来实现对日期和时间的格式化。
日期格式化
先来看一个例子。返回一个月中的天数,代码如下:
上述代码中变量s的值是当前日期中的天数,如今天是15号,则s的值为15;%te是转换符。常用的日期格式化转换符如表10.1所示。
时间格式化
使用format()方法不仅可以完成日期的格式化,也可以实现时间的格式化。时间的格式化转换符要比日期的格式化转换符更多、更精确,它可以将时间格式化为时、分、秒、毫秒等。格式化时间的转换符如表10.2所示。
格式化常见的日期时间组合
格式化日期与时间组合的转换符定义了各种日期时间组合的格式,其中最常用的如表10.3所示。
使用正则表达式
正则表达式通常被用于判断语句中,用来检查某一字符串是否满足某一格式。正则表达式是含有一些具有特殊意义字符的字符串,这些特殊字符称为正则表达式的元字符。例如,“\\d”表示数字0~9中的任何一个,“\d”就是元字符。正则表达式中的元字符及其意义如表10.5所示。
在正则表达式中,可以使用方括号括起若干个字符来表示一个元字符,该元字符可代表方括号中的任何一个字符。例如,reg = "[abc]4",这样字符串a4、b4、c4都是和正则表达式匹配的字符串。方括号元字符还可以为其他格式。如:[插图] [^456]:代表4、5、6之外的任何字符。[插图] [a-r]:代表a~r中的任何一个字母。[插图] [a-zA-Z]:可表示任意一个英文字母。[插图] [a-e[g-z]]:代表a~e或g~z中的任何一个字母(并运算)。
在项目中创建Judge类,使用正则表达式来判断“aaa@”“aaaaa”“1111@111ffyu.dfg.com”这3个E-mail地址哪一个是合法的。实例代码如下:
运行结果如图10.22所示。
字符串生成器
创建成功的字符串对象,其长度是固定的,内容不能被改变和编译。虽然使用“+”可以达到附加新字符或字符串的目的,但“+”会产生一个新的String实例,会在内存中创建新的字符串对象。如果重复地对字符串进行修改,将极大地增加系统开销。而JDK新增了可变的字符序列StringBuilder类,大大提高了频繁增加字符串的效率。
append()方法
该方法用于向字符串生成器中追加内容。通过该方法的多个重载形式,可实现接受任何类型的数据,如int、boolean、char、String、double或者另一个字符串生成器等。语法如下:
其中,content表示要追加到字符串生成器中的内容,可以是任何类型的数据或者其他对象。
insert(int offset, arg)方法
该方法用于向字符串生成器中的指定位置插入数据内容。通过该方法的不同重载形式,可实现向字符串生成器中插入int、float、char和boolean等基本数据类型的数据或其他对象。语法如下:
offset:字符串生成器的位置。该参数必须大于等于0,且小于等于此序列的长度。[插图] arg:将插入至字符串生成器的位置。该参数可以是任何数据类型的数据或其他对象。
向字符串生成器中指定的位置添加字符,代码如下:
delete(int start , int end)方法
移除此序列的子字符串中的字符。该子字符串从指定的start处开始,一直到索引end -1处的字符。如果不存在这种字符,则一直到序列尾部。如果start等于end,则不发生任何更改。语法如下:
[插图] start:将要删除的字符串的起点位置。[插图] end:将要删除的字符串的终点位置。
删除指定位置的子字符串,代码如下:
常用类库
包类型
Java是一种面向对象语言,但在Java中不能定义基本数据类型的对象,为了能将基本数据类型视为对象进行处理,Java提出了包装类的概念,它主要是将基本数据类型封装在包装类中,如int型的包装类Integer、boolean型的包装类Boolean等,这样便可以把这些基本数据类型转换为对象进行处理。
Integer类
java.lang包中的Integer类、Byte类、Short类和Long类,分别将基本数据类型int、byte、short和long封装成一个类,由于这些类都是Number类的子类,区别就是封装不同的数据类型,其包含的方法基本相同,所以本节以Integer类为例讲解整数包装类。Integer类在对象中包装了一个基本数据类型int的值,该类的对象包含一个int类型的字段。此外,该类提供了多个方法,能在int类型和String类型之间互相转换,同时还提供了其他一些处理int类型时非常有用的常量和方法。Integer类的常用方法如表11.2所示。
Integer类提供了以下4个常量:
[插图] MAX_VALUE:表示int类型可取的最大值,即231-1。[插图] MIN_VALUE:表示int类型可取的最小值,即-231。[插图] SIZE:用来以二进制补码形式表示int值的位数。[插图] TYPE:表示基本类型int的Class实例。
Double类
Double类和Float类是对double、float基本类型的封装,它们都是Number类的子类,都是对浮点数进行操作,所以常用方法基本相同,本节将对Double类进行讲解。对于Float类,可以参考Double类的相关内容。Double类在对象中包装一个基本类型为double的值,每个Double类的对象都包含一个double类型的字段。此外,该类还提供多个方法,可以将double类型转换为String类型,将String类型转换为double类型,也提供了其他一些处理double类型时有用的常量和方法。Double类的常用方法如表11.3所示。
Double类主要提供了以下常量:
[插图] MAX_EXPONENT:返回int值,表示有限double变量可能具有的最大指数。[插图] MIN_EXPONENT:返回int值,表示标准化double变量可能具有的最小指数。[插图] NEGATIVE_INFINITY:返回double值,表示保存double类型的负无穷大值的常量。[插图] POSITIVE_INFINITY:返回double值,表示保存double类型的正无穷大值的常量。
Boolean类
Boolean类将基本类型为boolean的值包装在一个对象中。一个Boolean类型的对象只包含一个类型为boolean的字段。此外,此类还为boolean类型和String类型的相互转换提供了许多方法,并提供了处理boolean类型时非常有用的其他一些常量和方法。Boolean类的常用方法如表11.4所示。
Boolean提供了以下3个常量:
[插图] TRUE:对应基值true的Boolean对象。[插图] FALSE:对应基值false的Boolean对象。[插图] TYPE:基本类型boolean的Class对象。
Character类
Character类在对象中包装一个基本类型为char的值,该类提供了多种方法,以确定字符的类别(小写字母、数字等),并可以很方便地将字符从大写转换成小写,反之亦然。Character类提供了很多方法来完成对字符的操作,常用的方法如表11.5所示。
Character类提供了大量表示特定字符的常量,例如:
[插图] CONNECTOR_PUNCTUATION:返回byte型值,表示Unicode规范中的常规类别“Pc”。[插图] UNASSIGNED:返回byte型值,表示Unicode规范中的常规类别“Cn”。[插图] TITLECASE_LETTER:返回byte型值,表示Unicode规范中的常规类别“Lt”。
Number类
前面介绍了Java中的包装类,对于数值型的包装类,它们有一个共同的父类——Number类,该类是一个抽象类,它是Byte、Integer、Short、Long、Float和Double类的父类,其子类必须提供将表示的数值转换为byte、int、short、long、float和double的方法。例如,doubleValue()方法返回双精度浮点值,floatValue()方法返回单精度浮点值,这些方法如表11.6所示。
数字处理
在Java语言中,提供了一个执行数学基本运算的Math类,该类包括常用的数学运算方法,如三角函数方法、指数函数方法、对数函数方法、平方根函数方法等一些常用数学函数方法。除此之外还提供了一些常用的数学常量,如PI、E等。本节将讲解Math类以及其中的一些常用方法。在实际开发中,随机数的使用是很普遍的,所以要掌握生成随机数的操作。在Java中主要提供了两种生成随机数的方式,分别为调用Math类的random()方法生成随机数和调用Random类生成各种数据类型的随机数。在Java中,还提供了大数字的操作类,即java.math.BigInteger类与java.math.BigDecimal类。这两个类用于高精度计算,其中BigInteger类是针对大整数的处理类,而BigDecimal类则是针对大小数的处理类。
数字格式化
数字格式化在解决实际问题时应用非常普遍,如表示某超市的商品价格,需要保留两位有效数字。数字格式化操作主要针对的是浮点型数据,包括double型和float型数据。在Java中使用java.text.DecimalFormat格式化数字,本节将着重讲解DecimalFormat类。在Java中,没有格式化的数据遵循以下原则:[插图] 如果数据绝对值大于0.001并且小于10000000,使以常规小数形式表示。[插图] 如果数据绝对值小于0.001或者大于10000000,使用科学记数法表示。由于上述输出格式不能满足解决实际问题的要求,通常将结果格式化为指定形式后输出。在Java中,可以使用DecimalFormat类进行格式化操作。DecimalFormat类是NumberFormat的一个子类,用于格式化十进制数字。它可以将一些数字格式化为整数、浮点数、百分数等。通过使用该类可以为要输出的数字加上单位或控制数字的精度。一般情况下,可以在实例化DecimalFormat对象时传递数字格式,也可以通过DecimalFormat类中的applyPattern()方法来实现数字格式化。
Math类
Math类提供了众多数学函数方法,主要包括三角函数方法,指数函数方法,取整函数方法,取最大值、最小值,以及平均值函数方法。这些方法都被定义为static形式,所以在程序中应用比较简便。可以使用如下形式调用:
三角函数方法
Math类中包含的三角函数方法如下:[插图] public static double sin(double a):返回角的三角正弦。[插图] public static double cos(double a):返回角的三角余弦。[插图] public static double tan(double a):返回角的三角正切。[插图] public static double asin(double a):返回一个值的反正弦。[插图] public static double acos(double a):返回一个值的反余弦。[插图] public static double atan(double a):返回一个值的反正切。[插图] public static double toRadians(double angdeg):将角度转换为弧度。[插图] public static double toDegrees(double angrad):将弧度转换为角度。
指数函数方法
Math类中与指数相关的函数方法如下。[插图] public static double exp(double a):用于获取e的a次方,即取eª。[插图] public static double log(double a):用于取自然对数,即取lna的值。[插图] public static double log10(double a):用于取底数为10的a的对数。[插图] public static double sqrt(double a):用于取a的平方根,其中a的值不能为负值。[插图] public static double cbrt(double a):用于取a的立方根。[插图] public static double pow(double a,double b):用于取a的b次方。
取整函数方法
在具体的问题中,取整操作使用也很普遍,所以Java在Math类中添加了数字取整方法。Math类中主要包括以下几种取整方法:[插图] public static double ceil(double a):返回大于等于参数的最小整数。[插图] public static double floor(double a):返回小于等于参数的最大整数。[插图] public static double rint(double a):返回与参数最接近的整数,如果存在两个同样接近的整数,则结果取偶数。[插图] public static int round(float a):将参数加上0.5后返回与参数最近的整数。[插图] public static long round(double a):将参数加上0.5后返回与参数最近的整数,然后强制转换为长整型。
取最大值、最小值、绝对值函数方法
在程序中最常用的方法就是取最大值、最小值、绝对值等,Math类中包括的操作方法如下:
public static double max(double a,double b):取a与b之间的最大值。[插图] public static int min(int a,int b):取a与b之间的最小值,参数为整型。[插图] public static long min(long a,long b):取a与b之间的最小值,参数为长整型。[插图] public static float min(float a,float b):取a与b之间的最小值,参数为单精度浮点型。[插图] public static double min(double a,double b):取a与b之间的最小值,参数为双精度浮点型。[插图] public static int abs(int a):返回整型参数的绝对值。[插图] public static long abs(long a):返回长整型参数的绝对值。[插图] public static float abs(float a):返回单精度浮点型参数的绝对值。[插图] public static double abs(double a):返回双精度浮点型参数的绝对值。
Random类
Random类是JDK中的随机数生成器类,可以通过实例化一个Random对象创建一个随机数生成器,语法如下:
以这种方式实例化对象时,Java编译器将以系统当前时间作为随机数生成器的种子。因为每时每刻的时间不可能相同,所以产生的随机数不同。但是如果运行速度太快,也会产生两个运行结果相同的随机数。
在Random类中,提供了获取各种数据类型随机数的方法,下面列举几个常用的方法:
[插图] public int nextInt():返回一个随机整数。[插图] public int nextInt(int n):返回大于等于0且小于n的随机整数。[插图] public long nextLong():返回一个随机长整型值。[插图] public boolean nextBoolean():返回一个随机布尔型值。[插图] public float nextFloat():返回一个随机单精度浮点型值。[插图] public double nextDouble():返回一个随机双精度浮点型值。[插图] public double nextGaussian():返回一个概率密度为高斯分布的双精度浮点型值。
BigInteger类
BigInteger类的数字范围较Integer类的数字范围要大得多。前文介绍过Integer类是int的包装类,int的最大值为231-1,如果要计算更大的数字,使用Integer类就无法实现了,所以Java中提供了BigInteger类来处理更大的数字。BigInteger类支持任意精度的整数,也就是说,在运算中BigInteger类可以准确地表示任何大小的整数值而不会丢失信息。在BigInteger类中封装了多种操作,除了基本的加、减、乘、除操作,还提供了绝对值、相反数、最大公约数以及判断是否为质数等操作。使用BigInteger类,可以实例化一个BigInteger对象,并自动调用相应的构造函数。BigInteger类具有很多构造函数,但最直接的一种方式是参数以字符串形式代表要处理的数字。
一旦创建了对象实例,就可以调用BigInteger类中的一些方法进行运算操作,包括基本的数学运算和位运算以及一些取相反数、取绝对值等操作。下面列举了BigInteger类中常用的几种运算方法:
public BigInteger add(BigInteger val):做加法运算。[插图] public BigInteger subtract(BigInteger val):做减法运算。[插图] public BigInteger multiply(BigInteger val):做乘法运算。[插图] public BigInteger divide(BigInteger val):做除法运算。[插图] public BigInteger remainder(BigInteger val):做取余操作。[插图] public BigInteger[] divideAndRemainder(BigInteger val):用数组返回余数和商,结果数组中第一个值为商,第二个值为余数。[插图] public BigInteger pow(int exponent):进行取参数的exponent次方操作。[插图] public BigInteger negate():取相反数。[插图] public BigInteger shiftLeft(int n):将数字左移n位,如果n为负数,做右移操作。public BigInteger shiftRight(int n):将数字右移n位,如果n为负数,做左移操作。public BigInteger and(BigInteger val):做与操作。[插图] public BigInteger or(BigInteger val):做或操作。[插图] public int compareTo(BigInteger val):做数字比较操作。[插图] public boolean equals(Object x):当参数x是BigInteger类型的数字并且数值与对象实例的数值相等时,返回true。[插图] public BigInteger min(BigInteger val):返回较小的数值。[插图] public BigInteger max(BigInteger val):返回较大的数值。
BigDecimal类
BigDecimal类和BigInteger类都能实现大数字的运算,不同的是BigDecimal类加入了小数的概念。一般的float型和double型数据只可以用来做科学计算或工程计算,但由于在商业计算中要求数字精度比较高,所以要用到BigDecimal类。BigDecimal类支持任何精度的定点数,可以用它来精确计算货币值。
system类
System类是JDK中提供的系统类,该类是用final修饰的,所以不允许被继承。System类提供了很多系统层面的操作方法,并且这些方法全部都是静态的。System类提供的较常用方法如表11.11所示。本节重点讲解利用System类控制台输出和计时这两个操作。
不会自动换行的print()方法
print()方法的语法如下:
此方法输出“Hello”文字,输出完毕后,光标会停留在“Hello”文字末尾,不会自动换行。
可以自动换行的println()方法
println()方法在print后面加上了“ln”后缀(就是line的简写),语法如下:
此方法输出“书籍是人类进步的阶梯!”后会自动换行。光标停留在下一行的开头。
计时
System.currentTimeMillis()方法可以获取自1970年1月1日零点至今的毫秒数。虽然Date日期类也有类似的方法,但代码会比System类多,所以System.currentTimeMillis()方法是为获取当前毫秒数最常用的方法。因为该方法的返回值精确到毫秒,所以可以利用该方法来记录程序的运行时间。
scanner类
与C语言不同,Java从控制台中读出用户输入的值,用到的不是一行可以直接使用的代码,而是由一个叫Scanner的类来实现的。Scanner英文直译就是扫描仪,它的用途就和现实生活的扫描仪一样,可以把数字化信息流转为人类可识别的文字。控制台输出内容用到了System.out就表示向控制台输出,System.in就表示从控制台输入,让Scanner扫描System.in就可以获取用户输入的值了。
使用Scanner类首先要引入该类,其语法如下:
Scanner类提供了如表11.13所示的几种常用的方法,通过这些方法可以获取控制台中输入的不同类型的值。
使用Scanner类扫描控制台的代码如下:
System.in表示控制台输入流,在创建Scanner对象时把System.in作为参数,这样创建出的扫描器对象扫描的目标就是用户在控制台中输入的内容,再通过表11.13中列出的方法将用户输入的内容转为Java的数据类型,就可以对数据进行加工、显示了。
日期时间类
在程序开发中,经常需要处理日期时间,Java中提供了专门的日期时间类来处理相应的问题,本节将对Java中的日期时间类进行详细讲解。
Date类
Date类用于表示日期时间,使用该类表示时间需要使用其构造方法创建对象,其构造方法及其说明如表11.14所示。
例如,使用Date类的第2种构造方法创建一个Date类的对象,代码如下:
上述代码中的System类的currentTimeMillis()方法主要用来获取当前系统时间距标准基准时间的毫秒数。另外,这里需要注意的是,创建Date对象时使用的是long型整数,而不是double型,这主要是因为double类型可能会损失精度。
日期时间格式化
如果在程序中直接输出Date对象,显示的是“Mon Feb 2917:39:50 CST 2016”这种格式的日期时间,那么应该如何将其显示为“2016-02-29”或者“17:39:50”这样的日期时间格式呢?Java中提供了DateFormat类来实现类似的功能。
DateFormat类是日期时间格式化子类的抽象类,可以按照指定的格式对日期或时间进行格式化。DateFormat类提供了很多类方法,以获得基于默认或给定语言环境和多种格式化风格的默认日期时间Formatter,格式化风格主要包括SHORT、MEDIUM、LONG和FULL 4种:
[插图] SHORT:完全为数字,如12.13.52或3:30pm。[插图] MEDIUM:较长,如Jan 12, 1952。[插图] LONG:更长,如January 12, 1952或3:30:32pm。[插图] FULL:完全指定,如Tuesday、April 12、1952 AD或3:30:42pm PST。
另外,使用DateFormat类还可以自定义日期时间的格式。要格式化一个当前语言环境下的日期,首先需要创建DateFormat类的一个对象,由于它是抽象类,因此可以使用其静态方法getDateInstance()进行创建,语法如下:
使用getDateInstance()方法获取的是所在国家或地区的标准日期格式。另外,DateFormat类还提供了一些其他静态方法。例如,使用getTimeInstance()方法可获取所在国家或地区的时间格式,使用getDateTimeInstance()方法可获取日期和时间格式。
由于DateFormat类是一个抽象类,不能用new创建实例对象。因此,除了使用getXXXInstance()方法创建其对象,还可以使用其子类,如SimpleDateFormat类,该类是一个以与语言环境相关的方式来格式化和分析日期的具体类,它允许进行格式化(日期→文本)、分析(文本→日期)和规范化。
Calendar类
打开Java API文档可以看到java.util.Date类提供的大部分方法都已经过时了,因为Date类在设计之初没有考虑到国际化,而且很多方法也不能满足用户需求,比如需要获取指定时间的年月日时分秒信息,或者想要对日期时间进行加减运算等复杂的操作,Date类已经不能胜任,因此JDK提供了新的时间处理类——Calendar日历类。
Calendar类是一个抽象类,它为特定瞬间与一组诸如YEAR、MONTH、DAY_OF_MONTH、HOUR等日历字段之间的转换提供了一些方法,并为操作日历字段(如获得下星期的日期)提供了一些方法。另外,该类还为实现包范围外的具体日历系统提供了其他字段和方法,这些字段和方法被定义为protected。
Calendar提供了一个类方法getInstance(),以获得此类型的一个通用的对象。Calendar类的getInstance()方法返回一个Calendar对象,其日历字段已由当前日期和时间初始化,其使用方法如下:
Calendar类提供的常用字段及其说明如表11.19所示。
Calendar类提供的常用方法及其说明如表11.20所示。
从表11.20可以看到,add()方法和roll()方法都用来为给定的日历字段添加或减去指定的时间量,它们的主要区别在于:使用add()方法时会影响大的字段,像数学里加法的进位或错位,而使用roll()方法设置的日期字段只是进行增加或减少,不会改变更大的字段。
Runtime类
Runtime类是JDK提供的运行时类,该类为Java程序提供了与当前运行环境相连接的一个通道,Java程序可以利用该类对当前的运行环境执行一些简单的操作。Runtime类不能使用new关键字创建实例,只能通过Runtime. getRuntime()方法获取实例。Runtime类的常用方法如表11.21所示,本节将重点讲解利用Runtime类执行本地命令和查看Java虚拟机所占内存这两个操作。
执行本地命令
本地命令指的是操作系统的命令。例如,在Linux系统下就表示shell命令,在Windows系统下就表示cmd命令。Runtime类提供exec()方法让Java代码可以执行系统的命令,exec()方法有很多重载的形式,例如:
[插图] command:要执行的系统命令,字符串类型。[插图] cmdarray:要执行的命令和相应的命令参数,字符串数组类型。
其实这两个重载方式很类似,如执行“javac hello.java”这行命令,使用第一种重载方式的代码如下:
使用第二种重载方式的代码如下:
exec()方法会返回一个Process对象。Process类是Java中进程类,该类是抽象类,不能使用new关键字创建实例。Process类的常用方法如表11.22所示,开发可以使用getInputStream()方法获取进程返回的信息。
查看内存
Runtime类可以通过freeMemory()方法查看当前Java虚拟机可用内存的剩余量。如果程序能够实时监控内存剩余量,就可以尽量控制程序对内存的占用,从而避免出现“内存溢出”的情况。同样,也可以用来对测试程序性能,检验程序算法是否导致内存紧张。
创建一个长度为10000的整型数组,利用循环为数组的每一个元素赋值,利用freeMemory()方法记录循环前后的内存剩余量,并计算出循环操作占用的内存总字节数。
运行结果如下:
0 条评论
下一页