static表示「全局」或者「靜態」的意思,用來修飾成員變量和成員方法,也能夠造成靜態static代碼塊,可是Java語言中沒有全局變量的概念。java
被static修飾的成員變量和成員方法獨立於該類的任何對象。也就是說,它不依賴類特定的實例,被類的全部實例共享。bash
只要這個類被加載,Java虛擬機就能根據類名在運行時數據區的方法區內定找到他們。所以,static對象能夠在它的任何對象建立以前訪問,無需引用任何對象。函數
用public修飾的static成員變量和成員方法本質是全局變量和全局方法,當聲明它類的對象市,不生成static變量的副本,而是類的全部實例共享同一個static變量。學習
static變量前能夠有private修飾,表示這個變量能夠在類的靜態代碼塊中,或者類的其餘靜態成員方法中使用(固然也能夠在非靜態成員方法中使用--廢話),可是不能在其餘類中經過類名來直接引用,這一點很重要。實際上你須要搞明白,private是訪問權限限定,static表示不要實例化就可使用,這樣就容易理解多了。static前面加上其它訪問權限關鍵字的效果也以此類推。測試
static修飾的成員變量和成員方法習慣上稱爲靜態變量和靜態方法,能夠直接經過類名來訪問,訪問語法爲:ui
類名.靜態方法名(參數列表...)
類名.靜態變量名複製代碼
用static修飾的代碼塊表示靜態代碼塊,當Java虛擬機(JVM)加載類時,就會執行該代碼塊。this
按照是否靜態的對類成員變量進行分類可分兩種:一種是被static修飾的變量,叫靜態變量或類變量;另外一種是沒有被static修飾的變量,叫實例變量。spa
二者的區別是:調試
因此通常在須要實現如下兩個功能時使用靜態變量: - 在對象之間共享值時 - 方便訪問變量時日誌
靜態方法能夠直接經過類名調用,任何的實例也均可以調用, 所以靜態方法中不能用this和super關鍵字,不能直接訪問所屬類的實例變量和實例方法(就是不帶static的成員變量和成員成員方法),只能訪問所屬類的靜態成員變量和成員方法。 由於實例成員與特定的對象關聯!!
由於static方法獨立於任何實例,所以static方法必須被實現,而不能是抽象的abstract。
例如爲了方便方法的調用,Java API中的Math類中全部的方法都是靜態的,而通常類內部的static方法也是方便其它類對該方法的調用。
靜態方法是類內部的一類特殊方法,只有在須要時纔將對應的方法聲明成靜態的,一個類內部的方法通常都是非靜態的。
static代碼塊也叫靜態代碼塊,是在類中獨立於類成員的static語句塊,能夠有多個,位置能夠隨便放,它不在任何的方法體內,JVM加載類時會執行這些靜態的代碼塊,若是static代碼塊有多個,JVM將按照它們在類中出現的前後順序依次執行它們,每一個代碼塊只會被執行一次。例如:
public class Test5 {
private static int a;
private int b;
static{
Test5.a=3;
System.out.println(a);
Test5 t=new Test5();
t.f();
t.b=1000;
System.out.println(t.b);
}
static{
Test5.a=4;
System.out.println(a);
}
public static void main(String[] args) {
// TODO 自動生成方法存根
}
static{
Test5.a=5;
System.out.println(a);
}
public void f(){
System.out.println("hhahhahah");
}
}複製代碼
運行結果:
3
hhahhahah
1000
4
5複製代碼
利用靜態代碼塊能夠對一些static變量進行賦值,最後再看一眼這些例子,都一個static的main方法,這樣JVM在運行main方法的時候能夠直接調用而不用建立實例。
static final用來修飾成員變量和成員方法,可簡單理解爲「全局常量」!
對於變量,表示一旦給值就不可修改,而且經過類名能夠訪問。對於方法,表示不可覆蓋,而且能夠經過類名直接訪問。
有時你但願定義一個類成員,使它的使用徹底獨立於該類的任何對象。一般狀況下,類成員必須經過它的類的對象訪問,可是能夠建立這樣一個成員,它可以被它本身使用,而沒必要引用特定的實例。在成員的聲明前面加上關鍵字static(靜態的)就能建立這樣的成員。若是一個成員被聲明爲static,它就可以在它的類的任何對象建立以前被訪問,而沒必要引用任何對象。你能夠將方法和變量都聲明爲static。static 成員的最多見的例子是main( ) 。由於在程序開始執行時必須調用main() ,因此它被聲明爲static。
聲明爲static的變量實質上就是全局變量。當聲明一個對象時,並不產生static變量的拷貝,而是該類全部的實例變量共用同一個static變量。聲明爲static的方法有如下幾條限制:
若是你須要經過計算來初始化你的static變量,你能夠聲明一個static塊,Static 塊僅在該類被加載時執行一次。下面的例子顯示的類有一個static方法,一些static變量,以及一個static 初始化塊:
// Demonstrate static variables,methods,and blocks.
class UseStatic {
static int a = 3;
static int b;
static void meth(int x) {
System.out.println("x = " + x);
System.out.println("a = " + a);
System.out.println("b = " + b);
}
static {
System.out.println("Static block initialized.");
b = a * 4;
}
public static void main(String args[]) {
meth(42);
}
}複製代碼
一旦UseStatic 類被裝載,全部的static語句被運行。首先,a被設置爲3,接着static 塊執行(打印一條消息),最後,b被初始化爲a*4 或12。而後調用main(),main() 調用meth() ,把值42傳遞給x。3個println ( ) 語句引用兩個static變量a和b,以及局部變量x 。
注意:在一個static 方法中引用任何實例變量都是非法的。下面是該程序的輸出:
Static block initialized.
x = 42
a = 3
b = 12複製代碼
在定義它們的類的外面,static 方法和變量能獨立於任何對象而被使用。這樣,你只要在類的名字後面加點號運算符便可。例如,若是你但願從類外面調用一個static方法,你可使用下面通用的格式:
classname.method( )複製代碼
這裏,classname 是類的名字,在該類中定義static方法。能夠看到,這種格式與經過對象引用變量調用非static方法的格式相似。一個static變量能夠以一樣的格式來訪問——類名加點號運算符。這就是Java 如何實現全局功能和全局變量的一個控制版本。
下面是一個例子。在main() 中,static方法callme() 和static 變量b在它們的類以外被訪問。
class StaticDemo {
static int a = 42;
static int b = 99;
static void callme() {
System.out.println("a = " + a);
}
}
class StaticByName {
public static void main(String args[]) {
StaticDemo.callme();
System.out.println("b = " + StaticDemo.b);
}
}複製代碼
下面是該程序的輸出:
a = 42
b = 99複製代碼
static成員是不能被其所在class建立的實例訪問的。
若是不加static修飾的成員是對象成員,也就是歸每一個對象全部的。
加static修飾的成員是類成員,就是能夠由一個類直接調用,爲全部對象共有的.
靜態代碼塊,在虛擬機加載類的時候就會加載執行,並且只執行一次;非靜態代碼塊,在建立對象的時候(即new一個對象的時候)執行,每次建立對象都會執行一次。例如:
例:
//普通類
public class PuTong {
public PuTong(){
System.out.print("默認構造方法!-->");
}
//非靜態代碼塊
{
System.out.print("非靜態代碼塊!-->");
}
//靜態代碼塊
static{
System.out.print("靜態代碼塊!-->");
}
public static void test(){
{
System.out.println("普通方法中的代碼塊!");
}
}
}複製代碼
static{}(即static塊),會在類被加載的時候執行且僅會被執行一次,通常用來初始化靜態變量和調用靜態方法。
public class Test
{
public static int X = 100;
public final static int Y = 200;
public Test()
{
System.out.println("Test構造函數執行");
}
static
{
System.out.println("static語句塊執行");
}
public static void display()
{
System.out.println("靜態方法被執行");
}
public void display_1()
{
System.out.println("實例方法被執行");
}
}
public class StaticBlockTest
{
public static void main(String args[])
{
try
{
Class.forName("Test");
Class.forName("Test");
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
}複製代碼
結果:你會發現雖然執行了兩條Class.forName("Test")語句,可是,只輸出了一條"靜態方法被執行"語句;其實第二條Class.forName()語句已經無效了,由於在虛擬機的生命週期中一個類只被加載一次;又由於static{}是伴隨類加載執行的,因此,無論你new多少次對象實例,static{}都只執行一次。
static{}語句塊執行的時機,即類被加載準確含義:
(1)用Class.forName()顯示加載的時候;
(2)實例化一個類的時候,如將main()函數的內容改成:Test t=new Test();//這種形式其實和1相比,原理是相同的,都是顯示的加載這個類,讀者能夠驗證Test t=new Test();和Test t=(Test)Class.forName().newInstance();這兩條語句效果相同。
(3)調用類的靜態方法的時候,如將main()函數的內容改成:Test.display();
(4)調用類的靜態變量的時候,如將main()函數的內容改成:System.out.println(Test.X);
整體來講就這四種狀況,可是咱們特別須要注意一下兩點:
(1)調用類的靜態常量的時候,是不會加載類的,即不會執行static{}語句塊,讀者能夠本身驗證一下(將main()函數的內容改成System.out.println(Test.Y);),你會發現程序只輸出了一個200;(這是java虛擬機的規定,當訪問類的靜態常量時,若是編譯器能夠計算出常量的值,則不會加載類,不然會加載類)
(2)用Class.forName()形式的時候,咱們也能夠本身設定要不要加載類,如
將Class.forName("Test")
改成
Class.forName("Test",false,StaticBlockTest.class.getClassLoader())複製代碼
你會發現程序什麼都沒有輸出,即Test沒有被加載,static{}沒有被執行。
(1)當一個類中有多個static{}的時候,按照static{}的定義順序,從前日後執行;
(2)先執行完static{}語句塊的內容,纔會執行調用語句;
public class TestStatic
{
static
{
System.out.println(1);
}
static
{
System.out.println(2);
}
static
{
System.out.println(3);
}
public static void main(String args[])
{
System.out.println(5);
}
static
{
System.out.println(4);
}
}複製代碼
結果:
程序會輸出1,2,3,4,5複製代碼
(3)若是靜態變量在定義的時候就賦給了初值(如 static int X=100),那麼賦值操做也是在類加載的時候完成的,而且當一個類中既有static{}又有static變量的時候,一樣遵循「先定義先執行」的原則;
class Test
{
public static int X = 300;
static
{
System.out.println(X);
X = 200;
System.out.println(X);
}
}
public class StaticBlockTest
{
public static void main(String args[])
{
System.out.println(Test.X);
}
}複製代碼
結果:程序會依次輸出300,200,200,先執行完X=300,再執行static{}語句塊。
(4)訪問靜態常量,若是編譯器能夠計算出常量的值,則不會加載類。即若是A類的靜態常量值是經過B類的靜態常量賦值,則不加載,不然須要加載A類。
public class TestA
{
public static final int a = TestB.a;
public static final int b = TestB.b;
public static final int c = 90;
static
{
System.out.println("TestA static語句塊執行");
}
}
public class TestB
{
public static int a = 90;
public static final int b = 90;
static
{
System.out.println("TestB static語句塊執行");
}
}
public class StaticTest
{
public static void main(String args[])
{
System.out.println(TestA.a);
}
}複製代碼
System.out.println(TestA.a);的結果:
TestB static語句塊執行
TestA static語句塊執行
90複製代碼
System.out.println(TestA.b)和System.out.println(TestA.c)的結果:
1複製代碼
1)在虛擬機的生命週期中一個類只被加載一次。
2)類加載的原則:延遲加載,能少加載就少加載,由於虛擬機的空間是有限的。
3)類加載的時機: - 第一次建立對象要加載類. - 調用靜態方法時要加載類,訪問靜態屬性時會加載類。 - 加載子類時一定會先加載父類。 - 建立對象引用不加載類. - 子類調用父類的靜態方法時
(1)當子類沒有覆蓋父類的靜態方法時,只加載父類,不加載子類
(2)當子類有覆蓋父類的靜態方法時,既加載父類,又加載子類複製代碼
首先定義A, B, C三個類用做測試,其中B繼承了A,C又繼承了B,並分別給它們加上靜態初始化塊、非靜態初始化塊和構造函數,裏面都是一句簡單的輸出。主類Main裏面也如法炮製。 測試代碼
class A {
static {
System.out.println("Static init A.");
}
{
System.out.println("Instance init A.");
}
A() {
System.out.println("Constructor A.");
}
}
class B extends A {
static {
System.out.println("Static init B.");
}
{
System.out.println("Instance init B.");
}
B() {
System.out.println("Constructor B.");
}
}
class C extends B {
static {
System.out.println("Static init C.");
}
{
System.out.println("Instance init C.");
}
C() {
System.out.println("Constructor C.");
}
}
public class Main {
static {
System.out.println("Static init Main.");
}
{
System.out.println("Instance init Main.");
}
public Main() {
System.out.println("Constructor Main.");
}
public static void main(String[] args) {
C c = new C();
//B b = new B();
}
}複製代碼
固然這裏不使用內部類,由於內部類不能使用靜態的定義;而用靜態內部類就失去了通常性。那麼能夠看到,當程序進入了main函數,並建立了一個類C的對象以後,輸出是這樣子的:
Static init Main.
Static init A.
Static init B.
Static init C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.
Instance init C.
Constructor C.複製代碼
觀察上面的輸出,能夠觀察到兩個有趣的現象:
1)Main類是確定沒有被實例化過的,可是因爲執行main入口函數用到了Main類,因而static初始化塊也被執行了;
2)全部的靜態初始化塊都優先執行,其次纔是非靜態的初始化塊和構造函數,它們的執行順序是:
那麼若是有多個實例化對象,又會不會發生變化呢?因而在第一個C類的對象後面,再實例化一個B類的對象,再觀察輸出:
Static init Main.
Static init A.
Static init B.
Static init C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.
Instance init C.
Constructor C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.複製代碼
能夠發現這輸出跟前面的基本長得同樣對吧?只是在後面多了4行,那是新的B類對象實例化時產生的信息,一樣也是父類A的初始化塊和構造函數先執行,再輪到子類B的初始化塊和構造函數執行;同時還發現,靜態初始化塊的輸出只出現了一次,也就是說每一個類的靜態初始化塊都只在第一次實例化該類對象時執行一次。
不管如何,初始化塊和構造函數總在一塊兒執行是件有趣的事情,讓咱們反編譯一下看看吧!
查看生成目錄發現已經生成了4個.class文件,分別是A.class, B.class, C.class, Main.class,先看看Main.class的結構(這裏從新註釋了new B):
1 javap -c Main
Compiled from "Main.java"
public class Main {
public Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String Instance init Main.
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #5 // String Constructor Main.
17: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return
public static void main(java.lang.String[]);
Code:
0: new #6 // class C
3: dup
4: invokespecial #7 // Method C."<init>":()V
7: astore_1
8: return
static {};
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #8 // String Static init Main.
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
Main.class的反編譯結果複製代碼
能夠看到整個Main類被分紅三個部分,static {}部分很顯然,就是咱們的static初始化塊,在裏面調用了println並輸出了String「Static init Main.」;而main入口函數也很清晰,首先新實例化了一個類C的對象,而後調用了類C的構造函數,最後返回;而上面public Main();的部分就頗有意思了,這是類Main的構造函數,但咱們看到裏面調用了兩次println,分別輸出了String「Instance init Main.」和String「Constructor Main.」。難道初始化塊和構造函數被合併到一塊兒了?
咱們再看看C類的反編譯結果吧:
1 javap -c C
Compiled from "Main.java"
class C extends B {
C();
Code:
0: aload_0
1: invokespecial #1 // Method B."<init>":()V
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String Instance init C.
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #5 // String Constructor C.
17: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return
static {};
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String Static init C.
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
C.class的反編譯結果複製代碼
靜態初始化塊仍然單獨分出一部分,輸出了咱們的調試語句。而另外一部分,仍然仍是類C的構造函數C();,能夠看到它先調用了父類B的構造函數,接着輸出了咱們初始化塊中的語句,而後才輸出咱們寫在構造函數中的語句,最後返回。屢次試驗也都是如此。因而咱們可以推斷:初始化塊的代碼是被加入到子類構造函數的前面,父類初始化的後面了。
1)用於初始化靜態成員變量:
好比給類C增長一個靜態成員變量sub,咱們在static塊裏面給它賦值爲5:
1 class C extends B {
2
3 static public int a;
4
5 static {
6 a = 5;
7 System.out.println("Static init C.");
8 }
9
10 ......
11
12 }複製代碼
main函數裏輸出這個靜態變量C.sub:
1 public static void main(String[] args) {
2 System.out.println("Value of C.sub: " + C.sub);
3 }複製代碼
則輸出結果:
Static init Main.
Static init A.
Static init B.
Static init C.
Value of C.sub: 5複製代碼
符合類被第一次加載時執行靜態初始化塊的結論,且C.sub被正確賦值爲5並輸出了出來。
可是乍一看彷佛沒有什麼用,由於靜態成員變量在定義時就能夠順便賦值了。所以在賦值方面有點雞肋。
好比能夠記錄第一次訪問類的日誌,或方便單例模式的初始化等。對於單例模式,能夠先用static塊初始化一些可能還被其餘類訪問的基礎參數,等到真正須要加載大量資源的時候(getInstance)再構造單體,在構造函數中加載資源。
基本跟構造函數一個功能,但比構造函數先執行。最多見的用法應該仍是代碼複用,即多個重載構造函數都有若干段相同的代碼,那麼能夠把這些重複的代碼拉出來放到初始化塊中,但仍然要注意它的執行順序,對順序有嚴格要求的初始化代碼就不適合使用了。
總結:
歡迎你們掃下方二維碼加java學習技術交流羣,一塊兒夯實基礎,提高自我價值