上完Java課,雖然也寫了不少的Java代碼,但是一直有不少的疑惑,而static關鍵字一直困惑著我很久,今天無意探究竟,上知乎再仔細查了一下,發現了這個話題的優秀答案https://www.zhihu.com/question/36615154 這篇文章徹底把我之前對static的理解概念土崩瓦解 ...
上完Java課,雖然也寫了不少的Java代碼,但是一直有不少的疑惑,而static關鍵字一直困惑著我很久,今天無意探究竟,上知乎再仔細查了一下,發現了這個話題的優秀答案https://www.zhihu.com/question/36615154
這篇文章徹底把我之前對static的理解概念土崩瓦解,看來我還是低估了設計語言大師的智慧,老以為非靜態聲明函數會造成實例化多個實例時會造成記憶體空間的浪費,實際上每個實例裡面的函數方法在實例的記憶體區域中僅僅只有個引用而已,並且此引用指向了該函數方法的記憶體頭地址。看完文章後再想想之前我寫的代碼,整個人都有點傻逼了,寫出的所謂優化,反而是不符合面向對象編程的規範。
但是後來我又思考了一下,在面向對象編程已經流行了20多年的今天,為何static關鍵字還有存在的意義呢?在一頓飯時間的思考之後,我發現靜態函數在對象的功能擴展上有著很強大的功能,使用恰當的話,確實可以優化記憶體的占用。
比方說,拿個游戲舉個例子,假設我們對士兵的基本裝備有定性的要求(在類中定義各種非靜態屬性),因此訓練出來計程車兵,都有各自的基本裝備(對象的屬性與方法),但是你總不可能在基本屬性里寫士兵帶AT火箭筒,m240重機槍,m320步槍榴彈,便攜無線電基站。。。如果真這樣,訓練出來計程車兵要重死了(實例化對象占用記憶體大),結果在任務中就一個持刀歹徒,拿個手槍突突兩下就解決了,那什麼火箭筒完全是擺設,有點小題大作了。
於是,在面向對象中有種比較好的解決方法,比如基礎類就是防具+人組成計程車兵,這時我可以用繼承的方式分化成醫療兵帶步槍,工程兵帶衝鋒槍+火箭筒,補給兵機槍+彈葯袋,偵察兵狙擊槍+望遠鏡。如此分配兵種,士兵裝備也剛剛好不會過重,能充分利用好。但是這些士兵是需要訓練的(實例化,new操作,要單獨給一片記憶體空間),要資源的,一個任務需要1個人絕不用2個。這種情況,一個專門為裝有靜態方法的類就起到拓展的作用。
雖然說靜態方法在程式啟動的時候就占用了記憶體了,而且static用多了也不是好習慣,容易導致記憶體溢出。但是按需求來重外部擴展靜態方法,是一個不錯的方法。比方說地面的步兵呼叫飛行員進行地面打擊(消息傳遞,一個對象使用另一個的對象的方法,需要實例化的對象),但也可以呼叫總部,用遠程導彈進行地面打擊(調用公共類中的靜態方法,不需要實例化對象,用公共類調用,但靜態方法同樣要占用記憶體,且要在程式結束後才能釋放)。區別就在於,對於這個軍事系統來說,外派的資源前者多了架飛機,如果用不到空中打擊,還廢油呢。。。
因此對於一些經常使用的公共方法,且這些公共方法可以作為擴展的話,可以已靜態方法的形式單獨寫在一個類中,當其他類需要使用的時候,單獨封裝一個方法調用公共類的靜態方法,以便形成最小的擴展,節約記憶體資源。
代碼例子:
在Map類中封裝一個方法,實現面向對象編程的規範,此方法調用KruskalMap類中的靜態方法KruskalAlgo,並把此Map實例傳入此靜態函數。如果不想用Kruskal演算法了,還可以修改代碼,換用Prime類的靜態方法。註意,靜態方法會常駐記憶體,只有jvm關閉後記憶體才會釋放,因此只擴展經常需要調用的函數。
靜態方法的執行效率比非靜態更高,因為在堆中是地址是連續,而非靜態是離散的,使用的函數才會申請記憶體。
另一種是針對函數很少使用的情況,該類的函數是非靜態的,利用實例,用完就釋放方法的記憶體,相比直接面向對象的寫法慢一些,因為涉及到new:
測試用例:
測試結果: