◆傳值與傳引
嚴(yán)格來說,Java中所有方法參數(shù)都是傳值。因?yàn)榧词故莻鬟f的參數(shù)是對(duì)象的引數(shù)時(shí),引數(shù)本身也是傳值的。所有基本類型都是傳值的。
傳值:被傳入的參數(shù)被視為內(nèi)部的局域變量,其變化不影響外部變量的原始值。
傳引:對(duì)于引數(shù)本身來講也是傳值的,但是在方法內(nèi)部若對(duì)引數(shù)所指向的對(duì)象屬性有改變,則會(huì)直接實(shí)時(shí)地影響所指向的對(duì)象屬性。
理解傳引這一概念的最佳方式就是畫出指向圖。eg. Aà(Object)O 對(duì)于A本身而言它是傳值的,也就是說當(dāng)A作為參數(shù)傳遞的時(shí)候,假若在方法內(nèi)部把其它的引數(shù)賦給了A,但是當(dāng)方法返回時(shí),A仍舊是指向原來的對(duì)象,而不會(huì)改變。其次,對(duì)于傳引來說,假若在方法內(nèi)部對(duì)A所指向的對(duì)象屬性有改變,那么對(duì)象屬性的改變會(huì)是直接實(shí)時(shí)的。
再次強(qiáng)調(diào),Java中所有的參數(shù)傳遞都是傳值的。
傳值這種題型的考試很多,基本類型傳值問題考的較多的是對(duì)某一變量,故意用某一方法試圖改變它,然后方法返回時(shí)再打印它。按傳值的說法,實(shí)際上該變量并沒有改變。
◆構(gòu)造函數(shù)
a.構(gòu)造器沒有任何返回類型,哪怕是void也不行。假如指定了返回值,那么Java會(huì)視為一個(gè)普通的方法。
b.如果沒有顯示地調(diào)用父類的構(gòu)造器,Java總是自動(dòng)地調(diào)用父類的默認(rèn)構(gòu)造器。(也就是第一行會(huì)默認(rèn)為super( ))
c.只要類中顯式地定義一個(gè)或多個(gè)構(gòu)造器,那么Java不會(huì)再為你定義一個(gè)默認(rèn)的構(gòu)造器
d.構(gòu)造函數(shù)可以被重載,并且在其體內(nèi)可以用this()和super()來調(diào)用其它的構(gòu)造器。但是this()和super()只能放在構(gòu)造函數(shù)體的第一行進(jìn)行調(diào)用。
e.構(gòu)造器的修飾符只可以是接入控制符public、private、protected、(default)。其它修飾符一律不可。
f.構(gòu)造方法不可被繼承。
◆重載與覆蓋
重載的英文為Overload,而覆蓋的英文為Override。重載是出現(xiàn)在同一類中多個(gè)同名函數(shù),而覆蓋是出現(xiàn)在類繼承體系中同名函數(shù)。(注意:覆蓋有時(shí)被稱為重寫)
重載是依據(jù)同名方法參數(shù)的個(gè)數(shù)、參數(shù)的類型和參數(shù)的順序來確定的。方法的修飾符、返回值、拋出的異常均不能作為區(qū)分重載方法的依據(jù)。(繼承體系中也有重載現(xiàn)象)
覆蓋是在繼承體系中子類覆蓋超類中定義的方法。子類中覆蓋方法的返回類型和參數(shù)類型必須精確匹配。接入控制符只能更加公有化;拋出的異常是超類拋出的子集或不拋。
(static方法不能覆蓋,private方法也不能覆蓋。Java視它們?yōu)楸浑[藏)
· 參數(shù)類型一致,返回類型不同,編譯錯(cuò)誤,提示“試圖用不兼容的返回類型覆蓋”。
· 只要參數(shù)類型不一致,返回類型同與不同,編譯都能通過,此不為覆蓋而是重載。
◆多態(tài)
多態(tài)是出現(xiàn)于類的繼承層次中,通過向上轉(zhuǎn)型和方法重寫的機(jī)制來實(shí)現(xiàn)的。
面向?qū)ο蟪绦蛟O(shè)計(jì)的目標(biāo)是:希望所撰寫的程序代碼都只對(duì)基類進(jìn)行操作。這么一來,當(dāng)加入新的繼承類時(shí),大部分程序代碼都不會(huì)受影響而改寫(也即是說代碼具有擴(kuò)充性)。所以當(dāng)調(diào)用新加入的繼承類時(shí),都會(huì)首先向上轉(zhuǎn)型為基類。這就是多態(tài)的向上轉(zhuǎn)型。
當(dāng)你希望通過調(diào)用基類的方法而能讓繼承類產(chǎn)生正確的行為時(shí),這顯然需要在繼承類進(jìn)行重寫該方法。而究竟是該調(diào)用哪個(gè)繼承類,這是由Java的動(dòng)態(tài)綁定決定的。
多態(tài)最重要的一點(diǎn)在于run-time binding。多態(tài)是面向?qū)ο蟪绦蛟O(shè)計(jì)的目標(biāo)。
關(guān)于多態(tài)中覆蓋注意如下:
屬性既可以繼承,也是可以“覆蓋”的。但是對(duì)屬性而言沒有動(dòng)態(tài)綁定這一特性,所以覆蓋的屬性被認(rèn)為是子類的特別屬性。從某種意義上來講,屬性決定了類(性質(zhì))。另一方面,申明的類型就決定了對(duì)象的屬性。這是因?yàn)椋魏螌?duì)象或變量等號(hào)右面是用來賦值給符合等號(hào)左面所申明類型的,所以左面的類型是先決條件,賦值必須要符合申明類型。對(duì)于向上轉(zhuǎn)型而言,因?yàn)榈忍?hào)右面的對(duì)象is a申明類型,所以是成立的。一定要記住,屬性永遠(yuǎn)都是跟著申明類型走。但是,對(duì)方法而言是在運(yùn)行時(shí)動(dòng)態(tài)綁定的,它取決于對(duì)象自身的實(shí)際類型(實(shí)際上對(duì)方法而言,也是先檢查向上轉(zhuǎn)型后的基類該方法,若無該方法的定義,則編譯錯(cuò),然后再動(dòng)態(tài)綁定到繼承類的覆蓋方法)。
另外,static方法不能覆蓋,private方法也不能覆蓋。
還需特別注意,方法覆蓋時(shí),若覆蓋得不對(duì)(例如參數(shù)一致,僅依靠返回類型不同),則編譯會(huì)出錯(cuò),而不是被Java認(rèn)為方法重載(除非參數(shù)類型也不一樣,這樣java會(huì)認(rèn)為不是override,實(shí)際上它是overload)。
◆類初始化
類的初始化大致上有這么幾個(gè)方面。
a.靜態(tài)變量的初始化 b.一般實(shí)例變量的初始化 c.構(gòu)造函數(shù)的初始化
初始化的難點(diǎn)在于繼承體系中。當(dāng)有繼承體系時(shí),初始化始終要遵循的原則就是,無論如何必先初始化基類
0.當(dāng)載入當(dāng)前類時(shí),若當(dāng)前類有繼承體系,則依次無條件載入基類
0’.先從最頂?shù)幕愰_始順序初始化其靜態(tài)static變量,接著初始化當(dāng)前類的靜態(tài)static變量(也就是說,static變量的初始化是伴隨著類被裝載時(shí)而初始化的,它的初始化在任何構(gòu)造函數(shù)執(zhí)行前)
1.先從最頂端基類,構(gòu)造基類對(duì)象。
(假如構(gòu)造函數(shù)中有this或super調(diào)用,則先執(zhí)行此調(diào)用)
1.1.首先按出現(xiàn)次序初始化其實(shí)例變量
1.2.再執(zhí)行其構(gòu)造函數(shù)體
2.依次遞歸上述步驟
<此外,實(shí)例變量可以不顯式初始化(系統(tǒng)會(huì)賦默認(rèn)值),但是局部變量必須顯式初始化>
◆異常
throws是異常的申明,它置于方法的定義處;throw是異常的擲出,它置于方法體內(nèi)。
異常可分為可檢測(cè)異常和非檢測(cè)異常,調(diào)用申明為可檢測(cè)異常的方法時(shí)必須捕獲異常。
a.方法申明了可檢測(cè)異常,則調(diào)用該方法的塊一定要捕獲異常,否則編譯出錯(cuò)
b.throw后面不能跟任何語(yǔ)句,否則編譯提示語(yǔ)句不可到達(dá)
c.多個(gè)catch語(yǔ)句,要求更具體的異常在前,超類異常在后,否則編譯出錯(cuò)
d.finally語(yǔ)句會(huì)在return語(yǔ)句之前執(zhí)行,即在跳出方法之前一定會(huì)執(zhí)行finally語(yǔ)句
e.假如遇到的是System.exit(0),則無論如何,程序馬上退出,finally語(yǔ)句不會(huì)執(zhí)行
f.方法申明了異常,但是在方法體內(nèi)可以不顯示地用throw拋出異常
g.沒有申明可檢測(cè)異常的方法調(diào)用時(shí),不可用catch捕獲,否則編譯出錯(cuò)
其它注意:
a子類中覆蓋的方法只能拋出父類方法拋出異常的子集,也可以不拋出任何異常(這本身就是子集)
b 但是對(duì)于非檢測(cè)異常RuntimeException則不會(huì)受到上面的約束,它們可以被隨時(shí)拋出。也不受范圍限制。
c 當(dāng)繼承的子類沒有申明異常時(shí),假如它的一個(gè)實(shí)例被申明為超類(向上轉(zhuǎn)型),這時(shí)再調(diào)用子類沒有申明異常的方法,而用了catch,程序也會(huì)編譯通過。(實(shí)際運(yùn)行中調(diào)用的還是子類中的方法)
◆equals()和==
對(duì)于上述關(guān)于equals()總結(jié)如下:
a.類型不兼容的兩個(gè)對(duì)象可以用equals(),但是只要比較的對(duì)象類型不同(哪怕值相同),永遠(yuǎn)返回false
b.對(duì)于沒有覆蓋equals()的類,即使對(duì)象類型相同,值也相同,但返回的仍舊是false,因?yàn)樗玫氖莖bject的默認(rèn)equals方法(與==相同)
c然而對(duì)于覆蓋equals()的類,只要值相同,便返回true。這些類是String,Wrappers,Date,Calendar,BitSet等
對(duì)于==總結(jié)如下:
a.類型不兼容的兩個(gè)對(duì)象不可以用==,若用則編譯錯(cuò)誤
b.同種類型的對(duì)象,假如不是指引同一個(gè)對(duì)象,則返回為false(只有指向同一個(gè)內(nèi)存塊的對(duì)象才返回true)
c.對(duì)于String情況有些不同,因?yàn)镾tring對(duì)象有字面量和構(gòu)造函數(shù)之分。字面量對(duì)象是放在緩沖池中的,這意味著,如果兩個(gè)字面量值相同,則第二個(gè)對(duì)象會(huì)指向第一個(gè)已經(jīng)存在的對(duì)象,而不會(huì)新產(chǎn)生,所以==返回的是true。用構(gòu)造器產(chǎn)生的對(duì)象同一般對(duì)象。對(duì)于字面量來說,多個(gè)類共享的是同一個(gè)緩沖池。這意味著在另外一個(gè)類中創(chuàng)建一個(gè)先前類中已有的字面量字符串,則仍舊是同一對(duì)象。
另外,注意,toUpperCase()、toLowerCase()方法而言,如果大小寫形式與原始String沒什么差別,方法返回原始對(duì)象,==返回true。
d.對(duì)于基本類型而言,系統(tǒng)會(huì)自動(dòng)先歸一精度,然后再比較值,若值相同則返回true。
◆String
String類最重要的一點(diǎn)在于“不變性(immutable)”。它的意思是一旦String類產(chǎn)生了就不會(huì)再改變了,若試圖改變它都會(huì)產(chǎn)生新的對(duì)象。
String對(duì)象有字面量和構(gòu)造函數(shù)之分。字面量對(duì)象是放在緩沖池中的,這意味著,如果兩個(gè)字面量值相同,則第二個(gè)對(duì)象會(huì)指向第一個(gè)已經(jīng)存在的對(duì)象,而不會(huì)新產(chǎn)生,所以==返回的是true。用構(gòu)造器產(chǎn)生的對(duì)象同一般對(duì)象。對(duì)于字面量來說,多個(gè)類共享的是同一個(gè)緩沖池。這意味著即使在另外一個(gè)類中創(chuàng)建一個(gè)先前類中已有的字面量字符串,則仍舊是同一對(duì)象。
考試中需要注意的是:s=new String(“abc”);s+=”def”;System.out.println(s);
s=new String(“abc”);s.concat(“def”);System.out.prinln(s);
前一程序打印為“abcdef”,后一程序打印為“abc”。區(qū)別是第一個(gè)程序又重新賦值給了s。而第二個(gè)程序中s.concat(“def”)只是產(chǎn)生了一個(gè)新的對(duì)象但沒有賦給誰,但原來的s不變。
另外,對(duì)于StringBuffer而言是可變的,對(duì)它的任何改變都是實(shí)時(shí)的。
◆包裝類
包裝類是為了方便對(duì)基本數(shù)據(jù)類型操縱而出現(xiàn)的。有了包裝類就可以用很多的方法來操縱基本數(shù)據(jù)類型(沒有包裝類想直接對(duì)基本數(shù)據(jù)類型操作是不方便的,除非自己編寫方法)。
要熟悉包裝類應(yīng)該著種理解下面幾點(diǎn):
a.包裝類的構(gòu)造器。一般說來,包裝類的構(gòu)造器參數(shù)有兩種:基本數(shù)據(jù)值、字符串
注意:Boolean包裝類構(gòu)造器當(dāng)傳入字符串時(shí),除了不分大小寫的true返回true外,其它字符串一律返回false
b.常見的操作方法。例如:轉(zhuǎn)換為本基本類型或其它基本類型的方法
eg. byteValue(),intValue()…;parseByte(String s),parseInt(String s)…
c.valueOf(各基本類型或字符串)的使用。ValueOf(str)是包裝類的靜態(tài)方法,作用等同于構(gòu)造器。它會(huì)解析傳入的參數(shù),然后構(gòu)造一個(gè)相同類型的包裝類,并返回該包裝類。
例子:原始類型à字符串 (new Integer(101)).toString();String.valueOf(“101”)
字符串à(包裝類)à原始類型 Integer.parseInt(“string”);
(new Integer(“101”)).doubleValue();Integer.valueOf(“101”).intValue()
◆Math類
Math類中都是靜態(tài)方法。其中最易錯(cuò)的是三個(gè)方法:ceil(),floor(),round()
另外還需注意,有許多方法隨基本數(shù)據(jù)類型不同有多個(gè)重載版本。eg.abs(),max()
a.ceil()方法。該方法返回類型為double,往單一的正坐標(biāo)方向取最近的整數(shù)
b.floor()方法。該方法返回類型類double,取最靠近其負(fù)向的整數(shù)。
c.round()方法。它有兩個(gè)重載版本:double和float,返回分別為long和int
long round(double a);int round(float)
round()方法=Math.floor(a+0.5),這意味著正數(shù)5入,負(fù)數(shù)6入
eg.System.out.println(Math.ceil(Double.MIN_VALUE)) //1.0
System.out.println(Math.floor(-0.1)) //-1.0
System.out.println(Math.round(-9.5)) //-9
System.out.println(Math.round(-9.6)) //-10
System.out.println(Math.round(Double.MIN_VALUE)) //0
◆collection類
collection類提供了持有對(duì)象的便利性,并對(duì)持有對(duì)象的操作便利性。正如其名,收集意為將各種類型的對(duì)象收在一起,且數(shù)目不限(有點(diǎn)像收集袋)。收集會(huì)將放入其中的所有對(duì)象均視為Object(向上轉(zhuǎn)型),所以在取出元素對(duì)象時(shí),必須顯式(即強(qiáng)制轉(zhuǎn)型)指出其類型。
對(duì)象收集從整體上分為Collection接口和Map接口。這種分類的標(biāo)準(zhǔn)是:某個(gè)元素位置上放置元素對(duì)象的個(gè)數(shù)。顯然,Map接口放置的是一對(duì)。
Collection接口又可擴(kuò)展為兩個(gè)基本接口:List接口和Set接口。
依上所述,對(duì)象收集可以劃分為四個(gè)基本的類型:Collection、List、Set、Map
· Collection 它是一個(gè)基類的接口,對(duì)元素沒有任何的限制,可以重復(fù)并且無序。
· List 從其名就知是有序的列表。它描述的是按順序?qū)?duì)象放入其中。顯然,后放入的元素有可能與先前放入的對(duì)象是相同的。所以,List是允許對(duì)象重復(fù)出現(xiàn)的有序列表。
· Set 其實(shí)就是數(shù)學(xué)上所說的集合,它不允許有重復(fù)的元素。其中可以有空集(即null對(duì)象)。Set中的元素不要求有序。
· Map 即映射,借助于key和value來描述對(duì)象的搜索。key域中要求唯一性(其實(shí)就是一個(gè)Set),value域可以允許有重復(fù)的元素(其實(shí)就是一個(gè)Collection)。另外注意:常見的HashMap是無序的,而TreeMap是有序的。
◆標(biāo)識(shí)符
a.所有標(biāo)識(shí)符的首字符必須是字母(大小寫)、下劃線_、美元符號(hào)$(或符號(hào)¥)
b.接下來的可以是由數(shù)字(0-9)及首字符相同類型的字符(字母、_、$),其它任何特殊字符均不可
c.標(biāo)識(shí)符不能使用Java關(guān)鍵字和保留字(50個(gè))。但是注意像java,Integer,sizeof,friendly等都不是Java的關(guān)鍵字
d.標(biāo)識(shí)符大小寫是敏感的,但沒有長(zhǎng)度的限制。
◆Switch(i)
a.switch(i)中的參數(shù)最高精度是int(或者short,byte,char),但不可是long,float,double
b.default語(yǔ)句可以放置于任何地方(default意為都不匹配case中的值)
c.當(dāng)語(yǔ)句中未加break語(yǔ)句時(shí),則程序會(huì)從匹配的地方開始執(zhí)行(包括匹配default語(yǔ)句),接下來所有的語(yǔ)句都會(huì)被執(zhí)行(而不管匹配否),直到遇到break語(yǔ)句或switch尾部。
◆垃圾收集
a.只要一個(gè)對(duì)象失去了所有的reference,就可以考慮收集到垃圾收集堆了。
B.當(dāng)失去對(duì)一個(gè)對(duì)象的所有引用時(shí),JVM只是考慮垃圾收集,但并不意味著就立刻收回這個(gè)對(duì)象的內(nèi)存,甚至根本不收回。JVM僅會(huì)在需要更多的內(nèi)存以繼續(xù)執(zhí)行程序時(shí),才會(huì)進(jìn)行垃圾收集。
C.多數(shù)情況下,你永遠(yuǎn)不會(huì)知道垃圾收集什么時(shí)候會(huì)發(fā)生。Java將垃圾收集進(jìn)程作為一個(gè)低優(yōu)先級(jí)線程在運(yùn)行。在Java中垃圾收集是不能被強(qiáng)迫立即執(zhí)行的。調(diào)用System.gc()或Runtime.gc()靜態(tài)方法不能保證垃圾收集器的立即執(zhí)行,因?yàn)椋苍S存在著更高優(yōu)先級(jí)的線程。
D.如果你想人工調(diào)用垃圾收集,并想在收集對(duì)象時(shí)執(zhí)行一些你想要的任務(wù),你就可以覆蓋一個(gè)叫finalize()的方法。java會(huì)為每一個(gè)對(duì)象只調(diào)用一次finalize()方法。finalize()方法必須被申明為protected的,不返回任何值(viod),而且要申明拋出一個(gè)Throwable對(duì)象,并一定要調(diào)用超類的finalize()方法(super.finalize())。
eg.protected void finalize() throws Throwable(){
super.finalize();
…………;}
◆is a & has a
is a 描述的是一個(gè)超類和一個(gè)子類的關(guān)系,也即是繼承的關(guān)系。
has a 描述的是一個(gè)對(duì)象的部分是另一個(gè)對(duì)象,也即是組合的關(guān)系(或稱為調(diào)用)。
◆內(nèi)類與匿名類
內(nèi)類是被包含的類中類,有三個(gè)方面需要注意:一般內(nèi)類、方法內(nèi)類、靜態(tài)內(nèi)類。
· 一般內(nèi)類它可以被當(dāng)做外類的一個(gè)“實(shí)例變量”來看待。因此,四個(gè)接入控制符public、protected、default、private。只是注意:要在外類的non-static函數(shù)外產(chǎn)生該內(nèi)類對(duì)象,必須以OuterClassName.InnerClassName的形式指定該內(nèi)類對(duì)象的類型申明。一般內(nèi)類必須得關(guān)聯(lián)至其外類的某個(gè)對(duì)象。
一般內(nèi)類不可擁有static成員。
· 方法內(nèi)類它屬于范圍型內(nèi)類,也就是說你無法在方法外來調(diào)用內(nèi)類,從這一點(diǎn)來講它可視為方法的局部變量。但是,雖然有它的范疇性,畢竟內(nèi)類還是類,它不會(huì)像局部變量那樣隨著方法的返回就消失了,它仍舊被java視為類。
A.方法內(nèi)類可以直接訪問外類的任何成員
B.方法內(nèi)類只能訪問該方法中final型局部變量和final型的方法參數(shù)
C. 方法內(nèi)類不可有任何接入控制符修飾(這一點(diǎn)與局部變量相同)
· 靜態(tài)內(nèi)類它在產(chǎn)生其對(duì)象時(shí)不需要存在一個(gè)外類對(duì)象。它可被視為static函數(shù)。
static內(nèi)類可以置于接口中。
· 匿 名 類它實(shí)際上是繼承自new類的一個(gè)無名類。New傳回的reference會(huì)被自動(dòng)向上轉(zhuǎn)型。匿名類不能擁有構(gòu)造器,但是可以通過其基類默認(rèn)或帶參數(shù)的構(gòu)造器來申明。
匿名類添加任何修飾符(遵循超類的修飾符),也不可實(shí)現(xiàn)接口、拋出異常。
◆斷言
斷言是Java 1.4中新添加的功能,是Java中的一種新的錯(cuò)誤檢查機(jī)制,它提供了一種在代碼中進(jìn)行正確性檢查的機(jī)制,但是這項(xiàng)功能可以根據(jù)需要關(guān)閉。斷言包括:assert關(guān)鍵字,AssertionError類,以及在java.lang.ClassLoader中增加了幾個(gè)新的有關(guān)assert方法。
assert最重要的特點(diǎn)是assert語(yǔ)句可以在運(yùn)行時(shí)任意的開啟或關(guān)閉,默認(rèn)情況下是關(guān)閉的。
斷言語(yǔ)句有兩種合法的形式:a.assert expression_r1; b.assert expression_r1 : expression_r2;
expression_r1是一條被判斷的布爾表達(dá)式,必須保證在程序執(zhí)行過程中它的值一定是真;expression_r2是可選的,用于在expression_r1為假時(shí),傳遞給拋出的異常AssertionError的構(gòu)造器,因此expression_r2的類型必須是合法的AssertionError構(gòu)造器的參數(shù)類型。AssertionError除了一個(gè)不帶參數(shù)的缺省構(gòu)造器外,還有7個(gè)帶單個(gè)參數(shù)的構(gòu)造器,分別為:object(eg.String) boolean char int long float double。第一種形式如果拋出異常,則調(diào)用AssertionError的缺省構(gòu)造器,對(duì)于第二種形式,則根據(jù)expression_r2值的類型,分別調(diào)用7種單參數(shù)構(gòu)造器中的一種。
A.assert程序的編譯:javac -source 1.4 TestAssert.java(提示java按1.4版本編譯)
B.assert程序的運(yùn)行:java –ea TestAssert 或者 java –ea:TestAssert TestAssert
其它的運(yùn)行參數(shù):java -ea:pkg0... TestAssert;java –esa;java –dsa(系統(tǒng)類斷言),另外,還可以同時(shí)組合用。當(dāng)一個(gè)命令行使用多項(xiàng) -ea -da 參數(shù)時(shí),遵循兩個(gè)基本的原則:后面的參數(shù)設(shè)定會(huì)覆蓋前面參數(shù)的設(shè)定,特定具體的參數(shù)設(shè)定會(huì)覆蓋一般的參數(shù)設(shè)定。
C.AssertinError類是Error的直接子類,因此代表程序出現(xiàn)了嚴(yán)重的錯(cuò)誤,這種異常通常是不需要程序員使用catch語(yǔ)句捕捉的。
D.使用assert的準(zhǔn)則:assert語(yǔ)句的作用是保證程序內(nèi)部的一致性,而不是用戶與程序之間的一致性,所以不應(yīng)用在保證命令行參數(shù)的正確性。可以用來保證傳遞給private方法參數(shù)的正確性。因?yàn)樗接蟹椒ㄖ皇窃陬惖膬?nèi)部被調(diào)用,因而是程序員可以控制的,我們可以預(yù)期它的狀態(tài)是正確和一致的。公有方法則不適用。此外,assert語(yǔ)句可用于檢查任何方法結(jié)束時(shí)狀態(tài)的正確性,及在方法的開始檢查相關(guān)的初始狀態(tài)等等。
assert語(yǔ)句并不構(gòu)成程序正常運(yùn)行邏輯的一部分,時(shí)刻記住在運(yùn)行時(shí)它們可能不會(huì)被執(zhí)行。
◆線程
線程是將程序中容易消耗大量cpu且易陷入死循環(huán)的片斷代碼獨(dú)立出來作為一個(gè)線程來運(yùn)行(也即線程是一個(gè)代碼塊)。
線程一經(jīng)啟動(dòng)start,就會(huì)進(jìn)入ready狀態(tài)(實(shí)際上就是runnable狀態(tài),只是等待分配cpu)。這也說明線程并不會(huì)馬上就running,其運(yùn)行具有不確定性。線程啟動(dòng)后,只要不跳出run()方法,則一直都有機(jī)會(huì)running,它由系統(tǒng)自動(dòng)在各線程間分配cpu時(shí)間來running。要牢記的是:線程運(yùn)行與中斷具有不確定性,你永遠(yuǎn)也不知道線程何時(shí)會(huì)運(yùn)行,何時(shí)會(huì)中斷。
線程從對(duì)象的角度來看,它自身也可以是一個(gè)對(duì)象。它可視為其它對(duì)象中的一個(gè)代碼塊。
在線程的概念中要特別注意幾個(gè)概念:單線程、多線程、多線程的運(yùn)行、多線程間的同步(資源訪問)、多線程間的通信。
· 單線程 對(duì)于單線程而言,編寫其程序是比較簡(jiǎn)單的,也比較容易理解,因?yàn)樗⒉簧婕暗絪ynchronized和communication問題。創(chuàng)建單線程的方法有兩種,其一、擴(kuò)展Thread類,即class A extends Thread{public void run(){};……};其二、實(shí)現(xiàn)Runnable接口,class B implements Runnable{Thread t=new Thread(this);public void run(){};……};
· 多線程 相對(duì)于單線程而言,多線程會(huì)復(fù)雜很多,原因就是它們會(huì)涉及到多線程間的資源訪問和多線程間通信問題。這就涉及到下面所說的三個(gè)方面:多線程的運(yùn)行、多線程間的同步(資源訪問)、多線程間的通信
· 多線程的運(yùn)行 對(duì)于多個(gè)可運(yùn)行runnable的線程來說,運(yùn)行與中斷具有不確定性,永遠(yuǎn)也無法知道線程何時(shí)會(huì)運(yùn)行,何時(shí)會(huì)中斷。但是多線程運(yùn)行也遵循幾個(gè)原則:如果多個(gè)線程具有同樣的優(yōu)先級(jí),則系統(tǒng)會(huì)在它們之間切換cpu時(shí)間運(yùn)行;JVM基于優(yōu)先級(jí)來決定線程的運(yùn)行,但是這并不意味著一個(gè)低優(yōu)先級(jí)的線程一直不運(yùn)行。
· 多線程的同步 被線程可訪問的每個(gè)對(duì)象都有一個(gè)僅被一個(gè)線程訪問控制的鎖,鎖控制著對(duì)對(duì)象的同步碼的存取。這個(gè)可被多個(gè)線程訪問的對(duì)象就是所說的資源共享問題。
A.在程序中,可以通過定義一個(gè)synchronized代碼塊或多個(gè)synchronized方法來使得調(diào)用的線程獲得該對(duì)象的控制鎖。通過獲得對(duì)象的鎖,該線程就會(huì)阻止其它線程對(duì)該對(duì)象所定義的同步塊或同步方法進(jìn)行操作(特別注意的是,此時(shí)并不能保證其它線程對(duì)該對(duì)象的非同步變量和非同步方法進(jìn)行操作)。
B.線程只有在同步塊或同步方法返回后才釋放鎖。
C.synchronized并不能保證程序的運(yùn)行連續(xù)性,而只是保證同一性。也就是說即使在synchronized塊或方法中,線程的運(yùn)行仍舊會(huì)有中斷的可能性。盡管如此,但它卻能確保別的線程不會(huì)再訪問該對(duì)象的其它同步塊和方法,因?yàn)閷?duì)象鎖并未釋放。這一事實(shí)說明了“多線程的運(yùn)行”與“多線程間的同步”是兩個(gè)獨(dú)立的概念。
D.多線程的同步塊或方法可以放在任何可被線程訪問的對(duì)象中(包括線程本身,它實(shí)際上也可被其它的線程訪問)。
· 多線程間的通信 多線程間的同步消除了一個(gè)線程在改變另一個(gè)線程的穩(wěn)定對(duì)象狀態(tài)時(shí)發(fā)生的并發(fā)錯(cuò)誤,但是就線程間通信而言,同步不起任何作用,也就是說“多線程間的通信”又是一個(gè)獨(dú)立的概念。多線程間的通信通常是靠wait(),notify()兩個(gè)方法來實(shí)現(xiàn)的,有關(guān)這兩個(gè)方法的總結(jié)如下:
1.wait(),notify()屬于object方法,并不是線程的方法
2.object.wait() 意為:調(diào)用我(指該object)的當(dāng)前線程你得等等,也就是使...(調(diào)用我的當(dāng)前線程)...等待
object.notify()意為:該喚醒其它先前調(diào)用過我的且在等待的線程
從上述意義可知,wait(),notify()的對(duì)象是指線程所要用到的共享對(duì)象(當(dāng)然共享對(duì)象也可以是線程對(duì)象),但是它的方法動(dòng)作卻是針對(duì)調(diào)用它的線程。(通常情況下,對(duì)象的方法是作用于自己的屬性,而很少作用于其它對(duì)象。若要作用于其它對(duì)象,則用調(diào)用object.method())
3.wait(),notify()必須成對(duì)出現(xiàn),出現(xiàn)的方式可有3種形式。
A.{wait();.... notify();}
B.{wait();}... {object.notify();}
C.{notify();}... {object.wait();}
4.wait(),notify()必須出現(xiàn)在synchronized方法或塊中,否則會(huì)出現(xiàn)異常。原因是因?yàn)閣ait()會(huì)釋放對(duì)象鎖,而鎖必然是出現(xiàn)在同步方法或塊中。另外,wait()同sleep()一樣,也必須捕捉異常InterruptedException。
5.wait(),notify()的執(zhí)行一般與外在的條件有關(guān),只有條件改變了才觸發(fā)喚醒等待的線程。這種條件的改變通常是以旗標(biāo)(Tag)的方式出現(xiàn),也即當(dāng)某一方法執(zhí)行完后,應(yīng)當(dāng)立即改變旗標(biāo)值。假若需要讓線程交替執(zhí)行,則還需要加入互斥條件的判斷。eg.同步方法1中{if(a)},則同步方法2中{if(!a)}
6.當(dāng)執(zhí)行完notify()時(shí),程序并不會(huì)立即去運(yùn)行調(diào)用wait()的線程,而直到釋放notify()的對(duì)象鎖。當(dāng)釋放完鎖后,程序重新分配cpu,要注意的是,此時(shí)系統(tǒng)并不一定就讓wait的線程去運(yùn)行,而有可能是剛才調(diào)用notify()的線程接著繼續(xù)運(yùn)行。這一點(diǎn)正說明了線程運(yùn)行與中斷的不確定性。
7.一般說來,notify()是喚醒等待池中等待時(shí)間最長(zhǎng)的線程;而notifyAll()是喚醒等待池中所有等待線程,然后線程去競(jìng)爭(zhēng)對(duì)象鎖。這里說的是一般情況,有時(shí)情況并非如此,這是由系統(tǒng)中線程運(yùn)行與中斷的不確定性決定的。
8.wait(),notify()通常情況下需要sleep()的配合,否則屏幕中的運(yùn)行顯式會(huì)“飛速”。
9.多線程間的通信會(huì)出現(xiàn)死鎖現(xiàn)象,即wait的線程有可能永遠(yuǎn)也得不到對(duì)象鎖。
-------------------------------------------------------------------------------
◆其他注意問題
☆ 數(shù)組
a.?dāng)?shù)組在使用之前,必須要保證給其分配內(nèi)存(系統(tǒng)會(huì)用默認(rèn)值初始化),不可只定義。否則編譯通過運(yùn)行也會(huì)出現(xiàn)空指針錯(cuò)誤。分配內(nèi)存只需通過new就可以了。
b.二維數(shù)組的第二維可以是變長(zhǎng)的,而且可以在定義時(shí)不指定具體值。這意味著java中的二維數(shù)組不必像矩陣那樣要求每一維長(zhǎng)度都相同。
☆ 變量賦值
a.實(shí)例變量只可在定義時(shí)顯式賦值,不可先定義,再賦值(這樣的話編譯出錯(cuò))。
b.方法變量既可以在定義時(shí)顯式賦值,又可以先定義以后再賦值。
c.static變量可以在類的任何地方賦值(若在方法中賦值,實(shí)際上是重賦值了)。
d.final變量可以在任何地方賦值,但是一旦賦值,就不允許再次重賦值。
e.static final變量只能在定義處賦值(即:常量)。
☆ 移位
a.>> 首位的拷貝右移位。等同于有符號(hào)的除法。
b.>>> 零填充右移位。
c.<< 左移位。等同于有符號(hào)乘法,但是必須遵循整數(shù)的溢出原則。
d.>>32 >>>32任何移多少位都是移模32的余數(shù)。eg.移32位即不移。
☆ byte、char和int
由于char的取值范圍和int的正取值范圍相同,所以,整型字面量可以直接賦給char。但是要是明確將一個(gè)整型(int)變量直接賦給char變量則編譯錯(cuò)誤。
另外,int i=5;char c=’a’;c+=i;編譯是通過的。
byte類型在強(qiáng)制轉(zhuǎn)型的情況下,當(dāng)范圍超出時(shí)會(huì)循環(huán)溢出。
☆ 求模%
求余只管左邊的符號(hào),右邊不管。
eg. int a=-5;int b=-2;System.out.println(a%b) //-1
int a=-5;int b=2;System.out.println(a%b) //-1
int a=5;int b=-2;System.out.println(a%b) //1