从零开始学Java(第2版)(不提供光盘内容)(txt+pdf+epub+mobi电子书下载)


发布时间:2020-09-21 02:37:32

点击下载

作者:郭现杰,张权

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

从零开始学Java(第2版)(不提供光盘内容)

从零开始学Java(第2版)(不提供光盘内容)试读:

前言

Java技术与Web服务之间没有界限。——James Gosling(Java技术之父)

Java语言自诞生以来,经过十多年的发展和应用,已经成为非常流行的编程语言,根据权威编程语言排行榜显示,它始终居于第一位。现在全球已有超过15亿台手机和手持设备应用Java技术。同时,Java技术因其跨平台特性和良好的可移植性,成为广大软件开发技术人员的挚爱,是全球程序员的首选开发平台之一。

日益成熟的Java语言编程技术现在已无处不在,使用该编程技术可以进行桌面程序应用、Web应用、分布式系统和嵌入式系统应用开发,并且在信息技术等各个领域得到广泛应用。

本书全面介绍了Java语言基础,通过实例介绍了Java语言的编程技术和开发过程。关于Java的技术很多,只有学好编程基础,再学习深入的高级技术时才能得心应手,快学快用。本书优势

1.由浅入深

本书从Java语言的发展、开发环境及基本语法知识入手,逐步介绍了Java的基本概念、面向对象基础、图形界面程序的开发、网络程序开发及数据库应用程序的开发,让读者在没有编程基础的情况下,能够很快掌握Java语言编程的各种技术。

2.技术全面

本书从Java的基本概念入手,拓展到Swing、编程异常、线程、网络编程、数据库编程、JSP和Servlet等高级技术,以及对面向对象程序设计的主要原理和方法的介绍,可以让读者学得更充实。

3.示例讲解

本书每讲解到语法使用、编程要点时都会以示例的形式展现给读者如何具体应用,让读者在实践中得真知,并列举了大量翔实的情境插图,使读者更容易理解客观的理论知识。书中的代码示例都可以用在以后的实际应用当中。

4.辅助学习

为帮助读者学习,本书赠送光盘一张,其中包含书中用到的所有示例代码、PPT教案及长达数个小时的视频教程。本书的内容

第一篇Java基础(1~5章)

本篇主要讲解了Java语言的历史、特性、基本语法、数据类型、数组、程序控制语句和对字符串的处理,让读者对Java语言有一定的了解。通过学习本篇可以掌握Java的基本知识点,为以后开发打好基础。

第二篇Java面向对象(6~9章)

本篇主要介绍了面向对象编程的内容及特性,类、对象、继承、接口及集合等内容的使用,可以帮助读者从理论的角度理解什么是面向对象设计思想。

第三篇Swing(10~13章)

本篇主要内容包括Java图形界面开发知识,详细介绍了Swing组件、标准布局及事件的处理。读者通过学习本篇可以开发出漂亮的图形界面。

第四篇Java编程技术(14~19章)

本篇包括了Java高级编程的相关技术,对程序异常处理、并发程序线程、网络程序定义使用和输入/输出进行了讲解,这些都是较难理解和掌握的。本篇还介绍了数据库应用程序的开发及使用Swing组件创建数据库开发程序。读者可以自己多做练习,以便更快地掌握这些Java高级编程技术。

第五篇Java Web基础(20~21章)

本篇对Web开发程序进行了一些基础讲解,让读者对Web开发也有一定的认识和了解。本篇主要介绍了JSP程序设计和Servlet的一些基础知识及使用。

第六篇Java实战(22~23章)

本篇通过两章内容详细讲解了教务管理系统设计。让读者全面地认识到如何开发程序、如何分析业务流程、如何对程序需求进行分析,这些都是程序员必备的知识。读者可以应用前面所学的知识开发这套教务管理系统,学会独立开发程序。适合的读者 想从事软件开发的入门者。 Java自学者。 初级软件程序员。 从其他语言迁移过来的开发人员。 大中专院校的学生。 社会培训人员。

本书主要由郭现杰和张权组织编写。其他参与本书编写的人员有曾光、张双、朱照华、黄永湛、孟祥嵩、张贺军、李勇、关涛、王岩、李晓白、魏星、刘蕾、吕峰军等,在此一并表示感谢!

第一篇 Java基础

第1章 第一个Java程序——HelloWorld

Java是Sun公司于1995年推出的高级编程语言,具有跨平台的特性,它编译后的程序能够运行在多种类型的操作系统平台上。在当前的软件开发行业中已经成为主流,JavaSE、JavaEE技术已经发展成应用软件开发技术。Java在互联网的重要性可见一斑。

1.1 Java语言简介

Java可以开发出安装和运行在本机上的桌面程序、通过浏览器访问的面向Internet的应用程序,以及做出非常优美的图像效果。目前,Java成为了许多从事软件开发工作的程序员的首选开发语言。下面的章节将对其发展历史及应用进行介绍。1.1.1 Java语言的历史

Java是印度尼西亚爪哇岛的英文名称,因盛产咖啡而闻名。在Java中,许多库类名称都与咖啡有关,如JavaBeans(咖啡豆)、NetBeans(网络豆)及ObjectBeans(对象豆),等等。它的标识也正是一杯正冒着热气的咖啡。

Java的历史:

1991年4月,Sun公司开发了一种名为OaK的语言来对其智能消费产品(如电视机、微波炉等)进行控制。

1995年5月,Sun公司正式以Java来命名这种自己开发的语言。

1998年12月,Sun公司发布了全新的Java 1.2版,标志着Java进入了Java 2.0(Java two)时代,Java也被分成了现在的J2SE、J2EE和J2ME三大平台。这三大平台至今仍满足着不断增长的市场需求。

2002年2月,Sun公司发布了JDK 1.4,JDK 1.4的诞生明显提升了Java的性能。

2006年6月,Sun公司公开Java SE 6.0。同年公开了Java语言的源代码。

2009年4月,甲骨文公司以74亿美元收购Sun公司,取得Java的版权。

2010年9月,JDK 7.0发布,增加了简单闭包功能。

2011年7月,甲骨文公司发布Java 7.0的正式版。

2014年,甲骨文公司发布JDK 8.0,新增了对Lambda表达式的支持,JDK有了关键性的提升。不过,JDK8.0需要安装在Windows 7以上操作系统中,为了方便读者学习,本书仍然采用JDK7.0。

目前,共有3个独立的版本,用于开发不同类型的应用程序。 JavaSE。JavaSE的全称是Java Platform Standard Edition(Java

平台标准版),是Java技术的核心,主要用于桌面应用程序的开

发。 JavaEE。JavaEE的全称是Java Platform Enterprise

Edition(Java平台企业版),主要应用于网络程序和企业级应用

的开发。任何Java学习者都需要从JavaSE开始入门,JavaSE是

Java语言的核心,而JavaEE是在JavaSE的基础上扩展的。 JavaME。JavaME的全称是Java Platform Micro Edition(Java平

台微型版),主要用于手机游戏、PDA、机顶盒等消费类设备和

嵌入式设备中。1.1.2 Java语言的优点

Java语言最大的优点是它的跨平台性。一次编写,多处运行。能始终如一地在任何平台上运行,使得系统的移植、平台的迁移变得十分容易。其他优点如下。 简单易学:Java语言的语法与C语言和C++语言很接近,使得大

多数程序员很容易学习和使用Java。另一方面,Java丢弃了C+

+中很少使用的、很难理解的、令人迷惑的那些特性,如操作符

重载、多继承、自动的强制类型转换。特别地,Java语言不使用

指针,并提供了自动的废料收集,使得程序员不必为内存管理而

担忧,是很容易学习的。 面向对象:Java语言提供类、接口和继承等原语,为了简单起见,

只支持类之间的单继承,但支持接口之间的多继承,并支持类与

接口之间的实现机制(关键字为implements)。Java语言全面支

持动态绑定,而C++语言只对虚函数使用动态绑定。总之,Java

语言是一个纯的面向对象程序设计语言。 安全性:Java语言不支持指针,只有通过对象的实例才能访问内

存,使应用更加安全。 可移植性:这种可移植性来源于体系结构中立性,另外,Java还

严格规定了各个基本数据类型的长度。Java系统本身也具有很强

的可移植性,Java编译器是用Java实现的,Java的运行环境是用

ANSI C实现的。

对对象技术的全面支持和平台内嵌的API使得Java应用具有无比的健壮性和可靠性,这也减少了应用系统的维护费用。1.1.3 发展前景

自从Sun公司被甲骨文公司收购以后,Java的发展前景就变得扑朔迷离起来,很多程序开发者都感到很迷惑。2010年4月9日,被称为Java之父的James Gosling又在个人博客上宣布离开Oracle,这一事件更为Java的前景增加了一层迷雾。但是在进入5月份之后,一切开始变得明朗起来。

首先是Oracle在Java的后续支持方面,宣布了一系列关于Java的相关计划。在Oracle的活动发布网站上连续发布了多个关于Java的推广活动。Oracle主要产品负责人Dave Hofert提到以下问题: 商业版与社区版本之间平台支持的差异。 如何获得专家帮助,以帮助企业增强其Java应用。 对于旧版本的安全修补问题,可使用的发布工具和更新。

在赫尔辛基、斯图加特、布达佩斯、伦敦举行Oracle、Sun专家与用户见面会,在见面会上与用户一起探讨Java的发展路线。主要讨论的问题包括Oracle将如何继续投资和改进Java技术,并且还会向用户通报JavaSE、JavaME专家团队的最新消息、JavaFX和JDK 7.0最新的消息,以及Oracle Berkeley DB的相关信息。

Oracle绝对不会轻易放弃Java这块巨大的蛋糕,并且Oracle也开始逐渐学会了对开源社区的尊重。首先在JDK的商业版本方面,Oracle将会继续深入挖掘Java的商业利益,与其固有产品进行更深入的整合。在社区版本方面,Oracle将与Java开发者一起探讨和研发Java的技术。这里需要特别提到的一个产品是Oracle Berkeley DB,该产品是Oracle一直支持的一个开源非关系数据库产品,在NoSQL大行其道的今天,如果Oracle能够将Berkeley DB与Java进行深入整合,将会为Java带来更多的活力和生命。

1.2 工作原理

Java语言引入了Java虚拟机,具有跨平台运行的功能,能够很好地适应各种Web应用。同时,为了提高Java语言的性能和健壮性,还引入了如垃圾回收机制等的新功能,通过这些改进让Java具有其独特的工作原理。1.2.1 Java虚拟机(JVM)

Java虚拟机(Java Virtual Machine,JVM)是软件模拟的计算机,JVM是Java平台的核心,它可以在任何处理器上(无论是在计算机中还是在其他电子设备中)安全、兼容地执行保存在.class文件中的字节码。Java虚拟机的“机器码”保存在.class文件中,有时也可以称为字节码文件。

Java程序的跨平台特性主要是指字节码文件可以在任何具有Java虚拟机的计算机或电子设备上运行,Java虚拟机中的Java解释器负责将字节码文件解释成为特定的机器码运行。因此,在运行时,Java源程序需要通过编译器编译成为.class文件。

Java虚拟机的建立需要针对不同的软硬件平台来实现,既要考虑处理器的型号,也要考虑操作系统的种类。由此在SPARC结构、X86结构、MIPS和PPC等嵌入式处理芯片上,在UNIX、Linux、Windows和部分实时操作系统上都可实现Java虚拟机。

为了让编译产生的字节码能更好地解释与执行,把Java虚拟机分成了6个部分:JVM解释器、JVM指令系统、寄存器、栈、存储区和碎片回收区。 JVM解释器:虚拟机处理字段码的CPU。 JVM指令系统:该系统与计算机很相似,一条指令由操作码和操

作数两部分组成。操作码为8位二进制数,主要是为了说明一条

指令的功能,操作数可以根据需要而定,JVM有256种不同的操

作指令。 寄存器:JVM有自己的虚拟寄存器,这样就可以快速地与JVM的

解释器进行数据交换。为了功能的需要,JVM设置了4个常用的

32位寄存器:pc(程序计数器)、optop(操作数栈顶指针)、

frame(当前执行环境指针)和vars(指向当前执行环境中第一

个局部变量的指针)。 JVM栈:是指令执行时数据和信息存储的场所和控制中心,它提

供给JVM解释器运算所需要的信息。 存储区:JVM存储区用于存储编译过的字节码等信息。 碎片回收区:JVM碎片回收是指将使用过的Java类的具体实例从

内存中进行回收,这就使得开发人员避免自己编程控制内存的麻

烦和危险。随着JVM的不断升级,其碎片回收的技术和算法也更

加合理。JVM 1.4.1版后产生了一种分代收集技术,简单来说就

是依据对象在程序中生存的时间划分成代,以此为标准进行碎片

回收。1.2.2 无用内存自动回收机制

在程序的执行过程中,部分内存在使用过后就处于废弃状态,如果不及时进行回收,很有可能导致内存泄露,进而引发系统崩溃。在C++语言中是由程序员进行内存回收的,程序员需要在编写程序时把不再使用的对象内存释放掉,这种人为管理内存释放的方法往往会因程序员的疏忽而致使内存无法回收,同时也增加程序员的工作量。而在Java运行环境中,始终存在着一个系统级的线程,专门跟踪内存的使用情况,定期检测出不再使用的内存,并自动进行回收,避免了内存的泄露,也减轻了程序员的工作量。1.2.3 代码安全性检查机制

安全和方便总是相对矛盾的。Java编程语言的出现使得客户端计算机可以方便地从网络中上传或下载Java程序到本地计算机上运行,但是如何保证该Java程序不携带病毒或没有其他危险目的呢?为了确保Java程序执行的安全性,Java语言通过Applet程序来控制非法程序的安全性,也就是有了它才确保了Java语言的生存。

1.3 搭建Java程序开发环境

在编写程序之前,需要把相应的开发环境搭建好。开发环境搭建包括下载并安装Java开发工具包(JavaSE Development Kit,JDK)、安装运行时环境及配置环境变量。安装了JDK以后,才能对编写的Java源程序进行编译,而在安装了运行时环境后才能运行二进制的.class文件。1.3.1 系统要求

JDK是一种用于构建Java应用程序、Java小应用程序(又称为Applet)和组件的开发环境,其中包含了开发所必需的常用类库。JDK中带有进行编译的编译器工具javac.exe和运行程序的java.exe工具,所以JDK对于开发者来说是必备的。

要在Windows平台下编写并运行Java程序,对操作系统、开发工具有如下要求。

1.操作系统要求

在Windows中开发Java应用程序,要求至少是如下的操作系统之一:Windows XP Professional、Windows XP Home、Windows 2000 Professional、Windows Server 2003、Windows Vista/7。

2.Java SE开发工具箱(JDK 7.0)

在本书中,使用的是应用于微软Windows操作系统的JDK,当前的版本是JDK 7.0。下节将介绍如何下载并安装用于Windows操作系统的JDK 7.0开发工具箱。1.3.2 下载Java程序开发工具包JDK

在开发程序前,要在本机上安装开发工具包JDK,具体步骤如下。(1)打开浏览器,在地址栏中输入网址“http://www.oracle.com/technetwork/java/javase/downloads/index.html”,按Enter键,进入JDK下载中心界面,如图1.1所示。(2)在如图1.1所示的界面中选择Java Platform (JDK) 7u67进行下载,进入如图1.2所示的JavaSE Development Kit 7u67界面,选择“Accept License Agreement”单选按钮,然后选择适用于Windows x86的JDK版本jdk-7u67-windows-i586.exe文件进行下载就可以了。将下载的JDK保存在相应的文件夹中。图1.1 JDK下载中心界面图1.2 JDK选择下载

因为JDK版本更新很快,所以读者在实际下载时,可能具体的JDK名称与此稍有不同,根据各自需要进行下载就可以了。1.3.3 安装JDK

只有安装了JDK,才能使用其中的编译工具软件对Java源程序进行编译、使用其中的解释执行工具来运行Java字节码程序。安装JDK的具体步骤如下:(1)关闭所有正在运行的程序,双击下载的jdk-7u1-windows-i586.exe安装文件,弹出如图1.3所示的界面。(2)单击“下一步”按钮,进入如图1.4所示的界面。图1.3 安装文件首页图1.4 安装向导(3)单击“下一步”按钮,进入如图1.5所示的界面,选择所需安装的JDK组件,此处保持默认即可。图1.5 选择安装路径(4)单击“下一步”按钮,开始安装JDK,JDK安装完成后将打开如图1.6所示界面,设置JRE的安装位置,此处不做更改,使用默认安装路径“C:\Program Files\Java\jre7\”。图1.6 默认安装(5)单击“下一步”按钮,开始安装JRE,安装完成后单击“完成”按钮即可,如图1.7所示。图1.7 安装完成1.3.4 在Windows系统下配置JDK

读者按1.3.3节的操作步骤完成以后,就成功安装了JDK,但是,要想正确地使用JDK中的类库和编译器及程序启动工具,还需要手动设置JDK环境变量。在Windows操作系统下设置JDK环境变量的具体操作步骤如下:(1)选择“控制面板”|“系统”|“高级”|“环境变量”命令,弹出“环境变量”对话框。在“系统变量”列表框中进行环境变量的设置,如图1.8所示。(2)在列表框中找到“JAVA_HOME”变量,单击“编辑”按钮,输入变量值为用户所安装JDK的路径(C:\Program Files\Java\jdk1.7.0_67)。如没有该变量,则单击“新建”按钮,弹出如图1.9所示的“编辑系统变量”对话框。在对话框中输入变量名“JAVA_HOME”,再进行编辑。单击“确定”按钮,保存设置。图1.8 设置系统环境变量图1.9 编辑JAVA_HOME变量(3)“PATH”变量、“classpath”变量的编辑和JAVA_HOME是类似的,将“PATH”变量值的编辑为“%JAVA_HOME%\bin”,并以分号与其后的变量值隔开,单击“确定”按钮,保存设置。将“classpath”变量值编辑为“.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar”,单击“确定”按钮,保存设置。注意,输入的变量值最前面是一个点,用分号将其与后面的路径隔开,这样就编辑完环境变量了。(4)测试环境变量是否配置成功。选择“开始”|“所有程序”|“附件”|“命令提示符”命令,打开“命令提示符”窗口。在光标处输入命令“java-version”,并按Enter键。如果出现JDK的版本说明,如图1.10所示,则说明环境变量配置成功;否则,应重新配置环境变量。图1.10 环境变量配置成功

重新配置环境变量以后,需要打开一个新的“命令提示符”窗口进行测试,而不能在原来的“命令提示符”窗口下继续测试。

1.4 开发第一个Java应用程序

安装好JDK及配置好环境变量以后,就可以开发Java应用程序了。在编写程序之前先给大家介绍一下它的开发工具,如Eclipse、MyEclipse、JavaWorkshop、JBuider、Jdeveloper等。其中,Eclipse是比较受欢迎的一款开发工具。

Eclipse是针对Java编程的集成开发环境(IDE),可免费下载,大家可以登录Eclipse的官网http://www.eclipse.org/downloads/下载。而且Eclipse不需要安装,将下载好的zip包解压保存到指定目录下就可以使用了。1.4.1 Eclipse编写HelloWorld

下面就来感受一下Java的魅力吧!(1)打开Eclipse,弹出启动界面,如图1.11所示。接着弹出设置工作空间界面,如图1.12所示。保持默认路径就可以了,当然,用户也可以单击“Browse”按钮来更改路径,然后单击“OK”按钮。图1.11 Eclipse启动界面图1.12 设置工作空间界面(2)如图1.13所示,在Eclipse中选择“File”|“New”|“Java Project”命令,在打开的“New Java Project”窗口中输入自己的项目名称,单击“Finish”按钮,如图1.14所示,这样就完成了项目的创建。图1.13 新建项目图1.14 编辑项目名称(3)选中刚才创建的工程,选择“File”|“New”|“File”命令,在打开的窗口中输入源程序的名字,这里我们称之为“HelloWorld”,单击“Finish”按钮,如图1.15所示。这样我们就创建了源文件。图1.15 创建源文件(4)打开源文件,手动输入System.out.print("Hello World")。代码如下:(5)运行Java程序,选中HelloWorld.java文件,选择“运行”|“运行方式”|“Java应用程序”命令。这样我们的第一个Java程序就编写运行成功了。运行结果如图1.16所示。图1.16 运行结果

这种方法是用开发工具来实现的,是不是感觉很简单呢?1.4.2 源文件与命令行执行HelloWorld

还有一种方法是使用Windows系统自带的记事本作为Java源文件的编辑器,利用命令行执行程序,步骤如下。(1)首先需要创建一个Java的源程序文件。选择“程序”|“附件”|“记事本”命令,启动记事本编辑器。在文本文档中输入以下代码:

必须准确地输入代码、命令和文件名,因为编译器javac和启动程序java都是大小写敏感的,所以必须保持大小写一致。另外,文件中的所有标点符号必须在英文状态下输入。(2)在记事本中选择“文件”|“另存为”命令,弹出“另存为”对话框。 在“保存在”下拉列表框中指定要保存文件的目录。在本示例中

是“C:\test\chapter1”目录。 在“文件名”文本框中输入“HelloWorld.java”。 在“保存类型”下拉列表框中选择“文本文档(*.txt)”选项。 在“编码”下拉列表框中保持编码为ANSI。

结束以上操作后,对话框如图1.17所示。图1.17 保存Java源文件(3)打开“C:\test\chapter1”目录,可以看到一个名为HelloWorld.java的文件。(4)选择“开始”|“运行”命令,弹出“运行”对话框。在“运行”对话框中输入“cmd”命令并按Enter键,打开“命令提示符”窗口。(5)改变当前目录编译源文件,源文件保存在“C:\test\chapter1”目录中。输入以下命令,如图1.18所示。图1.18 进入源文件所在目录(6)查看源文件。输入“dir”命令,列出当前目录下的文件清单,如图1.19所示。可以看到当前目录下有HelloWorld.java源文件。图1.19 使用“dir”命令查看当前目录下的源文件(7)开始编译。在命令提示符下输入以下命令并按Enter键。(8)查看.class类文件。在命令提示符下输入“dir”命令并按Enter键,可以看到多了一个新的文件HelloWorldApp.class,如图1.20所示。图1.20 编译后的字节码(.class)文件(9)运行HelloWorld应用程序(在与源文件同一目录下),在命令提示符状态下,输入以下命令并按Enter键,可以看到如图1.21所示的内容。图1.21 运行HelloWorld应用程序

从图1.21中可以看到,运行HelloWorld应用程序,输出了一个问候信息“Hello World”。说明运行成功。1.4.3 Java应用程序的基本结构

在成功地编写、编译并运行了第一个Java应用程序“HelloWorld.java”程序以后,我们来分析一下HelloWorld应用程序的3个主要部分。

1.程序框架

HelloWorld是类名,类名前面要用public(公共的)和class(类)两个词修饰。Java程序是由类(class)组成的,一个源文件中可以包含多个类。

2.main()方法的框架

main方法是Java程序的入口,一个程序只能有一个main方法。

public static void main(String[] args)为固定用法,称为main方法的“方法签名”。其中,public、static、void都是关键字。

3.填写的代码

System.out.println()是Java语言自带的功能,向控制台输出信息。

1.5 小结

本章介绍了Java语言的发展历史、特点、工作原理、运行环境、开发过程及开发工具的使用。读者通过学习实例程序“HelloWorld”应该对程序的编写、编译、运行有了一定的了解,重点掌握搭建Java程序的开发环境,包括下载、安装、配置环境变量、JDK环境测试。下一章将向读者全面介绍Java语言的语法。

1.6 习题

1.Java语言的特点主要有哪几点?

2.如何配置Java环境变量?

3.如何测试JDK配置是否成功?

4.自己写一个简单的HelloWorld或其他具有简单功能的程序。

第2章 Java变量、数据类型、运算符

Java是一门高级程序语言,既然是语言就不可避免地要学习“词汇”、“句子”、“语法”,就像学习英文一样,先要学习单词、词组,把它们组合在一起才能编写出美妙的文章。Java语言也要从基础语法学起,这样才能编写出高效、简洁的程序。

2.1 标识符和关键字

标识符和关键字是编程的语言基础,命名标识符和对关键字的理解对编写程序有很大的帮助。程序中大量的类、对象、方法和变量都需要使用标识符和关键字,下面就针对标识符和关键字进行详细讲解。2.1.1 标识符

标识符是用来标识类名、对象名、变量名、方法名、类型名、数组名、文件名的有效字符序列,也就是它们的名称。Java规定,标识符由字母、数字、下画线“_”、美元符号“$”组成,并且首字母不能是数字。Java区分大小写,所以标识符user与User是不同的。2.1.2 标识符命名规则

为了日后能更好地维护或扩展程序,标识符要命名得有意义,初学时都喜欢用一些简单的字母来命名,如a、b、c等,尽管正确,但标识符多了,就分不清分别代表什么意思了。所以从一开始就要养成好习惯,使用有意义的标识符,最好能使用简短的英文单词。标识符的具体命名规则如下: 一个标识符可以由几个单词连接而成,以表明它所代表的含义,

如userName。 如果是类名,每个单词的首字母都要大写,其他字母则小写,如

UserInfo。 如果是方法名或变量名,第一个单词的首字母小写,其他单词的

首字母都要大写,如getUserName()、getUserInfo。 如果是常量,所有单词的所有字母全部大写,如果由多个单词组

成,通常情况下单词之间用下画线“_”分隔,如PI、

MIN_VALUE。 如果是包名,所有单词的所有字母全部小写,如

examples.chapter1。2.1.3 关键字

关键字是Java中赋予了一些特定含义的词汇,只能用于特定的地方。所以,对于有特定含义的关键字,在编程时是不能用来命名标识符的。

关键字是根据语法定义的需要而特别定义的标识符。这些标识符构成了Java语言最基本的语素,它们用来表示一种数据类型,或者表示程序的结构等。常用关键字分类如下。 用于包、类、接口定义:package、class、interface。 访问控制修饰符:public、private、protected、default。 数据类型:byte、char、int、double、boolean。 流程控制:if、else、while、switch、case、do、break、continue。 异常处理:try、catch、finally、throw、throws。 引用:this、supe。 创建对象:new。

使用关键字需要注意大小写,关键字不能用于命名标识符。虽然true、false、null不是关键字,但是保留字,所以仍然不能用于命名标识符。

2.2 常量与变量

编写代码时经常接触不同类型的数据,有的数据在程序运行中是不允许改变的(常量),有的数据在程序运行中是需要改变的(变量)。在程序中怎么表示常量和变量呢?下面进行详细介绍。2.2.1 常量概念及声明

常量是指在程序执行期间值不变的数据。一旦初始化后,就不能对其进行修改和再次赋值,只能进行访问。

声明一个常量,是指创建一个常量,通过常量名可以简单、快速地找到它的存储数据,常量类型为基本数据类型。声明常量必须使用关键字final。语法如下:

在声明常量标识符时,按照Java的命名规则,所有的字符都要大写。如果常量标识符由多个单词组成,则在各个单词之间用下画线“_”分隔,如STUENT_NUMBER。也可以先声明常量,再进行初始化,例如:

如果需要声明多个同一类型的常量,可以使用下面的语法:2.2.2 枚举类型

枚举类型是指字段由一系列固定的常量组成的数据类型。在生活中,一年四季的春、夏、秋、冬;表示方向的东、南、西、北;十二生肖,等等,都可以用枚举类型来表示。

Java中的枚举类型字段用大写字母表示,使用关键字enum声明枚举类型,例如:

在任何时候,如果需要代表一系列固定的常量,就可以使用枚举类型。如何使用枚举类型呢?下面我们介绍一个示例,代码如下:

运行结果如下:

Java语言中的枚举类型比其他语言中的枚举类型要强大得多。enum声明定义了一个类(称为“枚举类型”)。枚举类的类体中可能包括方法和其他字段。当编译器创建一个枚举时,它会自动添加一些专门的方法。2.2.3 变量概念及声明

变量是指在程序执行期间值可变的数据。类中的变量是用来表示类的属性的,在编程过程中,可以对变量的值进行修改。

实际上,变量和常量都是程序在运行时存储数据信息的地方,它们的区别就在于程序运行中值是否改变。在程序中使用变量时,先要声明变量,语法如下:

也可以先声明变量,然后在需要时再初始化,例如:

同时声明多个同一类型的变量,例如:

变量的值如果需要的话,可以在程序的任何地方被改变,例如:2.2.4 变量的作用域

变量的作用域是指变量的使用范围,只有在使用的范围内才可以调用变量。由于作用域的不同,变量类型有类变量、局部变量、方法参数变量和异常处理参数变量之分。

1.类变量

类变量指的是在类中声明的变量。类变量不属于任何方法,在整个类中可以随意调用。下面的示例在一个类中声明两个变量:name和age,并对它们进行赋值,在main()方法中调用。代码如下:

运行结果如下:

2.局部变量

局部变量是指在方法或方法代码块中定义的变量。下面的示例在main()方法中声明变量num1,并在该方法中就实现调用。代码如下:

运行结果如下:

3.方法参数变量

方法参数变量是指在方法中作为参数来定义的变量,例如:

4.异常处理参数变量

异常处理参数变量和方法参数变量类似,只不过异常参数变量是给异常服务的,也只能在异常代码块中调用。例如:

2.3 基本数据类型

Java中基本数据类型可以分为:整型、浮点型、布尔型、字符型。整型包括byte(字节型)、short(短整型)、int(整型)、long(长整型)。浮点型包括float(单精度型)、double(双精度型)、布尔型boolean及字符型char。基本数据类型是构造语言的最基础的要素。2.3.1 整型

整型是取值为整数的数据类型,不含小数的数字,默认为int型。可以用八进制、十进制、十六进制来表示。Java有4种整数类型,如表2.1所示。表2.1 Java中的整数类型

编写程序时在满足需求的情况下,选择合适的整数类型。不同类型的整型变量,内存分配空间大小也不一样。因此,对于不同类型变量,能保存的数值大小也是有限制的,不能超出它的取值范围。下面的示例中声明了两个变量,注释部分的变量赋值超出了类型的分配空间大小。例如:

为long型常量或变量赋值时,需要注意在所赋值的后面加上一个字母“L”(或小写“l”),说明赋值为long型。如果赋的值未超出int型的取值范围,也可以省略字母“L”(或小写“l”)。例如:2.3.2 浮点型

另一种存储数字类型的是浮点型。包括两种:float型(单精度浮点型)和double型(双精度浮点型),可以用十进制表示,主要用来存储小数。这两种类型占用空间和取值范围各不相同,如表2.2所示。表2.2 Java中的浮点类型

用来保存小数的变量,必须声明为浮点类型。下面定义了3个变量并赋值为小数。例如:

为float类型变量赋值时,需要在所赋值的后面加上字母“F”(或小写“f”),如果不加上字母“F”或(小写“f”)时,系统将默认为double类型,把double类型的数值赋给float类型的变量,是不正确的。

为double类型变量赋值时,既可以在所赋值的后面加上字母“D”(或小写“d”),也可以不加。浮点型变量除了可以接收小数之外,也可以接收整数,例如:2.3.3 布尔型

布尔型是用来表示逻辑值的数据类型,只有true(真)或false(假)两个值,用boolean关键字表示。布尔型通常用在关系运算和流程控制中进行逻辑判断。布尔型数据占1字节,默认为false。声明赋值布尔型变量的语法如下:2.3.4 字符型

字符型在程序中表示单个字符,一个字符占两个字节。用关键字char来声明字符型常量或变量,当声明char类型的变量并为其赋值时,所赋的值必须为一个英文字母、一个符号或一个汉字,并且要用英文状态下的单引号括起来,如'男'、'*'、'π'等。例如:

因为计算机只识别二进制数据,所以在Java中字符属于Unicode编码,并且Unicode字符集中的前128个字符与ASCII码字符集兼容,几乎可以处理世界上所有国家的语言文字,这是Java开发的一个特点。

有些字符不能通过键盘输入到程序中,这就需要使用转义字符常量,如表2.3所示。表2.3 Java中的转义字符2.3.5 数据类型转换

当把一种数据类型变量的值赋给另一种数据类型变量时,或者不同类型的数据混合在一起进行运算时,就需要进行数据类型转换。数据类型转换分自动类型转换和强制类型转换两种。

1.自动类型转换

自动类型转换是指由低优先级数据类型转换高优先级数据类型,这种转换系统会自动完成。在原始数据类型中,除了boolean类型外,其他数据均可参与算术运算,它们的数据类型从低到高的排列顺序如图2.1所示。图2.1 数据类型级别与取值范围

一种类型是否可以自动转换为另一种类型,要看是否能通过箭头到达。例如下面的代码,在进行赋值运算时:

可以看出,因为相对float类型来说,整数30为int型,属于低级数据类型,所以Java自动将其转换为float类型的30.0,并赋值给float类型的变量price。

不同类型的数据进行混合运算时,分为以下两种情况: 参与混合运算的,只有byte、short或char类型的数据。

这种情况,Java首先将byte、short或char类型转换为int类型,然后再参与运算,运算结果也是int型的。例如:

将变量byteVar、shortVar、charVar转换为int型,然后相加,结果为int型,并赋值给int型变量value。字符'a'的值转换时为98。 参与混合运算的,含有int、long、float或double型的数据。

这种情况下,Java首先将所有低数据类型转换为表达式中数据类型最高的数据类型,然后再参与运算,并且运算结果也是表达式中最高的数据类型。例如:

3个变量byteVar、intVar、floatVar进行加法运算时,首先被转换为double型,然后相加,结果也为double型,并赋值给double型变量value。

2.强制类型转换

强制类型转换是指由高优先级数据类型转换低优先级数据类型。这种转换系统不会自动完成,必须由程序员强制进行类型转换。例如,在进行赋值运算时:

代码中(int)显示为强制类型转换的语法,即在欲转换的数据或变量前面,用“(目的数据类型)”的形式,强制转换为括号中的目的数据类型。从示例中也可以看到,在进行强制类型转换时,进行了截断而不是四舍五入(输出228,而不是229)。

2.4 运算符

运算符是指一些特殊的符号,被用于数学函数、赋值语句和逻辑比较等方面,是有特定意义的符号。表达式是具有确定值的语句,由操作数和运算符组成。程序中会用到大量的运算符和表达式。运算符有以下几种类别: 算术运算符。 赋值运算符。 关系运算符。 逻辑运算符。 位运算符。 自增自减运算符。 三元运算符。2.4.1 算术运算符

算术运算符相应地完成基本的算术运算,包括+(加)、-(减)、*(乘)、/(除)四则运算及%(取余)运算。这5种算术运算符如表2.4所示。表2.4 算术运算符

下面通过一个示例来认识一下运算符的应用,分别对两个变量进行求和、求差、求商计算。代码如下:

运行结果如下:

这里要特别注意除法运算和取余运算。如果进行除法运算的两个操作数都是整数,那么不论能否整除,运算结果都将是一个整数。运算结果只是简单的截断,即去掉小数部分,而不是四舍五入。如果当在整数之间进行取余运算时,运算结果为数学运算中的余数。2.4.2 赋值运算符

赋值运算符为“=”,即数学中的等于号。赋值运算符是将运算符“=”右边的值赋给左边的变量。

Java中可以把赋值语句连在一起,进行一连串的赋值。例如:

另外,还提供了几个复合赋值运算符,以提高程序员的编码效率,如+=、-=、*=、/=、%=。把变量经过计算后把值再赋给变量,如表2.5所示。表2.5 赋值运算符

下面通过示例来对赋值运算符进行详细了解,代码如下:

运行结果如下:

要特别注意其中的除号“/”,该运算符两边为整数时,结果也为整数。2.4.3 关系运算符

关系运算符表示两个值或变量之间的关系,运算结果为boolean型。当关系表达式成立时,运算结果为true(真);不成立时,运算结果为false(假)。关系运算符如表2.6所示。表2.6 关系运算符

所有关系运算符都可以对整数、浮点数和字符型数据进行比较,其中等于和不等于运算符可用于所有数据类型的比较。2.4.4 逻辑运算符

逻辑运算符是对结果进行判断运算的。操作数和运行结果只能是布尔型,即true(真)和false(假)。逻辑运算符如表2.7所示。表2.7 逻辑运算符

1.与运算符“&&”

当两个关系表达式通过“&&”连接在一起,且两个关系表达式的值都为true(真)时,该组合的表达式的值才为true(真),否则为false(假)。

2.或运算符“||”

当两个关系表达式通过“||”连接在一起时,两个关系表达式的值有一个为true(真),该组合的表达式的值就为true(真)。当两侧表达式的值都为false(假)时,则表达式的值为false(假)。

3.取反(非)运算符“!”

运算符“!”用于对逻辑值进行取反运算。当逻辑值为true(真)时,经过取反运算后,结果为false(假);当逻辑值为false(假)时,经过取反运算后,结果为true(真)。例如:2.4.5 位运算符

位运算是对操作数以二进制为单位进行的运算,运算结果为整数。就是将操作数转换为二进制形式,按位进行布尔运算,运算结果也为二进制数。位运算符有7个,如表2.8所示。表2.8 位运算符

1.按位与运算符“&”

按位与运算符是将参与运算的两个二进制数进行“与”运算,如果两个二进制位都为1,则该位的运算结果为1,否则为0。即:0&0=0,0&1=0,1&0=0,1&1=1。例如:

十进制6的二进制表示为00000000 00000110,十进制11的二进制表示为00000000 00001011。运算过程如下:

输出结果为2。

2.按位或运算符“|”

按位或运算符是将参与运算的两个二进制数进行“或”运算,如果二进制位上有一个位的值是1,则该位的运算结果为1,否则为0。即:0|0=0,0|1=1,1|0=1,1|1=1。例如:

运算过程如下:

输出结果为15。

3.按位异或运算符“^”

按位异或运算符是将参与运算的两个二进制数进行“异或”运算,如果二进制位相同,则值为0,否则为1。即:0^0=0,0^1=1,1^0=1,1^1=0。例如:

运算过程如下:

输出结果为13。

4.按位取反运算符“~”

按位取反运算符只对一个操作数进行操作。如果二进制位是0,则取反值为1;如果是1,则取反值为0。即:~0=1,~1=0。例如:

十进制6的二进制表示为00000000 00000110。其运算过程如下:

输出结果为-7。

5.左移位运算符“<<”

左移位运算符“<<”就是将操作数所有二进制位向左移动一位。运算时,右边的空位补0。左边移走的部分舍去。例如:

十进制11的二进制表示为00000000 00001101。a<<1的运算过程如下:

输出结果为22。

6.右移位运算符“>>”

右移位运算符“>>”就是将操作数所有二进制位向右移动一位。运算时,左边的空位根据原数的符号位补0或1(原来是负数就补1,是正数就补0)。例如:

十进制11的二进制表示为00000000 00001011。a>>1的运算过程如下:

输出结果为-6。

7.无符号右移位“>>>”

无符号右移位运算符“>>>”就是将操作数所有二进制位向右移动一位。运算时,左边的空位补0(不考虑原数正负)。例如:

十进制11的二进制表示为00000000 00001011。a>>>1的运算过程如下:

输出结果为5。2.4.6 自增自减运算符

Java提供了一类特殊的运算符,称为“++(自增运算符)”和“——(自减运算符)”。使用自增和自减运算符可减少一定的代码量,使程序更加简洁。“++”、“——”运算符是一元运算符,表达式x++或++x相当于x=x+1,而表达式x——或——x相当于x=x-1。例如:“——”运算符使用方式与“++”运算符一致。如果“++”运算符放在变量名前面,称为“前缀运算符”;如果放在变量名后面,就称为“后缀运算符”。对前缀运算符来说,它总是先自增1,然后参与运算;对后缀运算符来说,它总是先以原来的值参与运算,然后再自增1。2.4.7 三元运算符

Java中只有一个三元运算符“?:”,其返回值更直接,书写形式更简单。语法如下:

三元运算符的运算规则是:首先判断逻辑表达式的值,如果为true(真),整个三元表达式的值为表达式1的值;否则,为表达式2的值。例如:2.4.8 运算符的优先级

运算符有不同的优先级,优先级越高越优先执行。运算符的优先级的顺序(由高到低)如表2.9所示。表2.9 运算符的优先级

其实没有必要去刻意记忆运算符的优先级别。编写程序时,尽量使用括号来实现想要的运算顺序,以免产生歧义。

2.5 小结

本章讲述了常量和变量的概念及声明,4种基本数据类型:整型、浮点型、布尔型和字符型。8种运算符:算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符、自增自减运算符和三元运算符,并有相应的示例演示。读者应对Java语言的基本语法有一定的认识,理解数据类型和运算符的原理,掌握变量的使用。下一章将介绍重要的引用数据类型——数组。

2.6 习题

1.举例说明哪些是非法标识符?

2.自定义两种不同数据类型变量,完成它们之间的类型转换。

3.声明几个数字变量,进行算术运算,并输出结果。

第3章 数组

数组是一种复合数据类型,是相同数据的集合。数组可以分为一维数组和多维数组。Java中也提供了很多内置函数来对数组进行操作,可以编写更复杂的程序。

3.1 数组的概念

数组是在程序设计中,为了处理方便,把具有相同类型的若干变量按有序的形式组织起来的一种形式。这些按序排列的同类数据元素的集合称为数组。数组中的每个数据称为数组元素,数组元素是有序的。下面详细介绍数组的定义、声明及使用。3.1.1 什么是数组

数组是用来存储相同数据类型的数据集合,可使用共同的名称来引用数组中的数据。数组可以存储任何类型的数据,包括原始数据类型和对象,因此,按数组元素的类型不同,数组又可分为数值数组、字符数组、指针数组、结构数组等各种类别。但一旦指定了数组的类型之后,就只能用来存储指定类型的数据。也可以理解为数组是专门用来存储大批量数据信息的。3.1.2 数组的特点

数组提供了一种数据分组的方法。可以通过数组的下标(即数据项在数组中的索引值,从0开始。)来访问数据。数组有以下特点: 既能存储原始数据类型,又能存储对象类型。 数组元素的个数称为数组的长度。长度一旦确定,就不能改变。 数组元素的下标是从0开始的,即第一个元素的下标是0。 可以创建数组的数组。 数组可以作为对象处理。数组对象含有成员变量length,用来表

示数组的长度。3.1.3 数组的规则

规则说明有以下几点: 可以只给部分元素赋初值。当{}中值的个数少于元素个数时,只

给前面部分元素赋值。例如,static int a[10]={0,1,2,3,4};表示只给

a[0]~a[4]五个元素赋值,而后五个元素自动赋0值。 只能给元素逐个赋值,不能给数组整体赋值。例如,给10个元

素全部赋值为1,只能写为static int a[10]={1,1,1,1,1,1,1,1,1,1};,

而不能写为static int a[10]=1;(注意:在C语言中是这样,但并非

在所有涉及数组的地方都这样)。 如不给可初始化的数组赋初值,则全部元素均为0值。 如给全部元素赋值,则在数组说明中可以不给出数组元素的个

数,例如,static int a[5]={1,2,3,4,5};可写为static int

a[]={1,2,3,4,5};。动态赋值可以在程序执行过程中对数组作动态

赋值。这时可用循环语句配合scanf函数逐个对数组元素赋值。

3.2 一维数组

编程最常用到的是一维数组。所谓一维数组,是一组相同类型数值的集合。使用一维数组要声明数组变量、创建数组对象并赋值、访问或修改存储的数据(元素)。3.2.1 声明一维数组

声明一维数组的语法有两种格式。数据类型可以是Java中常用的类型,数组名称要符合标识符命名规则。参数[]可以放在数据类型前或数组名称后。例如:

例如,可以分别声明一个int型、一个boolean型、一个float型的一维数组。

也可以使用第二种声明数组变量的方式。如下所示:

建议使用第一种方式,它更符合数组变量的原理,即在数组名字前面指定数组可以保存的数据类型,这里使用方括号“[]”来代表数组类型。3.2.2 初始化一维数组

一维数组初始化也有两种格式。一种是先声明再赋值,另一种是直接声明赋值。例如:

一维数组是引用对象,所以可使用new运算符来直接创建一个数组对象,但必须指定数组的长度。这种初始化的方法效果是一样的,但把数组元素值直接放在大括号中来完成创建和初始化数组比较简洁,这种方法创建数组对象时,大括号中的元素类型必须与声明的数据类型一致,数组的长度与大括号中元素的个数要相同。

使用new运算符来创建数组对象时,必须指定这个数组的大小。创建数组对象时,仅仅是在内存中为数组变量分配指定大小的空间,并没有实际存储数据,这时数组的所有元素会被自动地赋予初值,其中: 数字数组,初值是0。 布尔数组,初值是false。 字符数组,初值是'\0'。 对象数组,初值是null。3.2.3 访问一维数组

当创建数组变量并赋值后,就可访问数组中的元素了。例如:

运行结果如下:

数组元素的下标从0开始。如果在程序中引用了下标超出数组范围的元素,Java编译器就会显示一个“ArrayIndexOutOfBoundsException”错误提示。3.2.4 修改一维数组元素

数组中的元素值是可以改变的。在声明一个数组变量和创建一个数组对象以后,可以通过为数组中的元素赋值,来修改数组中任一元素的值。例如,用数组计算两个学生的语文、数学、英语总分成绩。代码如下:

本示例中先声明了4个数组,其中,mathScore、englishScore和languageScore存储学生的各科成绩,students存储学生的姓名,totalScore存储学生的总成绩。通过修改数组中指定元素值的方式为数组englishScore和languageScore初始化成绩。最后输出两个同学的姓名及其总成绩,运行结果如下:

3.3 数组的常用操作

数组的常用操作包括数组的填充、复制、比较、排序等。Java提供了相应对数组操作的系统函数(方法),利用系统函数(方法)可以对数组进行各种操作。3.3.1 数组长度

数组长度指的是数组的大小,也就是数组包含元素的个数。如果想获得数组的长度,可以用其本身的length属性获得。使用方法就是在数组名后加“.length”。语法如下:

下面输出一个数组的长度大小。代码如下:

运行结果如下:3.3.2 数组填充

数组填充指的是将一个数组或数组指定元素用固定值添加到数组中。可以使用Arrays类提供的fill对数组进行填充。语法如下:

或:

下面是两种填充元素语法的应用,分别对两个数组进行填充元素,代码如下:

运行结果如下:3.3.3 数组复制

数组复制是将一个指定数组范围内的元素值复制到另一个数组中去。Java提供了Arraycopy函数(方法)来进行数组的复制操作。语法如下:

将数组a的元素从开始复制元素的下标复制到数组b中,复制元素的个数由复制长度决定。示例如下:

运行结果如下:3.3.4 数组比较

数组之间也可以比较,如果两个数组的长度一样,并且相同位置的元素也一样,那么这两个数组相等;否则,不相等。可以使用Arrays提供的equals来判断两个数组是否相等。语法如下:

返回值为boolean值,示例如下:

运行结果如下:3.3.5 数组排序

数组排序指的是将数组中的元素按照一定的顺序进行排列,实际应用中会经常对数组进行排序操作,数组排序主要包括sort函数(方法)排序和冒泡排序。Arrays提供了sort函数(方法)排序,语法如下:

sort函数(方法)是升序排序,可以将数组全部排序,也可以在指定范围内将元素排序。示例如下:

运行结果如下:

数组排序除了sort函数(方法)排序外,还有一种冒泡排序法,又称交换排序法。整个过程是把数组中最小的元素看做重量最轻的气泡,让它上浮,并依次从底端进行上浮操作,所以形象地称之为冒泡排序。下面的示例将一个数组用冒泡排序法进行排序。代码如下:

运行结果如下:3.3.6 在数组中搜索指定元素

有时需要搜索数组中某个元素是否存在,可以使用Arrays提供的binarySearch函数(方法)来解决这个问题。语法如下:

该方法的返回值是int类型,指所在的下标。示例如下:

运行结果如下:3.3.7 把数组转换为字符串

数组还可以转换为由数组元素列表组成的字符串,可以使用Arrays类的toString函数(方法),任何数组类型都可以。语法如下:

返回值为字符串类型。示例如下:

运行结果如下:

3.4 多维数组

数组元素除了可以是原始数据类型、对象类型之外,还可以是数组,即数组元素是数组。通过声明数组的数组来实现多维数组。3.4.1 声明二维数组

本节介绍二维数组的声明、创建和使用。多维数组的使用和二维数组使用相似,只做简单介绍。声明二维数组语法有两种格式。例如:

建议采用第一种方式,同声明一维数组类似。对于其他多维数组声明也是类似的。例如:3.4.2 创建二维数组

创建二维数组对象有两种格式。例如:

两种方法都可以,可按照程序需求来确定使用哪种方式。创建多维数组和创建二维数组类似。例如:

使用new运算符来创建二维数组对象时,必须指定这个数组的长度。也可以把数组元素值直接放在大括号中,来同时完成创建和初始化数组。在大括号中使用逗号分隔每个花括号,每个花括号中用逗号分开数据。在这里,定义一个二维数组num1,示例如下:

它有2行4列。可以把数组num1看成如表3.1所示的二维表格。表3.1 二维数组num1的结构3.4.3 访问二维数组

创建数组变量并赋值后就可以访问二维数组元素了。用该数组的名称后面加两个中括号表示,第一个下标为行索引,第二个下标为列索引。示例如下:

输出数组num1的第1行(下标为0)第3列(下标为2)的元素,应该输出的值为30。运行结果如下:

在二维数组中,行和列的下标都是从0开始计数的。3.4.4 遍历二维数组

通过以下程序使用两种不同的方法创建二维数组,并将两个二维数组输出。代码如下:

3.5 小结

本章详细介绍了Java数组的概念,了解如何声明、创建、访问多维数组,重点掌握一维数组的概念和数组的常用操作。其中二维数组的创建及用法比较难一些,建议读者了解即可。下一章将介绍Java程序流程控制基本语句。

3.6 习题

1.用程序实现对数组a[45,96,78,6,18,66,50]中的元素进行排序。

2.自定义数组,实现输出该数组的长度、最大值和最小值。

3.一维数组和多维数组的区别有哪些?

第4章 条件结构和循环结构

Java中程序流程控制语句包括条件结构、循环结构和跳转语句。程序可以根据需求选择不同的执行语句。通过综合运用这些流程语句,可以实现复杂的计算问题。

4.1 条件结构

条件结构包括顺序结构和选择结构。顺序结构在程序执行中没有跳转和判断,直到程序结束为止。选择结构包括if语句、if-else语句和switch语句。这些语句用来控制选择结构,程序执行中可改变程序的执行流程。4.1.1 if语句

if语句是根据条件判断之后再处理的一种语法结构,是经常使用的判断语句。例如,当一个男人向一个女人求婚时,这个女人会做什么判断呢?

上例中,想要女人嫁给男人,男人必须达到女人的要求,就是有车有房有存款。Java中是如何使用if语句来进行判断呢?语法格式如下:

当条件表达式判断为true(真)时,程序会执行if后面花括号中的代码;为false(假)时,程序会跳过if语句执行后面的代码。if语句执行流程如图4.1所示。图4.1 if语句流程图

关键字if后小括号中的条件必须是条件表达式,表达式的值必须是boolean类型true或false。示例如下:4.1.2 if-else语句

if-else语句是在if语句形式基础上加了一条else语句。可以对判断结果做出选择。例如,在4.1.1节例子中,当一个男人向一个女人求婚时,女人思考的逻辑也可以是这样的:

女人根据条件有两个选择:“我嫁给你”和“我不嫁给你”。满足条件是一个结果,不满足条件是另一个结果,生活当中是很常见的。在Java中,也有相应的条件语句来完成类似的逻辑判断和有选择地执行这样的功能,这就是if-else语句。语法格式如下:

if-else语句又称双分支选择结构,流程图如图4.2所示。图4.2 if-else语句流程图

示例如下:

运行结果如下:4.1.3 if-else-if语句

if-else-if语句可以对更多的条件进行判断,else后面又跟着一个if,比前两种语句又复杂些。语法格式如下:

if-else-if语句执行时,对if后面括号中的条件表达式进行判断,如果条件表达式的值为true,就执行语句块1。否则,对条件表达式2进行判断,如果条件表达式的值为true,就执行语句块2,依此类推。

如果所有条件表达式的值都为false,则执行最后语句块n+1。

if-else-if又称多分支选择语句,流程图如图4.3所示。图4.3 if-else-if语句流程图

根据学生的成绩,判断其属于哪个档次并输出。代码如下:

当程序执行到score>=90时,计算结果为true,执行其后的语句块,输出“优”,并结束if-else-if语句。运行结果如下:4.1.4 选择语句的嵌套

选择语句的嵌套是指if语句中再嵌套if语句。一般用在比较复杂的分支语句中,语法格式如下:

使用嵌套的if语句,根据学生的成绩判断其属于哪个档次并输出。代码如下:

运行结果如下:

else总是和离它最近的if进行匹配,在if语句嵌套时,尽可能使用花括号进行划分逻辑关系,避免出现问题。例如:

运行结果如下:

很明显,输出结果是错误的。因此大家最好加上花括号为代码划分界限。4.1.5 switch语句

switch语句属于多分支结构,可以代替复杂的if-else-if语句。表达式的结果类型只能是byte、short、int或char类型。switch语句是多分支的开关语句,语法格式如下:

程序执行时,如果表达式的值与某个case的值相等,就执行此case后的语句。如果表达式的值没有与之相等的值就会执行default后的执行语句。break可以省略,但程序会执行每一条语句,直到遇到break为止。其流程图如图4.4所示。图4.4 switch语句流程图

使用switch语句编写一个程序,要求根据学生成绩等级,输出成绩为“优”、“良”、“及格”和“不及格”。代码如下:

运行结果如下:

变量score依次和case后面的变量值进行比较,当case为80时和score的值相等,执行后面的语句,输出“良”。4.1.6 if与switch的区别

if语句和switch语句结构很相似,都是多分支选择语句,但是switch结构只能处理等值条件判断,而且必须是整型变量或字符型变量,而多重if结构却没有这个限制。在使用switch结构时不要忘记每个case的最后加上break。通常情况下,分支的层次超过3层时,使用switch语句。如果条件判断一个范围,这时要使用if-else-if语句。

4.2 循环结构

循环结构可以重复执行相同或类似的操作,让程序完成繁重的计算任务,同时还可以简化程序编码。循环结构语句包括for、while、do-while共3种循环语句。4.2.1 while循环语句

while循环首先判断循环条件是否满足,如果第一次循环条件就不满足的话,直接跳出循环,循环操作一遍都不会执行。这就是while循环的一个特点:先判断,后执行。语法格式如下:

while是关键字,循环条件是一个布尔表达式,结果为true(真)时执行循环,结果为false(假)时结束循环。流程图如图4.5所示。图4.5 while语句流程图

使用while语句循环输出1~100,代码如下:

运行结果如下:

程序首先定义一个循环变量i,并赋初值为1。循环变量i用在while后面的表达式i<101中,作为循环的条件,保证循环只到100。i++为改变循环条件语句,每执行一次循环体,变量i自增1,当循环执行到101时,循环条件不再成立,结束循环。如果在while语句中没有改变循环条件的语句,那么循环将无限期地执行下去,这称为“死循环”。下面介绍一下死循环的程序示例。代码如下:

因为没有改变循环变量语句i++,所以条件“i=1且永远小于101”,循环将会无限期地执行下去。编写程序时这是要避免的。4.2.2 do-while循环语句

do-while语句与while语句很相似,都可以完成相同的功能。它是先执行do后面的循环体语句,然后对while后面的布尔表达式进行判断,如果为true,再次执行do后面的循环体语句,并再次对布尔表达式的值进行判断;否则,结束循环语句。由于先执行一遍循环操作,然后在判断条件,所以它的特点是先执行后判断。语法格式如下:

循环条件也是一个布尔表达式。需要注意的是,while后面使用分号“;”作为结尾,这与while语句不同。流程图如图4.6所示。图4.6 while语句流程图

使用do-while语句循环输出1~100,代码如下:

运行结果如下:

虽然while语句和do-while语句在大多数情况下可以相互替代,是等价的,但是在某些情况下,它们的使用还是有区别的,如while语句是先判断,后执行;而do-while语句是先执行,后判断。所以即使一开始循环条件就不成立,do-while语句中的循环体也会执行一次。例如,下面两个示例:

运行结果如下:

本例使用了while循环语句,对布尔类型的条件变量flag的值进行判断。如果flag的值为真,就将变量num1的值增1。循环执行,直到flag的值为flase为止,最后输出执行循环语句以后的变量num1的值。

运行结果如下:

使用do-while循环语句,无论布尔变量的初值是否为flase(即不论初始条件是否成立),都会先执行num1++语句。4.2.4 for循环语句

for语句是最经常使用的循环语句,一般用在循环次数已知的情况下。for循环比while和do-while循环更复杂也更灵活。语法格式如下:

初始化表达式只在循环前执行一次,通常作为迭代变量的定义。条件表达式是一个布尔表达式,当值为true(真)时,才会执行或继续执行for语句的循环体语句。迭代表达式用于改变循环条件的语句,如自增、自减等运算。

for循环语句的执行过程如下:(1)执行初始化表达式。(2)对中间的条件表达式的值进行判断,如果为true,则执行后面的循环体语句。(3)执行迭代表达式,改变循环变量的值。(4)重复执行上述两步,开始下一次循环,直到某次中间的条件表达式的值为false,结束整个循环语句。流程图如图4.7所示。

使用for循环语句输出1~100。代码如下:图4.7 while语句流程图

先对循环变量i赋初值1,然后判断条件表达式i<101的值,如果为true,就执行循环语句,为第i+1个元素赋值,更新循环变量。当i=101时,条件不成立,结束循环。for语句的循环变量也可以在for语句外面声明并初始化,例如:

即使表达式为空,分隔3个表达式的分号“;”也不能省略,3个表达式都为空也是合法的,但是分号依然不能省略,例如:4.2.5 嵌套循环语句

嵌套循环是指一个循环结构循环体中可以包含另一个循环结构。while语句、do-while语句及for语句都可以嵌套,而且它们之间也可以相互嵌套。下面是几种嵌套的语法格式。(1)while语句嵌套。(2)do-while语句嵌套。(3)for语句嵌套。(4)for循环与while循环嵌套。(5)for循环与do-while循环嵌套。(6)while循环与for循环嵌套。(7)do-while循环与for循环嵌套。(8)do-while循环与while循环嵌套。(9)while循环与do-while循环嵌套。

使用嵌套循环用“*”输出一个直角三角形。代码如下:

运行结果如下:

采用两重循环,第一重循环控制打印行数,第二重循环输出“*”号,每一行“*”号个数逐行增加,最后输出一个直角三角形。

4.3 跳转语句

为了在程序中更好地控制循环操作进行流程跳转,这就需要跳转语句。跳转语句有break、continue和return。4.3.1 break跳转语句

在switch语句中已经接触到了break语句,其作用是终止switch语句的执行,而整个程序继续执行后面的语句。循环结构中的break语句也起同样的作用。当循环结构中执行到break语句时,它立即停止当前循环并执行循环下面的语句。语法格式如下:

在循环结构中使用break输出0~99。代码如下:

运行结果如下:

当i=100时,执行break语句,终止循环。一个循环中可以有多个break语句,break不是专门用来终止循环的,只有在某些情况下来取消一个循环。4.3.2 continue跳转语句

continue应用在for、while、do-while等循环语句中,作用是跳过本次循环,执行下一次循环语句。语法格式如下:

continue跳转语句应用示例如下:

运行结果如下:

当i=3时,用continue语句跳过本次循环,继续执行下面的判断循环。4.3.3 break与continue的区别

continue和break语句都是跳出循环,但它们的作用是不同的。continue语句只结束本次循环,而不是终止整个循环的执行。而break语句则是结束整个循环过程,不再判断执行循环的条件是否成立。有以下两个循环结构: 循环结构1 循环结构2

循环结构1的流程图如图4.8所示,而循环结构2的流程图如图4.9所示。请注意两图中当“表达式2”为true时流程的转向。图4.8 使用break语句的流程图图4.9 使用continue语句的流程图4.3.4 return跳转语句

return语句是终止当前方法运行,返回到调用该方法的语句处。该语句还提供相应返回值。语法格式如下:

return跳转语句应用示例如下:

当if条件语句结果为true(真)时,执行第一条return语句(return true),退出方法。下面(return false)跳过不执行。

4.4 实例

本节通过打印九九乘法表实例程序讲解,帮助读者学习和掌握循环结构在程序中的应用。

使用双重循环来实现打印输出一个九九乘法表,代码如下:

运行结果如下:

在程序MultiTable中,第二层for循环的循环条件为j<=i,即列索引值要小于等于行索引值,只有这样,才能在行和列索引值相等时换行。另外,为了输出结果的美观整齐,当乘积为个位数时,后面要多输出一个空格。

4.5 小结

本章深入介绍了Java中两大流程控制结构:条件结构和循环结构。其中,if语句、switch语句、while语句、do-while语句及for语句是本章需要重点掌握的内容,最后讲解的跳转语句内容主要是提高循环语句的灵活性,读者熟悉了解即可。建议读者多学习一些有关算法的知识,从而能够解决更复杂的问题。在学习本章时,一定要将书中的实例运行成功,以便更好地理解程序流程控制语句。

在下一章中,将向读者介绍Java编程中经常要遇到的问题:字符串的处理。

4.6 习题

1.用循环实现1~100的总和。

2.用循环输出一个等腰三角形。

3.break和continue的区别是什么?

第5章 字符串处理

字符串是复合数据类型。在程序中经常会用到字符串及对字符串的各种操作,如字符串的连接、比较、截取、查找、替换等。Java提供了Java.lang.String类来对字符串进行这一系列的操作,以及StringBuffer类。

5.1 字符

字符指的是用单引号括起来的单个字母。在Java中,表示字符的数据类型为char。一个字符在内存中占16位大小的空间(2个字节)。在编写程序的多数时候,如果想使用一个单独的字符值,通常会使用原始的char类型。例如:

有时可能需要使用一个字符作为一个对象,例如,将一个字符作为一个方法的参数,而该参数应该为对象类型。同样,Java语言也提供了一个“包装(wrapper)器”类,用来将char类型的字符“包装”为一个Character对象。一个类型为Character的对象包含一个单独的字段,其类型为char。Character类还提供有一系列的类方法(静态方法)用于操纵字符。可以使用Character构造器创建一个Character对象,代码如下:

Java编译器会根据需要自动创建一个Character对象。例如,如果传递一个原始的char类型字符到一个期望参数是对象的方法中,编译器会自动将char转换为Character。这个特性被称为“自动装箱”,或者如果转换是相反方向的,称为“拆箱”。下面是一个自动装箱的示例。

下面是一个既有装箱又有拆箱的示例。

Character类是不可变的,所以一旦一个Character对象被创建,就不能被改变。表5.1中列出了Character类中最有用的一些方法。表5.1 Character类中有用的方法

在一个字符前带一个反斜线符号“\”,是一个“转义字符序列”,并且对于编译器来说,每一个转义字符序列都有一个特定的含义。本书的System.out.println()语句中,已经频繁地使用到“\n”转义字符,它的含义是输出一个字符串后转到下一行。表5.2列出了Java中的转义序列。表5.2 转义字符序列

5.2 字符串

字符串或串(String)是由零个或多个字符组成的有限序列。它是编程语言中表示文本的数据类型。通常以串的整体作为操作对象,例如,在串中查找某个子串、求取一个子串、在串的某个位置上插入一个子串及删除一个子串等。两个字符串相等的充要条件是:长度相等,并且各个对应位置上的字符都相等。设p、q是两个串,求q在p中首次出现的位置的运算称为模式匹配。串的两种最基本的存储方式是顺序存储方式和链接存储方式。5.2.1 字符串声明与赋值

String是字符串变量的类型,字符串使用String关键字来声明。Java中,字符串一定是用双引号括起来的零个或多个字符序列。

在Java中,像其他原始数据类型一样,在使用字符串对象之前,需要先声明一个字符串变量。语法格式如下:

字符串变量必须赋值后才可以使用,这称为字符串对象初始化。初始化有3种方式,分别为使用new运算符、直接赋值和初始化为空。语法格式如下:

使用new运算符来创建对象时,Java会自动为该字符串分配相应大小的内存。也可以直接将字符串字面量赋给String类型的变量,用“=”连接,并要用双引号“”括住要赋的值。对于字符串对象来说,如果一开始并无确定的初值,那么可以定义为null。需要注意,null值与空字符串是不同的。空字符串仅仅是不含字符,它还需要双引号括起来。而null值则是此变量本身就没有引用任何值。5.2.2 获取字符串长度

length()方法是用来获取字符串长度的。它会返回字符串对象中所包含的字符的个数。例如:

调用对象的方法,要使用圆点“.”运算符,上面代码中的hello.length()可以理解为hello的length()方法。这条语句执行以后,变量len的值为10。包含在字符串中的标点或空格在计算字符串的长度时也要包括在内。

5.3 字符串基本操作

String类型的对象是不能改变的,而在编程过程中,经常有改变字符串形式或长短的情况,Java语言提供了几种对字符串的操作函数(方法),下面就给大家详细介绍这几种操作字符串的函数(方法)。5.3.1 字符串连接

最经常对字符串进行的操作之一就是将两个字符串连接起来,合并为一个字符串。String类提供连接两个字符串的方法concat( ),格式如下:

concat( )方法返回一个字符串,是将字符串string2添加到string1后面后形成的新字符串。例如:

运行结果如下:

也可以直接使用字符串字面量来调用concat( )方法,例如:

连接字符串还可以使用加号“+”运算符。这是一个重载了的运算符,用来直观地连接两个字符串。它使用起来比concat()方法更加灵活。例如:

运行结果如下:

需要注意的是,当表达式中包含多个加号“+”,并且存在各种数据类型参与运算时,则按照加号“+”运算符从左到右地进行运算,Java会根据加号“+”运算符两边的操作数类型来决定是进行算术运算还是字符串连接的运算。例如:

运行结果如下:

第一行代码从左至右先计算“10+2.5”,结果为12.5。然后计算“12.5+"price"”,结果为“12.5prcie”;对于第二行代码,先计算“"price"+10”,然后再计算“prcie10+2.5”,结果为“prcie102.5”。在多行之间使用加号“+”运算符进行连接,代码更加清晰,在打印输出语句中使用很普遍。例如:

运行结果如下:5.3.2 字符串比较

Java中String类提供了几种比较字符串的方法。最常用的是equals()方法,它是比较两个字符串是否相等,返回boolean值。使用格式如下:

equals()方法会比较两个字符串中的每个字符,相同的字母,如果大小写不同,其含义也是不同的。例如:

运行结果如下:

还有一种是忽略字符串大小写的比较方法,这就是equalsIgnoreCase()方法。同样返回boolean值。使用格式如下:

运行结果如下:

在比较字符串时,不能使用“==”,因为使用“==”比较对象时,实际上判断的是是否为同一个对象,如果内容相同,但不是同一个对象,返回值为false。5.3.3 字符串截取

所谓截取就是从某个字符串中截取该字符串中的一部分作为新的字符串。String类中提供substring方法来实现截取功能。使用格式如下:

字符串第一个字符的位置为0。第一种是只有开始位置,它截取的是从这个位置开始一直到字符串的结尾部分。第二种是开始和结尾位置都有,那么直截取指定开始和结尾位置部分。例如:

运行结果如下:5.3.4 字符串查找

字符串查找是指在一个字符串中查找另一个字符串。String类中提供了indexOf方法来实现查找功能。使用格式如下:

第一种是从指定字符串开始位置查找。第二种是从指定字符串并指定开始位置查找。例如:

运行结果如下:5.3.5 字符串替换

字符串替换指的是用一个新字符去替换字符串中指定的所有字符,String类提供的replace方法可以实现这种替换。使用格式如下:

string1表示原字符串,用newchar替换string1中所有的oldchar,并返回一个新字符串。例如:

运行结果如下:5.3.6 字符串与字符数组

有时会遇到字符串和字符数组相互转换的问题,可以方便地将字符数组转换为字符串,然后利用字符串对象的属性和方法,进一步对字符串进行处理。例如:

运行结果如下:

在使用new运算符创建字符串对象时,将字符数组作为构造函数的参数,可以将字符数组转换为字符相应的字符串。相反,也可以将字符串转换为字符数组,这需要使用字符串对象的一个方法toCharArray()。它返回一个字符串对象转换过来的字符数组。例如:

运行结果如下:5.3.7 字符串其他常用操作

Java中String提供了很多方法来对字符串进行各种复杂操作,在实际编程中可以查看相关API,根据不同需求使用各种方法。表5.3列出了字符串的常用方法及说明。表5.3 String类常用方法

5.4 StringBuffer类

一个String对象的长度是固定的,如果使用String类对字符串进行不同的操作,会产生很多对象,需要另外分配空间。针对这个问题Java提供了StringBuffer类,既可以节省空间,又能改变字符串的内容。5.4.1 认识StringBuffer类

StringBuffer类所产生的对象默认有16个字符的长度,内容和长度都可改变的。如果附加的字符超出可容纳的长度,则StringBuffer对象会自动增加长度以容纳被附加的字符。String类型和StringBuffer类型的主要性能区别其实在于String是不可变对象,因此在每次对String类型进行改变时其实都生成了一个新的String对象。而StringBuffer类则不一样,每次操作结果都会在StringBuffer对象本身进行,不会生成新的对象。所以,在字符串对象经常改变的情况下使用StringBuffer类型,会让程序的运行效率提高。5.4.2 StringBuffer类提供的操作方法

在StringBuffer对象上有append()和insert()方法,它们有多种重载的形式,可以把不同类型的数据转换为字符序列,然后添加或插入到StringBuffer对象中。append()方法总是添加字符串到字符序列的最后,而insert()方法则将字符或字符串添加到指定的位置。关于StringBuffer类的各种方法,如表5.4所示。表5.4 各种StringBuffer方法5.4.3 StringBuffer实例

下面做一个测试StringBuffer的程序,看看它具体是怎么操作的。代码如下:

运行结果如下:5.4.4 String类与StringBuffer类对比

String类和StringBuffer类有以下不同点。

String类:该类一旦产生一个字符串,其对象就不可以改变,并且字符串的内容和长度也是不变的。如果在程序中需要调用该字符串的信息,就需要调用系统所提供的各种字符串操作方法,通过这些方法来对字符串进行相关操作,不会改变对象实例本身,而是产生了一个新的字符串对象示例,并且系统在为String类对象分配内存时,也是按照对象所包含的实际字符数来分配的。

StringBuffer类:该类具有缓冲功能。StringBuffer类处理可改变字符串。如果需要修改一个StringBuffer类的字符串,不需要再创建一个新的字符串对象,可以直接在原来的字符串上进行操作。该类中的有些方法和String类不同。系统在为StringBuffer类对象分配内存时,除去当前字节所占有控件外,还另外提供了16个字符大小的缓冲区。Length方法可以返回当前实际所包含的字符串长度,而capacity()方法则可以返回当前数据容量和缓冲区容量的和。

5.5 实例

本节通过一个“用户登录验证程序”实例来帮助读者了解和掌握字符串处理在实际应用程序开发中的应用。

用户登录验证方式是对用户所输入的用户名和密码进行验证。如果用户输入的用户名和密码正确,就认为是合法用户,允许进入系统;否则,就认为是非法用户,拒绝其登录。代码如下:

本例进行了简化,将用户名和密码设置为固定的值,用户名“张三@163.com”和密码“123456”,在实际开发过程中,原始的用户名和密码应该来源于数据库、文件或其他保存数据的地方。args是main()方法的字符串数组参数。它自动保存用户在执行Java程序时所输入的参数字符串序列。trim()方法用来消除字符串首尾的空格。最后,调用String类的equals()方法,将用户登录时输入的用户名和密码与程序中保存的原始用户名和密码进行比较,如果相符,则用户登录成功;否则,给出相应的提示信息。

5.6 小结

本章深入介绍了Java中有关字符串处理的相关知识,对字符串的处理包括字符串的连接、比较、截取、查找、替换等。本章需要重点掌握对字符串的各种处理操作,建议读者多做练习。对StringBuffer类的概念和方法了解即可。从下一章开始,将向读者介绍Java的面向对象概念。

5.7 习题

1.自定义3个字符串,分别比较字符串的内容。

2.自定义2个字符串,将其连接成一个字符串。

3.自定义字符串,使用StringBuffer替换字符串中的某些部分。

4.简述String和StringBuffer的区别。

第二篇 Java面向对象

第6章 面向对象

面向对象是Java语言的基本特征。将客观世界中的事物描述为对象,并通过抽象思维方法将需要解决的实际问题分解成人们易于理解的对象模型,然后通过这些对象模型来构建应用程序的功能。类和对象是面向对象编程的基础。

6.1 面向对象编程简介

面向对象编程(Object Oriented Programming)是一种创建程序的方法,对象是对现实世界实体的模拟,由现实实体的过程或信息来定义。一个对象可被认为是一个把数据(属性)和程序(方法)封装在一起的实体,这个程序产生该对象的动作或对它接收到的外界信号的反应。这些对象操作有时称为方法。面向对象开发的要素有:封装、继承和多态性。6.1.1 类

类是面向对象程序设计语言中的一个概念。一个类定义了一组对象。类具有行为(Be-havoir),它描述一个对象能够做什么及做的方法(Method),它们是可以对这个对象进行操作的程序和过程。类(Class)实际上是对某种类型的对象定义变量和方法的原型。它表示对现实生活中一类具有共同特征的事物的抽象,是面向对象编程的基础。

类是对某个对象的定义。它包含有关对象动作方式的信息,包括它的名称、方法、属性和事件。实际上它本身并不是对象,因为它不存在于内存中。当引用类的代码运行时,类的一个新的实例(即对象)就在内存中创建了。虽然只有一个类,但这个类在内存中创建多个相同类型的对象。

可以把类看做“理论上”的对象,也就是说,它为对象提供蓝图,但在内存中并不存在。从这个蓝图可以创建任何数量的对象。从类创建的所有对象都有相同的成员:属性、方法和事件。但是,每个对象都像一个独立的实体一样动作。例如,一个对象的属性可以设置成与同类型的其他对象不同的值。

类是具有相同属性和共同行为的一组对象的集合。例如,顾客和收银员,现实世界中顾客很多,收银员也很多,因此,顾客m仅仅是顾客这类人群中的一员,即一个实例。因此我们可以将它们共同具有的特征抽象出来,这些共同的属性和行为被组织在一个单元中,就称为类。类有属性和方法。对象或实体所拥有的特征在类中表示时称为类的属性。对象执行动作称为类的方法。6.1.2 对象

在Java的世界中“万物皆对象”,现实世界中所有事物都可视为对象,对象无处不在。Java是一门面向对象的编程语言,我们要学会用面向对象的思想思考问题,编写程序。面向对象(Object-Oriented,OO)思想的核心就是对象(Object)。对象表示现实世界中的实体,因此,面向对象编程能够很好地将现实世界中遇到的概念模拟到计算机程序中。例如,顾客m和收银员n就是两个对象,都有自己的特征。顾客m特征:姓名、年龄、体重,执行动作:购物;收银员n特征:姓名、年龄、体重,执行动作:收款。

面向对象的特征如下。

1.唯一性

每个对象都有自身唯一的标识,通过这种标识,可找到相应的对象。在对象的整个生命期中,它的标识都不改变,不同的对象不能有相同的标识。

2.分类性

分类性是指将具有一致的数据结构(属性)和行为(操作)的对象抽象成类。一个类就是这样一种抽象,它反映了与应用有关的重要性质,而忽略其他一些无关内容。任何类的划分都是主观的,但必须与具体的应用有关。

3.继承性

继承性是子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类时,可以在一个已经存在的类的基础之上来进行,把这个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。

继承性是面向对象程序设计语言不同于其他语言的最重要的特点,是其他语言所没有的。6.1.3 继承

不同类型的对象,相互之间经常有一定的共同点。例如,公交车、小轿车、卡车都有汽车的特性(品牌、排汽量、当前速度等)。同时,每一个对象还定义了额外的特性使得它们与众不同。例如,公交车的底盘高度;小轿车的载人量;而卡车有额外的载重量。

又如,小学生、中学生、大学生都是学生,都有学生的特性(学号、姓名、班级等)。同时,每一个还定义了额外的、自己独有的而别人没有的特性,如小学生有一个是否为少先队员的属性;中学生有一个参加高考的行为;而大学生有专业属性。

面向对象程序设计允许类从其他类继承通用的状态和行为。类的继承关系如图6.1所示。图6.1 类的一个继承层次

在Java程序中,每一个类有一个直接的父类,每一个父类有可能有无限多的子类。使用继承,可以快速创建新的类。继承使用关键字extends表示。语法格式如下:6.1.4 接口

接口中可以声明属性、方法、事件和类型(Structure),但不能声明变量,也不能设置这些成员的具体值,也就是说,只能定义而不能给它里面定义的变量赋值。接口通常是一组相关方法。实现一个接口,使类所提供的方法更加统一。如电视机、DVD,它们都实现了相同的“按钮”接口,这样对外界来说它们的行为是统一的:只需用户操作按钮,就可以打开相应的电器。接口在类和外部世界之间形成了一个契约的关系,如果一个类声明实现一个接口,那么在类能被成功地编译之前,在接口中定义的所有方法必须出现在类的源代码中。要使用接口就要声明接口,用interface关键字表示。语法格式如下:6.1.5 包

为了更好地组织类,Java提供了包机制。包是类的容器,用于分隔类名空间。如果没有指定包名,所有的示例都属于一个默认的无名包。Java中的包一般均包含相关的类。

包是组织一系列相关类和接口的一个命名空间。从概念上,可以将包理解为计算机上的文件夹。要创建包通过关键字package声明,语法格式如下:

Java平台提供一个规模庞大的类库(一系列的包),用于在程序员编写的应用程序中使用。这个类库就是有名的“应用程序接口”,简称“API”。它里面的包代表了与通用程序相关的最常用的任务。

当使用包说明时,程序中无须再引用(import)同一个包或该包的任何元素。import语句只用来将其他包中的类引入当前名字空间中。而当前包总是处于当前名字空间中。

6.2 类

前面介绍过,类是用来创建对象的模板,包含被创建对象的属性和方法的定义。因此,要学习Java编程就必须学会怎样去编写类,即怎样用Java的语法去描述一类事物共有的属性和行为。对象的属性通过变量来表示,而对象的行为通过方法来实现。方法可以操作属性形成一定的算法来实现一个具体的功能。类把属性及对属性进行操作的相关方法封装为一个整体。6.2.1 基本结构

在Java语言中,类是构成程序的基本要素,程序是由类组成的。使用面向对象的语言开发程序,就犹如使用一个个零件组装机器一样,大大降低了开发的难度,提高了开发的效率。因此,能否熟练掌握类及类的使用,是衡量一个程序员水平的重要标志。Java的类主要包括两个部分:类的声明和类的主体。

1.类的声明

最简单的类的声明,包括类的名称、类名前面加关键字class、类名后面紧跟一对花括号。类的声明语法格式如下:

其中,class是关键字,类的名称用来标识一个类,通常类的名称的第一个字母要大写,例如:

在类的声明中,也可以加上访问权限控制符,以控制对该类的访问,其语法格式如下:

在这里,用方括号将类修饰符括起来,它代表的含义是里面的内容是可选的。类修饰符可以是public、private、abstract、final和strictft,它们决定其他类能否访问本类或能访问什么。public表示定义的类可以被Java的所有软件包使用。例如:

在声明一个类时,还可以提供更多的信息。例如,指明该类的父类(又称超类)的名称,即该类是从哪个类派生过来的,这需要在类名后面加上关键字extends来实现,例如:

还可以在声明一个类时指明该类是否实现接口,这需要在类名后面加上关键字implements来实现,例如:

一般情况下,类的声明可以按顺序包括以下这些内容: 修饰符,如public、private及许多其他修饰符。 类名,按惯例首字母要大写。 父类(超类)的名称,都要在前面加上关键字extends。一个类

只能继承自一个父类。 被该类实现的接口列表(用逗号进行分隔),在接口前面加上关

键字implements。一个类可以实现多个接口。

2.类的主体

类的主体,简称类体,指的是类名后面花括号中的内容。类体包含所有用于创建自该类的对象的生命周期的代码,包括构造器(用于初始化新对象)、属性声明(用于表示类及其对象的状态)及方法(实现类及其对象的行为)。6.2.2 类变量

在程序中有多种类型的变量,如字段、局部变量、参数等。这些变量在程序中的位置不同,所起的作用也不相同。下面对这些变量加以说明。 在一个类中的成员变量被称为字段。 在一个方法中或代码块中的变量被称为局部变量。 在方法声明中的变量被称为参数。

其中,字段的声明由3部分组成,其语法格式如下:

例如,在6.2.1节声明的Car类有3个属性:品牌、排汽量和速度。可以使用下面的代码来定义3个字段。

Car类的字段被命名为brand、exhaust和speed。关键字public说明这些字段是公共成员,可以被任何能够访问该类的对象所访问。

1.访问修饰符

修饰符可以让程序员知道控制字段有什么样的使用权限。目前只考虑public和private。 public修饰符:所有的类访问该字段。 private修饰符:它所在的类内部访问该字段。

根据封装的原则,通常使用私有字段(即使用private修饰符来定义字段),如下面的代码定义Car类:

在这里,声明了3个private(私有的)字段,没有赋初值,那么Java虚拟机会赋给其默认的初始值:String类型的为null,float类型的为0.0,int类型的为0。

2.数据类型

所有的成员变量都必须有一个类型。可以是原始数据类型,如int、float、boolean等。也可以是引用类型,如字符串、数组或对象。

3.变量名称

所有的变量或参数都遵循Java标识符的命名规范。方法名和类名也遵循同样的命名规范,但在以下方面与变量命名不同。 类名的首字母应大写。 方法名的第一个单词应该是一个动词。6.2.3 类方法

类由一组具有相同属性和共同行为的实体抽象而来,对象执行的操作通过编写类的方法来实现。显而易见,类的方法是一个功能模版,作用是“做一件事”。在类中声明成员方法的语法格式如下:

由一对花括号括起来的语句是方法体,它包含一段程序代码。方法名称主要在调用这个方法时使用,命名方法与命名变量、类一样,要遵守一定的规则,必须以字母、下画线或“$”开头。可以包含数字但不能以数字开头。在方法的主体内,如果方法具有返回值,则必须使用关键字return返回。6.2.4 类方法命名

在编写程序时,方法名称应该是一个小写的动词或一个多词组组成的名称,但是要以一个小写的动词起始,后面跟形容词、名词等。在多词组组成的方法名中,从第二个单词开始,后面的每一个单词的首字母要大写。如下面这些方法的名称:

通常,一个方法在一个类中具有唯一的名称。然而,一个方法也可能具有与其他方法相同的名称,这就是“方法重载”。6.2.5 调用类方法

调用方法就是执行方法体中的代码语句。在程序中要调用方法,必须指明调用哪个对象的方法,因为在面向对象的语言中,方法是封装在对象中的。当调用某个对象的方法时,程序流程就会转向方法定义中的第一条语句,并顺序执行其中的代码,直到遇到return语句或右花括号为止。方法调用示例,代码如下:

程序输出结果如下:

在程序中,定义成员方法sayHello()时,因其仅仅是简单的输出,不需要返回值,所以返回类型为void。而且此方法不需要参数,因此参数列表为空,但圆括号不能省略。当调用sayHello()方法时,只需要在对象名后面使用圆点运算符“.”引用其方法名即可。在调用方法时,主程序暂停,转而执行sayHello方法中的代码,直到执行完毕,再转回主程序继续执行后面的语句。6.2.6 方法重载

Java语言支持“重载”方法,并且Java能够根据不同的“方法签名”来区分重载的方法。这意味着,在一个类中,可以有相同名称但具有不同参数列表的方法(当然会有一些限定条件,这将在接口和继承当中讨论)。

假设有一个类,它可以输出各种类型的数据(字符串、整数等),并且每一次输出是一个数据类型的方法。为每一个方法都命名一个新的名称是很麻烦的,这些方法所做的操作基本上都是相似的。可以命名一个相同的名称,但是为每一个方法传递一个不同的参数列表。因此,这个数据输出类可能会声明4个同名的方法,每一个都有一个不同的参数列表,如下所示:

通过传递给方法的参数的数量和类型来区分重载的方法。6.2.7 构造方法

构造方法通常用来完成对象的初始化。构造方法的声明看上去和方法声明类似,但是有自己的特点。 构造方法的名称必须与类名相同。 构造方法没有返回类型,包括关键字void也不能有。 任何类都含有构造方法。如果没有显式地定义类的构造方法,则

系统会为该类提供一个默认的构造方法。这个默认的构造方法不

含任何参数。一旦在类中定义了构造方法,系统就不会提供默认

的构造方法了。

下面定义了两个Test3类,一个不带参数,一个带两个参数,并分别对成员变量a和b进行初始化赋值。代码如下:6.2.8 方法返回值

方法实际上是一段单独执行的代码段。当方法执行完毕以后就会返回主程序。不管哪种情况先发生,方法都会返回。当以下情况发生时,方法返回到调用它的代码处:完成方法中所有语句;遇到return语句和抛出一个异常。

在方法体中使用return语句来返回一个值。任何声明void的方法都不返回任何值。return语句通常被用于终止一个控制流程块并退出方法。语法格式如下:

如果一个方法没有声明为void,那么必须包含一个return语句。例如:

返回值的数据类型必须与方法所声明的返回类型一致。例如:

当返回值类型与方法声明中的返回类型不一致时,就会导致编译错误。

6.3 抽象类和抽象方法

抽象类属于Java类的高级特性,是一种特殊的类。抽象类用来提供更高级的类型抽象,在面向对象的程序设计语言中,类是有层次和继承关系的。子类总是比其父类更加具体。程序开发过程中,很多时候需要构造一系列的类及其继承关系,这时,通常会将类层次中共有的特性抽取出来,创建包含这些共有特性的抽象类,并由抽象类派生出更加具体、有更多实现的子类或后代类,形成一个完整的类的层次体系。

抽象类可能含有抽象方法,也可能没有,但抽象类中可以含有非抽象的方法。抽象类不能被实例化,不能使用new运算符创建抽象类的实例对象,但是抽象类可以派生子类。6.3.1 抽象类

用关键字abstract声明抽象类,关键字abstract在class前面,语法格式如下:

如果一个类包含有抽象方法,那么这个类必须被声明为abstract,如下所示:

当从一个抽象类派生子类时,子类通常提供父类中所有抽象方法的实现。如果子类中没有实现其父类中所有的抽象方法,那么这个子类也必须被声明为abstract(抽象的)。6.3.2 抽象类实例

首先声明一个抽象类Animal,在这个类中,提供被所有的子类所全部共享的成员变量和方法,还声明一些抽象方法,如run()或eat(),这些方法需要被子类实现,但是实现的途径又各自不同。声明的抽象类Animal如下所示:

每一个Animal类的非抽象子类,如dog类和cat类,必须实现run()和eat()方法,代码如下:

虽然dog类和cat类都有自己的run ()方法和eat ()方法,但它们实现的方式是不同的。这样就在抽象类Animal中提供了统一的对外接口,而在子类中去具体实现。6.3.3 抽象类的类成员

一个抽象类也可以有静态字段和静态方法(static字段和static方法)。与其他类一样,可以直接使用抽象类名来引用这些静态成员,例如:

需要注意的是,修饰符static和abstract不能一起使用。如果一个方法声明为静态的,就不能声明为抽象的;如果声明为抽象的,就不能声明为静态的。6.3.4 抽象方法

如果一个方法被声明但是没有被实现(即没有花括号、方法体,声明后面直接就是分号),那么该方法被称为“抽象方法”。如下面的代码所示:

方法moveTo()即为抽象方法。抽象方法只有方法的声明,而没有方法的实现,用关键字abstract进行修饰。其语法格式如下:

除了关键字abstract,其他声明与普通方法的声明相同。实际上,一个接口中的所有方法,隐含的都是抽象的,因此接口的方法可以不使用abstract修饰符。当然也可以使用,只不过不是必需的。6.3.5 抽象类与接口对比

接口可以被任何类实现。抽象类经常用于被子类化,并共享部分实现。在一个单独的抽象类中,提供大部分子类的共同点(即抽象类已经实现的部分),但是还有一些不同点,抽象方法只声明不实现,留给不同的子类根据自己的要求去具体实现。

抽象类可以包含有非static和final的字段,并且抽象类可以包含有实现的方法。这样的抽象类与接口很相似,但抽象类提供部分方法的实现,其余的让子类来完成实现。如果一个抽象类只包含抽象方法的声明,那么应该将其声明为一个接口。

6.4 嵌套类

通常情况下,使用嵌套类的情形并不多,但在编写事件响应的代码时,使用嵌套类相对较多。使用嵌套类有多种原因,包括以下几种: 将只用在同一个地方的类进行逻辑上的分组的一种方法。如果一

个类只对另一个类有用,那么将其逻辑地嵌入另一个类并使两个

类紧密地结合在一起。嵌套这样的“辅助类”使得它们所在的包

更加简化和有效。 增强了封装性。例如,两个顶级类A和B,B需要访问A中被声明

为private的成员。通过将类B隐藏在类A中,A的成员可以被声明

为私有的,同时B也可以访问它们。另外,B本身对外部世界是

隐藏的。 嵌套类能使代码可读性和可维护性更强。在顶级类中嵌套较小的

类使得代码最接近它被使用的地方。6.4.1 嵌套类定义

在Java中允许在一个类(如ClassA)中定义另一个类(如ClassB),这样,ClassB称为“嵌套类”,ClassA称为“外部类”。例如:

嵌套类分为两种类别:静态的和非静态的。声明为static的嵌套类称为“静态嵌套类”。非静态嵌套类称为“内部类”。如下所示:

嵌套的类可以被声明为private、public、protected或包级私有的(package private);而外部类只能被声明为public或包级私有的。6.4.2 内部类

内部类中不能定义任何静态成员。内部类的实例对象存在于外部类的实例对象中。并可以直接访问包围它的实例的方法和字段,包括私有方法和私有字段。

要实例化一个内部类,必须首先实例化其外部类。然后使用下面的语法创建位于外部对象中的内部对象:

一个嵌套类可以访问它的封装类的所有私有成员,包括字段和方法。因此,被一个子类所继承的一个public或protected嵌套类可以间接访问父类的所有私有成员。6.4.3 静态嵌套类

与类方法和类变量一样,静态嵌套类与它的外部类相关联。并且像静态类方法一样,一个静态嵌套类不能直接引用其外部类的实例变量或实例方法,只能通过一个对象引用使用它们。语法格式如下:

要创建一个静态嵌套类的对象,可以使用下面所示的语法:

6.5 对象

Java程序会创建许多对象,对象间通过调用方法进行交互和通信。通过这些对象的交互作用,程序能够执行各种各样的任务,如实现一个GUI界面、运行一个动画、发送和接受网络上的信息。一旦一个对象已经完成了它应该完成的工作,它的资源就会被回收以供其他对象使用。6.5.1 对象实例

下面通过一个简单的程序,说明对象的使用。在下面这个程序中定义了3个类。其中,类Point和Rectangle分别代表点和矩形。在另外一个主程序CreateObjectDemo中,创建这两个类的实例对象并使用其属性和方法完成相应的操作。

在这个程序中创建3个对象,一个Point对象和两个Rectangle对象。代码如下:

运行结果如下:

在上述程序中,创建、操纵、显示了各种对象的信息。使用上面的这个例子来描述在一个程序中对象的生命周期。掌握如何在程序中编写创建和使用对象的代码,以及当一个对象的生命周期结束以后,系统是如何进行清理的。6.5.2 创建对象

类是对象的模板,可以从一个类创建一个对象。下面的语句来自于上面的程序,每一个语句都创建一个对象并将其赋予一个变量。例如:

第一行创建一个Point类的对象,第二行和第三行各自创建一个Rectangle类的对象。每个语句都由声明、实例化、初始化组成。声明:赋值符号(=)左边的代码都是变量声明。实例化:关键字new用来创建一个新的对象。初始化:new运算符后面跟一个构造方法的调用,由构造方法来初始化新的对象。

1.声明一个变量指向一个对象

前面内容中,学习过如何声明一个变量。声明变量的语法格式如下:

该声明通知编译器,程序中使用一个变量名“a”指向一个int型的数据。对于原始数据类型的变量,这样的声明还为该变量分配适当大小的内存。相类似的,声明一个引用类型的变量。例如:

简单地这样声明一个引用变量并不会生成一个对象。要真正地生成对象,需要使用new运算符。在使用originOne之前,必须给它赋一个对象,否则,就会出现编译错误。

2.实例化一个类

当使用new运算符来实例化一个类时,通过为新的对象分配内存返回一个引用来实现。new运算符还调用对象的构造方法。例如:

由new运算符所返回的引用不必一定要赋给变量,它也可以直接在一个表达式中使用。例如,下面的代码,直接使用new运算符创建一个Rectangle对象,并直接调用该对象的height属性。不过,这样创建的对象不可以重复使用,因为没有保存对它的引用。

3.初始化一个对象

上述Point类有两个属性:x和y。这两个属性分别代表点的水平横坐标和垂直纵坐标。Point类的定义如下:

这个类只包括一个单一的构造器。Point类的构造方法需要两个参数。下面的代码提供23和94作为构造器的实参:

在这行代码中,使用new生成一个新的Point类的对象,在内存中为其分配相应的空间,然后将其引用赋给Point类型的变量originOne保存,执行过程如图6.2所示。图6.2 对象originOne的内存状态

如果一个类有多个构造器,那么这些构造器必须有不同的签名。就像上面的类Rectangle,Java编译器基于参数的数量和类型来区分不同的构造器。例如,当Java编译器遇到下面的代码时,它就知道需要调用Rectangle类中Point参数和两个整型参数的构造方法。

其中的origin成员变量初始化为originOne。构造方法设置width字段值为100和height字段值为200。现在有两个引用指向同一Point对象,执行过程如图6.3所示。图6.3 两个引用指向同一个对象

类至少有一个构造器。如果一个类没有显式地声明一个构造方法,Java编译器会自动提供一个无参构造器,称为“默认构造器”。这个默认构造方法会调用它的父类的无参构造器。6.5.3 使用对象

在使用对象时有时需要它的某个字段值,有时需要改变它的某个字段值或调用它的方法来执行一个动作。

1.引用一个对象的字段

通过对象的字段名称来访问对象的字段。在一个类的内部,可以使用简洁的名称来引用字段。可以在上例的Rectangle类中添加一行输出width和height值的语句。例如:

width和height是简洁的名称。如果在对象类的外部引用对象中的字段,必须使用一个对象引用或表达式,后面跟一个圆点(.)运算符,再跟一个简洁字段名,例如:

如果调用Rectangle的对象rectOne中的origin、width和height字段,必须使用rectOne.origin、rectOne.width和rectOne.height来引用,代码如下:

2.调用一个对象的方法

还可以使用一个对象的引用来调用方法,语法格式如下:

通过一个对象名后面跟一个圆点运算符,再跟方法名称及参数列表(如果没有参数,也要保留空的圆括号)。Rectangle类有两个方法:getArea( )用来计算矩形的面积,move( )改变矩形的原点。调用这两个方法的代码如下:

6.6 this、static、final关键字

this、static、final、supper都是Java中和类相关的关键字,关键字this是对当前对象的引用。static用来修饰类中的变量或方法,称为静态变量或静态方法。final也可以修饰变量或方法,称为最终变量或最终方法。supper是继承的意思。6.6.1 this关键字

this关键字是对当前对象自身的引用。this关键字常用于在对象的一个字段被方法或构造方法的参数屏蔽时,需要调用这个被屏蔽的字段的情况。例如:6.6.2 static关键字

static用来修饰类中的变量或方法,称为静态变量或静态方法。静态变量对于所有的类对象共享同一个内存空间。当Java程序执行时,类的字节码文件加载到内存中,虽然没有创建对象,但静态变量此时被分配相应的内存。例如:6.6.3 final关键字

final是最终、最后的意思。可以修饰成员变量或成员方法,称为最终变量或最终方法。继承包含最终方法类的子类不能覆盖最终方法,即子类不能覆盖父类中的最终方法。

6.7 控制对类的成员的访问

访问级别修饰符用来决定其他类是否能访问一个特定的字段或调用一个特定的方法。有以下两种级别的访问控制。 最高级别:public或包级私有(package-private,没有指定修饰

符),用于修饰类。 成员级别:public、private、protected或包级私有(package-

private,没有指定修饰符),用于修饰类中的成员。

一个类可能会使用public修饰符声明,在这种情况下,类对在任何地方的其他类都是可见的。如果一个类没有修饰符(这时访问级别是默认的,即包级私有),那么这个类只能在它自己的包中可见。

在成员级别,也有public修饰符或无修饰符(包级私有),它们的含义与最高级别相同。对于成员,还有两个额外的访问修饰符private和protected。private修饰符说明成员只能在它所在的类的内部被访问。protected修饰符说明成员只能在它所在的包中被访问到(包级私有),另外,也可以被位于其他包中的该类的子类访问到。在成员级别,访问优先级为:public>protected>package-private>private,如表6.1所示。表6.1 访问级别

一个类总是可以访问它自己的成员。当访问其他类时,访问级别决定可以使用这些类的哪些方法。当编写了一个类时,可以决定类中的每一个成员变量和每一个成员方法的访问级别。

6.8 标注

标注(Annotations)是代码中的标记,它提供与程序有关的数据,但是标注本身不是程序的一部分。标注对其所注解的代码的操作没有直接的影响。有的书中也将标注称为注释。通过使用注释,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或进行部署。6.8.1 标注用法

标注在类加载、运行或编译时可以被解释,但是不对程序的运行产生直接的影响。注释有很多用法,包括: 为编译器提供信息。编译器可以使用标注来检测错误或禁止警告。 在编译和部署时处理。软件开发工具可以处理标注信息以生成代

码、XML文件等。 在运行时处理。有些标注在运行时可以被检查并使用。

从某些方面来看,注释就像修饰符一样被使用,并应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中。注释可以应用到程序的类的声明、字段的声明、方法的声明及其他程序元素的声明中。按惯例注释要出现在所在行的第一行,并且可以包括带有名称或未命名值的元素,如下所示:

或者

另外,标注也可以没有元素,可以省略圆括号,例如:6.8.2 文档标注

许多注释用来代替应该出现在代码中的注释。假设一个软件以传统的方式开始每个类,通常使用大量的注释来提供重要的信息,如下所示:

如果要使用标注来添加同样的元数据信息,必须首先定义“标注类型”。定义标注类型的语法如下所示:

如果要使“@AuthorInfo”中的信息出现在Javadoc生成的文档中,必须使用“@Documented”标注注释“@AuthorInfo”的定义,如下所示:

6.9 小结

本章主要学习了类和对象的相关概念,包括类的声明、类的实例(即对象),以及两种特殊类:抽象类和嵌套类。所讲的知识是Java面向对象语言中最基本的概念,所涉及的类和声明、对象的构造、访问控制修饰符都是读者学习的重点。通过本章的学习,读者应该对类和对象有深刻的理解,并能正确而熟练地使用类和对象。下一章将向读者介绍面向对象另一个非常重要的概念——继承。

6.1 0习题

1.简述什么是面向对象。

2.分别简述什么是类,什么是对象,以及它们之间的区别。

3.构造方法和成员方法有哪些不同?

4.编写一个用户类,包括成员变量、构造方法和成员方法。

5.public、private、protected有什么不同?

第7章 继承

继承是面向对象语言的三大特征之一。本章将学习怎样从一个类派生出另一个类,也就是说,一个子类如何继承父类的字段和方法。还将学习到所有的类都派生自Object类,以及如何修改从父类继承过来的子类的方法。

7.1 继承概述

Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。Java不支持多重继承,单继承使Java的继承关系很简单,一个类只能有一个父类,易于管理程序,同时一个类可以实现多个接口,从而克服单继承的缺点。在面向对象程序设计中,继承是不可或缺的一部分。通过继承,可以快速创建新的类,可以实现代码的重用,提高程序的可维护性,节省大量创建新类的时间,提高开发效率和开发质量。7.1.1 什么是继承

在Java语言中,一个类可以从其他的类派生出来,从而继承其他类的字段和方法。派生出来的类称为子类。用来派生子类的类称为父类或基类。

除了Object类没有父类,其他每个类都有一个并且只有一个直接的父类(这称为Java的“单继承”)。在没有任何其他显式父类的情况下,每个类的父类都是Object类。子类也可以再派生其他子类,依此类推。

继承的思想很简单,但功能却很强大:当程序员想要创建一个新的类时,如果已经存在一个含有他所想要的代码的类,那么他只需从已经存在的类派生出新的类,就可以重用已经存在的类的字段和方法。

需要注意的是,构造方法不会被子类继承,但是可以从子类中调用父类的构造方法。在面向对象程序设计中运用继承原则,就是在每个由一般类和特殊类形成的一般—特殊结构中,把一般类的对象实例和所有特殊类的对象实例都共同具有的属性和操作一次性地在一般类中进行显式的定义,在特殊类中不再重复定义一般类中已经定义的东西,但是在语义上,特殊类却自动地、隐含地拥有它的一般类(以及所有更上层的一般类)中定义的属性和操作。特殊类的对象拥有其一般类的全部或部分属性与方法,称为特殊类对一般类的继承。

继承所表达的就是一种对象类之间的相交关系,它使得某类对象可以继承另外一类对象的数据成员和成员方法。若类B继承类A,则属于类B的对象便具有类A的全部或部分性质(数据属性)和功能(操作),称被继承的类A为基类、父类或超类,而称继承类B为类A的派生类或子类。

继承避免了对一般类和特殊类之间共同特征进行的重复描述。同时,通过继承可以清晰地表达每一项共同特征所适应的概念范围——在一般类中定义的属性和操作适应于这个类本身及它以下的每一层特殊类的全部对象。运用继承原则使得系统模型比较简练、清晰。7.1.2 类的层次

在java.lang.package包中定义的Object类,定义和实现了对所有的类来说最通用的行为。在Java平台中,许多类都直接继承自Object,其他类再从这些类继承,依此类推,形成类的一个层次,如图7.1所示。图7.1 Java平台中所有的类都继承自Object

在类层次的顶点是Object类,Object类提供了对所有类的高度概括。在类层次底部的类则提供更加特定的行为。7.1.3 继承示例

People类是对人的一个基本概括,包含姓名和年龄字段及对这些字段属性进行读取的相应方法。代码如下:

这个People类是泛指,代表广义概念上的人类。现在想要创建一个代表更具体的男人类,最简单的方法是从People类派生出来,然后在子类中只需要增加新的特性(字段或方法)即可。例如:

男人类Man继承了People类的所有字段和方法,并增加了一个代表性别的字段sex和一个eat()方法。除了构造方法之外,就好像重新写一个全新的Man类,Man类带有3个字段和1个方法,包括从父类People继承过来的字段和方法。7.1.4 继承优点

不论子类位于哪个包中,它都继承其父类所有的public(公共的)和protected(受保护的)成员。如果子类和父类在同一个包中,它还继承包级私有(package-private)的成员(包级私有成员,指的是类中不带修饰符的成员和方法,默认对其访问权限仅限于同一个包中)。在子类中,既可以不加修改地使用继承过来的成员,也可以替换、隐藏它们,或者使用新的成员对其进行补充。继承的优点如下: 继承过来的字段可以像任何其他字段一样被直接使用。 在子类中可以声明一个与父类中同名的新的字段,从而“隐藏”

父类中的字段(不推荐使用)。 可以在子类中声明一个在父类中没有的新的字段。 从父类继承过来的方法可以直接使用。 可以在子类中编写一个与父类当中具有相同签名的新的实例方

法,这称为“方法重写”或“方法覆盖”。 可以在子类中编写一个与父类当中具有相同签名的新的静态方

法,从而“隐藏”父类中的方法。 可以在子类中声明一个父类当中没有的新的方法。 可以在子类中编写一个调用父类构造方法的子类构造方法,既可

以隐式地实现,也可以通过使用关键字super来实现。

7.2 对象类型转换

在Java中,一个对象的类型可以看做是其自身所属类的类型,也可以看做是其父类的类型。那么在其自身所属类型和其父类类型之间,就会发生对象类型转换。对象类型转换表示在允许继承和实现的对象中,使用一个类型的对象来替代另一个类型。7.2.1 隐式对象类型转换

例如,周杰杰(对象)是个歌星(类),也可以说周杰杰是个明星(歌星、笑星、影星是明星的子类),凡是明星能做的事情,周杰杰都能做。可以这样理解,使用父类的地方,都可以使用子类替代,因为子类继承了其父类所有的成员。换句话说,在代码中可以定义一个父类的引用变量,实际指向一个子类。例如:

这里,car既是一个Object(或AutoCar)类型,又是一个Car类型,这称为“隐式类型转换”。即将Car类型隐含地转换为其父类类型,因为子类继承了父类中所有的成员,凡能调用父类的地方,都可以使用子类。7.2.2 强制对象类型转换

但是反过来就不成立:一个AutoCar可能是一个Car,但并不一定是一个Car。例如,在上面的例子中,周杰杰是明星或歌星。但反过来,说到明星或歌星是周杰杰时,不成立。将7.2.1节的代码反过来写,代码如下。

那么,在编译时将会得到一个编译时错误,因为在第二行中,编译方法无法判断出car是一个Car类型。但是,可以通过“强制类型转换”来“告诉”编译方法,car引用变量引用的是一个Car类型的,代码如下:

在这种强制类型转换中,编译时编译方法会进行运行时检查,会发现car引用变量已经被赋予了一个Car类型的对象,因此,编译方法就会安全地判断出car是一个Car类型。如果在运行时car不是一个Car类型,那么就会抛出一个异常。7.2.3 使用instanceof运算符

使用强制类型转换时,如果转换的类型不合适,编译时会抛出异常。所以,最好的做法是在对某个对象进行强制类型转换之前,先对其类型进行判断。判断对象的类型使用instanceof运算符。

使用instanceof运算符对一个特定对象的类型进行逻辑判断,可以防止因为不合适的类型转换而产生的运行时错误。例如,将7.2.2节强制类型转换的代码改写如下:

在上述代码中,instanceof运算符检查car引用变量指向一个Car实例对象,所以可以放心地进行类型转换,而不必担心会抛出运行时异常。

7.3 重写和隐藏父类方法

子类继承了父类中的所有成员及方法。在某些情况下,子类中该方法所表示的行为与其父类中该方法所表示的行为不完全相同。例如,在父类动物中,定义了跑这个方法,而在子类中,跑的方法是不同的:狮子由狮子的跑方法实现,兔子由兔子的跑方法的实现,这时,需要在子类中重写或隐藏其父类中的该方法。7.3.1 重写父类中的方法

当一个子类中的一个实例方法具有与其父类中的一个实例方法相同的签名(指名称、参数个数和类型)和返回值时,称子类中的方法“重写”了父类的方法。例如:

运行结果如下:

在上面的示例程序中,子类B中定义了一个与其父类A中具有相同签名的方法sayHello()。当在子类B中调用sayHello()方法时,调用的是在子类中定义的该方法,这称为子类的sayHello()方法重写了父类中的sayHello()方法。而道别的方法sayBye()没有被重写,所以在子类B中调用的是从其父类A继承的该方法。重写的方法具有与其所重写的方法相同的名称、参数数量、类型和返回值。7.3.2 隐藏父类中的方法

如果一个子类定义了一个类方法(静态方法),而这个类方法与其父类中的一个类方法具有相同的签名(指名称、参数个数和类型)和返回类型,则称在子类中的这个类方法“隐藏”了父类中的该类方法。

当调用被重写的方法时,调用的版本是子类中的方法。当调用被隐藏的方法时,调用的版本取决于是从父类中调用的还是从子类中调用的。例如:

编写Animal类的子类,名为Cat类。

再编写一个用来测试的程序Test2。

在上面的示例中,类Cat重写了类Animal中的实例方法并隐藏了Animal中的类方法。在main方法中,创建一个Cat类的实例对象并调用Animal和myAnimal、Cat的类方法testClassMethod()和myAnimal的实例方法testInstanceMethod()。运行结果如下:

可以看出,得到调用的隐藏方法的版本是父类中的方法,而得到调用的重写方法的版本是子类中的方法。7.3.3 方法重写和方法隐藏后的修饰符

在子类中被重写的方法,其访问权限允许大于但不允许小于被其重写的方法。也就是说,子类中重写方法的访问控制修饰符的作用域要大于父类中被重写的方法的访问控制修饰符的作用域。例如,父类中一个受保护的实例方法(protected)在子类中可以是公共的(public)的,但不可以是私有的(private)。

不允许将父类中的一个实例方法在子类中改变为类方法,换句话说,如果一个方法在父类中是static方法,那么在子类中也必须是static方法;如果一个方法在父类中是实例方法,那么在子类中也必须是实例方法。7.3.4 总结

表7.1列出了当在子类中定义一个与父类中的方法具有相同签名的方法时,会发生的情况。表7.1 重写或隐藏

在一个子类中,可以重载从父类继承过来的方法。重载的方法既不隐藏也不重写父类的方法,它们是新的方法,对于子类来说是唯一的。方法重载示例如下:

运行结果如下:

在这个程序中,父类A和子类B中有两个同名的方法sayHello(),但子类中的sayHello()方法带有一个字符串类型的参数,而父类中的sayHello()方法不带参数,所以子类中的sayHello()方法重载了从父类中继承过来的sayHello()方法。这样,在子类中就有两个可用的sayHello()方法,一个不带参数,一个带有一个String类型的参数。当通过B的对象实例b调用其sayHello()方法时,根据是否带有参数来决定调用哪一个。当调用不带参数的sayHello()方法时,会调用从父类A继承过来的sayHello()方法;当调用带参数的sayHello()方法时,会调用在子类B中重载的sayHello()方法。

7.4 隐藏父类中的字段

一个子类中与其父类有同名的字段,即使这些字段的数据类型不同,也可以称为子类中的字段隐藏了父类的字段。在子类中,不能通过简单的名称来引用父类中的这个字段,必须通过关键字super访问它。例如,在类A中定义了一个String类型的变量name,在其子类B中也定义了一个同名的变量,数据类型也为String,代码如下:

运行结果如下:

子类中的name字段隐藏了父类中的name字段。不推荐使用隐藏字段,因为这有可能使代码难以阅读。

7.5 子类访问父类成员

7.5.1 子类访问父类私有成员

子类继承其父类的所有public(公共的)和protected(受保护的)成员,但不继承其父类中的private(私有的)成员。例如,下面的示例中,类A含有一个公共变量value,在其子类B中就可以直接访问这个字段。代码如下:

运行结果如下:

如果将类A的字段value前的访问控制修饰符改为private,再一次编译此程序,会出现编译错误信息,因为子类不继承其父类中的私有成员,所以在子类中不能直接访问其父类中的私有成员。如果想在子类中访问到父类中的私有字段,那么需要在父类中提供用来访问其私有字段的public或protected方法,然后子类就可以使用这些方法来访问相应的字段。将类A的字段value改为私有的,然后提供一个访问该字段的公共方法getValue。其代码如下:

运行结果如下:

在上述程序中,子类B继承了其父类的public方法getValue(),因此可以通过调用getValue()方法间接地访问其父类的私有字段。而对类A来说,通过将其字段封装为私有的,并提供公共访问接口,很好地保护了其私有数据。7.5.2 使用super调用父类中重写的方法

子类的一个方法重写了父类中的一个方法,要想在子类中可以访问到父类中被重写的方法,可以在子类中通过使用关键字super来调用父类中被重写的方法。使用super关键字调用父类中被重写的方法。代码如下:

编写一个包含main方法的测试程序Test6,代码如下:

在子类a中,say()方法重写了A中的say()方法。因此,要调用A中的say()方法,必须使用super关键字。运行结果如下:7.5.3 使用super访问父类中被隐藏的字段

在7.4节中我们隐藏了父类中的字段,当子类中重写了父类中的字段后,子类就无法调用父类的字段。如果需要调用父类中被重写的字段,可以使用super关键字,语法格式如下:

下面修改7.4节中的示例程序,使用super关键字访问父类中被隐藏的字段。代码如下:

编译并运行上述程序,输出结果如下:7.5.4 使用super调用父类的无参构造方法

子类不继承其父类的构造方法。因此,如果要初始化父类中的字段,可以在子类的构造方法中通过关键字super调用父类的构造方法。对父类的构造方法的调用必须放在子类构造方法的第一行。调用一个父类构造方法需要使用super关键字,当使用super()时,父类的无参构造方法会被调用。语法格式如下:

调用父类的无参构造方法示例,代码如下:

运行结果如下:

实际上,如果一个构造方法没有显式地调用一个父类的构造方法,那么Java编译方法会自动插入一个对父类无参构造方法的调用。例如,将上面示例中子类B中的“super();”语句去掉,如下所示:

输出结果不变。虽然在子类B的构造方法中没有显式地调用父类的构造方法,但是Java编译方法自动地插入了对父类A的无参构造方法的调用。例如:

如果父类没有一个无参构造方法,就会产生编译时错误。由于父类A中没有无参构造方法(当一个类中显式定义一个构造方法时,Java就不会为其创建默认的无参构造方法了),在B类的构造方法中,首先调用父类A的无参构造方法,而父类A并没有无参的构造方法,这样就会产生一个编译时错误。7.5.5 使用super调用父类的带参构造方法

如果要完成对父类成员的一些初始化工作,那么使用无参的super()方法就不可以了。这时,需要使用带有参数的super()方法,其语法形式如下所示:

调用父类的无参构造方法示例。代码如下:

在创建子类Tom时,构造方法中带有两个参数。在构造方法中,首先调用了其父类Tom的构造方法,然后添加它自己的初始化代码,代码如下:

使用“super(参数列表)”,父类的带有相匹配的参数列表的构造方法会被调用。因为构造方法不能被继承,那么对从父类中继承过来的字段的初始化,必须通过调用父类的带参构造方法来完成,如上面的程序所示。因此,在子类的带参构造方法中,参数列表中包括父类构造方法要用到的参数。7.5.6 构造方法链

如果一个子类的构造方法显式或隐式地调用其父类的一个构造方法,而其父类会再显式或隐式地调用父类的父类的构造方法,那么将会存在一个完整的构造方法的连环调用,一直调用到Object的构造方法,称为“构造方法链”。当存在一个很长的类的继承链时,在使用时要考虑到这一点。

构造方法链示例,代码如下:

在这个示例中,类B继承自类A,而类C又继承自类B。当在main()方法中使用类C的构造方法构造一个C类的对象实例时,会先调用其父类B的构造方法,然后才执行其自身构造方法中的其他语句。同样的道理,在调用B的构造方法时,也会先调用其父类A的构造方法,依此类推,在类A的构造方法中也是先调用其父类Object的构造方法。

所以,在执行这个程序时,最先执行的输出语句是类A的构造方法中的输出语句,即最先输出“这里是父类A的构造方法。”;其次是类B的构造方法中的输出语句得到执行:“这里是子类B的构造方法。”;最后输出的语句是类C中的“这里是子类C的构造方法。”。编译并运行上述程序,输出结果如下:

该示例的构造方法链如图7.2所示。图7.2 Java中的构造方法链

7.6 Object类

在很多地方都提到了Object类。Object类是很特别的一个类,位于java.lang包中,处于Java类层级树的顶端。Java中的所有类都是Object类的直接或间接子孙类。Object类为Java中的所有类提供了最基本的属性和方法,是Java中所有类的高度概括。

程序中所使用的或编写的每一个类都继承了Object的实例方法。通常不会使用这些方法,但是如果要使用的话,就需要使用特定的代码来重写它们。从Object继承过来的方法如下所示:

另外,Object还有notify、notifyAll和wait等方法,主要用在同步程序的独立的线程活动中,这些内容将在后面的相关章节中讲解。类似这样的方法共有5个,如下所示:

7.7 小结

本章是学习Java重要的一章,主要学习了继承的概念。重点掌握继承中对父类方法的重写、隐藏及子类如何访问父类的字段和方法,理解构造方法链和Object类。通过本章的学习,读者应该对类的继承有深刻的理解,并能在编程时正确使用继承。下一章将向读者介绍Java语言的另一个重要概念——接口。

7.8 习题

1.继承有哪些优点?

2.如何重写父类的方法?

3.自定义父类Animal定义eat()方法和move()方法,然后写一个dog子类继承并重写父类的两个方法。

第8章 接口和包

接口是面向对象编程中的一个重要概念,在开发程序中,有些不相关的类但是有相同的行为(方法),接口就是来定义这种行为的。接口只提供方法,不定义方法的具体实现。一个类只能继承一个父类,但是接口却可以继承多个接口。

8.1 接口的概念

我们可以把现实生活中的接线板作为接口,不管是电脑、电视机、微波炉还是电冰箱,只要插上电源,就能打开使用。虽然对象不一样,但是这些对象具有相似的行为。我们就可以认为接线板就是这些对象的接口。

在Java语言规范中,一个方法的特征仅包括方法的名称、参数的数目和种类,而不包括方法的返回类型、参数的名称及所抛出来的异常。在Java编译器检查方法的重载时,会根据这些条件判断两个方法是否为重载方法。但在Java编译器检查方法的置换时,则会进一步检查两个方法(分处超类型和子类型)的返回类型和抛出的异常是否相同。8.1.1 为什么使用接口

如果有两个类,分别有相似的方法,其中一个类调用其中另一个类的方法,动态地实现这个方法,那么它们就提供一个抽象父类,一个子类。子类实现父类所定义的方法。

Java是一种单继承的语言,一般情况下,类可能已经有了一个超类,我们要做的是给它的父类加父类,或者给它父类的父类加父类,直到移动到类等级结构的最顶端。这样,对一个具体类的可插入性的设计,就变成了对整个等级结构中所有类的修改。接口的出现解决了这个问题。

在一个等级结构中的任何一个类都可以实现一个接口,这个接口会影响到此类的所有子类,但不会影响到此类的任何超类。此类将不得不实现这个接口所规定的方法,而其子类可以从此类自动继承这些方法,当然也可以选择置换掉所有的这些方法或其中的某一些方法,这时,这些子类具有了可插入性(并且可以用这个接口类型装载,传递实现了它的所有子类)。

接口提供了关联及方法调用上的可插入性,软件系统的规模越大,生命周期越长。接口使得软件系统的灵活性和可扩展性、可插入性方面得到保证。

使用Java接口将软件单位与内部和外部耦合起来。使用Java接口不是具体的类进行变量的类型声明、方法的返回类型声明、参量的类型声明及数据类型的转换。

在理想的情况下,一个具体的Java类应当只实现Java接口和抽象Java类中声明的方法,而不应当给出多余方法。8.1.2 Java中的接口

在Java程序设计语言中,接口是一个引用类型,与类相似,所以可以在程序中定义并使用一个接口类型的变量。在接口中只能有常量、方法签名。

接口没有构造方法,不能被实例化,只能被类实现或被另外的接口继承,所以在接口中声明方法时,不用编写方法体。接口中的方法签名后面没有花括号,以分号结尾。

接口继承和实现继承的规则不同,一个类只有一个直接父类,但可以实现多个接口。

Java接口本身没有任何实现,因为Java接口不涉及表象,而只描述public行为,所以Java接口比Java抽象类更抽象化。

Java接口的方法只能是抽象的和公开的,Java接口不能有构造方法,Java接口可以有public、静态的和final属性。

接口把方法的特征和方法的实现分割开来。这种分割体现在接口常常代表一个角色,它包装与该角色相关的操作和属性,而实现这个接口的类便是扮演这个角色的演员。一个角色由不同的演员来演,而不同的演员之间除了扮演一个共同的角色之外,并不要求其他的共同之处。8.1.3 作为API的接口

作为API使用的接口还普遍使用在商业软件生产中。典型的软件公司会开发并销售包含复杂方法的一个软件包,而另外一家公司在他们自己的软件产品中使用这些方法。

例如,A公司开发一个含有数字图像处理方法的软件包,然后将此软件包销售给制作终端用户图片程序的B公司。A公司编写它们自己的类以实现一个接口,而将接口公开给它的客户B。然后B公司根据接口中定义的方法签名和返回类型,调用相应的图像处理方法。当A公司的API对它的客户B公司公开时,它对API的实现实际上是隐藏的。事实上,A公司可能会在以后的应用中修改API的实现,升级其软件包。但是,只要新的软件包继续实现原始接口,就不会影响到它的客户B公司对API的使用。8.1.4 接口和多继承

接口有另外一个非常重要的作用,因为Java中不允许多继承,一个类只能继承自一个父类,因此有时这种单继承并不能反映现实世界的某些现象。例如,未来的某个时候,出现了一种超级汽车,可以在水、陆、空3种环境下行驶。但是它只能从最初的Cars类继承,只继承了陆地行驶的方法,这时只通过单继承的方式不能满足创建此超级汽车的目的。因此它可以实现多个接口。这时,要想创建能水、陆、空都能行驶的汽车,除了从Cars类继承陆地行驶的方法之外,还可以实现带有水中行驶和空中行驶方法的接口,那么它就具有了水中行驶和空中行驶的方法。当实现多个接口时,对象可以同时具有多个类型: 自身所属类的类型。 其所实现的所有接口的类型。

这意味着,如果声明一个接口类型的变量(接口是引用类型),那么它的值可以引用任何实现了该接口的类的任何实例对象。8.1.5 Java接口与Java抽象类的区别

Java接口和Java抽象类有太多相似的地方,又有太多特别的地方,究竟在什么地方才是它们的最佳位置呢?比较一下,就可以发现。 Java接口和Java抽象类最大的一个区别,就在于Java抽象类可以

提供某些方法的部分实现,而Java接口不可以,这是Java抽象类

唯一的优点,但这个优点非常有用。当向一个抽象类中加入一个

新的具体方法时,它所有的子类都立刻得到了这个新方法,而

Java接口做不到这一点。如果向一个Java接口中加入一个新方法,

所有实现这个接口的类就无法成功通过编译了,因为必须让每一

个类都再实现这个方法才行,这显然是Java接口的缺点。 一个抽象类的实现只能由这个抽象类的子类给出,也就是说,这

个实现处在抽象类所定义出的继承的等级结构中,而由于Java语

言的单继承性,所以抽象类作为类型定义工具的效能大打折扣。

这时,Java接口的优势就显现出来了,任何一个实现了一个Java

接口所规定的方法的类都可以具有这个接口的类型,而一个类可

以实现任意多个Java接口,从而这个类就有了多种类型。 Java接口是定义混合类型的理想工具,混合类表明一个类不仅具

有某个主类型的行为,而且还具有其他的次要行为。

8.2 定义接口

有时,程序员需要自己定义要使用的接口或用来分发的接口。定义接口与定义类很相似,包括接口的声明和接口体的实现。在接口体中,含有对接口所包含的所有的方法的方法声明。接口所含有的方法声明后面紧跟一个分号,而不是花括号,因为一个接口不提供对它里面所声明的方法的实现。在一个接口中声明的所有方法都隐含是public的,所以public修饰符可以被省略。8.2.1 声明接口

接口属于引用类型,定义接口需要使用关键字interface。接口的名称要遵循Java标识符命名规则。语法格式如下:

各选项说明如下。 修饰符:可选,用于指定接口的访问权限,可选值为public。如

果省略,则使用默认的访问权限,即只能在当前的软件包中使用。

换句话说,声明接口时,关键字interface前面要么是修饰符

public,要么什么都没有,而不能使用protected或private关键

字。 interface:必选,定义接口的关键字。 接口名称:必选,用于指定接口的名称。接口名称必须是合法的

Java标识符。一般情况下,要求接口名称的首字母为大写。 extends父接口名称列表:可选,用于指定该接口继承自哪个父

接口。当使用extends关键字时,父接口名称为必选参数。接口

可以是多继承的,即一个接口可以有任意多个父接口。在接口的

声明中,包括其所有父接口的一个列表,用逗号分隔。例如,下

面声明一个接口AInterface,该接口继承3个父接口:

在上面的声明中,public访问控制符指出该接口可以被任何包中的任何类所使用。如果没有指定接口是public的,那么接口将只能被与其定义在同一个包中的类所访问。8.2.2 接口体

接口体就是接口声明后面的花括号括起来的部分。由两部分组成:常量声明和方法声明。其语法格式如下所示:

各选项说明如下。 常量声明:接口中可以包含常量声明,也可以不包含,应根据需

求而定。如果有常量声明的话,默认是public、static、final类型

的,接口中的所有字段都隐含地具有public、static和final属性。

所以可以省略常量声明的修饰符public、static和final。 方法声明:接口中的方法只有返回类型和方法名,没有方法体。

接口中的方法都具有public和abstract属性,所以在声明方法时,

可以省略前面的修饰符public和abstract。也就是说,即使声明方

法时前面不使用修饰符,该方法也隐含地是public和abstract的。

例如:

8.3 实现接口

接口的主要作用是声明共同的常量或方法,用来为不同的类提供不同的实现,但这些类仍然可以保持同样的对外接口。接口可以被类实现,也可以被其他接口继承。8.3.1 接口的实现

声明一个实现接口的类,需要在类的声明中使用implements短语。一个类可以实现多个接口,所以implements关键字后面要跟一个被类实现的接口列表,用逗号分开。如果有extends短语的话,implements短语跟在extends短语后面,语法格式如下:8.3.2 接口示例

为了让大家更容易理解,下面举例说明。定义两个接口:InterfaceA和InterfaceB,并在类InterfaceTest实现两个接口。代码如下:

运行结果如下:

如果接口的返回类型不是void,在类实现该接口时,方法体中至少有一个return语句。如果在接口前加public关键字,则该接口可以被任何一个类使用。8.3.3 接口的继承

接口也是可以被接口继承的,用关键字extends,和类的继承相似。语法格式如下:

注意,当类实现了一个接口,而该接口继承了另一个接口时,则这个类必须实现这两个接口的所有方法。8.3.4 实现多个接口时的常量和方法冲突问题

每个类只能实现单重继承,而实现接口时,则可以实现多个接口。这时就有可能出现常量或方法名冲突的情况。例如,一个类实现两个接口,两个接口中都声明了相同名称的常量或相同名称的方法,那么在实现这两个接口的类中,引用常量或实现方法时,就不明确是哪个接口中的。在解决这类冲突问题时,如果是常量冲突,则需要在类中使用全限定名(接口名称.常量名称)明确指定常量所属的接口;如果是方法冲突,则只需要实现一个方法就可以了。

下面的示例程序中定义了两个接口,并且都声明了一个同名的常量和一个同名的方法。然后再定义一个同时实现这两个接口的类。(1)创建A接口,在该接口中声明一个常量和两个方法,代码如下:(2)创建B接口,在该接口中声明一个常量和两个方法,代码如下:(3)创建一个C类,该类同时实现A接口和B接口,代码如下:(4)创建一个含有main()方法,代码如下:

在这个程序中,接口A和接口B都声明有getArea()方法和变量PI,而类C实现了这两个接口,所以为了避免变量冲突,在引用PI时,使用了全限定名A.PI;为了避免方法冲突,只实现一个getArea()方法。

8.4 包

“包”指的是一组提供访问保护和命名空间管理的相关的类型。如何将类和接口封装到一个包中、如何使用包中的类是学习编程的一个重要内容。8.4.1 包的概念

为了更好地组织类,Java提供了包机制。包是类的容器,用于分隔类名空间。如果没有指定包名,所有的示例都属于一个默认的无名包。Java中的包一般均包含相关的类,例如,所有关于交通工具的类都可以放到名为Transportation的包中。

程序员可以使用package指明源文件中的类属于哪个具体的包。包语句的格式如下:

程序中如果有package语句,该语句一定是源文件中的第一条可执行语句,它的前面只能有注释或空行。另外,一个文件中最多只能有一条package语句。

包的名称有层次关系,各层之间以点分隔。包层次必须与Java开发系统的文件系统结构相同。通常包名全部用小写字母,这与类名以大写字母开头,且各字的首字母也大写的命名规则有所不同。

当使用包说明时,程序中无须再引用(import)同一个包或该包的任何元素。import语句只用来将其他包中的类引入当前名字空间中。而当前包总是处于当前名字空间中。

为了使得各种类型易于查找和使用,避免命名冲突,并控制访问,程序员将一组相关的类型封装到包(package)中。当程序中的文件越来越多时,一定不要将它们全部放置在同一目录下,而应按其不同的用途放置在不同的包中。使用包的好处如下: 程序员能很容易地判断出这些类型是相关的。 程序员自己创建的类型的名称将不会和其他包中的类型的名称相

冲突,因为包创建了一个新的命名空间。 可以允许在此包中的类型彼此自由地相互访问,同时仍能限制对

包外的类型的访问。8.4.2 创建包

要创建一个包,需要为包选择一个名称,并将一个package语句和包的名称放在每一个想要放到包中的源文件的顶部。所有的包名都必须小写。使用含有关键字package的语句来创建包。例如:

如果在一个源文件中有多个类型,那么只有一个允许是public的,并且它必须与源文件同名。8.4.3 包命名惯例

包名使用小写字母,以避免与类名或接口名冲突。对公司来说,使用其颠倒的Internet域名来命名其包名。例如,命名为com.company.region.package。不过在有的情况下,互联网的域名可能不是一个有效的包名。例如,域名包含一个连字符或其他特殊字符,包名以数字开头或以其他Java名称开头都是非法的,在这种情况下,建议的规则是添加下画线。8.4.4 导入包

由于不同的包之间的类不可以直接相互使用,因此就必须要通过导入包的方式来解决。使用带有通配符“*”的import语句导入一个特定包中包含的所有类型,例如:

通配符“*”代表该包中所有的类型,但不包括包含的子包。导入以后,就可以通过简单名引用包中的任何类或接口。“*”只被用于指定包中的所有类。它不能被用于匹配一个包中的类的子集。如果想引用包中某一个类,就可以使用以下导入语句:

8.5 小结

本章主要介绍了接口和包的概念。在学习本章内容时,需重点掌握接口的概念,理解如何声明接口和使用接口的方法。读者可能会感到难以理解,多多练习会慢慢体会到什么是接口。包的概念了解即可。下一章将向读者介绍Java语言的集合。

8.6 习题

1.简述接口的概念。

2.接口和抽象类有哪些区别?

3.自定义一个接口,在接口中定义几个属性和方法。

4.包有什么作用?

第9章 集合

Java集合是多个对象的容方法,容方法中放了很多对象。集合框架是Java语言的重要组成部分,包含系统而完整的集合层次体系,封装了大量的数据结构的实现。深刻理解Java集合框架的组成结构及其中的实现类和算法,会极大地提高程序员编码的能力。

9.1 Java集合框架

集合有时又称容方法,简单地说,它是一个对象,能将具有相同性质的多个元素汇聚成一个整体。集合被用于存储、获取、操纵和传输聚合的数据。集合代表形成一个自然组合的数据条目,如一副纸牌(一个纸牌卡片的集合)、一个邮包(一个信函的集合)或一个电话本(一个姓名和电话号码的映射集合)。

集合框架(Collections Framework)是用来表现和操纵集合的一个统一的体系结构。

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载