? ? ? 我們已經知道類體中的方法分為實例方法和類方法兩種,用static修飾的是類方法。二者有什么區別呢?當一個類創建了一個對象后,這個對象就可以調用該類的方法。
? ? ? ?當類的字節碼文件被加載到內存時,類的實例方法不會被分配入口地址,當該類創建對象后,類中的實例方法才分配入口地址,從而實例方法可以被類創建的任何對象調用執行。需要注意的是,當我們創建第一個對象時,類中的實例方法就分配了入口地址,當再創建對象時,不再分配入口地址,也就是說,方法的入口地址被所有的對象共享,當所有的對象都不存在時,方法的入口地址才被取消。
? ? ? 類方法在類的字節碼加載到內存時就分配了入口地址,因此,Java語言允許通過類名直接調用類方法,而實例方法不能通過類名調用。在講述類的時候我們強調過,在Java語言中,類中的類方法不可以操作實例變量,也不可以調用實例方法,這是因為在類創建對象之前,實例成員變量還沒有分配內存,而且實例方法也沒有入口地址。
有時候我們對靜態方法和實例化方法會有一些 誤解 :
1、大家都以為 “?靜態方法常駐內存,實例方法不是,所以靜態方法效率高但占內存。”
? ? ?事實上,他們都是一樣的,在加載時機和占用內存上,靜態方法和實例方法是一樣的,在類型第一次被使用時加載。調用的速度基本上沒有差別。
2、大家都以為“?靜態方法在堆上分配內存,實例方法在堆棧上”
? ? ?事實上所有的方法都不可能在堆或者堆棧上分配內存,方法作為代碼是被加載到特殊的代碼內存區域,這個內存區域是不可寫的。
方法占不占用更多內存,和它是不是static沒什么關系。 ??? ? ?? 因為字段是用來存儲每個實例對象的信息的,所以字段會占有內存,并且因為每個實例對象的狀態都不一致(至少不能認為它們是一致的),所以每個實例對象的所以字段都會在內存中有一分拷貝,也因為這樣你才能用它們來區分你現在操作的是哪個對象。 但方法不一樣,不論有多少個實例對象,它的方法的代碼都是一樣的,所以只要有一份代碼就夠了。因此無論是static還是non-static的方法,都只存在一份代碼,也就是只占用一份內存空間。 ??
同樣的代碼,為什么運行起來表現卻不一樣?這就依賴于方法所用的數據了。主要有兩種數據來源,一種就是通過方法的參數傳進來,另一種就是使用class的成員變量的值……
3、大家都以為 “實例方法需要先創建實例才可以調用,比較麻煩,靜態方法不用,比較簡單”? ? ?事實上如果一個方法與他所在類的實例對象無關,那么它就應該是靜態的,而不應該把它寫成實例方法。所以所有的實例方法都與實例有關,既然與實例有關,那么創建實例就是必然的步驟,沒有麻煩簡單一說。
當然你完全可以把所有的實例方法都寫成靜態的,將實例作為參數傳入即可,一般情況下可能不會出什么問題。
從面向對象的角度上來說,在抉擇使用實例化方法或靜態方法時,應該根據是否該方法和實例化對象具有邏輯上的相關性,如果是就應該使用實例化對象? 反之使用靜態方法。這只是從面向對象角度上來說的。
如果從線程安全、性能、兼容性上來看? 也是選用實例化方法為宜。
我們為什么要把方法區分為:靜態方法和實例化方法 ?
? ? ? 如果我們繼續深入研究的話,就要脫離技術談理論了。早期的結構化編程,幾乎所有的方法都是“靜態方法”,引入實例化方法概念是面向對象概念出現以后的事情了,區分靜態方法和實例化方法不能單單從性能上去理解,創建c++,java,c#這樣面向對象語言的大師引入實例化方法一定不是要解決什么性能、內存的問題,而是為了讓開發更加模式化、面向對象化。這樣說的話,靜態方法和實例化方式的區分是為了解決模式的問題。
拿別人一個例子說事:
? ? ? 比如說“人”這個類,每個人都有姓名、年齡、性別、身高等,這些屬性就應該是非靜態的,因為每個人都的這些屬性都不相同;但人在生物學上屬于哪個門哪個綱哪個目等,這個屬性是屬于整個人類,所以就應該是靜態的——它不依賴與某個特定的人,不會有某個人是“脊椎動物門哺乳動物綱靈長目”而某個人卻是“偶蹄目”的。