Android系统下java编程详解


内 容 简 介 本书共 12 章,内容主要包含两大部分。第一部分是 J ava 语法相关内容,主要介绍 J ava 语言语法、JSP 和 MySQL 数据库等知识。第二部分关注 Android 系统下的 Java 编程特点,讲解 Android 系统下 J ava 编程 的优化原则和方法,并给出了具体建议。 本书可作为大学院校计算机专业、嵌入式技术专业、电子信息类相关专业的教材,也可供高等及中等 职业技术院校使用。 未经许可,不得以任何方式复制或抄袭本书之部分或全部内容。 版权所有,侵权必究。 图书在版编目(CIP)数据 Android 系统下 Java 编程详解 / 郑萌等编著.—北京:电子工业出版社,2012.10 高等院校 3G 人才培养规划教材 ISBN 978-7-121-18493-2 Ⅰ. ①A… Ⅱ. ①郑… Ⅲ. ①Java 语言-程序设计-高等学校-教材 Ⅳ. ①TP312 中国版本图书馆 CIP 数据核字(2012)第 215387 号 策划编辑:胡辛征 责任编辑:许 艳 特约编辑:赵树刚 印 刷: 装 订: 出版发行:电子工业出版社 北京市海淀区万寿路 173 信箱 邮编:100036 开 本:787×1092 1/16 印张:18.75 字数:480 千字 印 次:2012 年 10 月第 1 次印刷 印 数:4000 册 定价:45.00 元 凡所购买电子工业出版社图书有缺损问题,请向购买书店调换。若书店售缺,请与本社发行部联系,联 系及邮购电话:(010)88254888。 质量投诉请发邮件至 zlts@phei.com.cn,盗版侵权举报请发邮件至 dbqq@phei.com.cn。 服务热线:(010)88258888。 北京中新伟业印刷有限公司  III  前 言 Android 一词英文本义指“机器人”,是由 Google 公司于 2007 年 11 月正式对外发布的, 一种以 Linux 为基础的开放源代码操作系统,主要用于便携设备。依靠 Google 的强大开发和 媒体资源,凭借其开放性和优异性,Android 平台在发展的过程中得到了包括大手机厂商和 著名移动运营商在内的业界的广泛支持,除手机之外,目前其应用已逐渐扩展到平板电脑及 其他领域。2011 年第一季度,Android 在全球的市场份额首次超过塞班系统,跃居全球第一。 2012 年 2 月,Android 占据全球智能手机操作系统市场 52.5%的份额,中国市场占有率为 68.4%。与此同时,随着行业的迅猛发展,Android 研发工程师更是日益成为 IT 职场的紧缺 人才。近几年来,各大学院校已经纷纷开设 Android 移动开发专业或方向。但是,各院校在 Android 专业教学建设的过程中几乎都面临教材难觅、内容更新迟缓的困境。虽然目前市场 上的 Android 开发相关书籍比较多,但几乎都是针对有一定基础的行业内研发人员而编写的, 并不完全符合高校的教学要求。高校教学需要一套充分考虑学生现有知识基础和接受度的、 明确各门课程教学目标的、便于学校安排课时的 Android 专业系列教材。 针对高校专业教材缺乏的现状,我们以多年来在嵌入式工程技术领域及移动开发行业内 人才培养、项目研发的经验为基础,汇总了近几年积累的数百家企业对 Android 研发相关岗 位的真实需求,并结合行业应用技术的最新状况及未来发展趋势,调研了开设 Android 专业 的院校的课程设置情况、学生特点和教学用书现状。通过细致的整理和分析,对专业技能和 基本知识进行合理划分,我们编写了这套“高等院校 3G 人才培养规划教材”,包括以下 4 种:  《Android 系统下 Java 编程详解》  《Android 应用程序开发与典型案例》  《Android 游戏案例开发与关键技术》  《Android 系统移植与驱动开发技术》 本套教材按照专业整体教学要求组织编写,各自对应的主干课程之间既相对独立又有机 衔接,整套教材具有系统性。考虑到 Android 研发对学生 Java 语言能力要求较高,我们专门 有针对性地编写了《Android 系统下 Java 编程详解》这本教材,可供“Java 语言基础”课程 的后续提高课程使用;《Android 应用程序开发与典型案例》则结合 Android 应用开发的核心 知识,重点突出了贯穿前面所学知识的实训案例及内容,可供“Android 应用程序开发”课 程使用;在 Android 游戏开发方面,根据各院校的教学重点和行业实际应用情况,编写了 《Android 游戏案例开发与关键技术》;《Android 系统移植与驱动开发技术》侧重介绍 Android 底层移植和驱动技术。 Java 作为 Android 应用编程所使用的语言,也是现阶段世界上应用最广泛的语言之一, 本书除了介绍 Java 语言的基本语法之外,重点讨论了在 Android 系统下 Java 编程的优化。考 虑到使用 Android 平台的设备一般为移动设备,其运算能力、存储空间、电池容量都比较有  IV  限,所以对于 Android 应用程序来说,为保证其顺畅地运行,其程序的执行必须是高效节能 的。而这其中,电池续航能力更是迫使程序员必须优化程序的关键,因为 Android 设备一般 耗电量都比较大,即使编写的应用程序运行已经很快,但是耗电量巨大的话,用户迟早会发 现这一点而抛弃我们编写的应用程序。本书则有针对性地讲解如何实现 Android 应用程序的 性能优化。通过大量实例,帮助学生达到学用结合的目的。 本书共 12 章,内容主要包含两大部分。第一部分是 Java 语法相关内容,主要介绍 Java 语言的基本语法以及 JSP 和 MySQL 数据库的应用开发。第二部分关注 Android 系统下 Java 编程特点,主要讲解 Android 系统下 Java 编程的优化原则和方法,并结合实际的研发需求给 出了具体建议。 本书由华清远见嵌入式学院资深讲师郑萌编著并统校全稿。本书的完成需要感谢华清远 见嵌入式学院及华清远见 3G 学院,教材内容参考了学院与嵌入式及移动开发企业需求无缝 对接的、科学的专业人才培养体系。参与本书编写的人员有赵常松、谢培良、崔浩、周志强、 李宗亮、李珊珊、吴现凯、高良伟、王泽政、蒋铎、袁升、付世金、赵健乔、赵晶晶,在此 表示衷心的感谢。 由于作者水平所限,书中不妥之处在所难免,恳请读者批评指正。对于本书的批评和建 议,可以发到 www.farsight.com.cn 网站的技术论坛中。 编著者 2012 年 9 月  V  目 录 第 1 章 Android基本概念 .................................................................................................1 1.1 Android简介 ...........................................................................................................1 1.2 Android平台特性 ....................................................................................................2 1.3 Android系统架构 ....................................................................................................3 1.3.1 Linux内核(Linu x Kernel)............................................................................................................. 4 1.3.2 Android程序库(Libraries) ............................................................................................................ 4 1.3.3 Android运行时(Android Runtime) .............................................................................................. 4 1.3.4 Android应用程序框架(Application Framework) ........................................................................ 5 1.3.5 Android应用程序和小部件 .............................................................................................................. 5 1.4 Android开发框架 ....................................................................................................5 1.4.1 应用方面............................................................................................................................................ 6 1.4.2 数据存储............................................................................................................................................ 7 1.4.3 网络访问方面.................................................................................................................................... 8 1.5 Android开发环境搭建 .............................................................................................8 1.5.1 安装JDK和配置Java开发环境 ......................................................................................................... 8 1.5.2 Eclipse的安装 .................................................................................................................................... 9 1.5.3 SDK和ADT的安装和配置 ............................................................................................................... 9 第 2 章 面向对象程序设计初步 ....................................................................................... 1.6 本章小结 .............................................................................................................. 11 13 2.1 面向对象概念 ....................................................................................................... 13 2.1.1 从结构化程序设计到面向对象程序设计...................................................................................... 13 2.1.2 面向对象特征.................................................................................................................................. 14 2.2 面向对象程序设计 ................................................................................................ 15 2.2.1 知识准备:面向对象编程术语...................................................................................................... 15 2.2.2 知识准备:对象.............................................................................................................................. 15 2.2.3 知识准备:类.................................................................................................................................. 16 2.2.4 知识准备:类的声明...................................................................................................................... 16 2.2.5 知识准备:属性的声明.................................................................................................................. 18 2.2.6 知识准备:方法的声明.................................................................................................................. 18 2.2.7 知识准备:构造器(构造方法).................................................................................................. 19 2.2.8 知识准备:对象的创建和使用...................................................................................................... 21  VI  2.2.9 任务一:创建并引用一个对象...................................................................................................... 21 2.2.10 技能拓展任务:带参数构造器的声明与使用............................................................................ 22 2.3 信息的封装和隐藏 ................................................................................................ 23 2.3.1 知识准备:信息的封装.................................................................................................................. 23 2.3.2 知识准备:信息的隐藏.................................................................................................................. 24 2.4 Java源文件结构 .................................................................................................... 24 2.4.1 知识准备:package语句................................................................................................................. 25 2.4.2 知识准备:import语句 ................................................................................................................... 26 2.4.3 任务二:package语句和import语句实例 ...................................................................................... 27 2.5 JDK中常用的包 .................................................................................................... 28 第 3 章 标识符、关键字与数据类型 ................................................................................ 2.6 本章小结 .............................................................................................................. 29 31 3.1 Java注释 ............................................................................................................... 31 3.1.1 知识准备:Java注释使用规则....................................................................................................... 31 3.1.2 知识准备:利用javadoc来产生API文档....................................................................................... 32 3.1.3 任务一:使用javadoc注释,生成API文档................................................................................... 35 3.2 分隔符和标识符 ................................................................................................... 37 3.2.1 知识准备:空白分隔符.................................................................................................................. 37 3.2.2 知识准备:普通分隔符.................................................................................................................. 38 3.2.3 知识准备:Java语言标识符的组成规则....................................................................................... 38 3.2.4 任务二:综合使用Java分隔符和标识符....................................................................................... 39 3.3 Java关键字/保留字 ................................................................................................ 39 3.3.1 知识准备:Java关键字使用规范................................................................................................... 39 3.3.2 知识准备:重点关键字解析.......................................................................................................... 40 3.4 数据类型 .............................................................................................................. 41 3.4.1 知识准备:简单类型...................................................................................................................... 41 3.4.2 知识准备:非boolean简单数据类型之间的转换 ......................................................................... 44 3.4.3 任务三:简单数据类型转换实例.................................................................................................. 45 3.4.4 知识准备:引用类型...................................................................................................................... 46 3.4.5 任务四:引用类型程序示例.......................................................................................................... 46 3.4.6 技能拓展任务:分析对象的构造和初始化.................................................................................. 47 3.5 变量及其初始化 ................................................................................................... 49 3.5.1 知识准备:局部变量...................................................................................................................... 49 3.5.2 知识准备:成员变量...................................................................................................................... 50 3.5.3 知识准备:变量初始化.................................................................................................................. 51 3.5.4 知识准备:局部变量的初始化...................................................................................................... 51 3.5.5 知识准备:成员变量的初始化...................................................................................................... 52  VII  3.5.6 任务五:成员变量的 3 种初始化方式.......................................................................................... 52 3.6 值传递和引用传递 ................................................................................................ 53 3.6.1 知识准备:Java中的值传递........................................................................................................... 53 3.6.2 知识准备:Java中的引用传递....................................................................................................... 54 3.7 Java编码规范 ........................................................................................................ 55 3.7.1 知识命名规范.................................................................................................................................. 55 3.7.2 代码编写格式规范.......................................................................................................................... 56 第 4 章 运算符、表达式与流程控制 ................................................................................ 3.8 本章小结 .............................................................................................................. 57 58 4.1 运算符 ................................................................................................................. 58 4.1.1 知识准备:算术运算符.................................................................................................................. 58 4.1.2 知识准备:递增、递减运算符...................................................................................................... 60 4.1.3 知识准备:关系和布尔运算符...................................................................................................... 60 4.1.4 任务一:短路布尔运算.................................................................................................................. 62 4.1.5 知识准备:三元运算符.................................................................................................................. 63 4.1.6 知识准备:位运算符...................................................................................................................... 64 4.1.7 知识准备:赋值运算符.................................................................................................................. 65 4.1.8 任务二:简单数据类型和引用数据类型的赋值操作.................................................................. 67 4.1.9 知识准备:运算符的优先顺序...................................................................................................... 68 4.1.10 技能拓展任务:字符串连接运算符............................................................................................ 69 4.2 表达式 ................................................................................................................. 70 4.2.1 知识准备:表达式中运算符的结合性.......................................................................................... 70 4.2.2 知识准备:表达式中运算符的优先顺序...................................................................................... 70 4.3 分支语句 .............................................................................................................. 71 4.3.1 知识准备:if语句 ........................................................................................................................... 71 4.3.2 任务三:if语句的用法 ................................................................................................................... 72 4.3.3 知识准备:switch语句 ................................................................................................................... 73 4.3.4 任务四:switch分支语句实例 ....................................................................................................... 73 4.4 循环语句 .............................................................................................................. 75 4.4.1 知识准备:for语句 ......................................................................................................................... 75 4.4.2 任务五:for循环语句实例 ............................................................................................................. 77 4.4.3 知识准备:while语句..................................................................................................................... 77 4.4.4 知识准备:do…while语句............................................................................................................. 78 4.4.5 知识准备:break/continue语句 ...................................................................................................... 79 4.4.6 技能拓展任务:continue结合标签的使用 .................................................................................... 81 4.5 本章小结 .............................................................................................................. 82  VIII  第 5 章 数组 ................................................................................................................... 84 5.1 数组基本概念 ....................................................................................................... 84 5.2 一维数组 .............................................................................................................. 84 5.2.1 知识准备:一维数组的声明.......................................................................................................... 84 5.2.2 知识准备:一维数组的创建.......................................................................................................... 85 5.2.3 任务一:一维数组的声明与创建实例.......................................................................................... 86 5.2.4 知识准备:一维数组的初始化...................................................................................................... 86 5.2.5 知识准备:引用数组元素.............................................................................................................. 89 5.2.6 任务二:引用数组实例,对数组排序.......................................................................................... 89 5.2.7 知识准备:简单数据类型数组的内存空间.................................................................................. 90 5.2.8 技能拓展任务:数组复制.............................................................................................................. 93 5.3 数据结构及数组应用 ............................................................................................ 95 5.3.1 知识准备:堆栈.............................................................................................................................. 95 5.3.2 任务三:使用数组实现堆栈.......................................................................................................... 95 5.3.3 知识准备:队列.............................................................................................................................. 97 5.3.4 任务四:使用数组实现队列.......................................................................................................... 97 5.3.5 知识准备:排序算法...................................................................................................................... 99 5.3.6 技能拓展任务:排序算法实例.................................................................................................... 102 5.4 多维数组 .............................................................................................................104 5.4.1 知识准备:多维数组的声明........................................................................................................ 104 5.4.2 知识准备:多维数组的创建........................................................................................................ 104 5.4.3 知识准备:多维数组的初始化.................................................................................................... 104 第 6 章 面向对象编程进阶 ............................................................................................. 5.5 本章小结 .............................................................................................................105 108 6.1 继承 ....................................................................................................................108 6.1.1 类的继承........................................................................................................................................ 108 6.1.2 任务一:利用继承实现通迅录实例............................................................................................ 111 6.1.3 访问控制........................................................................................................................................ 113 6.2 super关键字 .........................................................................................................114 6.2.1 调用父类构造器............................................................................................................................ 114 6.2.2 调用父类属性和方法.................................................................................................................... 115 6.2.3 任务二:sup er关键字的使用 ....................................................................................................... 116 6.3 this关键字 ...........................................................................................................117 6.3.1 知识准备:使用this获得当前对象的引用 .................................................................................. 117 6.3.2 知识准备:在构造器中调用构造器............................................................................................ 119 6.3.3 知识准备:static的含义 ............................................................................................................... 120 6.4 方法的覆盖与重载 ...............................................................................................121  IX  6.4.1 知识准备:方法覆盖.................................................................................................................... 121 6.4.2 知识准备:方法重载.................................................................................................................... 123 6.4.3 知识准备:方法重载构造器重载................................................................................................ 124 6.5 通常需要覆盖的几种方法 ....................................................................................125 6.5.1 知识准备:对象的toString方法................................................................................................... 125 6.5.2 任务三:覆盖toString方法........................................................................................................... 125 6.5.3 知识准备:==和equals() .............................................................................................................. 126 6.6 对象的初始化 ......................................................................................................130 6.7 封装类 ................................................................................................................135 6.7.1 知识准备:Java中的封装类......................................................................................................... 135 6.7.2 知识准备:自动拆箱和装箱........................................................................................................ 136 6.7.3 知识拓展:在Java中实现小数的精确计算................................................................................. 138 第 7 章 高级类特性 ....................................................................................................... 6.8 本章小结 .............................................................................................................139 141 7.1 static关键字 .........................................................................................................141 7.1.1 知识准备:static概述 ................................................................................................................... 141 7.1.2 知识准备:static变量的引用方法 ............................................................................................... 142 7.2 final关键字 ..........................................................................................................142 7.2.1 知识准备:final数据 .................................................................................................................... 142 7.2.2 知识准备:final方法 .................................................................................................................... 144 7.2.3 知识准备:final类 ........................................................................................................................ 144 7.3 抽象类 ................................................................................................................145 7.3.1 知识准备:abstract概述 ............................................................................................................... 145 7.3.2 知识准备:abstract class .............................................................................................................. 145 7.3.3 任务一:abstract实例 ................................................................................................................... 146 7.3.4 知识拓展:模板设计模式............................................................................................................ 148 7.4 接口 ....................................................................................................................150 7.4.1 知识准备:接口的定义................................................................................................................ 150 7.4.2 知识准备:使用接口.................................................................................................................... 152 7.4.3 任务二:使用接口的例子............................................................................................................ 152 7.4.4 知识准备:接口的扩展................................................................................................................ 153 7.4.5 技能扩展任务:抽象类与接口.................................................................................................... 154 7.5 多态 ....................................................................................................................155 7.5.1 知识准备:多态概述.................................................................................................................... 155 7.5.2 知识准备:instanceof运算符 ....................................................................................................... 156 7.5.3 知识准备:引用类型数据转换.................................................................................................... 156 7.6 内部类 ................................................................................................................158  X  7.6.1 知识准备:内部类定义................................................................................................................ 158 7.6.2 知识准备:局部内部类................................................................................................................ 159 7.6.3 任务三:局部内部类.................................................................................................................... 160 7.6.4 知识准备:匿名内部类................................................................................................................ 161 7.6.5 任务四:匿名内部类练习............................................................................................................ 162 7.6.6 知识准备:内部类特性................................................................................................................ 162 7.7 修饰符的适用范围 ...............................................................................................163 第 8 章 Java异常处理 .................................................................................................... 7.8 本章小结 .............................................................................................................163 165 8.1 异常概述 .............................................................................................................165 8.1.1 知识准备:异常的概念................................................................................................................ 165 8.1.2 知识准备:Error/Exception层次关系 .......................................................................................... 165 8.1.3 任务一:数学计算异常示例........................................................................................................ 166 8.1.4 任务二:访问空对象引起的异常示例........................................................................................ 167 8.1.5 任务三:访问文件异常示例........................................................................................................ 167 8.2 Java中异常的处理 ................................................................................................168 8.2.1 知识准备:常见异常.................................................................................................................... 168 8.2.2 知识准备:Java中的异常处理机制............................................................................................. 168 8.2.3 知识准备:通过t ry …catch…finally来处理异常 ........................................................................ 169 8.2.4 知识准备:将异常抛出................................................................................................................ 173 8.2.5 任务四:捕获异常和抛出异常结合使用.................................................................................... 175 8.2.6 任务五:进行方法覆盖时对异常的处理.................................................................................... 176 8.3 自定义异常 .........................................................................................................177 8.3.1 知识准备:自定义异常概念........................................................................................................ 177 8.3.2 知识拓展:通过printStackTrace()追踪异常源头........................................................................ 178 第 9 章 Android中的Java线程 ....................................................................................... 8.4 本章小结 .............................................................................................................179 9.1 181 线程 概述 ............................................................................................................. 9.2 181 Java线程模型 ....................................................................................................... 9.3 182 创建 线程 .............................................................................................................183 9.3.1 知识准备:继承Thread类创建线程 ............................................................................................ 183 9.3.2 知识准备:实现Runnable接口创建线程 .................................................................................... 184 9.3.3 知识准备:后台线程概念............................................................................................................ 184 9.3.4 任务一:继承Thread类创建线程实例 ........................................................................................ 185 9.3.5 任务二:实现Runnable接口方式创建线程 ................................................................................ 185 9.3.6 技能拓展任务:实现后台线程示例............................................................................................ 186  XI  9.4 线程运行机制.......................................................................................................187 9.4.1 知识准备:线程基本状态............................................................................................................ 187 9.4.2 知识准备:线程结束方式............................................................................................................ 188 9.5 9.4.3 任务三:线程基本状态示例........................................................................................................ 189 线程 控制 .............................................................................................................190 9.5.1 知识准备:测试线程.................................................................................................................... 190 9.5.2 知识准备:中断线程.................................................................................................................... 191 9.5.3 知识准备:设置线程优先级........................................................................................................ 191 9.5.4 知识准备:Thread.join()线程等待 .............................................................................................. 192 9.5.5 任务四:设置线程优先级示例.................................................................................................... 192 9.6 多 9.5.6 技能拓展任务:线程的join()方法实现示例............................................................................... 194 线程 编程 ........................................................................................................197 9.6.1 知识准备:多线程概述................................................................................................................ 197 9.6.2 知识准备:多线程共享数据........................................................................................................ 199 9.6.3 知识准备:线程间通信................................................................................................................ 205 9.6.4 知识准备:实现线程间通信........................................................................................................ 206 9.6.5 知识拓展:定时器........................................................................................................................ 209 9.8 java. 9.7 多线程编程的一般规则.........................................................................................210 util.concurrent中的同步API .............................................................................211 9.8.1 知识准备:线程池........................................................................................................................ 211 9.8.2 知识准备:锁................................................................................................................................ 213 9.8.3 任务五:线程池的实例................................................................................................................ 214 9.8.4 知识准备:使用锁实例................................................................................................................ 216 9.8.5 知识准备:使用读/写锁............................................................................................................... 221 9.9 9.8.6 技能拓展任务:使用Condition来实现线程间的通信................................................................ 224 本章 小结 ............................................................................................................. 第 10 章 网络编程 ......................................................................................................... 227 10.1 228 网络 基础 ...........................................................................................................228 10.1.1 知识准备:网络类型.................................................................................................................. 228 10.1.2 知识准备:网络工作模式.......................................................................................................... 228 10.2 10.1.3 知识准备:网络通信结构.......................................................................................................... 229 网络 通信协议 ....................................................................................................229 10.2.1 知识准备:TCP .......................................................................................................................... 230 10.2.2 知识准备:IP .............................................................................................................................. 230 10.2.3 知识准备:TCP/IP...................................................................................................................... 230 10.2.4 知识准备:IP地址 ...................................................................................................................... 231 10.2.5 知识准备:端口.......................................................................................................................... 233  XII  10.3 10.2.6 任务一:通过Java编程获得IP地址 ........................................................................................... 234 Socket套接字 ...................................................................................................... 10.4 235 Java Socket编程 ..................................................................................................235 10.4.1 知识准备:ServerSocket ............................................................................................................ 236 10.4.2 知识准备:Socket类................................................................................................................... 236 10.4.3 知识准备:Socket通信过程....................................................................................................... 236 10.4.4 任务二:Socket通信案例........................................................................................................... 237 10.5 Java URL类 ........................................................................................................239 10.5.1 知识准备:URL概念.................................................................................................................. 239 10.5.2 知识准备:Java 中的 URL 类................................................................................................... 239 10.6 10.5.3 知识拓展:URL应用实例.......................................................................................................... 240 本章 小结 ........................................................................................................... 第 11 章 JSP+MySQL数据库开发 .................................................................................. 243 11. 1 245 JSP简介 ..............................................................................................................245 11.1.1 知识准备:什么是JSP................................................................................................................ 245 11.1.2 知识准备:JSP页面.................................................................................................................... 245 11. 2 11.1.3 知识准备:安装配置JSP运行环境............................................................................................ 246 JSP语法 ..............................................................................................................248 11.2.1 知识准备:JSP页面基本结构.................................................................................................... 248 11.2.2 知识准备:JSP中的注释............................................................................................................ 249 11.2.3 知识准备:JSP指令标签............................................................................................................ 249 11.2.4 知识准备:JSP动作标签............................................................................................................ 250 11.2.5 知识准备:JSP中变量和方法的声明........................................................................................ 252 11.2.6 知识准备:Java程序片............................................................................................................... 253 11.2.7 任务一:插入程序片实例.......................................................................................................... 253 11.2.8 知识准备:表达式...................................................................................................................... 254 11. 3 JSP 内置对象 ..................................................................................................... 255 11.2.9 任务二:JSP页面中计算表达式的值........................................................................................ 254 11.3.1 知识准备:request对象 .............................................................................................................. 256 11.3.2 知识准备:response对象............................................................................................................ 257 11. 4 11.3.3 知识准备:session对象 .............................................................................................................. 257 JSP中使用数据库 ................................................................................................258 11.4.1 知识准备:从ODBC到JDBC技术 ............................................................................................. 258 11.4.2 知识准备:JDBC中的API.......................................................................................................... 258 11.4.3 知识准备:MySQL数据库简介................................................................................................. 260 11.4.4 知识准备:通过JDBC访问MySQL数据库 ............................................................................... 267 11.4.5 任务三:在JSP页面插入程序连接MySQL数据库................................................................... 270  XIII  11.4.6 知识准备:使用连接池.............................................................................................................. 271 11. 5 11.4.7 技能拓展任务:使用连接池与字符转换.................................................................................. 274 本章 小结 ........................................................................................................... 第 12 章 Android下Java高效编程 ................................................................................ 276 12.1 And roid下Java编程性能优化介绍......................................................................... 277 12.2 提升性能的优化方法 .......................................................................................... 277 278 12.2.1 使用本地方法.............................................................................................................................. 278 12.2.2 使用虚方法优于使用接口.......................................................................................................... 278 12.2.3 使用静态代替虚拟...................................................................................................................... 278 12.2.4 缓冲对象属性调用...................................................................................................................... 278 12.2.5 声明final常量 .............................................................................................................................. 279 12.2.6 考虑用包访问权限替代私有访问权限...................................................................................... 280 12.3 编程中注意避免的事项 ....................................................................................... 12.2.7 使用改进的for循环语法 ............................................................................................................. 281 282 12.3.1 避免创建不必要的对象.............................................................................................................. 282 12.3.2 避免使用内部的Getters/Setters .................................................................................................. 282 12.3.3 避免使用枚举类型...................................................................................................................... 283 12.4 12.3.4 避免使用浮点类型...................................................................................................................... 284 标准 操作的时间比较.......................................................................................... 12.5 284 本章 小结 ...........................................................................................................285 第 4 章 运算符、表达式与流程控制 本章介绍了 Java 运算符的概念和用法,并论述在表达式中各种运算符结合使用的情况, 分析了运算符的优先级;介绍了分支语句和循环语句等流程控制语句的用法。 4.1 运算符 Java 语言提供了丰富的运算符环境,其中主要包括四大类运算符,即算术运算符、位 运算符、关系运算符和逻辑运算符。本章主要介绍 Java 运算符的概念和各种运算符的用法, 并对各种运算符在表达式中的结合性和优先级做了论述;分支语句和循环语句及 continue/break 的用法。 4.1.1 知识准备:算术运算符 Java 常见的算术运算符有 5 种,如表 4-1 所示。 表 4-1 常见算数运算符 运算符 含义 + 加法运算 — 减法运算 * 乘法运算 / 除法运算 % 取模运算 这些算术运算符可以用于 Java 基本数据类型中的数值型(byte、short、char、int、long、 float、double)数据。对于+、-和*运算符,都是很容易理解的,它们分别接受两个操作数, 通过运算后返回得到新值。需要注意的是除法运算和取模运算。 1.除法运算 在数学运算中,0 作为除数是没有意义的,在 Java 程序中,对于以 0 作为除数的情况, 根据操作数的数据类型,做了不同的处理:对于整形的运算,它将会出现异常;而对于浮 点型数据的运算,它将得到一个无穷大值或者 NaN(not a number)。示例如下: 源文件:Division.Java 第 4 章 运算符、表达式与流程控制 59 4 public class Division { public static void main(String[] args) { System.out.println("123.0/0 = " + 123.0 / 0); System.out.println("123/0 = " + 123/ 0); } } 编译运行这个程序,将得到如下的结果: 123.0/0 = Infinity Exception in thread "main" Java.lang.ArithmeticException: / by zero at Division.main(Division.Java:5) 关于 Exception,是 Java 的异常处理机制,将在后面的章节详细讲解。 2.取模运算 取模运算即求余运算,对于 Java 语言来说,其操作数可以是浮点数,计算结果也将是 浮点数。 源文件:Mode.Java public class Mode { public static void main(String args[]) { System.out.println("123.5 mod 4 = " + 123.5 % 4); System.out.println("123 mod 4 = " + 123 % 4); } } 编译运行这个程序,将得到如下的输出: 123.5 mod 4 = 3.5 123 mod 4 = 3 取模运算可以用于判别奇偶数,一个整数 n 对 2 取模,如果余数为 0,则表示 n 为偶 数,否则 n 为奇数。此外,判别素数,求最大公约数的运算中也会用到取模运算。 注意: 取模运算也会执行除法操作,所以,对于整形数据来说,也不能使用 0 作为取模运算中的“除数”, 否则也会出现和除法运算一样的异常。 3.二元运算符简捷赋值方式 +、-、*、/、%运算如果用在赋值语句中,还可以使用二元运算符的简捷方式来实现, 比如: a = a+5; 可以使用如下的运算式表示: a +=5; 它们在运算结果上是相等的。其他 4 个运算符也可以像上面这个例子中的运算符一样 使用,也就是说,将运算符放在“=”的左边,如 a*=5、a/=5 等。 Android 系统下 Java 编程详解 60 4 4.1.2 知识准备:递增、递减运算符 在编写 Java 程序时,经常需要对一个变量加一或者减一,这时通常使用递增或递减运 算符来完成。其中递增运算符对操作数加 1,递减运算符从操作数减 1。 递增和递减操作符有两种形式:“前缀版”和“后缀版”。“前递增”表示++运算符位于 变量或表达式的前面;而“后递增”表示++运算符位于变量或表达式的后面。类似地,“前 递减”意味着--运算符位于变量或表达式的前面;而“后递减”意味着--运算符位于变量 或表达式的后面。对于“前递增”和“前递减”(如++A 或--A), 会先执行运算,再生成 值。而对于“后递增”和“后递减”(如 a++或 a--),会先生成值,再执行运算,示例如下: int counter =20; counter++; 此时,counter 的值为 21。 前缀方式和后缀方式的作用都是对操作数加上或减去 1,区别在于用在表达式中的时 候。例如: int a = 10; int b = 10; int m = 2*++a; int n = 2*b++; 此时,m 的值是 22,n 的值是 20,a 和 b 的值都是 11。这是因为,在进行 m = 2*++a 运算时,程序会先将 a 加上 1,然后再进行乘法运算。而对于 n=2*b++的后缀递增运算,则 会先取出 b 的数值进行乘法运算,然后再将 b 递增 1。所以,此时 m 的值是 22(m=2*(10+1)), n 的值是 20(n=2*10),a 和 b 的值都为 11。 注意: 递增/递减操作符只能用于变量而不能用在数字本身,如这样的用法是错误的:10--,5++。 4.1.3 知识准备:关系和布尔运算符 Java 中提供了完整的关系运算符,共有 6 种,用来对两个简单类型操作数进行比较运 算,所组成的表达式结果为 boolean 类型的值 true 或 false,如表 4-2 所示。 表 4-2 Java 关系运算符 运算符 名称 示例 功能 < 小于 a 大于 a>b a 大于 b 时返回真;否则返回假 >= 大于等于 a>=b a 大于等于 b 时返回真;否则返回假 第 4 章 运算符、表达式与流程控制 61 4 == 等于 a==b a 等于 b 时返回真;否则返回假 != 不等于 a!=b a 不等于 b 时返回真;否则返回假 关系运算符的优先顺序为: (1)前 4 种关系运算符的优先级别相同,后两种也相同。前 4 种高于后两种。 (2)关系运算符的优先级低于算术运算符。 (3)关系运算符的优先级高于赋值运算符。 来看一些简单的例子: 2>3; //返回 false 2==3; //返回 false 2!=3; //返回 true 这里要提醒一些有编程经验的读者需要注意的是,在 Java 中,“不等于”是用“!=” 表示的,而不是一些编程语言的“<>”,而等于使用“==”而非“=”,在 Java 中, “ =”用 于赋值操作,而非关系运算符。“==”和“!=”除了用于简单类型的操作数外,还可以用于 比较引用类型数据。 注意: 除了“==”和“!=”外,其他的关系运算符都不能用在 boolean 类型的操作数中。 1.逻辑运算符 逻辑运算符也称为布尔运算符,是指进行逻辑运算的符号。逻辑运算符主要包括:!、 &、|、^、&&、||,这些运算符分别实现“非”、“与”、“或”、“异或”、“短路与”、“短路或” 等逻辑运算。和关系运算一样,逻辑运算结果也是布尔类型值 true 或 false,参与逻辑运算 的数据也必须是 boolean 类型。关于逻辑运算符的种类和功能说明如表 4-3 所示。 表 4-3 逻辑运算符的种类和功能说明 符号 名称 功能说明 ! 非 只操作一个数据,对数据取反 & 与 两个条件同时为 true 才为 true,否则为 fal s e | 或 两个条件有一个为 true 则为 true,否则为 fal s e ^ 异或 两个条件真值不相同,则异或结果为真。反之,为假 && 短路与 同&,两个条件同时为 true 才为 true,否则为 fal s e || 短路或 两个条件有一个为 true 则为 true,否则为 fal s e 在布尔运算符中,需要特别说明的是,短路与“&&”和短路或“||”,这两个运算符是 按照“短路”的方式进行求值的,也就是说,如果第一个表达式已经可以判断出整个表达 式的值时,就不进行后面的运算了。例如,当对表达式 a&&b 进行运算时,如果表达式 a 的值为 false,将不对 b 的值进行计算。而当对表达式 a||b 进行运算时,如果 a 的值为 true, 将不对 b 的值进行计算。 Android 系统下 Java 编程详解 62 4 4.1.4 任务一:短路布尔运算 1.任务描述 编写一个程序,包含几个返回值为布尔类型的方法,方法中向控制台输出调用信息。 在主函数中通过含“短路与”和“短路或”的逻辑表达式调用这些方法,验证短路布尔运 算的特点。随后将短路布尔运算符换乘普通布尔运算符,比较输出结果。 2.技能要点  使用布尔运算符。  比较短路布尔运算符与普通布尔运算符的异同。 3.任务实现过程 (1)编写一个名为LogicalOperators 的类,定义 3个返回值为boolean类型的方法 Msg1、 Msg2 和 Msg3,返回值分别为 true、false、false,并在每个方法里都输出一条语句,显示该 方法被调用。在 Mail()方法中,调用 3 个方法,将 3 个方法的返回值进行短路与运算和短 路或运算。查看输出结果和方法调用情况。 源文件:LogicalOperators.Ja va public class LogicalOperators { public static void main(String[] args) { LogicalOperators lg= new LogicalOperators(); System.out.println("短路或运算"); System.out.println(lg.Msg1() || lg.Msg2() || lg.Msg3()); System.out.println("短路与运算"); System.out.println(lg.Msg1() && lg.Msg2() && lg.Msg3()); } boolean Msg1() { System.out.println("显示信息 1"); return 1 < 2;// true } boolean Msg2() { System.out.println("显示信息 2"); return 1 == 2;// false } boolean Msg3() { System.out.println("显示信息 3"); return 1 > 2;// false } } (2)编译运行上面的程序,将得到如下的输出: 第 4 章 运算符、表达式与流程控制 63 4 短路或运算 显示信息 1 true 短路与运算 显示信息 1 显示信息 2 false 分析:上面的程序中,因为方法 Msg1()的值为 true,而“或”运算中如果有一个表达 式为真(true),则整个表达式均为真(true),因此,无须计算后面方法 Msg2()和方法 Msg3() 两个表达式就可以得到整个表达式的值了。而第二条“短路与”语句,因为在逻辑“与” 运算中,只需要一个表达式的值为假(false),则整个表达式的值都为假(false)。 Msg1() 为真(true),所以将进行第二个表达式的运算,它将调用方法 Msg2(),而此时,Msg2()方 法的返回值为假(false),所以将不用进行后面的运算了。 (3)将 LogicalOperators.Java 中的短路布尔运算符修改成普通布尔运算符。即将如下 语句修改,程序其他语句不变: System.out.println("短路或运算"); System.out.println(lg.Msg1() | lg.Msg2() | lg.Msg3()); System.out.println("短路与运算"); System.out.println(lg.Msg1() & lg.Msg2() & lg.Msg3()); 输出结果如下: 短路或运算 显示信息 1 显示信息 2 显示信息 3 true 短路与运算 显示信息 1 显示信息 2 显示信息 3 false 分析:运算符“&&”和“&”、“ ||”和“|”所求得的结果是一样的,它们的区别在于, “&”和“|”不会进行“短路”运算,而是会计算运算符两边的各个参数的值。 4.1.5 知识准备:三元运算符 Java 还支持三元运算符“?:”(也称为条件运算符),这个运算符的用法如下: condition? a:b 其中条件 condition 是一个布尔表达式, 如果 condition 为 true,则表达式的值为 a;否 则,表达式的值为 b。来看一个简单的例子: public class demo { public static void main(String[]args){ int a=10,b=20,y; a>b?(y=a):(y=b;) //1,这样写是错误的 Android 系统下 Java 编程详解 64 4 y=a>b?a:b; //2,这样写是正确的 } } 可以看到,a>b 的时候,执行 y= a ,本例中,ab 的值为 false, 执行 y=b,此时 y 的值为 20。. 4.1.6 知识准备:位运算符 位运算是以二进制位为单位进行的运算,其操作数和运算结果都是整型值。可以使用 运算符直接处理组成这些整数的各个二进制位。适用的数据类型有 byte、short、char、int、 long。 位运算符共有 7 个,分别是位与(&)、位或(|)、位非(~)、位异或(^)、右移(>>)、 左移(<<)、 0 填充的右移(>>>)。其功能说明如表 4-4 所示。 表 4-4 位运算符功能表 运算符 名称 示例 说明 & 位与 x&y 把 x 和 y 按位求与 | 位或 x|y 把 x 和 y 按位求或 ~ 位非 ~x 把 x 按位求非 ^ 位异或 x^y 把 x 和 y 按位求异或 >> 右移 x>>y 把 x 的各位右移 y 位 << 左移 x<>> 右移 x>>>y 把 x 的各位右移 y 位,左边填 0 其中位运算的位与(&)、位或(|)、位非(~)、位异或(^)与逻辑运算的相应操作的 真值表完全相同,其差别只是位运算操作的操作数和运算结果都是二进制整数,而逻辑运 算相应操作的操作数和运算结果都是逻辑值。 而>>、<<、>>>也被称为移位运算符,作用是左移运算是将一个二进制位的操作数按 指定移动的位数向左移位,移出位被丢弃,右边的空位一律补 0。右移运算是将一个二进 制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位或者一律补 0, 或者补符号位。在使用补码作为机器数的机器中,正数的符号位为 0,负数的符号位为 1。 程序示例如下。 (1)有如下程序段: int x = 64; //x 等于二进制数的 01000000 int y = 70; //y 等于二进制数的 01000110 int z = x&y //z 等于二进制数的 01000000 即运算结果为 z 等于二进制数 01000000。位或、位非、位异或的运算方法类同。 (2)右移是将一个二进制数按指定移动的位数向右移位,移掉的被丢弃,左边移进的 部分或者补 0(当该数为正时),或者补 1(当该数为负时)。这是因为整数在机器内部采用 补码表示法,正数的符号位为 0,负数的符号位为 1。例如,对于如下程序段: 第 4 章 运算符、表达式与流程控制 65 4 int x = 70; //x 等于二进制数的 01000110 int y = 2; int z = x>>y //z 等于二进制数的 00010001 即运算结果为 z 等于二进制数 00010001,即 z 等于十进制数 17。 对于如下程序段: int x = -70; //x 等于二进制数的 11000110 int y = 2; int z = x>>y //z 等于二进制数的 11101110 即运算结果为 z 等于二进制数 11101 110,即 z 等于十进制数-18。要透彻理解右移和左 移操作,读者需要掌握整数机器数的补码表示法。 (3)0 填充的右移(>>>)是不论被移动数是正数还是负数,左边移进的部分一律补 0。 注意: 没有与>>>对应的<<<操作。 4.1.7 知识准备:赋值运算符 在前面的章节中,已经在很多地方都用到了赋值运算符。赋值运算符“=”将“=”右 边的值赋给(更准确地说是“复制到”)左边的变量。“=”右边的值可以是任何的变量、常 量或者一个可以产生值的表达式,而“=”的左边必须是一个明确的、命名的变量,不可以 为常量,如 a= 10 是合法的,而 10 = a 却是不允许的。 对于基本数据类型的赋值,其非常简单,它直接将“=”右边的值复制到左边的变量中; 对于引用数据类型的赋值,操作的并非是对象本身,而是它的“对象引用”,它实际上是将 “=”右边的引用(而非对象本身)复制到左边的变量中。 1.扩展赋值运算 将赋值运算符和其他的运算符结合起来,就可以作为一种特别的“扩展”赋值运算符。 扩展赋值运算符有:+=、-=、*=、/=、%=、&=、|=、^=、>>=、<<=、>>>=等。注意,并 非所有的运算符都可以和赋值运算符结合成扩展赋值运算符。 扩展赋值运算符的引入只是为了简化赋值运算的表达形式,将“a=a operator b;”简化 为“ a operator=b;”,其作用是相同的。 2.运算中的数据类型转换 在第 3 章中,已经知道了,数值简单数据类型数据之间是可以相互转换的。那么,在 表达式中,它是如何转换的呢?比如,一个表达式中既有 f loat 类型的数据,又有 double 类型的数据,那么,得出来的结果到底是什么数据类型呢? Java 在编译期间就会进行扩展类型检查,并且数据从一种类型转换到另一种类型时有 严格的限制。在 Java 中,存在两种不同类型的类型转换: 隐式转换:在对包含非 boolean 简单数据类型(primitive type)的表达式求值的时候, Java 会进行大量的隐式类型转换。这些转换有很大的限制,但最基本的原则是这种转换必 Android 系统下 Java 编程详解 66 4 须是提升(widen ing,或称为扩大)而不是下降(narrowing,或称为缩小)转换。也就是 说,隐式转换只能将一种简单数据类型转换到比它范围更大的类型。 强制类型转换:当隐式转换不能被所要求的表达式支持,或者是有特殊的需求,则需 要进行强制的类型转换,这时候需要使用类型转换运算符进行强制转换。 对于一元运算符,例如++或--,隐式转换比较简单: byte、short、c har 类型的数被转 换成 int 的,而其他类型的数据保持不变。 对于二元运算符,情况比较复杂,但是这种转换基本上遵循如下的基本方式:表达式 中最长的类型为表达式的类型。下面是具体的运算规则: (1)如果两个操作数中有一个是 double 类型的,则另一个也将会转换成 double 类型, 最后的运算结果也是 double 类型的,也就是说,表达式的类型为 double 类型。 (2)如果两个操作数中有一个是 float 类型的,则另一个操作数也会转换成 float 类型, 此时表达式类型是 float 类型。 (3)如果两个操作数中有一个是 long 类型的,另一个将会转换成 lo ng 类型,此时, 表达式的类型也为 long 类型,否则,两个操作数都会转换成 int 类型。 (4)对于 byte/char/short 类型的数据,在进行计算时都会转换成 int 类型来计算,得出 的结果也是 int 类型。 下面来看一个例子: 源文件:TypeConversion.Ja va public class TypeConversion { public static void main(String[] args) { short s = 11; long l = 111; int i = 1; byte b1 = 2, b2 = 3; char c = 'c'; System.out.println(l * s); // 将会得到一个 long 类型的数值 int j = b1 + c; //byte 类型+char 类型结果为 int 类型 // byte f = b+e; //将会报错,因为计算得出的结果应该是 int 类型 int m = 1123456789; float n = m; // 将会损失精度,得到的结果是 1.12345677E9 System.out.println(j); System.out.println(n); } } 在这个例子中,如果将两个 byte 类型的数据相加,将结果赋给一个 byte 类型的变量, 编译的时候将会出错,这是因为两个 byte 类型的值相加返回的是 int 类型的值。而如果将 一个整型的值赋给一个 float 类型的变量,则会保留正确的数值级数,但是,从例子中的结 果可以看出,转换后已经损失了一些精度。 虽然不能将一个会产生 int 类型结果的表达式的值赋给 byte 类型变量,但是实际上, 可以将整型值直接赋值给 byte/short/char 等“更窄”类型的变量,而不需要进行强制类型转 换,只要不超出其表数范围即可。 第 4 章 运算符、表达式与流程控制 67 4 例如: byte b1 = 33; //合法 short s = 456; //合法 char ch = 345; //合法 byte b2 = 142 //非法,超出 byte 型数据表数范围 隐式转换不但发生在表达式中,还发生在方法调用的时候。比如,当调用方法时的参 数不是方法定义中所规定的参数类型的时候。 在上面已经讲过,Java 可以自动“提升”数据类型。但是,经常需要将数据从较长的 类型转换到较短的类型,如将 double 类型的数据转换成 int 类型的数据,这时,Java 不会 自动完成这个动作(默认情况下只会将 int 类型的数据转换成 double 类型的),所以,需要 在程序中对其进行强制转换。当然,这种操作可能会引起信息的丢失,所以,应该尽量小 心使用。 除了简单数据类型外,类型转换还可以引用于引用类型的数据。任何对象都可以被转 换成它的基类或任何它所实现的接口。一个接口也可以被转换成它所扩展的任何其他接口。 4.1.8 任务二:简单数据类型和引用数据类型的赋值操作 1.任务描述 写一段程序,分别给简单类型变量和引用类型变量进行赋值操作,要求输出赋值后变 量值,并体现出引用类型赋值后变量指向同一对象。 2.技能要点  简单的赋值操作。  了解引用类型变量赋值和简单类型变量赋值的区别。 3.任务实现过程 (1)编写源程序,定义了一个类“Cloc k”,它有一个“time”的属性。在类“Assignment” 的 main( )方法中,定义了两个 int 简单数据类型的变量 a、b,并给 b 赋值 100,然后将 b 的 值赋给变量 a,此时实际上是将 b 的值的一个“副本”复制给了 a,因此,a 和 b 中任何一 方的变化,都不会影响到另一方。 (2)定义了两个 Cloc k 引用类型的变量 c1、c2,并给 c1 初始化了一个对象引用,然 后,将 c1 的值赋给 c2,此时,这个操作实际上是将 c1 的对象引用复制给了 c2,此时,c1 和 c2 所指向的是同一个对象!因此,无论通过变量 c1 还是 c2 去改变对象,改变的都是同 一个对象。 源文件:Assignment.Java public class Assignment { public static void main(String[] args) { // 简单数据类型 int a, b = 100; Android 系统下 Java 编程详解 68 4 a = b; b = 10; System.out.println("a = " + a); System.out.println("b= " + b); Clock c1 = new Clock(10); Clock c2; c2 = c1; c1.setTime(12); System.out.println("Clock1 的 time=" + c1.getTime()); System.out.println("Clock2 的 time=" + c2.getTime()); } } class Clock { private int time; // 构造器 public Clock(int clockTime) { time = clockTime; } public int getTime() { return time; } public void setTime(int time) { this.time = time; } } (3)编译并运行上面的类“Assignment”,将得到如下的输出: a = 100 b= 10 Clock1 的 time=12 Clock2 的 time=12 4.1.9 知识准备:运算符的优先顺序 除了上面的这些运算符外,Java 还提供其他非常丰富的运算符来进行其他运算。 Java 运算符在风格和功能上都与C和C++极为相似。下面按优先顺序列出了各种运算符: 分隔符: [] () ; , 从右到左结合: ++ -- + - ~ ! (data type) 从左到右结合: * / % 从左到右结合: + - 从左到右结合: << >> >>> 从左到右结合: < > <= >= instanceof 从左到右结合: == != 从左到右结合: & 从左到右结合: ^ 从左到右结合: | 从左到右结合: && 从左到右结合: || 从右到左结合: ?: 从右到左结合: = *= /= %= += -= <<= >>= >>>= &= ^= |= 第 4 章 运算符、表达式与流程控制 69 4 注意: instanceof 是 Java 编程语言特有的运算符。 4.1.10 技能拓展任务:字符串连接运算符 1.任务描述 运算符“+”除了用于数值类型的加法运算外,在字符串类型(String)数据中,它还 是一个用于连接字符串的特殊的运算符。在表达式中用“+”连接两个操作数,其中有一个 操作数是字符串类型(String), Java 自动将另一个操作数也转换成字符串,然后将这两个 字符串相连起来生成一个新的字符串。 要求通过实例来验证这一转换过程,编写程序实现输出一月和二月的手机数据流量值。 2.技能要点  使用“+”连接字符串。 3.任务实现过程 (1)写一个类 StringConnect.Java,在该类中可以通过将数字和一个空字符串相连的方 式,将数字转换成字符串类型。 源文件:StringConnect.Java public class StringConnect { public static void main(String[] args) { double jan = 98.987; double feb = 76; // 自动将 int 型的数值 1 提升到 double 类型 1.0 double total = jan + feb; String flow = "January Dataflow is: " + jan; // 上面得到一个字符串:"Price is:9.987" String sflow = "The Total DataFlow is: " + total; // 上面得到一个字符串:"Total Price is:10.987" System.out.println(flow); System.out.println(sflow); System.out.println("" + jan + feb); // 打印出一个字符串:"9.9871.0" System.out.println(jan + feb +""); // 打印出一个字符串:"10.987" } } (2)运行程序,输出结果是: January Dataflow is: 98.987 The Total DataFlow is: 174.987 98.98776.0 174.987 从上面的例子中可以看到,String 和一个数字类型的数据进行“+”运算,将会得到一 个新的字符串,这个字符串由旧的字符串和这个数字组成。 Android 系统下 Java 编程详解 70 4 再来看这行程序: System.out.println("" + jan + feb); 根据运算符从左到右的结合原则,空字符串“""”首先和 jan 进行运算,得到一个字符 串,这个字符串的内容就是“98.987”,然后,这个字符串再和数字 y 进行运算,此时得到 一个由 x 和 y 组合成的新的字符串:98.98776.0。 比较一下下面这条语句: System.out.println(jan + feb +""); 这条语句首先进行数值的相加运算,得到一个新的数值:174.987,然后再和空字符串 进行连接运算,此时得到一个新的字符串,内容为“174.987”。 4.2 表达式 在前面的内容中,一直在使用一个概念:表达式。表达式就是运算符和操作数的结合。 Java 中的表达式分为算数表达式和逻辑表达式两种。当代码执行的时候,由 Java 解释器进 行求值,如果结果可以预先计算的话,可以由编译器来进行求值。 4.2.1 知识准备:表达式中运算符的结合性 所有的数学运算都认为是从左到右结合的,在 Java 中,大部分运算也是从左到右结合 的,只有单目运算符、赋值运算符和条件运算符例外。 乘法和加法是两个可结合的运算,也就是说,这两个运算符左右两边的操作符可以互 换位置而不会影响到结果。 4.2.2 知识准备:表达式中运算符的优先顺序 在进行表达式的转换过程中,必须了解各种运算的优先顺序,使转换后的表达式能满 足数学公式的运算要求。表 4-5 列出了运算符的优先级。 表 4-5 运算符的优先级 运算符说明 Java 运算符 运算符说明 Java 运算符 分隔符 . [] () , ; 按位与 & 单目运算符 + - ~ ! ++expr --expr 按位异或 ^ 创建或类型转换 new (type)expr 按位或 | 乘法/除法 * / % 条件与 && 加法/减法 + - 条件或 || 移位 << >> >>> 条件 ?: 关系 < <= >= > instanceo f 赋值 = 第 4 章 运算符、表达式与流程控制 71 4 等价 == != 问:如果是同级的运算符要怎么办呢? 答:如果同级的运算,运算符是按从左到右依次进行;多层括号时由里向外进行。 4.3 分支语句 从结构化程序设计角度出发,程序有 3 种结构:顺序结构、选择结构、循环结构。顺 序结构的程序设计是最简单的,只要按照解决问题的顺序写出相应的语句就行,它的执行 顺序是自上而下,依次执行,在这里不再介绍。本节主要介绍程序设计结构中的选择结构。 选择结构主要通过分支语句实现程序流程控制 ,即根据一定的条件有选择地执行或跳 过特定的语句。Java 分支语句分为两种。 if-else 语句:一种控制程序流程的最基本的方法,else 子句可有可无。 sw itch 语句:另一种效率程序流程控制语句,当必须在程序中检测一个整型表达式的 多个值时将会用到它。 下面首先来看 if 语句,然后来讨论 switch 语句。 4.3.1 知识准备:if 语句 if 条件语句是最常用的一种分支语句,它的基本格式有 3 种。 (1)形式一: if (boolean 条件表达式 ){ 语句 A; } 在 if 后面的条件语句中,必须是一个可以转换成 boolean 的表达式,这个表达式需要 用括号括起来。当表达式值为 false 时,执行语句 A,否则跳过语句 A。 (2)形式二: if (boolean 条件表达式){ 语句 A; } else { 语句 B; } 表达式为 true,执行 A;表达式为 false,执行 B。 (3)形式三: if(boolean 条件表达式) 语句 1 else if(boolean 表达式 2) Android 系统下 Java 编程详解 72 4 语句 2 else if(boolean 表达式 3) 语句 3 … else if(boolean 表达式 n) 语句 n else 语句 n 表达式 1 为 true,则执行语句 1,表达式 1 为 false,判断表达式 2,表达式 2 为 true, 执行语句 2,表达式 2 为 false,判断表达式 3,如此直至表达式 n 为 false,则执行最后一 个 else 后的语句 n。 4.3.2 任务三: if 语句的用法 1.任务描述 使用 if-else 语句,对手机电池电量使用情况进行提示。当手机电池电量大于 30%时, 显示手机电量充足;当电量处于 10%~30%时,提示手机电量低;当电量处于 10%以下时, 提示更换电池;当电量小于 5%时,提示电量耗尽将自动关机。 2.技能要点  使用 if…else 多分支语句。 3.任务实现过程 (1)定义一个名为 Battery 的类,该类中有一个名为 warning 的方法,该方法入口处 传入一个 int 类型的参数,该参数表示当前手机电量。该方法中使用 if…else 语句进行多分 支条件的判断。当电池电量小于等于 5 时,直接输出信息;当电量大于 5 时,进行下一层 判断,电量小于 10 时,输出信息;当电量大于 10 时,再继续进行判断;如果电量小于 30, 输出信息,电量大于 30 时,不再进行判断,执行最后的 else 语句,输出信息。 (2)在 main( )方法里声明了 4 个 int 类型变量,覆盖了 4 个分支条件。调用 warning 方法,将这 4 个变量依次作为参数传入。 源文件:Battery.Ja va public class Battery { public static void main(String[] args) { int a = 40,b=20,c=6,d=5; Battery bat = new Battery(); bat.warning(a); bat.warning(b); bat.warning(c); bat.warning(d); } public void warning(int power){ if (power <= 5) { 第 4 章 运算符、表达式与流程控制 73 4 System.out.println("*The power runs out,about to shutdown!*"); } else if (power < 10) { System.out.println("*Replace the battery!*"); } else if(power < 30){ System.out.println("*The power is low*"); } else System.out.println("*The power is enough*"); } } (3)运行程序,它将向控制台输出如下的信息: *The power is enough* *The power is low* *Replace the battery!* *The power runs out,about to shutdown!* 4.3.3 知识准备:switch 语句 对于多选择分支的情况,可以用 if 语句的 if-else 形式或 if 语句嵌套处理,但大多数情 况下略显麻烦。为此,Java 提供了另一种方法——switch 语句,也称开关语句。一个 switch 语句由一个控制表达式和一个由 c ase 标记描述的语句块组成。和 if 不同,switch 后面的控 制表达式求出的值应该是整型而不是 boolean 类型。对控制表达式求得的值,决定了哪一 个 case 分支将被执行。每一个 c ase 都用唯一的常量表达式或常量来标示,用于控制这个 c ase 是否必须被执行。程序将执行到那些表达式值与控制表达式的值相匹配的 case 分支中。如 果不存在这样的匹配,则将执行 default 后面的代码块。如果没有 default 标记(并不推荐这 样做),则控制被传递到 switch 块后的第一条语句,也就是退出了 switch 块。控制表达式 必须是可以转化为 byte、short、c har 或 int 类型的值的表达式。 在 switch 中,需要了解 4 个关键字——switch、case、break 和 default,它们的意思分 别是开关、情况、中断、默认(值)。用一句话套起来的说法就是根据开关值的不同、执行 不同的情况、直到遇上中断;如果所有的情况都不符合开关值,那么就执行默认的分支。 注意在每一个 c ase 标记的代码块中,最后都有一个 break 语句。这条语句具有重大的作用, 如果没有这条语句,当与 case 分支相连续的代码块执行完毕后,将会继续运行与下一个 c ase 分支相联系的代码块。 4.3.4 任务四:switch 分支语句实例 1.任务描述 使用分支语句 switch,输出一周车辆限行尾号(默认周一、周六限行)。 2.技能要点  使用 switch 分支语句及 case、default、break 关键字。 Android 系统下 Java 编程详解 74 4 3.任务实现过程 (1)定义一个名为 MorningGreetings 的类,在该类中定义一个 greetings 方法,该方法 传入一个 int 类型变量 n。使用 switch 语句对 n 进行判断。如果 n 是 1,则代表周一,限行 车号为 n=1 和 m=5+n;n=2,3,4 时计算法方法相同。n=5 时,m=5-n。输出 m、n。 (2)当 n=6,7 时,此时为周末,输出信息都显示车辆自由行驶。将“c ase 6 :”的执 行语句设为空,则程序会执行与紧随其后的 case 语句相同的动作,n=7 时的动作。 (3)当传入 n 的值不是 1~7 时,程序执行 default 之后的语句,提示错误信息。 源文件:MorningGreetings.Ja va public class MorningGreetings { public static void main(String[] args) { MorningGreetings mg = new MorningGreetings(); int day1=1,day2=5,day3=6,day4=7,day5=9; mg.greetings(day1); mg.greetings(day2); mg.greetings(day3); mg.greetings(day4); mg.greetings(day5); } public void greetings(int n){ int m; switch (n) { case 1: { m = n+5; System.out.println("Monday,Driving restrictions is "+n+","+m); break; } case 2: { m = n+5; System.out.println("Tuesday,Driving restrictions is "+n+","+m); break; } case 3: { m = n+5; System.out.println("Wednesday,Driving restrictions is "+n+","+m); break; } case 4: { m = n+5; System.out.println("Thursday,Driving restrictions is "+n+","+m); break; } case 5: { m = n-5; System.out.println("Friday,Driving restrictions is "+n+","+m); break; } case 6: 第 4 章 运算符、表达式与流程控制 75 4 case 7: { System.out.println("Great!Today is freedom!"); break; } default: System.out.println("Not of the week"); } } } (4)执行该程序,显示如下输出: Monday,Driving restrictions is 1,6 Friday,Driving restrictions is 5,0 Great!Today is freedom! Great!Today is freedom! Not of the week 4.4 循环语句 4.3 节中使用分支语句实现了选择结构的程序控制,本节中,将关注三大节本结构之中 的循环结构。实现循环结构的语句是循环语句,循环语句是由循环体和循环终止条件两部 分组成的。其功能是在循环条件满足的情况下,反复执行一段代码,直到不再满足循环条 件为止。循环可分为三类:  条件变为 false 之前重复执行语句。  条件变为 true 之前重复执行语句。  按照指定的次数重复执行语句。 Java 编程语言支持 3 种循环构造类型:for、while 和 do while。3 种循环结构均通过一个 条件表达式控制。在while()和 for()结构中,条件判断均先于语句块执行,所以,有可能语句 块一次也不执行。在 do…while()中,语句块先于条件判断,所以,语句块至少执行一次。 4.4.1 知识准备:for 语句 for 语句的基本格式如下: for ( 初始化语句 ;循环条件; 迭代语句){ 循环体; } 其中:初始化语句是循环的初始状态,循环条件是条件判断的布尔表达式,如果表达 式的值为 true,则执行后面的语句,接下来进行后面迭代语句。如果条件判断表达式第一 次求值就为 false,那么 for 循环不会进行任何的迭代,后面的循环体和迭代语句也不会执 行任何操作。 Android 系统下 Java 编程详解 76 4 一次循环结束后,下一次循环开始前,执行迭代部分的语句,然后判断循环条件表达 式的值,决定是否进行下一次循环。 请看下面 for 循环的例子: int counter = 1; for (int i = 5;i>1;i--) { counter = counter * i; } 上面程序片断的作用是实现一个简单的阶乘运算:n*(n-1)*(n-2)*…*1,在这里,n 的 值为 5,因此,它运算的结果是 5*(5-1)*(5-2)*(5-3)*(5-4)=5*4*3*2*1=120。 上面的例子中,初始化语句只有一个初始化的值,条件判断表达式也只有一个条件, 步进代码也是每次递减一个数字。但是,其实 Java 允许在 for 语句的循环控制的各个部分 放置任何表达式,如下例: for(int b = 0,s = 0,p = 0;(b<10)&&(s<4)&&(p<10);p++){ //代码块 //更新 b 和 s 的值 } 在这个例子中,初始化的变量有 3 个,但是,只能有一个声明语句,所以,如果需要 在初始化表达式中声明多个变量,那么这些变量必须是同一种数据类型的。 for 循环并没有限制它的 for 语句的每一部分都必须提供一个表达式,这 3 个部分其实 都可以为空,此时,是一个无限的循环。这在语法上是没有错误的,只是这种循环在实际 应用中会引起很多的问题,应该避免这种会引起无限循环的 for 语句。 下面来看一个例子,了解一下 for 语句中各部分为空时的控制情况: int sum=0; //注意看 for 语句,它的步进部分是空的 for (int i=1;i<=n;){ sum=sum+i; //将步进放到了 for 程序块中 i++; } 注意 for 语句,它的步进部分是空的,将这个步进运算放到了程序块中。一个无限 for 循环的例子如下: for(;;){ //程序块 } 这种情况一般是要避免的,不应该让程序出现这种无限循环的情况。 在 for 语句内定义的变量,它的作用范围仅限于 for 语句块、表达式及 for 子句的语句 部分。在 for 循环终止后,它们将不可被访问。如果需要在 for 循环外部使用循环计数器的 值,可以将这个变量定义在 for 循环体外,如下: int k; for(k = 0;k<10;k++){ //statements 第 4 章 运算符、表达式与流程控制 77 4 } //此时可以再使用变量 k 另外,如果变量定义在 for 循环体内,则在另外一个循环体中还可以使用相同的变量 名称,如下: for(int k=0;k<10;k++) { … } for(int k = 100;k>0;k--) { … } 上面的代码段是合法的。 4.4.2 任务五: for 循环语句实例 1.任务描述 手机用户设定在一小时内,每隔 10 分钟,手机闹钟响铃一次。 2.技能要点  使用 for 循环语句。  for 循环语句与 if 分支语句结合使用。 3.任务实现过程 (1)定义一个名为 Alarm 的类,该类中使用 for 进行响铃循环输出。for 语句的初始条 件为 1,即从第一分钟开始计时,当时间小于 60 分钟时,判断此时时间是否可以被 10 整 除,如果可以整除,则响铃,不能整除,则时间加 1,重新开始循环。 (2)定义一个 int 类型变量 j 来记录响铃次数。每次响铃之后使 j++。 public class Alarm { public static void main(String[] args) { int j =1; for(int i=1;i<=60;i++){ if(i%10==0){ System.out.println("第"+j+"次响铃"); j++; } } } } (3)程序输出结果: 第 1 次响铃 第 2 次响铃 第 3 次响铃 第 4 次响铃 第 5 次响铃 Android 系统下 Java 编程详解 78 4 第 6 次响铃 4.4.3 知识准备: while 语句 while 循环语句的格式: while (条件判断表达式 ){ 循环体; 迭代运算; } while 语句首先测试条件表达式,这个表达式的值必须是布尔类型的,如果为 true,则 运行代码块中的程序,并且一般需要进行迭代运算,以改变条件表达式中的变量的值,直 到表达式中的值变为 false。 如果刚开始条件表达式就为 false,则 while 循环一次也不会被执行。 来看一个 while 循环的例子: … //while 循环 int counter=0; //初始化一个变量 int i=1; //利用这个变量构成一个条件表达式 while(i<=10) { counter=counte1+i; //将 i 加 1 i=i++; } System.out.println("After the While Loop,the counter is:"+counter); … for 循环和 while 循环是等价的,可以将如下的 for 循环: for(init_expr;test_expr;alter_expr){ statements; } 改写成如下的 while 循环方式: init_expr; while(test_expr){ statements; alter_expr; } 这两种方式是完全可以相互替换的。for 循环和其他两个循环控制语句不同的地方在 于,它可以在控制表达式中定义变量,而 while/do…whi le 不能这样做。 4.4.4 知识准备:do…while 语句 do…while 循环语句的格式如下: [初始化表达式] 第 4 章 运算符、表达式与流程控制 79 4 do{ 循环体; 迭代运算; }while(条件表达式); Do…while 循环类似于 whi le 循环,在 while 后面也得跟一个 boolean 类型的条件表达 式。do…while 循环首先执行里面的代码段,然后再判断条件表达式是否为 true,如果为 true, 则返回到 do 语句来执行,否则,退出整个循环。因为 do…while 循环是先运行里面的代码 块,然后再判断条件,所以,do…whi le 循环至少会执行一次,这是 do…while 循环和 while、 for 循环最大的区别所在。 来看下面这个例子: int counter=0; int j=1; do { counter = counter +j; j=j+1; }while(j<=10); System.out.println("After the Do Loop,the counter is:"+ counter); 比较一下这个例子和上面 while 的例子,这两个例子中的条件表达式都是一样的,但 是,它们运行后得到的结果也是一样的。在whi le 循环中,得出的运算结果是55,而 do…whi le 得出的结果也是 55。但是,如果将各自的条件改成(i<=0)和(j<=0),则 do…while 循环将会 返回一个 1 的结果,而 while 循环却只能返回一个 0 的结果,这是因为 do…while 是“先执 行、后判断”,而 while 却是“先判断、后执行”。 4.4.5 知识准备: break/continue 语句 使用 break 语句可以终止 switch 语句和终止循环的子语句块,甚至是普通的程序块。 关于如何终止 switch 语句,请参考前面的 switch 的内容。 1.break 语句 在循环中,经常需要在某种条件出现时,强行终止循环的运行,而不是等到循环的判 断条件为 false 时,这个时候,可以通过 break 来完成这个功能。 break 语句通常用在循环语句和开关语句中。例如,前面的章节已经提到的开关语句 switch 中,break 可以使程序跳出 switch 而执行 switch 以后的语句,这样可以防止程序进入 死循环而无法退出。当 break 语句用于 do-while、for、whi le 循环语句中时,可使程序终止 循环而执行循环 下面来看一个 break 用于循环语句中的例子: int sum1 = 0,n=10; for (int i=1;i<=n;i++){ sum1=sum1+i; if(i%2==0)break; } Android 系统下 Java 编程详解 80 4 以上例子中,如果 i 能够被 2 整除,就跳出 for 循环。因此,实际上,for 循环只能循 环两次,得到的 sum1 的值是 3。 从上面的例子中可知 break 可以跳出循环体,但是如果是多层循环嵌套的时候,break 的作用就有局限性了,因为它只能跳出单层循环。如果希望可以跳出指定层数的循环到这 里对于有 C++或其他编程经验的读者,可能会想到了 goto 语句。goto 语句是无条件转移语 句,break/continue 语句可以理解为弱化了的 goto 语句。但是,在结构化程序设计中不主张 使用 goto 语句,以免造成程序流程的混乱。在 Java 中,goto 语句虽然是保留字,但并没有 使用它,而是提供了一种类似 goto 功能的实现方法,就是使用 break/ continue 与标签结合。 在本质上而言,它和 goto 语句的跳跃是不同的,它是一种循环中断的方式而已。它和 goto 语句的相同点在于,它们都使用了标签(labe l)。 所谓标签(labe l),就是后面跟了一个冒号“:”的标识符,例如: oneLabel: 从语法上看,在 Java 程序中,标签可以放在任意的地方,但是,一般而言,标签只有 放在循环语句之前,才能真正起到应有的作用,如下: LableOne: 循环 { … } 来看一个用在嵌套循环中的和标签结合的 break 例子: outer: for(int i = 0;i<10;i++) { System.out.println("Outer loop:"); inner: while(true) { int k = System.in.read(); System.out.println("Inner Loop:"+k); if(k=='b') break inner; if(k=='q') break outer; } } 在这个例子中,从控制台接收一个输入,如果输入 b,则退出内层的 while 循环,如果 输入 q,则退出外层的循环(也就是终止整个循环)。 另外,如果需要终止普通的语句块(既不是 switch 也不是循环语句),则必须使用标签: Label1: { Label2:{ Lable 3:{ … } } } 第 4 章 运算符、表达式与流程控制 81 4 2.continue 语句 continue 语句用来略过循环中剩下的语句,停止当前迭代,重新开始新的循环,这和 break 语句的完全跳出循环是不一样的。 continue 仅仅出现在 while/do…while/for 语句的子语句块中。也可以使用和标签结合的 方式来选择需要终止的嵌套循环的层级。 下面来看一个例子: int sum1=0; int sum2=0; //Continue for (int j=1;j<=10;j++) { if(j%2==0)continue; sum2=sum2+j; } System.out.println(sum2); 在这个例子中,如果在 j 可以被 2 整除,则不进行后面的相加操作,而重新返回到循 环的开头,判断 j=3 时是否满足循环条件。因此,它运算后的值为 25。 问:continue 和 break 的区别是什么呢? 答:如果遇到 continue,程序将立即返回循环的入口,继续执行;如果遇到 break,程序将立即 跳出循环,从循环后的第一条语句开始执行。自己多编几个小程序去体会吧! 4.4.6 技能拓展任务:continue 结合标签的使用 1.任务描述 使用 break/continue 语句和标签结合,实现在短信中查找关键字的功能。 2.技能要点  break/continue 语句跳出循环。  标签和 continue 语句的结合使用。 3.任务实现过程 (1)定义一个名为 SearchKeyWord 的类,这个程序的作用是从字符串 msg 中搜索指 定的子串 keyword,从要搜索的字符串 keyword(程序中设定为“key”)第一个子符开始去 匹配 msg 的第一个字符,如果第一个字符都不匹配,就不用再比较第二个字符了(利用 continue)。如果第一个字符匹配,则比较第二个字符,如果第二个字符不匹配,则不用再 往下比较,否则,往下比较第三个字符,依此类推。如果找到完全匹配的子字符串,则退 出整个循环(break)并且返回 true。然后根据是否返回 true 打印出“Found it”或“ Didn't find it”。 public class SearchKeyword { public static void main(String[] args) { Android 系统下 Java 编程详解 82 4 String msg = "Look for a keyword in msg"; String keyword = "key"; boolean foundIt = false; int max = msg.length() - keyword.length(); test: for (int i = 0; i <= max; i++) { int n = keyword.length(); int j = i; int k = 0; while (n-- != 0) { if (msg.charAt(j++) != keyword.charAt(k++)) { // 跳出的本次循环是 for 循环,而不是 while 循环 continue test; } } foundIt = true; // 跳出整个循环 break test; } System.out.println(foundIt ? "Found it" : "Didn't find it"); } } (2)运行程序。如果 substring 的值为“key”(如程序中所示),则会在控制台上打印 出“Found it”,如果修改 keyword 的值为“123”,则会打印出“Didn't find it”。 4.5 本章小结 本章介绍了运算符的基本概念;着重介绍了算术运算符,递增递减运算符,关系和布 尔运算符,位运算符和赋值运算符等主要运算符,通过实例使读者了解这些运算符的使用 方法;并介绍了各种运算符的优先级。描述了表达式的概念和表达式中运算符的结合性与 优先级。分析应用于 Java 流程控制中的分支语句和循环语句,通过实际代码使读者了解了 if-else、switch、for、while 等语句的用法。通过布置任务使读者掌握技巧,学会在编程中 使用这些语句。 课后练习题 一、选择题 1.阅读下面代码: boolean var1=false; boolean var2=true; boolean result1=var1&&var2; boolean result2=!var2; boolean result2=(var1&var2)&(!var2); 第 4 章 运算符、表达式与流程控制 83 4 System.out.println("result1="+result1+";result2="+result2); 以上代码运行的结果是( )。 A.result1=false;result2=false B.result1=true;result2=true C.result1=true;result2=false D.result1=false;result2=true 2.下列代码的执行结果是( )。 public class Test1{ public static void main(String args[]){ float t=6.5f; int q=5; System.out.println((t++)*(--q)); } } A.30 B.30.0 C.26.0 D.26 3.int j=2; switch(j){ case 2: System.out.println("北京"); case 2+1: System.out.println("上海"); break; default: System.out.println("南京"); break; } 运行以上代码输出结果为( )。 A.北京 B.北京 C.北京 D.编译错误 上海 上海 南京 4.以下选项中循环结构合法的是( )。 A. B. C. D. 5.下列程序段的输出结果是( )。 int x=5,y=3; int a; System.out.println(a=x>y?x:y) ; while (int i<5){ i++; System.out.println("i is "+i); } int j=3; while(j){ System.out.println(j); } int j=0; for(int k=0;j+k!=10;j++,k++){ System.out.println("j is"+j+"k is"+k); } int j=0; do{ System.out.println("j is"+j++); if(j==3){ loop;} }while(j<10); Android 系统下 Java 编程详解 84 4 A.3 B.5 C.8 D.2 二、编程题 1.编写程序实现比较两个数的大小,输出比较结果,比如输入 5,3,输出 5 比 3 大。 2.编写程序打印 100 以内的 3 的倍数,3,6,9,…,99。 3.编写一个程序为一周中的每天打印一句问候语(要求用 switch 语句)。 第 5 章 数组 本章介绍了数组的基本概念;详细说明了一维数组的声明、创建和初始化方法;一维 数组的内存空间和数据复制;数据结构的基础知识和如何使用一维数组实现数据结构和排 序算法;最后简单介绍了多维数组的相关知识。 5.1 数组基本概念 数组是编程语言中非常常见的一种数据结构,用于储存一组有序数据的集合。数组中 的每一个元素都属于同一数据类型,用一个统一的数组名和下标唯一地确定数组中的元素。 可以通过整型索引访问数组中的每一个值。 可以通过数组来保存任何相同数据类型的数据:简单类型或者引用类型。数组本身属 于引用类型。 数组被创建以后,它的大小是不能被改变的,但是,数组中的各个数组元素的值是可 以被改变的。 5.2 一维数组 5.2.1 知识准备:一维数组的声明 定义一个一维数组很简单,可以使用下面两种方式: 数据类型[] 数组名 或 数据类型 数组名[] 通过上面两种方式中的一种,仅仅声明了一个数组变量,并没有创建一个真正的数组, 也无法确定数组长度。这时候数组还不能被访问。上面两种声明数组的方式,可以任选一 种,它们之间并没有优劣之分。一般来说,选择第一种方式比第二种方式要直观一些。 Android 系统下 Java 编程详解 86 4 下面这段代码声明了不同数据类型的数组: //int 数组 int[] intArray; //字符型数组 char[] charArray; //布尔型数组 boolean[] booleanArray; //引用类型数组(字符串数组) String[] stringArray; //对象数组 MSG[] MsgArray; 5.2.2 知识准备:一维数组的创建 在声明了数组之后,就要具体规定数组的大小,给数组分配内存空间。可以通过 new 操 作符来显示创建一个数组: type[] arr_name; arr_name = new type[length]; 其中 type 是数组元素类型;arr_name 为自定义的数组名,命名规则和变量名相同,遵 循标识符定名规则;length 是一个常量表达式,表示数组元素个数,即该数组的长度。 注意: 数组名后是用方括弧[]括起来的常量表达式,不能用圆括弧(),如 inta(10);这样的用法是错误的。 数组创建示例: int[] a; a = new int[10]; 这条语句创建了一个可以存储 10 个整型数据的数组,也就是分配了 10 个可以被 int 类型数据占用的内存空间。注意数组的索引从 0 开始,本例为 a[0]~a[9]。通过数组名和数 组索引是访问数组元素的唯一方式,例如,要访问数组 a 的第一个元素,可以通过 a[0]来 访问,要访问第 10 个元素,可以通过 a[9]来访问,要想访问第 n 个元素,可以通过 a[n-1] 来访问。 为了方便操作数组,Java 语法中提供了获得数组长度的语法格式。对于一个已经创建 完成的数组,获得该数组长度的语法格式为:“数组名.length”。例如上面的例子,可以用 a.length 来获得它的长度为 10。 当然,也可以更简便地将数组的声明和数组大小的分配放到一起来完成,如下: type[] arr_name = new type[length]; 例如,上面的数组 a 的声明和数组大小定义可以合并为如下的语句来完成: int[] a = new int[10]; 如果需要声明一个存放引用类型数据的数组,使用的方法也是一样的: 第 5 章 数组 87 4 String[] s = new String[50]; 5.2.3 任务一:一维数组的声明与创建实例 1.任务描述 声明并创建一个数组用于存放开机密码,根据数组长度提示用户需要输入的密码位数。 2.技能要点  使用 new 操作符创建数组。  获取数组长度。 3.任务实现过程 (1)首先声明了一个 int 类型的数组 numberArray,然后,通过 new 操作符来给这个 数组设置了它的长度,以及给这个数组分配了存放 6 个 int 类型数据的内存空间。通过 numberArray.length 可以得到这个数组的长度,在这里为 6。 源文件:NumberLength.java public class NumberLength { public static void main(String[] args) { int[] numberArray; numberArray = new int[6]; System.out.println("请输入长度为" + numberArray.length+"位开机密码"); } } (2)编译并运行这个程序,将在控制台上得到如下的输出结果: 请输入长度为 6 位开机密码 5.2.4 知识准备:一维数组的初始化 Java 数组的初始化主要分为两种:静态初始化和动态初始化。在了解这两种初始化方 式之前,先看一下 Java 提供的数组默认初始化。 Java 为了保证安全性,防止内存缺失,为已创建的数组提供了默认初始化机制。在创 建成功一个数组后,将完成如下 3 个动作:  创建一个数组对象。  在内存中给数组分配存储空间。  给数组的元素初始化一个相应的数据类型的默认值。比如,将 int 类型的数组各个 元素初始化为 0,引用类型是 null 等。 将任务一中的程序稍做修改,让它打印出数组第一个元素的默认值: 源文件:NumberLength.java Android 系统下 Java 编程详解 88 4 public class NumberLength { public static void main(String[] args) { int[] numberArray; numberArray = new int[6]; System.out.println("请输入长度为" + numberArray.length+"位开机密码"); System.out.println("第一位密码默认初始化值是:" + numberArray[0]); } } 在这个程序中,首先声明了一个 int 类型的数组,然后,利用 new 操作符创建了一个 长度为 6 的数组,它将给这个数组分配存储空间并且初始化这些数组元素,在这里将给这 些数组元素一个值 0;最后,试图向控制台打印出这个数组第一个元素的值,因为 Java 中 的数组索引(下标)是从 0 开始的,所以,第一个数组元素对应的索引为 0,所以可以通 过 numberArray [0]的方式来得到数组的第一个元素的值。 编译并运行这个程序,将打印出如下的信息: 请输入长度为 6 位开机密码 第一位密码默认初始化值是:0 其实,此时在这个数组中的任何一个元素的值都是 0。读者可以自己修改数组的索引 来获得不同的数组元素,注意这个数组的索引取值在 0~10 之间。 但是,通常情况下,定义一个数组并不是想用系统自动给的默认值,而是自己给数组 的值。这时,就需要对数组进行初始化操作。也就是说,给数组的各个元素指定对象的值。 下面来看静态和动态这两种初始化方式的方法及它们的区别。 1)静态初始化 所谓静态初始化,就是在定义数组的时候就对数组进行初始化,如下: int k[] = {1,3,5,7,9}; 在这个例子中,定义了一个 int 类型的数组 k,并且用大括号中的数据对这个数组进行 了初始化,各个数据之间用“,”分割开。此时数组的大小由大括号中的用于初始化数组的 元素个数决定,注意不要在数组声明中指定数组的大小,否则将会引起错误。在这个例子 中,将数组声明、数组的创建及数组的初始化都放在了同一条语句中,在这边并没有使用 到 new 来创建这个数组。其等同于 int k[] =new int[5] k[] = {1,3,5,7,9}; 来看一个静态初始化的例子: 源文件:StaticOpenNunber.java public class StaticOpenNunber { public static void main(String[] args) { int numberArray[] = { 6, 5, 4, 3, 2, 1 }; System.out.println("默认开机密码为"); for (int i = 0; i < numberArray.length; i++) { System.out.println(numberArray[i]); 第 5 章 数组 89 4 } } } 在这个例子中,利用静态方式对数组进行初始化。这个数组的长度是数组中的元素的 个数:6。然后用一个 for 循环将数组的各个元素取出来打印到控制台。程序执行的结果如 下: 默认开机密码为 654321 注意在这边用到了前面所论述的用于获得数组长度的方法:使用数组的 length 属性, 用它来获得数组的长度。 在 Java 中,还可以利用静态初始化的方法来初始化一个匿名的数组,方法如下: new type[] {… } 例如: new String[] {"abc","cde","efg"} 可以通过这种方法来重新初始化一个数组,例如,有一个 String 类型的数组,它通过 下面的静态方式被初始化: String[] s = {"tom","jerry","mickey"}; 此时,可以对 s 这个数组变量进行重新初始化,如下: s = new String[]{"abc","cde","efg"}; 这条语句等同于下面的两条语句: String[] temp = {"abc","cde","efg"}; s = temp; 可以只给一部分元素赋值。例如: int a[10]={0,1,2,3,4}; 只给前 5 个元素赋值,后 5 个元素为 0。 初始化之后:a[0]=0,a[1]=1,a[2]=2,a[3]=3,a[4]=4,a[5]=0,…,a[8]=0,a[9]=0。 对全部数组元素赋初值时,可以不指定数组长度。例如: int a[]={1,2,3,4,5}; 上面的写法中,{ }中只有 5 个数,系统会据此自动定义数组的长度为 5。初始化之后: a[0]=1,a[1]=2,a[2]=3,a[4]=4,a[5]=5。如果被定义的数组长度与提供初值的个数不同,则数组 长度不能省略。例如: 想定义数组长度为 10,就不能省略数组长度的定义。而必须写成: int a[10]={ 1,2,3,4,5}; 只初始化前面 5 个元素,后 5 个元素为 0。不能写成: int a[ ]={1,2,3,4,5}; 2)动态初始化 Android 系统下 Java 编程详解 90 4 所谓动态初始化,就是将数组的定义和空间分配与给数组元素赋值分开。创建时系统 进行数组的默认初始化。例如,对数组中的元素一个个地分别指定它们各自对应的值,这 些赋值可以在程序的任意位置: char ch = new char[3]; ch[0] = a; ch[1] = b; ch[2] = c; 或者用一个循环来对一个数组一次赋值,例如: char[] ch; ch = new char[26]; for ( int i=0; i<26; i++ ) { ch[i] = (char) ('A' + i); } 5.2.5 知识准备:引用数组元素 定义并用运算符 new 为之分配空间后,才可以引用数组中的每个元素,数组元素的引 用方式为: 数组名[数组索引] 数组索引可以是一个整数或者一个整数表达式。依然要强调注意数组的索引从 0 开始 到数组长度减 1。比如,数组长度为 n,则索引的范围为 0~(n-1)。 在使用数组名加数组索引的方式来取得数组的元素时,注意元素的索引必须小于数组 的长度,也就是只能在 0~(n-1) 之间,否则会引起数组越界的异常: java.lang.ArrayIndexOutOfBoundsException(关于异常将在后面章节中学习)。可以使用数 组的一个属性 length 来获得数组的长度。 问:数组类型的变量也可以像基本类型那样整体引用吗? 答:这点正要提醒大家注意! Java 语言规定只能逐个引用数组元素,不能一次引用整个数组。 5.2.6 任务二:引用数组实例,对数组排序 1.任务描述 声明并初始化一个字符类型数组,对数组元素进行由小到大的排序,并输出数组元素。 2.技能要点  掌握数组的静态和动态初始化。  掌握根据索引号引用数组元素。 第 5 章 数组 91 4 3.任务实现过程 对于数组的排序,可以像前面的一维数组应用的例子一样,自己写一个算法来对数组 进行排序。但是,也可以利用 java.ut il 包中的 Arrays 类的一个静态方法 sort 来进行数组元 素的排序。这个方法有一个数组类型参数,用来接收需要进行排序的数组。 源文件:ArraysSort.java import java.util.Arrays; public class ArraySort { public static void main(String[] args) { char[] a = { 'd', 'c', 'b', 'a', 'f', 'e'}; System.out.println("Before Sorting:"); for (int i = 0; i < a.length; i++) { System.out.print("a[" + i + "]=" + a[i] + " "); } System.out.println(""); Arrays.sort(a); System.out.println("After Sorting:"); for (int i = 0; i < a.length; i++) { System.out.print("a[" + i + "]=" + a[i] + " "); } } } 运行这个程序后,在控制台上将打印出如下的信息: Before Sorting: a[0]=d a[1]=c a[2]=b a[3]=a a[4]=f a[5]=e After Sorting: a[0]=a a[1]=b a[2]=c a[3]=d a[4]=e a[5]=f 可以看出,通过 Arrays 的静态方法 sort(),已经将数组 a 中的所有元素按照从小到大排 好了顺序。 5.2.7 知识准备:简单数据类型数组的内存空间 通过以上的学习,已经知道了数组初始化的一些步骤,下面来了解一下数组的内存空 间和内存分配。一般 Java 在内存分配时会涉及以下区域。  寄存器:在程序中是无法控制的。  栈:存放基本类型和对象的引用,但对象本身不存放在栈中,而是存放在堆中。  堆:存放用 new 产生的数据。  静态域:存放在对象中用 static 定义的静态成员。  常量池:存放常量。  非 RAM 存储:硬盘等永久存储空间。 首先来看简单数据类型数组从定义到初始化的内存变化过程。 Android 系统下 Java 编程详解 92 4 1.简单数据类型数组的声明 在声明数组的时候,系统会给这个数组分配用于存放这个数组的内存空间:它会在堆 (He ap)内存空间中给数组分配一个空间用于存放数组引用变量。在栈内分配空间存入数 组对象的引用,如图 5-1 所示。 图 5-1 int 类型数组的定义 2.简单数据类型数组的创建 在创建简单数据类型的数组的时候,系统会分配合适的堆空间用来存放该种数据类型 数据的内存空间,并且将这个数组的各个元素赋一个和数组类型匹配的初值。 具体到这个 int 类型数组的例子,所有的数组元素都会被初始化成 0,如图 5-2 所示。 图 5-2 int 类型数组的创建 3.简单数据类型数组的初始化 当对数组进行初始化时,会将值赋给对应的各个数组元素。比如,通过下面的一个循 环对这个 int 类型的数组进行初始化: for(int i=0;i<10;i++){ arr[i]=i+1; } 则会将 1~10 的值赋给这个长度为 10 的 int 类型数组,如图 5-3 所示。 第 5 章 数组 93 4 图 5-3 int 类型数组的初始化 4.引用数据类型数组的内存空间 在介绍完简单数据类型数组的初始化过程后,再来看引用类型数组的初始化过程中的 内存变化。 5.引用数据类型数组的声明 引用类型数组的定义和简单数据类型数组的定义基本相同。 图 5-4 所示为执行下面操作后的结果: String[] arr; 图 5-4 引用数据类型数组的定义 6.引用数据类型数组的创建 引用数据类型数组在创建的时候也是首先给数组元素分配内存空间,然后赋给这些数 组元素一个默认的初始值 null。 图 5-5 所示为执行下面操作后的结果: arr = new String[10]; Android 系统下 Java 编程详解 94 4 图 5-5 引用数据类型数组的创建 7.引用数据类型数组的初始化 在进行引用数据类型数组的初始化的时候,和简单数据类型数组的初始化有些不同, 因为数组本身是引用类型,而现在数组元素也是引用类型,所以这个时候需要给数组元素 所引用的对象也分配内存空间。 图 5-6 所示为执行下列操作的结果: arr[0]=new String("one"); arr[1]=new String("two"); arr[2]=new String("three"); arr[3]=new String("four"); arr[4]=new String("five"); arr[5]=new String("six"); arr[6]=new String("seven"); arr[7]=new String("eight"); arr[8]=new String("night"); arr[9]=new String("ten"); 图 5-6 引用类型数组的初始化 5.2.8 技能拓展任务:数组复制 1.任务描述 声明一个数组变量存放一串电话号码,将此数组变量复制给另一个数组变量以做备份。 备份后修改任何一个号码,另一个号码也同时被修改。 第 5 章 数组 95 4 2.技能要点  了解数组对象创建后在内存中的存放机制。  编写算法进行数据复制。 3.任务实现过程 要求体现出复制之后的数组变量和原数组变量是对同一数组对象的引用。 这两个数组变量均指向同一个数组;通过任何一个数组变量进行操作,均会对另一个 数组变量中的数组产生影响。 可以将一个数组变量复制给另一个数组变量,这个时候,这两个数组变量均指向同一 个数组。通过任何一个数组变量进行操作,均会对另一个数组变量中的数组产生影响。 请看下面这个例子: 源文件:NumberBackup.java public class NumberBackup { public static void main(String[] args) { int[] a = { 6, 7, 3, 4, 1, 9, 0, 5 }; int[] b; b = a; System.out.println("Before Backup:"); for (int i = 0; i < a.length; i++) { System.out.print("a[" + i + "]=" + a[i] + " "); } System.out.println("\n"+"Backup a to b"); for (int i = 0; i < b.length; i++) { System.out.print("b[" + i + "]=" + b[i] + " "); } b[3] = 8; System.out.println("\n"+"After Modifying b :"); for (int i = 0; i < b.length; i++) { System.out.print("b[" + i + "]=" + b[i] + " "); } System.out.println("\n"); for (int i = 0; i < a.length; i++) { System.out.print("a[" + i + "]=" + a[i] + " "); } } } 这个程序首先初始化了一个 int 类型的数组 a,然后,将这个数组变量复制给另一个 int 类型的数组 b,这个时候,数组变量 a 和 b 均指向同一个数组,如果通过数组变量 b 来对 数组的内容进行修改,也会反映到数组变量 a 中。 执行这个程序后的结果是: Before Backup: a[0]=6 a[1]=7 a[2]=3 a[3]=4 a[4]=1 a[5]=9 Backup a to b b[0]=6 b[1]=7 b[2]=3 b[3]=4 b[4]=1 b[5]=9 Android 系统下 Java 编程详解 96 4 After Modifying b : b[0]=6 b[1]=7 b[2]=3 b[3]=8 b[4]=1 b[5]=9 a[0]=6 a[1]=7 a[2]=3 a[3]=8 a[4]=1 a[5]=9 注意看 a[3]数组元素的值的变化。图 5-7 揭示了以上程序的执行过程。 图 5-7 通过数组变量实现的数组复制 5.3 数据结构及数组应用 5.3.1 知识准备:堆栈 堆栈(Stack),也称为“栈”,是一种简单的、使用广泛的数据结构。堆栈是一种特殊 的序列,这种序列只在其中的一头进行数据的插入和删除操作,通常将这头称为 “栈顶”。 相应地,另一头称为栈底。不含任何数据(元素)的堆栈称为空栈。图 5-8 是堆栈的示意 图。 图 5-8 堆栈示意图 在这幅示意图中,可以看出堆栈的一些特点:栈可以看做先进后出(FILO)的线性表。 或后进先出(LIFO)的线性表,表尾进行插入和删除的操作。也就是说,在堆栈中,堆栈 元素(数据)的操作都是遵循“后进先出(Last In First Out,LIFO)”的原则进行的。在实 际开发中,只要问题满足“后进先出”原则,都可以使用堆栈来解决。 在 Java 中,如果要定义一个类来表示堆栈,通常需要让这个类能够实现下面的功能:  入栈:将元素往堆栈中添加,这个动作也经常被称做“压栈(Push)”。 出栈(pop) 入栈( p ush ) 第 5 章 数组 97 4  出栈:将元素从堆栈中取出来,这个动作也经常被称做“弹栈(Pop)”。  清空:将堆栈中的所有元素都清空。  返回顶端元素:获得最顶端的元素,但不将它从堆栈中删除。 5.3.2 任务三:使用数组实现堆栈 1.任务描述 使用数组实现利用堆栈记录并查询手机通话记录。 2.技能要点  掌握堆栈先进后出的结构特点。  使用数组实现堆栈的入栈出栈操作。 3.任务实现过程 (1)编写一个类,在类中定义一个数组,用来记录手机通话。编写出栈入栈的方法来 操作数组。 源文件:MyCall.java class MyCall { private int capacity = 100; private String[] items; private int top = 0; // 不带参数构造器 public MyCall() { this(100); } // 带参数构造器,参数为堆栈大小 public MyCall(int cap) { this.capacity = cap; items = new String[cap]; } // 入栈 public void push(String s) { top++; items[top] = s; } // 出栈 public void pop() { items[top] = null; top--; } // 清空堆栈 public void empty() { top = 0; } // 取出最顶端的堆栈元素 Android 系统下 Java 编程详解 98 4 public String top() { return items[top]; } // 获得堆栈元素个数 public int size() { return top; } } (2)然后编写一个类,在这个类中,使用上一个类中堆栈的相关方法来操作堆栈。 源文件:CallHistory.java public class CallHistory { public static void main(String[] args) { MyCall mc = new MyCall(5); mc.push("Mia 16:12"); mc.push("Kathy 18:02"); mc.push("Alex 19:35"); System.out.println("I have "+mc.size()+" calls today"); System.out.println("The latest one is: "+mc.top()); } } (3)编译并运行这个程序,将得到如下的输出: I have 3 calls today The latest one is: Alex 19:35 可以看到,输出结果是将最后入栈的记录“Alex 19:35”最先弹出显示。 提示: 上面的 Stack 例子中,其实不是很完美,至少有两个地方是 Stack 没有考虑到的:当堆栈中的数 据已经满的时候,如果再试图进行压栈操作,将会发生错误;如堆栈中没有数据,那么,如果 此时试图进行弹栈操作,也将会发生错误。一个可行的实现方式是在压栈或者弹栈操作的时候, 对栈内的空间进行判断,在发生上述问题的时候抛出一个 RuntimeException 类型的异常(应该 自己定义一个 RuntimeException 子类)。关于异常,请参考后续章节的内容。 5.3.3 知识准备:队列 队列 ( Queue)是另外一种常用的数据结构。它也是一种特殊的线性表,对这种线性表, 删除操作只在表头(称为队头)进行,插入操作只在表尾(称为队尾)进行。队列的修改 是按先进先出的原则进行的,所以队列又称为先进先出(First In First Out)表,简称 FIFO 表。这种特点和队列的名字是很相符的,就像生活中的排队一样:排在队伍最前面的人最 先得到相关的服务,也最先从队伍中出来。与堆栈中的数据操作总是在栈顶进行不同,在 队列中,它的两头都能够进行操作,在队头(front)删除元素,而在队尾(rear)加入元素, 如图 5-9 所示。 第 5 章 数组 99 4 图 5-9 队列示意图 一个典型的队列可以实现以下功能。  往队列的队尾插入一个元素(enqueue)。  将队列的队头元素删除(dequeue)。  清空队列(makeEmpty)。  判断队列是否为空(isEmpty)。 5.3.4 任务四:使用数组实现队列 1.任务描述 使用数组实现利用队列记录并查询手机通话记录。 2.技能要点  掌握队列先进先出的结构特点。  使用数组实现队列入列、出列等基本操作。 3.任务实现过程 (1)编写一个类,在类中定义一个数组,用来记录手机通话。使用队列的数据结构操 作数组,并编写入列、出列,判断队列是否为空,判断队列是否已满的操作。 源文件:CallHistory2.java class MyQueue { // 队头和队尾索引 private int front = -1, rear = -1; // 定义一个数组模拟队列 private String[] queue; // 构造器,参数 maxElements 为队列长度 public MyQueue(int maxElements) { queue = new String[maxElements]; } // 入列 public void enqueue(String e) { queue[++rear] = e; } Android 系统下 Java 编程详解 100 4 // 判断队列是否为空 public boolean isEmpty() { return front == rear; } // 判断队列是否已满 public boolean isFull() { return rear == queue.length - 1; } // 出列 public String dequeue() { return queue[++front]; } } 在这个类中,定义了一个数组用于模拟队列,并且定义了 enqueue()用于入列,dequeue() 方法用于出列,另外,还有两个方法 isFull()和 isEmpty()分别用于判断队列是否已经满了或 者队列是否为空。 (2)编写一个类,在这个类中,使用上一个类中队列的相关方法来操作队列。 源文件:CallHistory2.java public class CallHistory2 { public static void main(String[] args) { MyQueue queue = new MyQueue(20); queue.enqueue("Mia 16:12"); queue.enqueue("Kathy 18:02"); queue.enqueue("Alex 19:35"); System.out.println(queue.dequeue()); System.out.println(queue.dequeue()); System.out.println(queue.dequeue()); } } 编译并运行这个程序,可以得到如下的输出: Mia 16:12 Kathy 18:02 Alex 19:35 可以看到,使用队列的数据结构是,先入列的记录首先输出,最后入列的记录最后输出。 5.3.5 知识准备:排序算法 在编写程序的时候,经常会碰到算法问题。所谓算法(Algor ithm),就是在有限步骤 内求解某一问题所使用的一组定义明确的规则。算法的好坏直接影响到程序的运行效率, 因此,选择一个好的算法对于编写高效的程序是至关重要的。本节以编程中常用的几种排 序算法为例,来初步讲解算法,并结合数组的相关知识,加深对数组应用的理解。 第 5 章 数组 101 4 1.冒泡排序法 对几个无序的数字进行排序,最常用的方法是所谓的冒泡排序法。算法思想是每次比 较两个相邻的数,将较小的放到前面,较大的放到后面,这样就可以将这些数中最大的找 出来放到最后,然后比较剩下的数,再在这些数中找出最大的,直到所有的数字按照从小 到大的顺序排列。 可以用一个一维数组来存放这些需要进行排序的数字,然后对这个一维数组使用上面 的算法对它的数组元素进行冒泡排序。下面是一种实现方式。 源文件:BubbleSort.java public class BubbleSort { static String sortArray(int before[]) { String result = ""; for (int i = 0; i < before.length; i++) { result += before[i] + " "; } return result; } static int[] bubbleSort(int before[]) { int t; for (int i = 0; i < before.length; i++) { for (int j = 0; j < before.length - i - 1; j++) { if (before[j] > before[j + 1]) { t = before[j]; before[j] = before[j + 1]; before[j + 1] = t; } } } return before; } public static void main(String args[]) { int a[] = { 12, 43, 23, 56, 8, 22, 65, 87 }; System.out.println("Before sorting:" + sortArray(a)); a = bubbleSort(a); System.out.println("After Sorting:" + sortArray(a)); } } 通过类 BubbleSort 中的 bubbleSort()方法,可以对数组元素实现冒泡排序。并返回一个 排好序的新的数组。 2.选择排序 选择排序的基本思想是:每一趟从待排序的数据元素中选出最小(或最大)的一个元 素,顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。选择排序相对于 冒泡来说,它不是每次发现逆序都交换。选择排序是不稳定的排序方法。 Android 系统下 Java 编程详解 102 4 假设有一个数组如下: 初始关键字 [49 38 65 97 76 13 27 49] 第一趟排序后 13 [38 65 97 76 49 27 49] 第二趟排序后 13 27 [65 97 76 49 38 49] 第三趟排序后 13 27 38 [97 76 49 65 49] 第四趟排序后 13 27 38 49 [76 97 65 49 ] 第五趟排序后 13 27 38 49 49 [97 65 76] 第六趟排序后 13 27 38 49 49 65 [97 76] 第七趟排序后 13 27 38 49 49 65 76 [97] 最后排序结果为13 27 38 49 49 65 76 97,下面来看一个使用Java 实现快速排序算法的例子: 源文件:SelectionSort.java public class SelectionSort { // 选择排序方法 public static void selectionSort(int[] number) { for (int i = 0; i < number.length - 1; i++) { int m = i; for (int j = i + 1; j < number.length; j++) { if (number[j] < number[m]) m = j; } if (i != m) swap(number, i, m); } } // 用于交换数组中的索引为 i、j 的元素 private static void swap(int[] number, int i, int j) { int t; t = number[i]; number[i] = number[j]; number[j] = t; } public static void main(String[] args) { // 定义一个数组 int[] num = { 2, 1, 5, 876, 12, 56 }; // 排序 selectionSort(num); for (int i = 0; i < num.length; i++) { System.out.println(num[i]); } } } 3.快速排序 快速排序是对冒泡排序算法的改进,是目前使用最广泛的排序算法。快速排序的基本 思路是:将一个大数组的排序问题,分解成两个小的数组的排序。而每一个小的数组又可 以继续分解成更小的两个数组,这样这个数组的排序方式可以一直的递归分解下去,直到 第 5 章 数组 103 4 数组的大小最大为 2。在第一次划分的时候,选择一个基准元素,然后将它分成左右两个 无序的数组,并且,使得左边的所有数组元素都小于等于基准元素,而右边的所有数组元 素都大于等于基准元素,然后分别对左边和右边的数组递归做同样的操作。在这个排序方 法中,算法效率的关键在于如何分解数组,也就是如何确定数组的基准元素。通常情况下 可以选择数组最左边的元素作为基准元素,或者选择数组中间的元素作为基准元素。下面 来看一个例子: 源文件:QuickSort.java public class QuickSort { // 排序方法,接受一个 int[]参数,将会调用快速排序方法进行排序 public static void sort(int[] number) { quickSort(number, 0, number.length - 1); } // 快速排序方法 private static void quickSort(int[] number, int left, int right) { if (left < right) { int s = number[left]; int i = left; int j = right + 1; while (true) { // 向右找大于 s 的数的索引 while (i + 1 < number.length && number[++i] < s) ; // 向左找小于 s 的数的索引 while (j - 1 > -1 && number[--j] > s) ; // 如果 i>=j,退出循环 if (i >= j) break; // 否则交换索引 i 和 j 的元素 swap(number, i, j); } number[left] = number[j]; number[j] = s; // 对左边进行递归 quickSort(number, left, j - 1); // 对右边进行递归 quickSort(number, j + 1, right); } } // 交换数组 number 中的索引为 i、j 的元素 private static void swap(int[] number, int i, int j) { int t; t = number[i]; number[i] = number[j]; number[j] = t; } Android 系统下 Java 编程详解 104 4 public static void main(String[] args) { int[] num = { 34, 1, 23, 345, 12, 546, 131, 54, 78, 6543, 321, 85, 1234, 7, 76, 234 }; sort(num); for (int i = 0; i < num.length; i++) { System.out.println(num[i]); } } } 提示: 快速排序采用了分治法的基本思想,它将原问题分解为若干个规模更小但结构与原问题相似的 子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。 5.3.6 技能拓展任务:排序算法实例 1.任务描述 使用上述一种排序算法,在一个数组中存放近十天内手机上网流量使用情况,并按从 大到小的顺序输出。 2.技能要点  掌握使用数组实现各种排序算法。  灵活运用各种排序算法解决实际问题。 3.任务实现过程 (1)使用选择排序算法。 public class GPRSFlow { // 选择排序方法 public static void flowSort(double[] number) { for (int i = 0; i < number.length - 1; i++) { int m = i; for (int j = i + 1; j < number.length; j++) { if (number[j] > number[m]) m = j; } if (i != m) swap(number, i, m); } } // 用于交换数组中的索引为 i、j 的元素 private static void swap(double[] number, int i, int j) { double t; t = number[i]; number[i] = number[j]; number[j] = t; 第 5 章 数组 105 4 } public static void main(String[] args) { // 定义一个数组 double[] num = { 5.2,7.0 , 2.1, 0.9, 12.8, 13.0,3.4,4.4,10.0,15.0 }; // 排序 flowSort(num); for (int i = 0; i < num.length; i++) { System.out.println(num[i]); } } } (2)运行程序,得到如下输出: 15.0 13.0 12.8 10.0 7.0 5.2 4.4 3.4 2.1 0.9 可以看到,只是将上一节中的选择排序算法进行了小幅修改,改变了数据类型和输出 顺序。所以上面几个排序算法应该熟记。 5.4 多维数组 Java 中支持多维数组,也就是“数组的数组”。多维数组每一维(最里面的一维不算), 本身是一个一般数组,只不过里面的每一个元素的类型,是一个维度比它小一维的另一个 数组。 5.4.1 知识准备:多维数组的声明 多维数组的声明是通过每一维一组方括号的方式来实现的。 格式:类型说明符 数组名[常量表达式 1] [常量表达式 2] 二维数组:int[][],double[][]等。 三维数组:float[][][],String[][][]等。 可以用数学的思维来理解多维数组,如二维数组中元素的排列顺序是:先行后列。 因 此,可以把二维数组看成是一个矩阵。 5.4.2 知识准备:多维数组的创建 Android 系统下 Java 编程详解 106 4 当使用 new 来创建多维数组时,不必指定每一维的大小,而只需指定最左边的维的大 小就可以。如果指定了其中的某一维的大小,那么所有处于这一维左边的各维的大小都需 要指定。 下面是一些创建多维数组的例子: boolean[][] b = new boolean[10][3]; int[][] a = new int[5][]; String[][][] = new String[4][5][6] double[][][] = new double[40][][] 下面的创建方式是错误的: //int[ ][ ] a = new int [ ][5]; 5.4.3 知识准备:多维数组的初始化 在知道数组元素的情况下,可以直接初始化数组,不必调用 new 来创建数组,这和一 维数组的静态初始化类似: 分行给二维数组赋初值。 int a[2][3]={{1,2,3},{4,5,6}}; 按数组的排列顺序对各数组元素赋初值。 int b[2][3]={1,2,3,4,5,6}; 可以对部分元素赋初值。 int c[3][4]={{1},{5},{9}}; int d[3][4]={{1},{5,6},{0,9,7}}; 在对全部数组元素赋初值时,数组第一维的长度可以不指定。 int e[ ][3]={1,2,3,4,5,6}; int f[ ][4]={{0,0,3},{0},{0,10}}; 在引用多维数组的时候,通过指定数组名和各维的索引来引用。 注意: 二维数组元素仍然是从 a[0][0]开始。 除了静态初始化外,多维数组也可以通过数组声明和初始化分开的动态初始化方法来 对数组进行初始化,例如: int a[][] = new int[4][5]; int b[][] = new int[3][] b[0] = new int[4]; b[1] = new int[3]; b[2] = new int[5]; 第 5 章 数组 107 4 5.5 本章小结 本章对一维和多维数组的基本概念,声明、创建和初始化方式进行了介绍。结合数组 内存空间的分配的相关知识使同学们加深了解数组的原理和使用方式。着重介绍了队列, 堆栈等数据结构、排序算法与数组的结合使用。通过学习本章,同学们应该掌握数组基本 的使用方法。数组的使用很广泛也很灵活,同学们在今后的开发中要结合内存空间,程序 效率等多方面合理使用数组。 课后练习题 一、选择题 1.编译并且执行以下代码结果是( )。 public class Test{ public static void main(String args[]){ int array[]=new int[]{3,2,1}; System.out.println(array[1]); } } A.1 B.2 C.3 D.有错误,数组的大小没有定义 2.编译并且执行以下代码结果会是( )。 public class Test{ public static void main(String args[]){ String array[]=new String[5]; System.out.println(array[0]); } } A.不确定的值 B.0 C.null D.有错误,数组没有初始化 3.下面( )代码能够正确的计算出由命令行传递给应用程序的参数个数。 A.int count=args.length; B.int count=args.length-1; C.int count=0; while(args[count]!=0) count++; D.int count=0; while(!(args[count].equals("))) count++; 4.能正确定义一个数组的代码是( )。 Android 系统下 Java 编程详解 108 4 A.int [][] num=new int{{1},{2,3}}; B.int num[][5]; C.int num[][]=new int[5][]; D.int num[][5]=new int[][5]; 5.阅读下面代码,运行结果是( )。 public class Test{ public static void main(String[] args){ int i; int f[]=new int[6]; f[0]=f[1]=1; for(i=2;i<6;i++) { f[i]=f[i-1]+f[i-2]; } for(i=0;i<6;i++) { System.out.print(f[i]+" "); } } } A.1 2 3 5 8 B.1 1 2 3 5 8 C.2 3 5 8 D.1 1 2 3 5 二、填空题 数组用来存储一组__________数据。可以通过_____访问数组中的每一个值。可以通过 数组来保存任何相同数据类型的数据:简单类型或者引用类型。数组本身属于____类型。 数组被创建以后,它的______是不能被改变的,但是,数组中的各个数组元素是可以被改 变的。 三、编程题 1.编写一个方法,方法的返回值为一个数组,数组里存放 Student 类的对象,数组的 大小由参数传入。在程序中调用此方法,输出数组中各元素的值。 2.在一个数组中存放 10 个学生信息(姓名,成绩),然后按成绩从大到小排列输出。 第 6 章 面向对象编程进阶 本章详细介绍了类的继承的概念,介绍了如何控制属性和方法的访问权限。介绍了 super 和 this 关键字的使用。详细论述了重载和覆盖的概念。揭示了对象初始化的细节。介 绍了简单数据类型的封装类及它们之间的关系、区别,说明了如何使用覆盖类的 toString() 方法来得到表示对象的字符串。详细分析了==和 equals()两种比较操作,并介绍了通过覆盖 equals()方法来自定义对象相等的含义。 6.1 继承  面向对象程序设计中,可以在已有类的基础上定义新的类,而不需要把已有类的 内容重新书写一遍,这就叫做继承。已有的类称为基类或父类,在此基础上建立 的新类称为派生类或子类。  运用继承,父类的特性不必再重新定义,就可以被其他类继承。  继承是面向对象编程技术的一个重要机制,较好地解决了代码重用问题。  任何一个类都可以作为基类,从这个基类可以派生出多个子类,这些派生的类不 仅具有基类的特征,而且还可以定义自己独有的特征。 6.1.1 类的继承 面向对象程序设计的一个重要特点就是类的重用。这可以通过两种方法来实现:一种 是将一个类的实例当做另一个类的属性。一种是使用类的继承来实现,通过关键字 extends, 可以使一个类继承另一个类,使这个类也具有被继承类的特点。实现继承的类称为子类, 而被继承的类称为父类,也称为超类。类的继承是面向对象程序设计的一个重要特点。 来看一个例子,假设现在要开发手机通讯录名片,名片中规定的角色有同事,朋友, 可以给同事定义一个类,如下所示: public class colleaguesCard{ String name;//姓名 Android 系统下 Java 编程详解 110 4 String sex;//性别 String tele;//办公电话 String department;//属于哪个办公室 public void setName(String theName){… } public String getName(){… } … } 而朋友名片也可以定义一个类,如下: public class friendCard { String name;//姓名 String sex;//性别 String tele;//联系电话 String assdress;//年级 public void setName(String theName){… } public String getName(){… } … } 仔细分析这两个类,可以发现这两个类结构上非常类似,比如,姓名、性别、电话, 唯一的区别就在于“同事”类有一个属性“department”,用于说明此同事是属于哪个办公 室;而“朋友”类有一个属性“address”,表示这个朋友的住址。倘若现在需要在这两个类 上新增一个属性:生日,那么,必须在这两个类上都做修改。其实,在面向对象的编程方 法中,完全可以将这两个类的一些共性抽象出来,当做这两个类的“父类”。例如,这两个 类中的姓名、电话、性别都是作为一个“名片”所共有的特性,因此,可以将这些特性抽 取出来,作为一个新的类“Card”,如下所示: public class Card{ String name; String sex; String tele ; public void setName(String theName){… } public String getName(){… } … } 然后,根据需要,使同事和朋友这两个类都继承“Card”类,再在这个基础上添加上 自己特有的一些特性,例如“朋友”类如下: public class friendCard extends Card{ //不再需要定义姓名、电话、性别这些属性了, //它们从父类“Card”中获得 String address;//地址 public void setAddress(String theAddress){… } public String getAddress(){… } … } 而“同事”类可以定义如下: public class colleaguesCard extends Card{ //同样不需要定义姓名、电话、性别这些属性, 第 6 章 面向对象编程进阶 111 4 //而从父类“Card”中获得 String department;//办公室 public void setDepartment(String theDept){… } public String getDepartment(){… } … } 通过这种方式,就可以共用父类“Card”的特性,这时,如果需要给“朋友”和“同 事”都加上“生日”这个属性,只需要在它们共同的父类“Card”上加上这个属性就可以 了,如下所示: public class Card{ String name; String sex; String tele ; java.util.Date birthday;//新增生日属性,类型为一个 Date 引用类型 //加上对这个属性进行操作的方法 public void setBirthday(java.util.Date theDate){… } public java.util.Date getBirthday(){… } … } 此时,如果需要在这个管理系统中新增一个对学校职工的管理功能,需要新增一个“亲 属名片”类,它也可以使用“Card”类中定义的属性,然后再加上自己特有的属性,代码 如下: public class relativesCard extends Card{ //在此加上“亲属”特有的属性 } 在这个例子中,“Card”类称为“colleaguesCard”、“ friendCard”、“ relativesCard”这 3 个类的“父类”或“超类”(SuperClass),而“ colleaguesCard” 、“ friendCard” 、“ relativesCard” 称为“Card”类的“子类”(SubClass)。 从上面的例子中,可以看到,如果一个子类要继承父类,只要使用关键字“extends” 即可。在 Java 中,类继承的基本语法如下: class [extends ]{ * } 其中,用关键字“extends”来进行类的继承,后面紧跟的是父类的类名。 在 Java 中,一个类只能从一个父类继承,而不能从多个类中继承。这种继承方式称为 “单继承”。写过 C++等其他面向对象语言程序的读者注意 Java 的继承方式。 在 java.lang 包中有一个 Ob ject 类,这个类是所有类的顶级父类。所有的 Java 类,包括 标准库中的类和自己定义的类,都直接或间接地继承了这个类。这个类没有任何的属性, 只是定义了一些方法。因此,只要你定义了一个 Java 类,就有一些默认的方法供你调用。 在 Java 中,如果你定义了一个类,这个类没有继承任何的父类,那么,系统会自动将 这个类的父类设置为 java.lang.Object,例如上面定义的“Card”类: Android 系统下 Java 编程详解 112 4 public class Card{… } 它实际上等价于: public class Card extends java.lang.Object{… } 实际上,在定义一个类的时候也可以这样写,只是,这个工作可以完全由系统来代劳。 在 Java 中,虽然一个子类只能继承一个父类,但是,一个父类却可以“派生”出任意多个 的子类。这种状况有点类似于生活中的“父子关系”:一个父亲可以生几个孩子,而一个孩 子却只能有一个生父。所谓“派生”,只是从父类的角度来看类的继承。也就是说,“继承” 是从子类的角度来看父类和子类的关系的,而“派生”却是从父类的角度来看父类和子类 的关系的。 6.1.2 任务一:利用继承实现通讯录实例 1.任务描述 完善上面通讯录名片的例子。 2.技能要点  掌握类继承的方法与技巧。 3.任务实现过程 (1)定义一个类“Card”,用来表示“名片”。 源文件:Card.java public class Card { String name; String sex; String tele ; public Card(String theName){ this.name = theName; } public Card(){} public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getTele() { return tele; 第 6 章 面向对象编程进阶 113 4 } public void setTele(String tele) { this.tele = tele; } } 它有一个构造器,用名片中的姓名来作为参数。属性 name、sex 和 tele 用来表示姓名、 性别、电话,类中定义了用于存取属性的两个方法:getXXX()和 setXXX()。 (2)定义一个子类 FriendCard,这个子类继承自 Card,因此,此时这个类也拥有了父 类 Cardl 的属性和相应的方法。在此基础上,又在子类上新增了一个用于描述地址的属性: address。 源文件:FriendCard.java public class FriendCard extends Card{ String address;//子类新增属性 public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } } 再来定义 ColleaguesCard 类, 该 类有新增属性 department,并且该类定义了自己的构造 器,参数为父类 Card 的属性 name,子类可以直接使用此属性。 源文件:ColleaguesCard.java public class ColleaguesCard extends Card{ String department;//子类新增属性 public ColleaguesCard(String name){ this.name = name; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } } 最后来看如何实例化 FriendCard 和 ColleaguesCard 类,以及如何体现它和父类 Card 的 关系。 源文件:MyCard.java public class MyCard { public static void main(String[] args) { FriendCard fc = new FriendCard(); Android 系统下 Java 编程详解 114 4 fc.setName("Alex"); fc.setAddress("平安大街 23 号"); ColleaguesCard cc = new ColleaguesCard("Mia"); cc.setDepartment("B305"); System.out.println("Friend: "+fc.getName()+" Address:"+fc.getAddress()); System.out.println("Colleagues: "+ cc.getName() + " Department: "+ cc.getDepartment()); } } 在 MyCard 类中,首先调用 FriendCard 的默认构造器来实例化了一个 FriendCard 类, 此时得到一个 FriendCard 对象,然后,调用 FriendCard 从父类继承的 setName()、setAddress() 方法来设置它的属性 name 和 address,以及调用从父类继承的 getName()、getAddress()方法 来获取属性 name 和 address 的值。而 ColleaguesCard 则使用带参数构造器实例化 ColleaguesCard 对象,确定了属性 name 的值。使用继承自父类的 getDepartment()、 setDepartment()方法确定和获取 department 属性值。运行这个程序,将在控制台上打印出如 下信息: Friend: Alex Address:平安大街 23 号 Colleagues: Mia Department: B305 6.1.3 访问控制 在第 2 章已经知道,通过将属性设置为 private(私有的)的,可以限制对相应属性的 访问。在 Java 中,可以在类、类的属性及类的方法前面加上一个修饰符(modifier),来对 类进行一些访问上的控制。比如,在前面已经讨论过的,一般情况下将类的属性定义为私 有(private)的,而通过公共的(public)方法来对这些属性进行访问。在这个类程序外的 其他程序只能通过公共的方法来访问这个类的属性,这样,实现了信息的隐藏和封装。但 是,有时候也需要让其他的程序直接访问类的属性,或者只能让子类访问父类的属性,这 时就不能用 private 来限制这些属性了。 在 Java 中,定义了 3 个修饰符用来控制类、类的属性及类的方法等的访问范围。通过 这 3 个修饰符,可以定义 4 种程度的限制。下面将对这些修饰符做详细的说明。 private:这是限制最严格的一个修饰符,使用这个关键字来限制的属性或者方法,只 能在同一个类中被访问。也就是说,在这个类文件之外,这些属性或方法是被隐藏的。这 个修饰符最常用于修饰类中的全局变量。注意,这个修饰符不能用在类前面。 Default:Default 不是关键字,只是对类、类的属性及类的方法的访问权限的一种称呼。 如果在类、类的属性、类的方法前面没有添加任何的修饰符,则说它的访问权限是 default 的。在这种情况下,只有类本身或者同一个包中的其他类可以访问这些属性或方法,而对 于其他包中的类而言是不可以访问的。 protected:protected 修饰符修饰的属性或方法,可以被同一个类、同一个包中的类及 子类访问。注意,这个修饰符同样不能用于类前面。 第 6 章 面向对象编程进阶 115 4 public:这个修饰符对类、类的属性及类的方法均可用。它是最宽松的一种限制,使用 这个修饰符修饰的类属性、类的方法可以被任何其他的类访问,无论这个类是否在同一个 包中,以及是否是子类等。 一般来说,应该将和其他类无关的属性或者方法设置为 private 的,只有需要将它给其 他的类访问的属性或方法才将它设置为 public 或者 protected,或者不加任何修饰符,让其 为 default。 表 6-1 列出了各种访问修饰符的限制范围。 表 6-1 修饰符的限制范围 修饰符 同一个类中 同一个包中 子类中 全局 private Yes default Yes Yes protected Yes Yes Yes public Yes Yes Yes Yes 访问控制修饰符的限制程度从高到低为:private、default、protected、public。 注意: default 不是 Java 关键符,它只是表明了一种访问限制状态。 6.2 super 关键字 在从子类继承父类的过程中,可能需要在子类中调用父类中的成员,如属性、方法或 者构造器,这个时候,可以使用 super 关键字来完成。super 的作用是用于引用父类的成员, 如属性、方法或者是构造器。 6.2.1 调用父类构造器 用于调用父类的构造器,是 super 的用法之一,它的基本格式如下: super([arg_list]) 直接用 super()加上父类构造器所需要的参数,就可以调用父类的构造器了。如果父类 中有多个构造器,系统将自动根据 super()中的参数个数和参数类型来找出父类中相匹配的 构造器。 来看下面这个例子,类“FriendCard”继承了父类“Card”构造器中调用了父类的构造器: 源文件:friendCard.java public class FriendCard extends Card{ Android 系统下 Java 编程详解 116 4 public FriendCard (String friendName) { super(friendName); } //其他代码 … } 在 FriendCard 这个子类中,有一个构造器 FriendCard (String friendName)里有一个 super (friendName)的语句,意思是用参数 friendName 来调用父类的构造器,也就是说,如果 调用 FriendCard 的构造器来构建一个对象,它将会调用父类的构造器来完成这个任务。 父类必须自己负责初始化自己的状态而不是让子类来做,因此,如果子类的构造器中没有 显式地调用父类构造器,也没有在构造器中调用重载的其他构造器,则系统将会默认调用 父类中无参数的构造器。所以,父类中必须定义无参的默认构造器,否则编译器将会报错。 例如,如果将 Card 类定义成如下: public class Card { String name; String sex; String tele ; public Card(String theName){ this.name = theName; } // 其他代码 } 则对于 Card 的子类 FriendCard 来说,如果定义为如下的方式: public class FriendCard extends Card { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } // 其他代码 } 那么,编译这个类的时候,将会出现如下的错误: FriendCard.java:1: cannot resolve symbol symbol : constructor Card () location: class Card public class friendCard extends Card ^ 1 error 这是因为在调用类 FriendCard 的默认无参数的构造器创建对象的时候,它会去调用父 类 Card 的不带参数构造器,而在类 Card 中,因为没有定义不带参数的构造器,所以,编 译器因为无法成功调用 Card 的不带参数的构造器而报错。 第 6 章 面向对象编程进阶 117 4 6.2.2 调用父类属性和方法 当 super 用于引用父类中的属性或方法时,使用下面的形式: super.属性 super.方法() 例如,可以在 FriendCard 子类中通过下面的方式来调用父类中的方法: super.getName(); 注意,这时,父类的属性或方法必须是那些 protected(受保护)或者 public(公共) 等可以让子类访问的属性或者方法。 super 用于调用父类中的方法主要用于在子类中定义了和父类中同名的属性,或进行了 方法的覆盖,而又要在子类中访问父类中的同名属性或覆盖前的方法的时候。 6.2.3 任务二:super 关键字的使用 1.任务描述 使用 super 调用父类中的方法和属性。 2.技能要点  掌握 super 关键字的使用方法。 3.任务实现过程 源文件:Card.java public class Card { private String name; private String sex; private String tele ; public Card(String theName){ this.name = theName; } public Card(){} public String showName() { return name; } //其他代码 } 在这个例子中,将 Card 类中的属性都是 private 的,通过各自的 public 的方法来存取。 源文件:friendCard.java public class friendCard extends Card{ String address; public friendCard(String friendName){ super(friendName); Android 系统下 Java 编程详解 118 4 } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String showFriendName(){ return "My Friend "+ super.showName(); } } 在子类 friendCard 中,如果使用下面的代码段所示直接返回父类中的属性 name: return "My Friend "+ super.name; 在编译的时候,将会因为访问权限问题而出错,将会出现下面的错误: friendCard.java:17: name has private access in Card return "My Friend "+ super.name; 1 error 这个时候,可以在 showFriendName()方法中调用被覆盖方法,得到需要的“name”属 性的值: public String showFriendName(){ return "My Friend "+ super.showName(); } 这样就解决了访问权限的问题,也清晰地指明了在这个覆盖方法中调用的 showName() 方法是父类中的方法。 注意: 如果不使用 s up er 指明此处调用的 showName()方法的出处,系统依然会自动调用父类中的 showName()方法。但是当子类中有同名方法覆盖父类方法时,系统将会当做是子类自身的方法, 当运行此程序时,将会递归地调用方法本身,引起程序错误。为避免混淆,sup er 关键字不要默 认。 6.3 this 关键字 6.3.1 知识准备:使用 this 获得当前对象的引用 编写类的方法时,会希望获得当前对象的引用,Java 引入关键字 this。this 代表其所在 方法的当前对象,包括:  构造器中指该构造器所创建的新对象。 第 6 章 面向对象编程进阶 119 4  方法中调用该方法的对象。  在类本身的方法或构造器中引用该类的实例变量和方法。 this 只能用在构造器或者方法中,表示对“调用此方法的那个对象”的引用。可以和 任何的对象引用一样来处理 this 对象。如果在方法内部调同一个类的另一个方法,可以不 必显示地使用 this,直接调用即可,效果是一样的。 (1)调用当前对象的属性。 源文件:Person.java public class Person { private String name; private int age; private String sex; public String showName() { return this.name; } public void setName(String theName) { this.name = theName; } // … } 在类“Person”中,定义了两个方法用于存取 name 属性。来看一下 showName()这个 方法,它将返回当前对象的 name 属性的值,在这里使用了 t hi s 表示当前对象的属性,在方 法 setName()中也有类似的用法。其实,如果只是在类的某个方法或构造器中调用另一个方 法,可以不用显式使用 th is。这样的写法虽然并非必要,但可以使你的程序清晰易读,特 别是在你的方法中的参数名称和属性名称一样的时候,例如,如果方法 setName()中的参数 名称也为 name,那么,如果没有用 this 来标示对象的属性,方法中的代码将如下: … public void setName(String name){ name = name; } … (2)引用对象本身。 还有一种情况是必须使用 this 关键字的,那就是当需要在对象中明确地指明当前的对象 引用是本对象的时候。比如,当你需要返回当前的对象的时候,就需要用到 this 关键字了。 假设需要设置手机用户账户、行号 源文件:MobileAccount.java public class MobileAccount { private int accountId = 0; Android 系统下 Java 编程详解 120 4 public MobileAccount createAccount() { accountId++; return this; } public int getAccountId() { return accountId; } public void setAccountId(int accountId) { this.accountId = accountId; } public static void main(String[] args) { MobileAccount account = new MobileAccount(); System.out.println("账号是:" + account.createAccount().createAccount().getAccountId()); } } 由于 createAccount()方法返回了同一个对象,所以可以在这个对象上多次调用方法 createAccount()。 编译并运行上面的程序,将得到如下的输出: 手机用户账号是:2 6.3.2 知识准备:在构造器中调用构造器 在一个类中,由于初始化条件不同,可能定义了多个构造器,这称为构造器的重载。 在这些构造器中,可能一个构造器中的一段代码和另一个构造器完全一样,那么,就可以 在这个构造器中直接调用另一个构造器,这样可以避免编写相同的代码,th is 关键字可以 做到这一点。this 不再表示对象本身对当前对象的引用。在构造器中,为 t hi s 添加了参数列 表,可以调用类本身其他的构造器。语法如下: this([args_list]); 如果该类中有多个其他构造器定义,系统将自动根据 this()中的参数个数和参数类型来 找出类中相匹配的构造器。 我们来看一个在构造器中使用 this()的例子: 源文件:Card.java public class Card { String name; String sex; String tele ; public Card(){ System.out.println("Card()被调用"); } public Card(String theName){ this(); 第 6 章 面向对象编程进阶 121 4 this.name = theName; System.out.println("Card(String theName)被调用"); } public Card(String theName,String theTele,String theSex ){ this("theName"); this.sex = theSex; this.tele = theTele; System.out.println("Card(String theName,String theTele,String theSex )被调用"); } /* getter,setter */ public static void main(String[] args) { Card c = new Card("Alex","186***","male"); } } 这个示例中定义了一个类“Person”,这个类中定义了 3 个构造器:没有参数的构造器、 有一个参数的构造器,以及有两个参数的构造器。没有参数的构造器将以“Male”值来初 始化新建对象的 sex 属性。下面重点来看后面两个构造器,在带一个参数的构造器中,接 收一个 String 类型的参数 theName(姓名)来创建对象,这个构造器代码块里有一条语句: this(); 这条语句的作用是用于调用对象的没有参数的构造器,也就是 Person();而在带两个参 数的构造器中,接收 theName(姓名)和 theAge(年龄)来创建对象,它也通过 this()来调 用了该对象中的另外一个构造器: this(theName); 此时,这条语句调用的是带一个 Strin g 类型参数的构造器,在这里是 Person(String theName)构造器。 编译并运行上面的程序,将得到如下的输出: Card()被调用 Card(String theName)被调用 Card(String theName,String theTele,String theSex )被调用 注意: 在构造器中可以通过 this()方式来调用其他的构造器,但在一个构造器中最多只能调用一次其他 的构造器。并且,对其他构造器的调用动作必须在构造器的起始处,否则编译的时候将会出现 错误。 另外,不能在构造器以外的地方以这种方式调用构造器。 6.3.3 知识准备:static 的含义 Android 系统下 Java 编程详解 122 4 了解 this 关键字后,我们可以更加全面地了解 stat ic 方法的含义。stat ic 方法没有 th is 的静态方法,不可以通过 th is 来调用静态方法。在 stat ic 方法内部不能调用非静态方法, 可以在没有创建任何对象的时候就通过类本身来调用 stat ic 方法,这也是 stat ic 方法的主 要用途。 这可能会引起一些质疑,static 方法是否违背了 Java“面向对象”的概念呢?因为 static 方法具有全局函数的特点;由于使用 static 方法时不能通过 this,所以不是通过“向对象发 送消息”的方式来完成调用的。所以,如果你的代码里大量出现了 static 方法,应该重新考 虑自己的程序设计问题了。但是不可否认 static 的实用性,很多地方需要用到它,至于是否 有违“面向对象”的概念,我们大可不必深究,在实际中灵活运用才是好的。 6.4 方法的覆盖与重载 6.4.1 知识准备:方法覆盖 当一个子类继承了一个父类时,它也同时继承了父类的属性和方法。可以直接使用父 类的属性和方法,或者,如果父类的方法不能满足子类的需求,则可以在子类中对父类的 方法进行“改造”,“改造”的过程在 Java 中称为“覆盖(override)”。 比如,在任务二的 Card 类中定义了一个方法用于显示名片的姓名,代码如下: public String showName() { return name; } 那么,子类 FriendCard 就会继承这个父类的 showName()方法,此时,即使 FriendCard 类中没有定义 showName()方法,也可以在它的实例(instance)中使用这个方法。比如, 实例化一个 Teacher 的实例,这时就可以直接调用从 Teacher 的父类 Person 中继承的 showName()方法。代码如下: FriendCard friend = new FriendCard (); friend.showName(); 但是,在 FriendCard 类中,希望输出姓名前面加上“friend:”,在任务二中,给 FriendCard 定义了方法 showFriendName,其实可以直接对从父类中继承的 showName()方法进行“改 造”,也就是在子类 FriendCard 中覆盖父类 Card 的方法 showName(): public class friendCard extends Card{ … public String showName(){ return "My Friend "+ this.name; } } 第 6 章 面向对象编程进阶 123 4 在子类 FriendCard 中,覆盖了父类的 showName 方法,在覆盖的过程中,需要提供和 父类中的被覆盖方法相同的方法名称、输入参数(此处为空)及返回类型。 另外,在子类对父类的方法进行覆盖的过程中,不能使用比父类中的被覆盖方法更严 格的访问权限。比如,父类 Person 中的方法 showName()的修饰符是 public,那么,在子类 FriendCard 中的覆盖方法 showName()就不能用 protected、默认(Default)或者 private 等来 限制。 从前面的讨论可以看出,类的继承主要可以从两个方面来看: (1)对父类的扩充。如在子类中加入新的属性、新的方法。 (2)对父类的改造。比如,对方法的覆盖。 首先定义一个父类:Person,它有 3 个属性,分别由各自的存取方法来存取。 源文件:Person.java public class Card { String name; String sex; String tele ; public Card(String theName){ this.name = theName; } public Card(){} public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getTele() { return tele; } public void setTele(String tele) { this.tele = tele; } public String showName() { return name; } } 在这里,将 Person 的属性的访问控制定义为 Default,是因为在子类中需要访问这些属性。 接着,定义一个类“FriendCard”,它继承“Card”类。 Android 系统下 Java 编程详解 124 4 源文件:FriendCard.java public class friendCard extends Card{ String address; public friendCard(String friendName){ super(friendName); } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String showName(){ return "My Friend "+ this.name; } } 在这个子类中,继承了父类,新增了一个“地址”的属性:address,并新增了相应的 存取方法。这是对父类属性和方法的扩充。改写(覆盖)了父类中的 showName()方法,在 name 前加上“My Friend”后返回。因为在同一个包中的子类中用到了父类的属性 name,所 以,父类 Card 中的 name 属性不能定义为 private 的。这是对父类方法的改造,即方法覆盖。 6.4.2 知识准备:方法重载 在 Java 程序中,如果同一个类中有两个相同的方法(方法名相同、返回值相同、参数 列表相同)是不行的,因为这样编译器无法将方法的调用和特定的方法联系起来。但是, 在一个类中,如果有多个方法具有相同的名称,而有不同的参数,这种情况是允许的,称 这种行为为方法的重载(overload)。经常使用 pr int ln 来向控制台输出各种类型的数据,这 些 pr int ln 方法就是实现了方法的重载。 在进行方法的重载时,方法的参数列表必须不同(参数个数或者参数数据类型,或者 两者皆不同)。而方法的返回值可以相同,也可以不同。 来看一个例子。还是以上面的“Card”类的 showName 方法来显示对象姓名。需要取 得“Card”对象的“name”属性,这里假设会有两种情况:一种是返回值直接为“name” 属性;还有一种情况是在获取的对象属性“name”的方法上输入一个参数,将这个参数和 “name”结合起来,比如,输入的参数是“先生”,则通过方法返回的是:“XXX 先生”, 输入的参数是“女士”,则方法返回的是“XXX 女士”。可以为这两个需求定义两个方法, 但是,为了显示这两个方法的相似点,更倾向于使用方法重载来完成: public class Card{ … public String showName() { return name; } public String showName(String personCall){ return name+personCall; 第 6 章 面向对象编程进阶 125 4 } … } 这样,如果只需要输出“Card”对象的“name”属性,调用不带参数的 showName()方 法就可以了;如果需要得到“Card”对象的“name”属性并且需要指明此人的称谓,则可 以调用带一个参数的 showName()方法。 在进行方法的重载时,有 4 条基本原则需要遵守: (1)方法名相同。 (2)参数列表必须不同。 (3)返回值可以不同。 (4)可以相互调用。 注意: 方法的返回值不是方法签名(Signature)的一部分,所以,进行方法重载的时候,不能将返回值 类型的不同当成两个方法的区别。也就是说,在同一个类中,不能有这样的两个方法,它们的 方法名相同、参数相同,只是方法的返回值类型不同。 6.4.3 知识准备:方法重载构造器重载 在前面说过,构造器在某种程度上可以看成是一个特殊的方法:它没有返回值,它的 方法名称必须和类的名称一致。因此,构造器也常常被称为“构造方法”。作为 Java 类的 组成成分之一,构造器也可以进行重载。例如下面的代码中,类 Card 就定义了 3 个重载的 构造器以满足不同的需要。 源文件:Card.java public class Card { String name; String sex; String tele ; public Card(){ System.out.println("Card()被调用"); } public Card(String theName){ this.name = theName; System.out.println("Card(String theName)被调用"); } public Card(String theTele,String theSex ){ this.sex = theSex; this.tele = theTele; System.out.println("Card(String theTele,String theSex )被调用"); } /* Android 系统下 Java 编程详解 126 4 getter,setter */ public static void main(String[] args) { Card c = new Card("186***","male"); } } 在这个类中,定义了 3 个构造器,这 3 个构造器中,各自的参数个数都不一样。在创 建对象的时候,编译器会根据参数类型和参数个数来确定到底调用哪一个构造器。 编译并运行上面这个程序,它将会向控制台打印出如下信息: Card(String theTele,String theSex )被调用 这说明,它的带两个参数的构造器被调用来创建 Card 对象了。 6.5 通常需要覆盖的几种方法 6.5.1 知识准备:对象的 toString 方法 在 Object 类中,定义了一个 toString()方法,用来返回一个表示这个对象的字符串: public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } 在这个方法中,它将返回一个由类名、紧随其后的“@”符号和 hash 码的无符号的十 六进制字符串,用来表示这个对象。我们知道所有类都是继承 Object,所以“所有对象都 有这个方法”,其 作用就是为了方便所有类的字符串操作。 来看一下调用“Person”对象的 toString()方法返回的值。 … Person person = new Person(); System.out.println(person); … 上述代码将打印出表示“Person”对象的字符串。它将打印出类似如下的信息: Person@15ff48b 显然,这个信息对于我们来说没有什么用。因此,通常情况下,需要覆盖父类中的方 法 toString(),用来提供某对象的自定义信息。在 Java 的 API 文档中也指出“建议所有子类 都重写此方法”。一般来说,大多数类的 toString()方法覆盖后返回的用于表示对象的字符串 都遵循如下的格式: 类名[属性 1=值 1,属性 2=值 2,…] 第 6 章 面向对象编程进阶 127 4 覆盖 toString()方法的一个基本原则是,它应该返回包含在对象中的所有令人感兴趣的 信息,比如对象的属性的值。 6.5.2 任务三:覆盖 toString 方法 1.任务描述 在任务一的 Card 类中添加自定义 toString 方法以覆盖 Object 中的 toString()方法。 2.技能要点  掌握自定义 toString()方法。 3.任务实现过程 源文件:Card.java public class Card { String name; String sex; String tele ; public Card(String theName){ this.name = theName; } public Card(){} public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getTele() { return tele; } public void setTele(String tele) { this.tele = tele; } // 覆盖 toString()方法 public String toString() { return getClass() + "[" + "name = " + name + ",sex = " + sex+ ",tele = " + tele + "]"; } } 在这个类中,覆盖了父类(在这里是 Object 类)的 toString()方法,让它按照上面所说 的惯例来返回一个用以表示对象的字符串。 Android 系统下 Java 编程详解 128 4 除显式调用对象的 toString()方法外,在进行 String 与其他类型数据的连接操作时,会 自动调用 toString()方法,它可以分为两种情况: (1)如果 String 类型数据和引用数据类型连接,则引用类型数据直接调用其 toString() 方法返回表示该对象的字符串. (2)如果 String 类型数据和简单类型数据连接,则简单类型数据先转换为对应的封装 类型,再调用该封装类对象的 toString()方法转换为 String 类型。 6.5.3 知识准备:==和 equals() 在 Java 程序设计中,经常需要比较两个变量值是否相等,例如: a = 10; b = 12; if(a == b) { //statements } 再如: ClassA a = new ClassA("abc"); ClassA b = new ClassA("abc"); if (a == b){ //statements } 对于第一个例子,比较的是简单类型数据,它们是明显相同的,所以 a==b 值为 true。 但是,对于第二个例子中的比较表达式,它们比较的是对象的引用,即判断这两个变量 a 和 b 是否指向同一个对象引用。在这里,因为 a 指向一个对象,而 b 指向另一个对象,所 以,它们并不指向同一个对象,因此,a==b 返回的值是 false。 为了方便说明简单类型和引用类型比较,此处用 string 类型和它的封装类 String 来说 明简单类型和封装类型进行比较时的区别。请看下面的例子: 源文件:TestEqual.java public class TestEqual { public static void main(String[] args) { // 简单类型比较 string1="aaa"; string2="aaa"; System.out.println("str ing1== str ing2? " + (stri ng1 == stri ng2)); // 引用类型比较 String string3=new String("aaa"); String string4=new String("aaa"); System.out.println("str ing3== str ing4? " + (str ing3 == string4)); } } 运行这个程序,在控制台上打印出如下信息: 第 6 章 面向对象编程进阶 129 4 str ing1== str ing2? true str ing3== str ing4? false 可以看出,比较两个引用类型的时候,虽然用了同一个参数构造两个变量,但它们并 不相同。我们知道,对于引用类型,它们指向的是两个不同的对象:这两个对象的值都为 100。因为它们指向的对象是两个对象,因此,比较这两个变量会得到 false 的值。也就是 说,对于引用类型变量,运算符“==”比较的是两个对象是否引用同一个对象。那么,如 何比较对象的值是否相等? Java 中提供了一个 equals()方法,用于比较对象的值。下面将上面的程序稍做修改: String string3=new String("aaa"); String string4=new String("aaa"); System.out.println("str ing3 equals str ing4? " + (str ing3 == string4)); 这时表达式 c.equals(d)会得到一个 true,这是因为,方法 equals()进行的是“深层比较”, 它会去比较两个对象的值是否相等。 equals()方法是由谁来实现的呢?在所有类的父类 Object 中,已经定义了一个 equals() 方法,但是这个方法实际上也只是测试两个对象引用是否指向同一个对象。所以,可以使 用这个方法来进行比较操作,但是,它并不一定能得到所期望的效果。所以,经常的,还 需要自己将定义的类中的 equals()进行覆盖。像 Integer 封装类,就覆盖了 Object 中的 equals() 方法。 关于==和 equals()两种比较方式,在使用的时候要小心选择。如果测试两个简单类型的 数值是否相等,则一定要使用“==”来进行比较;如果要比较两个引用变量对象的值是否 相等,则用对象的 equals()方法来进行比较;如果需要比较两个引用变量是否指向同一个对 象,则使用“==”来进行比较。对于自定义的类,应该视情况覆盖 Object 或其父类中的 equals() 方法。equals()方法只有在比较的两者是同一个对象的时候,才返回 true。 下面,来考虑如下的这种场景:假设在一个 Java 应用程序中,创建了两个“公民”对 象。现在,需要比较两个“公民”对象是否相等,此时,关心的是这两个“公民”对象代 表的是否为同一个人,而并不关心它们是否是同一个内存区域里的对象。 假设有一个类“Cit izen”用于表示公民,它有一个属性 id 用来表示这个公民的身份证 号,假设身份证号不会重复,也就是说,一个身份证号对应一个公民。它的类定义如下(为 简单起见,省略了其他的属性而只留下用于表示身份证号的 id 属性): public class Citizen { // 身份证号 String id; // 其他属性略 public Citizen(String theId) { id = theId; } } 假设在一个 Java 应用程序中,建立了两个“Citizen”对象,然后,在某个点上需要判 断这两个对象是否代表了同一个公民: Android 系统下 Java 编程详解 130 4 Person p1 = new Person("id00001"); Person p2 = new Person("id00001"); … if (p1.equals(p2)){ … } 在这个程序中,因为这两个“Cit izen”对象的身份证号一样,所以,它们代表的应该 是同一个人,但如果用 Object 的默认方式,它只会去比较两个对象引用变量是否指向同一 个对象,因此,它返回 false。显然,这个结果不是我们所期待的。此时,就需要在“Cit izen” 中覆盖 Object 类中的 equals()方法,以满足我们的需求。 源文件:Citizen.java public class Account { String accountName; public Account(String theName){ this.accountName = theName; } public String getAccountName() { return accountName; } public void setAccountName(String accountName) { this.accountName = accountName; } public boolean equals(Account a) { // 首先判断需要比较的 Object 是否为 null, // 如果为 null,返回 false if (a == null) { return false; } // 判断测试的是否为同一个对象, // 如果是同一个对象,毋庸置疑,它应该返回 true if (this == a) { return true; } // 判断它们的类型是否相等, // 如果不相等,则肯定返回 false if (this.getClass() != a.getClass()) { return false; } // 只需比较两个对象的 id 属性是否一样, // 就可以得出这两个对象是否相等 return accountName.equals(a.accountName); } public void print(Account ac){ if (this.equals(ac)){ System.out.println("Welcome "+this.accountName); } 第 6 章 面向对象编程进阶 131 4 else { System.out.println("用户名错误"); } } public static void main(String[] args) { Account a1 = new Account("Alex"); Account a2 = new Account("Mary"); a1.print(new Account("Alex")); a2.print(new Account("Alex")); } } 在类“Cit izen”中,覆盖了父类(Object)中的 equals(),使它能够根据对象的 id 属性 是否相等来判断这两个对象是否相等。这个覆盖方法中,已经加入了足够的注释,请读者 参考程序中的注释来理解这个方法的定义。 源文件:TestCitizen.java public class TestCitizen { public static void main(String[] args) { Citizen p1 = new Citizen("id00001"); Citizen p2 = new Citizen("id00001"); System.out.println(p1.equals(p2)); } } 这个类中,定义了一个 main( )方法,它是一个应用程序。在 main( )方法中,使用身份 证号“id00001”创建了两个 Cit izen 对象,然后,用覆盖的 equals()方法比较这两个对象的 相等性,此时,因为它们的身份证号相同,equals()方法因此将返回 true。编译并运行这个 程序,将向控制台输出如下信息: true 读者可以试着将覆盖的 equals()方法删除或注释掉,然后重新编译运行 TestCitizen. java, 看看此时的结果,然后想想为什么。 在这个覆盖的 equals()方法中,还使用到了另一个方法 getClass(),它将返回对应的对 象的运行期类(runt ime class)。关 于 getClass()的更多信息,请读者查看 API 文档,在此不 再赘述。 另外,如果一个类的父类不是 Ob ject,那么,你首先需要检查它的父类是否定义了 equals()方法,如果是的话,在覆盖父类的 equals()方法的时候,需要在子类的 equals()方法 中使用下面的方法来调用父类的 equals()方法,以确保父类中的相关比较能够执行: super.equals(obj) 例如,假设有一个类“Armyman”用来表示军人,它是一个“Cit izen”的子类,此时, 如果在 Armyman 中覆盖 Cit izen 的 equals()方法,则需要在覆盖的 equals()方法中调用被覆 盖的 Cit izen 类中的 equals()方法: public boolean equals(Object obj){ Android 系统下 Java 编程详解 132 4 return super.equals(obj)&&(其他比较语句); } 6.6 对象的初始化 当调用类的构造器来创建对象时,它将给新建的对象分配内存,并对对象进行初始化 操作。现在我们来探讨对对象进行初始化操作时的细节。 对象的初始化操作将递归如下的步骤来进行: (1)设置实例变量的值为默认的初始值 (0, false, null),不同的数据类型有不同的初始值。 (2)调用类的构造器(但是还没有执行构造方法体),绑定构造器参数。 (3)如果构造器中有 this()调用,则根据 this()调用的参数调用相应的重载构造器,然 后转到步骤(5);否则转到步骤(4)。 (4)除 java.lang.Object 类外,调用父类中的初始化块初始化父类的属性,然后调用父类 构造器,如果在构造器中有super()调用,则根据super()中的参数调用父类中相应的构造器。 (5)使用初始化程序和初始化块初始化成员。 (6)执行构造器方法体中的其他语句。 所谓的初始化块,就是我们在前面章节提到的所谓“游离块”。不管使用哪个构造器创 建对象,它都会被首先运行,然后才是构造器的主体部分被执行。 我们来看一个例子。 源文件:TestPerson.java class Person { private String name; private int age; private String sex; public Person() { System.out.println("构造器 Person()被调用"); sex = "Male"; System.out.println("name=" + name + " ,age=" + age + " ,sex=" + sex); } public Person(String theName) { // 调用构造器 Person() this(); System.out.println("构造器 Person(String theName)被调用"); name = theName; System.out.println("name=" + name + " ,age=" + age + " ,sex=" + sex); } public Person(String theName, int theAge) { 第 6 章 面向对象编程进阶 133 4 // 调用构造器 Person(String theName) this(theName); System.out.println("构造器 Person(String theName,int theAge)被调用"); age = theAge; System.out.println("name=" + name + " ,age=" + age + " ,sex=" + sex); } // 初始化块 { name = "Tony Blair"; age = 50; sex = "Female"; System.out.println("初始化块执行后:name=" + name + " , age=" + age + " ,sex=" + sex); } } public class TestPerson { public static void main(String[] args) { Person person = new Person(); } } 编译执行上面的程序,将会得到如下的输出: 初始化块执行后:name=Tony Blair ,age=50 ,sex=Female 构造器 Person()被调用 name=Tony Blair ,age=50 ,sex=Male 可以看到,初始化块会先于构造器调用执行。读者可以将 main( )方法中调用的创建 Person 对象的构造器换成其他两个,再观察它的结果,同样可以得出上面的结论。 提示: 初始化块的机制并不是必须的,完全可以将属性的初始化和属性的声明结合在一起,例如: String name = "Tony Blair"; 下面看一个对象初始化的例子,以加深对对象初始化的理解: 源文件:Person.java class Person { private String name; private int age; private String sex; public Person() { System.out.println("构造器 Person()被调用"); sex = "Male"; System.out.println("name=" + name + " ,age=" + age + " ,sex=" + sex); } Android 系统下 Java 编程详解 134 4 public Person(String theName) { System.out.println("构造器 Person(String theName)被调用"); name = theName; System.out.println("name=" + name + " ,age=" + age + " ,sex=" + sex); } public Person(String theName, int theAge) { System.out.println("构造器 Person(String theName,int theAge)被调用"); name = theName; age = theAge; System.out.println("name=" + name + " ,age=" + age + " ,sex=" + sex); } // 初始化块 { name = "Tony Blair"; age = 50; sex = "Female"; System.out.println("Person 初始化块执行后:name=" + name + " ,age=" + age + " ,sex=" + sex); } } 这里定义了一个父类 Person,它里面定义了 3 个构造器以及 1 个初始化块。 我们再来定义一个 Person 类的子类 Teacher,代码如下。 源文件:Teacher.java //Person 子类 class Teacher extends Person { // 部门 String department; // 教龄 int schoolAge; public Teacher() { System.out.println("构造器 Teacher()被调用"); } public Teacher(String name) { // 调用父类中的构造器 Person(String theName) super(name); System.out.println("构造器 Teacher(String name)被调用"); } public Teacher(int theSchoolAge) { schoolAge = theSchoolAge; } public Teacher(String dept, int theSchoolAge) { // 调用本类中重载的构造器 Teacher(int theSchoolAge) 第 6 章 面向对象编程进阶 135 4 this(theSchoolAge); department = dept; } // 初始化块 { department = "教务部"; System.out.println("Teacher 初始化块执行后:name=" + name + " ,age=" + age+ " ,sex=" + sex); } } 这个类中定义了 4 个构造器:一个不带参数的构造器;一个带一个 String 数据类型参 数的构造器,它通过 super()显式调用父类的构造器;一个带一个 int 数据类型参数的构造器; 一个带两个参数的构造器,通过 this()来调用类中带 int 类型参数的构造器。 最后我们来看测试程序。 源文件:TestInit.java public class TestInit { public static void main(String[] args) { System.out.println("------------------------------------"); Teacher t1 = new Teacher(); System.out.println(""); System.out.println("------------------------------------"); Teacher t2 = new Teacher("Tom"); System.out.println(""); System.out.println("------------------------------------"); Teacher t3 = new Teacher("财务部", 20); } } 这个程序通过 3 种构造器来创建 3 个 Teacher 对象,因为调用的构造器不同,所以对象 初始化的步骤也有所不同。因为在这几个程序中,几个关键部分都已经有信息打印到控制 台,所以,只要执行这个程序,就可以看出各个调用构造器创建对象的运行细节。 编译并运行 TestInit,可以在控制台上得到如下的信息: ------------------------------------ Person 初始化块执行后:name=Tony Blair ,age=50 ,sex=Female 构造器 Person()被调用 name=Tony Blair ,age=50 ,sex=Male Teacher 初始化块执行后:name=Tony Blair ,age=50 ,sex=Male 构造器 Teacher()被调用 ------------------------------------ Person 初始化块执行后:name=Tony Blair ,age=50 ,sex=Female 构造器 Person(String theName)被调用 name=Tom ,age=50 ,sex=Female Teacher 初始化块执行后:name=Tom ,age=50 ,sex=Female 构造器 Teacher(String name)被调用 Android 系统下 Java 编程详解 136 4 ------------------------------------ Person 初始化块执行后:name=Tony Blair ,age=50 ,sex=Female 构造器 Person()被调用 name=Tony Blair ,age=50 ,sex=Male Teacher 初始化块执行后:name=Tony Blair ,age=50 ,sex=Male 请读者参考上面列出的对象初始化的 6 个步骤和本程序的执行结果,充分理解和掌握 对象初始化的过程。 6.7 封装类 6.7.1 知识准备:Ja va 中的封装类 虽然 Java 语言是典型的面向对象编程语言,但其中的 8 种基本数据类型并不支持面向 对象的编程机制,基本类型的数据不具备“对象”的特性——没有属性、没有方法可调用。 沿用它们只是为了迎合程序员根深蒂固的习惯,并能简单、有效地进行常规数据处理。 这种借助于非面向对象技术的做法有时也会带来不便,比如引用类型数据均继承了 Object 类的特性,要转换为 String 类型(经常有这种需要)时只要简单调用 Object 类中定 义的 toString()即可(关于 toString()方法,参见 6.7.2 节内容),而基本数据类型转换为 String 类型则要麻烦得多。为解决此类问题,Java 语言引入了封装类的概念,在 JDK 中针对各种 基本数据类型分别定义相应的引用类型,并称之为封装类(Wrapper Classes)。 所有的封装类对象都可以向各自的构造器传入一个简单类型数据来构造: boolean b = true; Boolean B = new Boolean(b); byte by = ’42’; Byte By = new Byte(by); int i = 123; Integer I = new Integer(i); … 除了 Character 外,还可以通过向构造器传入一个字符串数据来构造,如果传入的字符 串不能用于表示对应的值,除了 Boo le an 类型外,将会抛出一个 NumberFormatException 异常: Boolean B = new Boolean("true"); Boolean B1 = new Boolean("a");// 对,不抛出异常 try { Byte By = new Byte("42"); Short S = new Short("121212"); Integer I = new Integer("123456789"); 第 6 章 面向对象编程进阶 137 4 // … } catch (NumberFormatException e) { e.printStackTrace(); } 封装在封装类中的值,可以通过各自的 xxxValue()方法来转换成简单类型。  Boo le an:public boolean booleanValue()。  Byt e:public byte byteValue()。  Character:public char charValue()。  Double:public double doubleValue()。  Float:public float floatValue()。  Integer:public int intValue()。  Long:public long longValue()。  Short:public short shortValue()。 下面我们来看一个封装类的例子。 源文件:WrapperClass.java public class WrapperClass { public static void main(String[] args) { Integer i = new Integer(10); Integer j = new Integer(10); System.out.println(i == j); } } 在这个类中,创建了两个 in t 的封装类 Integer 对象,并且比较它们是否相等。运行这 个程序,将在控制台上输出: false 可以从结果看出,它们并不相等。这是因为,i 和 j 各自指向的对象是不一样的。 6.7.2 知识准备:自动拆箱和装箱 在 JDK 5. 0 中,引入了自动装箱/拆箱(Autoboxing/Unboxing)功能,可以让我们方便 地在简单类型和对应的封装类型数据之间转换,例如,我们来看下面这个例子: Integer iObject = 100; 这行代码在 JDK 5. 0 之前是非法的:不能将一个简单类型的数据赋值给引用类型变量, 而在 JDK 5. 0 中,通过自动装箱功能,可以自动进行“装箱”——将简单类型数据“装” 到对应的封装类型中。相反的,通过自动拆箱功能,可以将封装类型的数据赋值给对应简 单类型变量,例如: int i = new Integer(100); Android 系统下 Java 编程详解 138 4 这个功能带来的一个直接好处就是,以后在那些本来只接收引用类型数据的方法中, 可以直接使用简单类型数据,而不再需要先在程序中将它转换成引用类型数据。例如,下 面的方法在 JDK 5. 0 之前是非法的,而在 JDK 5.0 中,它是合法的: public class TestBoxing{ public void test(Object o) { System.out.println(o); } public static void main(String[] args){ TestBoxing tb = new TestBoxing(); tb.test(100); //自动装箱 } } 在这个例子中,我们定义了一个 test()方法,它有一个参数 Object,如果在 JDK 5. 0 之 前,在调用这个方法的时候,必须传递一个引用类型的数据给它,而在 JDK 5. 0 中,可以 直接给它传递一个简单类型数据。 另外,在 JDK 对 5.0 中,使用自动装箱的时候还有一个问题需要特别注意,下面我们 来讨论这个问题。 在 Java 中,为了节省创建对象的时间和空间,对于一些常用的对象,会将它在内存中 缓存起来。在前面,我们已经学到了 String 对象就是这样的,当直接使用“String s = "str"” 这种形式来产生 String 对象时,如果在内存中已经有一个使用这种方式产生的字符串对象, 那么就不会再新建对象,而是直接使用已经存在的那个 String 对象。而实际上,对于如下 范围内的简单数据类型:  boolean 类型的值。  所有 byte 类型的值。  在-128~127 之间的 short 类型的值。  在-128~127 之间的 int 类型的值。  在\u0000~\u007F 之间的 char 类型的值。 它们在使用自动装箱转换成相关封装类型对象的时候,其行为也和 String 类似。上面 列表范围中的数据在进行自动装箱的时候,将首先检查内存中是否已经有使用自动装箱产 生的具有相同值的对象。如果已经有一个“值”相同的对象存在,那么,并不会产生新的 对象。这个机制和使用 String s = "test"这种方式产生一个字符串对象类似。也就是说,当简 单类型的数据是上面列表中的数据类型和对应范围内的值的时候,使用自动装箱得到的对 象在内存中可能已经存在,而不是新产生的,就跟 String 类型数据一样。 例如,我们来看下面的例子。 源文件:TestAutoBoxing.java public class TestAutoBoxing{ public static void main(String[] args){ Integer t1 = new Integer(127); Integer t2 = new Integer(127); System.out.println("t1 == t2 ? "+(t1 == t2)); 第 6 章 面向对象编程进阶 139 4 Integer t3 = 127; Integer t4 = 127; System.out.println("t3 == t4 ? "+(t3 == t4)); System.out.println("t1 == t4 ? "+(t1 == t4)); Integer t5 = 128; Integer t6 = 128; System.out.println("t5 == t6 ? "+(t5 == t6)); } } 编译并运行这个程序,将得到如下的输出: t1 == t2 ? false t3 == t4 ? true t1 == t4 ? false t5 == t6 ? false 对于 t1 和 t2 的关系,相信读者很容易就可以得出正确的判断。而对于 t3/t4 及 t5/t6 的 关系,可能不是那么容易就能得到正确答案了。根据上面所述的规则,当 int 类型数据在 -128~127 之间的时候,它通过自动装箱所产生的 Integer 对象会缓存在内存中,而当试图通 过自动装箱方式产生另一个相等值的 Integer 对象的时候,系统将不会重新生成新的对象, 而是直接使用内存中已经存在的 Integer 对象。当数值范围不在该数据类型所对应的范围内 的时候,自动装箱产生的数据等同于用 new 方式产生的数据。 提示: 在 JDK 5.0 以后,原来很多只接收对象参数的类、方法,现在可以接收简单类型数据了,这并不 意味着这些类、方法改变了它们的行为方式,而是自动装箱功能帮我们自动完成了将简单类型 数据转换成对应引用类型数据的动作。 6.7.3 知识拓展:在 Ja va 中实现小数的精确计算 我们在编写 Java 程序的时候,可能经常需要用到小数,例如,给某个厂商编写一个采 购平台,需要计算货物的价格等。 现在假设有两个商品,价格分别为 0.05 元和 0. 01 元,需要计算这两个商品的价格的和, 并且将它打印出来。我们可以编写如下的代码: System.out.println(0.05+0.01); 但是,当你执行上面的代码时,将会得到下面的输出: 0.060000000000000005 显然,这个输出并不是我们所期望的结果。为什么会出现这样的情况呢?这是因为计 算机中的数字表示的方式原因(所有的数值都需要转换成二进制数),0. 05 不能被精确表示 为一个 double(默认情况下,小数类型数据为 double 类型)类型数据,而是被表示为最接 近它的 double 值。因此,对于需要精确运算结果的地方,请勿使用 double 或者 f loat 类型 的数据来表示。要实现数值的精确运算,有两种选择:使用 int 或者 long 类型数据,得到 Android 系统下 Java 编程详解 140 4 最后结果后,再除以适当的 10 的倍数,来获得精确的小数。或者使用 BigD ecimal 来进行 精确的小数运算。对于 int 或者 long 类型数据的运算,在此不再赘述,本节主要讨论如何 使用 BigDecimal 进行精确的小数运算。 BigD ecimal 位于 java.math 包中,它有 4 个构造器,其中,有两个接收 BigInteger 参数, 而我们要用的是使用 String 或者 double 类型参数的构造器,但是,基于上面的理由,不要 使用 double 类型参数的构造器来构造 BigDecimal 对象,而要使用 String 类型参数的构造器。 在这个类上定义了很多进行加减乘除运算的方法:add()、subtract()、multip ly( )、div ide(), 另外还有进行小数点移位运算的 movePointLeft()和 movePointRight()等。 我们来看一个使用 BigDecimal 来实现精确运算小数的例子。 源文件:TestFloat.java import java.math.BigDecimal; public class TestFloat{ public static void main(String args[]){ //直接使用 double 类型数据进行运算 System.out.println(0.05+0.01); //使用 BigDecimal 的 double 参数的构造器 BigDecimal bd1 = new BigDecimal(0.05); BigDecimal bd2 = new BigDecimal(0.01); System.out.println(bd1.add(bd2)); //使用 BigDecimal 的 String 参数的构造器 BigDecimal bd3 = new BigDecimal("0.05"); BigDecimal bd4 = new BigDecimal("0.01"); System.out.println(bd3.add(bd4)); } } 编译运行这个程序,可以得到如下输出: 0.060000000000000005 0.06000000000000000298372437868010820238851010799407958984375 0.06 从这个例子中可以看出,如果使用 double 类型参数的构造器来获得 Big Decimal 对象进 行运算,也可能得不到想要的结果,而如果使用 String 参数的构造器来获得 BigDecim al, 将可以得到正确的运算结果。 6.8 本章小结 本章详细介绍了类的继承的概念,着重介绍了 super 和 this 关键字的使用,并介绍了重 载和覆盖的概念,揭示了对象初始化的细节。通过本章的学习,读者对类的继承应该有了 一定的了解和掌握,并掌握了对 toString()方法、equals()方法的使用和覆盖,这些在今后的 第 6 章 面向对象编程进阶 141 4 编程中是常常会用到的。继承是 Java 面向对象的一大特点,在以后的学习中读者们会有深 入的体会。 课后练习题 一、选择题 1.关于继承,下列说法正确的是( )。 A.Java 中的类是单继承的,但接口可以继承多个接口 B.一个类只能有一个子类 C.子类引用可以指向一个父类对象 D.子类可以继承父类所有的属性和方法 2.关于方法重写(Override),下列说法正确的是( )。 A.方法重写是指一个类里面有多个同名的方法 B.方法重写是指子类和父类有同名的方法,并且参数列表和返回类型都完全一样 C.方法重写是指子类和父类有同名的方法,但要有不同的参数列表 D.重写的方法可以比原方法有更严格的访问权限 3.关于方法重载(Overload),下列说法正确的是( )。 A.方法重载是子类和父类有同名的方法。 B.实现方法重载必须是方法名相同,参数列表不同 C.可以用返回值不同来区分两个重载的方法 D.重载的方法可以相互调用 4.下列说法正确的是()。 A.用 protected 修饰的属性只能被同一个包中的类访问 B.不加任何访问修饰符的属性只能被同一个类及子类访问 C.protected 和 private 不可以用来修饰类 D.protected 和 private 和 public 既可以修饰类,又可修饰属性 5.下列说法正确的是( )。 A.每个子类对象创建时都会默认调用父类的所有构造器 B.每个子类对象创建时都会默认调用父类的无参构造器 C.子类中不能显式调用父类的构造器 D.子类中显式调用了父类的某个构造器后,就不会再调用父类的无参构造器了 二、简答题 1.简述 Java 中的继承。 2.简述 Java 中有哪些访问控制修饰符及各自的作用。 3.简述方法覆盖和方法重载的区别。 Android 系统下 Java 编程详解 142 4 三、编程题 1.创建一个 An imal 类,它有一个默认构造器,在默认构造器中输出“I am an animal”。 创建一个 An imal 类的对象。 2.创建一个 Dog 类,它继承 Anima l 类,也有一个默认构造器,在默认构造器中输出 “I am a dog”, 创建一个 Dog 类的对象。 3.在 Dog 类中添加一个重载的构造器,接收一个字符串 dogname,在构造器中调用默 认构造器,并输出“myname is”加上你接收到的参数。
还剩97页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 7 金币 [ 分享pdf获得金币 ] 6 人已下载

下载pdf

pdf贡献者

njcdh

贡献于2013-01-02

下载需要 7 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!