你們應該都明白靜態方法/字段比普通方法/字段的寫法要多一個static關鍵字,簡單寫下他們的寫法吧,瞭解的能夠直接略過java
class Test{ // 靜態變量 public static int id = 1; // 普通變量 public int usualId = 2; // 靜態常量 public static final int finalNextId = 3; // 靜態方法 public static void A(){ // 靜態方法只能訪問靜態字段,不能訪問非靜態字段 System.out.println("this is static function A!"); } // 普通方法 public void B(){ // 普通方法能夠訪問靜態字段和非靜態字段 System.out.println("this is usual function B!"); } }
靜態變量(帶有static關鍵字的字段)是屬於類的,全部該類的對象共用該字段;
非靜態變量(普通字段)是屬於類的對象的,每個該類的對象都有本身的非靜態字段,他們互不影響。數組
class Test{ // 靜態變量 public static int id = 1; // 普通變量 public int usualId = 2; } class TestA{ // 對於靜態字段,不實例化類(即建立對象)就可以使用 Test.id; // 對於普通字段,Test.usualId 就會報錯 // 對於普通字段,須要先實例化類 Test test = new Test(); test.usualId; // 不會報錯 }
靜態方法與普通方法的區別,與靜態字段與普通字段的區別相似
靜態方法是不在對象上執行的方法,在調用靜態方法時,不須要實例化該類而調用普通方法必須實例化該類。jvm
class Test{ // 靜態方法 public static void A(){ // 靜態方法只能訪問靜態字段,不能訪問非靜態字段 System.out.println("this is static function A!"); } // 普通方法 public void B(){ // 普通方法能夠訪問靜態字段和非靜態字段 System.out.println("this is usual function B!"); } } class TestA{ // 對於靜態方法,不實例化類(即建立對象)就可調用 Test.A(); // 對於普通字段,Test.B()就會報錯 // 對於普通方法,須要先實例化類 Test test = new Test(); test.B(); // 不會報錯 }
能夠了解下Java中類的生命週期,就能知道爲何訪問靜態方法/字段不須要實例化類而訪問非靜態的方法/字段須要實例化類
靜態字段/方法在類的鏈接階段就存在了,幾乎能夠理解爲類存在,靜態字段/方法就存在;
非靜態字段/方法在類初始化後(new 類名)纔會存在,也就是對象存在後,非靜態字段/方法纔會存在。ide
此部分幾乎搬運了「三級小野怪」的文章,參考連接:https://blog.csdn.net/zhengzhb/article/details/7517213this
當咱們編寫一個Java源文件後,通過編譯會生成一個後綴名爲class的文件,這種文件叫作字節碼文件,只有這種字節碼文件纔可以在Java虛擬機中運行,Java類的聲明週期就是指一個class文件從加載到卸載的全過程。.net
一個Java類的完整的生命週期會經歷加載,鏈接,初始化,使用,卸載五個階段,固然也有在加載或者鏈接以後沒有被初始化就直接被使用的狀況。code
方法區:專門用來存放已經加載的類信息,常量,靜態變量以及方法代碼的內存區域
常量池:是方法區的一部分,主要用來存放常量和類中的符號引用等信息;
堆區:存放類的對象實例
棧區:也叫Java虛擬機棧,由一個個的棧幀組成的後進先出的棧式結構,存放方法運行時產生的局部變量,方法出口等信息。當調用一個方法時,虛擬機棧就會建立一個棧幀存放這些數據,當方法調用完成時,棧幀消失,若是方法調用了其餘方法,則繼續在棧頂建立新的棧幀。對象
在加載階段,Java虛擬機會找到須要加載的類,並把類信息放到jvm的方法區中,而後堆中實例化。
是類的生命週期中的第一個階段,加載階段以後是鏈接階段,可是有時鏈接階段並不會等加載階段完成以後纔開始,而是交叉進行,可能一個類只加載了一部分以後,鏈接階段就已經開始了。可是兩個階段總的開始時間和完成時間老是固定的:加載階段總在鏈接階段以前開始,鏈接階段老是在加載階段完成以後完成。blog
鏈接階段主要任務是作一些加載後的驗證工做以及一些初始化前的準備工做生命週期
驗證:當一個類被加載會後,驗證類是否合法,好比這個類的變量與方法是否是有重複,數據類型是否有效等,目的是保證加載的類可以被jvm所運行。
準備:爲類的靜態變量分配內存並設爲jvm默認的初始值,非靜態變量則不分配內存。須要注意的是,這時候靜態變量的初值是jvm默認的初始值而不是咱們再程序中設定的初值。jvm默認的初值是這樣的:
1.基本類型(int、long、short、char、byte、boolean、float、double)的默認值爲0。
2.引用類型的默認值爲null。
3.常量的默認值爲咱們程序中設定的值,好比咱們在程序中定義final static int a = 100,則準備階段中a的初值就是100。
解析
若是一個類被直接引用就會觸發類的初始化,直接引用的狀況有:
經過new關鍵字實例化對象,讀取或設置類的靜態變量,調用類的靜態方法
經過反射執行以上三種行爲
初始化子類的時候,會觸發父類的初始化
做爲程序入口直接運行時(也就是直接調用main方法)
除了以上四種狀況,其餘使用類的方法叫作被動引用,被動引用不會觸發類的初始化
主動引用代碼示例
class InitClass{ static { System.out.println("初始化InitClass") } public static String a = null; public static void method(){} } class SubInitClass extends InitClass{} public class Test1{ public static void main(){ // 主動引用引發類的初始化:new 對象、讀取或者是類的靜態變量,調用類的靜態方法 new InitClass(); InitClass.a = ""; String a = InitClass.a; InitClass.method(); // 主動引用引發類的初始化,經過反射實例化對象,讀取或設置類的靜態變量,調用類的靜態方法 Class cls = InitClass.class; cls.newInstance(); Field f = cls.getDeclaredField("a"); f.get(null); f.set(null,"s"); Method md = cls.getDeclaredMethod("method"); md.invoke(null, null); // 主動引用引發類的初始化,實例化子類 new SubInitClass(); } }
初始化過程:按照順序自上而下運行類中的變量賦值語句和靜態語句,若是有父類,則首先按照順序運行父類中的變量賦值語句和靜態語句。
在初始化階段,只會初始化與類相關的靜態賦值語句和靜態語句,也就是有static關鍵字修飾的信息,而沒有static修飾的賦值語句和執行語句在實例化對象時纔會運行
使用
類的使用包括主動引用和被動引用,主動引用上面說過了,下面主要說下被動引用
引用父類的靜態字段,只會引發父類的初始化,而不會引發子類的初始化;
定義類數組,不會引發類的初始化;
引用類的常量,不會引發類的初始化。
被動引用代碼示例
class InitClass{ static{ System.out.println("初始化InitClass"); } public static String a = null; public final static String b = "b"; public static void method(){} } class SubInitClass extends InitClass{ static { System.out.println("初始化SubInitClass"); } } public class Test4 { public static void main(String[] args) throws Exception{ // String a = SubInitClass.a;// 引用父類的靜態字段,只會引發父類初始化,而不會引發子類的初始化 // String b = InitClass.b;// 使用類的常量不會引發類的初始化 SubInitClass[] sc = new SubInitClass[10];// 定義類數組不會引發類的初始化 } }
卸載
若是知足下面的狀況,類就會被卸載:
該類全部的實例都已經被回收,也就是java堆中不存在該類的任何實例。加載該類的ClassLoader已經被回收。該類對應的java.lang.Class對象沒有任何地方被引用,沒法在任何地方經過反射訪問該類的方法。