Java 靜態方法/變量,非靜態方法/變量的區別

最近在看JAVA核心技術卷1,下面是我自己的理解,如果有不對的地方,歡迎大家指正

靜態/非靜態 方法/變量的寫法

大家應該都明白靜態方法/字段比普通方法/字段的寫法要多一個static關鍵字,簡單寫下他們的寫法吧,瞭解的可以直接略過

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; // 不會報錯
}
靜態方法

靜態方法與普通方法的區別,與靜態字段與普通字段的區別類似
靜態方法是不在對象上執行的方法,在調用靜態方法時,不需要實例化該類而調用普通方法必須實例化該類。

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 類名)纔會存在,也就是對象存在後,非靜態字段/方法纔會存在。

類的生命週期

此部分幾乎搬運了“三級小野怪”的文章,參考鏈接:https://blog.csdn.net/zhengzhb/article/details/7517213

當我們編寫一個Java源文件後,經過編譯會生成一個後綴名爲class的文件,這種文件叫做字節碼文件,只有這種字節碼文件才能夠在Java虛擬機中運行,Java類的聲明週期就是指一個class文件從加載到卸載的全過程。

一個Java類的完整的生命週期會經歷加載,連接,初始化,使用,卸載五個階段,當然也有在加載或者連接之後沒有被初始化就直接被使用的情況。

jvm中的幾個重要的內存區域
  • 方法區:專門用來存放已經加載的類信息,常量,靜態變量以及方法代碼的內存區域
  • 常量池:是方法區的一部分,主要用來存放常量和類中的符號引用等信息;
  • 堆區:存放類的對象實例
  • 棧區:也叫Java虛擬機棧,由一個個的棧幀組成的後進先出的棧式結構,存放方法運行時產生的局部變量,方法出口等信息。當調用一個方法時,虛擬機棧就會創建一個棧幀存放這些數據,當方法調用完成時,棧幀消失,如果方法調用了其他方法,則繼續在棧頂創建新的棧幀。
加載

在加載階段,Java虛擬機會找到需要加載的類,並把類信息放到jvm的方法區中,然後堆中實例化。
是類的生命週期中的第一個階段,加載階段之後是連接階段,但是有時連接階段並不會等加載階段完成之後纔開始,而是交叉進行,可能一個類只加載了一部分之後,連接階段就已經開始了。但是兩個階段總的開始時間和完成時間總是固定的:加載階段總在連接階段之前開始,連接階段總是在加載階段完成之後完成。

連接

連接階段主要任務是做一些加載後的驗證工作以及一些初始化前的準備工作

  • 驗證:當一個類被加載會後,驗證類是否合法,比如這個類的變量與方法是不是有重複,數據類型是否有效等,目的是保證加載的類能夠被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對象沒有任何地方被引用,無法在任何地方通過反射訪問該類的方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章