ITPub博客

首页 > 应用开发 > Javascript > OCA Java SE 8程序员认证考试指南(Exam 1Z0-808)

OCA Java SE 8程序员认证考试指南(Exam 1Z0-808)

原创 Javascript 作者:qinghuawenkang 时间:2018-10-22 14:46:51 0 删除 编辑


OCA Java SE 8 程序员认证
考试指南(Exam 1Z0-808)
[美]
凯西·西拉(Kathy Sierra)
伯特·贝茨(Bert Bates)

陶佰明 译
北 京

Kathy Sierra, Bert Bates
OCA Java SE 8 Programmer I Exam Guide(Exam 1Z0-808)
EISBN: 978-1-260-01139-5
Copyright © 2017 by McGraw-Hill Education.
All Rights reserved. No part of this publication may be reproduced or transmitted in any form or by any means,
electronic or mechanical, including without limitation photocopying, recording, taping, or any database, information
or retrieval system, without the prior written permission of the publisher.
This authorized Chinese translation edition is jointly published by McGraw-Hill Education and Tsinghua University
Press Limited. This edition is authorized for sale in the People’s Republic of China only, excluding Hong Kong,
Macao SAR and Taiwan.
Translation copyright © 2018 by McGraw-Hill Education and Tsinghua University Press Limited.
版权所有。未经出版人事先书面许可,对本出版物的任何部分不得以任何方式或途径复制或传播,包括但不限
于复印、录制、录音,或通过任何数据库、信息或可检索的系统。
本授权中文简体字翻译版由麦格劳-希尔(亚洲)教育出版公司和清华大学出版社有限公司合作出版。此版本经授
权仅限在中国大陆区域销售,不能销往中国香港、澳门特别行政区和中国台湾地区。
版权©2018 由麦格劳-希尔(亚洲)教育出版公司与清华大学出版社有限公司所有。
北京市版权局著作权合同登记号 图字: 01-2017-8967
本书封面贴有 McGraw-Hill Education 公司防伪标签,无标签者不得销售。
版权所有,侵权必究。侵权举报电话: 010-62782989 13701121933
图书在版编目(CIP)数据
OCA Java SE 8程序员认证考试指南: Exam 1Z0-808 / (美)凯西·西拉(Kathy Sierra) , (美)伯特·贝茨(Bert
Bates)著;陶佰明 译. —北京:清华大学出版社, 2018
书名原文: OCA Java SE 8 Programmer I Exam Guide(Exam 1Z0-808)
ISBN 978-7-302-50381-1
Ⅰ. ①O… Ⅱ. ①凯… ②伯… ③陶… Ⅲ. ①JAVA语言-程序设计-资格考试-自学参考资料
Ⅳ. ①TP312.8
中国版本图书馆CIP数据核字(2018)第123040号
责任编辑: 王 军 于 平
封面设计: 牛艳敏
版式设计: 思创景点
责任校对: 孔祥峰
责任印制: 董 瑾
出版发行: 清华大学出版社
网 址
地 址 :北京清华大学学研大厦 A 座 邮 编 : 100084
社 总 机 : 010-62770175 邮 购 : 010-62786544
投稿与读者服务 : 010-62776969, c-service@tup.tsinghua.edu.cn
质 量 反 馈: 010-62772015 zhiliang@tup.tsinghua.edu.cn
印 装 者: 北京鑫丰华彩印有限公司
经 销: 全国新华书店
开 本: 185mm×260mm 印 张: 20 字 数: 512 千字
版 次: 2018 年 8 月第 1 版 印 次: 2018 年 8 月第 1 次印刷
定 价: 69.80 元
——————————————————————————————————————————————
产品编号: 077886-01

译 者 序
作为 IT 行业的领导者, Oracle 的认证考试颇具分量。 Oracle 认证考试由浅入深分为 OCA、
OCP 和 OCM 三个级别。同时, Oracle 认证考试有不同的科目,如 Oracle DBA 认证、 Oracle
网络应用开发人员认证等。而本书是关于 Oracle OCA Java SE 8 程序员认证考试的一本考试
指南。
本书严格遵循了 OCA Java SE 8 程序员认证考试的考试大纲,侧重介绍变量、类和接口的
定义, 也详细介绍了数组、 异常处理、 封装、 多态和流程控制, 同时涵盖了对 String 和 ArrayList
的基础 API 的介绍,以及对 Java 8 新内容的部分介绍。
在最初接手本书的翻译工作时,我期许的目标是通过翻译本书,重新复习 Java 的基础知
识。事实也确实如此。本书的内容非常基础,哪怕是 Java 初学者,也可以由浅入深、逐步理
解书中的内容。
虽然如此,但是本书并不像 Java 教材那样面面俱到。它是完全为 Oracle Java 认证考试量
身定制的辅导书。它介绍的每一个知识点,都源自于认证考试的考点。它超强的针对性能够提
高考生在准备认证考试时的效率。此外,本书中还包含了大量的示例和考试模拟练习题,这些
示例和练习题能够模拟真正考试的内容。让你早一步接触考试的内容,把握题目的难度,甚至
体验试题的出题形式。
对于能够熟练使用 Java 的人,甚至是资深的 Java 程序员,或许会认为本书的内容过于简

单。然而,如果你正在准备 OCA 认证考试,那么我仍然建议你通读本书。因为在实际编程中,
我们使用的 IDE 工具会帮助我们。而在考试中,我们并没有 IDE 工具。而出题者往往就会以
此为突破口,将一些非法的代码隐藏在复杂的逻辑中。通读本书,便会发现作者深谙出题者的
套路,在可能出现误导的地方,都做了相应的介绍。
此外,书中还会涵盖一些平时不注意,但是考试却会出现的题目。例如,对于对象 Integer
i1=1000 和 Integer i2=1000,判断 i1==i2 时,返回的结果是 false。这个结果并不意外,它考查了
自动装箱和引用变量比较。但是,如果将 1000 换成 10, i1==i2 的结果却是 true!你能想到原
因吗?阅读本书,定会让你受益良多。
在本书的翻译过程中,我要特别感谢清华大学出版社的编辑对本书的校验及审阅。同时,
如下人员也参与了本书的翻译工作:孙宇佳、汪刚、李超华、姜静、王田田、孙玉亮、侯珊珊、
高俊英、李显琴、邵丹、孙亚芳、佟秀风。
最后,我要特别感谢我的妻子,她默默的陪伴是支撑我完成本书的动力。
由于译者水平有限,翻译工作中可能会有不准确的内容,如果读者在阅读过程中发现失误
和遗漏之处,望多多包涵,并欢迎批评指正。敬请广大读者提供反馈意见,读者可以将意见发
到 bmtao0807@gmail.com,我会仔细查阅读者发来的每一封邮件,并一一回复。
译者

序 言
本书的主要目的是帮助读者备战 Oracle 的 OCA Java SE 8 Programmer 认证考试。
本书同时兼顾了认证考试内容的广度和深度。比如说,阅读本书后,你可能不会变成面向
对象编程的专家,但是如果你仔细地学习本书,并出色地完成自测题,你会对面向对象编程有
一个基本的理解,而且你会在认证考试上游刃有余。认真学习本书后,你应该会非常有信心地
面对 Oracle 认证考试的所有知识点。
本书内容
本书通过 6 章的内容,优化对 OCA 8 知识点的学习。我们尽可能地使章节内容与 Oracle
认证考试的内容平行,但是有时我们为了更好的学习效果,会将考点混在一起介绍,或者是在
某些地方重复介绍一些考点。
每章的结构安排
为使你更加关注重要的内容,我们对每章的结构进行了安排,用以突出重点,并为参加考
试提供有用的思路。下面介绍了每一章的内容安排:
● 每章都以认证目标为起始,这是读者在解答章节测验时需要运用的知识点。认证目标
以标题的形式声明了当前章节的目标,一目了然。
考试须知
“考试须知” 用于着重强调关于考试的信息和可能出现的陷阱。 因为我们曾经在命名试题
的团队中工作, 我们知道你们将要经历的事情。
● 实际经验部分讨论了认证考试知识点在实际应用方面的内容,这些内容可能不会出现
在认证考试中,但是在实际应用中会非常有用。
● 练习分布在各个章节中。它们能够帮助你掌握认证考试的重点内容。不要只是简单地
阅读习题,它们是需要手动实现的。而且这些练习实现起来应该并不难。边做边学是
掌握知识的有效方法。
● 课内讨论部分用于描述培训课堂中经常出现的问题。这部分内容能够为认证相关的课
题和产品相关的课题提供有价值的观点。它们能够指出常见的错误,并解决课堂讨论
中提出来的各种问题。
● 认证考试总结是章节内容的简要回顾,同时再次强调认证考试中的重点内容。
● 每章的最后都有一个两分钟冲刺,检查本章的主要知识点。可以用于考前最后一分钟
的复习。
● 自测题中的问题与实际认证考试相似,是多选题的形式。这些问题的答案和答案释义
在每章的最后。在学习完每章的内容之后,通过进行自我测试,可以加强对所学知识
的理解,同时还能熟悉认证考试问题的结构。

作 者 简 介
Kathy Sierra 曾经是 Java 5 和 Java 6 SCJP 考试的首席开发者,是 Sun 公司的“大师级培训
师”。在 1997 年,她建立的 JavaRanch.com(现在是 Coderanch.com)是世界上最大的 Java 社区
网站。她销量最好的 Java 书籍曾多次获得
Software Development Magazine 奖。同时,她也是
Oracle Java Champions 项目的创始人之一。
目前, Kathy 在不同的领域中开创高级培训项目,从马术到计算机编程。但是,将她所有
项目贯穿在一起,帮助学习者减少认知负荷。
Bert Bates 曾经是 Sun 公司的 Java 认证考试的首席开发者,这其中也包括 Java 5 和 Java 6
的 SCJP 考试。他也是 Oracle OCA 7 和 OCP 7 认证考试的首席开发者之一,同时参与了 Oracle
OCA 8 和 OCP 8 认证考试的开发。他曾是 Coderanch.com(曾经的 JavaRanch.com)的论坛版主,
而且做过 30 年的软件开发! Bert 是多部 Java 畅销书的合著者,他也是 Oracle Java Champions
项目的创始人之一。现在,本书已经撰写完成, Bert 打算回到乒乓球场上一展英姿,并重返马
场,骑上他漂亮的冰岛骏马。


技术复审团队简介
这是我们策划的第 5 个版本。我们处理的第 1 版针对的是 Java 2。之后,针对 SCJP 5、
SCJP 6,甚至是 OCA 7 和 OCP 7 认证考试,我们不断地更新版本,一直到现在的 OCA 8。每
一次更新,我们都荣幸地拥有一批出色的技术复审团队,他们是以 JavaRanch.com 为中心的技
术团队。在过去的 14 年中,我们并没有重写第 1 版教材,而是使它不断地“进化”。在最初针
对 Java 2 的版本中,书中的很多章节内容到现在仍然保留。在后续的几页中,我们要感谢很多
技术复审团队的成员,是他们让我们的知识经得起考验。
Java 2 技术复审团队简介
Johannes de Jong 是我们技术复审团队永远的领导者(他比我们所了解的任何人都有耐心)。
在 Java 2 版本中,他领导了我们史上最强大的队伍。同时,真诚地感谢如下志愿者,他们是如
此的博学、勤勉、耐心,以及挑剔!
Rob Ross、 Nicholas Cheung、 Jane Griscti、 Ilja Preuss、 Vincent Brabant、 Kudret Serin、 Bill
Seipel 、 Jing Yi 、 Ginu Jacob George 、 Radiya 、 LuAnn Mazza 、 Anshu Mishra 、 Anandhi
Navaneethakrishnan、Didier Varon、Mary McCartney、Harsha Pherwani、Abhishek Misra 和 Suman Das。

SCJP5 技术复审团队简介
我们不知道谁复审的时间最多,但是我们能够计算每一个人的修订量,而且为了展示我们
的超级明星,我们确实做了这样的计算。
我们的最高荣誉属于 Kristin Stromberg。每当我们看到一个正确使用的分号,都应该向
Kristin 致敬。下一位贡献者是 Burk Hufnagel, 他修改的代码比我们所关注的还多。 Bill Mietelski
和 Gian Franco Casula 捕获了每一个抛给他们的异常——干得漂亮! Devender Thareja 确保我们
没有使用多余的俚语,而 Mark Spritzler 保持了内容的幽默。Mikalai Zaikin 和 Seema Manivannan
抓住了每一步的内容, Marilyn de Queiroz 和 Valentin Crettaz 同时贡献了一流的性能。再加上
Marcelo Ortega、 Jef Cumps(一位退伍老兵)、 Andrew Monkhouse 和 JeroenSterken,一起组成了
我们的明星团队——感谢你们所有人。 Jim Yingst 曾经是 Sun 认证考试的创建团队中的一员,
他帮助编写和复审了本书中的一些非常晦涩的问题。
每当你读到一页干净的内容时,都要一如既往地感谢我们的复审者,而且如果你确实捕捉
到了一个错误,基本上可以肯定是作者的错误。当然,最后的一份感谢要送给 Johannes,感谢
你的管理!
SCJP6 技术复审团队简介
升级到 Java 6 的认证考试,就像是做了一个小的外科手术,我们在升级本书时,决定使用
同样方式的技术复审。最后,我们手动挑选了 JavaRanch 上的顶尖选手,作为 Java 6 版本的技
术复审者。
对于 Mikalai Zaikin,我们存有无尽的感激之情。 Mikalai 在 Java 5 的版本中扮演着重要的
角色,最终,他又在 Java 6 版本中帮助我们摆脱困境。我们还要感谢 Volha、 Anastasia 和 Daria,
感谢他们借给我们 Mikalai。他的建议和修订帮助我们大大提高了本书的质量。谢谢你, Mikalai!
Marc Peabody 帮助我们解决了双页眉的问题,为此他获得了特殊的荣誉!此外,为了帮助
我们处理 Sun 的新 SCWCD 考试, Marc 为本书的不同版本做出了很大的贡献——你为我们保
住了冬天的粮食! (顺便提一下,在最后,我们发现 Marc、 Bryan Basham 和 Bert 一起共享一个
终极 Frisbee 的权限! )
与其他很多复审者一样, Fred Rosenberger 不仅自愿花费大量时间贡献在 JavaRanch 上,同
时他还帮助我们处理本书。 Stacey 和 Olivia,感谢你们将 Fred 借给我们。
Marc Weber 负责 JavaRanch 最繁忙的几处版块。 Marc 了解他的工作内容,而且他还发现
了隐藏在本书中的一些难以发现的问题。我们非常感谢 Marc 的帮助。我们需要警告你们所有
人——他拥有一个相位器!
最后,我们要感谢 Christophe Verre——前提是如果我们能找到他。仿佛 Christophe 在全球
不同的地点内履行他在 JavaRanch 上的责任,包括法国、威尔士,以及近期的东京。 Christophe
不止一次地保护了缺少组织的我们。感谢你的耐心, Christophe!需要知道的是,这些人都将
他们的复审报酬贡献给了 JavaRanch。 JavaRanch 社区欠了你们的债。

OCA 7 OCP 7 团队
贡献作者
OCA 7 认证考试基本上是 SCJP 6 考试主要内容的重新包装。在另一方面, OCP 7 考试引
入了很多新的话题。我们招募了一些新的人才,来帮助我们覆盖 OCP 7 中的新内容。感谢 Tom
McGinn 出色的工作,他为我们编写了 JDBC 章节。几位复审员告诉我们, Tom 纠正了我们在
整本书中使用的非正式用语。接着,感谢 Jeanne Boyarsky。 Jeanne 是这个项目的一位真正的拯
救者。她贡献了若干 OCP 的章节;为大师级考试编写了一些问题;她还做了一些项目管理工
作;而且这还不够,她还是我们最有精力的几位技术复审者之一。 Jeanne,我们对你感恩不尽!
此外, 感谢 Matt Heimer 所做的杰出贡献, 一个相当艰难的主题被完美解决!最后, Roel De Nijs
和 Roberto Perillo 也为本书做出了杰出的贡献,并帮助了技术复审团队——感谢你们!
技术复审团队
Roel,我们能说什么?作为技术复审者,你的工作是无与伦比的。 Roel 捕捉到了非常多的
技术错误,让我们天旋地转。 Roel 仔细斟酌每一页,从未失去他的焦点,使本书日臻完美。感
谢你, Roel!
此外, Jeanne 为我们提供了我们从没接收过的最详尽的技术复审(我们怀疑她招募了一队
机器人杀手在帮助她)。
看起来,如果没有老朋友 Mikalai Zaikin 的帮助,我们一本书也完不成。不知道为什么,
在获取了 812 个不同的 Java 认证,做一位好丈夫和好父亲(感谢 Volha、 Anastasia、 Daria 和 Ivan),
甚至是做一名理论上的渔夫的同时, Mikalai 仍然能够持续地为本书做出贡献,保证本书的质
量。有你的帮助,是我们的荣幸, Mikalai!
接下来,我们要感谢 Vijitha Kumara,他是 JavaRanch 的版主以及杰出的技术复审者。在漫
长的著书过程中,我们获得了很多复审者的帮助,但是 Vijitha 是为数不多的几人中,能够陪着
我们一直从第 1 章到第 15 章结束的复审者。 Vijitha,谢谢你的帮助和你的坚持!
最后,感谢我们复审团队的其他成员: Roberto Perillo, Jim Yingst(这是第 4 次提到你? ),
其他重复提到的人: Fred Rosenberger、 Christophe Verre、 Devaka Cooray、 Marc Peabody, 以及
新人 Amit Ghrpade——谢谢你们!
关于 OCA 8 技术复审团队
由于作者的“飞行员差错”,本书的复审团队的日程工作计划比想象中更紧张。我们非常
感谢这个 6 人成员的复审团队。他们根据临时通知迅速作出反应,为本书的质量做出了不计其
数的贡献。我们所有的复审者都是世界上最棒的 Java 社区网站(Coderanch.com)的版主,这几乎
已成为一种“规范”。
我们第一个要提到的人是 Campbell Ritchie,他是 Coderanch 的长期版主,是一名善于攀爬
陡峭山崖的徒步旅行者。而且根据统计,他是本书中最多产的复审者。换言之,他找到的错误

最多。在这些工作中, Compbell 是异常状态和编程方面的专家。每一次读到无误的页面时,都
应该想起这位复审者。
在“发现错误”的竞争者中,我们要介绍 Pawel Baczynski 和 Fritz Walraven。 Pawel 声称自
己的家乡是波兰。他以自己的妻子 Ania 和孩子 Szymek、 Gabrysia 为荣。
Fritz 来自于荷兰,如果我们理解正确,他为孩子们在阿默斯福特市体育馆展示的超凡的运
动技术而欢呼。 Fritz 同时是乌干达一所孤儿院的志愿者。 Fritz,如果我们有机会见面, Bert 可
能会向你发起一场乒乓球赛挑战。
我们要大声感谢 Fritz 和 Pawel,因为从第 1 章到第 2 个实战测试,他们一直跟随我们。这
是一场马拉松,我们非常感谢你们。
接下来,我们要感谢回归的复审员 Vijitha Kumara。是的,他曾经帮助过我们,而现在又
自愿回来了。当 Vijitha Kumara 不在旅行或徒步时,他非常享受他所谓的“疯狂的实验”。我们
爱你, Vijitha——不要把自己炸掉!
当我们需要 Tim Cooke 时,他就出现了。他在最开始和收尾时帮助了我们。我们喜欢 Tim,
虽然大家知道他花了大量时间在可恶的“函数设计”上(我们怀疑他就是因为这个原因,在本
书的中间章节中消失的)。Tim 生活在爱尔兰, 很年轻时就开始在 Amstrad CPC 464 上编写程序。
最后,要向技术复审团队的另一位老兵致谢,他是 Roberto Perillo。感谢你回来,再次帮
助了我们。 Roberto 是一位顾家的男人,他喜欢和他的儿子 Lorenzo 一起相处。当 Lorenzo 上床
睡觉后, Roberto 会弹一会吉他,或者为圣保罗的“足球俱乐部”欢呼。
你们是最棒的。谢谢你们完美的协助。

致 谢
Kathy 和 Bert 向以下所有人表示感谢:
● 感谢所有在 McGraw-Hill Education 辛勤工作的人: Tim Green(他已经与我们一起工作
了 14 年)、 Lisa McClain 和 LeeAnn Pickrell。感谢你们的帮助。你们的反应速度、耐心、
灵活的思维和专业的知识,组成了我们所希望的最棒的团队。
● 感谢所有在 Krafture 的朋友(和其他通过马术相识的朋友), 特别是 Sherry、 Steinar、 Stina
和 Kacie、 DJ、 Jec、 Leslie, 以及 David、 Annette 和 Bruce、 Lucy、 Cait、 Jennifer、 Gabrielle、
Mary,还有 Pedro 和 Ely。
● 感谢最初帮助我们的一些软件专家和朋友: Tom Bender、 Peter Loerincs、 Craig
Matthews、 Leonard Coyne、 Morgan Porter 和 Mike Kavenaugh。
● 感谢 Dave Gustafson 给我们带来的持续支持,以及他的洞察力和对我们的辅导。
● 感谢我们在 Oracle 最棒的联系人和朋友 Yvonne Prefontaine。
● 感谢我们伟大的朋友和专家: Simon Roberts、 Bryan Basham 和 Kathy Collina。
● 感谢 Stu、 Steve、 Burt 和 Marc Hedlund,他们为整个过程注入了更多的乐趣。
● 感谢 Eden 和 Skyler,他们震惊地认为辍学的人应该更努力地学习本书,从而通过认证
考试。

● 感谢 Coderanch Trail 的老板 Paul Wheaton,感谢他努力地经营这个最好的 Java 社区网
站。感谢所有慷慨和耐心的 JavaRanch 人和 JavaRanch 版主们。
● 感谢那些曾经和现在为我们传授 Java 知识的人,他们的帮助,使我们认识到学习 Java
是一个有趣的经历。这些人包括 Alan Petersen、 Jean Tordella、 Georgianna Meagher、
Anthony Orapallo、 Jacqueline Jones、 James Cubeta、 Teri Cubeta、 Rob Weingruber、 John
Nyquist、 Asok perumainar、 Steve Stelting、 Kimberly Borrow、 Keith Ratlif,还有这颗
蓝色星球上最关心和鼓舞 Java 的人——Jari Paukku。
● 感谢我们的挚友: Eyra、 Kara、 Draumur、 Vafi、 Boi、 Niki 和 Bokeh。
● 最后,感谢 Eric Freeman 和 Beth Robson,谢谢你们一直以来的鼓励。

前 言
本书的组织结构
本书的内容面向深度复习 OCA 8 认证考试的人群,包括资深的 Java 专家和 Java 技术的初
学者。每一章至少都覆盖认证考试的一个方面,强调 Java 编程中的“为什么”和“如何做”。
本书下载资源中包含了两个 80 个问题的测试。
本书不包含的内容
本书并不包含 Java 的初学者手册。 本书的所有内容都专注于考试内容。如果你从未学习
过 Java,我们建议你在 Java 基础知识上多花些时间。在没有了解如何编写、编译和运行简单
的 Java 程序之前,不建议阅读本书。而且,我们不介绍每一个主题所需要的先验知识。而另
一方面,对于任一主题(主题内容严格按照实际考试目标制定),我们假定读者并未学习过该主
题,并以此为前提来准备本书的内容。即,我们假定读者不具备各个主题相关的知识,但是具

备基础的 Java 知识。
同时,本书的目的并不是让读者同时掌握考试内容和 Java 技术。这是一个认证考试的学
习指南,它的目的性非常明确。这并不是说准备 Java 认证考试,对成为一名 Java 程序员毫无
帮助。相反,即便是非常资深的 Java 开发人员,他们通常也会认为学习认证考试内容能够拓
宽他们的知识面,丰富编程经验。
建议
在读完本书后, 留一些空闲时间做一次全面复习。你可能会使用如下方法复习本书的内容:
(1) 重读所有的“两分钟冲刺”,或者是让别人考你这部分知识。这些内容也可以作为你考
前死记硬背的知识点。你可以考虑针对这些内容做一些学习卡片。
(2) 重新阅读所有的考试须知。 记住,这些考试须知来自于认证考试出题者之手。他们知
道你将会面临的是什么,以及你需要小心的内容是什么。
(3) 重做自测题。在学习每一章后,完成自测题,有助于利用这些问题加强所学知识点。
然而,更好的办法是先不做测试,而是在读完全书后,一次性将所有测试做完。就像是在完成
认证考试一样(每次测试时,在单独的一张草稿纸上记录答案,这样在掌握内容之前,你可以
一遍遍地重复测试)。
(4) 完成练习。我们对练习的设计涵盖了认证考试的考试内容,学习本书最好的方法是实
战练习。在每一步练习中,要确保了解它的真正含义。如果有知识点不够清楚,重新阅读当前
章节的内容。
(5) 多编写 Java 代码。我们会多次强调这个建议。当编写此书时,我们编写了几百个 Java
小程序用来做研究。根据通过认证考试的考生的反馈,我们了解到几乎所有的考生中,在学习
期间编写代码的考生的成绩都非常好。在尝试本书的代码时,可能会创建无数的编译错误——
抛开 IDE,打开命令行,编写代码吧!
本书内容介绍
OCA 8 考试是 IT 行业最难的认证考试之一。而且很多考生都在毫无准备的情况下参加考
试。通常作为程序员,我们只需要在疯狂的最后期限之前完成我们手中的项目。
但是这个认证考试的目的,是要证明你对 Java 知识的全面理解,不仅是熟悉工作中使用
的部分。
仅靠经验不足以让你通过考试,因为你的理解可能与事实存有偏差。同时,仅仅是让手上
的代码成功运行是不够的,你需要深度理解核心功能,并且在广度上能够覆盖使用语言时可能
出现的任何情况。
对认证考试感兴趣的人
雇主、猎头、程序员,都很在意这个认证考试。通过考试,能够向现在或者未来的雇主证
明三件事情: 你很聪明; 你知道如何学习和准备具有挑战性的测试; 而最重要的是, 你了解 Java

前 言 XV
语言。如果雇主面前出现两个候选人,分别是通过认证考试的人和未通过认证考试的人,雇主
深知通过考试的人不需要再花费额外的时间学习 Java 语言。
但是,这能够说明你可以熟练使用 Java 语言做软件开发了吗?并不是这样的,但这至少是
一个好的开端。想要真正展示你的开发能力(对比对语言知识的掌握能力),你应该考虑参加 Java
Developer Exam,在这个考试中,你的试题是创建并完成一个项目,然后提交给评审员评分。
参加程序员的考试
在理想世界中,对知识掌握的评估,不能是简单地回答若干个测试问题。但是,人生本就
不够完美,而且对每一个人的知识进行一对一的检测,也并不现实。
对于多数的 Oracle 认证, Oracle 对考生的评估使用的是基于电脑的测试系统。为了抵制简
单的死记硬背, Oracle 认证考试为不同的考生提供不同的问题。在试题的开发过程中,基础测
试者对成百上千个问题进行编译和修订。从这个庞大的题库中,抽取每一个主题的问题,组成
不同版本的考试。
每个 Oracle 考试都有固定数量的问题,考试的时长非常充裕。考试的剩余时间显示在屏幕
的角落。如果在考试过程中超时,测试将自动终止,未完成答案视为错误答案。
考试须知
很多经验丰富的考生都不会回到已完成的题目上更改答案,除非有非常确定的原因。只有
当读题有误或理解问题有误时,才会更改答案。紧张可能会让你对答案做出第二次猜测,从而
将正确答案改为错误答案。
在完成考试后,你会收到来自 Oracle 公司的一封电子邮件,告知考试结果已经公布在网站
上。截至 2017 年冬季,考试成绩都可以在 certview.oracle.com 网站查询。如果需要打印证书,
可以提出特殊申请。
问题的形式
Oracle Java 考试的内容以多选题的形式呈现。
多选题
在早期的考试中,当遇到多选问题时,是不显示有多少个正确答案的。但是在后续的版本
中,问题变得越来越难。因此,在现在的考试中,每一道多选题都标明了正确答案的个数。本
书每一章最后的自测题中,严格遵循实际考试中的问题格式、措辞和难度,但是有两处例外:
● 我们的问题尽量不标明有多少个正确答案(我们会说“选择所有正确的答案” )。我们的
目的是让你能够掌握书中的内容。当标明正确答案的个数时,有些聪明的考生能够从
中排除错误答案。而且,如果正确答案的个数已知,就可以选择最可能正确的几个答
案。我们的目的是锻炼你,让你在真正的考试中更加游刃有余。
● 真正的考试往往在问题中标注代码的行数。有时,我们不会为代码标记行数——因此,
在多数情况下,我们有更多的空间在关键点上添加备注。在实际考试中,如果代码的

起始行是第 1 行,这意味着你在看整篇代码。如果代码的起始行比 1 大,这意味着你
在看的是一部分源代码。在查看部分源代码时,其前提是未呈现的其他代码部分是正
确的(例如,除非有明显的说明,否则我们认为呈现的部分源代码对应的源码中,有正
确的 import 和 package 语句)。
考试须知
当你发现自己被某个多选问题卡住时,使用你的草稿纸(或白板)写下你认为最有可能正确
的两三个答案,然后标记出你认为最有可能正确的答案。例如,下面是参加考试的一份草稿纸
上的内容:
● 21. B 或 C
● 33. A 或 C
这在标记当前问题后继续做题时,是非常有用的。之后当你重新考虑这个问题时,能直接
拾起最初做题时的思路。使用这个方法,可以避免重读问题或重新思考问题。在遇到复杂的、
基于文本的问题时,也需要使用草稿纸创建可视的内容,从而更好地理解问题。这个方法对于
视觉学习者尤为有效。
参加考试的小窍门
对于每次考试来说,问题的数目和及格分数都是可变的。在参加考试之前,一定要去
网站上了解清楚。
在整个考试过程中,可以使用任何顺序答题,也可以随时回到任意已经回答的题目上检查
答案。对于错误的答案,并不会倒扣分,所以宁可答错也不要跳过题目。
一个好的策略是第一遍快速浏览并回答所有问题,然后再返回处理。在回答一个问题时,
你的思路可能还停留在上一个问题上。
要仔细对待示例代码。首先检查语法错误:分别计算大括号、分号、小括号的数量,然后
确保括号都是成对出现的。在读懂代码功能之前,查找大小写错误,以及其他类似的语法错误。
考试中的很多问题都可能在隐蔽的语法上做文章。需要掌握周密的 Java 语言知识,才可
以成功。
这也给我们带来了其他考生反馈的另一个问题。测试中心应该为考生提供充足的设备用于
编写代码实现,使考生能够在“纸上”解决问题。在某些情况下,测试中心提供不充足的马克
笔和擦写板。它们不仅小,而且用起来也非常笨重。我们建议在考前致电考试中心,询问是否
会提供足够大的擦写板,和足够细的马克笔,以及好用的板擦。我们鼓励每一个人都向 Oracle
公司和考试机构抱怨,使他们提供真正的铅笔和若干空白纸张。
准备考试的小窍门
首先,给自己足够的学习时间。 Java 是一门复杂的编程语言。不能期望仅在一次学习中就
可以记下所有需要掌握的知识。这是一个通过不断学习和实践才能掌握的科目。为自己建立一
个学习计划并坚持执行。但是要注意合理安排时间,特别是还有正常的工作职责的考生。

前 言 XVII
准备认证考试的一个简单技巧,是使用每天 15 分钟的方法。每天最少学习 15 分钟。这个
时间不长,但却是一个重要的承诺。如果在某一天无法集中精神学习,那么就在 15 分钟后结
束。如果某天的状态好,就学习更长的时间。如果状态好的次数更多,你就越容易成功。
在准备考试时,我们强烈建议准备学习卡片。每个卡片可以是简单的 3cm×5cm 或 4cm×
6cm 的索引卡片,正面是问题,背面是答案。在每学习一个章节时,都制作一定数量的卡片,
将你认为重要的知识点记在上面。你可以阅读卡片正面的问题,思考问题的答案,并翻阅背面
的答案做对比检验。或者你也可以让其他人拿着卡片,然后验证你的答案是否正确。大多数学
生都认为这是一个非常有效的方法,特别是它的灵活性。使用它可以在任何地点学习。当然,
最好不要在开车时学习,除非是在等红灯。我们曾经带着这些卡片出现在很多地方——医生的
诊室、餐厅、影院等一切你能说得出名字的地方。
认证考试的学习小组是另一个极好的资源, Coderanch.com 的 Big Moose Saloon 认证论坛是
最大最理想的社区。如果被本书中的问题,或者是任何其他模拟考试的问题难住了, 你可以在
认证考试论坛上发表问题,你会在一天之内收到针对所有情况的答案——通常是在几小时之内
就会收到答案。
最后,我们建议编写更多的 Java 程序!在编著本书时,我们编写了几百个 Java 小程序。而
且,根据通过考试的考生的描述(这些考生的正确率是 98%),他们都声称自己编写过很多代码。
规划你的考试
你可以通过 Oracle 公司或考试中心报名参加考试。访问 Oracle.com(关于培训/认证的链接),
或者访问 PeasonVue.com,获取考试时间安排和考点安排的详细信息。
参加考试
与其他考试一样,你可能会在考试的前一天晚上临阵磨枪。抵制住这种想法。到了这个时
候,你应该已经掌握了材料中的知识内容。如果第二天早上昏昏沉沉的,你会忘记昨晚学过的
内容。不如睡个好觉!
早一点抵达考试地点,这能给你充足的时间放松,并复习重要知识点。这是个复习笔记的
机会。如果没有精力学习,也可以早几分钟开始考试。我们不建议迟到,这可能会取消你的考
试资格,或者导致没有足够的时间完成考试。
抵达考试中心后,你需要提供一个当前有效的带照片证件。访问 PearsonVue.com 以了解关
于证件的要求。他们只是要确认你没有雇用隔壁的 Java 专家来替你考试。
除了大脑里的知识,你不需要带任何东西进入考场。事实上,你大脑里面的知识是唯一允
许带入考场的。
所有考试都是闭卷考试,这意味着你不能带入任何参考资料。同时,也不能从考场中带出
任何笔记。监考老师会提供一个小的白板。如果允许的话,我们建议带一瓶水或果汁(参考考
场携带须知)。这些考试的时间长,而且非常难,带些饮品能让你的大脑更加活跃。最理想的
方法是频繁地小口喝水。同时,你应该了解在整个考试过程中,允许有多少次的“中间休息”。
将手机放在车里,否则它只会让情况更加紧张,虽然考场中不允许携带手机,但有时仍然
能听见它在考场外响起的声音。手提包、书籍和其他资料,必须在进入考场之前交给监考老师。

进入考场后,针对考试所用的软件会有个简要介绍。你肯定会被请求填写一份问卷反馈。
填写问卷反馈的时间不计算在考试时间内——当然,快速地完成问卷反馈也不会延长考试的时
间。同时,考题也不会因为问卷调查上的答案而变化。在完成问卷调查后,真正的考试才开始。
使用考试软件,你可以直接选择跳转到前一题或后一题。更重要的是,屏幕上会有一个标
记复选框——这是一个非常重要的功能,后续内容会予以介绍。
参加考试的技巧
没有计划的进攻,考生可能会被试题打倒,或者是由于不断变换的题目导致时间不足。多
数情况下,如果对于资料的复习到位,分配的时间足以完成考试。关键在于,不要在某个特殊
的问题上花费太多的时间。
最直接的目的就是准确并快速地回答问题,但是其他因素可能会使你分心。下面是使你更
高效地完成考试的一些技巧。
评估困难
首先,在考试中快速浏览一遍所有的问题。通过“择优选择”的方法,选出简单的问题,
并直接给予回答。简短地阅读每一个问题,注意问题的类型和主题。作为指导原则,建议花费
少于 25%的时间在这一部分。
这个步骤能让你对整个考试的范围和难易度有一个评估。它能帮助你如何分配时间。同时,
对于某些问题,它也能告诉你在哪里找到可能的答案。有时,一道考题的描述可能为你解答另
一个题目提供思路。
如果对于某个问题,不能够百分之百地确定答案,也先给它一个答案,但是用标记复选框
标记它,用来做后续的检查。这样即使时间不充裕,至少做出了一个“第一猜测”,而不是留
下空白。
第二,根据第一遍的经验,重新浏览整个考试。例如,如果整个测试看起来困难,那么在
每一道题目上最好多花费一两分钟。创建一些小的里程碑——例如“每 15 分钟完成 10 道问题”。
在这个阶段,跳过耗时较多的问题是一个不错的想法。在整个考试时间的 50%或 60%之前
完成这一阶段。
第三,回顾标记过的问题,使用检查标记按钮,直接显示标记过的问题。这个步骤重新检
查之前不确定答案的问题,以及因问题耗时太久而临时推迟的问题。在解答所有问题之前,可
以一直处理这一组问题。
如果对标记的问题的答案更加放心,你可以取消对问题的标记。否则,就一直标记它。现
在,继续处理耗时较多的问题,特别是那些需要手动计算的问题。在对计算的答案满意之后,
取消对它们的标记。
经过这一步之后,虽然对有些答案仍不确定,但是你应该回答了整个考试的所有问题。如
果在下一步之前已经没有时间了,至少不会因为缺少答案而失分。如果此时仍然有 10%或 20%
的时间保留,你的节奏就非常好。

前 言 XIX
检查答案
现在,你轻松了!你已经回答了所有问题,而且准备做一轮质量检查。再一次浏览整个测
试的问题,简要阅读每个问题和答案。
仔细地检查问题,查看是否有“陷阱”存在。特别是对于那些包含选项“无法编译”的题
目,要格外谨慎。注意最后一分钟的线索。此时,虽然你几乎已经熟悉了每个问题,但是你仍
然可能会发现之前错过的一些线索。
最重要的结尾
当你已经确定好所有的答案之后,通过提交答案来完成考试。完成考试后,如果考试成绩
已经发布,你会收到来自 Oracle 公司的一封电子邮件,其中包含用于查询成绩的链接。截至编
写本书为止,你必须特殊地申请认证证书的实体复印,否则没有人会给你发送。
复考
如果你没能通过考试,不要泄气。尝试从另一个角度来理解这份经历,并且准备复考。至
少你学到了一些知识。你会更了解考试的形式,并且在下一次考试时,能够更好地理解问题的
难度。
如果能在失利之后快速恢复状态,你可能还会记住一些题目。这能够使你在复习时,侧重
在正确的范围。
最后,记住 Oracle 认证是很有价值的,因为获取这个认证很难。毕竟,如果所有人都可以
获取,那它还有什么价值呢?最后,它需要正确的心态和大量的学习,你可以做得到!
考试内容映射
下面的表格描述了考试的目标,以及在本书中如何找到对应的知识内容(注意:我们总结
了 Oracle.com 网站上的一些描述)。
OCA Java SE 8 Programmer(考试 1Z0-808)

考试目标 包含在书中
Java 基础
定义变量作用域(1.1) 第 3 章
定义 Java 类的结构(1.2) 第 1 章和第 2 章
在 main 方法中创建一个可执行的 Java 应用(1.3) 第 1 章
导入 Java 包,使 Java 包在你的代码中可见(1.4) 第 1 章
比较和对比 Java 的特性(1.5) 第 1 章


(续表)

考试目标 包含在书中


使用 Java 数据类型

声明和初始化变量(2.1) 第 1 章和第 3 章
区分对象引用变量和基本类型变量(2.2) 第 1~3 章
了解如何读写对象字段(2.3) 全书
解释对象的生命周期(创建,“解除引用”,以及垃圾收集)(2.4) 第 3 章
使用封装类,例如 Boolean、 Double、 Integer(2.5) 第 6 章


使用运算符和条件结构

使用 Java 运算符(3.1) 第 1 章和第 4 章
使用==和 equals()验证字符串和对象之间的相等性(3.2) 第 1 章和第 4 章
创建 if 和 if/else,以及三目运算(3.3) 第 4 章和第 5 章
使用 switch 语句(3.4) 第 5 章


创建并使用数组

声明、实例化、初始化,以及使用一个一维数组(4.1) 第 3 章和第 6 章
声明、实例化、初始化,以及使用一个多维数组(4.2) 第 3 章和第 6 章


使用循环结构

创建并使用 while 循环(5.1) 第 5 章
创建并使用 for 循环,包括改进后的 for 循环(5.2) 第 5 章
创建并使用 do/while 循环(5.3) 第 5 章
比较循环结构(5.4) 第 5 章
使用 break 和 continue(5.5) 第 5 章


处理方法和封装

创建包含参数和返回值的方法(6.1) 第 2 章
在方法和字段上使用 static 关键字(6.2) 第 1 章和第 2 章
创建和重载构造函数(6.3) 第 1 章和第 2 章
使用访问修饰符(6.4) 第 1 章
类的封装性原则(6.5) 第 2 章和第 6 章
当引用变量在函数中被修改时,判断对象引用变量和原始变量受到的影响(6.6) 第 3 章


处理继承

描述继承和它的优点(7.1) 第 2 章
编写代码展示多态的使用(7.2) 第 2 章
判断类型转换的时机(7.3) 第 2 章
使用 super 和 this 访问对象和构造函数(7.4) 第 2 章
使用抽象类和接口(7.5) 第 1 章和第 2 章



前 言 XXI
(续表)

考试目标 包含在书中


处理异常

区分检查异常、运行时异常和错误(8.1) 第 5 章
创建 try-catch 代码块,判断异常如何改变程序的执行顺序(8.2) 第 5 章
描述异常处理的优点(8.3) 第 5 章
创建并调用一个可以抛出异常的方法(8.4) 第 5 章
认识常见的异常类(8.5) 第 5 章


处理 Java API 的选择类

使用 StringBuilder 类处理数据(9.1) 第 6 章
创建并处理字符串(9.2) 第 6 章
创建并处理日历数据(9.3) 第 6 章
声明和使用 ArrayList(9.4) 第 6 章
编写一个简单的 lambda 表达式并使用 Lambda Predicate 表达式(9.5) 第 6 章




目 录
第 1 章 声明和访问控制 ............................. 1
1.1 Java 复习 ······································· 2
1.1.1 标识符和关键字·························· 2
1.1.2 继承············································· 2
1.1.3 接口············································· 2
1.2 Java 的特性和优点(OCA
考点 1.5) ········································ 3
1.3 标识符和关键字(OCA 考点
1.2 和 2.1) ······································ 4
1.3.1 合法的标识符······························ 4
1.3.2 Oracle 的 Java 语言编码规范······ 5
1.4 定义类(OCA 考点 1.2, 1.3, 1.4,
6.4, 7.5) ·········································· 6
1.4.1 源文件的声明规则······················ 7
1.4.2 使用 javac 和 java 命令 ··············· 7
1.4.3 使用 public static void main
(String[ ] args) ······························
8
1.4.4 import 语句和 Java API ··············· 9
1.4.5 静态导入语句···························· 10
1.4.6 类声明和修饰符························ 11
1.5 使用 Java 接口(OCA 考点 7.5)····16
1.5.1 声明接口 ··································· 16
1.5.2 声明接口常量···························· 18
1.5.3 声明 default 接口方法··············· 19
1.5.4 声明 static 接口方法 ················· 19
1.6 声明类成员(OCA 考点 2.1, 2.2,
2.3, 4.1, 4.2, 6.2, 6.3, 6.4)··············20
1.6.1 访问修饰符 ······························· 20
1.6.2 非访问成员修饰符···················· 30
1.6.3 构造函数的声明························ 35
XXIV OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
1.6.4 变量的声明 ·······························
35
1.7 声明和使用枚举类型(OCA
考点 1.2) ·······································42
1.7.1 声明枚举类型···························· 43
1.7.2 在枚举类型中声明构造函数、
方法和变量································
44
1.8 认证考试总结·······························46
1.9 两分钟冲刺···································47
1.10 自测题 ········································52
1.11 自测题答案·································57
第 2 章 面向对象....................................... 59
2.1 封装(OCA 考点 6.1 和 6.5) ··········60
2.2 继承和多态(OCA 考点 7.1
和 7.2) ···········································62
2.2.1 继承的进化 ······························· 63
2.2.2 IS-A 和 HAS-A 关系················· 65
2.3 多态(OCA 考点 7.2)·····················68
2.4 重写/重载(OCA 考点 6.1
和 7.2) ···········································71
2.4.1 重写方法 ··································· 71
2.4.2 重载的方法 ······························· 75
2.5 类型转换(OCA 考点 2.2
和 7.3) ···········································80
2.6 实现接口(OCA 考点 7.5) ·············82
2.7 合法的返回类型(OCA 考点 2.2
和 6.1) ···········································87
2.7.1 返回类型声明···························· 87
2.7.2 返回值 ······································· 88
2.8 构造函数和实例化(OCA 考点
6.3 和 7.4) ·····································89
2.8.1 构造函数基础···························· 90
2.8.2 构造函数链 ······························· 90
2.8.3 构造函数的规则························ 91
2.8.4 判断是否会创建默认构造
函数 ···········································
92
2.8.5 重载的构造函数························ 95
2.9 初始化块(OCA 考点 1.2
和 6.3) ···········································98
2.10 Static(OCA 考点 6.2)················100
2.11 认证考试总结···························105
2.12 两分钟冲刺 ······························106
2.13 自测题 ······································109
2.14 自测题答案 ······························116
第 3 章 赋值············································ 119
3.1 栈和堆的快速回顾·····················120
3.2 字面值、赋值和变量(OCA
考点 2.1, 2.2 和 2.3)··················121
3.2.1 所有基本类型的字面值 ········· 121
3.2.2 赋值运算符 ···························· 124
3.3 作用域(OCA 考点 1.1)···············131
3.4 变量初始化(OCA 考点 2.1, 4.1
和 4.2)·········································133
3.4.1 使用未初始化和未赋值的
变量或数组元素·····················
133
3.4.2 局部(栈、自动)基本类型变量
和对象类型变量·····················
135
3.5 将变量传递给方法(OCA 考
点 6.6)·········································139
3.5.1 传递对象引用变量················· 139
3.5.2 Java 使用值传递语义吗 ········· 140
3.5.3 传递基本类型变量················· 141
3.6 垃圾回收(OCA 考点 2.4)···········143
3.6.1 内存管理和垃圾回收概要 ····· 143
3.6.2 Java 垃圾回收器概要············· 143
3.6.3 编写代码显式地将对象标记
为可回收对象·························
144
3.7 认证考试总结 ····························149
3.8 两分钟冲刺 ································149
3.9 自测题 ········································151
3.10 自测题答案 ······························157
第 4 章 运算符········································ 159
4.1 Java 运算符(OCA 考点 3.1, 3.2
和 3.3)·········································160
4.1.1 赋值运算符 ···························· 160
4.1.2 关系运算符 ···························· 161
4.1.3 instanceof 比较运算符············ 165
4.1.4 算术运算符 ···························· 167
4.1.5 条件运算符 ···························· 171
目 录 XXV
4.1.6 逻辑运算符 ···························· 172
4.1.7 运算符的优先级····················· 175
4.2 认证考试总结·····························177
4.3 两分钟冲刺·································177
4.4 自测题 ········································179
4.5 自测题答案·································183
第 5 章 流程控制和异常 .........................187
5.1 使用 if 和 switch 语句(OCA 考
点 3.3 和 3.4) ······························188
5.1.1 if-else 分支语句······················ 188
5.1.2 switch 语句····························· 192
5.2 创建循环结构(OCA 考点 5.1,
5.2, 5.3, 5.4, 5.5)····················198
5.2.1 使用 while 循环······················ 198
5.2.2 使用 do 循环 ·························· 199
5.2.3 使用 for 循环·························· 199
5.2.4 使用 break 和 continue ··········· 203
5.2.5 无标签的语句························· 204
5.2.6 带标签的语句························· 205
5.3 处理异常(OCA 考点 8.1, 8.2,
8.3, 8.4, 8.5)·····························206
5.3.1 使用 try 和 catch 捕获异常 ···· 207
5.3.2 使用 finally····························· 208
5.3.3 未捕获异常的传递················· 210
5.3.4 定义异常 ································ 212
5.3.5 异常的层级结构····················· 212
5.3.6 处理异常树上的整个类 ········· 213
5.3.7 异常的匹配 ···························· 214
5.3.8 异常的声明和公共接口 ········· 215
5.3.9 重新抛出同一个异常 ············· 219
5.4 常见的异常和错误(OCA 考
点 8.5) ·········································220
5.4.1 异常来自于何处····················· 220
5.4.2 JVM 抛出的异常···················· 221
5.4.3 由程序抛出的异常················· 221
5.4.4 考试范围中的异常和错误的
总结 ········································
222
5.5 认证考试总结·····························223
5.6 两分钟冲刺·································224
5.7 自测题 ········································226
5.8 自测题答案 ································233
第 6 章 字符串、数组、 ArrayList、日
期与 lambada 表达式 ··············· 237
6.1 使用字符串和 StringBuilder 类
(OCA 考点 9.2 和 9.1)················238
6.1.1 String 类 ································· 238
6.1.2 关于字符串和内存的重要
事实 ········································
242
6.1.3 String 类中的重要方法 ·········· 243
6.1.4 StringBuilder 类······················ 245
6.1.5 StringBuilder 类的一些重要
方法 ········································
247
6.2 处理日历数据(OCA 考点 9.3)·····248
6.2.1 不变性 ···································· 249
6.2.2 工厂类 ···································· 250
6.2.3 使用和处理日期和时间 ········· 250
6.2.4 格式化日期和时间················· 252
6.3 使用数组(OCA 考点 4.1
和 4.2)·········································253
6.3.1 声明数组 ································ 253
6.3.2 构造数组 ································ 254
6.3.3 初始化数组 ···························· 256
6.4 使用 ArrayList 和封装类(OCA
考点 9.4 和 2.5) ··························263
6.4.1 何时使用 ArrayList ················ 264
6.4.2 实际使用中的 ArrayList 方法 ·· 266
6.4.3 ArrayList 类的重要方法········· 266
6.4.4 ArrayList 的自动装箱 ············ 267
6.4.5 Java 7 的“菱形”语法 ·········· 270
6.5 高级封装(OCA 考点 6.5)···········270
6.6 使用简单的 lambda 表达式
(OCA 考点 9.5)···························271
6.7 认证考试总结 ····························275
6.8 两分钟冲刺 ································276
6.9 自测题 ········································278
6.10 自测题答案 ······························286
附录 A 关于模拟考试软件 ··················· 289

第 3 章
赋 值
认证目标
● 使用类的成员
● 理解基本类型的转换
● 理解变量的作用域
● 区分基本类型变量和引用类型变量
● 判断给方法传递变量的影响
● 理解对象生命周期和垃圾回收机制
● 两分钟冲刺
● Q&A 自测题

120 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
3.1 栈和堆的快速回顾
对于大多数人而言,理解栈和堆的基础知识后,能够更容易地理解其他主题,如参数传递、
多态、线程、异常和垃圾回收。本节只是概要回顾这些主题,但是它们的扩展内容会在全书多
次出现。
多数情况下, Java 程序的各个部分(方法、变量和对象)存储在内存中的两个地方:栈或堆。
这里只关注三个内容:实例变量、局部变量和对象。
● 实例变量和对象存储在堆中。
● 局部变量存储在栈中。
先看一个 Java 程序,看看其不同部分如何创建并映射到栈和堆中:
1. class Collar { }
2.
3. class Dog {
4. Collar c; //
实例变量
5. String name; // 实例变量
6.
7. public static void main(String [] args) {
8.
9. Dog d; //
局部变量: d
10. d = new Dog();
11. d.go(d);
12. }
13. void go(Dog dog) { //
局部变量: dog
14. c = new Collar();
15. dog.setName("Aiko");
16. }
17. void setName(String dogName) { //
局部变量: dogName
18. name = dogName;
19. //
做更多的事情
20. }
21. }
图 3-1 展示了上述代码执行到第 19 行时,栈和堆的状态。下面是一些关键点。
● 第 7 行——栈中加入 main()函数。
● 第 9 行——栈中创建引用变量 d,但此时还没有 Dog 对象。
● 第 10 行——在堆中创建新的 Dog 对象,将它赋值给引用变量 d。
● 第 11 行——将引用变量 d 的副本传递给 go()方法。
● 第 13 行——在栈中放入 go()方法,其中 dogName 参数作为局部变量。
● 第 14 行——在堆中创建新的 Collar 对象,并将它赋值给 Dog 的实例变量。
● 第 17 行——向栈中添加 setName()方法,其中 dogName 参数作为局部变量。
● 第 18 行——name 实例变量现在也引用 String 对象。
● 注意,两个不同的局部变量引用同一个 Dog 对象。
● 注意,一个局部变量和一个实例变量同时引用相同的字符串 Aiko。
● 第 19 行执行完毕后, setName()完成并被移出栈。与此同时,局部变量 dogName 也消
失,虽然它引用的 String 对象还在堆中。

第 3 章 赋 值 121

图 3-1 栈和堆的概述
认证目标
3.2 字面值、赋值和变量 (OCA 考点 2.1 2.2 2.3)
2.1 声明和初始化变量(包括基本数据类型的强制转换)
2.2 区分对象引用变量和基本类型变量
2.3 了解如何读写对象的字段
3.2.1 所有基本类型的字面值
基本类型的字面值,就是在源代码中所展示的基本数据类型——换言之,就是指在编写代
码时,输入的整数、浮点数、布尔类型值或字符。下面是基本类型字面值的一些示例:
'b' // char 字面值
42 // int 字面值
false // boolean 字面值
2546789.343 // double 字面值
1. 整数字面值
在 Java 语言中,有 4 种方法表示整数值:十进制、八进制、十六进制以及 Java 7 中的二
进制。涉及整数字面值的考试问题,多数都是使用十进制表示的,但是也有少数问题使用八进
制、十六进制或二进制,它们也值得学习研究。即使在实际中使用八进制的概率微乎其微,但
是考试中仍然包含这部分。在了解这 4 种表示整数的方法之前, 先看看 Java 7 中添加的新特性:
包含下画线的字面值。
包含下画线的字面值 在 Java 7 中,声明数值字面值时,可以使用下画线字符(_),用于增
强可读性。下面比较 Java 7 之前的声明和 Java 7 中更易读的声明:
int pre7 = 1000000; // Java 7 之前的声明——我们希望它是百万位数
int with7 = 1_000_000; // 更加清楚了!
需要记住的主要规则是,不能在字面值的开头或结尾使用下画线。这里有一个可能,就是
可以在“奇怪的”地方使用下画线:
int pre7 = _1_000_000; // 非法,开头不能使用 "_"
int with7 = 1_000_000; //
合法,但是让人困惑
122 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
最后,记住可以在所有的数值类型中使用下画线(包括 double 和 float),但是对于 double
和 float 类型,不能在小数点后面紧接着使用下画线,不能在十六进制或二进制的 X 或 B 后面
紧接着使用下画线(马上就介绍这部分内容)。
十进制字面值 十进制整数不需要解释,从小学一年级(或更早时间)开始,就在使用它们
了。通常, 不会在支票簿上使用十六进制(如果真的是这样, Geeks Anonymous(GA)可以帮助你)。
在 Java 语言中,十进制的表示没有任何前缀,如下:
int length = 343;
二进制字面值 二进制字面值是 Java 7 的新特性。二进制字面值只使用数字 0 和 1。二进
制字面值必须以 0B 或 0b 开头,如下:
int b1 = 0B101010; // 设置 b1 值为二进制 101010( 十进制 42)
int b2 = 0b00011; //
设置 b2 值为二进制 11( 十进制 3)
八进制字面值 八进制整数只能使用数字 ~7。在 Java 中,在数字前面使用 0 表示该整
数是八进制格式,如下:
class Octal {
public static void main(String [] args) {
int six = 06; //
等于十进制数字 6
int seven = 07; //
等于十进制数字 7
int eight = 010; //
等于十进制数字 8
int nine = 011; //
等于十进制数字 9
System.out.println("Octal 010 = " + eight);
}
}
一个八进制表示的数,最多能有 21 位数字,不包括开头的 。如果执行上面的程序,则输
出如下:
Octal 010 = 8
十六进制字面值 十六进制(简称 hex)数字以 16 个不同的符号组成。因为从数字 10 到 15
没有单独的数字符号表示,所以使用字母字符来代替这些数字,十六进制中, ~15 的计数
如下:
0 1 2 3 4 5 6 7 8 9 a b c d e f
对于额外的数字, Java 同时接受大写和小写字母(这是 Java 忽略大小写的少数几种情况之
一)。一个十六进制数字,最多有 16 位数字,不包括前缀 0x(或 0X)或是可选后缀 L,本章后续
内容会详细解释。下面的十六进制赋值都是合法的:
class HexTest {
public static void main (String [] args) {
int x = 0X0001;
int y = 0x7fffffff;
int z = 0xDeadCafe;
System.out.println("x = " + x + " y = " + y + " z = " + z);
}
}
执行 HexTest,得到如下输出:
x = 1 y = 2147483647 z = -559035650
不要被十六进制数字或数字前面字母 x 的大小写所误导。 0XCAFE 和 0xcafe 都是合法的,
而且它们表示相同的值。
默认情况下,这 4 种整数字面值(二进制、八进制、十进制和十六进制)都是 int 类型。但是
通过在数字后添加后缀 L 或 l,可以将它们标记为 long 类型:
long jo = 110599L;
long so = 0xFFFFl; //
注意,这里是 L 的小写字母 'l'
2. 浮点数字面值
浮点数包括整数部分、小数点以及小数部分。在下面的例子中, 11301874.9881024 是一个
字面值:
double d = 11301874.9881024;
默认情况下,浮点数字面值定义为 double(64 位)类型。因此,如果将浮点数字面值赋值给
float(32 位)类型变量,必须在数字后添加后缀 F 或 f。如果没有这么做,编译器提示可能会丢
失精度,因为这是在尝试将一个数字放入一个(可能)精度更小的“容器”中。后缀 F 能够告诉
编译器“嘿,我知道我在做什么,我可以接受这个风险,非常感谢。”
float f = 23.467890; // 编译器错误,可能会丢失精度
float g = 49837849.029847F; //OK ,有后缀 "F"
对于 double 字面值,也可以选择附加后缀 D 或 d,但这不是必要的,因为这是默认情况:
double d = 110599.995011D; // 可选,并不是必须的

double g = 987.897; // 没有 'D' 后缀,但是是没问题的,因为字面值默认是 double 类型
注意包含逗号的数字字面值;例如:
int x = 25,343;
// 因为逗号,无法通过编译


3.布尔类型字面值
布尔类型字面值是指在源代码中的布尔值。布尔值只能是 true 或 false。虽然在 C 语言(以
及一些其他语言)中,经常使用数字来代替 true 或 false,但是在 Java 中不行。再次跟我念“Java
不是 C。”
boolean t = true; // 合法
boolean f = 0; // 编译错误!
要小心一类问题:当要求用布尔类型时,却使用了数值类型。可能会看到 if 语句中使用数
字作为判断条件,如下:

int x = 1; if (x) { } // 编译错误
4. 字符字面值
字符字面值是单引号中的单个字符:
char a = 'a';
char b = '@';


同时可以输入字符的 Unicode(唯一编码)值,使用前缀\u 作为 Unicode 标记,如下:
char letterN = '\u004E'; // 字母 'N'
记住,在底层,字符只是 16 位无符号整数。这意味着,可以将数字字面值赋值给字符变
量,假设该字面值符合 16 位无符号数字的范围(0~65535)。例如,下面的代码是合法的:
char a = 0x892; // 十六位数字字面值
char b = 982; //int 字面值
char c = (char)70000; // 需要类型转换, 70000 超出了 char 的范围
char d = (char) -98; // 荒谬,但是是合法的
下面的代码不合法,会导致编译错误:
char e = -29; // 可能会丢失精度,需要类型转换
char f = 70000; // 可能会丢失精度,需要类型转换
当字符不能直接以字面值的形式输入时,可以使用转义符(反斜线),这些字符包括换行符、
水平制表符、退格符和引号:
char c = '\"'; // 双引号
char d = '\n'; // 换行符
char tab = '\t'; // 水平制表符
5. 字符串类型的字面值
字符串字面值是源代码中 String(字符串)对象值。下面是两种字符串字面值的示例:
String s = "Bill Joy";
System.out.println("Bill" + " Joy");
虽然字符串不是基本类型,但是这里仍然要介绍,是因为它也能以字面值的形式表示——
换言之,可以直接在代码中输入字符串字面值。仅有的另一个可以用字面值表示的非基本类型
是数组,稍后会介绍。
Thread t = ??? // 这里可以使用什么字面值?
3.2.2 赋值运算符
将值赋给一个变量,这看起来相当简单。可以简单地将等号(=)右侧的内容赋给等号左侧的
变量。当然,不要指望考试中出现类似这样的内容:
x = 6;
是的,考试中不会出现这种无脑(专业术语)的赋值。不过,你会看到更狡猾的赋值,其中
包括复杂的表达式和类型转换。我们会同时介绍基本类型和引用变量类型的赋值。但是在开始
之前,需要复习和深入理解变量。什么是变量?变量和它的值是如何关联的?
变量是已指定类型的位容器。可以是 int 类型容器、 double 类型容器、 Button 类型容器,甚至
是 String[]容器。在容器中, 是一些二进制位表示的值。 对于基本数据类型, 二进制位表示数字值(虽
然不知道布尔类型的二进制位的表示形式, 但是很幸运, 这里不考虑这部分)。 例如, 值为 6 的 byte
类型数据,它在变量(byte 容器)中的二进制位形式是 00000110,是一个 8 位的二进制数字。
因此,基本类型变量的值非常清楚,但是对象容器中包含的是什么内容呢?如果
Button b = new Button();
Button 容器 b 中包含什么呢?是 Button 对象吗?不!引用对象的变量只是引用变量。引用
变量位容器包含的二进制位表示的是访问对象的路径。我们不知道它的格式。对象引用中存储
的路径是与特定虚拟机相关的(它是一个指针,我们并不知道实际是什么)。能够确认的只有一
点,变量的值并不是对象,而是堆中指定对象的值,或者是 null。如果引用变量没有被赋值,
或者是显式地赋值为 null,那么变量保存的位表示 null。可以将下面的代码理解为“Button
变量 b 不引用任何对象”。
Button b = null;
现在,我们知道变量只是包含二进制值的小盒子,下面可以尝试改变这些二进制值。下面
先介绍给基本类型变量赋值,然后介绍给引用变量赋值。
1. 基本类型的赋值
等号(=)用于将值赋给变量,它被聪明地命名为赋值运算符。实际上有 12 个赋值运算符,
但是考试范围中只有 5 个最常用的赋值运算符,在第 4 章会详细介绍它们。
可以使用字面值或表达式结果为基本类型变量赋值。
看看如下代码:
int x = 7; // 字面值赋值
int y = x + 2; // 使用表达式赋值 ( 其中包括一个字面值 )
int z = x * y; //
使用表达式赋值
需要记住的最重要的一点是,字面值整数(如数字 7)总是隐式的 int 类型。回忆第 1 章, int
是一个 32 位的值。在给 int 或 long 变量赋值时,这不是什么问题,但是如果给 byte 变量赋值
呢?毕竟, byte 的容器所能保存的位数设有 int 容器大。从这里开始,就有些奇怪了。下面的
代码是合法的,
byte b = 27;
但这只是因为编译器自动将字面值缩小到 byte 的大小。换言之,编译器做了类型转换。上述代
码与下面的代码完全一致:

byte b = (byte) 27; // 显式地将 int 字面值转换为 byte
看起来编译器帮了个忙,它让你在给比 int 更小的整数变量赋值时,可以直接赋值(这里关
于 byte 的内容,同样适用于 char 和 short,因为它们也比 int 小)。顺便提一下,我们还没有接
触到真正奇怪的部分。
我们知道,字面值整数总是 int 类型,但更重要的是,包含 int 或比 int 更小的类型时,表
达式结果总是 int 类型。换言之,两个 byte 类型做加法运算,结果是 int 类型——即使这两个
byte 类型数据是非常小的数字。 int 类型乘以 short 类型,结果是 int 类型。 short 类型除以 byte
类型,结果是……int 类型。好吧,现在才是诡异的部分。查看如下代码:


byte a = 3; // 没问题, 3 符合 byte 类型
byte b = 8; // 没问题, 8 符合 byte 类型

byte c = a + b; // 应该没问题,两个 byte 类型数据求和,其结果仍然符合 byte 类型
最后一行无法通过编译!会返回如下错误:
TestBytes.java:5: possible loss of precision


found : int
required: byte
byte c = a + b;
^
尝试将两个 byte 类型的和赋给 byte 变量,其结果(11)也足够小,符合 byte 类型的使用范
围,但是编译器并不关心这些。它知道使用 int 或更小类型做运算时,结果总是 int 类型。如果
显式地进行类型转换,就会通过编译:
byte c = (byte) (a + b);
考试须知
在考虑如何介绍这部分内容时,我们非常纠结。我们的朋友, JavaRanch 版主和技术复查
员 Marc Peabody 提出了下面的内容。我们认为他做得棒极了:声明变量时,在一行代码中声
明多个变量并用逗号隔开,这是完全合法的:
int a, b, c;
同时可以对大量的变量进行初始化:
int j, k=1, l, m=3;
每个变量从左到右分别赋值。它与在单独一行声明变量的情况一致:
int j;
int k=1;
int l;
int m=3;
但是顺序很重要。下面的语句面是合法的:
int j, k=1, l, m=k+3; // 合法:在 m 使用 k 之前, k 已经被初始化
但是下面的语句是不合法的:
int j, k=m+3, l, m=1; // 不合法:在 k 使用 m 之前, m 没有被声明和初始化
int x, y=x+1, z; // 不合法:在 y 使用 x 之前, x 没有被初始化
2. 基本类型转换
通过类型转换,能够将基本类型值在不同类型之间转换。在前面的章节中,提到了基本类
型转换,现在将深入介绍该主题(第 2 章中介绍了对象类型转换)。
类型转换可以是隐式的,也可以是显式的。隐式的类型转换意味着不需要编写代码,类型
转换会自动发生。通常,隐式的类型转换发生在拓宽转换时——换言之,将较小的值(如 byte)
放进较大的容器(如 int)。还记得在赋值部分,看到的编译错误“possible loss of precision”吗?
当把较大的值(如 long)放进较小的容器(如 short)时,就会发生这种情况。将大值放进小容器的
转换,称为缩小转换,它需要显式的类型转换。这样的转换等于在告诉编译器,你知道可能存
在的风险,并且能够接受。
首先来看隐式类型转换:
int a = 100;
long b = a; //
隐式类型转换, int 值永远符合 long 类型要求
显式类型转换如下:
float a = 100.001f;
int b = (int)a; //
显式类型转换, int 值可能会失去 float 类型数据的一部分信息
不需要进行显式类型转换,整数值就可以被赋值给 double 类型的变量,这是因为整数值符
合 64 位 double 类型的要求。如下所示:
double d = 100L; // 隐式类型转换
在上面的语句中,使用 long 值初始化 double 类型变量(数字值后面的 L 表示其为 long 类型
值)。在这种情况中,不需要类型转换,因为 double 容器可以存储 long 的全部信息。然而,如
果想要将 double 值赋给一个整数(int)类型,则编译器知道,是在尝试进行缩小转换:
class Casting {
public static void main(String [] args) {
int x = 3957.229; //
非法的
}
}
如果尝试编译上述代码,会返回如下结果:
%javac Casting.java
Casting.java:3: Incompatible type for declaration. Explicit cast
needed to convert double to int.
int x = 3957.229; //
非法的
1 error
在前面的代码中,浮点值被赋给一个整数变量。因为整数变量不能存储小数部分,所以发
生了错误。为了使代码正确,将浮点类型数值转换为 int 类型:
class Casting {
public static void main(String [] args) {
int x = (int)3957.229; //
合法的类型转换
System.out.println("int x = " + x);
}
}
将浮点数转换为整数类型时,小数点后面的值会被舍去。上述代码的输出如下:
int x = 3957
还可以将较大的数值类型(如 long)转换为较小的数值类型(如 byte)。看如下代码:
class Casting {
public static void main(String [] args) {
long l = 56L;
byte b = (byte)l;
System.out.println("The byte is " + b);
}
}
上述代码能够通过编译,并且成功执行。但是,当 long 值大于 127(byte 可以存储的最大
数值)时,会发生什么情况呢?修改代码如下:
class Casting {
public static void main(String [] args) {
long l = 130L;

128 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
byte b = (byte)l;
System.out.println("The byte is " + b);
}
}
代码能够通过编译,运行代码时返回如下内容:
%java Casting
The byte is -126
即使缩小非常大的数值,也不会收到运行时错误。从左侧开始,一直到第 8 位,这些数据
都被舍弃。如果 byte(或任意整数基本类型)中最左侧位(符号位)是 1,则基本类型变为负值。
练习 3-1
转换基本类型数据
创建一个 float 数值类型的数值,通过类型转换,将它赋给 short 变量。
(1) 声明 float 变量: float f = 234.56F;
(2) 将 float 赋值给 short: short s = (short)f;
1. 浮点数赋值
浮点数赋值与整数赋值稍有不同。首先,必须知道每个浮点数字面值都是隐式的 double
类型(64 位),不是 float 类型。例如,字面值 32.3 是 double 类型。如果尝试将 double 类型值赋值给
float 类型变量,编译器知道 32 位的 float 类型容器无法保存精度为 64 位的 double 类型值,所
以编译器会报错。下面的代码看起来是正确的,但是它无法编译:
float f = 32.3;
可以看到, 32.3 完全满足 float 类型变量的范围,但是编译器不允许。为了将浮点数字面值
赋给 float 类型变量, 必须进行类型转换, 或者是在字面值后面附加 f。 下面的赋值语句能够编译:
float f = (float) 32.3;
float g = 32.3f;
float h = 32.3F;
2. 为变量赋予超出变量类型范围的字面值
在赋值时,如果字面值超出变量类型的范围,会返回编译错误:
byte a = 128; // byte 类型变量能保存的最大值是 127
上述代码返回如下错误:
TestBytes.java:5: possible loss of precision
found : int
required: byte
byte a = 128;
使用类型转换可以解决这个问题:
byte a = (byte) 128;
那么结果是什么呢?在缩小基本类型时, Java 简单地丢弃超出范围的高位部分。换言之,
缩小位左侧的所有数位都会被舍去。
看一下上面的代码执行时会发生什么。这里, 128 的二进制(位模式)是 10000000。需要 8
位来表示 128。但是字面值 128 是 int 类型,实际上有 32 位。在这 32 位中, 128 处于最右侧(低
位)的 8 位。因此,字面值 128 实际上是:
00000000000000000000000010000000
相信我们,这里有 32 位。
为了缩小 32 位表示的 128, Java 简单地砍掉左侧(高位)的 24 位。剩下的只是 10000000。
但要记住, byte 是有符号的,最左侧的位代表符号(不属于变量值的一部分)。因此最终得到了
一个负数(之前, 1 用于表示 128,现在它代表负号位)。记住,为了得到负数的值,可以使用二
进制的补码。对 8 位二进制数按位取反,得到 01111111,加 1 后得到的是 10000000,即 128!
应用符号位,得到的结果是-128。
将 128 赋值给 byte 时,必须使用显式的类型转换,而赋值之后的结果是-128。类型转换就
是对编译器说“相信我。我是专业的。如果砍掉高位部分发生任何奇怪的事情,则都由我负责。”
这引出了复合赋值运算符。下面的代码能够编译:

byte b = 3;
b += 7;
// 没有问题—— b 的值加上 7( 结果是 10)
它与下面的代码等价:
byte b = 3;
b = (byte) (b + 7);
// 没有类型转换,就无法通过编译,因为 b+7 的值是 int 类型


使用复合赋值运算符+=,可以在没有显式类型转换的情况下,直接对 b 进行加法操作。事
实上, +=、-=、 *=和/=都会使用隐式转换。
3. 将一个基本类型变量赋值给另一个基本类型变量
将一个基本类型变量赋值给另一个变量时,右侧变量的内容会被复制。例如:
int a = 6;
int b = a;
上述代码可以这样读“将数字 6 的二进制值(位模式)赋值给 int 变量 a。然后复制这个值,
并将副本放在变量 b 中。”
因此,两个变量都保存 6 的二进制值,但是两个变量之间没有任何其他关系。变量 a 只用
于复制其内容。此时, a 和 b 的内容完全相同(即二者的值相等),但是如果改变其中一个变量
的值,无论是 a 还是 b,另一个变量都不受影响。
看下面的示例:
class ValueTest {
public static void main (String [] args) {
int a = 10; //
a 赋值
System.out.println("a = " + a);
int b = a;
b = 30;
System.out.println("a = " + a + " after change to b");
}
}

130 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
输出结果为:
%java ValueTest
a = 10
a = 10 after change to b
注意, a 的值一直是 10。记住一点,即使将 a 赋值给 b, a 和 b 引用的并不是同一块内存。
变量 a 和变量 b 并不是共享一个值,而是有不同的值。
4. 引用变量赋值
可以将新创建的对象赋值给另一个对象引用变量,例如:
Button b = new Button();
上述代码中,有 3 个关键点:
● 创建引用变量,名为 b,类型为 Button。
● 在堆中创建一个新的 Button 对象。
● 将新创建的 Button 对象赋值给变量 b。
也可以将 null 赋值给对象引用变量,意味着变量不会引用任何对象。
Button c = null;
上面代码为 Button 引用变量创建了空间(存储引用变量自身二进制值的容器),但是没有创
建实际的 Button 对象。
如第 2 章讨论的内容,引用变量同时可以用于引用其声明类型的子类的对象。如下:
public class Foo {
public void doFooStuff() { }
}
public class Bar extends Foo {
public void doBarStuff() { }
}
class Test {
public static void main (String [] args) {
Foo reallyABar = new Bar(); //
合法,因为 Bar Foo 的子类
Bar reallyAFoo = new Foo(); // 不合法! Foo 并不是 Bar 的子类
}
}
规则是,可以引用所声明类型的子类的对象,但是不能引用父类的对象。记住,对象 Bar
保证了它可以做 Foo 能做的任何事情。因此,任何引用 Foo 对象的引用,都可以调用 Foo 的方
法,即使实际对象是 Bar。
在上述代码中,我们看到 Foo 有一个方法 doFooStuff(), Foo 类型的引用可以尝试调用这
个方法。如果 Foo 类型引用变量引用的实际对象也是 Foo,没问题。当实际对象是 Bar 对象时,
仍然是没问题的,因为 Bar 继承了 doFooStuff()方法。然而,反过来则不行。如果使用 Bar 类
型的引用,则能调用 doBarStuff()方法,如果实际对象是 Foo,则不知道如何响应。
考试须知
OCA 8 考试涵盖了封装类。我们本可以在本章介绍封装类,但是觉得在介绍 ArrayLists(第
6 章)时讨论这部分知识更有意义。因此,在第 6 章之前,只需要知道下面的内容:

封装类是包含基本类型值的对象。每种基本类型都有对应的封装类: Boolean、 Byte、
Character、 Double、 Float、 Integer、 Long 及 Short。下面的代码创建两个封装对象,然后打印
它们的值:
Long x = new Long(42); // 创建 Long 类型的实例,值为 42
Short s = new Short("57"); //
创建 Short 类型的实例,值为 57
System.out.println(x + " " + s);
生成下面的输出:
42 57
第 6 章将深入介绍封装类。
3.3 作用域 (OCA 考点 1.1)
1.1 判断变量的作用域
变量作用域
声明和初始化变量之后,会出现一个很自然的问题:“变量会持续多久?”这是一个与变
量作用域相关的问题。变量作用域不只是一个需要理解的重要知识点, 也是考试中的重要部分。
先看一个类文件:
class Layout { //
static int s = 343; // 静态变量
int x; // 实例变量
{ x = 7; int x2 = 5; } // 初始化块
Layout() { x += 8; int x3 = 6;} // 构造函数
void doStuff() { // 方法
int y = 0; // 局部变量
for(int z = 0; z < 4; z++) { // for 语句代码块
y += z + x;
}
}
}
与所有 Java 程序的变量一样,上述代码中的变量(s, x, x2, x3, y 和 z)都有自己的作用域:
● s 是静态变量。
● x 是实例变量。
● y 是局部变量(也称为“本地方法”变量)。
● z 是代码块中的变量。
● x2 是初始化块中的变量,局部变量的一种。
● x3 是构造函数变量,局部变量的一种。
为了讨论变量的作用域,可以说有下面 4 种基本范围:
1) 静态变量作用域最大。当类被加载时,会创建静态变量,而且只要类还在 Java 虚拟机
(JVM)中,静态变量就一直存在。

2) 实例变量是生命周期第二长的变量。创建一个新实例时,就会创建实例变量。在实例
对象被删除之前,实例变量一直存在。
3) 接下来是局部变量,只要方法还在栈中,局部变量就存在。很快就会看到,局部变量
可以在“范围之外”继续生存。
4) 块变量只有代码块还在执行期间存在。
有很多种关于变量大小和作用域的错误。一个常见的错误是,出现同名变量且作用域重合
的情况。我们会详细介绍关于重名的情况。产生作用域错误的最常见原因,是试图访问作用域
之外的变量。先来看这种错误的 3 种常见示例。
● 在静态上下文中(通常是 main()方法)中尝试访问实例变量:
class ScopeErrors {
int x = 5;
public static void main(String[] args) {
x++; //
不会通过编译, x 是实例变量
}
}
● 尝试访问调用方的方法的局部变量。 例如有一个方法 go(), 它调用了另一个方法 go2(),
go2()无法访问 go()的局部变量。当 go2()在执行时, go()的局部变量是存在的,但是它
的作用域不属于当前范围。当 go2()完成后,会从栈中移除这个方法,返回执行 go()方
法。此时,之前所有在 go()中声明的变量都可用了。例如:
class ScopeErrors {
public static void main(String [] args) {
ScopeErrors s = new ScopeErrors();
s.go();
}
void go() {
int y = 5;
go2();
y++; //
一旦 go2() 完成, y 就回到它的作用域
}
void go2() {
y++; //
无法通过编译, y go() 的局部变量
}
}
● 尝试在代码块完成之后,使用代码块中的变量。在代码块中声明和使用变量是非常常
见的,但是如果代码块已经完成,则要小心,不能使用该变量:
void go3() {
for(int z = 0; z < 5; z++) {
boolean test = false;
if(z == 3) {
test = true;
break;
}
}
System.out.print(test); // 'test'
是一个旧的变量,它已经不存在了……
}
后两个例子中,编译器会返回如下提示:
cannot find symbol
这是编译器在说:“那是你尝试使用的变量吗?好吧,它可能在很久之前是有效的(就像是
很久之前的某一行代码),但是现在是网络时代了,我已经没有存储这个变量的内存了。”
考试须知
要格外小心代码块中的变量的作用域。可能会在 switch、 try-catches、 for、 do 及 while 等循
环中看到它们,我们会在后续内容中介绍。
3.4 变量初始化 (OCA 考点 2.1 4.1 4.2)
2.1 声明和初始化变量(包括基本数据类型的转换)
4.1 声明、实例化、初始化及使用一维数组
4.2 声明、实例化、初始化及使用多维数组
3.4.1 使用未初始化和未赋值的变量或数组元素
对于声明的变量, Java 提供两种选择:可以对其初始化,也可以不初始化。使用未初始化
的变量时,根据所处理的变量或数组的类型的不同(基本类型或是对象),可能会得到不同的行
为。这个行为同时取决于变量声明的层级(作用域)。实例变量是在类中声明,但在方法或构造
函数之外,而局部变量是在方法(或方法的参数列表)中声明。
局部变量有时也称为栈变量、临时变量、自动变量或方法变量,但不管叫什么,它们的规
则都是不变的。虽然可以不初始化局部变量, 但是如果在不初始化的情况下使用这个局部变量,
编译器会报错,接下来会详细介绍。
1. 基本类型和对象类型的实例变量
实例变量(也叫成员变量)是在类层级定义的变量。即变量声明不是在方法、构造函数或任
意初始化块中完成。每当创建新的实例时,都会为实例变量初始化一个默认值,即使在父类型
构造函数执行完后可能会为这些实例变量显式地赋值。表 3-1 列出了基本类型和引用类型变量
的默认值。
表 3-1 基本类型和引用类型的默认值

变量类型 默认值
对象引用 null(不引用任何对象)
byte, short, int, long
float, double 0.0
boolean false
char '\u0000'


2. 基本类型实例变量
在下面的示例中,整数(int)类型变量 year 定义为类的成员,因为它在类的最初大括号中,
并且不在方法的大括号中:
public class BirthDate {
int year; //
实例变量
public static void main(String [] args) {
134 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
BirthDate bd = new BirthDate();
bd.showYear();
}
public void showYear() {
System.out.println("The year is " + year);
}
}
程序开始执行时,变量 year 的值是 ,这是基本类型数值实例变量的默认值。
实际经验:
初始化所有的变量是一个好主意,即使是只给它们赋默认值。代码会变得更
加易读,在你中了彩票搬到塔西提岛之后,后续维护代码的人会感激你的。
3. 对象引用类型实例变量
与未初始化的基本类型变量相比,初始化的对象引用类型完全是另一回事。看看如下代码:
public class Book {
private String title; //
实例引用变量
public String getTitle() {
return title;
}
public static void main(String [] args) {
Book b = new Book();
System.out.println("The title is " + b.getTitle());
}
}
代码能够通过编译。在运行代码时,输出为:
The title is null
变量 title 没有通过赋 String 值被显式初始化,因此实例变量的值是 null。记住, null 与空字符
串(" ")不同。 null 值意味着引用变量并没有引用堆中的任何对象。下面对 Book 类代码的修改,会
导致麻烦:
public class Book {

private String title; // 实例引用变量
public String getTitle() {
return title;
}
public static void main(String [] args) {
Book b = new Book();


String s = b.getTitle(); // 编译并运行

String t = s.toLowerCase(); // 运行时异常!
}
}
运行 Book 类时, JVM 会生成如下内容:
Exception in thread "main" java.lang.NullPointerException
at Book.main(Book.java:9)
返回上述错误,是因为引用变量 title 没有引用(指向)任何对象。可以通过使用关键字 null,
检查对象是否被初始化,如下面被修改过的代码:


public class Book {

private String title; // 实例引用变量
public String getTitle() {
return title;
}
public static void main(String [] args) {
Book b = new Book();


String s = b.getTitle(); // 编译并运行
if (s != null) {
String t = s.toLowerCase();
}
}
}
在上面的代码中,使用对象之前,通过变量值为非 null 检查确认引用的对象。要注意在考
试中出现的类似情况,可能需要追溯全部代码,判断对象引用是否值为 null。例如,在上面的
代码中,通过 title 的实例变量声明,可以看到没有显式初始化,说明 title 变量被赋予默认值
null,进而可以认识到变量 s 的值也是 null。记住, s 的值只是 title 值的副本(title 是 getTitle()
方法的返回值),因此,如果 title 是 null, s 也是 null。
4. 数组实例变量
在第 5 章中会详细介绍数组和多维数组的声明、构造和初始化。现在,只关注数组元素的
默认值的规则。
数组是一个对象,因此对于任何已经声明但是没有显式初始化的数组实例变量,值都是
null,这与其他对象引用实例变量相同。但是,如果数组被初始化了,数组中包含的元素会怎
么样?所有的数组元素都会被赋予默认值——这个默认值等同于元素类型的变量被声明为实
例变量时被赋予的默认值。底线:数组元素一定会被赋予默认值的,无论数组本身在哪里被实
例化。
如果在初始化一个数组时,没有单独初始化每一个对象引用元素,则它们的值都是 null。
如果数组中包含基本类型,则会被赋予类型对应的默认值。例如,在下面的代码中,数组 year
包含 100 个整数,则默认都是 :
public class BirthDays {
static int [] year = new int[100];
public static void main(String [] args) {
for(int i=0;i<100;i++)
System.out.println("year[" + i + "] = " + year[i]);
}
}
执行上述代码,输出结果表明数组中的 100 个整数都是 。


3.4.2 局部(栈、自动)基本类型变量和对象类型变量
局部变量在方法中声明,而且包含方法的参数。
考试须知
自动变量是局部变量的另一种称呼。自动变量并不是指变量会自动被赋值!事实上,恰好
相反。在代码中,自动变量必须被赋值,否则编译器会报错。

1. 局部基本类型变量
在下面的时光穿梭模拟器中,整数 year 被定义为自动变量,因为它位于方法的大括号中:
public class TimeTravel {
public static void main(String [] args) {
int year = 2050;
System.out.println("The year is " + year);
}
}
局部变量(包括基本类型的局部变量)在使用前,一定要被初始化(虽然不必与变量声明在同
一行代码)。 Java 不会给局部变量赋默认值;必须显式地给它初始化一个值,如同上面的代码
所示。如果在代码中尝试使用未初始化的基本类型变量,则会返回编译错误:
public class TimeTravel {
public static void main(String [] args) {
int year; //
局部变量 ( 声明,但是没有初始化 )
System.out.println("The year is " + year); //
编译器返回错误
}
}
编译代码,输出如下内容:
%javac TimeTravel.java
TimeTravel.java:4: Variable year may not have been initialized.
System.out.println("The year is " + year);
1 error
若想要修正代码,则必须给整数变量 year 赋值。在下面更新后的代码中,在单独的一行进
行声明,这是完全合法的:
public class TimeTravel {
public static void main(String [] args) {
int year; //
声明但是没有初始化
int day; // 声明但是没有初始化
System.out.println("You step into the portal.");
year = 2050; //
初始化 ( 显式地赋值 )
System.out.println("Welcome to the year " + year);
}
}
在上面的示例中,注意到有一个声明为 day 的整数变量,它没有被初始化,然而代码仍然
能通过编译并正常运行。从合法性的角度上讲,只要没有使用局部变量,就可以只声明而不初
始化该变量——但是坦白讲,如果声明了一个变量,就会有声明它的理由(虽然,我们听说过,
程序员会为了娱乐,声明一些随机的局部变量,然后再看他们是否能够弄清楚这些变量的用途
和使用方式)。
实际经验:
有时,编译器不能分辨出局部变量在使用前是否已经被初始化。例如,在逻辑
条件代码块中(即代码块可能不会执行,例如测试中没有 true 或 false 字面值的
if 或 for 循环)进行初始化,编译器知道初始化操作可能不会被执行,从而可能

public class TestLocal {
public static void main(String [] args) {
int x;
if (args[0] != null) { //
假设你知道这个条件为真
x = 7; // 编译器无法判断这个语句是否会执行
}
int y = x; //
编译器会卡在这里
}}
编译器返回如下错误:
TestLocal.java:9: variable x might not have been initialized
因为编译器无法确定问题,有时就需要在条件代码块之外初始化变量,只为满足编译器的
要求。如果你看到了保险杠上的贴纸内容“当编译器不高兴时,所有人都不高兴”,你就会知
道这有多么的重要了。
2. 局部对象引用
同样,在方法中声明的对象引用变量,其行为与实例引用变量也是不同的。对于实例变量
对象引用, 可以不用初始化, 只需要在使用该变量之前, 编写代码进行检查, 以确保它不是 null
值。记住,对于编译器, null 也是一种值。对于值为 null 的引用变量,不能使用点(.)运算符,
因为变量没有实际引用的对象,但值为 null 的引用与未初始化的引用是不同的。对于局部声明
的引用,除非局部变量被显式地初始化为 null 值,否则不能通过在使用前做非空检查来解决问
题。对于下面代码,编译器会报错:
import java.util.Date;
public class TimeTravel {
public static void main(String [] args) {
Date date;
if (date == null)
System.out.println("date is null");
}}
编译代码返回的错误,与下面的内容相似:
%javac TimeTravel.java
TimeTravel.java:5: Variable date may not have been initialized.
if (date == null)
1 error
在显式初始化之前,实例变量引用总是会被赋予默认值 null。但是,局部变量引用不会被
赋默认值,换言之,它不是 null。如果没有初始化局部引用变量,那么默认情况下,它没有任
何值!所以,我们简单地说:在真正的初始化之前,将这个可恶的东西显式地赋值为 null。下
面的局部变量会通过编译:
Date date = null; // 将局部引用变量显式地赋值为 null
3. 局部数组
与其他对象引用相似,在使用方法中声明的数组引用之前,必须对它赋值。这只是意味着
声明和构造数组。但是并不需要显式地初始化数组中的元素。我们之前提过,但是仍然值得重
复:无论数组声明为实例变量还是局部变量,数组中的元素都会被赋予默认值(0、 false、 null、

138 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
'\u0000'等)。然而,如果数组对象是在局部声明的,则它不会被初始化。换言之,如果在方法
中声明和使用数组,则必须显式地初始化数组引用变量,但是在构造数组对象时,其所有元素
都被自动赋予它们的默认值。
4. 将一个引用变量赋值给另一个引用变量
对于基本类型变量,将一个变量赋值给另一个变量,意味着将变量的内容(二进制值,即
位模式)复制给另一个变量。对象引用变量也是如此。引用变量的内容也是二进制值。因此,
如果将引用变量 a1 赋值给引用变量 b1,则 a1 的二进制值会被复制,然后赋给 b1(有的人创建
了一个游戏,用于计算本章中“复制”出现的次数……这个复制的概念是非常大的)。如果将
对象的现有实例赋值给一个新的引用变量,那么两个引用变量会保存相同的二进制值——这个
值引用堆中的指定对象。看如下代码:
import java.awt.Dimension;
class ReferenceTest {
public static void main (String [] args) {
Dimension a1 = new Dimension(5,10);
System.out.println("a1.height = " + a1.height);
Dimension b1 = a1;
b1.height = 30;
System.out.println("a1.height = " + a1.height +
" after change to b1");
}
}
在上述示例中, 声明了 Dimension 对象, 并初始化为宽度 5, 高度 10。然后, 声明 Dimension
变量 b1,并将 a1 赋值给 b1。此时,两个变量(a1 和 b1)保存相同的值,因为 b1 的内容来自于
a1 的复制。这里仍然只有一个 Dimension 对象——即 a1 和 b1 共同引用的对象。最后,使用
b1 引用变量修改 height 属性。现在,思考一下: a1 的 height 属性是否也会被改变?输出内容
如下:
%java ReferenceTest
a1.height = 10
a1.height = 30 after change to b1
从输出中可以看出,两个变量都引用同一个 Dimension 实例的对象。当改变 b1 时, a1 的
height 属性发生同样的变化。
String 对象是对象引用赋值的一个例外。在 Java 中, String 对象被特殊对待。首先, String
对象是不可变的;不能改变 String 对象的值(第 6 章将更详细地介绍这部分内容)。但看起来好
像是能够改变 String 对象的值。查看如下代码:
class StringTest {
public static void main(String [] args) {
String x = "Java"; //
x 赋值
String y = x; // 现在 y x 引用同一个 String 对象
System.out.println("y string = " + y);

x = x + " Bean"; // 现在,使用 x 引用变量修改对象
System.out.println("y string = " + y);
}
}


因为 String 是对象类型, 在变量 x 发生变化后, 你可能会认为字符串 y 中包含字符 Java Bean。
输出结果如下:
%java StringTest
y string = Java
y string = Java
如你所见,即使 y 和 x 是同一个对象的引用变量,修改 x 时, y 也没有改变。对于其他类
型的对象,如果两个引用指向同一个对象,其中任何一个引用修改对象,两个引用都能看到变
化,因为它们引用的仍然是同一个对象。但是,无论何时修改 String 对象,虚拟机都会更新引
用变量,使它指向不同的对象。这个不同的对象可能是一个新的对象,也可能不是,但它必然
是一个不同的对象。无法判断是否创建了新对象,是因为 String 常量池的存在(在第 6 章会详细
介绍)。
当使用 String 引用变量修改字符串时,发生了如下的事情:
● 新字符串被创建(或者是在 String 常量池中找到对应的字符串),不再接触原始的 String
对象。
● 将新的 String 对象赋值给用于修改字符串(或者说是通过修改原始字符串的副本来创建
新的字符串)的引用。
因此,当编写如下代码时:
1. String s = "Fred";
2. String t = s; //
现在 t s 引用同一个 String 对象
3. t.toUpperCase(); // 调用 String 方法修改 String
并没有改变在第 1 行中创建的原始 String 对象。第 2 行完成时, t 和 s 同时引用同一个 String
对象。但是当第 3 行代码执行时,并没有修改 t 和 s 引用的对象(即目前为止唯一的对象),而
是创建了一个全新的 String 对象。然后它就被抛弃了,因为这个新的 String 对象没有被赋值给
String 变量,新创建的 String 对象(保存字符串“Fred” )只是一个单独的对象。因此,虽然上述
代码创建了两个 String 对象,但实际上只有一个被引用,而且是 t 和 s 同时引用它。在考试中,
String 的行为极其重要,因此会在第 6 章详细介绍。
3.5 将变量传递给方法 (OCA 考点 6.6)
6.6 当对象引用和基本类型值传递给改变值的方法时,判断它们所受的影响
可以声明方法,使它能够接收基本类型变量和(或)对象引用类型变量。需要知道,被调用
的方法如何(或是否会)影响调用方的变量。当变量被传递给方法时,对象引用类型变量和基本
类型变量之间的区别非常大,而且非常重要。 3.2 节的内容有助于更好地理解本部分内容。
3.5.1 传递对象引用变量
将对象变量传递给方法时,必须要记住,传递的是对象的引用,而不是实际对象本身。记
住,引用变量保存的位是用于访问内存中(堆中)特定对象的路径。更重要的是,必须要记住,
传递的并不是实际的引用变量,而是引用变量的副本。变量的副本意味着复制变量的位(二进

140 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
制值)。因此,在传递引用变量时,实际上是将它的二进制值传递过去,其中二进制值是访问
对象的路径。换言之,调用方和被调用的方法有相同的引用;因此,这两个引用变量引用堆中
完全相同的对象(不是对象的副本)。
在这个例子中,使用 java.awt 程序包中的 Dimension 类:
1. import java.awt.Dimension;
2. class ReferenceTest {
3. public static void main (String [] args) {
4. Dimension d = new Dimension(5,10);
5. ReferenceTest rt = new ReferenceTest();
6. System.out.println("Before, d.height: " + d.height);
7. rt.modify(d);
8. System.out.println("After, d.height: " + d.height);
9. }
10. void modify(Dimension dim) {
11. dim.height = dim.height + 1;
12. System.out.println("dim = " + dim.height);
13. } }
执行该类时, modify()方法能够修改第 4 行中最初创建的(唯一)Dimension 对象。
C:\Java Projects\Reference>java ReferenceTest
Before, d.height: 10
dim = 11
After, d.height: 11
注意,第 4 行将 Dimension 对象传递给 modify()方法时,该对象在方法中发生的所有变化,
都会体现在引用所传递的对象上。在上面的代码中,引用变量 d 和 dim 指向同一个对象。
3.5.2 Java 使用值传递语义吗
如果说 Java 通过传递引用变量来传递对象,这是否意味着,对于对象来说, Java 使用的
是引用传递?虽然听到的和看到的经常是这样,但实际并非如此。对于单个虚拟机中运行的所
有变量, Java 实际上是值传递。值传递即为按变量的值传递,即传递变量的副本!
传递基本类型变量和传递引用类型变量一样,都是在传递变量中二进制值的副本。因此,
对于基本类型变量来说,传递的是代表变量值的二进制值的副本。例如,传递值为 3 的 int 变
量,则是在传递 3 的二进制值的副本。然后,被调用的方法获取这个值的副本,并按照自己的
逻辑处理。
如果传递的是对象引用变量,是在传递对对象进行引用的二进制值的副本。然后被调用方
法获取这个引用变量的副本,并按照自己的逻辑处理。但是,因为两个相同的引用变量引用同
一个对象,如果被调用的方法修改了对象(例如,通过调用 setter 方法),调用方会发现其原始
变量引用的对象也会改变。在 3.5.2 小节中,会介绍基本数据类型造成的影响。
值传递的底线:被调用的方法不能修改调用方的变量,即便是对象引用变量,被调用的方
法只能是修改变量引用的对象。修改变量和修改对象之间有什么区别?对于对象引用,这意味
着方法不能为调用方的原始引用变量重新赋值,并使变量引用不同的对象或是 null 值。例如,
在下面的代码段中:
void bar() {
Foo f = new Foo();
doStuff(f);
}

第 3 章 赋 值 141
void doStuff(Foo g) {
g.setName("Boo");
g = new Foo();
}
对 g 进行的重新赋值,不会对 f 重新赋值!在 bar()方法的最后,创建了两个 Foo 对象:一个是
局部变量 f 引用的对象,另一个是局部(参数)变量 g 引用的对象。因为 doStuff()方法中包含引
用变量的副本,它可以访问最初的 Foo 对象,例如调用 setName()方法。但是 doStuff()方法不
能访问 f 引用变量。因此, doStuff()可以改变 f 所引用的对象的值,但是 doStuff()不能修改 f 的
实际内容(位模式,即二进制值)。换言之, doStuff()可修改 f 所引用的对象的状态,但是它不能
使 f 引用不同的对象!
3.5.3 传递基本类型变量
下面看看给方法传递基本类型变量时,会发生什么:
class ReferenceTest {
public static void main (String [] args) {
int a = 1;
ReferenceTest rt = new ReferenceTest();
System.out.println("Before modify() a = " + a);
rt.modify(a);
System.out.println("After modify() a = " + a);
}
void modify(int number) {
number = number + 1;
System.out.println("number = " + number);
}
}
在这个简单的程序中,变量被传递给名为 modify()的方法,该方法使变量值递增 1。输出
结果如下:
Before modify() a = 1
number = 2
After modify() a = 1
注意,将 a 传递至方法后, a 没有变化。记住,实际传递给方法的是 a 的副本。当基本类
型变量被传递给方法时,传递的是值,即变量二进制值的副本。
课内讨论
捉摸不透的变量
当你认为已经完全理解变量时,会看到某段代码中,变量的行为与所想的不一样。当代码
中出现遮蔽变量(shadowed variable)时,可能会让你感到困惑。遮蔽一个变量可以有若干种方法。
我们只了解其中的一种:通过局部变量来遮蔽静态变量,从而隐藏该变量。
遮蔽变量,是指重用其他地方已经声明的变量的名字。遮蔽变量的效果,是隐藏前面声明的
变量,使得看起来是在使用被隐藏的变量,但是实际上是使用遮蔽变量。这种情况可能是故意为
之,但是通常是由意外导致的,而且它会导致难以发现的 bug。 在考试中, 会出现遮蔽变量的情况。
可以声明一个同名的局部变量来遮蔽变量,既可以直接声明,也可以作为参数的一部分
声明:

142 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
class Foo {
static int size = 7;
static void changeIt(int size) {
size = size + 200;
System.out.println("size in changeIt is " + size);
}
public static void main (String [] args) {
Foo f = new Foo();
System.out.println("size = " + size);
changeIt(size);
System.out.println("size after changeIt is " + size);
}
}
上述代码显示在 changeIt()方法中修改静态变量 size,但是因为 changeIt()中已经有名为 size
的参数,局部变量 size 会被修改,而静态变量 size 不会发生变化。
执行 Foo 类,输出如下结果:
%java Foo
size = 7
size in changeIt is 207
size after changeIt is 7
当遮蔽变量是对象引用,而不是基本类型时,事情会变得更加有趣:
class Bar {
int barNum = 28;
}
class Foo {
Bar myBar = new Bar();
void changeIt(Bar myBar) {
myBar.barNum = 99;
System.out.println("myBar.barNum in changeIt is " + myBar.barNum);
myBar = new Bar();
myBar.barNum = 420;
System.out.println("myBar.barNum in changeIt is now " + myBar.barNum);
}
public static void main (String [] args) {
Foo f = new Foo();
System.out.println("f.myBar.barNum is " + f.myBar.barNum);
f.changeIt(f.myBar);
System.out.println("f.myBar.barNum after changeIt is "
+ f.myBar.barNum);
}
}
上述代码的输出为:
f.myBar.barNum is 28
myBar.barNum in changeIt is 99
myBar.barNum in changeIt is now 420
f.myBar.barNum after changeIt is 99
可以看到,遮蔽变量(changeIt()中的局部参数 myBar)仍然可以影响 myBar 实例变量,因为
myBar 参数接收对同一个 Bar 对象的引用。但是,当局部变量 myBar 通过改变其 barNum 值被
重新分配一个新的 Bar 对象之后, Foo 的初始 myBar 实例变量不受影响。

3.6 垃圾回收 (OCA 考点 2.4)
2.4 解释对象的生命周期(创建,“通过重新赋值解除引用”,以及垃圾回收)
垃圾回收在考试范围中不断地出现和消失。截至 OCA 8 考试,它又回来了,这让我们非
常高兴。垃圾回收是一个常见的概念,而且是计算机科学中通用的词汇。
3.6.1 内存管理和垃圾回收概要
这就是你一直在等待的!终于到了深入研究内存管理和垃圾回收的时候了。
在很多类型的应用程序中,内存管理都是至关重要的元素。考虑程序会读入海量的数据,
例如从网络中读取数据,然后将数据写入硬盘的数据库中。一种典型的设计是,读取数据并
保存在内存中的某些集合中,对数据进行一些操作,然后将数据写入数据库。数据写入数据
库之后,在用于临时存储数据的集合中,旧数据必须被清空或删除,然后重新创建以处理下
一批数据。 这个操作可能会执行几千次, 而且在 C 或 C++等没有自动垃圾回收机制的语言中,
在删除集合数据的逻辑方面出现一个小瑕疵,就会导致一些小的内存没有被收回,或者丢失。
这些小的丢失称为内存泄漏,而在数以千计的迭代中,内存泄漏可能会导致大量内存无法访
问,以至于程序最终崩溃。可以通过编写代码来干净、完整地进入人工内存管理,这是一项
平凡又复杂的任务。而且,根据不同的估算,对于复杂的程序,手动管理内存可能会使开发
的工作量加倍。
Java 的垃圾回收器提供了内存管理的自动解决方案。在多数情况下,在应用程序中不需要
添加任何内存管理逻辑。自动垃圾回收机制的缺点,是不能完全控制它运行的时间。
3.6.2 Java 垃圾回收器概要
现在,我们了解下 Java 的垃圾回收机制。从 3 万英尺的高度往下看,垃圾回收一词就是
对 Java 自动内存管理的描述。软件程序执行时,无论是在 Java、 C、 C++、 Lisp、 Ruby 还是在
其他语言中,都通过不同的方式使用内存。这里不会从头介绍计算机科学,但是通常都会在内
存中创建栈、堆、 Java 常量池、方法区域。堆,是内存中 Java 对象存放的位置,而且它是垃
圾回收进程所涉及的唯一的内存部分。
堆就是那个堆。对于考试来说,你可以称它为堆,可以称它为垃圾回收所处理的堆,或者
是称它为 Johnson,这都不重要,实际上有且只有一个堆。
因此,垃圾回收的目的,是要确保堆尽可能有足够的空间。对于考试来说,其目的是要删
除 Java 程序运行时的不可达对象。稍后,会详细介绍什么是“不可达”,现在稍作渗透。当垃
圾回收器运行时,它的目的是发现并删除不可达的对象。可以这样认为, Java 程序是在恒定的
周期中,创建所需要的对象(占用堆的空间),当不需要对象时,抛弃对象,再创建,再抛弃,
依此类推,而垃圾回收器则是整个拼图中缺失的一块。当垃圾回收器运行时,它查找已经抛
弃的对象,并将它们从内存中删除。因此,使用内存和释放内存的循环可以继续。啊,伟大
的循环。

1. 垃圾回收器何时运行
垃圾回收器由 JVM 控制, JVM 决定何时运行垃圾回收器。在 Java 程序中,可以要求 JVM
来运行垃圾回收器,但是不保证 JVM 在任何情况下都遵从。根据自身的情况判断,当 JVM 感
觉内存运行缓慢时, 通常会运行垃圾回收器。经验表明, 如果 Java 程序要求进行垃圾回收, JVM
通常会很快同意运行垃圾回收器,但是这并不是一定的。可能你认为可以依赖它时, JVM 却决
定忽略你的请求。
2. 垃圾回收器如何运行
这是无法确定的。你可能听说过,垃圾回收器使用标记和交换算法,对于某些给定的 Java
实现,这可能是真的。但是 Java 规范没有承诺具体的实现情况。你可能也听说过,垃圾回收
器使用引用计数方法,再次强调,这可能是对的,也可能是错的。对于考试来说,需要理解的
重要概念是:对象何时满足垃圾回收的条件?
概括说,每个 Java 程序都有一到多个线程。每个线程都有自己小的执行栈。通常在 Java
程序中,程序员至少会运行一个线程,这个线程来自于 main()方法,位于栈的最底层。然而,
有很多相当出色的理由,使程序员从这个初始线程(如果准备参加 OCP8 考试,要学习这个内
容)中启动其他的线程。对于每一个线程而言,除了有自己的执行栈,还有自己的生命周期。
现在所需要知道的是,线程可以是活跃的,也可以是死的。
有了这些背景知识,现在就可以完美地阐述和解决一件事:当没有活跃的线程访问某
个对象时,则这个对象符合垃圾回收的条件(注意:因为 String 常量池的特殊性,考试中出
现的垃圾回收问题,都是针对非 String对象,因此我们关于垃圾回收的讨论也只针对非 String
对象)。
基于这个规定,垃圾回收器执行某些神秘的、未知的操作;当它发现某个对象对于所有活
跃线程而言都不可达时,它会认为这个对象符合删除条件,而且它可能会在某个时间点直接删
除它(你猜对了:也可能永远都不会删除它)。当我们讨论对象是否可达时,实际是在讨论是否
有一个可达的引用变量在引用问题中的这个对象。如果 Java 程序中有引用某个对象的引用变
量,而这个引用可以被活跃线程访问,则该对象是可达的。在后续的内容中,我们会介绍对象
是如何变为不可达的。
Java 程序执行时,是否会出现内存溢出?会的。垃圾回收系统尝试将不再使用的对象移出
内存。然而,如果维护太多的活跃对象(包括活跃对象引用的其他对象),则系统可能会出现内
存溢出的情况。垃圾回收并不能确保有足够多的内存,它只能保证可用内存会被尽可能地高效
使用。
3.6.3 编写代码显式地将对象标记为可回收对象
在前面的内容中,已经了解了 Java 垃圾回收背后的理论知识。本小节会展示如何通过编
写实际代码,使对象符合垃圾回收的条件。同时,也会介绍必要时如何尝试强制执行垃圾回收,
以及在对象被移出内存之前,如何针对对象执行额外的清理操作。
1. 将引用设置为空
如前所述,当没有更多可达的引用时,该对象符合垃圾回收的条件。很明显,如果对象没

有可达的引用,那么对象自身发生什么都无所谓。对于我们的目的来说,它就是一个漂浮在空
间中的、无用的、不可访问的、不再需要的对象。
移除对象的引用的第一种方法,是将引用该对象的引用变量设置为 null。查看如下代码:
1. public class GarbageTruck {
2. public static void main(String [] args) {
3. StringBuffer sb = new StringBuffer("hello");
4. System.out.println(sb);
5. // StringBuffer
对象不符合垃圾回收的条件
6. sb = null;
7. //
现在的 StringBuffer 对象符合垃圾回收的条件
8. }
9. }
在第 3 行,将值为 hello 的 StringBuffer 对象赋值给引用变量 sb。为了使对象满足垃圾回收
的条件,将引用变量 sb 设置为 null,这个操作移除了 StringBuffer 对象的唯一引用。第 6 行代
码执行时,值为 hello 的 StringBuffer 对象的命运已定,它已经符合垃圾回收的条件。
2. 为引用变量重新赋值
也可以通过设置引用变量引用其他对象,来解除引用变量和当前对象的绑定。查看如
下代码:
class GarbageTruck {
public static void main(String [] args) {
StringBuffer s1 = new StringBuffer("hello");
StringBuffer s2 = new StringBuffer("goodbye");
System.out.println(s1);
//
此时, Stringbuffer 对象 hello 不满足垃圾回收的条件
s1 = s2; // 重新赋值 s1 ,使它引用 goodbye 对象
// 现在, Stringbuffer 对象 hello 满足垃圾回收的条件
}}
同时需要考虑在方法中创建的对象。当方法被调用时,所创建的局部变量只存在于方法的
持续期。一旦方法返回,则方法中创建的对象满足垃圾回收的条件。然而,这里有一个明显的
例外情况。如果对象是方法的返回值, 它的引用可能会被赋值给调用它的方法中的的引用变量;
因此,它不符合垃圾回收的条件。查看如下代码:
import java.util.Date;
public class GarbageFactory {
public static void main(String [] args) {
Date d = getDate();
doComplicatedStuff();
System.out.println("d = " + d);
}
public static Date getDate() {
Date d2 = new Date();
StringBuffer now = new StringBuffer(d2.toString());
System.out.println(now);
return d2;
}
}
在上述示例中,创建了名为 getDate()的方法,它返回 Date 对象。这个方法创建了两个对
象:一个是 Date 对象,一个是包含日期信息的 StringBuffer 对象。因为方法返回对 Date 对象

的引用值,而且这个引用值被赋值给局部变量,因此,即使是在 getDate()方法完成之后,该
Date 对象仍然不满足垃圾回收的条件。然而, StringBuffer 对象符合,即使并没有显式地将 now
变量设置为 null。
3. 隔离引用
还有另一种方法,当对象存在有效的引用时,仍然使对象符合垃圾回收条件。这种情况称
之为“隔离岛”。
一种简单的情况是,类中包含实例变量,该变量引用同一个类的另一个实例。现在,假设
存在两个这样的实例对象,它们互相引用。如果移除引用这两个实例的所有其他引用,即使每
个对象仍具有有效的引用,仍然没有活跃线程可以访问这两个对象。当垃圾回收器运行时,通
常能够发现这些隔离岛对象,并将它们移除。可以想象,这样的隔离岛可能会变得非常大,理
论上包含数百个对象。查看如下代码:
public class Island {
Island i;
public static void main(String [] args) {
Island i2 = new Island();
Island i3 = new Island();
Island i4 = new Island();
i2.i = i3; // i2
引用 i3
i3.i = i4; // i3
引用 i4
i4.i = i2; // i4
引用 i2
i2 = null;
i3 = null;
i4 = null;
//
执行复杂的、消耗内存的操作
}
}
当代码执行至“//执行复杂的……”时, 3 个 Island 对象(i2、 i3、 i4 引用的对象)都有实例
变量,它们引用彼此,但是它们链接到外界的链接(i2、 i3、 i4)被赋值为 null。这 3 个对象满足
垃圾回收的条件。
这里介绍了需要知道的使对象满足垃圾回收条件的所有知识。学习图 3-2,从概念上加强
对无引用对象和隔离岛的理解。
4. 强制垃圾回收
与标题恰恰相反,这里首先要提到的一点,就是垃圾回收不能强制执行。然而, Java 提供
了一些方法,允许请求 JVM 执行垃圾回收操作。
注意: Java 垃圾回收器衍变至今,已经是相当先进的状态。这里建议你永远不要在代码中
调用 System.gc() ——把它留给 JVM 来处理。
实际上,唯一可以做的,就是建议 JVM 来执行垃圾回收操作。但是实际上,无法保证 JVM
会将所有无用对象都移出内存(即便是已经执行了垃圾回收操作)。对于考试来说,理解这个概
念是非常必要的。

图 3-2 符合垃圾回收条件的 Island 对象
Java 提供的垃圾回收例程是 Runtime 类的成员。 Runtime 类是特殊的类,对于每个主程序,
有一个单独的对象(Singleton)。 Runtime 对象提供了一种机制,可以直接与虚拟机沟通。通过使
用 Runtime.getRuntime()方法,可以获得 Runtime 实例,该方法返回 Singleton。获取这个单例
之后,就可以使用 gc()方法调用垃圾回收器。或者,也可以通过 System 类调用同一方法,这
个方法是 System 类的静态方法,可以获取 Singleton。请求执行垃圾回收的最简单方法如下(记
住——这只是个请求):
System.gc();
理论上讲,在调用了 System.gc()之后,将会尽可能释放出最大的内存空间。我们说“理
论上讲”,是因为实际情况并不总是这样。首先, JVM 可能没有实现这个例程;语言规范根
本不允许该例程做实际工作。其次,在运行垃圾回收器后,另一个线程可能会抢占大量的
内存。
这并不是说 System.gc()是一个无用的方法——至少比没有强。只是不能依赖 System.gc()
来释放足够内存,进而消除对内存溢出的顾虑。与可能出现的行为相比,认证考试更感兴趣的
是确定的行为。
现在,已经有些熟悉垃圾回收的工作方式,下面来做一个小的实验,验证垃圾回收的效果。
通过下面的程序,首先知道分配给 JVM 的内存总量和可用的内存总量。然后,创建 10 000 个
Date 对象。之后,表明了剩余的可用内存数量,然后再调用垃圾回收器(决定运行垃圾回收器
时,会挂起程序,直到所有的无用对象被移出内存)。最后的剩余可用内存量,能够说明垃圾
回收是否已经执行完毕。查看如下程序:
1. import java.util.Date;
2. public class CheckGC {

148 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
3. public static void main(String [] args) {
4. Runtime rt = Runtime.getRuntime();
5. System.out.println("Total JVM memory: "
+ rt.totalMemory());
6. System.out.println("Before Memory = "
+ rt.freeMemory());
7. Date d = null;
8. for(int i = 0;i<10000;i++) {
9. d = new Date();
10. d = null;
11. }
12. System.out.println("After Memory = "
+ rt.freeMemory());
13. rt.gc(); // System.gc()
的替代方法
14. System.out.println("After GC Memory = "
+ rt.freeMemory());
15. }
16. }
现在,运行程序并检验结果:
Total JVM memory: 1048568
Before Memory = 703008
After Memory = 458048
After GC Memory = 818272
如你所见, JVM 确实回收了(即删除了)符合条件的对象。在上面的示例中,当内存剩余
458 048 字节时,建议 JVM 执行垃圾回收操作,而且它响应了我们的请求。这个程序只有一
个用户线程在运行,因此在调用 rt.gc()时,没有其他事情发生。记住,对于不同的 JVM,调
用 gc()时,行为可能不同。因此,不能保证所有无用的对象都被移出内存。唯一可以确定的
是,如果程序在内存中的运行速度非常缓慢,在抛出 OutOfMemoryException 之前,会运行
垃圾回收器。
练习 3-2
垃圾回收实验
在上面的 CheckGC 程序中,尝试将第 13 行和第 14 行放到一个循环中。会发现每次运行
GC 时,可能并不是所有内存都会被释放。
1. 在进行垃圾回收之前清理对象——finalize()方法
Java 提供了一种机制,可以在对象被垃圾回收器删除之前,运行一些代码。这些代码位于
一个名为 finalize()的方法中,其所有的类都从 Object 类中继承。表面上看,这是一个非常棒的
想法;有个对象打开了某些资源,在对象被删除之前,可能想要关闭这些资源。问题在于,由
于目前可能被收集,因此永远不能指望垃圾回收器来删除一个对象。因此,类中被重写的
finalized()方法中的代码,不一定会执行。因为对于任意给定对象都可能会执行 finalize()方法,
但是又不能指望它一定会执行,所以不要将重要的代码放在 finalize()方法中。事实上,一般推
荐不重写 finalize()方法。
2. finalize()方法的小问题
关于 finalize()方法,需要记住两个概念:

● 对于任意给定的对象, finalize()方法只会(最多)被垃圾回收器调用一次。
● 调用 finalize()使对象实际上不会被删除。
进一步探讨这些结论。首先要记住,任何能放入普通方法中的代码,都可以写入 finalize()
方法。例如,在 finalize()方法中,可以编写代码,将有问题的对象传递给一个引用,再返回给
另一个对象,这立刻就会使该对象不符合垃圾回收条件。如果在之后某个时间点,该对象又再
次符合垃圾回收条件,垃圾回收器仍然可以处理这个对象并删除它。但是,垃圾回收器会记住,
这个对象的 finalize()方法已经运行过一次,它不会再次调用 finalize()方法。
3.7 认证考试总结
本章内容涉及很多主题。如果在后续章节中看到重复的主题,也不要担心。本章内容包含
了大量的基础知识,对后续的内容会起到一定的作用。
本章首先回顾了栈和堆的知识;记住局部变量存储于栈中,实例变量与其对象一起存储于
堆中。
接着回顾了基本类型和字符串(Strings)的合法字面值,然后介绍了为基本类型和引用类型
变量赋值的基本方法,以及基本类型的类型转换规则。
接下来介绍了变量作用域的概念,或者说“变量会存在多长时间?”按照存在时长记住 4
个基本的作用域:静态变量、实例变量、局部变量和块变量。
我们介绍了未初始化变量的隐藏含义,以及局部变量必须显式赋值这一重要事实。讨论了
将一个引用变量赋值给另一个引用变量的复杂情况,以及将变量传递给方法的一些微妙之处,
其中包括了对“遮蔽变量”的讨论。
最后,研究了垃圾回收机制,它是 Java 的自动内存管理特性。我们学习到,堆是存放对
象的地方,也是炫酷的垃圾回收操作发生的地方。我们还学习到,不管 JVM 是否想要执行垃
圾回收,在最后它都会执行。你(程序员)可以请求执行垃圾回收,但是不能强制。我们介绍了
垃圾回收只应用于符合回收条件的对象,符合条件意味着“任何活跃线程都不能访问这个对
象”。最后,讨论了用途不大的 finalize()方法,以及面对认证考试所需要知道的内容。总而言
之,这是能够引人入胜的一章。
3.8 两分钟冲刺
这里包含本章中每一个考点的关键知识点。
栈和堆
● 局部变量(方法变量)存储于栈中。
● 对象及其实例变量存储于堆中。
字面值和基本类型转换(OCA 考点 2.1)
● 整数字面值可以是二进制、十进制、八进制(如 013)或是十六进制(如 0x3d)。
● longs 字面值的末尾是 L 或 l(为了便于阅读,推荐使用“L” )。
● 浮点数字面值以 F 或 f 结尾, double 字面值以数字、 D 或 d 结尾。

150 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
● boolean 字面值是 true 或 false。
● chars 的字面值是单引号中的一个字符: ' d '。
作用域(OCA 考点 1.1)
● 作用域是指变量的生命周期。
● 有 4 种基本作用域:
静态变量的生命周期与它们的类的生命周期相同。
实例变量的生命周期与它们的对象的生命周期相同。
只要方法还在栈中,方法的局部变量就存在。然而,如果局部变量的方法调用了
其他方法,则变量会临时变得无法访问。
在块中代码执行完毕之前,块中的变量都存在。
基本赋值(OCA 考点 2.1, 2.2 和 2.3)
● 字面值整数的隐式类型是 int。
● 整数表达式的结果通常是 int 大小的结果值,不会更小。
● 浮点数的隐式类型是 double(64 位)。
● 缩小基本类型值时,会消除高位。
● 复合赋值操作(如+=)会自动做类型转换。
● 引用变量存储的二进制值用于引用一个对象。
● 引用变量可以引用其声明类型的子类,但是不能引用父类。
● 在创建一个新对象时,例如 Button b = new Button();, JVM 做 3 件事:
创建一个名为 b 的引用变量,类型为 Button。
创建新的 Button 对象。
将 Button 对象赋值给引用变量 b。
使用未初始化和未赋值的变量或数组元素(OCA 考点 4.1 和 4.2)
● 当对象的数组被初始化时,数组中的对象不会自动初始化,但所有的引用获得默认值
null。
● 当基本类型的数组被初始化时,数组中的元素被赋予默认值。
● 实例变量总会用默认值初始化。
● 局部的/自动的/方法的变量永远不会有默认值。如果在初始化局部变量之前使用它,会
返回编译错误。
将变量传递给方法(OCA 考点 6.6)
● 方法的参数可以接收基本类型和(或)对象引用类型变量作为参数。
● 方法参数永远都是(变量的)副本。
● 方法的参数不是实际的对象(可以是对对象的引用)。
● 基本类型参数是原始基本变量的非附加副本。
● 引用类型参数,是对原始对象的引用的另一个副本。
● 当两个变量拥有不同的作用域,却又同名时,它们是遮蔽变量。这种情况容易导致程
序中的 bug 难以发现,或者很难回答试题。

垃圾回收(OCA 考点 2.4)
● 在 Java 中,垃圾回收(GC)提供了自动的内存管理方法。
● 垃圾回收的目的是删除不可达的对象。
● 只有 JVM 能够决定何时运行垃圾回收器;而你只能提建议。
● 垃圾回收的算法是无法确切知道的。
● 在对象被删除前,它必须符合垃圾回收条件。
● 当没有活跃线程可以访问一个对象时,该对象符合垃圾回收条件。
● 要访问一个对象,必须有一个活跃的、可达的引用来引用该对象。
● Java 应用程序可以导致内存溢出。
● 对于孤岛对象,即使它们彼此之间相互引用,仍然可以对其进行垃圾回收。
● 使用 System.gc()请求垃圾回收操作。
● Object 类有一个 finalize()方法。
● 在垃圾回收器删除对象之前,能够确保执行一次且只执行一次 finalize()方法。
● 垃圾回收器不做任何担保, finalize()方法可能永远不会运行。
● 在 finalize()方法中,可以使对象不满足垃圾回收条件。
3.9 自测题
1. 给定代码:
class CardBoard {
Short story = 200;
CardBoard go(CardBoard cb) {
cb = null;
return cb;
}
public static void main(String[] args) {
CardBoard c1 = new CardBoard();
CardBoard c2 = new CardBoard();
CardBoard c3 = c1.go(c2);
c1 = null;
//
其他操作
} }
当代码执行到//其他操作时,有多少对象符合垃圾回收条件?
A. 0
B. 1
C. 2
D. 编译失败
E. 不可能知道
F. 运行时抛出异常
2. 给定代码:
public class Fishing {
byte b1 = 4;
int i1 = 123456;
long L1 = (long)i1; //
A
152 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
short s2 = (short)i1; // B
byte b2 = (byte)i1; //
C
int i2 = (int)123.456; //
D
byte b3 = b1 + 7; //
E
}
哪行代码不会通过编译? (选择所有正确答案)
A. 行 A
B. 行 B
C. 行 C
D. 行 D
E. 行 E
3. 给定代码:
public class Literally {
public static void main(String[] args) {
int i1 = 1_000; //
A
int i2 = 10_00; //
B
int i3 = _10_000; //
C
int i4 = 0b101010; //
D
int i5 = 0B10_1010; //
E

int i6 = 0x2_a; // F
}
}
哪行代码不会通过编译? (选择所有正确答案)
A. 行 A
B. 行 B
C. 行 C
D. 行 D
E. 行 E
F. 行 F
4. 给定代码:
class Mixer {
Mixer() { }
Mixer(Mixer m) { m1 = m; }
Mixer m1;
public static void main(String[] args) {
Mixer m2 = new Mixer();
Mixer m3 = new Mixer(m2); m3.go();


Mixer m4 = m3.m1; m4.go();
Mixer m5 = m2.m1; m5.go();
}
void go() { System.out.print("hi "); }
}
结果是什么?
A. hi
B. hi hi
C. hi hi hi
D. 编译失败

第 3 章 赋 值 153
E. hi,后面是一个异常
F. hi hi,后面是一个异常。
5. 给定代码:
class Fizz {
int x = 5;
public static void main(String[] args) {
final Fizz f1 = new Fizz();
Fizz f2 = new Fizz();
Fizz f3 = FizzSwitch(f1,f2);
System.out.println((f1 == f3) + " " + (f1.x == f3.x));
}
static Fizz FizzSwitch(Fizz x, Fizz y) {
final Fizz z = x;
z.x = 6;
return z;
} }
结果是什么?
A. true true
B. false true
C. true false
D. false false
E. 编译失败
F. 运行时抛出异常
6. 给定代码:
public class Mirror {
int size = 7;
public static void main(String[] args) {
Mirror m1 = new Mirror();
Mirror m2 = m1;
int i1 = 10;
int i2 = i1;
go(m2, i2);
System.out.println(m1.size + " " + i1);
}
static void go(Mirror m, int i) {
m.size = 8;
i = 12;
}
}
结果是什么?
A. 7 10
B. 8 10
C. 7 12
D. 8 12
E. 编译失败
F. 运行时抛出异常
7. 给定代码:
public class Wind {
154 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
int id;
Wind(int i) { id = i; }
public static void main(String[] args) {
new Wind(3).go();
//
注释行
}
void go() {
Wind w1 = new Wind(1);
Wind w2 = new Wind(2);
System.out.println(w1.id + " " + w2.id);
}
}
当代码执行至注释行时,下面哪些是正确的? (选择所有正确答案)
A. 输出包含 1
B. 输出包含 2
C. 输出包含 3
D. 0 个 Wind 对象符合垃圾回收条件
E. 1 个 Wind 对象符合垃圾回收条件
F. 2 个 Wind 对象符合垃圾回收条件
G. 3 个 Wind 对象符合垃圾回收条件
8. 给定代码:
3. public class Ouch {
4. static int ouch = 7;
5. public static void main(String[] args) {
6. new Ouch().go(ouch);
7. System.out.print(" " + ouch);
8. }
9. void go(int ouch) {
10. ouch++;
11. for(int ouch = 3; ouch < 6; ouch++)
12. ;
13. System.out.print(" " + ouch);
14. }
15. }
结果是什么?
A. 5 7
B. 5 8
C. 8 7
D. 8 8
E. 编译失败
F. 运行时抛出异常
9. 给定代码:
public class Happy {
int id;
Happy(int i) { id = i; }
public static void main(String[] args) {
Happy h1 = new Happy(1);
Happy h2 = h1.go(h1);
System.out.println(h2.id);
}
Happy go(Happy h) {
Happy h3 = h;
h3.id = 2;
h1.id = 3;
return h1;
}
}
结果是什么?
A. 1
B. 2
C. 3
D. 编译失败
E. 运行时抛出异常
10. 给定代码:
public class Network {
Network(int x, Network n) {
id = x;
p = this;
if(n != null) p = n;
}
int id;
Network p;
public static void main(String[] args) {
Network n1 = new Network(1, null);
n1.go(n1);
}
void go(Network n1) {
Network n2 = new Network(2, n1);
Network n3 = new Network(3, n2);
System.out.println(n3.p.p.id);
}
}
结果是什么?
A. 1
B. 2
C. 3
D. null
E. 编译失败
11. 给定代码:
3. class Beta { }
4. class Alpha {
5. static Beta b1;
6. Beta b2;
7. }
8. public class Tester {
9. public static void main(String[] args) {
10. Beta b1 = new Beta(); Beta b2 = new Beta();
11. Alpha a1 = new Alpha(); Alpha a2 = new Alpha();
12. a1.b1 = b1;
13. a1.b2 = b1;
14. a2.b2 = b2;

156 OCA Java SE 8 程序员认证考试指南(Exam 1Z0-808)
15. a1 = null; b1 = null; b2 = null;
16. //
其他操作
17. }
18. }
当代码执行到第 16 行时,有多少对象符合垃圾回收条件?
A. 0
B. 1
C. 2
D. 3
E. 4
F. 5
12. 给定代码:
public class Telescope {
static int magnify = 2;
public static void main(String[] args) {
go();
}
static void go() {
int magnify = 3;
zoomIn();
}
static void zoomIn() {
magnify *= 5;
zoomMore(magnify);
System.out.println(magnify);
}
static void zoomMore(int magnify) {
magnify *= 7;
}
}
结果是什么?
A. 2
B. 10
C. 15
D. 30
E. 70
F. 105
G. 编译失败
13. 给定代码:
3. public class Dark {
4. int x = 3;
5. public static void main(String[] args) {
6. new Dark().go1();
7. }
8. void go1() {
9. int x;
10. go2(++x);
11. }
12. void go2(int y) {
13. int x = ++y;
14. System.out.println(x);
15. }
16. }
结果是什么?
A. 2
B. 3
C. 4
D. 5
E. 编译失败
F. 运行时抛出异常
3.10 自测题答案
1. □ C 是正确答案。只有 CardBoard object (c1)符合条件,但是还有一个相关的 Short 封
装对象,也符合条件。
2. × 基于上述信息, A、 B、 D、 E、 F 是错误答案(OCA 考点 2.4)。
2. □
E 是正确答案,在行 E 发生编译错误。在执行数学计算时,如果基本变量类型比 int
小,结果会自动转换成 int 类型。
2. × 基于上述原因, A、 B、 C、 D 是错误答案(OCA 考点 2.1)。
3. □
C 是正确答案,在行 C 发生编译错误。截至 Java 7,下画线可以包含在数字类型的
字面值中,但是不能出现在开始或结尾。
2. × A、 B、 D、 E、 F 是错误答案。 A 和 B 是合法的数字类型字面值。 D 和 E 是合法的
二进制字面值, 这是 Java 7 的新特性。F 是使用了下画线的合法的十六进制字面值(OCA
考点 2.1)。
4. □
F 是正确答案。对象 m2 的实例变量 m1 没有被初始化,因此,当 m5 尝试使用它时,
抛出 NullPointerException。
2. × 基于上述原因, A、 B、 C、 D、 E 是错误答案(OCA 考点 2.1 和 2.3)。
5. □
A 是正确答案。引用变量 f1、 z 和 f3 都引用 Fizz 的同一个实例对象。 final 修饰符确
保了引用变量不能引用其他对象, 但是 final 修饰符不能保持组织对象的状态不被更改。
2. × 基于上述原因, B、 C、 D、 E、 F 是错误答案(OCA 考点 2.2)。
6. □
B 是正确答案。在 go()方法中, m 引用单独的 Mirror 实例,但是 int i 是一个新的 int
变量,是 i2 的一个单独副本。
2. × 基于上述原因, A、 C、 D、 E、 F 是错误答案(OCA 考点 2.2 和 2.3)。
7. □
A、 B、 G 是正确答案。构造函数为 w1 和 w2 的 id 赋值。代码执行到注释行时,这
3 个 Wind 对象都不可达,因此它们都符合垃圾回收条件。
2. × 基于上述原因, C、 D、 E、 F 是错误答案(OCA 考点 1.1、 2.3 和 2.4)。
8. □
E 是正确答案。第 9 行声明的参数是合法的(尽管很丑陋),但是在同一个范围内,
第 11 行不能声明与第 9 行同名的 ouch 变量。
2. × 基于上述原因, A、 B、 C、 D、 F 是错误答案(OCA 考点 1.1 和 2.1)
9. □
D 是正确答案。在 go()方法内, h1 在作用域之外。
2 × 基于上述原因, A、 B、 C、 E 是错误答案(OCA 考点 1.1 和 6.1)。
10. □
A 是正确答案。创建了 3 个 Network 对象。对象 n2 包含一个对对象 N 的引用,对
象 n3 包含对对象 n2 的引用。标准操作过程是“使用对象 n3 的 Network 引用(第一个
p),找到对象的引用(n2),再使用这个对象的引用(第二个 p)找到对象(n1)的 id,然后
打印这个 id。”
2.0 × 基于上述原因, B、 C、 D、 E 是错误答案(OCA 考点 2.2、 2.3 和 6.4)。
11. □
B 是正确答案。应该清楚的是, a2 所引用的对象的引用仍然存在,而且 a2.b2 所引
用的对象的引用也存在。可能不那么清楚的是, 仍然可以通过 a2.b1 来访问另一个 Beta
对象——因为它是静态的。
2.0 × 基于上述原因, A、 C、 D、 E、 F 是错误答案(OCA 考点 2.4)。
12. □
B 是正确答案。 在 Telescope 类中, 有 3 个不同的变量, 名字都是 magnify。 zoomIn()
方法并没有使用 go()方法中的 magnify 变量和 zoomMore()方法中的 magnify 变量。
zoomIn()方法使类变量乘以 5(*5)。结果(10)被发送至 zoomMore()方法,但是 zoomMore()
方法中的操作仅限于 zoomMore()方法之中。正确的流程是打印出 zoomIn()方法中的
magnify 值。
2.0 × 基于上述原因, A、 C、 D、 E、 F、 G 是错误答案(OCA 考点 1.1 和 6.6)。
13. □
E 是正确答案。在 go1()中,局部变量 x 没有被初始化。
2.0 × 基于上述原因, A、 B、 C、 D 和 F 是错误答案(OCA 考点 2.1 和 2.3)。

购买地址:

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26421423/viewspace-2216612/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
分享计算机前沿技术和国外计算机先进技术书籍。

注册时间:2011-11-08

  • 博文量
    73
  • 访问量
    119433