文章大綱:
1.為什么static會有這樣的效果?
2.static的使用
3.static誤區
4.static面試題
static的魔法效果是:
被static關鍵字修飾的方法或者變量不需要依賴于對象來進行訪問,只要類被加載了,就可以通過類名去進行訪問。
為什么static會有這樣的效果
這是一個簡單的java程序創建的過程,我們可以看到,在類的首次加載的時候會去執行static的變量或者塊,它是先于對象被加載的。所以不依賴于對象
JVM對這些static的東西只加載一次和分配一次內存。
static關鍵字的作用是:方便在沒有創建對象的情況下來進行調用(方法/變量)
(不想了解可以跳過這個部分)
關于java內存的一些知識
寄存器: JVM內部虛擬寄存器,存取速度非常快,程序不可控制。
棧: 保存局部變量的值,包括:a.用來保存基本數據類型的值;b.保存類的 實例 ,即堆區 對象 的引用(指針)。也可以用來保存加載方法時的幀。
堆: 用來存放動態產生的數據,比如new出來的 對象 。注意創建出來的對象只包含屬于各自的成員變量,并不包括成員方法。因為同一個類的對象擁有各自的成員變量,存儲在各自的堆中,但是他們共享該類的方法,并不是每創建一個對象就把成員方法復制一次。
常量池: JVM為每個已加載的類型維護一個常量池,常量池就是這個類型用到的常量的一個有序集合。包括直接常量(基本類型,String)和對其他類型、方法、字段的 符號引用(1) 。池中的數據和數組一樣通過索引訪問。由于常量池包含了一個類型所有的對其他類型、方法、字段的符號引用,所以常量池在Java的動態鏈接中起了核心作用。 常量池存在于堆中 。
代碼段: 用來存放從硬盤上讀取的源程序代碼。
全局數據段: 用來存放static定義的靜態成員或全局變量。分配該區時內存全部清0,結果變量的初始化為0。
通過這個我們就可以知道如果你每次都去new一個實例的對象,那么你會增加很多堆中的內存。那么你把這些都重復使用的東西放在static那里。那么就可以減少了內存的使用。下面會有一個例子來說明。
ClassA a=new ClassA();
這個語句是最常見的。 a是實例 new出來的才是對象 實例a放在棧 對象放在堆中。堆中的常量池似乎有點static的味道,把成員方法都放在里面而不是每次都復制一次。
關于java 和內存之間,有如下幾點需要注意:
** 1.** 一個Java文件,只要有main入口方法,我們就認為這是一個Java程序,可以單獨編譯運行。
** 2.** 無論是普通類型的變量還是引用類型的變量(俗稱實例),都可以作為局部變量,他們都可以出現在棧中。只不過普通類型的變量在棧中直接保存它所對應的值,而引用類型的變量保存的是一個指向堆區的指針,通過這個指針,就可以找到這個實例在堆區對應的對象。因此,普通類型變量只在棧區占用一塊內存,而引用類型變量要在棧區和堆區各占一塊內存。
普通變量指的是基本數據類型的那些變量,沒有類的存在。引用類型的變量指的是那些類的變量(ClassA a)
3. 分清什么是實例什么是對象。Class a= new Class();此時a叫實例,而不能說a是對象。實例在棧中,對象在堆中,操作實例實際上是通過實例的指針間接操作對象。多個實例可以指向同一個對象。
比如:ClassA a=new ClassA(); ClassA a1=a; 這樣就a和a1都指向同一個對象了。
4 . 棧中的數據和堆中的數據銷毀并不是同步的。方法一旦結束,棧中的局部變量立即銷毀,但是堆中對象不一定銷毀。因為可能有其他變量也指向了這個對象,直到棧中沒有變量指向堆中的對象時,它才銷毀,而且還不是馬上銷毀,要等垃圾回收掃描時才可以被銷毀。
5 . 以上的棧、堆、代碼段、數據段等等都是相對于應用程序而言的。每一個應用程序都對應唯一的一個JVM實例,每一個JVM實例都有自己的內存區域,互不影響。并且這些內存區域是所有線程共享的。這里提到的棧和堆都是整體上的概念,這些堆棧還可以細分。
6 . 類的成員變量在不同對象中各不相同,都有自己的存儲空間(成員變量在堆中的對象中)。而類的方法卻是該類的所有對象共享的,只有一套,對象使用方法的時候方法才被壓入棧,方法不使用則不占用內存。
7.棧中的數據可以共享
int a = 3; int b = 3; 編譯器先處理int a = 3;首先它會在棧中創建一個變量為a的引用,然后查找有沒有字面值為3的地址,沒找到,就開辟一個存放3這個字面值的地址,然后將a指向3的地址。接著處理int b = 3;在創建完b的引用變量后,由于在棧中已經有3這個字面值,便將b直接指向3的地址。這樣,就出現了a與b同時均指向3的情況。
特別注意的是,這種字面值的引用與類對象的引用不同。假定兩個類對象的引用同時指向一個對象,如果一個對象引用變量修改了這個對象的內部狀態,那么另一個對象引用變量也即刻反映出這個變化。相反,通過字面值的引用來修改其值,不會導致另一個指向此字面值的引用的值也跟著改變的情況。如上例,我們定義完a與b的值后,再令a=4;那么,b不會等于4,還是等于3。在編譯器內部,遇到a=4;時,它就會重新搜索棧中是否有4的字面值,如果沒有,重新開辟地址存放4的值;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。 (普通變量和引用變量的區別)
static的使用:
- 在static方法內部不能調用非靜態方法,反過來是可以的。而且可以在沒有創建任何對象的前提下,僅僅通過類本身來調用static方法
文章中提供的這個例子幫助我很好的理解了static的上面那句話。 如果調用了MyObject.print2();
此時對象都沒有,str2根本就不存在,所以就會產生矛盾了。同樣對于方法也是一樣,由于你無法預知在print1方法中是否訪問了非靜態成員變量,所以也禁止在靜態成員方法中訪問非靜態成員方法。而對于非靜態成員方法,它訪問靜態成員方法/變量顯然是毫無限制的。
2)static變量
static變量也稱作靜態變量,靜態變量和非靜態變量的區別是:靜態變量被所有的對象所共享,在內存中只有一個副本,它當且僅當在類初次加載時會被初始化。而非靜態變量是對象所擁有的,在創建對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。
static成員變量的初始化順序按照定義的順序進行初始化。
3)static代碼塊
static關鍵字還有一個比較關鍵的作用就是 用來形成靜態代碼塊以優化程序性能。static塊可以置于類中的任何地方,類中可以有多個static塊。在類初次被加載的時候,會按照static塊的順序來執行每個static塊,并且只會執行一次。
為什么說static塊可以用來優化程序性能,是因為它的特性:只會在類加載的時候執行一次。下面看個例子:
isBornBoomer是用來這個人是否是1946-1964年出生的,而每次isBornBoomer被調用的時候,都會生成startDate和birthDate兩個對象,造成了空間浪費,如果改成這樣效率會更好:
因此,很多時候會將一些只需要進行一次的初始化操作都放在static代碼塊中進行。
二.static關鍵字的誤區
1.static關鍵字會改變類中成員的訪問權限嗎?
有些初學的朋友會將java中的static與C/C++中的static關鍵字的功能混淆了。在這里只需要記住一點:與C/C++中的static不同,Java中的static關鍵字不會影響到變量或者方法的作用域(C++中的static則會隱藏變量,讓其他c++文件不能訪問)。
關于C++/C static關鍵字的文章鏈接:http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777441.html
在Java中能夠影響到訪問權限的只有private、public、protected(包括包訪問權限)這幾個關鍵字。看下面的例子就明白了:
提示錯誤"Person.age 不可視",這說明static關鍵字并不會改變變量和方法的訪問權限。
2.能通過this訪問靜態成員變量嗎?
雖然對于靜態方法來說沒有this,那么在非靜態方法中能夠通過this訪問靜態成員變量嗎?先看下面的一個例子,這段代碼輸出的結果是什么?
這里面主要考察隊this和static的理解。this代表什么?this代表當前對象,那么通過new Main()來調用printValue的話,當前對象就是通過new Main()生成的對象。而static變量是被對象所享有的,因此在printValue中的this.value的值毫無疑問是33。在printValue方法內部的value是局部變量,根本不可能與this關聯,所以輸出結果是33。在這里永遠要記住一點:靜態成員變量雖然獨立于對象,但是不代表不可以通過對象去訪問,所有的靜態方法和靜態變量都可以通過對象訪問(只要訪問權限足夠)。
3.static能作用于局部變量么?
在C/C++中static是可以作用域局部變量的,但是在Java中切記:static是不允許用來修飾局部變量。不要問為什么,這是Java語法的規定。
三.常見的筆試面試題
下面列舉一些面試筆試中經常遇到的關于static關鍵字的題目,僅供參考,如有補充歡迎下方留言。
1.下面這段代碼的輸出結果是什么?
至于為什么是這個結果,我們先不討論,先來想一下這段代碼具體的執行過程,在執行開始,先要尋找到main方法,因為main方法是程序的入口,但是在執行main方法之前,必須先加載Test類,而在加載Test類的時候發現Test類繼承自Base類,因此會轉去先加載Base類,在加載Base類的時候,發現有static塊,便執行了static塊。在Base類加載完成之后,便繼續加載Test類,然后發現Test類中也有static塊,便執行static塊。在加載完所需的類之后,便開始執行main方法。在main方法中執行new Test()的時候會先調用父類的構造器,然后再調用自身的構造器。因此,便出現了上面的輸出結果。
2.這段代碼的輸出結果是什么?
類似地,我們還是來想一下這段代碼的具體執行過程。首先加載Test類,因此會執行Test類中的static塊。接著執行new MyClass(),而MyClass類還沒有被加載,因此需要加載MyClass類。在加載MyClass類的時候,發現MyClass類繼承自Test類,但是由于Test類已經被加載了,所以只需要加載MyClass類,那么就會執行MyClass類的中的static塊。在加載完之后,就通過構造器來生成對象。而在生成對象的時候,必須先初始化父類的成員變量,因此會執行Test中的Person person = new Person(),而Person類還沒有被加載過,因此會先加載Person類并執行Person類中的static塊,接著執行父類的構造器,完成了父類的初始化,然后就來初始化自身了,因此會接著執行MyClass中的Person person = new Person(),最后執行MyClass的構造器。
3.這段代碼的輸出結果是什么?
雖然在main方法中沒有任何語句,但是還是會輸出,原因上面已經講述過了。另外,static塊可以出現類中的任何地方(只要不是方法內部,記住,任何方法內部都不行),并且執行是按照static塊的順序執行的。
總結一下static的加載過程:
1.首先找到main函數的入口,就是那個擁有main函數的類(判斷一下該類是否有父類,如果有先加載父類)進行加載。
2.加載完之后,去main函數中看看哪個類先進行初始化,然后去初始化這個類的成員變量之后再是它的構造函數(如果這個類有父類就要先初始化父類的成員變量和構造函數)
3.類加載先于類初始化 沒有出現過的類先會加載進來。
文章來源:
作者:海子 出處:http://www.cnblogs.com/dolphin0520/