?作者簡介:大家好,我是Leo,熱愛Java后端開發者,一個想要與大家共同進步的男人😉😉
🍎個人主頁:Leo的博客
💞當前專欄:每天一個知識點
?特色專欄: MySQL學習
🥭本文內容:第07天 Static關鍵字作用及用法
🖥?個人小站 :個人博客,歡迎大家訪問
📚個人知識庫: 知識庫,歡迎大家訪問
1. 概述
Static
是靜態修飾符,什么叫靜態修飾符呢?大家都知道,在程序中任何變量或者代碼都是在編譯時由系統自動分配內存來存儲的,而所謂靜態就是指在編譯后所分配的內存會一直存在,直到程序退出內存才會釋放這個空間,也就是只要程序在運行,那么這塊內存就會一直存在。這樣做有什么意義呢?在Java程序里面,所有的東西都是對象,而對象的抽象就是類,對于一個類而言,如果要使用他的成員,那么普通情況下必須先實例化對象后,通過對象的引用才能夠訪問這些成員,但是用static修飾的成員可以通過類名加“.”進行直接訪問。
官方解釋
: 靜態變量(Static Variable)在計算機編程領域指在程序執行前系統就為之靜態分配(也即在運行時中不再改變分配情況)存儲空間的一類變量。與之相對應的是在運行時只暫時存在的自動變量(即局部變量)與以動態分配方式獲取存儲空間的一些對象,其中自動變量的存儲空間在調用棧上分配與釋放。(選自維基百科)
如何理解?為什么要這樣做?例子?
- 解釋:
首先,先了解一下Java的內存分配:
Java 把內存分為棧內存和堆內存,其中棧內存用來存放一些基本類型的變量、數組和對象的引用,堆內存主要存放一些對象。
什么是句柄: 自己理解的話,其實就是類似于對這些靜態的成員進行編號。
那為什么要這樣做呢?
在 JVM 加載一個類的時候,若該類存在 static 修飾的成員變量和成員方法,則會為這些成員變量和成員方法在固定的位置開辟一個固定大小的內存區域,有了這些“固定”的特性,那么 JVM 就可以非常方便地訪問他們。同時如果靜態的成員變量和成員方法不出作用域的話,它們的句柄都會保持不變。同時 static 所蘊含“靜態”的概念表示著它是不可恢復的,即在那個地方,你修改了,他是不會變回原樣的,你清理了,他就不會回來了。
可能這些有些偏于理論,下面我們通過一些代碼示例來進一步了解Static
關鍵字
2. 修飾成員屬性
當我們需要在一個類(class)中定義一個屬性為公共的屬性
,就好比說我們需要這個屬性是所有類都是共有的,并且這個屬性的值是同一個。
package com.cisyam.testStatic;/*** @author : Leo* @version 1.0* @date 2023/8/18 11:06* @description :*/
class Book{// 設置一個默認的值static String pub = "清華大學出版社";// 用來和 static 作為對比String description = "描述";// Book 類正常的屬性private String title;private double price;// Book 的構造類public Book(String title, double price) {this.title = title;this.price = price;}// 獲取 Book 的信息public void getInfo(){System.out.println("圖書名稱:"+ this.title + ",價格為:"+ this.price + ",出版社為:"+ pub + ",描述 "+ this.description);}
}public class Test {public static void main(String[] args) {// 實例化三個Book類Book book1 = new Book("Android開發實戰", 69.8);Book book2 = new Book("Java EE開發實戰", 49.8);Book book3 = new Book("Java 開發教程", 79.8);// 在沒有設置 pub 屬性的情況下輸出book1.getInfo();book2.getInfo();book3.getInfo();System.out.println("————————————————————無敵分割線————————————————————");// 我們給 book1 設置一個 pub 屬性Book.pub = "中信出版社";book1.description = "這本書很牛逼,看了你就知道";book1.getInfo();book2.getInfo();book3.getInfo();}
}
控制臺輸出
從控制臺輸出的結果,可以看到:
- 如果給 屬性 賦默認值,在上面的例子中是(description 和 pub),輸出的結果為都是默認的。
- 當我們修改了類中 static 關鍵字聲明的屬性,那么只要修改了一次,那么其他所有的對象的這個屬性都給修改了。
通過類調用 static 聲明的屬性
但是基于上面的代碼,我們發現如果是其中的一個類對象就改變了所有的對象的屬性,這樣子操作是不是感覺不是特別好?然后在Java中,如果是使用 static 聲明的屬性值,是可以直接通過類調用的。
public class Test {public static void main(String[] args) {// 實例化三個Book類Book book1 = new Book("Android開發實戰", 69.8);Book book2 = new Book("Java EE開發實戰", 49.8);Book book3 = new Book("Java 開發教程", 79.8);// 在沒有設置 pub 屬性的情況下輸出book1.getInfo();book2.getInfo();book3.getInfo();System.out.println("————————————————————無敵分割線————————————————————");// 我們使用 Book 類直接調用pubBook.pub = "中信出版社";book1.description = "這本書很牛逼,看了你就知道";book1.getInfo();book2.getInfo();book3.getInfo();}
}
沒有實例化類時,調用 static 屬性
package com.cisyam.testStatic;/*** @author : Leo* @version 1.0* @date 2023/8/18 11:19* @description :*/
class Book2{// 設置一個默認的值static String pub = "清華大學出版社";// 用來和 static 作為對比String description = "描述";// Book 類正常的屬性private String title;private double price;// Book 的構造類public Book2(String title, double price) {this.title = title;this.price = price;}// 獲取 Book 的信息public void getInfo(){System.out.println("圖書名稱:"+ this.title + ",價格為:"+ this.price + ",出版社為:"+ pub + ",描述 "+ this.description);}
}public class Test3 {public static void main(String[] args) {// 在沒有實例化對象時,就調用System.out.println(Book.pub);// 沒事實例化對象的時候,去給static屬性賦值上默認值Book2.pub = "北京大學出版社";System.out.println(Book.pub);// 新建一個類,輸入 pub 屬性Book2 book = new Book2("Java", 88);book.getInfo();}
}
控制臺輸出
由此,我們看出,在沒有實例化對象的時候,就可以直接通過類去掉用 static屬性,并且還可以修改 static 的屬性。static 屬性聲明雖然是在類的結構中,但是并不受到對象的控制,是獨立存在的。
static 屬性與非 static 屬性的區別
-
static 聲明的屬性和普通屬性(非 static 屬性)最大的區別在于保存的內存區域不同。static 所修飾的在靜態數據區。而不是在堆和棧。
-
static 屬性與非 static 屬性還有一個最大的區別,就是在于所有非 static 屬性必須產生實例化之后才可以訪問,但是static 屬性不受實例化對象的控制。也就是說,在沒有產生實例化對象的情況下,依然可以使用 static 對象。
3. 修飾成員方法
說明
方法本來就是存放在類的定義當中的。static修飾成員方法的作用是可以使用"類名.方法名"的方式操作方法,避免了先要new出對象的繁瑣和資源消耗。
package com.cisyam.testStatic;/*** @author : Leo* @version 1.0* @date 2023/8/18 11:19* @description :*/
class Test2{public static void sayHello(String name) {System.out.println("Hello," + name);}
}public class Demo {public static void main(String[] args) {Test2.sayHello("Leo");}
}
控制臺輸出
4. 修飾代碼塊
說明
**static **{ }就是靜態塊,當類加載器載入類的時候,這一部分代碼被執行,常用于對靜態變量進行初始化工作。當其他代碼用到這個類,類加載器才會將它載入。
代碼只能執行一次,不能再調用。在靜態塊中,可以訪問靜態變量,調用靜態方法。
如果去掉static,{ }中的代碼則會在創建類對象的時候才執行,(相當于把這部分代碼復制到各個構造函數中),這樣可以實現塊中的內容在多個構造函數中的復用。
package com.cisyam.testStatic;/*** @author : Leo* @version 1.0* @date 2023/8/18 11:29* @description :*/
class Test4{static {System.out.println("靜態代碼塊執行");}{System.out.println("普通代碼塊執行");}public Test4(){System.out.println("This is Test()");}public Test4(String string){System.out.println("This is Test(String string)");}
}public class Demo2 {public static void main(String[] args) {System.out.println("------------------------");Test4 test1 = new Test4();System.out.println("------------------------");Test4 test2 = new Test4("Hello");}
}
控制臺輸出
5. 修飾內部類
static 不能修飾普通類,但可以修飾內部類。原因如下:
static 修飾的東西被我們成為類成員,它會隨著類的加載而加載,比如:靜態代碼塊,靜態成員,靜態方法(這里只是加載,并沒有調用)等等。若把一個Class文件中的外部類設為static,那目的何在呢?難道讓這個類隨著應用的啟動而加載嗎?如果我在這次使用過程中根本沒有使用過這個類,那么是不是就會浪費內存。這樣來說設計不合理,總而言之,設計不合理的地方,Java是不會讓它存在的。
為什么內部類可以使用 static 修飾呢,因為內部類算是類的成員了,如果我們沒有使用靜態來修飾,那么我們在創建內部類的時候就需要先有一個外部類的對象,如果我們一直在使用內部類,那么內存中就會一直存在外部類的引用,而我們有時候只需要使用內部類,不需要外部類,那么還是會浪費內存,甚至會造成內存溢出。使用 static修飾內部類之后,內部類在創建對象時就不需要有外部類對象的引用了。
package com.cisyam.testStatic;public class Singleton {private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}private Singleton (){}public static final Singleton getInstance() {return SingletonHolder.INSTANCE;}
}class Demo3 {public static void main(String[] args) {Singleton singleton = Singleton.getInstance();System.out.println(singleton);}}
6. static加載順序
首先思考,下邊程序是否能編譯通過?若可以編譯通過,那么執行結果是什么?
package com.cisyam.testStatic;/*** @author : Leo* @version 1.0* @date 2023/8/18 11:38* @description :*/
public class User {private static String name;public void setName(String name) {this.name = name;}public static String getName() {return User.name;}
}
public class Test6 {public static void main(String[] args) {User user = new User();user.setName("Leo");System.out.println("user = " + User.getName());}
}
控制臺輸出
從上述結果可見,實例對象可以訪問訪問類變量。