OCA Java SE 8程式設計師認證考試指南(Exam 1Z0-808)
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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- CISSP認證考試指南(第7版)
- 程式設計師程式碼面試指南程式設計師面試
- 好程式設計師Java培訓分享Java程式設計師技能提升指南程式設計師Java
- 好程式設計師:Java程式設計師面試秘籍程式設計師Java面試
- 職業認證---系統整合工程師考試工程師
- SOLIDWORKS認證考試流程Solid
- SOLIDWORKS官方認證考試Solid
- 計劃做點事情-可信認證考試
- Java程式設計師值得擁有的TreeMap指南Java程式設計師
- Java Platform SE 8(Java™程式語言)JavaPlatform
- java程式設計師程式設計筆試基礎學習Java程式設計師筆試
- 軟體設計師考試要求及考試範圍
- 程式設計師【黑話】指南程式設計師
- 程式設計師裝B指南程式設計師
- 程式設計師脫單指南程式設計師
- 程式設計師防猝死指南程式設計師
- 程式設計師50+Java面試題程式設計師Java面試題
- Java程式設計師面試題及解答Java程式設計師面試題
- JAVA程式設計師面試題庫分享Java程式設計師面試題
- 好程式設計師Java教程分享Java面試妙招程式設計師Java面試
- 程式設計師考公指南大火?工資和生活,你怎麼選?程式設計師
- Java網路程式設計快速上手(SE基礎)Java程式設計
- 又一名倒下的程式設計師! - 程式設計師健康指南程式設計師
- 程式設計師生存指南——映象加速程式設計師
- 程式設計師健康防猝指南程式設計師
- 硬核!程式設計師延壽指南程式設計師
- Java程式設計師面試常見問題Java程式設計師面試
- 好程式設計師Java教程分享:Java工程師常見面試題程式設計師Java工程師面試題
- 歷年軟體設計師考試試題分析
- 寫給Java程式設計師的Java虛擬機器學習指南Java程式設計師虛擬機機器學習
- 程式設計師垃圾程式碼分類指南程式設計師
- 程式設計師如何備戰全國計算機二級(Python)考試?程式設計師計算機Python
- 好程式設計師Java培訓Java程式設計師必學技術程式設計師Java
- 好程式設計師Java教程分享Java面試題之Hibernate程式設計師Java面試題
- Adobe國際認證設計師含金量,能否代表設計師的真實水平?
- SOLIDWORKS認證考試常見問題解答Solid
- 三年java程式設計師面試感悟,讓你從新認識自己的不足!Java程式設計師面試
- 中軟卓越:Java程式設計師面試技巧3Java程式設計師面試