SCJP 认证考试指南
生活随笔
收集整理的這篇文章主要介紹了
SCJP 认证考试指南
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
SCJP 認證考試指南
SCJP – Sun Certified Java Programmer (Sun Java 程序員認證)
第1 章 聲明和訪問控制
目標一 創(chuàng)建數(shù)組
數(shù)組
Java 中的數(shù)組跟C/C++這些語言中的數(shù)組的語法結(jié)構(gòu)很相似。但是,Java 去掉了C/C++中的可以通過[]或者使用指針來訪問元素的功能。這種在C/C++中被普遍接受的功能雖然強大,但是也讓Bug 橫行的軟件更容易出現(xiàn)。因為Java 不支持這種直接通過指針來操縱數(shù)據(jù),這類的Bug 也被消除了。
數(shù)組是一類包含被稱為元素的值的對象。這就為你在程序中移動或保存一組數(shù)據(jù)以很方便的支持,并且允許你根據(jù)需要訪問和改變這些值。用一個小例子來說:你可以創(chuàng)建一個String 類型的數(shù)組,每一個都包含一個運動隊隊員名字。數(shù)組可以傳送給一個需要訪問每個隊員名字的方法。如果一個新隊員加入,其中一個老隊員的名字可以被修改成新隊員的名字。這就顯得比player1、player2、player3 等等很隨意的不相關(guān)的變量方便很多。跟變量通過變量名來訪問不同的是,元素通過從0 開始的數(shù)字來訪問。因此,你可以一個個的訪問數(shù)組的每個元素。
數(shù)組跟對象很相似,它們都是用new 關(guān)鍵字來創(chuàng)建,并且有屬于主要父對象類的方法。數(shù)組可能存儲簡單類型或者對象的引用。
數(shù)組的每個元素必須是同一類型的。元素的類型在數(shù)組被聲明時確定。如果你需要存儲不同類型元素的方式,你可以選擇collection 類,collection 類是Java2 考試中的新增的考點,我們將會在第十部分討論它。你可以用數(shù)組來存儲對象的句柄,你能像使用其它任意對象引用一樣訪問,摘錄或者使用它。
聲明但不分配空間
聲明一個數(shù)組不需分配任何存儲空間,它僅僅是代表你試圖創(chuàng)建一個數(shù)組。跟C/C++聲明一個數(shù)組的明顯區(qū)別就是空間的大小沒有被特別標識。因此,下面的聲明將會引起一個編譯期錯誤。
int num[5];
一個數(shù)組的大小將在數(shù)組使用new 關(guān)鍵字真正創(chuàng)建時被給定,例如:
int num[];
num = new int[5];
你可以認為命令new 的使用跟初始化一個類的實例的使用是類似的。例子中數(shù)組名num說明數(shù)組大小可以是任意大小的整形數(shù)據(jù)。
同時聲明和創(chuàng)建數(shù)組
這個例子也可以使用一行語句完成:
int num[] = new int[5];
方括號也可以放在數(shù)據(jù)類型后面或者數(shù)組名后面。下面的兩種都是合法的:
int[] num;
int num[];
你可以讀作:
一個名字為num 的整型數(shù)組
一個數(shù)據(jù)類型為整型名字為num 的數(shù)組
Java 和C/C++數(shù)組的比較
Java 數(shù)組知道它的大小,并且Java 語言支持對意外的移動到數(shù)組末端的保護。
如果你從Visual Basic 背景下轉(zhuǎn)到Java 開發(fā),并且還不習慣于一直從0 開始計數(shù),這點是很方便的。這也可以幫你避免一些在C/C++程序中很難發(fā)現(xiàn)的錯誤,例如移動到了數(shù)組末端并且指向了任意內(nèi)存地址。
例如,下面的程序會引起一個ArrayIndexOutOfBoundsException 異常。
int[] num= new int[5];
for(int i =0; i<6; i++){
num[i]=i*2;
}
訪問一個Java 數(shù)組的標準習慣用法是使用數(shù)組的length 成員
例如:
int[] num= new int[5];
for(int i =0; i<num.length; i++){
num[i]=i*2;
}
數(shù)組知道它的大小
假如你跳過了C/C++的對照,Java 中的數(shù)組總是知道它們的大小,這表現(xiàn)在length 字段。
因此,你可以通過下面的語句動態(tài)移動數(shù)組:
int myarray[]=new int[10];
for(int j=0; j<myarray.length;j++){
myarray[j]=j;
}
注意,數(shù)組有l(wèi)ength 字段,而不是length()方法。當你開始用一組字符串的時候,你會像s.length()這樣使用字符串的length 方法。
數(shù)組中的length 是域(或者說特性)而不是方法。
Java 數(shù)組和Visual Basic 數(shù)組的對照
Java 中的數(shù)組總是從0 開始。如果使用了Option base 聲明,Visual Basic 可能從1 開始。Java 中沒有跟Visual Basic 中可以使你不刪除內(nèi)容就改變數(shù)組大小的redim preserve 命令等價的語句。但你可以建立一個同樣大小的新數(shù)組,并且復制現(xiàn)有元素到里面。
一個數(shù)組聲明可以有多個方括號。Java 形式上不支持多維數(shù)組,但是它可以支持數(shù)組的數(shù)組,就是我們常說的嵌套數(shù)組。
C/C++中那樣的多維數(shù)組和嵌套數(shù)組的最主要區(qū)別就是,每個數(shù)組不需要有同樣的長度。如果你將一個數(shù)字當作一個矩陣,矩陣不一定是矩形。按照Java 語言規(guī)范:(http://java.sun.com/docs/books/jls/html/10.doc.html#27805)“括號里的數(shù)指明了數(shù)組嵌套的深度”
在其他語言中,就要跟數(shù)組的維度相符。因此,你可以建立一個類似于下面的形式的二維數(shù)組:
int i[][];
第一個維度可以匹配X,第二個維度可以匹配Y。
聲明和初始化相結(jié)合
一個數(shù)組可以通過一個語句來創(chuàng)建并初始化,這就代替了通過數(shù)組循環(huán)來初始化的方式。這種方法很適合小數(shù)組。下面的語句創(chuàng)建了一個整型數(shù)組并且賦值為0 到4:
int k[]=new int[] {0,1,2,3,4};
注意,你沒有必要確定數(shù)組元素的數(shù)量。你可能在測驗中被問到下面的語句是不是正確的問題:
int k=new int[5] {0,1,2,3,4} //Wrong, will not compile!
你可以創(chuàng)建數(shù)組的同時確定任何數(shù)據(jù)類型,因此,你可以創(chuàng)建一個類似于下面形式的字符串
數(shù)組:
String s[]=new String[] {"Zero","One","Two","Three","Four"};
System.out.println(s[0]);
這句將會輸出String[0]。
數(shù)組的默認值
不同于其他語言中的變量在類級別創(chuàng)建和本地方法級別創(chuàng)建有不同的動作,Java 數(shù)組總是被設(shè)定為默認值。
無論數(shù)組是否被創(chuàng)建了,數(shù)組中的元素總是設(shè)為默認值。因此,整型的數(shù)組總是被置0,布爾值總是被置false。下面的代碼編譯時不會出錯,并且輸出0。
public class ArrayInit{
public static void main(String argv[]){
int[] ai = new int[10];
System.out.println(ai[0]);
}
}
問題
問題1)怎樣通過一個語句改變數(shù)組大小同時保持原值不變?
1) Use the setSize method of the Array class
2) Use Util.setSize(int iNewSize)
3) use the size() operator
4) None of the above
問題2) 你想用下面的代碼查找數(shù)組最后一個元素的值,當你編譯并運行它的時候,會發(fā)生什么?
public class MyAr{
public static void main(String argv[]){
int[] i = new int[5];
System.out.println(i[5]);
}
}
1) Compilation and output of 0
2) Compilation and output of null
3) Compilation and runtime Exception
4) Compile time error
問題3)作為一個好的Java 程序員,你已忘記了曾經(jīng)在C/C++中知道的關(guān)于數(shù)組大小信息的知識。如果你想遍歷一個數(shù)組并停止在最后一個元素處。你會使用下面的哪一個?
1)myarray.length();
2)myarray.length;
3)myarray.size
4)myarray.size();
問題4)你的老板為了你寫出了HelloWorld 而很高興地為你升職了,現(xiàn)在她給你分配了一個新任務(wù),去做一個踢踏舞游戲(或者我小時候玩的曲棍球游戲)。你認為你需要一個多維數(shù)組,下面哪一個能做這個工作?
1) int i =new int[3][3];
2) int[] i =new int[3][3];
3) int[][] i =new int[3][3];
4) int i[3][3]=new int[][];
問題5)
你希望找到一個更優(yōu)雅的方式給你的數(shù)組賦值而不使用for 循環(huán)語句,下面的哪一個能做到?
1)myArray{
[1]="One";
[2]="Two";
[3]="Three";
}
2)String s[5]=new String[] {"Zero","One","Two","Three","Four"};
3)String s[]=new String[] {"Zero","One","Two","Three","Four"};
4)String s[]=new String[]={"Zero","One","Two","Three","Four"};
問題6)當你試著編譯運行下面的代碼的時候,可能會發(fā)生什么?
public class Ardec{
public static void main(String argv[]){
Ardec ad = new Ardec();
ad.amethod();
}
public void amethod(){
int ia1[]= {1,2,3};
int[] ia2 = {1,2,3};
int ia3[] = new int[] {1,2,3};
System.out.print(ia3.length);
}
}
1) Compile time error, ia3 is not created correctly
2) Compile time error, arrays do not have a length field
3) Compilation but no output
4) Compilation and output of 3
答案
答案1)
4) None of the above
你不能改變一個數(shù)組的大小。你需要創(chuàng)建一個不同大小的臨時數(shù)組,然后將原數(shù)組中的內(nèi)容放進去。Java 支持能夠改變大小的類的容器,例如Vector 或者collection 類的一個成員。
答案2)
3) Compilation and runtime Exception
當你試著移動到數(shù)組的末端的時候,你會得到一個運行時錯誤。因為數(shù)組從0 開始索引,并且最后一個元素是i[4]而不是i[5]。
答案3)
2) myarray.length;
答案4)
3) int[][] i=new int[3][3];
答案5)
3)String s[]=new String[] {"Zero","One","Two","Three","Four"};
答案6)
4) Compilation and output of 3
所有的數(shù)組的聲明都是正確的。如果你覺得不太可能,可以自己編譯這段代碼。
目標二 定義類和變量
定義類,內(nèi)部類,方法,實例變量,靜態(tài)變量和自動(本地方法)變量,需要合適的選用允許的修飾詞。(例如public,final,static,abstract 諸如此類)。這些修飾詞或者單獨使用或者聯(lián)合使用,定義了包的關(guān)系。
本目標需要注意的
我發(fā)現(xiàn)目標中用了“諸如此類”,這讓我有些煩惱,我想你需要弄明白下面詞的意思:
native
transient
synchronized
volatile
什么是類?
一個類的的定義把它很生硬描述為“方法和數(shù)據(jù)的集合”。它把面向?qū)ο缶幊坛鰜碇暗木幊趟枷虢Y(jié)合起來,這對理解該概念很有幫助。在類和面向?qū)ο蟪绦蛟O(shè)計前的主要概念是結(jié)構(gòu)化程序設(shè)計。結(jié)構(gòu)化程序設(shè)計的理念是程序員將復雜問題劃分為小塊的代碼,一般稱為函數(shù)或子程序。這符合“做一件很大很復雜的事情的好辦法是把它分成一系列比較小但更容
易管理的問題”的理念。
盡管結(jié)構(gòu)化程序設(shè)計在管理復雜性方面很有用,但它不能容易的解決代碼重用問題。程序員發(fā)現(xiàn)他們總是“重復發(fā)明”輪子。在試著對現(xiàn)實物理對象的思考中,程序設(shè)計方面的思想家找到了面向?qū)ο蟮睦砟?#xff08;有時被稱為OO)。舉例來說,一個計算機廠商準備生產(chǎn)一種新型個人電腦,如果計算機廠商使用類似于程序設(shè)計的方式的話,就要求他建立新團隊來設(shè)計新CPU 芯片,新聲卡,沒準還需要另一個團隊設(shè)計規(guī)劃制造新的主板。事實上,這根本不可能出現(xiàn)。由于電腦組件接口的標準化,計算機廠商只需要聯(lián)系配件供應(yīng)商,并商議好他們要生產(chǎn)的新型號的說明書就行了。注意組件接口標準化的重要性。
比較C++/VB 和Java 的類
因為Java 被設(shè)計成容易讓C++程序員學習的語言,因此兩種語言在處理類上有很多相似的地方。C++和Java 都有繼承,多態(tài)和數(shù)據(jù)隱藏特性,并使用顯式的修飾詞。有一些不同也是因為使Java 更容易學習和使用。C++語言實現(xiàn)了多態(tài)繼承,這樣,一個類就可以比一個的父類(或基類)更強大。Java只允許單繼承,這樣就只有一個父類。為了克服這個限制,Java 有一個被稱作接口的特性。Java 語言的設(shè)計者確定接口能夠提供多態(tài)繼承的好處而沒有壞處。所有Java 類都是Object
類的后代。
對象在Visual Basic 中是語言設(shè)計之后才加入的想法。Visual Basic 有時被稱作基于對象的語言而不是面向?qū)ο蟮恼Z言。這就好像是語言的設(shè)計者認為類很酷,然后隨著VB4 的發(fā)布,他們決定加入一個新類型的模塊,稱它為類并且加上冒號,讓它看起來更像C++。VB 的類概念中失去了至關(guān)重要的元素:繼承。微軟在VB5 中加入了跟Java 的接口很相似的接口的概念。VB 類和Java 類的最主要相似之處是引用的使用和new 關(guān)鍵字。
Java 中類的角色
類是Java 的心臟,所有的Java 代碼都在一個類里。Java 里沒有自由獨立代碼的概念,甚至最簡單的HelloWorld 應(yīng)用都是包含在類里被創(chuàng)建的。為了指出一個類是另一個類的派生類,我們使用extend 關(guān)鍵字。如果extend 關(guān)鍵字沒有被使用,這個類將是基類Object 派生的。這可以使它有一些基本的功能,比如打印自己的名字和其他一些在線程中可能需要用到的功能。
類的最簡單特性
定義一個類至少需要class 關(guān)鍵字,類名和一對花括號。如:
class classname {}
如果不是有特別作用的類,它在語法上是正確的(我很驚訝的發(fā)現(xiàn),當我舉例說明繼承時,我定義了一個跟著類似的類)。通常,一個類還會包括一個訪問修飾符,放在關(guān)鍵字class 前面,還會有程序體放在花括號之間。下面的是一個更好的類模版:
public class classname{
//Class body goes here
}
創(chuàng)建一個簡單的HelloWorld 類
這里有一個簡單的HelloWorld 程序,它將會向控制臺輸出“hello world”。
public class HelloWorld{
public static void main(String argv[]){
System.out.println("Hello world");
}
}//End class definition
關(guān)鍵字public 是一個可見的修飾符,指明了這個類對于其他類來說都是可見的。一個文件只有一個外部類可以聲明為public。內(nèi)部類將會隱藏在任意位置。如果你在一個文件中定義了多于一個的public 類,將會發(fā)生一個編譯期錯誤。注意,Java 對每一部分都是很敏感的,包含這個類的文件名字必須是HelloWorld.java。當然,這跟微軟平臺雖然保護但是卻忽略文
件的大小寫有些差別。
關(guān)鍵字class 指明了一個將被定義的類,并且類名是HelloWorld。左花括號表明類的開始。注意,類結(jié)束的右花括號后面沒有分號。注釋語句
//End class definition
使用了C/C++中同樣允許的單行類型。Java 也能夠識別/**/的注釋模式。
創(chuàng)建一個類的實例
上面描述的HelloWorld 應(yīng)用例子很淺顯的告訴了你所能創(chuàng)建的最簡單的應(yīng)用,但是它漏掉了使用類時至關(guān)重要的元素,那就是關(guān)鍵字new 的使用,new 指出了一個類的新實例的創(chuàng)建。在HelloWorld 應(yīng)用中,因為只有System.out.println 這個唯一的static 方法,并且不需要類使用new 關(guān)鍵字創(chuàng)建,因此創(chuàng)建新實例不是必要的。static 方法只能訪問static 變量。可以稍微改進一下HelloWorld 應(yīng)用,下面舉例說明一個類的新實例的創(chuàng)建。
public class HelloWorld2{
public static void main(String argv[]){
HelloWorld2 hw = new HelloWorld2();
hw.amethod();
}
public void amethod(){
System.out.println("Hello world");
}
}
上面的代碼通過這行代碼創(chuàng)建了自己的一個新實例。
HelloWorld2 hw = new HelloWorld2();
這是使用類創(chuàng)建新實例的一個基本語法。注意類的名字怎樣出現(xiàn)了兩次。第一個指明了類的引用的數(shù)據(jù)類型。這需要它不能和new 關(guān)鍵字所修飾真正的類的名字相同。這個類實例的名字是hw。這僅僅是給變量選擇的名字。這里有一個命名習慣,一個類的實例名以小寫字母開頭,而類的名字以大寫字母開頭。
創(chuàng)建方法
在上一個例子HelloWorld2 中,一個Java 中的方法跟C/C++中的函數(shù)和Visual Basic 中的子程序很相似。上例中名字為amethod 的方法和本例中的amethod 方法被聲明為public,這說明它可以在任何地方被訪問。它有一個返回值void,表明沒有值返回。并且括號中也是空的,表明它沒有參數(shù)。
同樣的方法可以從下面幾種方式之中選擇:
private void amethod(String s)
private void amethod(int i, String s)
protected void amethod(int i)
這些例子說明了一些典型的方法簽名。使用關(guān)鍵字private 和protected 說明它們將會在別處隱藏。
Java 方法和其他像C 這樣的非面向?qū)ο笳Z言的方法的區(qū)別是Java 方法屬于類。這表明它們通過點號指明代碼屬于哪個類的實例來調(diào)用。(static 方法是一個例外,但我們現(xiàn)在無需擔心)
因此在HelloWorld 中amethod 通過下面的語句調(diào)用
HelloWorld hw = new HelloWorld();
hw.amethod();
在HelloWorld 類中創(chuàng)建的其他實例中,方法被類的每個實例所調(diào)用。每個類的實例將能夠訪問它自己的變量。因此下面的代碼將調(diào)用不同實例的amethod 方法
HelloWorld hw = new HelloWorld();
HelloWorld hw2 = new HelloWorld();
hw.amethod();
hw2.amethod();
類的兩個實例hw 和hw2 可能訪問不同的變量。
自動局部變量
自動變量是方法變量。它們在方法代碼開始運行時生效,并在方法結(jié)束時失效。因為它們只能在方法內(nèi)可見,因此臨時操作數(shù)據(jù)時比較有用。如果你希望一個值在方法被調(diào)用時保持,你需要將變量創(chuàng)建在類級別。一個自動變量將“屏蔽”類級別的變量。因此,下面的代碼將打印99 而不是10
public class Shad{
public int iShad=10;
public static void main(String argv[]){
Shad s = new Shad();
s.amethod();
}//End of main
public void amethod(){
int iShad=99;
System.out.println(iShad);
}//End of amethod
}
修飾語和封裝
修飾符的可見性是Java 封裝機制的一部分。封裝允許分離方法執(zhí)行的接口。修飾符的可見性是Java 封裝機制至關(guān)重要的部分。封裝允許分離方法執(zhí)行的接口。帶來的好處就是類內(nèi)部的代碼的細節(jié)可以被改變,同時不影響其他對象的使用。這是面向?qū)ο笤O(shè)計(最后不得不在某處使用這個詞)的一個關(guān)鍵概念。
封裝一般用找回或更新private 類的變量值的方法的形式。這些方法一般是accessor 或mutator 方法。訪問方法找回值而設(shè)置方法改變值。命名慣例是這些方法名類似于setFOO 改變值,getFOO 得到值。注意,使用set 和get 來命名的方法比僅僅使程序員感到方便更重要,并且是Javabean 系統(tǒng)的重要組成部分。不過我們的測試還沒有涉及到Javabean 的內(nèi)容。舉一個例子,你有一個變量用來存儲學生的年齡。你可能簡單的用一個public 的整型變量來存儲。
int iAge;
接下來,當你的應(yīng)用程序交付使用后,你可能會發(fā)現(xiàn)你的某些學生可能有超過200 歲的記錄,還有小于0 歲的記錄。你需要一段代碼來檢查錯誤條件。所以當你的程序改變年齡的值的時候,你用if 語句來檢查范圍。
if(iAge > 70){
//do something
}
if (iAge <3){
//do something
}
當你正在做這些的時候,你漏掉了一些使用過iAge 變量的代碼,所以你被召回了,因為你可能有一個19 歲的學生,但是你的記錄里卻是190 歲。
面向?qū)ο笫褂梅庋b處理了這樣的問題,就是創(chuàng)建一個訪問包含年齡值的private 域的方法,名字類似于setAge 和getAge。setAge 方法可能有一個整型的參數(shù)并且更新年齡的private值,getAge 方法沒有參數(shù)但從private 的年齡域返回值。
public void setAge(int iStudentAge){
iAge = iStudentAge;
}
public int getAge(){
return iAge;
}
開始,我們也許認為這么長的代碼來做一小段代碼就能完成的工作沒有意義,但是,當這些方法能夠滿足你的需求時,可以幫你做更多的iAge 域的確認工作,同時不會影響已經(jīng)在使用這些信息的代碼。通過這樣的代碼執(zhí)行處理方式,實際的程序代碼行可以改變,而外面的部分(接口)保持不變。
Private(私有)
私有變量僅僅在創(chuàng)建它的類內(nèi)部可見。這意味著它們在子類里不可見。這使變量除了當前類之外,絕緣于其他方法的修改。像是修飾語和封裝里描述的,這對于將接口與接口實現(xiàn)分離開很有幫助。
class Base{
private int iEnc=10;
public void setEnc(int iEncVal){
if(iEncVal < 1000){
iEnc=iEncVal;
}else
System.out.println("Enc value must be less than 1000");
//Or Perhaps thow an exception
}//End if
}
public class Enc{
public static void main(String argv[]){
Base b = new Base();
b.setEnc(1001);
}//End of main
}
public(共有)
public 修飾符可以應(yīng)用于變量(域)或者類。它可能是你學習Java 過程中最先接觸的修飾符。想想HelloWorld.Java 程序中被這樣聲明的類的代碼
public class HelloWorld
這是因為Java 虛擬機僅僅在一個聲明為public 的類中查找神奇的main 啟動方法。public static void main(String argv[])
一個public 類有全局的作用范圍,一個實例可以在程序內(nèi)部或外部的任意位置創(chuàng)建。任何文件中只能有一個非內(nèi)部類可以用public 關(guān)鍵字定義。如果你用public 關(guān)鍵字在一個文件中定義了超過一個非內(nèi)部類,編譯器將會報錯。
使用public 修飾符定義一個變量可以使它在任何位置適用。使用方法如下:
public int myint =10;
如果你希望創(chuàng)建一個可以在任何地方修改的變量,你可以將它聲明為public。你可以使
用類似于調(diào)用方法那樣的點號來訪問它。
class Base {
public int iNoEnc=77;
}
public class NoEnc{
public static void main(String argv[]){
Base b = new Base();
b.iNoEnc=2;
System.out.println(b.iNoEnc);
}//End of main
}
注意,并不建議你對代碼的接口和執(zhí)行不加分隔的使用。如果你想改變iNoEnc 的數(shù)據(jù)類型,你必須修改執(zhí)行改變代碼的每一部分。
protected(保護)
protected 有一點古怪。一個protected 變量在類,子類和同一個包內(nèi)部可見,但不是全部可見。限制就是它在包內(nèi)部的可見性可能超過你的預期。在同一路徑下的類都是被默認為
在一個包內(nèi),因此,protected 類將會可見。這就意味著一個protected 變量會比一個沒有任何訪問修飾符的變量更有可見性。
一個沒有訪問修飾符定義的變量稱為它有默認的可見性。默認可見性是說一個變量可以在類內(nèi)部可見,而包內(nèi)的其他類中均不可見,不在同一個包的子類內(nèi)也不可見。
靜態(tài)的(static)
雖然static 可以起到可見性修飾符的作用,但它不是直接的可見性修飾符。static 修飾符可以應(yīng)用于內(nèi)部類,方法和變量。功能代碼經(jīng)常放在static 方法中,例如Math 類有完整的功能方法,如:random,sin 和round。基本數(shù)據(jù)類型的包裝類Integer,Double 等等也有static方法處理包裝過的基本數(shù)據(jù)類型,如返回符合字符串“2”的int 值。
標記一個變量為static 表明每個類只能有一個副本存在。這是與普通的情況相區(qū)別。一般情況下,一個類的每個實例都有一個整型變量的副本。在下面的非static int 例子中,三個實例中的int iMyVal 都有對應(yīng)各自實例的不同值。
class MyClass{
public int iMyVal=0;
}
public class NonStat{
public static void main(String argv[]){
MyClass m1 = new MyClass();
m1.iMyVal=1;
MyClass m2 = new MyClass();
m2.iMyVal=2;
MyClass m3 = new MyClass();
m3.iMyVal=99;
//This will output 1 as each instance of the class
//has its own copy of the value iMyVal
System.out.println(m1.iMyVal);
}//End of main
}
下面的例子說明了當你有包含static 整型數(shù)的類的多個實例時會發(fā)生什么
class MyClass{
public static int iMyVal=0;
}
public class Stat{
public static void main(String argv[]){
MyClass m1 = new MyClass();
m1.iMyVal=0;
MyClass m2 = new MyClass();
m2.iMyVal=1;
MyClass m3 = new MyClass();
m2.iMyVal=99;
//Because iMyVal is static, there is only one
//copy of it no matter how many instances
//of the class are created /This code will
//output a value of 99
System.out.println(m1.iMyVal);
}//End of main
}
你必須要忍受這樣的事實,你不能在一個static 方法內(nèi)部訪問一個非static 變量。因此,下面的代碼會引起一個編譯時錯誤
public class St{
int i;
public static void main(String argv[]){
i = i + 2;//Will cause compile time error}
一個static 方法不能在一個子類中重寫為非static 方法。同樣,一個非static(普通的)方法也不能在子類中重寫為static 方法。但是同樣的規(guī)則對方法重載沒有作用。下面的代碼在它嘗試重寫類的方法為非static 方法amethod 時將會引起一個錯誤。
class Base{
public static void amethod(){}
public class Grimley extends Base{
public void amethod(){}//Causes a compile time error
}
IBM Jikes 編譯器會產(chǎn)生下面的錯誤
Found 1 semantic error compiling "Grimley.java":
6. public void amethod(){}
<------->
*** Error: The instance method "void amethod();"
cannot override the static method "void amethod();"
declared in type "Base"
static 方法不能在子類中重寫,但是可以被隱藏
在我的模擬測驗中,我有一個問題問到static 方法是否可以被重寫,答案是不能,但是引來了大量的email,很多人舉例說明static 方法被重寫了。在子類中,重寫過程包括的不僅僅是簡單的替代一個方法。它還包括運行時決定哪個方法被調(diào)用取決于它的引用類型。
這里有一個例子的代碼,看起來顯示了一個static 方法被重寫了
class Base{
public static void stamethod(){
System.out.println("Base");}
public class ItsOver extends Base{
public static void main(String argv[]){
ItsOver so = new ItsOver();
so.stamethod();public static void stamethod(){
System.out.println("amethod in StaOver");}
這段代碼會被編譯并且輸出"amethod in StaOver"
本地的(native)
native 修飾符僅僅用來修飾方法,指明代碼體不是用Java 而是用C 或C++所寫。native方法經(jīng)常為平臺的特殊目的所寫,例如訪問某些Java 虛擬機不支持的硬件。另一個原因是為了需要獲得更好的性能。
一個native 方法以一個分號結(jié)尾,而不是代碼塊。例如下面的代碼將會調(diào)用一個可能用C++所寫的外部程序:
public native void fastcalc();
抽象(abstract)
粗略的看一下abstract 修飾符顯得很容易,但是也會漏掉它的一些隱含內(nèi)容。屬于主考者很喜歡問的那種狡猾的,關(guān)于那類修飾符的問題。
abstract 修飾符可以被用在類和方法上。當用在方法上時,表明方法會沒有方法體(也就是沒有花括號的部分),并且代碼只能在子類執(zhí)行時運行。但是,還有一些關(guān)于何時何處你能擁有abstract 方法的限制和包含這類方法的類的規(guī)則。如果一個類有一個或多個abstract方法,或者繼承了不準備運行的abstract 方法,則它必須聲明為abstract。另外一個情況是,如果一個類實現(xiàn)了接口但是不準備運行接口的每個方法。但這種情況很少見。如果一個類有abstract 方法,則它需要聲明為abstract 類不要認為一個abstract 類不能有非abstract 方法而感到心煩意亂。任何從abstract 類繼承而來的類都要實現(xiàn)基類的abstract 方法,或者聲明自身為abstract 類。這些規(guī)則傾向于問你為什么想要創(chuàng)建abstract 方法?
abstract 類對于類的設(shè)計者很有用。它使類的設(shè)計者能夠創(chuàng)建應(yīng)當被實現(xiàn)的方法的原型,但是真正的實現(xiàn)留給以后使用這個類的人。下面的例子是一個包含abstract 方法的abstract類。再次注意,類必須被聲明為abstract,否則會出現(xiàn)編譯時錯誤。下面的類是abstract 類,它會被正確編譯并打印輸出字符串
public abstract class abstr{
public static void main(String argv[]){
System.out.println("hello in the abstract");
}
public abstract int amethod();
}
常量(final)
final 修飾符可以用在類,方法和變量上。它跟遺傳關(guān)系的意思很相近,因此很容易記憶。一個final 類可能從不被繼承。另外一種想法是,一個final 類不能作為父類。任何final類中的方法自動成為final 方法。如果你不希望別的程序員“弄亂你的代碼”,這是一個有效的方法。另一個好處就是效率,編譯器對于一個final 方法的工作很少。這些內(nèi)容在Core Java的第一卷中有提及。
final 修飾符表明方法不能被重寫。因此,如果你在子類中有一個同樣簽名的方法的話,你會得到一個編譯時錯誤。下面的例子說明對一個類使用final 修飾符。這段代碼將會打印字符串"amethod"
final class Base{
public void amethod(){
System.out.println("amethod");}
}
public class Fin{
public static void main(String argv[]){
Base b = new Base();
b.amethod();}
}
一個final 變量的值不能被改變,并且必須在一定的時刻賦值。這跟其他語言中的constant的思想比較相似。
同步的(Synchronized)
synchronized 關(guān)鍵字被用來保證不只有一個的線程在同一時刻訪問同一個代碼塊。參看第七部分關(guān)于線程的內(nèi)容來了解更多的關(guān)于它的運行的知識。
瞬時(Transient)
transient 修飾符是不常用的修飾符之一。它表明一個變量在序列化過程中不能被寫出。
不穩(wěn)定的(Volatile)
你可能對volatile 關(guān)鍵字有疑問。最壞的情況就是你確認它真的是一個Java 關(guān)鍵字。根據(jù)Barry Boone 所說“它告訴編譯器一個變量可能在線程異步時被改變”接受它是Java 語言的一部分,然后去擔心別的吧。
聯(lián)合使用修飾符
可見性修飾符不能被聯(lián)合使用,一個變量不可能同時是private 和public,public 和protected,protected 和private。你當然可以聯(lián)合使用可見性修飾符和我在下面列表中提及的修飾符。
native
transient
synchronized
volatile
這樣你就可以有一個public static native 方法了。
修飾符可以用在哪里?
問題
問題1)當你試著編譯運行下面的代碼的時候,可能會發(fā)生什么?
abstract class Base{
abstract public void myfunc();
public void another(){
System.out.println("Another method");}
}
public class Abs extends Base{
public static void main(String argv[]){
Abs a = new Abs();
a.amethod();}
public void myfunc(){
System.out.println("My func");}
public void amethod(){
myfunc();}
}
1) The code will compile and run, printing out the words "My Func"
2) The compiler will complain that the Base class has non abstract methods
3) The code will compile but complain at run time that the Base class has non abstract methods
4) The compiler will complain that the method myfunc in the base class has no body, nobody at allto looove it
問題2)當你試著編譯運行下面的代碼的時候,可能會發(fā)生什么?
public class MyMain{
public static void main(String argv){System.out.println("Hello cruel world");}
}
1) The compiler will complain that main is a reserved word and cannot be used for a class
2) The code will compile and when run will print out "Hello cruel world"
3) The code will compile but will complain at run time that no constructor is defined
4) The code will compile but will complain at run time that main is not correctly defined
問題3)下面的哪個是Java 修飾符?
1) public
2) private
3) friendly
4) transient
問題4) 當你試著編譯運行下面的代碼的時候,可能會發(fā)生什么?
class Base{abstract public void myfunc();public void another(){System.out.println("Another method");}
}
public class Abs extends Base{public static void main(String argv[]){Abs a = new Abs();a.amethod();}public void myfunc(){System.out.println("My func");}public void amethod(){myfunc();}
}
1) The code will compile and run, printing out the words "My Func"
2) The compiler will complain that the Base class is not declared as abstract.
3) The code will compile but complain at run time that the Base class has non abstract methods
4) The compiler will complain that the method myfunc in the base class has no body, nobody at all to looove it
問題5)你為什么可能會定義一個native 方法呢?
1) To get to access hardware that Java does not know about
2) To define a new data type such as an unsigned integer
3) To write optimised code for performance in a language such as C/C++
4) To overcome the limitation of the private scope of a method
問題6)當你試著編譯運行下面的代碼的時候,可能會發(fā)生什么?
class Base{public final void amethod(){System.out.println("amethod");}
}
public class Fin extends Base{public static void main(String argv[]){Base b = new Base();b.amethod();}
}
1) Compile time error indicating that a class with any final methods must be declared final itself
2) Compile time error indicating that you cannot inherit from a class with final methods
3) Run time error indicating that Base is not defined as final
4) Success in compilation and output of "amethod" at run time.
問題7)當你試著編譯運行下面的代碼的時候,可能會發(fā)生什么?
public class Mod{public static void main(String argv[]){}public static native void amethod();
}
1) Error at compilation: native method cannot be static
2) Error at compilation native method must return value
3) Compilation but error at run time unless you have made code containing native amethod available
4) Compilation and execution without error
問題8)當你試著編譯運行下面的代碼的時候,可能會發(fā)生什么?
private class Base{}public class Vis{transient int iVal;public static void main(String elephant[]){}
}
1) Compile time error: Base cannot be private
2) Compile time error indicating that an integer cannot be transient
3) Compile time error transient not a data type
4) Compile time error malformed main method
問題9)當你試著編譯運行下面的兩個放在同一個目錄的文件的時候,可能會發(fā)生什么?
//File P1.java
package MyPackage;
class P1{void afancymethod(){System.out.println("What a fancy method");}
}
//File P2.java
public class P2 extends P1{afancymethod();
}
1) Both compile and P2 outputs "What a fancy method" when run
2) Neither will compile
3) Both compile but P2 has an error at run time
4) P1 compiles cleanly but P2 has an error at compile time
問題10)下面的哪一個聲明是合法的?
1) public protected amethod(int i)
2) public void amethod(int i)
3) public void amethod(void)
4) void public amethod(int i)
答案
答案1)
1) The code will compile and run, printing out the words "My Func"
一個abstract 類可以有非abstract 方法,但是任何擴展它的類必須實現(xiàn)所有的abstract 方法。
答案2)
4) The code will compile but will complain at run time that main is not correctly defined
main 的簽名包含一個String 參數(shù),而不是string 數(shù)組。
答案3)
1) public
2) private
4) transient
雖然有些文本使用friendly 來表示可見性,但它不是一個Java 保留字。注意,測試很可能包含要求你從列表中識別Java 關(guān)鍵字的問題。
答案4)
2) The compiler will complain that the Base class is not declared as abstract.
當我使用我的JDK1.1 編譯器時的真正的錯誤信息是:
Abs.java:1: class Base must be declared abstract.
It does not define void myfunc() from class Base.
class Base{
^
1 error
答案5)
1) To get to access hardware that Java does not know about
3) To write optimised code for performance in a language such as C/C++
雖然創(chuàng)建“純正的Java”代碼值得鼓勵,但是為了允許平臺的獨立性,我們不能將此作為信仰,有很多時候,我們是需要native 代碼的。
答案6)
4) Success in compilation and output of "amethod" at run time.
這段代碼調(diào)用Base 類中的amethod 版本。如果你在Fin 中試著執(zhí)行amethod 的重寫版本,你會得到一個編譯時錯誤。
答案7)
4) Compilation and execution without error
因為沒有調(diào)用native 方法,因此運行時不會發(fā)生錯誤。
答案8)
1) Compile time error: Base cannot be private
一個Base 類這樣的頂級類不能定義為private。
答案9)
4) P1 compiles cleanly but P2 has an error at compile time
雖然P2 在P1 的同一個路徑下,但是P1 用package 語句聲明了,所以對于P2 不可見。
答案10)
2) public void amethod(int i)
如果你認為選項3 這樣攜帶一個void 參數(shù)是合法的,你可能需要從你的頭腦中清空一些C/C++方面的知識。選項4 不合法是因為方法的返回類型必須緊跟著出現(xiàn)在方法名之前。
目標3,默認的構(gòu)造方法對于一個給定的類,如果有一個默認的構(gòu)造方法被創(chuàng)建或者定義了構(gòu)造方法的原型,則類也被確定了。
本目標需要注意這是一個精致小巧的目標,通過輕松的俯瞰Java 語言,對各方面集中研究來完成它吧。
什么是構(gòu)造方法?你需要通過明白構(gòu)造方法的概念來明白本節(jié)的目標。簡單來說,構(gòu)造方法是一種在類實例化時自動運行的特殊類型的方法。構(gòu)造器通常被用來初始化類中的值。構(gòu)造器有和類同樣的名字并且沒有返回值。你可能會在測驗中被問到這樣的問題:跟類有同樣名字的方法,但是有整型或者字符串型的返回值。你要多加小心并確信,任何被認為是構(gòu)造方法的方法都是
沒有返回值的。如果一個方法有了類同樣的名字但還有返回值,它不是構(gòu)造器。這里有一個例子,一個有構(gòu)造器的類,當類的實例被創(chuàng)建時打印字符串“Greeting from Crowle”:
public class Crowle{public static void main(String argv[]){Crowle c = new Crowle();}Crowle(){System.out.println("Greetings from Crowle");}
}
何時Java 提供默認構(gòu)造方法?如果你沒有顯式定義任何構(gòu)造方法,編譯器會插入一個“后臺”的不可見的無參數(shù)的構(gòu)造方法。一般來說,這只是在理論上很重要。但是,一個重要的限制作用是,如果你沒有自己創(chuàng)建構(gòu)造方法,你就只能得到默認的無參數(shù)的構(gòu)造方法了。如果你自己創(chuàng)建了構(gòu)造方法,Java 就不支持默認的無參數(shù)的構(gòu)造方法了。一旦你創(chuàng)建了自己的構(gòu)造方法,你就釋放了默認的無參數(shù)構(gòu)造方法。如果接下來你想試著創(chuàng)建一個不傳送任何參數(shù)的類的實例(也就是通過一個零參數(shù)構(gòu)造方法調(diào)用這個類),你會得到一個錯誤。因此,一旦你為一個類創(chuàng)建了任何的構(gòu)造方法,你需要創(chuàng)建一個無參數(shù)的構(gòu)造方法。這也是像Borland/Inprise 的JBuilder 這樣的代碼產(chǎn)生器在你生成類的框架時會創(chuàng)建一個零參數(shù)構(gòu)造方法的原因之一。下面例子中的代碼不會被編譯。當編譯器創(chuàng)建名字為c 的Base 類的實例時,它會插入一個指向無參數(shù)的構(gòu)造方法的調(diào)用。由于Base 有一個integer 型的構(gòu)造方法,無參數(shù)的構(gòu)造方法此時不允許存在,一個編譯期錯誤產(chǎn)生了。可以通過在Base 類中創(chuàng)建一個“什么都不干”的零參數(shù)構(gòu)造方法來修復這個錯誤。
//Warning: will not compile.
class Base{Base(int i){System.out.println("single int constructor");}
}
public class Cons {public static void main(String argv[]){Base c = new Base();}
}
//This will compile
class Base{Base(int i){System.out.println("single int constructor");}Base(){}
}
public class Cons {public static void main(String argv[]){Base c = new Base();}
}
默認構(gòu)造方法的原型這個目標要求你明白默認構(gòu)造方法的原型。它當然不能有參數(shù),并且最明顯的是默認構(gòu)造方法沒有指定范圍,但你可以定義構(gòu)造方法為public 或者protected。構(gòu)造方法不能是native, abstract, static, synchronized 或final上面這句話源于一個編譯錯誤信息。看起來像是新版本Java 的錯誤信息質(zhì)量得到提高了似的。我聽說IBM 的新Java 編譯器有好的錯誤報告。你也許被忠告過去使用多個合適版本的Java 編譯器來檢查你的代碼并查找錯誤。
問題
問題1) 給定下面的類定義
class Base{Base(int i){}
}
class DefCon extends Base{DefCon(int i){//XX}
}
如果將標記//XX 的地方替換為下面的行,哪一行是獨立合法的?
1) super();
2) this();
3) this(99);
4)super(99);
問題2)給定下面的類
public class Crowle{public static void main(String argv[]){Crowle c = new Crowle();}Crowle(){System.out.println("Greetings from Crowle");}
}
構(gòu)造方法會返回哪一種數(shù)據(jù)類型?
1) null
2) integer
3) String
4) no datatype is returned
問題3)當你試著編譯運行下面的代碼的時候,可能會發(fā)生什么?
public class Crowle{public static void main(String argv[]){Crowle c = new Crowle();}void Crowle(){System.out.println("Greetings from Crowle");}
}
1) Compilation and output of the string "Greetings from Crowle"
2) Compile time error, constructors may not have a return type
3) Compilation and output of string "void"
4) Compilation and no output at runtime
問題4)當你試著編譯運行下面的類的時候,可能會發(fā)生什么?
class Base{Base(int i){System.out.println("Base");}
}
class Severn extends Base{public static void main(String argv[]){Severn s = new Severn();}void Severn(){System.out.println("Severn");}
}
1) Compilation and output of the string "Severn" at runtime
2) Compile time error
3) Compilation and no output at runtime
4) Compilation and output of the string "Base"
問題5)下面的哪一句陳述是正確的?
1) The default constructor has a return type of void
2) The default constructor takes a parameter of void
3) The default constructor takes no parameters
4) The default constructor is not created if the class has any constructors of its own.
答案
答案1)
4) super(99);
由于類Base 定義了一個構(gòu)造方法,編譯器將不會插入默認的0 參數(shù)的構(gòu)造方法。因此,super()的調(diào)用會引起一個錯誤。一個this()調(diào)用試著在當前類中調(diào)用一個不存在的0 參數(shù)構(gòu)造方法,this(99)調(diào)用會引起一個循環(huán)引用并將引起一個編譯時錯誤。
答案2)
4) no datatype is returned
如果定義了一個沒有數(shù)據(jù)類型的構(gòu)造方法,那么沒有返回類型是相當明顯的
答案3)
4) Compilation and no output at runtime
方法Crowle 因為有一個返回類型而不是構(gòu)造方法。因此,類將會編譯并且在運行時方法Crowle 不會調(diào)用。
答案4)
2) Compile time error
當類Severn 試著在類Base 中調(diào)用0 參數(shù)構(gòu)造方法時會產(chǎn)生一個錯誤。
答案5)
3) The default constructor takes no parameters
4) The default constructor is not created if the class has any constructors of its own.
選項1 相當明顯,因為構(gòu)造方法不會有返回類型。選項2 不容易確定,Java 沒有為方法或構(gòu)造方法提供void 類型。
目標四,重載和覆寫為任意方法定義合法的返回類型,這些方法是在本類或父類中聲明過的相關(guān)方法。
本目標需要注意的這個目標可能相當模糊,它主要是要求你理解重載和覆寫的不同。為了增強你的目的性,你需要對于方法重載和方法覆寫有基本的理解。請參看第六部分:重載,覆寫,運行時類型和面向?qū)ο?同一個類中的方法我假定目標中的相關(guān)方法是指有同樣名字的方法。如果一個類中的兩個或者多個方法有同樣的名字,就被稱為方法重載。你可以在一個類中有兩個同樣名字的方法,但是他們必須有不同的參數(shù)類型和順序。通過參數(shù)的順序和類型來區(qū)分兩個重載的方法。返回類型對區(qū)分方法沒有幫助。下面的代碼會引起一個編譯時錯誤:編譯器認為amethod 試圖定義同樣的方法兩次。這就引起了一個像下面這樣的錯誤,
method redefined with different return type: void amethod(int)
was int amethod(int)
class Same{public static void main(String argv[]){Over o = new Over();int iBase=0;o.amethod(iBase);}//These two cause a compile time errorpublic void amethod(int iOver){System.out.println("Over.amethod");}public int amethod(int iOver){System.out.println("Over int return method");return 0;}
}
返回值的類型不能幫助區(qū)分兩個方法。
子類中的方法你可以在一個子類中重載一個方法,所需要的就是新方法有不同的參數(shù)順序和類型。參數(shù)的名字或者返回類型都不作考慮。如果你想重寫一個方法,即在子類中完全取代它的功能,重寫后的方法必須跟基類中被取代的原始方法有完全相同的簽名。這就包括了返回值。如果你在子類中創(chuàng)建了一個有同樣名字和簽名但是有不同返回值的方法,你將會得到一個跟上例同樣的錯誤信息:
method redefined with different return type: void amethod(int)
was int amethod(int)
編譯器認為這是錯誤的嘗試方法重載,而不認為是方法重寫。static 方法不能被重寫。如果你認為重寫只是在子類中簡單的替換了一個方法,你就很容易認為static 方法也能被重寫。事實上,我有很多包含人們舉例指明static 方法能被重寫的代碼的郵件。然而,這些并沒有考慮方法重寫在運行時決定哪個版本的方法被調(diào)用的細節(jié)問題。下面的代碼似乎表明static 方法是怎樣被重寫的。
class Base{static void amethod(){System.out.println("Base.amethod");}
}
public class Cravengib extends Base{public static void main(String arg[]){Cravengib cg = new Cravengib();cg.amethod();}static void amethod(){System.out.println("Cravengib.amethod");}
}如果你編譯并運行這段代碼,你會發(fā)現(xiàn)輸出文本Cravengib.amethod,這似乎很好的指明了重寫。然而,對于重寫,還有相對于在子類中使用一個方法簡單替換另一個方法更多的東西。還有運行時決定的方法基于引用的類的類型的問題,這可以通過創(chuàng)建正在被實例化的類的引用類型(實例初始化語句的左半部分)來說明。在上面的例子中,因為名字叫amethod 的方法與類發(fā)生了關(guān)聯(lián),而不是與特定的類的實例相關(guān)聯(lián),它不在乎什么類型的類正在創(chuàng)建它,而僅僅在意引用的類型。因此,如果你在調(diào)用amethod 前改變一下這一行,
Base cg= new Cravengib()
你就會發(fā)現(xiàn)當你運行程序時,你會得到輸出:Base.amethodcg 是一個類Cravengib 在內(nèi)存中的一個Base 類型的實例的引用(或者指針)。如果一個static方法被調(diào)用了,JVM 不會檢查什么類型正在指向它,它只會調(diào)用跟Base 類相關(guān)聯(lián)的方法的實例。
與上面的情況相對比:當一個方法被重寫時,JVM 通過句柄檢查正在指向的類的類型,并調(diào)用此類型相關(guān)的方法。可以結(jié)束這個例子了,如果你將兩個版本的amethod 方法改變?yōu)榉莝tatic,并依然創(chuàng)建類:
Base cg= new Cravengib()
編譯并運行上述代碼,你會發(fā)現(xiàn)amethod 已經(jīng)被重寫了,并且輸出Cravengib.amethod。
問題
問題1)給定下面的類定義
public class Upton{public static void main(String argv[]){}public void amethod(int i){}//Here
}
下面哪一個在替換//Here 后是合法的?
1) public int amethod(int z){}
2) public int amethod(int i,int j){return 99;}
3) protected void amethod(long l){ }
4) private void anothermethod(){}
問題2)給定下面的類定義
class Base{public void amethod(){System.out.println("Base");}
}
public class Hay extends Base{public static void main(String argv[]){Hay h = new Hay();h.amethod();}
}
下面在類Hay 中的哪一個方法將會編譯并使程序打印出字符串"Hay"?
1) public int amethod(){ System.out.println("Hay");}
2) public void amethod(long l){ System.out.println("Hay");}
3) public void amethod(){ System.out.println("Hay");}
4) public void amethod(void){ System.out.println("Hay");}
問題3)給定下面的類定義
public class ShrubHill{public void foregate(String sName){}//Here
}
下面的哪一個方法可以合法的直接替換//Here?
1) public int foregate(String sName){}
2) public void foregate(StringBuffer sName){}
3) public void foreGate(String sName){}
4) private void foregate(String sType){}
答案
答案1)
2) public int amethod(int i, int j) {return 99;}
3) protected void amethod (long l){}
4) private void anothermethod(){}
選項1 由于兩個原因不會被編譯。第一個相當明顯,因為它要求返回一個integer。另一個是試著直接在類內(nèi)部重新定義一個方法。把參數(shù)的名字從i 換成z 是無效的,并且一個方法不能在同一個類里重寫。
答案2)
3) public void amethod(){ System.out.println("Hay");}
選項3 重寫了類Base 的方法,因此任何0 參數(shù)調(diào)用都調(diào)用這個版本。
選項1 將會返回一個表示你嘗試重新定義一個不同返回類型的方法的錯誤。選項2 將會編譯對于amethod()調(diào)用Base 類的方法,并且輸出字符串"Base"。選項4 是為了抓住滿腦子C/C++的人而設(shè)計的。Java 里沒有void 方法參數(shù)這樣的事。
答案3)
2) public void foregate(StringBuffer sName){}
3) public void foreGate(String sName){}
選項1 是試著定義一個方法兩次,有一個int 返回值并不能幫助將它與存在的foregate 方法相區(qū)分。而像選項4 那樣改變方法的參數(shù)名,也不能與存在的方法相區(qū)分。注意,選項2里的foreGate 方法有一個大寫的G。
第2 章 流程控制和差錯處理
目標一 if 和switch 語句用if 和switch 編寫代碼,識別這些語句的合法參數(shù)類型
If/else 語句在java 中If/else 結(jié)構(gòu)和你所了解的其他語言一樣,switch/case 語句有一些自己的特點
if/else 的語法是
if(boolean condition){
//the boolean was true so do this
}else {
//do something else
}與在Visual Basic 語句中不同,Java 中不存在"then"關(guān)鍵字花括號在Java 中是一個常用的復合語句的指示器,它可以使你把多行代碼作為一些判斷語句的一個結(jié)果來執(zhí)行。這可以被看作一個程序塊。else 部分常常是可選的。你可以像以下這樣鏈接多個if/else 語句(但是在鏈接了幾個之后你就要考慮使用case 結(jié)構(gòu)來代替了)
int i=1;
if(i==1){
//some code
} else if (i==2){
//some code
} else{
//some code
}Java 中if 語句的一個特性是必須帶一個boolean 類型的值。你不能像使用C/C++習慣的那樣使用任何非零的數(shù)值來表示true,而用零來表示false。因此,在Java 中以下語句將不會被編譯
int k =-1;
if(k){//Will not compile!System.out.println("do something");
}
因為你必須明確的使k 的判斷語句返回一個boolean 類型的值,就像下面的例子
if(k == -1){System.out.println("do something"); //Compiles OK!
}
當在C/C++中時,你可以去掉花括號,如下
boolean k=true;
if(k)
System.out.println("do something");
這有時候被認為是不好的設(shè)計風格,因為如果你稍后要修改代碼來包含更多語句,他們就會在條件語句塊外,像這樣
if(k)
System.out.println("do something");
System.out.println("also do this");
第二個輸出語句將總會被執(zhí)行
switch 語句Peter van der Lindens 對于switch 語句的評價概括起來就像他所說的“毀滅于switch 語句”因此,這是一個你必須花費更多的精力關(guān)注的問題。switch 語句的參數(shù)必須是一個byte,char,short 或int 類型的變量。你也許會遇到考試題中用float 或者long 做switch 語句的參數(shù)。有一個非常普遍的問題似乎就是,關(guān)于在執(zhí)行switch 語句的過程中使用break 語句。這里有一個這類問題的例子。
int k=10;
switch(k){case 10:System.out.println("ten");case 20:System.out.println("twenty");
}常識判斷,執(zhí)行case 語句后面的指令,然后碰到另一個case 語句,編譯器就應(yīng)該結(jié)束執(zhí)行switch 語句。但是,就像程序設(shè)計者所熟知的,case 語句只在碰到break 語句的時候才終止執(zhí)行。結(jié)果,在上面例子中,ten 和twenty 都將被輸出。可以作為一個問題提出來的另一個小的特性就是使用default 語句。
注意:default 語句不是必須在case 語句的結(jié)尾處出現(xiàn)
按照慣例default 語句是放在case 選項的結(jié)尾處,所以通常代碼寫成如下形式
int k=10;switch(k){case 10:System.out.println("ten");break;case 20:System.out.println("twenty");break;default:System.out.println("This is the default output");
}這種方法反映大多數(shù)人的思維方式。當你嘗試其他可能情況時,會執(zhí)行default 輸出。但是,如果不是被要求的話,把defalt 語句寫在switch 語句的頂部,在語法上也是正確的。
int k=10;
switch(k){default: //Put the default at the bottom, not hereSystem.out.println("This is the default output");break;case 10:System.out.println("ten");break;case 20:System.out.println("twenty");break;
}
if 和switch 語句的合法參數(shù)正如先前所提到的,if 語句只能用boolean 類型參數(shù),而switch 語句只能用byte,char,short 或者int 類型作參數(shù)。
三項 ?操作符一些程序員主張三項操作符很有用。我不這么認為。在目標中并沒有特別提到它,所以如果在考試中出現(xiàn)的話請告訴我。
其他流程控制語句雖然公布的目標只提到了if/else 和case 語句,考試中也許會涉及do/while 和while loop語句。
練習
習題1)
創(chuàng)建一個文件含有一個公共類叫IfElse。創(chuàng)建一個方法叫g(shù)o,它接收main 方法的字符串數(shù)組參數(shù)作為它的參數(shù)。在這個方法中創(chuàng)建了一個if/else 程序塊,用來查看來自數(shù)組的第一個元素,用字符串的equals 方法來判斷輸出。如果為"true"則打印"ok",如果為"false"則打印"Not ok",如果是true 或false 以外的字符串則打印"Invalid command parameter",用一個if/else
if/else 語句這樣的次序進行設(shè)計。
習題 2)
修改這個IfElse 類,使if 語句可以檢查傳到go 方法的字符串數(shù)組是否是零長度串,使用數(shù)組length 域來檢查。如果長度為零則輸出"No parameter supplied",把現(xiàn)有的if/else if/else塊放在這個練習的else 中,使程序能實現(xiàn)原版本的功能。
答案
答案 1)
public class IfElse{public static void main(String argv[]){IfElse ie = new IfElse();ie.go(argv);}public void go(String[] sa){String s = sa[0];if(s.equals("true")){System.out.println("OK");}else if(s.equals("false")){System.out.println("Not OK");}else{System.out.println("Invalid command parameter");}}
}
答案 2)
public class IfElse{public static void main(String argv[]){IfElse ie = new IfElse();ie.go(argv);}public void go(String[] sa){if(sa.length ==0){System.out.println("No parameter supplied");}else{String s = sa[0];if(s.equals("true")){System.out.println("OK");}else if(s.equals("false")){System.out.println("Not OK");}else{System.out.println("Invalid command parameter");}}}
}
問題
問題1) 編譯運行下列代碼時會發(fā)生什么情況?
public class MyIf{boolean b;public static void main(String argv[]){MyIf mi = new MyIf();}MyIf(){if(b){System.out.println("The value of b was true");}else{System.out.println("The value of b was false");}}
}
1) Compile time error variable b was not initialised
2) Compile time error the parameter to the if operator must evaluate to a boolean
3) Compile time error, cannot simultaneously create and assign value for boolean value
4) Compilation and run with output of false
問題2) 編譯運行下列代碼時會發(fā)生什么情況?
public class MyIf{public static void main(String argv[]){MyIf mi = new MyIf();}MyIf(){boolean b = false;if(b=false){System.out.println("The value of b is"+b);}}
}
1) Run time error, a boolean cannot be appended using the + operator
2) Compile time error the parameter to the if operator must evaluate to a boolean
3) Compile time error, cannot simultaneously create and assign value for boolean value
4) Compilation and run with no output
問題3 ) 編譯運行下列代碼時會發(fā)生什么情況?
public class MySwitch{public static void main(String argv[]){MySwitch ms= new MySwitch();ms.amethod();}public void amethod(){char k=10;switch(k){default:System.out.println("This is the default output");break;case 10:System.out.println("ten");break;case 20:System.out.println("twenty");break;}}
}
1) None of these options
2) Compile time error target of switch must be an integral type
3) Compile and run with output "This is the default output"
4) Compile and run with output "ten"
問題4) 編譯運行下列代碼時會發(fā)生什么情況?
public class MySwitch{public static void main(String argv[]){MySwitch ms= new MySwitch();ms.amethod();}public void amethod(){int k=10;switch(k){default: //Put the default at the bottom, not hereSystem.out.println("This is the default output");break;case 10:System.out.println("ten");case 20:System.out.println("twenty");break;}}
}
1) None of these options
2) Compile time error target of switch must be an integral type
3) Compile and run with output "This is the default output"
4) Compile and run with output "ten"
問題5) 下面哪個是不能用于switch 語句的參數(shù)?
1) byte b=1;
2) int i=1;
3) boolean b=false;
4) char c='c';
答案
答案 1)
4) Compilation and run with output of false
因為boolean b 在類級中被創(chuàng)建,它不需明確初始化,而且它有默認的boolean 值false。if語句判斷一個boolean 值,所以b 符合這個要求。
答案 2)
4) Compilation and run with no output
因為b 是boolean 類型,if 語句不會產(chǎn)生錯誤。如果b 是任何其他的數(shù)據(jù)類型,在你試圖賦值而不是比較的時候錯誤就產(chǎn)生了。下列表達
if(b=false)
通常是一個程序員的錯誤。程序員大多要表現(xiàn)
if (b==false)
如果b 的類型是boolea 以外的任意類型,會導致編譯期錯誤。if 表達式的要求是必須返回一個boolean 類型,因為(b=false)返回一個boolean 類型,所以被接受(如果無用處)。
答案 3)
4) Compile and run with output "ten"
答案 4)
1) None of these options
因為下句后缺少break 語句
case 10;
實際輸出結(jié)果會是"ten"接著是"twenty"
答案 5)
1) byte b=1;
2) int i=1;
4) char c='c';
switch 語句可以使用byte,char 或int 作參數(shù)。
目標二 循環(huán),break 和continue用循環(huán)格式編寫代碼,使用帶標簽和不帶標簽的break 和continue 語句,聲明循環(huán)計數(shù)器的值在循環(huán)執(zhí)行中或循環(huán)結(jié)束時
for 語句最常用的循環(huán)方法就是應(yīng)用for 語句。對于for 語句在其他的編程語言中有非常相似的結(jié)構(gòu)。比如C/C++和perl 就有for 結(jié)構(gòu)。很多程序員在循環(huán)中使用for 結(jié)構(gòu),因為其簡潔,自含,容易理解而且不容易混亂。類似C++而與C 語言不同,循環(huán)控制變量可以在for 語句中定義和初始化。如下
public class MyLoop{public static void main(String argv[]){MyLoop ml = new MyLoop();ml.amethod();}public void amethod(){for(int K=0;K<5l;K++){System.out.println("Outer "+K);for(int L=0;L<5;L++){System.out.println("Inner "+L);}}}
}
內(nèi)循環(huán)代碼在每次外循環(huán)執(zhí)行時會循環(huán)執(zhí)行五次。所以輸出為:
Outer 0;
Inner 0
Inner 1
Inner 2
Inner 3
inner 4
Outer 1;
Inner 0
Inner 2
for 語句和Visual Basic 的for/next 循環(huán)一樣。你可以認為它的語法是
for(initialization; conditional expression;increment)其中條件表達式必須是boolean 判斷,就像if 語句的簡單形式。在上例的代碼中,for 語句緊跟著是花括號中的程序塊。類似if 語句,當不需要使用程序塊時,你可以使用下面這樣的簡單形式
for(int i=0;i<5;i++)
System.out.println(i);在任何版本中你都不能用分號來結(jié)束一個for 行,如果你這么做,for 循環(huán)就會原地打轉(zhuǎn)直到條件滿足,然后就會以“直線”的方式執(zhí)行下面的代碼。在此例中你不是必須在for循環(huán)中定義變量,但是如果在循環(huán)中定義變量,當跳出循環(huán)時變量也就跳出了它的作用域。按照變量作用域盡可能小的說法,這可以看作是一個優(yōu)點。
for 中的塊為空在語法中也是正確的,這樣循環(huán)會永遠進行下去
for(;;){System.out.println("forever");
}
但是用while(true)的形式可能會更加簡潔
while(true){System.out.println("true");
}
while 循環(huán)和do 循環(huán),意料之中while 和do 循環(huán)的運行就像你想象的一樣,和在其他語言中相同。因此,while 會依照判斷執(zhí)行零到多次,而do 會執(zhí)行一到多次。while 循環(huán)的語法是:
while(condition){bodyOfLoop;
}像if 語句一樣,條件是一個boolean 類型的判斷。同樣,你不能像C/C++習慣的那樣用零來代表false,而用任意其他值來代表true。所以,你可能會像下面那樣創(chuàng)建一個while 循環(huán)
while(i<4){i++;System.out.println("Loop value is :"i);
}注意,如果變量i 為4,或者比4 大,當你到達while 語句時,將沒有輸出。相反,do 循環(huán)總是會執(zhí)行一次。所以,不管進入循環(huán)時變量i 的值是什么,以下代碼總是會得到至少一個輸出。
goto 語句,科學還是迷信?Java 的設(shè)計者決定同意寫過著名文章"Goto 有害"的編程領(lǐng)袖Edsger Dijkstra 的觀點。因為不加選擇的使用goto 語句會導致“意大利面條似的代碼”難以維護,不可使用,而且這被認為是不好的編程風格。“意大利面條似的代碼”是指不容易表述邏輯開始和結(jié)束的代碼。goto 語句有時會被說成“無條件跳轉(zhuǎn)”,也就是可能會寫這樣的代碼,不進行判斷就從程序的一部分跳轉(zhuǎn)到另外一處。這在某些情況下是有用的,即Java 為break 和continue 關(guān)鍵字提供了有標簽和無標簽兩個版本。
public class Br{public static void main(String argv[]){Br b = new Br();b.amethod();}public void amethod(){for(int i=0;i <3;i ++){System.out.println("i"+i+"\n");outer://<==Point of this exampleif(i>2){break outer;//<==Point of this example}//End of iffor(int j=0; j <4 && i<3; j++){System.out.println("j"+j);}//End of for}//End of for}//end of Br method
}
然后,你需要挑出代碼中哪個是要輸出的字母組合。順便說一下,"\n"是輸出一個空白行。
跳轉(zhuǎn)到標簽在有些條件下從內(nèi)循環(huán)跳到外循環(huán)常被描述,你可以使用帶標簽的break 和continue 語句來實現(xiàn)它。一個標簽是一個簡單的非關(guān)鍵字,后面跟一個冒號。通過在break 或continue后使用標簽,你的代碼可以跳轉(zhuǎn)到此標簽處。這是便捷的實現(xiàn)部分條件循環(huán)的方法。你當然可以用if 語句,但是一個break 語句更方便。按照Elliotte Rusty Harold,一個著名的Java作者所說,“在整個Java1.0.1 源代碼中,只用了七個continue 語句寫java 包。”這意味著在實際編程中你可能不會得到充分的練習,所以為了考試你要花費更大的精力來學好它。考試題的編寫者好像熱愛設(shè)計費解的網(wǎng)狀的帶有break 和continue 語句的循環(huán),你可能永遠不會遇到有好的設(shè)計的代碼。
關(guān)鍵概念break 語句完全放棄執(zhí)行當前循環(huán),continue 語句只放棄整個循環(huán)中當前本次循環(huán)
做下面的例子
public class LabLoop{public static void main(String argv[]){LabLoop ml = new LabLoop();ml.amethod();}public void amethod(){outer:for(int i=0;i<2;i++){for(int j=0;j<3;j++){if(j>1)//Try this with break instead of continuecontinue outer;System.out.println("i "+ i + " j "+j);}}//End of outer forSystem.out.println("Continuing");}
}
這個版本有以下輸出
i 0 j 0
i 0 j 1
i 1 j 0
i 1 j 1
Continuing
如果你用break 替換continue,i 計數(shù)器會在零處停止,因為外循環(huán)會被放棄,而不會簡單的進入下一個遞增。
問題
問題1) 編譯運行一個方法中的下列代碼時會發(fā)生什么情況?
for(int i=0;i<5;){System.out.println(i);i++;continue;
}
1) Compile time error, malformed for statement
2) Compile time error continue within for loop
3) runtime error continue statement not reached
4) compile and run with output 0 to 4
問題2)編譯運行下列代碼時會發(fā)生什么情況?
public class LabLoop{public static void main(String argv[]){LabLoop ml = new LabLoop();ml.amethod();mainmethod:System.out.println("Continuing");}public void amethod(){outer:for(int i=0;i<2;i++){for(int j=0;j<3;j++){if(j>1)break mainmethod;System.out.println("i "+ i + " j "+j);}}//End of outer for}
}
1)
i 0 j 0
i 0 j 1
Continuing
2)
i 0 j 0
i 0 j 1
i 1 j 0
i 1 j 1
Continuing
3)
Compile time error
4)
i 0 j 0
i 0 j 1
i 1 j 0
i 1 j 1
i 2 j 1
Continuing
問題3)編譯運行下列代碼時會發(fā)生什么情況?
public void amethod(){outer:for(int i=0;i<2;i++){for(int j=0;j<2;j++){System.out.println("i="+i + " j= "+j);if(i >0)break outer;}}System.out.println("Continuing with i set to ="+i);
}
1) Compile time error
2)
i=0 j= 0
i=0 j= 1
i=1 j= 0
3)
i=0 j= 0
i=0 j= 1
i=1 j= 0
i=2 j= 0
4)
i=0 j= 0
i=0 j= 1
問題4)編譯運行下列代碼時會發(fā)生什么情況?
int i=0;
while(i>0){System.out.println("Value of i: "+i);
}
do{System.out.println(i);
} while (i <2);
1)
Value of i: 0
followed by
0
1
2
2)
0
1
2
3)
Value of i: 0
Followed by continuous output of 0
4) Continuous output of 0
問題5) 編譯運行下列代碼時會發(fā)生什么情況?
public class Anova{public static void main(String argv[]){Anova an = new Anova();an.go();}public void go(){int z=0;for(int i=0;i<10; i++,z++){System.out.println(z);}for(;;){System.out.println("go");}}
}
1) Compile time error, the first for statement is malformed
2) Compile time error, the second for statement is malformed
3) Output of 0 to 9 followed by a single output of "go"
4) Output of 0 to 9 followed by constant output of "go"
問題6)下列代碼的輸出結(jié)果是什么?
public class MyFor{public static void main(String argv[]){int i;int j;outer:for (i=1;i <3;i++)inner:for(j=1; j<3; j++) {if (j==2)continue outer;System.out.println("Value for i=" + i + " Value for j=" +j);}}
}
1) Value for i=1 value for j=1
2) Value for i=2 value for j=1
3) Value for i=2 value for j=2
4) Value for i=3 value for j=1
答案
答案 1)
4) compile and run with output 0 to 4
這是一個很奇怪但是完全正確的語句
答案 2)
3) Compile time error
你不能武斷的跳入另一個方法,在goto 語句中會帶來很多有害的結(jié)果。
答案 3)
1) Compile time error
這實際上不是關(guān)于break 和continue 的問題。這段代碼不會被編譯,因為變量對for 循環(huán)外部來說永遠是不可見的。所以最后的System.out.println 語句會引起編譯時錯誤。
答案 4)
1) Continuous output of 0
沒有值被增加,而且如果第一次判斷不為真時while 循環(huán)將不會執(zhí)行。
答案 5)
4) Output of 0 to 9 followed by constant output of "go"
第一個for 循環(huán)結(jié)構(gòu)不常用但是完全正確。
答案6)
1) Value for i=1 value for j=1
2) Value for i=2 value for j=1
目標三 try/catch 和方法重寫編寫代碼合理使用異常和異常處理機制(try catch finally),定義和重寫方法拋出異常.一個異常情況是當程序進入一個不是很正常的狀態(tài).異常捕獲有時是指錯誤捕獲.一個典型的異常例子是當程序試圖打開一個不存在的文件時或者你試圖訪問一個數(shù)組中不存在的元素時.try 和catch 語句是構(gòu)建Java 異常處理的一部份.不論C/C++還是Visua Basic 都沒有直接對應(yīng)Java 異常處理的結(jié)構(gòu).C++支持異常,但是是可選的,Visial Basic 支持On Error/Goto 錯誤捕獲,這帶有早期不靈活的BASIC 編程時代的味道。Java 異常是Java 語言的一個結(jié)構(gòu)。例如如果你要執(zhí)行I/O 操作,你必須把它放在錯誤處理中。你當然可以不把它放在處理中,這毫無作用。下面是一個小代碼片斷,我用Borland/Inprise JBuilder 臨時停止控制臺輸出,等待按任意鍵繼續(xù)
public class Try{import java.io.*;public static void main(String argv[]){Try t = new Try();t.go();}//End of mainpublic void go(){try{InputStreamReader isr = new InputStreamReader(System.in);BufferedReader br = new BufferedReader(isr);br.readLine();} catch(Exception e){/*Not doing anything when exception occurs*/} //End of trySystem.out.println("Continuing");}//End of go
}在這個例子中,錯誤出現(xiàn)時沒有任何處理,但是程序員一定知道錯誤有可能發(fā)生。如果你移去try 和catch 字句,代碼將完全不會被編譯。編譯器知道I/O 方法會引發(fā)異常而且需要異常處理代碼。
與Visal Basic 和C/C++比較Visal Basic 或C/C++允許拋出混合“快且臟”的程序,假裝沒有錯誤發(fā)生過,Java 比它們嚴格些。記得DOS 的最初版本被他的創(chuàng)作者叫做QDOS,因為是快且臟的DOS,看看我們已經(jīng)在這樣的環(huán)境下生活了多久。當你開始把快且臟的程序放到try/catch 塊當中,也就開始了真正的錯誤跟蹤。這不是完全的束縛和編程律條,這只是勸說你“做正確的事”。
方法重寫,拋出異常在子類中一個重寫的方法可能只拋出父類中聲明過的異常或者異常的子類。這只適用于方法重寫而不適用于方法重載。所以如果如果一個方法有完全相同的名稱和參數(shù),它只能拋出父類中聲明過的異常或者異常的子類。但是它拋出很少或者不拋出異常。所以下面的例子
將不被編譯
import java.io.*;
class Base{public static void amethod()throws FileNotFoundException{}
}
public class ExcepDemo extends Base{
//Will not compile, exception not in base version of methodpublic static void amethod()throws IOException{}
}
如果在父類中有拋出IOException 異常的方法, 在子類中的方法拋出FileNotFoundException,代碼將編譯通過。再次,記住只適用于方法重寫,在方法重載中沒有類似規(guī)定。一個在子類中重寫的方法可能會拋出異常。
throw 子句我們在代碼中需要包含可能拋出異常的try/catch 塊的一個原因就是,你的代碼可以開始展現(xiàn)出什么可能發(fā)生,而不是什么應(yīng)該發(fā)生。你可以通過使用throws 字句作為方法聲明的一部分來把異常放到堆棧中。這就有效的說明“當一個錯誤發(fā)生時,這個方法拋出這個異常,并且這個異常必須被調(diào)用它的方法捕獲”。這有一個使用throw 子句的例子
import java.io.*;
public class Throws{public static void main(String argv[]){Throws t = new Throws();try{t.amethod();}catch (IOException ioe){}}public void amethod() throws IOException{FileInputStream fis = new FileInputStream("Throws.java");}
}
問題
問題 1) 編譯運行以下代碼會發(fā)生什么情況?
import java.io.*;
class Base{public static void amethod()throws FileNotFoundException{}
}
public class ExcepDemo extends Base{public static void main(String argv[]){ExcepDemo e = new ExcepDemo();}public static void amethod(){}protected ExcepDemo(){
try{
DataInputStream din = new DataInputStream(System.in);
System.out.println("Pausing");
din.readChar();
System.out.println("Continuing");
this.amethod();
}catch(IOException ioe) {}
}
}
1) Compile time error caused by protected constructor
2) Compile time error caused by amethod not declaring Exception
3) Runtime error caused by amethod not declaring Exception
4) Compile and run with output of "Pausing" and "Continuing" after a key is hit
問題2) 編譯運行以下代碼會發(fā)生什么情況?
import java.io.*;
class Base{
public static void amethod()throws FileNotFoundException{}
}
public class ExcepDemo extends Base{
public static void main(String argv[]){
ExcepDemo e = new ExcepDemo();
}
public static void amethod(int i)throws IOException{}
private ExcepDemo(){
try{
DataInputStream din = new DataInputStream(System.in);
System.out.println("Pausing");
din.readChar();
System.out.println("Continuing");
this.amethod();
}catch(IOException ioe) {}
}
}
1) Compile error caused by private constructor
2) Compile error caused by amethod declaring Exception not in base version
3) Runtime error caused by amethod declaring Exception not in base version
4) Compile and run with output of "Pausing" and "Continuing" after a key is hit
問題3) 編譯運行以下代碼會發(fā)生什么情況?
import java.io.*;
class Base{public static void amethod()throws FileNotFoundException{}
}
public class ExcepDemo extends Base{public static void main(String argv[]){ExcepDemo e = new ExcepDemo();}public static void amethod(int i)throws IOException{}private boolean ExcepDemo(){try{DataInputStream din = new DataInputStream(System.in);System.out.println("Pausing");din.readChar();System.out.println("Continuing");this.amethod();return true;}catch(IOException ioe) {}finally{System.out.println("finally");}return false;}
}
1) Compilation and run with no output.
2) Compilation and run with output of "Pausing", "Continuing" and "finally"
3) Runtime error caused by amethod declaring Exception not in base version
4) Compile and run with output of "Pausing" and "Continuing" after a key is hit
問題4) 以下哪個要求程序員添加外部的try/catch 異常處理。
1)Traversing each member of an array
2) Attempting to open a file
3) Attempting to open a network socket
4) Accessing a method in other class
問題5) 編譯運行以下代碼會發(fā)生什么情況?
import java.io.*;
class granary{public void canal() throws IOException{System.out.println("canal");}
}
public class mmill extends granary{public static void main(String argv[]){System.out.println("mmill");}public void canal(int i) throws Exception{System.out.println("mmill.canal");}public void canal(long i) {System.out.print("i");}
}
1) Compile time error
2) Runtime errors
3) Compile error, mmill version of canal throws Exception not in granary version
4) Compilation and run with output of mmill
答案
問題1) 答案
4) Compile and run with output of "Pausing" and "Continuing" after a key is hit
在子類中的重寫方法不能拋出在基類中沒有拋出的異常。在這個例子中的方法amethod 沒有拋出異常,所以編譯不會出現(xiàn)問題。構(gòu)造器不能是protect 類型的。
問題2) 答案
4) Compile and run with output of "Pausing" and "Continuing" after a key is hit
在這個版本中amethod 被重寫了,沒有限制拋出或不拋出異常。
問題3) 答案
1) Compilation and run with no output.
好的,我有點跑題了,注意構(gòu)造器有一個返回值。這把它變成了一個普通方法,而且當沒有實例被創(chuàng)建時它將不會被調(diào)用。
問題4) 答案
2) Attempting to open a file
3) Atempting to open a network socket
通常來說,所有的I/O 操作都需要外在的使用try/catch 塊的異常處理。JDK1.4 考試不明確的覆蓋I/O,但是也許會提到錯誤處理的內(nèi)容。
問題5) 答案
4) Compilation and run with output of mmill
什么樣的異常可以被拋出的限制只是應(yīng)用于被重寫的方法,不用于被重載的方法。因為canal方法在mmill 版本中被重載(也就是它帶有了不同的參數(shù)類型),所以不會有編譯或運行錯誤。
目標四 什么情況下產(chǎn)生異常識別發(fā)生在代碼片斷指定位置的異常產(chǎn)生的結(jié)果。注意:異常必須是運行時異常,一個被檢查的異常或者一個錯誤(代碼可能包括try,catch 或者finally 子句,在任何可能的組合中)
目標注釋這個目標要求你理解可控的和不可控異常(一種你要寫代碼捕獲,另一種不用),理解finally 子句如何工作。
檢查和非檢查異常雖然Java 強調(diào)你把捕獲異常代碼插入到他們可能發(fā)生的地方像I/O 操作等,這樣比較方便,但是如果你必須把這些代碼插入到程序員應(yīng)該控制程序狀態(tài)的地方,就不方便了。這種情況的一個例子就是遍歷數(shù)組的每一個元素。Java 中一個優(yōu)美的地方就是它不需要程序員的介入而明確的報告這種異常類型的方式。這種自動異常處理是由把異常分為可控和不可控異常實現(xiàn)的。像內(nèi)存耗盡或者訪問到數(shù)組末尾這種情況會自動識別,而試圖打開不存在的文件就需要明確的try/catch 異常捕獲。
默認的非檢查信息非檢查異常出現(xiàn)的一個默認結(jié)果就是一個信息會被發(fā)送到控制臺。例如下面代碼
public class GetArg{public static void main(String argv[]){System.out.println(argv[0]);}
}
如果編譯運行代碼而不輸入命令行參數(shù),你會在控制臺得到一個錯誤信息
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException at GetArg.main(GetArg.java:3)
同時這種情況是可以用于學習目的編寫的程序的,在實際程序中,用戶大概不會訪問控制臺,也不會理解這樣的信息。最好編寫代碼來檢查可能產(chǎn)生非運行異常的數(shù)值。所以代碼可以修改成
public class GetArg{public static void main(String argv[]){if(argv.length ==0){System.out.println("Usage: GetArg param");}else{System.out.println(argv[0]);}}
}
檢查異常要求程序員編寫代碼處理檢查異常。如果沒有代碼處理檢查異常可能會出現(xiàn)編譯不通過。就像前面的代碼帶有try/catch 塊結(jié)構(gòu),但是寫一個空catch 塊,不對產(chǎn)生的異常進行任何處理也是可以的。當然這通常不是好的設(shè)計,當你碰到要在異常時編寫代碼的問題時,你可能還是要在這些代碼中做些有用的事。在catch 塊常做的兩件事是產(chǎn)生出錯信息和打印錯誤跟蹤。異常系統(tǒng)提供一個非常方便的方法,通過getMessage 方法產(chǎn)生常見的有意義的錯誤信息。
看看以下代碼
import java.io.*;public class FileOut{public static void main(String argv[]){try{FileReader fr = new FileReader("FileOut.txt");}catch(Exception e){System.out.println(e.getMessage());}}
}
如果你在一個沒有FileOut.txt 文件的目錄中編譯運行這段代碼,你會得到一個錯誤信息
FileOut.txt (No such file or directory)
finally 子句在考試中你可能會被問到,在什么情況try/catch 子句后的finally 方法會被執(zhí)行。簡單回答就是finally 子句總是會被執(zhí)行,甚至當你覺得它可能不會被執(zhí)行時。所以說,try/catch/finally 語句的執(zhí)行順序是你應(yīng)該注意確認它在什么情況下是怎么執(zhí)行的。
關(guān)鍵概念不論在try/catch 部分是不是有返回,try/catch 塊的finally 子句總會執(zhí)行。少數(shù)情況下當有如下調(diào)用時,finally 子句不會被執(zhí)行
System.exit(0);考試往往不會在這個規(guī)則上考你。考試更可能給你一個包括return 語句的例子,來誤導你認為代碼不執(zhí)行finally 語句就返回。不要被誤導,finally 子句總是會執(zhí)行。try/catch 子句在它結(jié)構(gòu)正確時一定會捕獲錯誤。所以你不能試圖在捕獲特殊的IOException 之前寫一個捕獲一般異常的catch,來捕獲所有的Exception.
下面代碼將不會通過編譯
try{DataInputStream dis = new DataInputStream(System.in);dis.read();
}catch (Exception ioe) {}
catch (IOException e) {//Compile time error cause}
finally{}
這段代碼會在編譯時發(fā)出錯誤信息,更特殊的IOException 將不會被達到。
問題
問題1) 下列那個需要建立try/catch 塊或者重新拋出異常?
1) Opening and reading through a file
2) Accessing each element of an array of int values
3) Accessing each element of an array of Objectts
4) Calling a method defined with a throws clause
問題2) 編譯運行下列代碼會發(fā)生什么情況?
import java.io.*;
class Base{public static void amethod()throws FileNotFoundException{}
}
public class ExcepDemo extends Base{public static void main(String argv[]){ExcepDemo e = new ExcepDemo();}public boolean amethod(int i){try{DataInputStream din = new DataInputStream(System.in);System.out.println("Pausing");din.readChar();System.out.println("Continuing");this.amethod();return true;}catch(IOException ioe) {}finally{System.out.println("Doing finally");}return false;}ExcepDemo(){amethod(99);}
}
1) Compile time error amethod does not throw FileNotFoundException
2) Compile, run and output of Pausing and Continuing
3) Compile, run and output of Pausing, Continuing, Doing Finally
4) Compile time error finally clause never reached
答案
答案 1)
打開讀一個文件
4) Calling a method defined with a throws clather Resources on this topic
數(shù)組元素的類型對錯誤處理沒有任何影響。通過定義throws 子句在方法中的使用,可能會拋出一個異常,這個異常類型會被使用它的代碼捕獲或者再拋出。
答案 2)
3) Compile, run and output of Pausing, Continuing, Doing Finally
finally 子句總是會運行。
目標五、六 使用斷言編寫正確使用斷言的代碼,并且區(qū)分適當和不適當?shù)臄嘌允褂谩WR別關(guān)于斷言機制的正確論述。
目標的評論斷言是隨著2002 年中期JDK1.4 考試版本的發(fā)布而添加到Sun Certified Java Programmers 考試目標中的。由于它們是考試的新特性,你一定會在考試碰到這類題目。斷言是其他面向?qū)ο笳Z言的特性,在它被加入到Java 中的時候一度面臨很大的壓力。斷言是因JDK1.4 的發(fā)布而添加到Java 語言中的,所以在寫著此書的時候沒有太多關(guān)于這個主題的筆墨。
斷言為何存在C++中可以使用斷言,但C 或Visual Basic(或者據(jù)我了解還有Pascal)中沒有,所以很多人都沒有使用過。如果事情到了很多C++程序都沒有使用過它們的地步。斷言是一個相當簡單的概念,你只需要寫一個始終都是true 的語句,但是在形式上它們可以從最終的編譯版本中去除,所以不會導致運行時開銷。使用JDK1.4 之前模擬斷言功能的結(jié)構(gòu)來書寫代碼是完全可以的,但是以這種方式做起來會很困難,它們在運行時會被關(guān)閉。在C/C++語言中,斷言可以使用語言預處理器來創(chuàng)建,在新聞組中有大量關(guān)于Java 語言是否應(yīng)該有預處理系統(tǒng)的討論。觀點的分歧在于有人認為預處理宏是惡魔的東西,它帶來了創(chuàng)造過于復雜結(jié)構(gòu)的機會,有人認為它會給語言帶來不可思議的力量。無論是哪種方式,Java 設(shè)計者傾向于實現(xiàn)預處理機制,并且在JDK1.4 中包含了斷言。
如何使用斷言何地以及如何使用斷言大概需要一種類似于何地及如何使用注釋的判斷方式。一些程序員從來不使用注釋,這種風格的程序被廣泛認為是糟糕的程序。因為大約80%的代碼是由其他人而不是原作者維護的,所以注釋是很重要的。斷言可以被認為是對注釋的擴展,因為它相當于告訴人們閱讀一段始終都是true 的代碼的注釋。使用斷言,而不是通過注釋指定一個語句始終為true,你可以使用斷言來聲明它始終是true 的。然后,如果你運行含有斷言的代碼,不必像使用注釋那樣依賴于仔細代碼,但運行代碼的時候?qū)z查你的斷言是否為true,如果它們不是斷言錯誤就會被拋棄。正如名字暗示的那樣,斷言被用來斷定某些東西應(yīng)該始終都是true 的。當程序正常運行時,斷言就失效了,不會帶來性能開銷。當程序員在查找問題時,斷言可以被激活,如果任何斷言語句不為true,斷言異常就會被拋出。斷言是JDK1.4 中的關(guān)鍵部分,而且不需要在源文件中加入額外的import 語句。但是,因為過去程序員已經(jīng)使用了單詞assert 來創(chuàng)建他們自己的斷言,編譯過程需要一個命令行的參數(shù)來告訴編譯器使用JDK1.4 中的斷言。這需要如下的形式
javac –source1.4 Myprog.java
如果你以如下形式正常運行程序
java Myprog
斷言失效了,不會拋出斷言異常。如果你后來需要查明一個問題,并確定所有的斷言條目都是true,你可以像下面那樣激活斷言來運行程序。
java –enableassertions Myprog
你應(yīng)該斷言什么為true?斷言可以被用在任何你認為應(yīng)該始終為true 的地方。例如,一個人的年紀大于0 應(yīng)該始終是true 的。如果一個人的年紀小于0,你的程序或輸出就會有很大的問題。另一個例子,如果你正在登記人們死亡的日期,你的程序(或你的道德)可能會有死亡日期是否可以在未來的問題,所以,你可以對未來的死亡日期進行斷言。例如,如果你正面臨case 語句或一組if/else 語句,你可能相信代碼始終會在到達最后的測試之前退出。想象一下,如果你有一個處理媒體類型的程序。你的程序希望能夠處理jpg,mpg,avi 或gif 文件。你設(shè)置了一個根據(jù)文件類型分支的case 語句。因為你相信文件類型將始終是這些類型之一,如果你到達了case 語句的末尾而沒有分支了,這將是一個很明顯的問題,你可以在缺省選項處放置一個斷言語句。
你應(yīng)該在哪里使用斷言?斷言不可以用來強制程序的公共接口。一個最常見的程序公共接口是它的命令行參數(shù)。因此,傳統(tǒng)上程序員會通過查看從命令行傳入的String 數(shù)組args 中的值來檢查傳遞給Java程序的命令行。典型地,如果數(shù)組沒有包含程序期望類型的值,程序?qū)⑼顺霾⒋蛴〕鲋该髡_命令行格式的消息。斷言機制的引入不會改變這些。使用斷言來檢查程序的命令行參數(shù)是
不合適的,因為斷言永遠都不會被激活。使用斷言來檢查傳遞給公共方法的參數(shù)是不合適的。因為你的公共方法可能會被別人寫的程序使用,你無法確定他們是否激活了斷言,因此正常運行程序會出錯。但是,使用斷言來檢查傳遞給私有方法的參數(shù)是恰當?shù)?#xff0c;因為這些方法通常是由能夠訪問源代碼的人來調(diào)用的。同樣的假定可以作用于受保護或同一個包中的受保護方法。如你所見,這些僅僅是指導方針,但是考試可能會詢問基于這些指導方針的問題。
斷言語法
斷言語句由兩種格式
簡單的
assert somebooleantest
和
assert somebooleantest : someinformativemethod
在第一個簡單版本中,斷言測試某物是true 的,如果它不是斷言錯誤則拋棄。例如,
如果你正在測試一個人的年齡是否大于0,你可能創(chuàng)建如下形式的斷言
assert (iAge>);
復雜的版本可能是如下形式
assert (iAge) : “age must be greater than zero”;
這個例子很簡單,因為右手邊的表達式只是一個簡單的字符串,但是這可以是任何有返回值
的函數(shù)調(diào)用,例如一個除返回值為void 以外的方法。
課后測試題
問題1)下面哪些論斷是正確?
1) Using assetions requires importing the java.util.assert package
2) Assertions should be used to check the parameters of public methods
3) Assertions can be used as a substitute for the switch/case construct
4) An assertion that a persons date of death > date of birth is appropriate
問題 2)如果如下代碼沒有顯式激活斷言而成功的編譯并運行,會發(fā)生什么呢?
class Language {public static final int java = 1;public static final int pascal = 2;public static final int csharp = 3;
}
public class Mgos {static int lang = 0;public static void main (String argv []) {switch (lang) {case Language.java:System.out.println (“java”);break;case Language.pascal:System.out.println (“pascal”);break;case Language.csharp:System.out.println (“csharp”);break;default:assert false: lang;}}
}
1) An unmatched parameter exception will be thrown
2) An assert exception will be thrown
3) The program will run with not output
4) Output of “csharp”
問題 3)下面哪些是值得使用斷言結(jié)構(gòu)的候選?
1) An input form has a field for a person’s age. If the person entering the date of birth and the date of death enters an age of death that is before the age of birth the assertion mechanism is used to cause a dialog warning box to be shown and the data will not enter the system.
2) A Text Editing program has a file save mechanism. The assert mechanism is used to check if the drive that is being save to really exists. If the drive does not exist an assertion will be thrown generating a warning to the program operator.
3) A program is being created for food preparation that involves cooking meat. Code is included so that if the value of a temperature variable reading appears to be negative an assert exception is thrown.
4) A school attendance system is being created. In the system is code that will throw an assert exception if a child’s age is calculated to be less than zero.
問題 4)如果激活JDK1.4 的斷言,編譯如下代碼會發(fā)生什么?
public class Bbridge {int iRunningTotal = 0;public static void main (String argv []) {Bbridge bb = new Bbrige ();bb.go (argv [0]);}public void go (String s) {int i = Integer.parseInt (s);setRunningTotal (i);assert (iRunningTotal > 0) : getRunningTotal ();}public String getRunningTotal () {return “Value of iRunningTotal “ + iRunningTotal;}public int setRunningTotal (int i) {iRunningTotal += i;return iRunningTotal;}
}
1) Compile time error, getRunningTotal does not return a Boolean
2) Compile time error malformed assert statement
3) Compilation and no output given a command parameter of 1
4) Compilation and assert error given a command parameter of 0
問題 5)下面的論斷哪些是正確的?
1) The assert system introduces no backward compatibility issues
2) The assert system should be used to enforce command line usage
3) Asserts should be used to check for conditions that should never happen
4) Asserts can be used to enforce argument constraints on private methods.
答案
答案1)
4)An assertion that a persons date of death > date of birth is appropriate
使用斷言不需要引入任何包,但是它確實要求JDK1.4 或更高版本,并且需要對JDK 工具使用命令行參數(shù)。斷言不能用來檢查方法參數(shù)的值,因為在正常(非測試)模式中斷言檢查會被禁用。一個人死亡的日期比出生日期大的概念始終是正確的,所以使用斷言結(jié)構(gòu)是恰當?shù)摹?答案 2)
3)The program will run with no output
運行程序時沒有從命令行顯式激活斷言將不會產(chǎn)生斷言錯誤。
答案 3)
3)A program is being created for food preparation that involves cooking meat. Code is included so that if the value of a temperature variable reading appears to be negative an assert exception is thrown.
4) A school attendance system is being created. In the system is code that will throw an assert exception if a child’s age is calculated to be less than zero.
選項1 和2,以及2 和4 之間的重要不同點在于,選項1 和2 中斷言機制在程序正常運行過程中是必需的。斷言對于正常運行程序或標準運行時檢查不是必要的。當然,對于選項3和4 你可能希望包含運行時檢查而不是斷言,但是因為描述沒有指明程序正常運行的產(chǎn)出依賴于這個測試,所以使用斷言是恰當。
答案 4)
3)Compilation and no output given a command parameter of 1
4) Compilation and assert error given a command parameter of 0
答案 5)
3)Asserts should be used to check for conditions that should never happen
4) Asserts can be used to enforce argument constraints on private methods.
最少的對于程序設(shè)計語言運行方式的知識就能指出新特性都會引起向后兼容問題。如果程序員在JDK1.4 之前使用了單詞assert 作為變量,你將需要在使用JDK1.4 時傳遞一個命令行參數(shù)來指出這一點。使用斷言來檢查命令行參數(shù)是不合適的,因為斷言檢查永遠都不會被打開。
第3 章 垃圾收集
為什么想收集垃圾你可能是一位經(jīng)驗非常豐富的Java 程序員,但是你未必想過弄清楚垃圾收集的來龍去脈。的確垃圾收集在Java 程序中有點奇怪。本章中垃圾收集是指釋放前面分配的內(nèi)存,這些內(nèi)存不會再被程序繼續(xù)使用。當內(nèi)存已經(jīng)變得沒用的時候,我們把它們叫做垃圾,它們的存在還會使得其他可用內(nèi)存空間變得混亂。Java 語言設(shè)計的非常出色,其中之一就是你不用擔心垃圾收集。C/C++程序員必須要手動分配和釋放內(nèi)存,這會導致一個問題出現(xiàn)就是“內(nèi)存泄露”。有些版本的Windows 程序,比如Word 和Excel,可能幾次簡單的打開和關(guān)閉應(yīng)用程序就會引起某些問題出現(xiàn)。有時候內(nèi)存泄露可能最終導致系統(tǒng)死機,你不得不重新啟動電腦。在成千上萬的C/C++代碼中,程序員很可能分配一塊內(nèi)存卻忘記釋放它。
Java 和垃圾與C/C++不同,Java 語言會自動釋放不再使用的引用。你不用從成千上萬代碼中苦苦查找不會再使用的內(nèi)存。你也不需要知道如何分配合適大小的空間給不同的數(shù)據(jù)類型,以確保程序的兼容性。因此,看起來你沒有必要知道垃圾收集的細節(jié)知識。有一種情況例外,就是你想通過考試或者想了解垃圾收集的真實情況。如果你編寫程序過程中需要創(chuàng)建大量的對象和變量,這時候如果知道引用什么時候會被釋放是非常重要的。你需要知道自動垃圾收集的工作原理,你可以建議或者鼓勵虛擬機進行垃圾收集,但是記住你不能強迫它作這個工作。
finalizeJava 語言保證一個對象的finalize 方法在對象被回收之前會調(diào)用。與其他類似垃圾回收的行為不同的是,這里是“保證”。但是finalize 方法到底做什么呢?乍一看,finalization 像是C/C++語言中的析構(gòu)器,在對象銷毀之前清理其資源。不同的是Java 語言不需要釋放資源,因為垃圾回收器會處理內(nèi)存分配。但是如果你引用了其他外部資源,比如文件信息,那么就有必要在finalization 中釋放資源了,這也是在JDK 1.4 里面提出的參考。當垃圾收集器判斷出已經(jīng)沒有引用指向這個對象的時候,垃圾收集器就會調(diào)用對象的finalize 方法。因為垃圾收集器回收垃圾的行為是不確定的,你不知道什么時候他們會執(zhí)行來收集垃圾。因此你也就沒有辦法知道什么時候finalize 方法會被調(diào)用。但是,你一定想知道考試對垃圾回收這部分的要求,我們往下看。垃圾收集的確是一個考點陷阱,因為你沒有明顯的方法來決定什么時候垃圾收集可用。因此你不能編寫下面的代碼:
if(EligibleForGC(Object){ //Not real code
System.out.print("Ready for Garbage");
}
正因為如此,你必須掌握下面的原則。一旦一個對象不被其他任何對象引用的時候,它就變成可回收的對象了。你可以使用System.gc()來建議垃圾回收器收集垃圾,但是這并不能保證執(zhí)行。在方法中聲明的本地變量在方法退出的時候就無效了,這個時候方法中的本地變量就成為了可回收的,方法每次執(zhí)行的時候本地變量都會被重新創(chuàng)建。
無法訪問當代碼已經(jīng)無法再訪問對象的時候,這個對象就成為了可垃圾回收的。有兩種情況下會出現(xiàn)對象無法再被訪問,第一,對象的引用設(shè)置為null;第二,指向這個對象的引用指向了其他的對象。有這樣一種考試題目,在代碼的某個部分把引用設(shè)置為null,你必須找出在哪里對象成為了可垃圾回收的。這種類型的題目比較簡單。但是另外一種情況就不是這么明顯了,我們看看下面的代碼例子。
class Base{String s;Base(String s){this.s = s;}public void setString(String s){this.s = s;}
}
public class UnReach{public static void main(String argv[]){UnReach ur = new UnReach();ur.go();}public void go(){Base b1 = new Base("One");b1.setString("");Base b2 = new Base("Two");b1 = b2;}
}
什么時候b1 成為可垃圾回收的呢?假設(shè)你不被b1 設(shè)置為空字符串所影響,那么你就可以判斷出當b1 指向b2 的時候,原來的b1 成為可垃圾回收的了。
課后測試題
問題1)下面哪段代碼可以建議虛擬機執(zhí)行垃圾收集?
1) System.free();
2) System.setGarbageCollection();
3) System.out.gc();
4) System.gc();
問題2) 在下面的代碼片斷中插入一行代碼確保Integer 對象被垃圾收集器回收。
public class Rub{Integer i= new Integer(1);Integer j=new Integer(2);Integer k=new Integer(3);public static void main(String argv[]){Rub r = new Rub();r.amethod();}public void amethod(){System.out.println(i);System.out.println(j);System.out.println(k);}
}
1) System.gc();
2) System.free();
3) Set the value of each int to null
4) None of the above
問題3)下面那句話是正確的?
1)You cannot be certain at what point Garbage collection will occur
2) Once an object is unreachable it will be garbage collected
3) Both references and primitives are subject to garbage collection.
3) Garbage collection ensures programs will never run out of memory
問題4)在哪里第8 行創(chuàng)建的sb 對象成為可垃圾回收的?
public class RJMould{StringBuffer sb;public static void main(String argv[]){RJMould rjm = new RJMould();rjm.kansas();}public void kansas(){sb = new StringBuffer("Manchester");StringBuffer sb2 = sb;StringBuffer sb3 = new StringBuffer("Chester");sb=sb3;sb3=null;sb2=null;}
}
1) Line 11
2) Line 9
3) Line 12
4) Line 13
答案
答案1)
4) System.gc();
答案2)
4) None of the above 你只能建議垃圾回收器運行,但是無法決定他會在代碼的哪個部分執(zhí)行。注意只有對象的實例才可能成為垃圾回收對象,原始數(shù)據(jù)類型不會。
答案3)
1) You cannot be certain at what point Garbage collection will occur
一旦一個對象不能在被訪問,那么他將成為可垃圾回收的。但是你不能確定它什么時候會被回收。垃圾回收機制只對對象有效,對原始類型無效。你應(yīng)該知道垃圾收集不能確保程序不會出現(xiàn)內(nèi)存不足的情況。但是他能保證不再被使用的內(nèi)存可以成為可用的。
答案4)
4) Line 13
第9 行創(chuàng)建的sb2 指向了第8 行創(chuàng)建的對象,直到它成為不可到達的對象的時候,sb 才成為可垃圾回收的。
答案5)
1) finalize will always run before an object is garbage collected
對象在垃圾回收之前,它的finalize 方法會被調(diào)用。Finalize 方法不能在對象被回收后調(diào)用,因為那時候?qū)ο笠呀?jīng)不存在了。當一個對象不能訪問的時候,他就成為了可垃圾回收的,但是你無法保證它什么時候會被回收。選項4 在java 中是不正確的,在C++中正確。
第4 章 語言基礎(chǔ)
目標一 包,引入,內(nèi)部類,接口正確識別結(jié)構(gòu)化的包聲明,引入子句,類聲明(包含內(nèi)部類在內(nèi)的所有形式),接口聲明,方法聲明(包括類運行入口的main 方法),變量聲明和標識符。
目標的注解這是一個奇怪的使用短語表達的目標。它似乎在要求你理解何時,如何以及為何使用引入子句和包子句,以及應(yīng)該將接口子句和變量子句放在什么地方。
包名稱package意味著類的集合,有點類似于類庫。使用包也有點像使用目錄。如果你在一個文件中放置一個包子句,此文件只對同一個包中的其他類可見。包有助于解決命名沖突問題。因為你只能使用這么多有意義的名字作為類名,最終,你可能需要使用或創(chuàng)建相同名稱的類。通過在類之前附加一個完整的包名,你可以多次使用相同的名字。包名的使用慣例是用組織的internet域名來創(chuàng)建類。因此,當創(chuàng)建一個叫做Question的類來表示一個虛擬的測試題時,我使用我的網(wǎng)站域名www.jchq.net來創(chuàng)建目錄結(jié)構(gòu)。WWW 部分無法唯一標識網(wǎng)站的任何信息,所以使用的域名將是net.jchq。為了在我唯一的包中創(chuàng)建類,我創(chuàng)建了目錄net,并在此之下創(chuàng)建一個叫做jchq 的目錄。接著,在那個目錄中我可以創(chuàng)建叫做Question 的類,類的開頭為如下包定義:
package net.jchq.*;
這將賦予你訪問此包/目錄中任何類的權(quán)利。可選地,你可以僅僅指定一個需要獲取訪問權(quán)限的類,使用如下行:
package net.jchq.Question;
引入import 子句必須出現(xiàn)在任何package 子句之后和任何代碼之前。引入子句不能出現(xiàn)在類中,類聲明之后或其他任何地方。import 子句允許你直接使用類名,而不必使用完整的包名來限定它。一個例子就是類名java.awt.Button 通常被簡寫為Button,只要你已經(jīng)將如下子句放在文件的起始位置:
import java.awt.*;
如果我隨后想要創(chuàng)建我的Question 類的實例,我只需要引入包或指定此類的完整包名。為了引入其他包中的類,我將需要如下行:
import net.jchq.*;
為了指定類的完整包名,我需要使用如下風格的語法。
jchq.net.Question question = new net.jchq.Question();
你可以想象,經(jīng)常性地輸入完全限定的包名顯得不夠靈活,所以引入類通常是首選的方案。請注意,使用引入子句對性能沒有影響。這類似于在DOS(或Unix)環(huán)境中設(shè)定一個路徑聲明。這只是簡單的為類設(shè)定有效性或路徑,并不是直接將代碼引入程序中。僅僅在程序中實際地使用類才會影響性能。你可以在包自己之前放置一段注釋,但不能是其他任何內(nèi)容。你可能會遇到將引入子句放于包子句前面的考試題。
//你可以在包子句之前放置一段注釋
package MyPack;
public class MyPack {}
下面的代碼會導致錯誤
import java.awt.*;
//錯誤:將引入子句放于包子句前面
//語句會導致編譯時錯誤
package MyPack;
public class MyPack {}
package 子句可能會包含點號來指定包層次。因此如下代碼將不會導致編譯錯誤
package myprogs.MyPack;
public class MyPack {}記住,如果你沒有在源文件中放置包子句,這將被認為有一個相當于當前目錄的缺省包。這與在“1.2 節(jié) 定義和訪問控制”中提到的可見性有關(guān)。
類和內(nèi)部類聲明一個文件只能包含一個外部public 類。如果你試圖創(chuàng)建一個包含多個public 類的文件,編譯器將會報告特定的錯誤。一個文件可以包含多個非公共類,但是記住這將為每個類生成單獨的.class 輸出文件。公共類在文件中的放置位置是沒有關(guān)系的,只要在文件中僅有一個公共類。內(nèi)部類是在JDK1.1 中提出的。這個想法是為了允許一個類在另一個類中定義,在一個方法中定義,以及創(chuàng)建匿名內(nèi)部類。這會帶來一些有趣的影響,特別是對于可見性。
這是一個簡單的內(nèi)部類的例子:
class Outer {class inner{}
}
這會導致生成如下名稱的類文件
Outer.class
Outer$Inner.class
內(nèi)部類的定義僅僅在現(xiàn)有的Outer 類的上下文中可見。因此,如下代碼會導致編譯時錯誤
class Outer {class Inner{}
}
class Another {public void amethod () {Inner I = new Inner();}
}涉及到類Another 的時候,類Inner 是不存在的。它只能存在于Outer 類實例的上下文中。因此如下代碼運行良好,因為在創(chuàng)建Inner 實例的時候,有一個指向外部類的this 實例。
class Outer {public void mymethod () {Inner I = new Inner();}public class Inner {}
}但是,如果Outer 類的this 實例不存在時會發(fā)生什么呢。為了弄清楚為此提供的相當古怪的語法的含義,試著將如上例子中的new 關(guān)鍵字看作屬于this 實例當前的上下文。這樣,你可以改變創(chuàng)建實例的代碼行,如下
Inner i = this.new Inner ();這樣,如果你需要從一個static 方法或其他沒有this 對象的地方創(chuàng)建Inner 的實例,你可以把new 當作屬于外部類的一個方法來使用
class Outer {public class Inner {}
}
class another {public void amethod () {Outer.Inner i = new Outer ().new Inner ();}
}盡管有了我口齒伶俐的解釋,我發(fā)現(xiàn)這個語法不夠直觀,并在學完5 分鐘后就忘記了。你很有可能會在考試中遇到這個問題,所以請給予額外的注意。內(nèi)部類的一個好處是內(nèi)部類一般可以訪問它的嵌套類(或外部類)的域。不像外部類,內(nèi)部類可以是private 或static。主考者似乎有可能問一些歸結(jié)為“一個內(nèi)部類可以是static或private”的問題。靜態(tài)內(nèi)部類的方法當然可以訪問其嵌套類的任何靜態(tài)域,因為那些域?qū)⒅粫幸粋€實例。
聲明在方法中的內(nèi)部類內(nèi)部類可以在方法中創(chuàng)建。這是像Borland JBuilder 那樣的GUI 生成工具在創(chuàng)建事件處理器時花很多功夫做的事情。
這是一個這種自動生成的代碼的例子
buttonControl1.addMouseListener (new java.awt.event.MouseAdapter () {ublic void mouseClicked (MouseEvent e) {ttonControl1_mouseClicked (e);});
請注意第一個圓括號之后的new 關(guān)鍵字。它指出在方法addMouseListener 中一個匿名內(nèi)部類正在被定義。通常地,這個類可以使用一個名字定義,這可能會使它更容易被人們讀懂,但是由于在其他地方不需要對其進行處理,取名字不會有太多幫助。如果你手動創(chuàng)建這些代碼,很容易會被數(shù)字以及花括號和圓括號的層次弄糊涂。請注意完整的結(jié)構(gòu)為何是以分號結(jié)束的,因為這實際上是一個方法調(diào)用的結(jié)束。如你猜的那樣,一個匿名內(nèi)部類不能由程序員給定構(gòu)造函數(shù)。考慮一下,構(gòu)造函數(shù)是一個沒有返回值,并且名字與類名相同的方法。咄!我們在談?wù)摏]有名字的類。一個匿名類可以繼承其他類或?qū)崿F(xiàn)單一接口。這個特別的限制似乎不會在考試中考到。
在方法中定義的類的域可見性義在方法中的類只能訪問嵌套方法中的域,如果他們是被定義為final 的。這是因為定義在方法中的變量通常被認為是自治的(automatic),例如他們僅當方法執(zhí)行時才存在。在創(chuàng)建在方法中的類中定義的域可能比嵌套方法的生命周期長。因為final 變量不能被修改,JVM 可以確保它的值保持恒定,甚至在外部方法運行終止之后。你很可能在考試中遇到這方面的問題,包括考察作為參數(shù)傳遞給方法的變量狀態(tài)的問題(是的,他們也必須是final 的)。
創(chuàng)建接口口是Java 用來解決缺少多繼承的方式。有趣的是,Visual Basic 使用關(guān)鍵字interface并以與Java 相似的方式來使用此概念。有時候接口方法被認為面向契約編程。通過關(guān)鍵字“implements”來使用接口。因此,類可以被聲明為
class Malvern implements Hill, Well {ublic
}
主方法為java 中的所有代碼必須存在于類中,必須有一個特別的或“有魔力的”的方法來引導程序開始運行。這個方法具有如下署名
public static void main (String argv[])
聲明中的每一項來分析,關(guān)鍵字public 意味著方法到處可見。static 部分意味著方法屬于類本身,而不是屬于任何特定的實例。這意味著不需要創(chuàng)建類的實例就可以調(diào)用它。單詞void 意味著方法沒有返回值。注意單詞main 都是小寫的。在圓括號中的部分指出方法接受一個String 數(shù)組的參數(shù)。當然,單詞String 必須以大寫S 開頭。參數(shù)arg 的名字沒有關(guān)系,你可以叫它bicycle 或trousers 或任何正確的變量名稱,它都可以正確運行。但是參數(shù)命名為arg 是一個值得堅持的慣例。因為數(shù)組的方括號可以跟在名字或類型之后,將參數(shù)聲明為String [] arg 也是可以接受的。注意,為了Sun Certified Java Programmers,這是正確的署名。你可能發(fā)現(xiàn)其他類似的署名在現(xiàn)實中也可以運行,但是為了考試(以及未來兼容性的目的)你應(yīng)該使用這種署名。因為這個方法是靜態(tài)的,所以它被調(diào)用(或被Java 環(huán)境有效跟蹤)的時候不需要創(chuàng)建類的實例。同樣,因為它是靜態(tài)的,你不可以操作非靜態(tài)的方法或數(shù)據(jù)。因為這樣,main 方法經(jīng)常包含極少的代碼,典型地,它包含代碼來創(chuàng)建嵌套類的實例,然后調(diào)用真正使程序完成工作的非靜態(tài)方法。系統(tǒng)傳遞給main 方法的String 數(shù)組包含任何在程序開始時從命令行傳入的參數(shù)。當然,有了現(xiàn)代圖形用戶界面環(huán)境,更普通的方法是通過點擊圖標來啟動程序,這些都不會給傳遞參數(shù)帶來變化。試的目標4.2 明確地要求你理解命令行參數(shù)是如何傳遞給main 方法的,以及如何訪問它們。這就是說……述傳遞給main 方法的參數(shù)數(shù)組的下標值與命令行參數(shù)的對應(yīng)關(guān)系。
課后測試題
問題1)假設(shè)有如下代碼
public class FinAc {static int l = 4;private int k = 2;public static void main (String argv [] ) {FinAc a = new FinAc();a.amethod();}public void amethod () {final int i = 99;int j = 6;class CInMet {public void mymethod (int q) {// Here}// end of mymethod}// End of CInMetCInMet c = new CInMet ();c.mymethod (i);}// End of amethod
}
如下變量中,哪些在由注釋//Here 標記的行上是可見的?
1) l
2) k
3) i
4) j
問題 2)下面哪個選項可以正確編譯?
1)
// A Comment
import java.awt.*;
class Base {}
2)
import java.awt.*;
package Spot;
class Base ();
3)
// Another comment
package myprogs.MyPack;
public class MyPack {}
4)
class Base {}
import java.awt.*;
public class Tiny {}
問題 3)如下論述哪些是正確的?
1) An inner class may be defined as static
2) An inner class may NOT be define as private
3) An anonymous class may have only one constructor
4) An inner class may extend another class
問題 4)從不存在當前this 引用的代碼中如何創(chuàng)建內(nèi)部類的實例?
1) Outer.Inner i = new Outer ().new Inner ();
2) Without a this reference an inner class cannot be created
3) Outer.Inner i = Outer ().new new Inner ();
4) Outer i = Outer.new ().Inner ();
問題 5)如下哪些是開始執(zhí)行Java 程序的main 方法的正確形式?
1) public static void main (String[] bicycle);
2) public void main (String argv[]);
3) public static int main (String args[])
4) public static void main (String args[]);
問題 6)試圖編譯如下代碼時會發(fā)生什么?
abstract class Base {abstract public void getValue (Base b);
}
public class Robinwood extends Base {public static void main (String argv[]) {Robinwood rw = new Robinwood();rw.main();}public void main () {getValue (this);}public void getValue (Base b) {}
}
1) Compile error, only methods can be marked as abstract
2) Compile error, the name “main” is reserved for the startup method
3) Compile error, the parameter to the getValue call is of the wrong type
4) Compilation without error
問題 7)
// located in the East end
package spital;
abstract class Spital {public Spital (int i) {}
}
public class Mudchute extends Spital {public static void main (String argv[]) {Mudchute ms = new Mudchute ();ms.go ();}public Mudchute () {super (10);}public void go () {island();}public void island () {System.out.println (“island”);}
}
1) Compile time error, any package declaration must appear before anything else
2) Output of 10 followed by island
3) Output of 10 followed by “spital island”
4) Compile time error
問題 8)對于定義在方法中的類,什么規(guī)則管理對于嵌套類中變量的訪問?
1) The class can access any variable
2) The class can only access static variables
3) The class can only access transient variables
4) The class can only access final variables
答案
答案1)
1) l
2) k
3) i
定義在方法中的類只能看到來自嵌套方法中的final 域。但是它可以看到嵌套類中包括私有域在內(nèi)的域。域j 沒有被定義為final。
答案 2)
1)
//A Comment
import java.awt.*;
class Base {};
3)
//Another comment
package myprogs.MyPack;
public class MyPack {}
任何包子句必須是文件中的第一個條目(除去注釋)。引入子句必須在任何包子句之后和代碼之前。
答案 3)
1) An inner class may be defined as static
2) An inner class may extend another class
一個匿名類怎么能有構(gòu)造函數(shù)呢?內(nèi)部類可以被定義為私有的。
答案 4)
1) Outer.Inner i = new Outer ().new Inner ();
答案 5)
1) public static void main (String[] bicycle);
2) public static void main (String args[]);
選項2 可以編譯,但是不能成為程序的啟動方法,因為它沒有聲明為static。選項3 不能編譯,因為它被聲明為返回int 值。
答案 6)
4)Compilation without error
為一個非啟動方法取名為“main”在語法上是正確的,但這是很糟糕的風格。因為類Robinwood 繼承自類Base,所以可以將其作為參數(shù)傳遞給一個期望得到Base 類型參數(shù)的方法。
答案 7)
2)Output of 10 followed by island
包聲明必須出現(xiàn)在除注釋外的任何內(nèi)容之前,注釋可以出現(xiàn)在任何地方。
答案 8)
4)The class can only access final variables
注意這一限制適用于嵌套方法中的變量,而不是嵌套類中的變量。
目標二 使用接口識別正確實現(xiàn)了接口的類,這些接口既可以是java.lang.Runnable,也可以是試題中完整指定的接口。
接口——面向契約編程接口是總所周知的“面向契約編程”的一部分。這意味著一個程序員創(chuàng)造了一些東西來迫使其他程序員遵循一組條件。接口同樣也被認為是Java 用來無缺點獲取一些多繼承好處的方式。C++語言具有多繼承,這意味這一個類可以有多個父類。多繼承與單繼承的優(yōu)缺點是編程理論家之間廣泛爭論的話題。
Runnable 接口Runnable 接口是線程機制的一部分,線程機制將在其他的考試目標中進行明確地陳述。Runnable 接口指定實現(xiàn)了它的類必須定義一個具有如下署名的方法
public void run ()
使用接口的關(guān)鍵字是implements,因此,如果你打算創(chuàng)建一個實現(xiàn)Runnable 接口的類,代碼類似于
public class MyClass implements Runnable {public void run () {}
}當然,為了做一些有用的事,你需要在run 方法體中添加一些代碼,但是僅僅創(chuàng)建一個具有合適署名的run 方法已經(jīng)足夠完成Runnable 接口要求的契約了。除非你具有一個完全正確署名的方法,否則你會得到一個編譯時錯誤。
目標三 從命令行傳遞值陳述傳遞給main 方法的參數(shù)數(shù)組的下標值與命令行參數(shù)的對應(yīng)關(guān)系。注意:這似乎是一個微小的主題,幾乎不值得使之成為一個目標。這個主題可以找出更具經(jīng)驗的C/C++程序員,因為argv []的第一個元素是命令行中程序名稱后面的第一個字符串。因此,如果程序運行如下。
java myprog myparm
元素argv [0]將包含“myparm”。如果你具有C/C++背景,你可能認為它包含“java”。Java 不包含與Visual Basic 中的Option Base 等價的元素(譯者注:VB 中可以使用Option Base來限定下標的缺省下界),并且所有的數(shù)組都是從元素0 開始。
以如下程序為例
public class MyParm {public static vooid main (String argv []) {String s1 = argv [1];System.out.println (s1);}
}
為了強調(diào)argv 是一個String 數(shù)組,我將參數(shù)1 傳給一個String。如果你使用如下命令運行程序
java MyParm hello there
輸出結(jié)果將是there,而不是MyParm 或hello。
課后測試題
問題 1)假設(shè)類Cycle 中有如下主方法,并且有命令行
java Cycle one two
輸出是什么?
public static void main (String bicycle []) {System.out.println (bicycle [0]);
}
1) None of these options
2) Cycle
3) one
4) two
問題 2)如何從命令行中獲取傳遞給主方法的值?
1) Use the System.getParms () method
2) Assign an element of the argument to a string
3) Assign an element of the argument to a char array
4) None of these options
答案
答案 1)
3) one
答案2)
2) Assign an element of the argument to a string
目標四 識別關(guān)鍵字識別所有Java 程序設(shè)計語言的關(guān)鍵字。注意:不會出現(xiàn)關(guān)于關(guān)鍵字與描述常量之間的
深層次區(qū)別的問題。目標的注解:你可能希望在學習較少使用的關(guān)鍵字的基礎(chǔ)上解決這個目標,確保你沒有延續(xù)來自其他你所知道的語言中的“壞朋友”,特別是C/C++。考試特別強調(diào)識別關(guān)鍵字。此目標的第二部分提到的深層次區(qū)別是在JDK1.4 版本的考試中加入的目標。似乎越來越多的人擔心true/false 和null 是不是關(guān)鍵字。我想你可以從評論中斷定你不會被詢問關(guān)于true/false 和null 的問題。
Java 關(guān)鍵字
abstract boolean break byte case catch char class const * continue default do double else extends final finally float
for goto * if implements import instanceof int interface long native new package private protected public return short static strictfp super switch synchronized this throw throws transient try void volatile while
通過使用語言,你將會逐漸認識大部分的Java 關(guān)鍵字,但是,在考試中可能會出現(xiàn)極少使用的例外和保留詞。一些極少使用的詞(當然是對于初學者而言)的例子
volatile
transient
native
strictfp
帶星號的詞是保留的,現(xiàn)在并沒有被使用。注意所有的關(guān)鍵字都是小寫的,所以for 是關(guān)鍵字而FOR不是。
課后測試題
問題 1)下面哪些是Java 關(guān)鍵字?
1) double
2) Switch
3) then
4) instanceof
問題 2)下面哪些不是Java 關(guān)鍵字?
1) volatile
2) sizeOf
3) goto
4) try
答案
答案 1)
2) double
4)instanceof
注意,switch 中的大寫字母S 意味著它不是關(guān)鍵字,單詞then 是Visual Basic 中的一部分而不是Java。
答案 2)
2) sizeOf
這是C/C++中用來決定一個原始數(shù)據(jù)在某個特定平臺上的大小的關(guān)鍵字。因為Java 中原始數(shù)據(jù)在所有平臺上都具有相同大小,所以這個關(guān)鍵字沒有被使用。
目標五 未賦值的變量陳述當沒有顯式賦值時,使用任何類型的變量或數(shù)組元素的影響。
變量你可以學習使用Java 編程而不必真正理解這個目標背后的議程,但是它確實代表著有價值的實際知識。本質(zhì)上,類級別的變量總是會被賦予一個缺省值,而一個成員變量(包含在方法中)將不會被賦予任何缺省值。如果你試圖訪問一個未賦值的變量會發(fā)生錯誤。例如
class MyClass {public static void main (String argv[]) {int p;int j = 10;j = p;}
}
這段代碼將會導致如下錯誤:
“error variable p might not have been assigned”
從C/C++給你足夠的自由給p 留下一個任意值的傾向來看,這被認為是一個很受歡迎的改變。如果p 定義在類級別,它就會被賦予其缺省值,而不會有錯誤產(chǎn)生。
class MyClass {static int p;public static void main (String argv []) {int j = 10;j = p; System.out.println (j);}
}
整型數(shù)的缺省值是0,所以這將會打印出0。
數(shù)字類型的缺省值是0,布爾型為false,對象引用唯一的缺省值類型是null。
數(shù)組學習這部分目標需要理解一個簡單的規(guī)則。任何基本類型的數(shù)組元素的值將總是被初始化為缺省值,無論數(shù)組是否被定義。無論數(shù)組定義為類級別還是方法級別,元素值都會被設(shè)定為缺省值。你可能會遇到詢問一個數(shù)組的某個特定元素包含什么值的問題。除非是對象數(shù)組,否則答案都不會是null(或者如果它們被特別的設(shè)定為NULL)。
課后測試題
問題 1)假設(shè)有如下代碼,元素b [5]包含什么?
public class MyVal {public static void main (String argv []) {MyVal m = new MyVal ();m.amethod ();}public void amethod () {boolean b [] = new Boolean [5];}
}
1)1
2)null
3)""
4)none of these options
問題 2) 假設(shè)有如下構(gòu)造函數(shù),mycon 的元素1 包含什么?
MyCon () {int [] mycon = new int [5];
}
1)0
2)null
3)""
4)None of these options
問題 3)試圖編譯和運行如下代碼時會發(fā)生什么?
public class MyField {int i = 99;public static void main (String argv []) {MyField m = new MyField ();m.amethod ();}void amethod () {int i;System.out.println (i);}
}
1) The value 99 will be output
2) The value 0 will be output
3) Compile time error
4) Run time error
問題 4)試圖編譯和運行如下代碼時會發(fā)生什么?
public class MyField {String s;public static void main (String argv []) {MyField m = new MyFeild ();m.amethod ();}void amethod () {System.out.println (s);}
}
1) Compile time error s has not been initialized
2) Runtime error s has not been initialized
3) Blank output
4) Output of null
答案
答案 1)
4)none of these options
數(shù)組元素從0 開始編號,因此數(shù)組沒有元素5。如果你試圖運行
System.out.println (b [5])
你會得到一個異常。
答案 2)
1)0
這種情況下,構(gòu)造函數(shù)產(chǎn)生的效果與其他方法沒有什么不同。無論在哪里創(chuàng)建,一個整型數(shù)組的所有元素都將被初始化為0。
答案 3)
3) Compile time error
你會得到一個編譯時錯誤,指出變量i 沒有被初始化。類級別的變量i 會轉(zhuǎn)移你的注意力,因為它會被方法級別版本所覆蓋。方法級別的變量不會被初始化為缺省值。
答案 4)
4) Output of null
創(chuàng)建在類級別的變量總是會被賦予一個缺省值。對象引用的缺省值是null,并且使用System.out.println 隱式調(diào)用toString 方法時會打印出null。
目標六 數(shù)據(jù)類型的范圍和格式陳述所有原始數(shù)據(jù)類型的范圍,聲明String 文字以及使用所有允許的格式,基數(shù)和表示法來聲明原始數(shù)據(jù)類型。
目標的注解這是一個有點令人煩惱但又很容易遇到的目標。你可以書寫大量Java 代碼而不需要了解原始數(shù)據(jù)類型的范圍,但是要記住這些細節(jié)也不會花很多時間。對能使用所有格式的要求要小心,不要忽略了八進制格式。
整數(shù)原始數(shù)據(jù)類型的大小當這個目標要求原始數(shù)據(jù)類型的范圍時,我假定它只要求以2 的冪次表示,而不是確實表示的數(shù)值。由于byte 的大小很直觀,基于我對PC 的基本經(jīng)驗,是8 比特,在我的記憶中只有三種整型類型需要學習,
整型數(shù)據(jù)類型的范圍
Name Size Range
byte 8 bit -27 to 27-1
short 16 bit -215 to 215-1
int 32 bit -231 to 231-1
long 64 bit -263 to 263-1
聲明整型數(shù)有三種方式聲明整型數(shù)。缺省的,如你所期望的是十進制。這里是一些選項
聲明18 為整型數(shù)
Decimal 18
Octal 022 (Zero not letter O)
Hexadecimal 0x12
如果你編譯并運行這個小類,你每次都會得到18 的輸出。
public class Lit {public static void main (String [] argv) {int i = 18;int j = 022; //Octal version: Two eights plus twoint k = 0x12; //Hex version: One sixteen plus twoSystem.out.println (i);System.out.println (j);System.out.println (k);}
}Roberts 和Heller 描述了6 種聲明整型數(shù)的方法,因為對于Java 來說是很少見,字母X不是大小寫敏感的,16 進制符號中的字母A 到F 也是這樣。我覺得僅僅記住有三種方式以及字母是非大小寫敏感的要更容易一點。
浮點原始數(shù)據(jù)類型的大小浮點數(shù)是有點奇怪的“野獸”,因為在計算的時候會出現(xiàn)意想不到的結(jié)果。引用Peter VanDer Linden 的話“The exact accuracy depends on the number being represented”。為補償變量的精確度,你確實會接觸到巨大得超出想象的數(shù)據(jù)。因此,最大的double 可以存儲達到17 后面跟307 個0 的數(shù)。所以你甚至可以存儲經(jīng)濟報刊上說的Bill Gates 所擁有的價值那么大的數(shù)值(直到Linux 得到了全世界的控制權(quán),那么整數(shù)就能很好的完成任務(wù)了)。
浮點類型的范圍
float 32 bit
double 64 bit記住,一個具有小數(shù)部分的數(shù)據(jù)的缺省類型是double 而不是float。這會有點讓人困惑,因為你可能會認為“浮點數(shù)”的缺省類型是float。你可能會在考試中遇到類似于如下形式的題目。
如下能通過編譯嗎?
float i = 1.0;
直覺會告訴你這是可以編譯成功的。不幸地是考試不是為考察你的直覺而設(shè)計的。這會導致編譯時錯誤,因為它試圖將double 值賦給一個float 類型。你可以這樣修改代碼
float i = 1.0F;
或者甚至是
float i = (float) 1.0;
使用后綴字母指定數(shù)據(jù)類型如上一節(jié)中演示的那樣,你可以通過一個后綴字母告訴Java 一段數(shù)字文字的類型。如下那些是可用的指定數(shù)據(jù)類型的后綴
float F
long L
double D
布爾值和字符值布爾和字符原始數(shù)據(jù)類型有點古怪。如果你有C/C++的背景,請?zhí)貏e注意boolean 并確保沒有從其他語言中帶來任何“壞朋友”。boolean 數(shù)不能被賦予除true 或false 以外的其他的值。true 或false 的值不等價于0,-1 或其他任何數(shù)字。char 是Java 中唯一的未賦值的原始數(shù)據(jù)類型,它是16 位長的。char 類型可以用來表示一個Unicode 字符。Unicode 是ASCII 碼的替代方案,它使用2 個字節(jié)來存儲字符而不是ASCII 碼中的1 個字節(jié)。這提供了65K 個字符,盡管不足以覆蓋所有文本,但已經(jīng)是對255字符的ASCII 碼的很大的改進了。國際化是一個完全屬于其本身的話題,而且僅僅因為你能夠在中文或越南語中表示字符,并不能表示他們可以在標準的英文風格的操作系統(tǒng)上正常顯示。
可以通過將字符包含在單引號中來創(chuàng)建char,如下
char a = ‘z’;
注意使用的是單引號'而不是雙引號"。這在以英文為中心的世界中運行良好,但是由于Java 是一個全球的系統(tǒng),char 可能會包含任何存在于Unicode 系統(tǒng)中的字符。這可以使用在16 進制數(shù)之前放置\u 完成,并將整個表達式放入單引號中。因此,空格符可以被表示為
char c = ‘\u0020’
如果你給一個char 賦予普通的數(shù)字,它將輸出為文字字符。這樣,如下程序?qū)⒋蛴〕鲎帜?A(ASCII 碼為65)和空格。
public class MyChar {public static void main (String argv []) {char i = 65;char c = ‘\u0020’;System.out.println (i);System.out.println (“This” + c + “Is a space”);}
}
聲明字符串文字String 類型不是原始數(shù)據(jù)類型但它是如此重要以至于在某些場合Java 把它當作原始數(shù)據(jù)。特性之一就是可以聲明字符串文字而不需要使用new 來初始化類。String 文字相當易懂。確定你記住了String 文字包含在雙引號中,而char 文字使用單引號。像這樣
String name = “James Bond”
更多關(guān)于String 類的信息請看目標9.3 和5.2。
課后測試題
問題 1)下面哪些可以編譯成功?
1) float f = 10f;
2) float f = 10.1;
3) float f = 10.1f;
4) byte b = 10b;
問題2)下面哪些可以編譯成功?
1) short myshort = 99S;
2) String name = ‘Excellent tutorial Mr Green’;
3) char c = 17c;
4) int z = 015;
問題 3)下面哪些可以編譯成功?
1) boolean b = -1;
2) Boolean b2 = false;
3) int i = 019;
4) char c = 99;
答案
答案 1)
1) float f = 10f;
2) float f = 10.1f;
沒有這樣的byte 表達形式。選項2 將會導致錯誤,因為一個含有小數(shù)部分的數(shù)字的缺省類型是double。
答案 2)
4)int z = 015;
不存在字母c 和s 這種文字指示符,一個String 必須包含在雙引號種,而不是例子中的單引號。
答案 3)
2)boolean b2 = false;
4)char c = 99;
選項1 顯然是錯的,因為boolean 只能被賦予true 或false。選項3 有點狡猾,因為這是正確的聲明八進制數(shù)的方式,但是在八進制中你只能使用數(shù)字0 到7 而不能是9。或許這兒有點小把戲。
第5 章 運算符和賦值
目標1,應(yīng)用運算符確定使用任意運算符的結(jié)果,包括運算符賦值和instanceof 來操作任意類型類的作用域或可達域以及以上的組合。
instanceof 運算符instanceof 運算符是一個很陌生的東西,在我眼里,它更像是一個方法而不是一個運算符。你可能沒用過它而寫了大量的Java 代碼,但為了考試的目的,你需要弄懂它。instanceof運算符在運行時測試一個類的類型然后返回一個布爾值。它一般用來說:這個類是一個
instanceof 的那個類嗎?
如果你像下面這樣的小地方用它,看起來不是很有用
public class InOf {public static void main(String argv[]){InOf i = new InOf();if(i instanceof InOf){System.out.println("It's an instance of InOf");}//End if}//End of main
}
你可能會認為這段代碼會輸出
"It's an instance of InOf"
但是當你訪問了一個涉及到向下多層的對象引用的時候,情況可能會改變。你可能有一個把組件作為參數(shù)的方法,它可能真正指向一個Button,Label 或其他任何東西。這種情況下,instanceof 運算符就可以用來測試對象的類型,執(zhí)行匹配的角色,然后調(diào)用適當?shù)姆椒ā?用下面的代碼舉例說明:
import java.awt.*;
public class InOfComp {public static void main(String argv[]){}//End of mainpublic void mymethod(Component c){if( c instanceof Button){Button bc = (Button) c;bc.setLabel("Hello");}elseif (c instanceof Label){Label lc = (Label) c;lc.setText("Hello");}}//End of mymethod
}如果運行時的測試和角色匹配沒有執(zhí)行適當?shù)姆椒?#xff0c;setLabel 和setText 將不可用。注意,instanceof 測試反對一個類名但不反對類的對象引用。
+運算符
像你期望的一樣,+運算符會把兩個數(shù)相加。因此下面的代碼將會輸出10
int p=5;
int q=5;
System.out.println(p+q);+運算符在Java 中是一個罕見的操作符重載的例子。C++程序員習慣于能夠重載運算符為他們定義的任何意義。Java 程序員沒有這種便利,但是由于對于字符串來講,加號用來做串聯(lián)是很有用的。因此,下面的代碼將編譯
String s = "One";
String s2 = "Two"
String s3 = "";
s3 = s+s2;
System.out.println(s3);
這段代碼將會輸出字符串OneTwo。注意,兩個連接的字符串中間沒有空格。如果你是Visual Basic 背景的程序員,下面的語法可能不熟悉
s2+=s3
這句代碼在Java 中可以表達成更接近于Visual Basic 形式的語句
s2= s2+s3在某些情況下,Java 可能在后臺調(diào)用toString 方法。就像名字顯示的那樣,這個方法會試著轉(zhuǎn)換為一個String 表達。對一個整型來講,這就意味著數(shù)字10 在toString 調(diào)用后會返回字符串“10”這點在下面的代碼中有所展現(xiàn)。
int p = 10;
String s = "Two";
String s2 = "";
s2 = s + p;
System.out.printlns(s2);
這段代碼會輸出Two10
記住,只有+運算符可以對字符串進行重載,如果你對字符串使用除號和減號(/ -),你會得到一個錯誤。
為不同類型的原始型變量賦值一個布爾類型不能賦值給除了布爾類型外的變量。對于C/C++程序員來講,記得這說明一個布爾類型不能賦值為-1 或0,而一個Java 布爾類型不能用零或非零來替換。除了布爾型之外,我們學習這個目標的一般規(guī)則就是擴展轉(zhuǎn)換是允許的,當然不考慮精確性的危險。由于縮小轉(zhuǎn)換會產(chǎn)生降低精確性的結(jié)果,因此是不允許的。對于擴展,我的意思是一個例如byte 這樣的變量,占用一個byte(8bit),可以賦值給一個integer 這樣占用多個bit的變量。如果你試著將一個integer 賦值給一個byte,你會得到一個編譯時錯誤
byte b= 10;
int i = 0;
b = i;原始類型可能賦值給一個“更寬”的數(shù)據(jù)類型,一個boolean 只能賦值給另一個boolean。你可能預料到你不能將一個原始類型賦值給一個對象或相反的操作,這包括原始類型的包裝類。因此,下面的代碼是非法的
int j=0;
Integer k = new Integer(99);
j=k; //Illegal assignment of an object to a primitive賦值對象和賦值原始類型的一個重要區(qū)別是,原始類型在編譯時被檢查而對象在運行時被檢查。我們將會在后面提到,當一個對象在編譯時沒有被完全處理的話,還會有重要的含義。你當然可以運行一個角色來阻止一個變量適應(yīng)一個窄一些的數(shù)據(jù)類型。一般不建議你放任精確性降低。但是如果你真的想這樣干,Java 使用C/C++的習慣,將數(shù)據(jù)類型封裝進()中,因此,下面的代碼將會編譯運行。
public class Mc{public static void main(String argv[]){byte b=0;int i = 5000;b = (byte) i;System.out.println(b);}
}
輸出結(jié)果是
-120
可能不是很有必要這么干。給不同類型的對象引用賦值將一個對象引用賦值給另一個的一般規(guī)則是,你可以對繼承樹向上賦值但不能向下復制。你可以這樣想,如果你將一個子類的實例賦值給基類,Java 知道哪個方法會在子類中。但是一個子類可能有基類沒有的額外方法。你可以使用操作符強制轉(zhuǎn)換。對象引用可以從子類向基類賦值。下面舉例說明如何向上轉(zhuǎn)換對象引用。
class Base{}public class ObRef extends Base{public static void main(String argv[]){ObRef o = new ObRef();Base b = new Base();b=o;//This will compile OK/*o=b; This would cause an error indicatingan explicit cast is needed to cast Baseto ObRef */}
}
++和-運算符你可能因為發(fā)現(xiàn)在一個并非微不足道的Java 程序沒有使用++或-運算符而感到巨大的壓力,因而很容易認為你知道的一切只是為了測試的目的而需要知道的知識。這些運算符可以用于變量的前增或者后增。如果你不明白它們的差別,可能會因為一個相當容易的問題而導致測試丟分。舉例來說,下面的代碼編譯運行后,你認為它會向控制臺發(fā)送什么呢?
public class PostInc{static int i=1;public static void main(String argv[]){System.out.println(i++);}
}
如果你編譯運行了這段代碼,可以看到輸出是1 而不是2。這是因為++放在i 的后面,因此自增(加1)會在這行運行后發(fā)生,對于-運算符也是一樣的規(guī)則。
位移運算符我恨整個的位移部分。它需要你的大腦充滿了非直覺的能力,因此很少有程序員用它。現(xiàn)實中的典型應(yīng)用的例子是密碼系統(tǒng)和低級別的鏡像文件。你可能寫了大量的Java 程序但卻從沒自己移動過一個bit。但是學習它的大部分的原因是為了針對測試而不是為了其他想法。測試一般會有至少一個問題是關(guān)于位移運算符的。如果你具有C++背景,你可能會被誤導成認為你的所有C++知識都可以直接轉(zhuǎn)換成Java 語言。為了清楚的理解這點,你必須用二進制方式思考,也就是知道每個bit 位的值 32, 16, 8, 4, 2, 1不僅僅是欣賞二進制,你還需要從整體上把握“高度褒揚的二進制”編號系統(tǒng)。在這個系統(tǒng)描述中,第一bit 位表示這個數(shù)是正數(shù)還是負數(shù)。盡量依靠你的直覺,你會發(fā)現(xiàn)當你理解二進制系統(tǒng)的工作原理類似于汽車里程表時,事情開始變得奇怪了。想象每個輪子有一個1 或者0 在上面。如果你想把下面的顯示回退的時候
00000000 00000000 00000000 00000001
如果后退了一下,就會顯示
11111111 11111111 11111111 11111111 11111111
這表示-1。如果再后退一下,會顯示
11111111 11111111 11111111 11111111 1111110這些例子有些過于單純化了。直到我學習Java 程序測試,我都只是認為二進制系統(tǒng)只是利用第一位來表明標記部分。但你可以看到,實際情況要復雜的多。為了幫你多理解一點關(guān)于符號的知識,我寫了一個相當簡單的程序,它會顯示一個在命令行給定的數(shù)的bit 模式。也可以改進為八進制形式,但同樣很容易的得到大體的結(jié)果。
public class Shift{public static void main(String argv[]){int i = Integer.parseInt(argv[0]);System.out.println(Integer.toBinaryString(i));}
}如果你是從C/C++背景轉(zhuǎn)到Java,你可以為Java 中的右移運算符比C/C++稍微明確一點而得到一點安慰。在C/C++中,右移運算符可能是取決于編譯器執(zhí)行的有符號數(shù)或無符號數(shù)。如果你是從Visual Basic 背景轉(zhuǎn)來,恭喜你可以在低級別的情況下編程了。注意,本節(jié)的目標僅僅是要求你理解應(yīng)用這些運算符到int 值后的結(jié)果。這比應(yīng)用運算符到byte 或者short 上顯得容易一些,尤其是負數(shù),更有可能得到不可預料的結(jié)果。
正數(shù)的無符號右移我以無符號右移開始是因為它是最怪異的位移,而且需要對二進制表示法有充分的理解。而處理負數(shù)時會更加的怪異,因此我以正數(shù)開始。無符號右移操作將一個數(shù)字作為一個純粹的bit 模式,忽略特定的標識位。記住,一旦你開始將一個數(shù)字作為一系列bit 時,任何bit 級別的操作可能帶給你將它作為一個普通數(shù)字時想不到的結(jié)果。無符號右移操作包括兩個操作數(shù),第一個數(shù)字是需要位移的數(shù),跟在運算符后面的數(shù)字是需要位移的位置,例如下面
3 >>> 1表示你將數(shù)字3 的bit 向右移動一位。二進制補碼方式的系統(tǒng)意味著數(shù)字開頭的bit 位表示它是正數(shù)還是負數(shù)。如果是0,表示數(shù)字是正數(shù),如果是1,表示它是負數(shù)。無符號數(shù)的右移的第一位總是被0 補上,這就意味著一個無符號數(shù)右移操作總是得到正數(shù)的結(jié)果。如果你能想起數(shù)字3 的二進制形式011并且將它右移一位3 >> 1你會得到001注意,有新值的位離開了數(shù)的末端并被有效的拋棄了。如果你執(zhí)行了兩位的右移,你可能因為數(shù)字變成了0 并且0 覆蓋了所有的bit 位置而感到有點驚訝。如果你一直增加右移的數(shù)量,例如6 位,10 位或20 位,你會發(fā)現(xiàn)像你預料的那樣結(jié)果一直是0。可是如果你堅持到3 >>>32得到了令人驚訝的結(jié)果3,為什么會這樣?在移動之前的后臺,一個模32 被作為操作數(shù)。模運算符,在Java 中指一個數(shù)被另一個數(shù)通過%字符除,然后返回余數(shù)。同時一個數(shù)如果小于求模的數(shù),將會直接返回原值。同時,如果一個數(shù)位移不到32 時,模運算符不會注意到這種情況,一旦到了32,模運算符就會起作用了。因此,32%32 返回0,當作沒有東西剩余而使運算符作用于3 >>> 32的值是3,也就是說3 被移動了0 位。我開始依靠直覺沒能發(fā)現(xiàn)這點,因此我寫了下面的代碼
public class shift{static int i=2;public static void main(String argv[]){System.out.println(32 % 32);System.out.println( 3 >>> 32);}
}
這段代碼的輸出是
0
3
一個模32 在位移操作數(shù)執(zhí)行時起作用,這影響超過32 位的位移。
負數(shù)的無符號右移一個負數(shù)的無符號右移通常會得到一個正數(shù)的結(jié)果。我說的通常是因為有一個例外,就是你移動的包括符號位的原始數(shù)剛好是在32 位結(jié)束。像剛才解釋的,你通常得到一個正數(shù)的原因是無符號右移把第一個符號位用0 代替了,這表明是一個正數(shù)。一個負數(shù)的無符號右移有時看起來很怪異。看下面的代碼
System.out.println( -3 >>> 1);
你可能認為會得到這樣的數(shù)
1
也就是說符號位被0 代替,使它成為正數(shù),然后右移一位。但這是不會發(fā)生的,真正的結(jié)果是
2147483646
有些奇怪但卻是事實。這種奇怪的結(jié)果的背后原因跟二進制數(shù)的表示方式有關(guān)。如果你將數(shù)字的表示想象成汽車里程表代表的輪子,當你從最大可能的數(shù)開始往下數(shù)到0 會發(fā)生什么呢?然后再回到低于0 的第一個數(shù)?所有的數(shù)位包括表示負數(shù)的符號位都會變成1。當你執(zhí)行無符號右移時,你打破了這種數(shù)字表示方式,僅僅把符號位當作另一個數(shù)。因此,即使你從例子中-3 這樣的很小的負數(shù)開始,你也會得到一個很大的正數(shù)。你可能會在測試中遇到這樣的問題,問你一個負數(shù)的無符號右移的結(jié)果。正確的答案可能看起來很不可靠。一個很小的負數(shù)通過無符號右移,可能得到一個很大的正數(shù)返回值。
有符號移動運算符<<和>><<和>>運算符用0 設(shè)置新位。因此,下面的例子
System.out.println(2 << 1)
這句代碼將數(shù)字2 左移一位然后將最右邊一位置0。因此,值010變成100,即十進制4。你可以認為這個操作每次將原數(shù)翻倍。因此下面的代碼
System.out.println(2 << 4)
結(jié)果是32
你可以這樣認為
2*2=4(第一次位移)
2*4=8(第二次位移)
2*8=16(第三次位移)
2*16=32(第四次位移)
當你移動到數(shù)字結(jié)尾時,這種思考可能導致非常錯誤的結(jié)果,因此下面的代碼
System.out.println(2<<30)
結(jié)果是-2147483648這看起來相當不符合直覺,但是你可以想到數(shù)字2 可以移動最多的位置,現(xiàn)在變成了二
進制整型可以表示的最大的負數(shù)。如果你再移動一位
system.out.println(2 << 31)
結(jié)果是0,因為每一位都變成了0,而數(shù)字2 已經(jīng)到了末端而被拋棄了。隨著有符號右移,左邊的數(shù)位(新的)在移動前帶著符號位(作為對照,左移的新位置被置0)。這意味著右移將不會影響結(jié)果數(shù)的符號位。
2 >> 2;
這次將數(shù)字2 的所有位右移兩個位置,因此,值0010變成0000,或者說是十進制的0(我認為在所有進制中0 都是這樣)這跟執(zhí)行一個重復的integer 除法等價。在這個例子中,所有的位置都置0。有符號右移運算符的結(jié)果數(shù)字有同樣的符號位。我創(chuàng)建了一個applet,允許你嘗試各種移動運算符,并且查看十進制或者bit 模式的結(jié)果。我已經(jīng)在網(wǎng)頁中包含了這個applet 代碼,你可以看看它是如何工作的。
運算符優(yōu)先
運算符優(yōu)先是指哪個運算符執(zhí)行的優(yōu)先權(quán)的順序。下表是運算符優(yōu)先的總結(jié)
Operator Precedence
()
++expr --expr +expr -expr ~ !
* / %
+ -
<< >> >>>
< > <= >= instanceof
== !=
&
^
|
&&
||
? :
= += -= *= /= %= &= ^= |= <<= >>= >>>=我放在同一行的運算符有同樣的優(yōu)先權(quán)。通常在真正編程的時候,你將會用圓括號指定你期望的執(zhí)行的表達式的順序。這意味著你可以不必真正掌握優(yōu)先級的順序并且可以讓讀你代碼的其他程序員更清楚。但你可能在測驗中遇到建立在運算符優(yōu)先級上的問題,尤其是常用的運算符如+,-,*。如果運算符優(yōu)先級的概念對你沒什么意義,你可以試試下面的代碼會輸出什么
public class OperPres{public static void main(String argv[]){System.out.println(2 + 2 * 2);System.out.println(2 + (2 * 2));System.out.println(8 / 4 + 4);System.out.println(8 /(4 +4));int i = 1;System.out.println(i++ * 2);
}第一條語句2+2*2 意味著2+2 的值乘2 得到輸出結(jié)果8,還是表示2*2 再加2 得到結(jié)果6 呢?關(guān)于其他計算的類似問題可能被問到。順便說一下,這個程序的輸出結(jié)果是66612。
問題
問題1)在下面給定的類中,哪一個能夠不出錯的編譯?
interface IFace{}
class CFace implements IFace{}
class Base{}
public class ObRef extends Base{public static void main(String argv[]){ObRef ob = new ObRef();Base b = new Base();Object o1 = new Object();IFace o2 = new CFace();}
}
1) o1=o2;
2) b=ob;
3) ob=b;
4) o1=b;
問題2)在下面給定的包含變量的語句,哪一個能夠不出錯的編譯?
String s = "Hello";
long l = 99;
double d = 1.11;
int i = 1;
int j = 0;
1) j= i <<s;
2) j= i<<j;
3) j=i<<d;
4)j=i<<l;
問題3)給定下面的變量
char c = 'c';
int i = 10;
double d = 10;
long l = 1;
String s = "Hello";
哪一個能夠不出錯的的編譯?
1) c=c+i;
2) s+=i;
3) i+=s;
4) c+=s;
問題4)下面的語句會輸出什么?
System.out.println(-1 >>>1);
1) 0
2) -1
3) 1
4) 2147483647
問題5)下面的語句會輸出什么?
System.out.println(1 <<32);
1) 1
2) -1
3) 32
4)-2147483648
問題6)下面的哪個語句是正確的?
1) System.out.println(1+1);
2) int i= 2+'2';
3) String s= "on"+'one';
4) byte b=255;
問題7)當你試著編譯運行下面的代碼時會出現(xiàn)什么情況?
Public class Pres{public static void main(String argv[]){System.out.println( 2 * 2 | 2);}
}
1) Compile time errors, operators cannot be chained together in this manner
2) Compilation and output of 4
3) Compilation and output of 6
4) Compilation and output of 2
問題8)當你試著編譯運行下面的代碼時會出現(xiàn)什么情況?
public class ModShift{
static int i = 1;
static int j =1;
static int k = 0;
public static void main(String argv[]){i = i << 32;j = j >>32;k = i + j;System.out.println(k++);
}
1 )Compile time error
2) Compilation and output of 3
3) Compilation and output of -3
4) Compilation and output of 2
問題9)當你試著編譯運行下面的代碼時會出現(xiàn)什么情況?
public class Shift{static int i;static int j;public static void main(String argv[]){i = 2;j = i <<31;i =i++;System.out.println(j);System.out.println(i);}
}
1) -2147483648 followed by 2
2) -2147483648 followed by 3
3) 0 followed by 3
4) 0 followed by 2
問題10)下面程序的輸出是什么?
public class Mac{public static void main(String argv[]){System.out.println( -1 >>>1 & 2);}
}
1) 2147483647
2) -1
3) 10
4) 2
答案
答案1)
1)o1=o2;
2)b=ob;
4)o1=b;
答案2)
2)j= i<<j;
4)j=i<<l;
答案3)
2)s+=i;
如果你想測試各種可能出現(xiàn)的情況,可以試著編譯下面的代碼
public class Llandaff{public static void main(String argv[]){Llandaff h = new Llandaff();h.go();}public void go(){char c = 'c';int i = 10;double d = 10;long l = 1;String s = "Hello";//Start commenting these out till it all compilesc=c+i;s+=i;i+=s;c+=s;}
}
答案4)
4) 2147483647
即使你的大腦中可能沒有出現(xiàn)這個數(shù)字,對于無符號右移的理解也能告訴你其他答案是錯誤的。
答案5)
1) 1
bit 位會隨著左移運算符“滾動”,因此System.out.println(1 <<31);的結(jié)果將是-2147483648
答案6)
1) System.out.println(1+1);
2) int i= 2+'2';
第三個選項是不正確的,因為單引號指明一個字符而不是字符串。第四個選項不會被編譯,因為255 超過了一個byte 的范圍
答案7)
3) Compilation and output of 6
*運算符的優(yōu)先級高于|運算符。因此,該計算等價于(2*)|2,或者認為是4|2。|運算符比較每個位置的bit 位,如果任意數(shù)字的該位為1,則此位的輸出也是1。4 的順序是100,2 的順序是10,因此|運算符的結(jié)果為110,即十進制的6
答案8)
4) Compilation and output of 2
當你移動數(shù)字的位置為32 位時,模32 會在移位時執(zhí)行。32%32 是說如果你用32 除32 時會剩多少,答案是0。因此數(shù)字被移動0 位。也就是返回原數(shù)。這個問題有點陰險,因為輸出結(jié)果中使用了后增運算符++。這表示在當前行結(jié)束執(zhí)行后數(shù)字被增加,因此1 加1 得到2被輸出。
答案9)
4) 0 followed by 2
這道題沒有看起來那么難。結(jié)合int 型數(shù)的位數(shù)及數(shù)字2 的bit 模式和對于有符號左移運算符的理解,得知數(shù)字2 的唯一一個bit 位會被拋棄,所有bit 位置0。輸出結(jié)果中包括2 而不是3 是因為后增運算符在=運算符賦值后將i 加1。
答案10)
4) 2
這個問題可能讓你有些發(fā)狂的舉動。但是如果你有正確的背景知識的話,你可以得到答案的。如果你了解二進制表示法,你應(yīng)該知道-1 是在int 型數(shù)的每一位都是1。根據(jù)運算符優(yōu)先級,你可以知道>>>運算符在&前執(zhí)行。如果你去掉&運算符,你會發(fā)現(xiàn)輸出結(jié)果是選項1 中給出的最大數(shù)。但是由于&執(zhí)行,結(jié)果是2,表示輸出中的bit 位當兩個數(shù)字的位都為1 時才
為1。因此輸出為2。
目標二 equals 方法確定在任何java.lang.String,java.lang.Boolean 和java.lang.Object 類的對象應(yīng)用布爾類型的equals(Object)方法的結(jié)果。如果你有Visal Basic 的背景(像我一樣),用"="號比較兩部分變量的想法是很奇怪的.然而事實上,對于通常應(yīng)用的字符串的引用來講,這是相當重要的。為了測驗的目的,你可能被問到關(guān)于equals 操作符對于對象引用的引用和布爾類型的引用這樣的問題.注意是關(guān)于布爾類的問題而不是關(guān)于布爾基本類型(你不能用它來調(diào)用方法).
equals 和==的不同點equals 方法可以被認為是對兩個對象值的深層比較,而==操作是淺層比較。equals 方法比較兩個對象的所指對象,而不是兩個指針本身(如果你承認Java 有指針)。這種間接方式對C++程序員可能很清楚,但是在Visal Basic 中沒有直接的比較方式。
將equals 方法用于字符型equals 方法返回一個布爾型基本類型值。這表明它可以被用于if,while 或者其他循環(huán)語句。它可以被用于使用==操作符比較基本類型的情況。當比較字符串時equals 方法和==操作的執(zhí)行有一些不同結(jié)果。對于字符串恒量和它被Java 處理的方式是很混亂的。Java 中有兩種方法創(chuàng)建字符串。一個方法是用new 操作符,這是通常的字符串創(chuàng)建方法
String s = new String("Hello");
但是更簡短的方法是
String s= "GoodBye";
通常這兩種創(chuàng)建字符串的方法有些微不同,但是在考試中往往會問到這個不同。兩種創(chuàng)建字符串的方法,當用于字符串時有相同的結(jié)果,但不用new 關(guān)鍵字會創(chuàng)建Java字符串池中指向同一個字符串的指針。字符串池是Java 存儲資源的一種方法。舉例說明這
個結(jié)果
String s = "Hello";
String s2 = "Hello";
if (s==s2){System.out.println("Equal without new operator");
}
String t = new String("Hello");
string u = new String("Hello");
if (t==u){System.out.println("Equal with new operator");
}在上一個目標中你可能認為第一個輸出"Equal without new operator"不會出現(xiàn),因為s 和s2 時不同的對象,但是==運算符判斷兩個對象的指針,而不是他們的值。因為Java 存儲資源的方式是重用不用new 關(guān)鍵字創(chuàng)建的字符串,所以s 和s2 有相同的“地址”,所以會輸出以下字符串。
"Equal without new operator"
但是對于第二組字符串t 和u,new 操作符迫使Java 創(chuàng)建不同的字符串。因為==操作符之比較兩個對象的地址而不是值,t 和u 有不同的地址,所以"Equal with new operator"將不會輸出。
關(guān)鍵概念equals 方法用于字符串,但是字符串被創(chuàng)建后,會執(zhí)行字符和字符的比較。應(yīng)用字符串池的作用,以及使用==和equals 方法的區(qū)別不是很顯而易見,尤其是如果你有Visal Basic 的背景。理解它的最好方法是自己寫個例子看它是如何工作的。試試用new 和不用new 方法創(chuàng)建字符串。
在布爾型上應(yīng)用equals 方法理解在java.lang.Boolean 上應(yīng)用equals 運算是可能的要求。Boolean 是boolean 基本類型的封裝類型。它是對象型的可以在其上應(yīng)用equals 方法。依照JDK 文檔,equals 方法用于Boolean 封裝類型,“只是在參數(shù)非空而且布爾對象有相同的布爾值時返回真”。
例如
Boolean b1 = new Boolean(true);
Boolean b2 = new Boolean(true);
if(b1.equals(b2)){System.out.println("We are equal");
}
boolean 和Boolean 只有微小的區(qū)別,當你對Java 的if 運算非常熟悉,你就知道不能像C/C++程序員一樣應(yīng)用隱含的方式,就像這樣
int x =1;
if(x){
//do something, but not in Java
}
這在Java 中不能執(zhí)行,因為if 操作的變量必須是boolean 判斷,Java 沒有C/C++中任何非空值都可被認為真的概念。但是以下Java 代碼可以通過
boolean b1=true;
if(b1){
//do something in java
}
雖然這是不好的編程方法,但是在語法上是正確的。因為if 操作的變量是boolean 類型。
在對象類型上應(yīng)用equals 方法對于Java 的基本設(shè)計,任何類的實例也是java.lang.Object 的實例。試試equals 判斷對象類型的返回值應(yīng)用toString()方法.對于對象變量toString 方法簡單返回內(nèi)存地址。所以與使用==操作的結(jié)果一樣。因為Java 不是設(shè)計成操作內(nèi)存地址和指針的,所以這不是個有用的判斷。
看下面的例子
public class MyParm{public static void main(String argv[]){Object m1 = new Object();Object m2 = new Object();System.out.println(m1);System.out.println(m2);if (m1.equals(m2)){System.out.println("Equals");}else{System.out.println("Not Equals");}}
}
如果你編譯運行這段代碼,會得到如下輸出
java.lang.Object@16c80b
java.lang.Object@16c80a
Not Equals
這些奇怪的值是內(nèi)存地址,大概根本不是你想得到的。
問題
問題 1)編譯運行以下代碼時會發(fā)生什么情況?
public class MyParm{public static void main(String argv[]){String s1= "One";String s2 = "One";if(s1.equals(s2)){System.out.println("String equals");}boolean b1 = true;boolean b2 = true;if(b1.equals(b2)){System.out.println("true");}}
}
1) Compile time error
2) No output
3) Only "String equals"
4) "String equals" followed by "true"
問題 2) 編譯運行以下代碼時會發(fā)生什么情況?
String s1= "One";
String s2 = new String("One");
if(s1.equals(s2)){System.out.println("String equals");
}
Boolean b1 = new Boolean(true);
Boolean b2 = new Boolean(true);
if(b1==b2){System.out.println("Boolean Equals");
}
1) Compile time error
2) "String equals" only
3) "String equals" followed by "Boolean equals"
4) "Boolean equals" only
問題 3)編譯運行以下代碼的結(jié)果是什么?
What will be the result of attempting to compile and run the following code?
Object o1 = new Object();
Object o2 = new Object();
o1=o2;
if(o1.equals(o2))System.out.println("Equals");
}
1) Compile time error
2) "Equals"
3) No output
4) Run time error
答案
答案 1)
1) Compile time error
b1.equals() 這一行會引發(fā)錯誤,因為b1 是簡單類型,簡單類型沒有任何方法。如果創(chuàng)建基本類型的封裝類Boolean 你就可以應(yīng)用equals 方法。
答案 2 )
2) "String equals" only
用==操作符簡單判斷基本類型的封裝類Boolean 的一個實例的內(nèi)存地址。
答案 3)
2) "Equals"
因為一個對象的實例可以賦值給另一個對象用
o1=o2;
它們現(xiàn)在就指向同一個內(nèi)存地址,equals 方法判斷將返回true。
目標三 &、|、&&和||運算符在一個包含運算符&、|、&&、||和值已知的變量的表達式中,指出哪個運算符被求值,表達式的值是多少?很容易忘記哪個邏輯運算用哪個運算符和它們所做的操作,確保你可以在考試中說出它們的區(qū)別。如果你初次接觸這些運算符,你可能值得花時間好好記憶你才不會對它們這些按位運算符和邏輯運算符的操作搞亂。你可能會記得“雙邏輯”這種表達很奇怪。
邏輯運算符的短路效應(yīng)邏輯運算符(&&、||)在用于“短路”邏輯像C/C++的AND 和邏輯OR 操作時有一點特別的結(jié)果。如果你來自Visal Basic 背景這就有些奇怪了,因為Visal Basic 會計算所有的操作數(shù)的值。如果你理解AND,你就會理解Java 的方法,如果第一個操作數(shù)為假,第二個操作數(shù)的值就沒有作用了,所有的結(jié)構(gòu)都為假。對于邏輯OR 也是,如果第一個操作數(shù)為真,所有的計算結(jié)果將為真,因為只要一個操作數(shù)為真最后的結(jié)果就為真。這種依靠一邊結(jié)果的壓縮計算可能有一個結(jié)果。請看下面的例子
public class MyClass1{public static void main(String argv[]){int Output=10;boolean b1 = false;if((b1==true) && ((Output+=10)==20)){System.out.println("We are equal "+Output);}else{System.out.println("Not equal! "+Output);}}
}
"Not equal 10"會被輸出。這說明Output+=10 這個運算永遠不會被執(zhí)行,因為在第一個操作數(shù)的值為false 時運算就停止了。如果你把b1 的值改成true,運算就會像你想得一樣執(zhí)行,輸出會是"We are equal 20".當你真的不想在有任何值為false 時進行其它運算時,這也許有時是便捷的方法,但是當你完全不熟悉時這也許會產(chǎn)生意外的結(jié)果。
按位運算符&和|運算符用于做整形的按位與和或操作.在考試中你會遇到這樣的問題,給出一個十進制的數(shù),然你用按位與和或運算計算.要執(zhí)行這些操作你需要熟悉從十進制到二進制的轉(zhuǎn)換,并且知道其比特形式.這有一個典型的例子
下面運算的結(jié)果是什么?
3 | 4
3 的二進制比特形式是11
4 的二進制比特形式是100
要執(zhí)行二進制或運算,兩個數(shù)的每個比特都要互相比較.如果任一個比特為1 則結(jié)果中的比特數(shù)為1.所以這個操作的結(jié)果的二進制形式是111,也就是十進制的7.目標沒有特別要求你知道按位XOR 運算,用^符號執(zhí)行。
用二進制思考如果你感到用二進制思考不舒服(我更習慣用十進制思考),你也可能想做一些練習來掌握這個問題和二進制轉(zhuǎn)換操作。如果你使用windows 你可能發(fā)現(xiàn)用計算器的科學模式很有用。你可以在標準模式下選則View 和switch 來變成科學模式。在科學模式中,你可以轉(zhuǎn)換數(shù)值來看它的十進制和二進制模式,這會顯示數(shù)的二進制形式。有一個很方便的竅門,我如果在寫比特轉(zhuǎn)換applet 之前知道就好了,就是怎樣用整形來表示比特形式。這里有一個實現(xiàn)這個的小程序。
public class BinDec{public static void main(String argv[]){System.out.println(Integer.parseInt("11",2));System.out.println(Integer.toString(64,2));}
}
如果你編譯運行這段程序,將會得到輸出
3
1000000
注意程序怎樣把二進制11 轉(zhuǎn)換成十進制的對應(yīng)數(shù)3,又是怎樣把十進制數(shù)64 轉(zhuǎn)換成相應(yīng)的比特形式。每個方法的第二個參數(shù)是基數(shù)。所以在這個例子中會把書轉(zhuǎn)換成2 為基數(shù)的,而我們常常會用10 為基數(shù)的數(shù)。
問題
問題 1 )你嘗試編譯運行以下代碼時會發(fā)生什么情況
int Output=10;
boolean b1 = false;
if((b1==true) && ((Output+=10)==20)){System.out.println("We are equal "+Output);
}else
{System.out.println("Not equal! "+Output);
}
1) Compile error, attempting to perform binary comparison on logical data type
2) Compilation and output of "We are equal 10"
3) Compilation and output of "Not equal! 20"
4) Compilation and output of "Not equal! 10"
問題 2 )下面一行代碼會有什么輸出
System.out.println(010|4);
1) 14
2) 0
3) 6
4) 12
問題 3 )下面哪項編譯沒有錯誤
1)
int i=10;
int j = 4;
System.out.println(i||j);
2)
int i=10;
int j = 4;
System.out.println(i|j);
3)
boolean b1=true;
boolean b2=true;
System.out.println(b1|b2);
4)
boolean b1=true;
boolean b2=true;
System.out.println(b1||b2);
答案
答案 1)
4) Compilation and output of "Not equal! 10"
輸出是"Not equal 10". 這表明運算Output+=10 沒有被執(zhí)行,因為運算在第一個操作數(shù)被算出為true 時就停止了。如果你把b1 的值改成true,運算會像你想得那樣進行,輸出結(jié)果為
"We are equal 20";.
答案2 )
4) 12
和二進制OR 目標相同,這個問題要求你理解開頭的零表示八進制符號,第一個1 表示數(shù)中有一個8,沒有其他數(shù)。所以十進制運算是
8|4
轉(zhuǎn)換成二進制形式為
1000
0100
----
1100
|運算符表示兩個數(shù)每一位進行運算,其中有一個為1,相應(yīng)位的結(jié)果就為1。
答案 3)
2,3,4
選項一不會通過編譯,因為它試圖在整型上運行邏輯OR 操作。邏輯或只能用于操作boolean類型參數(shù)。
目標四 在方法中傳遞對象和基本類型值判斷傳遞對象和基本類型參數(shù)到方法中的結(jié)果,在方法中進行賦值或者其它的修改操作。
目標中注意事項目標可能會問你是否理解傳遞值到方法中時會發(fā)生什么結(jié)果。如果方法中的代碼改變了變量,對外部的方法是否可見?直接引用Peter van der Lindens 的Java 程序員解答的一段話(在http://www.afu.com)//引用所有的變量(基本類型值和對象的引用值)都是值傳遞。但是這不是全部情況,對象是經(jīng)常通過Java 的引用變量來操作的。所以也可以說對象是通過引用傳遞的(引用變量通過值傳遞)。這是變量不使用對象的值而是像前一個問題那樣描述的使用對象引用的值的結(jié)果。最后一行:調(diào)用者對基本類型參數(shù)(int,char 等)的拷貝在相應(yīng)參數(shù)變化時不會改變。但是,在被調(diào)用方法改變相應(yīng)作為參數(shù)傳遞的對象(引用)字段時,調(diào)用者的對象也改變其字段。
//引用結(jié)束如果你來自C++背景,你可能對值傳遞參數(shù)和用&符號傳遞引用參數(shù)熟悉。在Java 中沒有這樣的選擇,所有的都是值傳遞。但是看起來并不總是這樣。如果你傳遞的對象是對象的引用,你不能直接對對象的引用進行操作。所以如果你操作一個傳遞到方法的對象的字段,結(jié)果就好像你按引用傳遞(任何改變結(jié)果都會返回到調(diào)用函數(shù))。將對象引用作為方法變量
請看下面的例子
class ValHold{public int i = 10;
}
public class ObParm{public static void main(String argv[]){ObParm o = new ObParm();o.amethod();}public void amethod(){ValHold v = new ValHold();v.i=10;System.out.println("Before another = "+ v.i);another(v);System.out.println("After another = "+ v.i);}//End of amethodpublic void another(ValHold v){v.i = 20;System.out.println("In another = "+ v.i);}//End of another
}
程序的輸出結(jié)果是
Before another = 10
In another = 20
After another = 20
看變量i 是怎么被修改的。如果Java 總是值傳遞(也就是對變量的拷貝),它是怎么被修改的呢?過程是這樣的方法收到了句柄的拷貝或者對象的引用,但是這個引用的作用類似于指向真實的指針。對這個字段的改變會反映到它所指的值。這有些像是在C/C++中指針的自動間接應(yīng)用的的作用。
基本類型作為方法參數(shù)
當你對方法傳遞基本類型參數(shù),是直接傳遞值。方法得到它的拷貝,任何修改都不會在外部方法得到反映。請看以下例子
public class Parm{public static void main(String argv[]){Parm p = new Parm();p.amethod();}//End of mainpublic void amethod(){int i=10;System.out.println("Before another i= " +i);another(i);System.out.println("After another i= " + i);}//End of amethodpublic void another(int i){i+=10;System.out.println("In another i= " + i);}//End of another
}
程序的輸出結(jié)果如下
Before another i= 10
In another i= 20
After another i= 10
習題
習題 1)
以下所給代碼的輸出是什么?
class ValHold{public int i = 10;
}
public class ObParm{public static void main(String argv[]){ObParm o = new ObParm();o.amethod();}public void amethod(){int i = 99;ValHold v = new ValHold();v.i=30;another(v,i);System.out.println(v.i);}//End of amethodpublic void another(ValHold v, int i){i=0;v.i = 20;ValHold vh = new ValHold();v = vh;System.out.println(v.i+ " "+i);}//End of another
}
1) 10,0, 30
2) 20,0,30
3) 20,99,30
4) 10,0,20
答案
答案 1)
4) 10,0,20
第6 章 重載,重寫,運行時類型和OO
目標一 封裝和OO 設(shè)計陳述封裝在面向?qū)ο笤O(shè)計中的好處,并編寫實現(xiàn)緊密封裝的類的代碼,陳述“is a”和“has a”的關(guān)系。
“Is a”和“has a”關(guān)系這是一個很基礎(chǔ)的OO 問題,你很可能在考試中碰到一個題目。本質(zhì)上,它是為了考察你是否理解何時在談?wù)搶ο笏鶎俚念惤Y(jié)構(gòu)以及何時是在談?wù)撘粋€類擁有的方法或域。因此,貓是動物的一種(IS A),貓有尾巴(HAS A)。當然,區(qū)別可能會模糊不清。如果你是一名動物學家并且知道動物種類群的正確名字,你可能會說貓是(IS A)longlatinwordforanimalgroupwithtails(一個很長的表示有尾巴的動物群的拉丁單詞)。但是出于考試的目的,這不在考慮范圍之內(nèi)。考試題目趨向于這種類型:根據(jù)一段對于潛在層次結(jié)構(gòu)的描述,你會得到諸如什么應(yīng)該是域,什么應(yīng)該是新的子類的問題。這些問題乍一看比較復雜,但是如果你仔細閱讀的話都十分明顯。
封裝Java1.1 的目標中沒有特別提到封裝,雖然你會被急切的要求學習Java 而不沒有機會接觸概念。封裝包含將類的接口從實現(xiàn)中分離出來。這意味著你無法“偶然地”破壞某個域的值,你必須使用方法來修改值。通常,要實現(xiàn)這一點,需要創(chuàng)建私有變量(域),它們只能通過方法來更新和提取。這些方法的標準命名規(guī)范是setFieldName,getFieldName
例如,你要改變形狀的顏色,你會創(chuàng)建如下形式的方法對
public void setColor (Color c) {cBack = c;
}
public Color getColor () {return cBack;
}
控制變量訪問的主要關(guān)鍵字為
public
private
protected不要受到誤導而認為訪問控制系統(tǒng)與安全有關(guān)。它不是為防止程序員攻擊變量而設(shè)計的,而是為了幫助避免不期望的修改。使用上面Color 例子的標準方法是將cBack 域設(shè)為私有的。一個私有域只在當前類內(nèi)部可見。這意味著程序員不能偶然地在另一個類中寫代碼來修改它的值。這有助于減少bug的引入。接口與實現(xiàn)的分離使得在一個類中修改代碼而不破壞其他代碼變得更簡單。對于類的設(shè)計者這使他們能夠修改類而不必破壞使用它的程序。類的設(shè)計者可以為域修改的“安全檢查”插入額外的檢查流程。我曾經(jīng)致力于保險項目,此項目中的客戶的年齡值可能小于0。如果這個值被保存在簡單的域中,比如整數(shù),就沒有明顯的地方可以存放檢查流程。如果年齡只可以通過set 和get 方法訪問,就可以通過這種不破壞現(xiàn)存代碼的方式來對插入進行0 或負數(shù)年齡檢查。當然,隨著開發(fā)的進行,會發(fā)現(xiàn)更多需要檢查的情況。對于類的最終用戶,這意味著他們不需要理解內(nèi)部工作,呈現(xiàn)在他們面前的是一個清晰的處理數(shù)據(jù)的接口。最終用戶可以相信更新類代碼不會破壞他們現(xiàn)有的代碼。
運行時類型因為多態(tài)機制允許在運行時選擇執(zhí)行方法的版本,有時候?qū)⒁\行的方法并不明顯的。
以如下代碼為例。
class Base {int i = 99;public void amethod () {System.out.println (“Base.amethod ()”);}
}
public class RType extends Base {int i = -1;public static void main (String argv []) {Base b = new RType (); //<= Note the typeSystem.out.println (b.i);b.amethod ();}public void amethod () {System.out.println (“RType.amethod ()”);}
}
注意,b 引用的類型是Base,但是實際的類型是類RType。對amethod 的調(diào)用將啟動RType 中的版本,但是b.i 輸出的調(diào)用將引用Base 類中的域i。
課后測試題
問題1)假設(shè)你被給予如下設(shè)計
“一個人有姓名,年齡,地址和性別。你將要設(shè)計一個類來表示一類叫做病人的人。這種人可以被給予診斷,有配偶并且可能活著”。假設(shè)表示人的類已經(jīng)創(chuàng)建了,當你設(shè)計病人類時如下哪些應(yīng)該被包含在內(nèi)?
1) registration date
2) age
3) sex
4) diagnosis
問題 2)當你試圖編譯并運行如下代碼時會發(fā)生什么?
class Base {int i = 99;public void amethod () {System.out.println (“Base.amethod ()”);}Base () {amethod ();}
}
public class RType extends Base {int i = -1;public static void main (String argv []) {Base b = new RType ();System.out.println (b.i);b.amethod ();}public void amethod () {System.out.println (“RType.amethod ()”);}
}
1)
RType.amethod
-1
RType.amethod
2)
RType.amethod
99
RType.amethod
3)
99
RType.amethod
4)
Compile time error
問題 3)你的首席軟件設(shè)計者向你展示了她正要創(chuàng)建的新電腦部件系統(tǒng)的草圖。在層次結(jié)構(gòu)的頂端是一個叫做Computer 的類,在此之下是兩個子類。一個叫做LinuxPC,另一個叫做WindowsPC。兩者之間最大的不同點是一個運行Linux 操作系統(tǒng),另一個運行Windows系統(tǒng)(當然另一個不同在于一個需要不停的重啟,另一個則能夠可靠的運行)。在WindowsPC 之下是兩個子類,一個叫做Server,另一個叫做Workstation。你如何評價你的設(shè)計者的工作?
1) Give the go ahead for further design using the current scheme
2) Ask for a re-design of the hierarchy with changing the Operation System to a field rather than Class type
3) Ask for the option of WindowsPC to be removed as it will soon be absolete
4) Change the hierarchy to remove the need for the superfluous Computer Class.
問題 4)假設(shè)有如下類
class Base {int Age = 33;
}
關(guān)于對Age 域的訪問,你會如何修改來改進這個類?
1) Define the variable Age as private
2) Define the variable Age as protected
3) Define the variable Age as private and create a get method that returns it and a set method that updates it
4) Define the variable Age as protected and create a set method that returns it and a get method
that updates it
問題 5)下面哪些是封裝的好處?
1) All variables can be manipulated as Objects instead of primitives
2) by making all variables protected they are protected from accidental corruption
3) The implementation of a class can be changed without breaking code that uses it
4) Making all methods protected prevents accidental corruption of data
問題 6)指出三個面向?qū)ο缶幊痰闹饕攸c?
1) encapsulation, dynamic binding, polymorphism
2) polymorphism, overloading, overriding
3) encapsulation, inheritance, dynamic binding
4) encapsulation, inheritance, polymorphism
問題 7)你如何在類中實現(xiàn)封裝?
1) make all variables protected and only allow access via methods
2) make all variables private and only allow access via methods
3) ensure all variables are represented by wrapper classes
4) ensure all variables are accessed through methods in an ancestor class
答案
答案1)
1) registration date
2) diagnosis
對于病人來說,注冊日期是一個合理的添加域,并且設(shè)計明確地指出病人應(yīng)該有診斷報告。由于病人是人的一種,它應(yīng)該有域age 和sex(假設(shè)它們沒有被聲明為私有的)。
答案 2)
2)
RType.amethod
99
RType.amethod
如果這個答案看起來靠不住,試著編譯并運行代碼。原因是這段代碼創(chuàng)建了一個RType 類的實例但是把它賦予一個指向Base 類的引用。在這種情況下,涉及的任何域,比如i,都會指向Base 類中的值,但是方法的調(diào)用將會指向?qū)嶋H類中的方法而不是引用句柄中的方法。
答案 3)
2) Ask for a re-design of the hierarchy with changing the Operating System to a field rather than Class type
答案 4)
3) Define the variable Age as private and create a get method that returns it and a set method that updates it
答案 5)
3) The implementation of a class can be changed without breaking code that uses it
答案 6)
4) encapsulation, inheritance, polymorphism
我曾經(jīng)在一次工作面試上遇到這個問題。我得到了那份工作。不能保證你一定會在考試中遇到類似的問題,但是知道的話會很有用。
答案 7)
2)make all variables private and only allow access via methods
目標二 重寫和重載編寫調(diào)用重寫或重載的方法以及父類的或重載過的構(gòu)造函數(shù);并且描述調(diào)用這些方法的效果。
目標的評論術(shù)語重載(overloaded)和重寫(overridden)太相近了以至于會造成混淆。我記憶的方式是想象某物被踐踏(overridden)字面上的意思是被沉重的交通工具壓過并且不再是其原來的樣子。某物負載過重(overloaded)仍然在移動,但是負擔過重的功能將使其花費巨大的努力。這只是一個區(qū)別兩者的小竅門,跟Java 中實際的操作沒有任何關(guān)系。
重載方法重載是Java 中實現(xiàn)面向?qū)ο?#xff0c;多態(tài)機制等概念的方式之一。多態(tài)性(Polymorphism)是由多個單詞組成的詞語,Ply 意為“很多”,“morphism”暗示著含義。因此,重載允許同一個方法名稱具有多種意思或用途。方法重載是編譯器的技巧,依賴于不同的參數(shù),它允許你使用相同的名稱來完成不同的動作。這樣做的好處是Java 可以在運行時決定調(diào)用的方法而不是在編譯時決定。因而,設(shè)想一下你正在為模擬Java 認證考試設(shè)計系統(tǒng)接口。答案可能作為整數(shù),布爾數(shù)或文本字符串得到。你可以為每一個參數(shù)類型創(chuàng)建一個方法,并給予相應(yīng)的名字,比如
markanswerboolean (Boolean answer) {
}
markanswerint (int answer) {
}
markanswerString (String answer ) {
}
這樣可以正常運行,但這也意味著類的未來用戶需要知道更多不必要的方法名。使用一個單一的方法名會更實用,編譯器可以根據(jù)參數(shù)類型和數(shù)目來決定調(diào)用的實際代碼。進行方法重載不需要記住任何關(guān)鍵字,你只要創(chuàng)建多個具有不同數(shù)目或類型的參數(shù)的同名方法就可以了。參數(shù)的名稱并不重要,但是數(shù)目和類型必須不同。如下是一個markanswer方法重載的例子
void markanwwer (String answer) {
}
void markanswer (int answer) {
}
如下不是重載的實例,它會導致編譯時錯誤,指出這是重復的方法聲明。
void markanswer (String answer) {
}
void markanswer (String title) {
}
返回值類型并不是實現(xiàn)重載署名的要素。因此,改變?nèi)缟洗a使其放回int 值仍然會導致編譯時錯誤,但是這一次指出方法不能
用不同的返回值類型進行重新定義。
重寫方法重寫方法意味著它的所有功能被完全取代了。重寫是在子類中對一個定義在父類中的方法進行修改。為了重寫方法,要在子類中定義一個與父類中具有完全相同署名的方法。這樣做會覆蓋父類中的方法,并且此方法的功能再也不能被直接訪問了。Java 提供了一個重寫的例子,就是每個類都從最高父類Object 中繼承的equals 方法。繼承的equals 版本僅僅在內(nèi)存中比較類引用的實例。這通常不是我們想要的,特別是對于String。對于String,你通常希望通過逐個字符的比較來確定兩個字符串是否相同。為了做到這一點,String 中的equals 版本進行了重寫,并能執(zhí)行逐個字符的比較。
調(diào)用基類的構(gòu)造函數(shù)構(gòu)造函數(shù)是一種在每次創(chuàng)建類的實例時自動運行的特殊方法。Java 能夠識別構(gòu)造函數(shù),因為它們具有與類本身相同的名字,并不需要返回值。與其他方法一樣,構(gòu)造函數(shù)可以接受參數(shù),并且根據(jù)如何初始化類,你可以傳遞不同的參數(shù)。如此,以AWT 包中的Button 類為例,通過重載提供了兩個構(gòu)造函數(shù)的版本。一個是
Button ()
Button (String label)
因此,你可以創(chuàng)建一個沒有標簽的按鈕,并在稍后設(shè)定,或者使用普通的版本在創(chuàng)建的時候就設(shè)定標簽。但是,構(gòu)造函數(shù)是不能被繼承的,所以如果你想從父類中獲得一些有用的構(gòu)造函數(shù),缺省是不可用的。因此,如下代碼將不能編譯通過
class Base {public Base () {}public Base (int i) {}
}
public class MyOver extends Base {public static void main (String argv []) {MyOver m = new MyOver (10); // Will Not compile}
}
要從父類中得到構(gòu)造函數(shù),你需要使用神奇的關(guān)鍵字super。這個關(guān)鍵字可以被當作一個方法來使用,并且傳遞適當?shù)膮?shù)使之與你要求的父類中的構(gòu)造函數(shù)相吻合。在以下修改了上述代碼的例子中,關(guān)鍵字super 被用來調(diào)用基類中接受integer 參數(shù)的構(gòu)造函數(shù)版本,這段代碼編譯時不會報錯。
class Base {public Base () {}public Base (int i) {}
}
public class MyOver extends Base {public static void main (String arg []) {MyOver m = new MyOver (10);}MyOver (int i) {super (i);}
}
使用this ()調(diào)用構(gòu)造函數(shù)與使用super ()調(diào)用基類中構(gòu)造函數(shù)的方式相同,你可以使用this 調(diào)用當前類中的其他構(gòu)造函數(shù)。這樣,在前面的例子中你可以像下面那樣定義另一個構(gòu)造函數(shù)
MyOver (String s, int i) {this (i);
}如你猜測的,這將會調(diào)用當前類中那個只接受一個整數(shù)參數(shù)的構(gòu)造函數(shù)。如果你在構(gòu)造函數(shù)中使用super ()或this (),必須第一個調(diào)用它。由于只有一個能被第一個調(diào)用,你不能在構(gòu)造函數(shù)中既使用super ()又使用this ()。因此,如下代碼會導致編譯時錯誤。
MyOver (String s, int i) {this (i);super (); // Causes a compile time error
}基于構(gòu)造函數(shù)不能被繼承的知識,很明顯重寫是不切合實際的。如果你有一個叫做Base的基類,你創(chuàng)建了一個繼承它的子類,對于要重寫構(gòu)造函數(shù)的子類,它的名字必須跟父類相同。這會導致編譯時錯誤。這是一個沒有層次意義的例子。
class Base {}
class Base extends Base {} //Compile time error!
構(gòu)造函數(shù)和類層次構(gòu)造函數(shù)總是從層次結(jié)構(gòu)的頂端開始稱作向下。在考試中,你很可能會遇到一些題目涉及到在類層次中多次調(diào)用this 和super,你必須指出輸出什么內(nèi)容。當你遇到復雜的層次結(jié)構(gòu)時請格外小心,這可能跟構(gòu)造函數(shù)沒有關(guān)系,可能由于構(gòu)造函數(shù)同時調(diào)用了this 和super,而導致編譯時錯誤。有如下例子
class Mammal {Mammal () {System.out.println (“Creating Mammal”);}
}
public class Human extends Mammal {public static void main (String argv []) {Human h = new Human ();}Human () {System.out.println (“Create Humn”);}
}
當運行代碼時,由于隱式調(diào)用了基類中的無參構(gòu)造函數(shù),首先會輸出字符串“Create Mammal”。
課后測試題
問題 1)假設(shè)有如下類定義,如下哪些方法可以合法放置在“//Here”的注釋之后?
public class Rid {public void amethod (int i, String s) {}// Here
}
1) public void amethod (String s, int i) {}
2) public int amethod (int i, String s) {}
3) public void amethod (int i, String mystring) {}
4) public void Amethod (int i, String s) {}
問題 2)假設(shè)有如下類定義,哪些代碼可以被合法放置在注釋“//Here”之后?
class Base {public Base (int i) {}
}
public class MyOver extends Base {public static void main (String arg []) {MyOver m = new MyOver (10);}MyOver (int i) {super (i);}MyOver (String s, int i) {this (i);//Here}
}
1) MyOver m = new MyOver ():
2) super ();
3) this (“Hello”, 10);
4) Base b = new Base (10);
問題 3)假設(shè)有如下類定義
class Mammal {Mammal () {System.out.println (“Mamml”);}
}
class Dog extends Mammal {Dog () {System.out.println (“Dog”);}
}
public class Collie extends Dog {public static void main (String argv []) {Collie c = new Collie ();}Collie () {this (“Good Dog”);System.out.println (“Collie”);}Collie (String s) {System.out.println (s);}
}
將會輸出什么?
1) Compile time error
2) Mammal, Dog, Good Dog, Collie
3) Good Dog, Collie, Dog, Mammal
4) Good Dog, Collie
問題 4)下面哪些論述是正確的?
1) Constructors are not inherited
2) Constructors can be overridden
3) A parental constructor can be invoked using this
4) Any method may contain a call to this or super
問題 5)試圖編譯并運行下面代碼會發(fā)生什么?
class Base {public void amethod (int i, String s) {System.out.println (“Base amethod”);}Base () {System.out.println (“Base Constructor”);}
}
public class Child extends Base {int i;String Parm = “Hello”;public static void main (String argv []) {Child c = new Child ():c.amethod ():}void amethod (int i, String Parm) {super.amethod (i, Parm);}public void amethod () {}
}
1) Compile time error
2) Error caused by illegal syntax super.amethod (i, Parm)
3) Output of “Base Constructor”
4) Error caused by incorrect parameter names in call to super.amethod
問題 6)試圖編譯并運行如下代碼時將發(fā)生什么?
class Mammal {Mammal () {System.out.println (“Four”);}public void ears () {System.out.println (“Two”);}
}
class Dog extends Mammal {Dog () {super.ears ();System.out.println (“Three”);}
}
public class Scottie extends Dog {public static void main (String argv []) {System.out.println (“One”);Scottie h = new Scottie ();}
}
1) One, Three, Two, Four
2) One, Four, Three, Two
3) One, Four, Two, Three
4) Compile time error
答案
答案 1)
1) public void amethod (String s, int i) {}
4)public void Amethod (int i, String s) {}
Amethod 中的大寫字母A 意味著這是不同的方法。
答案 2)
4)Base b = new Base (10);
任何this 或super 的調(diào)用都必須是構(gòu)造函數(shù)中的第一行。由于方法已經(jīng)調(diào)用了this,不能有別的調(diào)用插入了。
答案 3)
2) Mammal, Dog, Good Dog, Collie
答案 4)
1) Constructors are not inherited
父類的構(gòu)造函數(shù)應(yīng)該使用super 調(diào)用,而不是this。
答案 5)
1) Compile time error
這會導致一個錯誤,意思是說“你不能重寫方法使其訪問權(quán)限更靠近私有”。基類的amethod版本被明確的標注為public,但是在子類中沒有標識符。好了,所以這不是在考察你的構(gòu)造函數(shù)重載的知識,但是他們也沒在考試中告訴你主題。若這段代碼沒有省略關(guān)鍵字public,將會輸出“Base constructor”,選項3。
答案 6)
3)One, Four, Two, Three
類是從層次的根部往下創(chuàng)建的。因此,首先輸出One,因為它在Scottie h 初始化之前創(chuàng)建。然后,JVM 移動到層次的基類,運行“祖父類”Mammal 的構(gòu)造函數(shù)。這會輸出“Four”。然后,運行Dog 的構(gòu)造函數(shù)。Dog 的構(gòu)造函數(shù)調(diào)用Mammal 中的ears 方法,因此輸出“Two”。最后,Dog 的構(gòu)造函數(shù)完成,輸出“Three”。
目標三 創(chuàng)建類實例編寫創(chuàng)建任何具體類實例的代碼,包括正常的高層次類,內(nèi)部類,靜態(tài)內(nèi)部類和匿名內(nèi)部類。
目標的注釋這份材料的一些內(nèi)容在別的地方提到過,特別是目標4.1 中。
實例化類具體類是指能夠被實例化為對象引用(也簡稱為對象)的類。因此,抽象類是不能被實例化的,所以不能創(chuàng)建對象引用。記住,包含任何抽象方法的類本身也是抽象的,并且不能被實例化。實例化類的關(guān)鍵是使用關(guān)鍵字new。典型地,如下所示
Button b = new Button ();
這個語法意為變量b 是Button 類型的,并且包含指向Button 實例的引用。但是,盡管引用的類型經(jīng)常與被實例化的類的類型是一樣的,但這不是必要的。因此,如下代碼也是合法的
Object b = new Button ();
這個語法指出b 引用的類型是Object 而不是Button。
聲明和實例化不是必須出現(xiàn)在同一行上。可以這樣創(chuàng)建一個類的實例。
Button b;
b = new Button ();
內(nèi)部類是隨著JDK1.1 的發(fā)布而加入的。它們允許一個類在另一個類中定義。
內(nèi)部類內(nèi)部類是隨著JDK1.1 的發(fā)布而引入的。它們允許類被定義在其他類中,有時候被稱作嵌套類。它們被廣闊的使用在新的1.1 事件處理模型中。你肯定會在考試中遇到嵌套類范圍的問題。
這是一個簡單的例子
class Nest {class NetIn {}
}
這段代碼編譯后的輸出是兩個class 文件。如你所想的,第一個是Nest.class,另一個是Nest$NestIn.class。這說明了嵌套類通常只是個命名規(guī)范,而不是一種新的類文件。內(nèi)部類允許你邏輯性地組織類。它們在你希望訪問變量時也有廣泛的好處。
嵌套高層類嵌套高層類是一個包容高層類的靜態(tài)成員。
這樣,修改之前的簡單例子
class Nest {static class NestIn {}
}
這種類型的嵌套經(jīng)常用來簡單的組合相關(guān)的類。因為類是靜態(tài)的,它不需要在外部類實例存在的情況下才能實例化內(nèi)部類。
成員類我認為成員類是“普通內(nèi)部類”。成員類類似于類的其他成員,你必須在創(chuàng)建內(nèi)部類的實例之前首先實例化外部類。由于需要結(jié)合外部類的實例,Sun 引入了新的語法允許在創(chuàng)建內(nèi)部類的同時創(chuàng)建外部類的實例。這形成如下形式
Outer.Inner i = new Outer ().new Inner ();
為了弄清楚為此提供的新語法的意思,設(shè)法認為在上面例子中使用的關(guān)鍵字new 屬于
this 當前存在的實例中,因此,你可以將創(chuàng)建實例的代碼修改為
Inner i = this.new Inner ();
因為成員類無法脫離外部類的實例存在,它可以訪問外部類中的變量。
創(chuàng)建在方法中的類這種類更正確的叫法應(yīng)該是局部類,但是把它們當作創(chuàng)建在方法中,有助于讓你知道最有可能在什么地方遇到它們。局部類只在它的代碼塊或方法中可見。在局部類定義中的代碼只能使用包容塊中的final局部變量或方法的參數(shù)。你很有可能在考試中遇到這樣的題目。
匿名類你對于匿名內(nèi)部類的第一反應(yīng)可能是“你為什么要這么做,而且如果它沒有名字,你怎么能引用它呢?”要回答這些問題,請考慮下面的情形。你可能會遇到不停的為類實例捏造自我描述的名字的情況。這樣,對于事件處理,兩件需要了解的重要事情是等待處理的事件和處理器附屬的模塊的名字。為事件處理器實例取名字不會有多大價值。至于如果類沒有名字,如何引用該類的問題,你是做不到,而如果你需要通過名字來引用它,就不應(yīng)該創(chuàng)建匿名類。缺乏名字有另一個副作用,就是你不能為它設(shè)定任何構(gòu)造函數(shù)。這是一個創(chuàng)建匿名內(nèi)部類的例子
class Nest {public static void main (String argv []) {Nest n = new Nest ();n.mymethod (new anon () {});}public void mymethod (anon i) {}
}
class anon {}
請注意匿名內(nèi)部類是如何在mymethod 的調(diào)用的圓括號中同時聲明和定義的。
課后測試題
問題 1)下面哪些論述是正確的?
1)A class defined within a method can only access static methods of the enclosing method
2)A class defined within a method can only access final variables of the enclosing method
3)A class defined with a method cannot access any of the fields within the enclosing method
4)A class defined within a method can access any fields accessible by the enclosing method
問題 2)下面哪些論述是正確的?
1) An anonymous class cannot have any constructors
2) An anonymous class can only be created within the body of a method
3) An anonymous class can only access static fields of the enclosing class
4) The class type of an anonymous class can be retrieved using the getName method
問題 3)下面哪些論述是正確的?
1) Inner classes cannot be marked private
2) An instance of a top level nested class can be created without an instance of its enclosing class
3) A file containing an outer and an inner class will only produce one .class output file
4) To create an instance of an member class an instance of its enclosing class is required.
答案
答案 1)
2)A class defined within a method can only access final variables of the enclosing method
這種類可以訪問傳遞給包容方法的參數(shù)
答案 2)
1) An anonymous class cannot have any constructors
答案 3)
2) An instance of a top level nested class can be created without an instance of its enclosing
class
4)To create an instance of a member class an instance of its enclosing class is required.
內(nèi)部類會被放在它自己的.class 輸出文件中,使用格式Outer$Inner.class.
高層次嵌套類是一個靜態(tài)類,因而不需要包容類的實例。成員類是普通的非靜態(tài)類,因而需要有一個包容類的實例。
第7 章 線程
目標一 實例化和啟動線程通過使用java.lang.Thread 和 java.lang.Runnable 在代碼中定義,實例化,和啟動新線程。
什么是線程?線程是表面上看似和主程序并行運行的輕量級進程。與進程不同的是它與程序的其他部分共享存儲空間和數(shù)據(jù)。在這里線程的英文單詞thread 實際上是“thread of execution” 的縮寫,you might like to imagine a rope from which you have frayed the end and taken one thread.它依然是主線程的一部分,但它可以獨立出來,自己完成操作。這里請注意,啟動一個多線程的程序和僅僅啟動一個程序的多個同一程序是有區(qū)別的,因為一個多線程的程序?qū)y(tǒng)一程序內(nèi)的數(shù)據(jù)進行讀取和存儲。一個可以顯示多線程用處的例子就是打印,當你按下打印按鈕的時候,你肯定不希望主程序直到打印完成才開始響應(yīng)。最棒的就是你可以讓打印進程“在后臺”悄悄的運行,同時你可以使用主程序的其他部分。而且當打印線程出現(xiàn)故障的時候主程序可以對此做出響應(yīng),一個講解多線程最佳的通用例子就是創(chuàng)建一個每當你按下按鈕的時候彈出一個彈球的圖形用戶界面程序。因為現(xiàn)在處理器速度快的原因,導致表面上看每個線程似乎是獨享CPU,這是由于處理器在各個線程之間的切換速度很快,控制彈球跳動的代碼更像是在處理器上運行唯一一個程序。不像大部分程序那樣,線程并不是嵌入到Java 語言的最核心部分,它的大部分,依然是繼承自最原始的類——Object,舊一點的語言如C/C++并沒有為線程設(shè)定標準。當你在為Java 程序員認證考試學習的時候,你必須要對當一個程序啟動一個新線程有一定認識,這個時候程序不再是在單一的路徑上執(zhí)行。因為當一個線程A 先于線程B 執(zhí)行,并不意味著線程A 一定會比線程B 先結(jié)束,當然線程B 也不一定是在線程A 結(jié)束后才開始運行。因此你有可能會遇到這樣的問題,如“最有可能輸出以下哪段代碼?”,最準確的輸出結(jié)果決定于底層的操作系統(tǒng)和同一時間正在一起運行的其它程序。正是因為一個多線程程序在你的機器操作系統(tǒng)的組合上產(chǎn)生一個特定的輸出,因此它不能保證在其他不同的系統(tǒng)上也能有同樣的輸出結(jié)果。出考題的人會憑空假設(shè)程序在一個更加通用的平臺上運行的(如Windows),考題可以考察底層操作系統(tǒng)對Java 線程的影響。不要只把注意力放在考試的關(guān)于線程的考點上,因為它僅僅是考察你對一小部分線程知識是否掌握的很牢固。有很多線程相關(guān)的概念考試并沒有覆蓋到,如果你僅僅是為考試做打算,那么你可以對線程組,線程池,線程優(yōu)先級等概念不做了解。當然,對這些概念的了解對更深層次領(lǐng)會Java 語言編程是有好處的,如果你只想把精力集中在應(yīng)付考試的考點上,那么你只需要對該指南上列出考點進行學習就行了。
兩種創(chuàng)建線程的方式在這兩種方法中,使用Runnable 似乎更常見一些,但是出于考試的原因,你必須對這兩種方法都了解。下面這種方法就是讓對象在創(chuàng)建的時候,用實現(xiàn)Runnable 接口的方法來實現(xiàn)創(chuàng)建線程。
class MyClass implements Runnable{public void run(){//Blank Body}
}
創(chuàng)建兩個線程并運行它們。
MyClass mc = new MyClass();
MyClass mc2 = new MyClass();
Thread t = new Thread(mc);
Thread t2 = new Thread(mc2);
t.start();
t2.start();這里需要注意的是,線程 t 并不肯定是比線程 t2 先結(jié)束運行,當然由于run()方法里面并沒有任何代碼,因此線程 t 很有可能比線程 t2 先結(jié)束運行。即使你讓該段代碼在你的機器上運行上千次,無法改變它最終的運行結(jié)果,當然無法保證在其他的系統(tǒng)上運行結(jié)果也是一樣的,或者當你更改系統(tǒng)的環(huán)境配置時,也有可能發(fā)生變化。注意到在用實現(xiàn)Runnble 接口的方法來創(chuàng)建線程時,必須要求創(chuàng)建一個Thread 對象的實例,并且必須要在創(chuàng)建的時候,把實現(xiàn)該Runnable 接口的對象作為構(gòu)造方法的參數(shù)傳遞進去。任何一個類在它實現(xiàn)一個接口的時候,它必須同時要創(chuàng)建該接口中已經(jīng)定義的方法。該方法不一定非要有任何意義,比如,方法里面的內(nèi)容可以為空,但是它必須在這個類的內(nèi)部出現(xiàn)。因此在上面那個例子里出現(xiàn)了空的run()方法,不包含run()將會導致程序在編譯期報錯。當你需要在一個類里面創(chuàng)建一個有某種用途的線程的時候,你需要在我上面的
//Blank Body
部分寫一些東西進去。另外一種創(chuàng)建線程的方法就是直接使類繼承自Thread。這樣做非常簡單,但同時你也無法再繼承其他的對象,因為Java 只支持單繼承。因此當你創(chuàng)建一個Button 對象的時候你無法使用這種方法來添加線程的功能,因為它已經(jīng)是繼承了AWT Button 這個類的,這使你不得不在繼承方面動一點腦筋。不過一些反對的聲音認為這種創(chuàng)建線程的方法更符合面向?qū)ο蟮乃枷搿5还茉趺凑f,你必須為了Java 考試對這種方法有一定了解。
實例化和啟動一個線程盡管在線程中運行的方法是run(),你并不需要調(diào)用這個方法來啟動一個線程,而是調(diào)用start() 方法來啟動一個線程。這點很關(guān)鍵,因為它極有可能在考試中出現(xiàn)。這點很有可能讓你栽跟頭,因為這和大多數(shù)往常你所遇見的Java 編程的慣例不一樣。通常如果你會把一段代碼放到一個方法里面去,當你需要執(zhí)行它的時候,你只需要調(diào)用該方法。如果你直接調(diào)用run()方法,當然這也不會造成什么錯誤,只是它會象一個普通的方法那樣運行,而不是作為線程的一部分來執(zhí)行。Runnable 接口并不包括start()方法,同樣也不包含其它一些線程中有用的方法(如sleep(),suspend()等等),你只需將你已經(jīng)實現(xiàn)了Runnable 接口的對象作為一個構(gòu)造方法參數(shù)傳遞給一個已經(jīng)實例化的Thread 對象。當你需要一個已經(jīng)實現(xiàn)了Runnable 接口的對象執(zhí)行多線程任務(wù)的時候,你需要使用以下的代碼。
MyClass mc = new MyClass();
Thread t = new Thread(mc);
t.start();
盡管是run 方法在運行, 但一個線程的啟動是調(diào)用start方法。再一次強調(diào)你不是調(diào)用run()方法,而是start()方法來啟動一個線程,盡管在run()方法里面的代碼才是線程執(zhí)行的時候所運行的。如果你的對象是繼承Thread,你可以簡單的調(diào)用start()方法來啟動它。缺點就是Thread的子類因為單繼承的原因再無法繼承其它功能的類。
練習題
習題1)當你試圖編譯運行下列代碼的時候會發(fā)生什么?
public class Runt implements Runnable{public static void main(String argv[]){Runt r = new Runt();Thread t = new Thread(r);t.start();}public void start(){for(int i=0;i<100;i++)System.out.println(i);}
}
1) Compilation and output of count from 0 to 99
2) Compilation and no output
3) Compile time error: class Runt is an abstract class. It can't
習題2)下列哪一項表述是正確的?
Which of the following statements are true?
1) Directly sub classing Thread gives you access to more functionality of the Java threading capability than using the Runnable interface
2) Using the Runnable interface means you do not have to create an instance of the Thread class and can call run directly
3) Both using the Runnable interface and subclassing of Thread require calling start to begin execution of a Thread
4) The Runnable interface requires only one method to be implemented, this is called run
習題3)當你試圖編譯運行下列代碼的時候會發(fā)生什么?
public class Runt extends Thread{public static void main(String argv[]){Runt r = new Runt();r.run();}public void run(){for(int i=0;i<100;i++)System.out.println(i);}
}
1) Compilation and output of count from 0 to 99
2) Compilation and no output
3) Compile time error: class Runt is an abstract class. It can't be instantiated.
4) Compile time error, method start has not been defined
習題4)下列哪一項表述是正確的?
1)To implement threading in a program you must import the class java.io.Thread
2) The code that actually runs when you start a thread is placed in the run method
3) Threads may share data between one another
4) To start a Thread executing you call the start method and not the run method
習題5) 下列哪一項是讓線程開始運行的正確代碼?
1)
public class TStart extends Thread{public static void main(String argv[]){TStart ts = new TStart();ts.start();}public void run(){System.out.println("Thread starting");}
}
2)
public class TStart extends Runnable{public static void main(String argv[]){TStart ts = new TStart();ts.start();}public void run(){System.out.println("Thread starting");}
}
3)
public class TStart extends Thread{public static void main(String argv[]){TStart ts = new TStart();ts.start();}public void start(){System.out.println("Thread starting");}
}
4)
public class TStart extends Thread{public static void main(String argv[]){TStart ts = new TStart();ts.run();}public void run(){System.out.println("Thread starting");}
}
答案
答案 1)
3) Compile time error: class Runt is an abstract class.它不能被實例化.
這個類實現(xiàn)了Runnable 接口,但是沒有定義run()方法。
答案 2)
3)不管是繼承Thread 對象還是實現(xiàn)Runnable 接口,都要使用start()方法來讓該線程開始運行。
4) 實現(xiàn)Runnable 接口只需要定義一個run()的方法。
答案 3)
1) 編譯輸出從0-99。
盡管如此,注意到這段代碼并沒有讓線程運行,run()方法不應(yīng)該這樣被調(diào)用。
答案 4)
2)當你讓線程跑起來的時候運行的實際上是run()方法里面的代碼。
3) 線程之間可以彼此共享數(shù)據(jù)信息。
4) 當你需要一個線程開始運行的時候調(diào)用的是start()方法而不是run()方法。你不需要導入額外的類,因為線程是Java 語言的一部分。
答案 5)
1) 僅選項1 是一個有效的方式開始一個新的線程執(zhí)行。 選項2 的代碼繼承Runnable 接口但沒意義,因為Runnable 是接口不是類,接口使用implements 關(guān)鍵字。 選項3 的代碼直接地調(diào)用起動方法。 如果您運行這個代碼您將發(fā)現(xiàn)文本輸出,但由于直接調(diào)用方法,并不是因為一個新的線程在運行。 選項4 也一樣,直接地調(diào)用運行線程僅是另一個方法,并且象其他的一樣執(zhí)行。
目標二 何時線程會被阻止運行
對什么情況下線程會被阻止運行有一定認識。
關(guān)于該目標的解釋“線程會被阻止運行”的表述看上去很籠統(tǒng),它的意思是該線程已經(jīng)被人為的暫停?還是這個線程已經(jīng)被徹底銷毀?其實“線程會被阻止運行”的意思是線程被阻塞了。
可能造成線程阻塞的原因
線程阻塞的原因可能是
1) 線程已經(jīng)被設(shè)置了一定長度的睡眠時間。
2) 調(diào)用了suspend()方法,它將一直保持阻塞直到resume()方法被調(diào)用。
3) 該線程因為被調(diào)用了wait()方法被暫停了,當收到notify 或者notifyAll 消息的時候該線程會重新被激活。
出于對付考試的原因,sleep(),notify 和notifyAll()是這些造成線程組塞的原因非常需要掌握的。sleep()方法是一個靜態(tài)的可以暫停線程一定毫秒時間長度的方法。還有一個版本可以支持設(shè)定睡眠的時間單位為十億分之一秒的版本。我認為沒有多少人會在有如此精確的機器或者實現(xiàn)Java 的平臺上進行工作。下面是一個展示線程如何進入睡眠狀態(tài)的例子,注意這個sleep()方法是如何拋出InterruptedException 異常的。
public class TSleep extends Thread{public static void main(String argv[]){TSleep t = new TSleep();t.start();}public void run(){try{while(true){this.sleep(1000);System.out.println("looping while");}}catch(InterruptedException ie){}}
}當Java2 版本發(fā)布的時候,Thread 類里面的stop(),suspend()和resume()方法已經(jīng)被認為是過時的了(不提倡繼續(xù)再使用,并且在編譯期會報出警告提示)。同時JDK 文檔認為//Quote這種方式因為它固有的造成死鎖可能的原因也不再提倡使用了。當目標線程正在所鎖定保護系統(tǒng)的臨界資源的監(jiān)視器時候因為被暫停而保持阻塞狀態(tài),其他線程將不能再讀寫該臨界資源直到該目標線程解除死鎖狀態(tài)。如果一個線程解除該目標線程的組塞,而同時又試圖在調(diào)用resume()之前保持該監(jiān)視器的鎖定狀態(tài),那么將會造成一個死鎖。這樣的死鎖具有代表性,就像”frozen”進程一樣。需要更多的信息請參考為什么Thread.stop, Thread.suspend 和Thread.resume 不提倡再被繼續(xù)使用的原因。//End Quote線程的通過wait/notify 的協(xié)議來進行阻塞操作將在下一個目標中進行表述。
使用Thread 包中的yield 方法由于Java 線程對平臺依賴的本質(zhì),你不能保證一個線程會把對CPU 資源的使用權(quán)移交給另一個線程。某些操作系統(tǒng)的線程調(diào)度規(guī)則會自動給不同的線程分配CPU 的占有時間。而另一些操作系統(tǒng)則僅僅是讓線程獨享處理器資源。因為上述原因,Java 的Thread 包里面構(gòu)造了一個靜態(tài)的名叫yield()的方法可以讓當前正在運行狀態(tài)的線程讓出正在占用的CPU周期。該進程則返回“準備運行”狀態(tài),這樣線程規(guī)劃系統(tǒng)可以有機會讓其他線程進來調(diào)用CPU 資源運行。如果沒有其他的線程在“準備運行”運行狀態(tài),則剛剛讓出CPU 資源的線程馬上重新恢復到運行狀態(tài)。
限制/搶占每一個線程都有一個設(shè)定好的CPU 占用周期來運行。一旦它用完了設(shè)定好的一個CPU占用周期時間,那么它將停止占用CPU 資源以讓其他正在等待中的線程獲得機會運行。當一個線程進入之前設(shè)定好的CPU 占用時間那么它的一個新的運行周期就又開始了。這種機制的好處就在于你可以讓所有的線程都跑起來而花費最少的時間。
沒有時間 限制/共享優(yōu)先級系統(tǒng)將會決定哪個線程將會運行。一個相對來說最高優(yōu)先級的線程將會獲得時間來運行。一段運行在該系統(tǒng)中的程序必須使自己能夠自動地讓每個線程讓出CPU 資源的占用,讓所有線程共享CPU 資源。
Java 線程的優(yōu)先級Java 考試并不認為你需要對系統(tǒng)如何設(shè)置線程的優(yōu)先級。盡管知道這些機制是非常有的。同時這樣的局限性讓你意識到Thread 包中的yield()方法的重要性是非常有用的。你可以通過Thread 包中Thread.setPriority 來設(shè)置線程的優(yōu)先級,你可以通過getPriority 來獲得線程的優(yōu)先級,一個新建線程的默認優(yōu)先級是Thread.NORM_PRIORITY。
練習題
習題1)當你試圖編譯運行下列代碼的時候會發(fā)生什么?
public class TGo implements Runnable{public static void main(String argv[]){TGo tg = new TGo();Thread t = new Thread(tg);t.start();}public void run(){while(true){Thread.currentThread().sleep(1000);System.out.println("looping while");}}
}
1) Compilation and no output
2) Compilation and repeated output of "looping while"
3) Compilation and single output of "looping while"
4) Compile time error
習題2)下面哪種方式是推薦的讓線程阻塞的方式?
1) sleep()
2) wait/notify
3) suspend
4) pause
習題3)下列哪一項表述是正確的?
1) The sleep method takes parameters of the Thread and the number of seconds it should sleep
2) The sleep method takes a single parameter that indicates the number of seconds it should sleep
3) The sleep method takes a single parameter that indicates the number of milliseconds it should sleep
4) The sleep method is a static member of the Thread class
習題4)下列哪一項表述是正確的?
Which of the following statements are true?
1) A higher priority Thread will prevent a lower priorty Thread from getting any access to the CPU.
2) The yield method only allows any higher priority priority thread to execute.
3) The Thread class has a static method called yield
4) Calling yield with an integer parameter causes it to yield for a specific time
練習題答案
答案1)
Compile time error
sleep()方法將會拋出InterruptedException 異常。除非讓這個代碼段放到try/catch 塊里面去,否則這段代碼將無法編譯。
答案2)
1) sleep()
2) wait/notify
Java2 版本里面suspend()方法已不推薦再繼續(xù)使用。
答案3)
3) sleep()方法只需要一個表示線程睡眠時間長度的參數(shù)。
4) sleep()方法是Thread 類里面的一個靜態(tài)方法。
答案4)
線程類有一個靜態(tài)方法yield,調(diào)用它可以允許任何等待的線程按照底層操作系統(tǒng)的計劃安排執(zhí)行.沒有帶一個整數(shù)型參數(shù)的yield 方法.是否高優(yōu)先級的線程比低優(yōu)先級能獲得更多的CPU 時間與平臺有關(guān),并不確定。
目標三 何時線程會被阻止運行編寫代碼的時候使用同步的wait ,notify 和 notifyAll 方法,以防止并行讀取問題的發(fā)生,同時保證各個線程之間的正常通信。當執(zhí)行同步的wait,notify 和notifyAll 方法的時候?qū)€程和線程之間以及線程和對象鎖之間的內(nèi)部交互進行定義。
為什么你需要wait/notify 法則?一個更容易理解的方式,比如你想象一下數(shù)據(jù)庫中的一條整型的變量數(shù)據(jù),如果你沒有一些鎖定數(shù)據(jù)的措施的話,你將會面臨數(shù)據(jù)污染的危險。這樣一個用戶可以將這條數(shù)據(jù)取出來,經(jīng)過一定運算后再放回去。期間如果其他的用戶也將該數(shù)據(jù)取出來進行運算后返回,那么第一個用戶運算后返回的數(shù)據(jù)將會失效。就像數(shù)據(jù)庫在任何事先不可知的情況下要處理更新一樣,所以一個多線程程序必須要有應(yīng)付這種可能性的機制。為了考試,你十分有必要對本目標的內(nèi)容進行研究,一些十分有經(jīng)驗的Java 程序員wait/notify 法則也并不是十分了解,這是一個普遍現(xiàn)象,強烈建議讀者寫一些簡單的程序來熟悉這個法則,并對后面的模仿考試的練習題進行針對性的練習。
一個銀行/帳戶 的例子下面的代碼講解了同步的線程之間對同一個數(shù)據(jù)進行操作。它一個名叫bank 的類,它主要用來驅(qū)動多個運行著Business 類中的數(shù)據(jù)處理方法的線程。Bussiness 線程實際上就是對 Account 里面的金額進行加減操作。下面代碼的思想展示了多線程是如何“踩到對方的腳”并造成數(shù)據(jù)污染的,但是是有代碼可以阻止這類事情發(fā)生的。為了“修復”已經(jīng)存在的這個數(shù)據(jù)污染我調(diào)用了sleep()方法,你可以認為是等同于暫停,當bank 中有代碼寫入操作數(shù)據(jù)庫的時候。如果沒有調(diào)用這個sleep()方法,數(shù)據(jù)污染發(fā)生的可能性就依然存在。你不得不運行很多次程序,這樣才能讓這個毛病顯現(xiàn)出來。
public class Account{private int iBalance;public void add(int i){iBalance = iBalance + i;System.out.println("adding " +i +" Balance = "+ iBalance);}public void withdraw(int i){if((iBalance - i) >0 ){try{Thread.sleep(60);}catch(InterruptedException ie){}iBalance = iBalance - i;}else{System.out.println("Cannot withdraw, funds would be < 0");}if(iBalance < 0){System.out.println("Woops, funds below 0");System.exit(0);}System.out.println("withdrawing " + i+ " Balance = " +iBalance);}public int getBalance(){return iBalance;}
}
關(guān)鍵字synchronized關(guān)鍵字synchronized 可以用在標記一段聲明或者鎖定一段代碼,保證在同一時間只有一個線程能夠運行它的一個實例。進入這段代碼將會受到負責監(jiān)視它的監(jiān)視器的保護。這個過程是由一個鎖定系統(tǒng)實現(xiàn)的。你也可以看到用監(jiān)控,或者使用互斥來形容(互不相關(guān))。一個鎖分配給一個對象以保證同一時間只能有一個線程的進入,因此當一個線程試圖進入的時候必須試圖獲得這個鎖的許可。其它的線程將無法進入這段代碼,知道第一個進入的線程完成然后釋放這個鎖。請注意的是這里的鎖是基于對象而不是基于方法的。
關(guān)鍵字synchronized 放在方法的名字之前,如:
synchronized void amethod() { /* method body */}
關(guān)鍵字synchronized 也可放在代碼段的括號之前,如:
synchronized (ObjectReference) { /* Block body */ }
注釋的部分是指對象或者類的里面某段需要監(jiān)視器需要鎖定的部分。大部分情況下我們使用的是前者,而不是后者。
當一個被關(guān)鍵字synchronized 標記的代碼開始執(zhí)行以后,擁有它的對象將保持鎖定狀
態(tài),它將不能被調(diào)用直到鎖定狀態(tài)被解除。
synchronized void first();
synchronized void second();
有更好的辦法比在一個代碼塊前加上關(guān)鍵字synchronized能獲得串行化的好處,它必須用于聯(lián)接管理可串行化代碼鎖的代碼 。
wait/notify除了鎖可以獲得和釋放以外,每個對象都會暫停或者進入等待當其它的線程獲得這個鎖的時候。這使得線程之間溝通情況隨時運行。由于Java 語言的單繼承性,每一個子類都是繼承自最原始的Object 對象,從它那獲得這個線程級的通信能力。
wait 和notify 應(yīng)該放在Synchronized 關(guān)鍵字標記的代碼中以保證當前的代碼在監(jiān)視器的監(jiān)控之中。在一個標記為synchronized 的代碼中調(diào)用wait()方法,會造成運行這段代碼的這個線程交出鎖的權(quán)限并進入睡眠狀態(tài)。這種情況通常是為了其他的線程來接管這個鎖以進行下一步操作。如果沒有讓線程喚醒并重新進入運行狀態(tài)的notify()或者notifyAll()方法的話,那么
wait()方法也變得毫無意義。一個典型的使用wait()/notify()法則來讓線程之間進行通信的例子,看上去它似乎陷入了死循環(huán)。
//producing code
while(true){try{wait();}catch (InterruptedException e) {}
}
//some producing action goes here
notifyAll();
如果真的是這樣,那這段代碼真的是垃圾。當你第一眼看到這段代碼的時候你會感覺它會一直這樣運行下去。其實wait()會告訴它交出鎖讓其它線程運行,直到你調(diào)用了notify 或者notifyAll 方法。線程調(diào)度不是獨立的,不能依靠虛擬機讓它用同一種方式
運行。不象Java 的大部分特性,線程在不同的平臺上會有不同的表現(xiàn)。這兩點分別是線程的優(yōu)先級和線程的調(diào)度。線程調(diào)度的兩種途徑是:搶占和時間片在一個可以進行搶占的系統(tǒng)上,程序可以通過搶占來獲得CPU 獨享周期。在一個實行時間片分配的系統(tǒng)上,每個線程都會獲得一個CPU 獨享周期,然后進入準備運行狀態(tài)。這樣可以確保不會讓一個線程一直獨享CPU。缺點在于你無法預測這個線程會運行多長時間才會結(jié)束,也無法預測什么時候這個線程會再運行,因此通常建議你使用notify 或者notifyAll方法。盡管Java 把線程的優(yōu)先級按1-10 從低到高來分配。一些平臺能夠識別這個優(yōu)先級的屬性,但其它的卻不能。notify 方法會喚醒一個線程讓它進入重新要求獲得某對象的監(jiān)控權(quán)限。你不能確定哪個線程被喚醒了。如果你只是有一個線程被喚醒了當然不會存在這種問題。如果你有很多線程等待喚醒,那么等待最長時間的那個將被喚醒。盡管如此,你依然不能確定,線程的優(yōu)先級對最后結(jié)果也有影響。因此一般推薦你使用notifyAll 而不是notify,不要對線程的優(yōu)先級和調(diào)度進行任何假設(shè)。你可能要讓你的代碼在盡量多的平臺上運行來測試一下,當然,并不總是這樣的。
練習題
問題 1)下列哪一個關(guān)鍵字表示該線程放棄了該對象的鎖?
1) release
2) wait
3) continue
4) notifyAll
問題 2)下列哪一是關(guān)于關(guān)鍵字synchronized 的表述是最合適的?
1) Allows more than one Thread to access a method simultaneously
2) Allows more than one Thread to obtain the Object lock on a reference
3) Gives the notify/notifyAll keywords exclusive access to the monitor
4) Means only one thread at a time can access a method or block of code
問題 3)當你試圖編譯運行下列代碼的時候會發(fā)生什么?
public class WaNot{int i=0;public static void main(String argv[]){WaNot w = new WaNot();w.amethod();}public void amethod(){while(true){try{wait();}catch (InterruptedException e) {}i++;}//End of while}//End of amethod
}//End of class
1)Compile time error, no matching notify within the method
2)Compile and run but an infinite looping of the while method
3)Compilation and run
4)Runtime Exception "IllegalMonitorStatException"
問題 4)你如何使用wait/notify 法則指定某個線程被喚醒?
1) Pass the object reference as a parameter to the notify method
2) Pass the method name as a parameter to the notify method
3) Use the notifyAll method and pass the object reference as a parameter
4) None of the above
問題 5)下列哪項表述是正確的?
1) Java uses a time-slicing scheduling system for determining which Thread will execute
2) Java uses a pre-emptive, co-operative system for determining which Thread will execute
3) Java scheduling is platform dependent and may vary from one implementation to another
4) You can set the priority of a Thread in code
答案
答案 1)
Wait
答案 2)
以為著在同一時間內(nèi)只有一個線程對該代碼段或方法進行操作。
答案 3)
Runtime Exception “IllegalMonitorStateException”
wait/notify 法則只能在被標記為synchronized 的代碼段里面使用,在該題這種情況下調(diào)用代碼會拋出異常。
答案 4)
4) None of the above.
wait/notify 法則沒有為哪個線程將被激活提供方法。
答案 5)
3) Java 的調(diào)度平臺不是獨立的,它最終的實現(xiàn)結(jié)果是不確定的。
4) 你可以在代碼中為代碼設(shè)定優(yōu)先級。
第8 章 Java 的lang 包
目標一 Math 類中的方法在開發(fā)過程中運用java.lang.Math 中的如下方法:abs ,ceil ,floor ,max ,min ,random ,round ,sin, cos, tan, sqrt。
本節(jié)需要注意的問題Math 類是不可被繼承的,它里面的方法都是靜態(tài)的。這或許是好事,因為它降低了了混亂情況發(fā)生的可能性。在這一塊你幾乎肯定會遇到問題,如果僅僅是因為你忽視它們而造成錯誤的發(fā)生,那將非常遺憾。
abs因為我薄弱的數(shù)學基礎(chǔ),一開始我?guī)缀鯇θ绾问褂胊bs()這個方法的用法一無所知,直到為了通過Java 程序員考試,我才開始認真學習弄懂它。它的作用是對一個數(shù)值進行取絕對值操作。因此下面的那段代碼打印出來的數(shù)字是99。如果進行操作的數(shù)是一個非負數(shù),它會原樣返回。
System.out.println(Math.abs(-99));
ceil這個方法返回的是比被操作數(shù)大的最小double 值。比如下面這個例子:
ceil(1.1)
它將返回2.0
如果你換成
ceil(-1.1)
它將返回 -1.0;
floor參考一下JDK 的說明文檔,該方法返回的是:返回最大的(最接近正無窮大)double 值,該值小于或等于參數(shù),并且等于某個整數(shù)。如果覺得這表達的不夠清楚,那么我們可以看一下,下面那一小段代碼和它的輸出情況:
public class MyMat{public static void main(String[] argv){System.out.println(Math.floor(-99.1));System.out.println(Math.floor(-99));System.out.println(Math.floor(99));System.out.println(Math.floor(-.01));System.out.println(Math.floor(0.1));}
}
它的輸出是:
-100.0
-99.0
99.0
-1.0
0.0
max 和 min注意一下這兩個方法需要兩個參數(shù)。你可能會有疑問,如果僅僅傳遞給它們一個參數(shù)會發(fā)生錯誤。你可以把這兩個方法看成是:“這兩個數(shù)里面哪一個是最大的?”以下的代碼演示了這兩個方法是如何工作的:
class MaxMin{public static void main(String argv[]){System.out.println(Math.max(-1,-10));System.out.println(Math.max(1,2));System.out.println(Math.min(1,1));System.out.println(Math.min(-1,-10));System.out.println(Math.min(1,2));}
}
下面是輸出的結(jié)果:
-1
2
1
-10
1
random該方法返回的是一個0.0 到1.0 之間的隨機數(shù)。不像一些隨機數(shù)系統(tǒng),Java 似乎并不支持提供種子數(shù)來增加隨機性。這個方法可以用下面的方法來生成0 到100 之間的隨機數(shù)。從考試的角度來說,這個“返回一個0.0 到1.0 之間的隨機數(shù)”的知識點是很重要的。因此下面的幾個數(shù)字是可能輸出結(jié)果:
0.9151633320773057
0.25135231957619386
0.10070205341831895經(jīng)常會遇到需要程序生成0 到10 之間或者0 到100 之間的隨機數(shù)的情況。下面這行代碼就是演示如何生成0 到100 之間的隨機數(shù):
System.out.println(Math.round(Math.random()*100));
round返回最接近參數(shù)的一個整型數(shù)。如果小數(shù)部分大于0.5 則返回下一個相對最小整數(shù),如果小數(shù)部分小于等于0.5 則返回上一個相對最大整數(shù)。如下例所示:
2.0 <=x < 2.5. then Math.round(x)==2.0
2.5 <=x < 3.0 the Math.round(x)==3.0
以下是一些例子和它們的輸出:
System.out.println(Math.round(1.01));
System.out.println(Math.round(-2.1));
System.out.println(Math.round(20));
1
-2
20
sin cos tan這三個方便快捷的方法都只需要一個 double 型的參數(shù),它們的功能和其他語言里面的方法功能是一樣的。在我12 年的編程工作中,我還從未使用過它們。可能需要記憶的僅僅是參數(shù)類型是double 型的。
sqrt返回該參數(shù)的double 型平方根。
總結(jié)
max 和min 方法需要兩個參數(shù)。
random 方法返回的數(shù)值在0 到1 之間。
abs 返回的是絕對值。
round 返回最接近參數(shù)的整型數(shù),但保留符號位。
練習題
習題 1) 下列哪個選項將會編譯正確?
1) System.out.println(Math.max(x));
2) System.out.println(Math.random(10,3));
3) System.out.println(Math.round(20));
4) System.out.println(Math.sqrt(10));
習題 2)下列哪個選項將會輸出1 到10 之間的隨機數(shù)?
1) System.out.println(Math.round(Math.random()* 10));
2) System.out.println(Math.round(Math.random() % 10));
3) System.out.println(Math.random() *10);
4) None of the above
習題 3)寫面一行代碼將會輸出什么?
System.out.println(Math.floor(-2.1));
1) -2
2) 2.0
3) -3
4) -3.0
習題 4)寫面一行代碼將會輸出什么?
System.out.println(Math.abs(-2.1));
1) -2.0
2) -2.1
3) 2.1
4) 1.0
習題 5)寫面一行代碼將會輸出什么?
System.out.println(Math.ceil(-2.1));
1) -2.0
2) -2.1
3) 2.1
3) 1.0
習題 6)當你試圖編譯下列代碼時將會發(fā)生什么?
class MyCalc extends Math{public int random(){double iTemp;iTemp=super();return super.round(iTemp);}
}
public class MyRand{public static void main(String argv[]){MyCalc m = new MyCals();System.out.println(m.random());}
}
1) Compile time error
2) Run time error
3) Output of a random number between 0 and 1
4) Output of a random number between 1 and 10
答案
答案1)
3) System.out.println(Math.round(20));
4) System.out.println(Math.sqrt(10));
選項1 錯誤是因為max 方法只需要一個參數(shù),而選項2 錯誤是因為random 方法只不需要參數(shù)。
答案2)
4) None of the above
最接近正確答案的是選項1,但是請別忘記一個細節(jié)就是,random 方法返回的數(shù)據(jù)中包括0,而題目問的是1 到10
答案3)
4) -3.0
答案4)
3) 2.1
答案5)
1) -2.0
答案6)
1) Compile time error
Math 類是不可被繼承的。這段代碼有一些低級的錯誤。你只能在構(gòu)造方法里面使用super,而它卻是在random 方法里面使用。
目標二 Strings 的不變性描述string 對象不變性的重要性。String 類的不變性理論說明, string 對象一旦被創(chuàng)建,它就決不能被改變。Java 編程的一些經(jīng)歷意味著似乎并不如此。
如下面的代碼所示:
public class ImString{public static void main(String argv[]){String s1 = new String("Hello");String s2 = new String("There");System.out.println(s1);s1=s2;System.out.println(s1);}
}
如果Strings 不能被改變,那么s1 應(yīng)該仍然打印出Hello,但是你如果運行這個程序段,你會發(fā)現(xiàn)第二次輸出的字符串是“There”,這是為什么呢?不變性實際上指的是字符串指針所指向的內(nèi)容。在例子中,將s2 賦給s1,字符串池中“Hello”字符串不再被指向,s1 現(xiàn)在和s2 指向同一個字符串。事實上“Hello”字符串沒有被修改,理論上,你不能再獲取到它了。這個目標要求你認清strings 的不變性,如果你想要改變字符串的內(nèi)容的話,主要的方法就是采用StringBuffer 類。因為在后臺實例化時,字符串連接會產(chǎn)生一個新的字符串,所以當你的操作大量的字符串時,比如從讀取一個大的文本文件時,性能就很重要了。通常字符串不變性并不影響每天的編程,但是在考試中它經(jīng)常被考到。記住不論怎么考,字符串一旦被創(chuàng)建,它本身就不會改變,即使指向它的指針指到別的字符串了。如果允許同一字符串再生,這就涉及到字符串在字符串池中的創(chuàng)建方式了。5.2 節(jié)在講解在使用strings 時=與equal 的作用時,將這個內(nèi)容作為一個部分涉及到了。雖然Java2 和Java1.1 都沒有特別到這個內(nèi)容,但是我認為一些問題的回答需要StrngBuffer 的內(nèi)容。
練習題
習題1)
已經(jīng)創(chuàng)建了兩個包含姓名的字符串,即:
String fname="John";
String lname="String"
你如果在同一個代碼塊中,改變這些字符串的值?
1)
fname="Fred";
lname="Jones";
2)
String fname=new String("Fred");
String lname=new String("Jones");
3)
StringBuffer fname=new StringBuffer(fname);
StringBuffer lname=new StringBuffer(lname);
4) 以上都不正確
習題2)
假如你寫了一個程序用于讀取8MB 的文本文件。一行一行的讀到一個String 對象中,但是你發(fā)現(xiàn)執(zhí)行性能不好。最可能的解釋是?
1) Java I/O 是圍繞最慢的設(shè)備而設(shè)計的,它本身就很慢
2) String 類不適合I/O 操作,字符數(shù)組將更合適
3) 因為String 的不變性,每一次讀要創(chuàng)建一個新的String 對象,改為StringBuffer 可能會提高性能
4) 以上都不正確
答案
答案1)
4)以上都不正確
一旦創(chuàng)建了一個String 對象,它就只能讀不能改變
答案2)
3)因為String 的不變性,每一次讀要創(chuàng)建一個新的String 對象,改為StringBuffer 可能會提高性能。我希望你們都不會像C 程序員那樣采用一個字符數(shù)據(jù)?
目標三 包裝類
本目標主要討論包裝類的重要性,包括因為特定的需求選擇最合適的包裝類。講述當一個包裝類的的實例代碼片段運行回產(chǎn)生什么結(jié)果。DoubleValue, floatValue,longValue,parseXxx,getXxx,toString,toHexString 等等。
本節(jié)需要注意的問題該目標的知識點明確在JDK1.4 版本的考試中有明確規(guī)定,如果你看過以前的舊模擬題,你肯定不會看見里面包含本目標中的知識。因為在實際開發(fā)過程中你經(jīng)常會用到本目標中的內(nèi)容,所以學習起來會很容易。要特別仔細的學習這些知識點,你將會在真題庫中看到它們的影子。
什么是包裝類Java 中的基本類型的包裝類提供了大量非常有用的公用方法。比如你需要往一個vetor里面存儲一列整型數(shù)據(jù),而Vetor 里面存儲的對象類型必須是Object 而不是基本類型數(shù)據(jù),當你從Vetor 中將這些對象再取出來的時候,你得要用相應(yīng)基本類型的包裝類中的toxxValue公用方法來將對象強制轉(zhuǎn)換成相應(yīng)的基本類型的數(shù)據(jù)。下面的代碼講述了這種技巧:
import java.util.*;
public class VecNum{public static void main(String argv[]){Vector v = new Vector();v.add(new Integer(1));v.add(new Integer(2));for(int i=0; i < v.size();i ++){Integer iw =(Integer) v.get(i);System.out.println(iw.intValue());}}
}包裝類提供了該對象與整形數(shù)據(jù)之間相互轉(zhuǎn)換的公用方法,因此當你有一個String 型的數(shù)據(jù)當你需要將它轉(zhuǎn)換成它所代表的整型數(shù)據(jù)的時候,你可以使用包裝類來完成這一系列操作。包裝類中提供的公用方法是靜態(tài)的,所以你不需要實例化一個包裝類的對象再對它里面的方法進行調(diào)用。當你對一個包裝類賦值以后,你將不能再改邊它。如果你在考試中遇到諸如Integer.setInt(int i)的表述,不用多想,這種方法是不存在的,它是錯誤的。
公用方法一個最有用的包裝類的公用方法是一些諸如parseXX 的方法,它的作用是把一個String型的數(shù)據(jù)轉(zhuǎn)換成一個它所對應(yīng)的基本類型數(shù)據(jù)。XX 代表包裝類所能包括的數(shù)據(jù)類型。它包括parseInt, parseLong, parseShort, parseCharacter, parseBoolean。如果你在一個WEB 頁面里面有一個字段代表一個數(shù)據(jù)類型,如果你有一個里面包含一個表格字段的WEB頁面,返回的一個String型數(shù)據(jù)可以轉(zhuǎn)化成一個數(shù)值。因此該字段可能包含"101"或者"elephant"。你可以試著用包裝類將這些String型的數(shù)據(jù)轉(zhuǎn)化成基本數(shù)據(jù)類型.如果它不能被適當?shù)霓D(zhuǎn)化(比如其中包含"elephant"),一個NumberFormatException將會被拋出。這里有一個例子,講解了如何將一個可能可以轉(zhuǎn)化成整型的String數(shù)據(jù)轉(zhuǎn)化成整型數(shù)據(jù),當轉(zhuǎn)化不能進行的時候打印出錯誤信息。包裝類可以構(gòu)造該包裝類所包裝的數(shù)據(jù)類型,以及可以轉(zhuǎn)化為該類型的String型。所以Integer類型的包裝類可以保存任何的整形數(shù)據(jù),但是如果你試圖將一個浮點數(shù)傳遞給它的時候一個錯誤將會發(fā)生。記住,包裝類不是基本數(shù)據(jù)類型,它的實例的操作方式和其他對象是一樣的。你可能對考試中出現(xiàn)的一些代碼很疑惑,它們使用數(shù)學操作符對包裝類的實例進行操作。你顯然可以使用“+”來對包裝類的實例進行操作,它會在后臺調(diào)用toString方法。不過小心當你看到“-”, “%”和 “*”的時候。
public class String2Int{public static void main(String argv[]){try{int i= Integer.parseInt(argv[0]);System.out.println("Coverted to int val = " + i);}catch(NumberFormatException nfe){ System.out.println("Could not covert to int");}}
}
toHexStringtoHexString方法以十六進制的無符號整數(shù)形式返回一個整數(shù)參數(shù)的字符串表示形式。它有一個孿生的兄弟方法,它能夠以二進制(基數(shù) 2)無符號整數(shù)形式返回一個整數(shù)參數(shù)的字符串表示形式。對這兩個方法的用法理解需要對二進制數(shù)和十六進制數(shù)的概念有一定了解。你需要知道和對象相關(guān)的比特偏移的概念。下面的代碼將會輸出10接著100的串。
public class NumberFormats{public static void main(String argv[]){System.out.println(Integer.toBinaryString(4));System.out.println(Integer.toHexString(16));}
}
練習題
習題1) 下列哪項表述是正確的?
1) The Integer class has a String and an int constructor
2) The Integer has a floatValue() method
3) The wrapper classes are contained in the java.lang.Math package
4) The Double class has constructors for type double and float
習題2) 當你試圖編譯運行下列代碼的時候會發(fā)生什么?
public class WrapMat{public static void main(String argv[]){Integer iw = new Integer(2);Integer iw2 = new Integer(2);System.out.println(iw * iw2);System.out.println(iw.floatValue());}
}
1 )Compile time error
2) Compilation and output of 4 followed by 2.0
3) Compilation and output of 4 followed by 2
4) Compile time error, the Integer class has no floatValue method
習題3) 當你試圖編譯運行下列代碼的時候會發(fā)生什么?
public class TwoEms {public static void main(String argv[]){Object[] oa = new Object[3];oa[0] = new Integer(1);int i = oa[0];System.out.print(i);}
}
1) Compile time error an array cannot contain object references
2) Compile time error elements in an array cannot be anonymous
3) Compilation and output of 1
4) Compile time error Integer cannot be assigned to int
5) Compilation and output of the memory address of the Integer instance
習題4) 當你試圖編譯運行下列代碼的時候會發(fā)生什么?
public class TwoPack {public static void main(String argv[]){Integer iw = new Integer(“2”);Integer iw2 = new Integer(“2”);String sOut = iw + iw2;System.out.println(sOut);}
}
1) Compile time error, the + operator cannot be applied to Integer
2) Compilation and output of 22
3) Compilation and output of 4
4) Compile time error, Integer has no String constructor
習題5) 下列哪段代碼是正確的?
1) System.out.println(Integer.toBinaryString(4));
2) System.out.println(Integer.toOctalString(4));
3) System.out.println(Integer.add(2,2));
4) Float[] ar = new Float[] { new Float(1.0), new Float(2.1)};
答案
答案 1)
1) Integer類有一個整型和String型的構(gòu)造方法
2) Integer類有一個floatValue()方法
4) Double類有一個float型和double型的構(gòu)造方法
答案 2)
1 )Compile time error
包裝類的實例不能像基本數(shù)據(jù)類型那樣進行操作,注意Integer確實有一個 floatValue方法
答案 3)
4) Compile time error Integer cannot be assigned to int
這段代碼可以通過 Integer類的intValue 方法正常進行。 它是一個包裝類對象不能賦給基本數(shù)據(jù)類型變量。
答案 4)
1) Compile time error, the + operator cannot be applied to Integer
包裝類的實例不能像基本數(shù)據(jù)類型那樣進行操作,它們是對象的實例,你必須將起進行轉(zhuǎn)換成基本數(shù)據(jù)類型來進行數(shù)學操作。
答案 5)
1) System.out.println(Integer.toBinaryString(4));
2) System.out.println(Integer.toOctalString(4));
4) Float[] ar = new Float[] { new Float(1.0), new Float(2.1)};
包裝類的實例不能像基本數(shù)據(jù)類型那樣進行操作,因此選項3中的add方法不存在。如果年紀需要那樣操作,你需要將其轉(zhuǎn)化成基本數(shù)據(jù)類型。
第9 章 Java 的Util 包
目標一 Collection 類/接口為了滿足特定的開發(fā)需求,選擇合適的Collection 類/接口。
本節(jié)需要注意的問題雖然沒有特別的提到,但是對本小節(jié)的集合類的知識點是Java2 版本考試的新考點之一,考試中關(guān)于新出現(xiàn)的集合類的題目非常簡單,只需要應(yīng)試者知道在哪如何使用這些類,而不需要應(yīng)試者完全清楚底層細節(jié)的方法和字段。
舊的Collection 類/接口Java2 通過一些新增加的類/接口加強了集合類的用途,早一些的Java 版本的Collection類包括:
vector
hashtable
array
BitSet
在這些類當中,只有array 包含在1.1 版本的認證考試的考點,從Java1.1 開始,就是對所有開發(fā)情況中經(jīng)常需要用到的排序功能提供了支持是導致Java 越來越臃腫的一個原因。
新的Collection 類/接口集合類的最底層是Collection 接口,它提供了一系列所有集合類開發(fā)中常用到的方法。在開發(fā)中,或許你從未在你創(chuàng)建的類中實現(xiàn)Collection 接口,那是因為Java 提供了一系列Collection 接口的子類/接口。Java2 的API 包含了以下幾個新的Collection 接口
Sets
Maps
所有實現(xiàn)Collection 接口的類存儲對象為元素而不是原始數(shù)據(jù)類型,這種機制有個缺點就是創(chuàng)建對象對性能的影響,而且元素在使用之前必須從Object 類型強制轉(zhuǎn)換成合適的類型,這也同時意味著集合類不要求元素是同一類型的,因為一個Object 對象可以是任何東西。
Set
Set 是一個不可包含重復元素的集合類接口,這恰好和關(guān)系數(shù)據(jù)庫中返回某一條記錄的set 概念相符合。Set 接口的奧妙就在于它的add 方法。
add(Object o)
任何一個傳遞給add 方法的對象必須實現(xiàn)equals 方法,這樣保證與已存數(shù)據(jù)進行對比。如果已經(jīng)存在該數(shù)據(jù),那么調(diào)用add 方法不會對該set 起任何影響并且返回false。這種試圖添加一個元素返回false 的思想更像是C/C++中使用的機制而不是Java,在這種情況下,大多數(shù)Java 的其他類似添加方法選擇的是拋出異常。
List
list 是一個有序的可以有重復元素的集合類接口,該接口中一些重要的方法如下:
add
remove
clear
JDK 的幫助文檔給出了使用List 處理一個實際GUI list 進行控制一列包含名為Planets的列表的例子。
Map
Map 是一個接口,實現(xiàn)它的類不能包含重復的key,這一點和hashtable 很相似。為什么我們使用集合類而不使用數(shù)組?相比較而言,集合類相對于數(shù)組一個最大的優(yōu)點就是它可以自增長,你不需要在創(chuàng)建它的時候為它分配大小空間,缺點就是集合類只能存儲Object 對象,而不能存儲原始數(shù)據(jù)類型,因此不可避免的影響了一定的性能。數(shù)組不能直接支持排序,但是這點可以通過使用靜態(tài)的集合類方法來克服。以下是一個例子。
import java.util.*;
public class Sort{public static void main(String argv[]){Sort s = new Sort();}Sort(){String s[] = new String[4];s[0]="z";s[1]="b";s[2]="c";s[3]="a";Arrays.sort(s);for(int i=0;i< s.length;i++)System.out.println(s[i]);}
}
使用 Vector下面的例子解釋了怎樣將不同類的對象添加到一個Vector里面.這與數(shù)組不同,不要求每個元素必須同類型.代碼會將每個對象輸出到標準輸出設(shè)備,它隱性調(diào)用了每個對象的toString()方法到了Java2 Vector類成為創(chuàng)建一個可變大小數(shù)據(jù)結(jié)構(gòu)的主要方法.可以用remove()方法從Vector類移出元素
import java.awt.*;
import java.util.*;
public class Vec{public static void main(String argv[]){Vec v = new Vec();v.amethod();}//End of mainpublic void amethod(){Vector mv = new Vector();//Note how a vector can store objects//of different typesmv.addElement("Hello");mv.addElement(Color.red);mv.addElement(new Integer(99));//This would cause an error//As a vector will not store primitives//mv.addElement(99)//Walk through each element of the vectorfor(int i=0; i< mv.size(); i++){System.out.println(mv.elementAt(i));}}//End of amethod
}
在Java2 之前Vector 類是創(chuàng)建可重新分配大小的數(shù)據(jù)結(jié)構(gòu)的主要手段。可以使用remove()方法將元素從Vector 中剔除掉。
使用 HashtablesHashtables 有點像 Visual Basic 中使用鍵來索引相應(yīng)的鍵值的概念。除了用數(shù)值來對應(yīng)元素以外,它的效果很像Vector。哈希表的名字部分通過引用數(shù)學概念中的數(shù)字索引概念來進行解決。一個hashtable 比 Vector 優(yōu)越的地方就在于快速的查找。
BitSet正如BitSet 所暗示的,它存儲的是一個序列比特。不要被它名字部分的set 部分誤導,它不同于數(shù)學中或者數(shù)據(jù)庫領(lǐng)域的set,并且它和Java2 中提供Sets 沒有任何關(guān)聯(lián)。它更適合于被看作一個存儲比特的容器。一個BitSet 適合更有效率的存儲一序列代表正/否值的比特值。其他可供選擇的某些集合類在存儲布爾值方面不及它有效率。參考一下Bruce Eckel 的《Thinking in Java》:如果僅僅是從存儲的角度來看它是很有效率;如果你期待更有效率的訪問,它比一些原生類型的數(shù)組要稍微慢一些。BitSet 是一個在開發(fā)中從未需要使用的比較生僻的類,我認為它在密碼領(lǐng)域或者圖片處理的開發(fā)過重使用起來比較方便。下面讓我看看你是否能夠應(yīng)付來自Java2 考試的習題。
練習題
習題1)
下面哪些是集合類?
1) Collection
2) Iterator
3) HashSet
4) Vector
習題2)
關(guān)于Collection interface 下面哪些是正確的?
1) The Vector class has been modified to implement Collection
2) The Collection interface offers individual methods and Bulk methods such as addAll
3) The Collection interface is backwardly compatible and all methods are available within the JDK 1.1 classes
4) The collection classes make it unnecessary to use arrays
習題3)
下面哪些是正確的?
1) The Set interface is designed to ensure that implementing classes have unique members
2) Classes that implement the List interface may not contain duplicate elements
3) The Set interface is designed for storing records returned from a database query
4) The Map Interface is not part of the Collection Framework
習題4)
下面哪些是正確的?
1) The elements of a Collection class can be ordered by using the sort method of the Collection interface
2) You can create an ordered Collection by instantiating a class that implements the
List interface
3) The Collection interface sort method takes parameters of A or D to change the sort order, Ascending/Descending
4) The elements of a Collection class can be ordered by using the order method of the Collection interface
習題5)
你希望存儲少量數(shù)據(jù)并能快速訪問. 你并不需要排序這些數(shù)據(jù), uniqueness is not an issue and the data will remain fairly static 那種數(shù)據(jù)結(jié)構(gòu)最適合這種需求?
1) TreeSet
2) HashMap
3) LinkedList
4) an array
習題6)
下面哪些是Collection 類?
1) ListBag
2) HashMap
3) Vector
4) SetList
習題7)
怎樣從Vector 中移出元素?
1) delete method
2) cancel method
3) clear method
4) remove method
答案
答案1)
3) HashSet
4) Vector
另外兩個是接口不是類
答案2)
1) Vector 類已經(jīng)被修改用類實現(xiàn)Collection
2) 集合類方法提供了單個的方法和addAll 等的批量方法。集合類是隨著JDK1.2 新推出的. 除了舊的集合類如Vetor,Bitset, 如果你在舊的平臺上運行包含了新集合類的代碼,將會拋出異常。
答案3)
1) Set 接口是為了確保正在執(zhí)行的類有特定的成員。實現(xiàn)List 接口的對象中可以包含重復的元素.盡管一個實現(xiàn)Set 接口的類的元素存儲的可能是用來數(shù)據(jù)庫查詢結(jié)果, 但它不是為了那個目的專門設(shè)計的。
答案4)
2) 你可以通過實力化一個實現(xiàn)List 接口來創(chuàng)建一個有序的集合類。
答案5)
4) 一個數(shù)組
像這些簡單的需求用數(shù)組是最合適的了。
答案6)
2) HashMap
3) Vector
JDK1.2(Java2)中Vector 這個類被“加裝到”集合框架中來了。
習題7)
4) remove 方法
目標二 實現(xiàn)hashCode正確與錯誤hashCode 實現(xiàn)方法的區(qū)別
本節(jié)需要注意的問題此宗旨是與JDK1.4 的發(fā)布新推出的. Sun 的網(wǎng)站上顯示這個宗旨時,用小寫字母c 拼寫hashcode,但從Object 對象繼承的方法卻用了大寫的C 拼作hashCode.這是一個已經(jīng)引入到此宗旨的奇怪的話題,處理大量的非常嚴肅的java 編程,卻不必麻煩你實現(xiàn)hashcode.真正的數(shù)據(jù)庫例子不會讓你困惑于此問題,但是這是你應(yīng)該理解的宗旨.
它來自O(shè)bject 對象這個hashcode 方法繼承自所有類對象的父類,所以任何對象的實例都可以調(diào)用hashcode方法,此hashcode 方法的簽名是:
public int hashCode()
所以,你可能會遇到hashcode 的一些偽簽名,比如碩返回非int 值或者帶有非空參數(shù).盡管如此,我懷疑問題會比這個稍微理論化一些.返回int 值是基于hash 的集合類的特殊應(yīng)用,例如
HashTable, HashSet, HashSet基于hash 的集合的本質(zhì)是存儲鍵、值.你用鍵來查找值。所以,舉個例子,你可以用一個HashMap 來存儲職工的id 作為鍵,職工名字作為值。通常,一個hashcode 值會是對象的內(nèi)存地址。你可以很容易地用一些瑣碎的代碼來示范這個:
public class ShowHash{public static void main(String argv[]){ShowHash sh = new ShowHash();System.out.println(sh.hashCode());}
}當我編譯、運行這段代碼,就會輸出7474923,這個就是運行程序時這個類內(nèi)存地址的表示。這就說明了一個hashcode 的一個特性:在運行不同程序時它會得到不同的值。如果你考慮一個對象的內(nèi)存地址,就不能確定一個程序的不同運行所得到的值。這里有一段來自JDK1.4 的引用,它包含了一個hashcode 值的要求“不管什么時候,當一個java 應(yīng)用程序執(zhí)行期間,多次援引同一個對象,hashCode 方法必須一致地返回同樣的integer 類型,對象上的無信息應(yīng)用的equals 比較就被修改了。這個整數(shù)在一個應(yīng)用程序的不同執(zhí)行可以不必保持一致性。”既然它說,在同樣的程序運行中hashCode 的返回值必須一致,改變了對象上的無信息應(yīng)用的equals 比較,這個就告訴我們equals 和hashCode 方法之間的關(guān)系了。
equals 和 hashCode由于每個對象都繼承自一個叫Object 的最終父對象,所以它們都可以訪問equals 方法。但是,當默認情況下,它只是簡單地比較對象的內(nèi)存地址。在用String 類的時候,它的弊端就戲劇性地暴露出來了。如果String 類不實現(xiàn)equals 方法自己的版本,在比較兩個字符串的時候就會比較它們的內(nèi)存地址,而不是字符串序列。這顯然不是你想要的,基于此,String類實現(xiàn)了自己的equals 方法,可以比較兩個字符串。這里有API 文檔的另一個重點。如果兩個對象用對象的equals 方法比較是相等的,那么它們調(diào)用hashCode 方法必須生成同樣的整數(shù)值。此原則可以用下面的代碼來解釋
public class CompStrings{public static void main(String argv[]){String s1 = new String("Hello");String s2 = new String("Hello");System.out.println(s1.hashCode());System.out.println(s2.hashCode());Integer i1 = new Integer(10);Integer i2 = new Integer(10);System.out.println(i1.hashCode());System.out.println(i2.hashCode());}
}
每次程序的運行,這段代碼都可以輸出s1 和s2,i1 和i2 的同樣hashCode 值。理論上,在不同情況下會輸出不同值。
當兩個對象不等時就象上面所寫,用equals 方法判斷兩個不同的對象一定會返回不同hashCode 值,這是一個看似合理的推斷。實際上不是,就像API 文檔所說的那樣。如果兩個依據(jù)java.lang.Object 的equals 方法不相等的兩個對象,分別調(diào)用hashCode 方法一定會生成不同的整數(shù)值,這顯然是不確定的。但是程序員應(yīng)該了解這些。同時,你也可以查詢原始的API 文檔來理解hashCode 方法的要求。
練習題
習題1 )下面所述哪些是正確的?
1)一個對象的hashCode 方法會返回任何原始的整數(shù)類型
2)依據(jù)equals 方法,兩個相等的對象調(diào)用hashCode 方法會生成同樣的結(jié)果。
3)一個對象的hashcode 方法在一個應(yīng)用程序的不同執(zhí)行,一定會返回同樣的值。
4) Object 類的hashcode 方法簽名是public int hashCode()
習題 2)
定義:
public class ValuePair implements Comparable{private int iLookUp;public ValuePair(int iLookUp, String sValue){this.iLookUp=iLookUp;}public void setLookUp(int iLookUp){this.iLookUp = iLookUp;}public int getLookUp(){return iLookUp;}public boolean equals(Object o){if( o instanceof ValuePair){ValuePair vp = (ValuePair) o;if(iLookUp == vp.getLookup()){return true;}return false;}public int compareTo(Object o) {ValuePair vp = (ValuePair) o;Integer iwLookUp= new Integer(vp.getLookUp());if(iwLookUp.intValue() < iLookUp){return -1;}if(iwLookUp.intValue() > iLookUp){return +1;}return 0;}}
}
下面那個是有效的hashCode 方法
1)
public int hashCode() {return (int) System.currentTimeMillis();
}
2)
public char hashCode(){reutrn (char) iLookUp;
}
3)
public int hashCode(){return iLookUp;
}
4)
public int hashCode(){return iLookup * 100;
}
習題 3)
給出下面的代碼
public class Boxes{String sValue;Boxes(String sValue){this.sValue=sValue;}public String getValue(){return sValue;}public boolean equals(Object o){String s = (String) o;if (sValue.equals(s) ){return true;}else{return false;}}public int hashCode(){return sValue.hashCode();}
}
哪些是正確的
1) 正確地執(zhí)行hashCode 方法
2)此類不會編譯,因為String 沒有hashCode 方法
3)不正確地執(zhí)行hashCode 方法
4)類不會編譯,因為compareTo 方法不會執(zhí)行
習題 4)
判斷對錯
如果正確地創(chuàng)建了一個對象,那么調(diào)用它的hashCode 方法將返回同樣的值
1) True
2) False
答案
答案 1)
2)依據(jù)equals 方法,兩個相等的對象調(diào)用hashCode 方法會生成同樣的結(jié)果。
4) Object 類的hashcode 方法簽名是public int hashCode()
答案 2)
3)
public int hashCode(){return iLookUp;
}
4)
public int hashCode(){return iLookup * 100;
}
hashCode 方法必須返回整數(shù)值就排除了返回一個char 值的選項2,選項1 返回了毫秒形式的time 類型,由于程序的單次運行一定會得到不同的值,所以就破壞了hashCode 的一個特殊要求。正確選項3 和4 可能不是hashCode 方法的好版本,但是它們一致地得到相等值并返回正確的數(shù)據(jù)類型
答案 3)
1)正確地執(zhí)行hashCode 方法String 類有hashCode 方法的自己實現(xiàn)。如果沒有,它會繼承Object 對象的hashCode 方法,此方法簡單地返回對象實例的內(nèi)存地址。
答案 4)
2) False
小心任何帶always 詞的問題。對象類的hashCode 方法默認返回對象的內(nèi)存地址。Java 工作的一些知識告訴我們,不同執(zhí)行不一定會得到同樣的內(nèi)存地址。一個hashCode 方法在一個程序的同樣運行下一定會返回同樣的值,但在不同運行下不一定會。如果你測試一個對象實例的hashCode,你可能發(fā)現(xiàn)在多程序運行期間,好像返回同樣的內(nèi)存地址,但是這并不是確定的。
附
SCJP認證考試授權(quán)考試中心
天津 天津智知堂培訓中心 022 23657188 23657188 天津市南開區(qū)復康路25號 天津市教育科學研究院7樓 300191
湖北省武漢 武漢愛科信息技術(shù)有限公司 027 87522501 87522530-803 59713102 87522501 珞瑜路272號關(guān)山高新大廈502房 430074
?
轉(zhuǎn)自:http://xiangxm.iteye.com/blog/1744131
?
總結(jié)
以上是生活随笔為你收集整理的SCJP 认证考试指南的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: hive 日誌怎麼查看_Hive各个日志
- 下一篇: 第三十期:BAT 为什么都看上了重庆?