static
修飾變量。在類加載的時候,初始化順序按照類定義的順序執行。也就是父類順序初始化—>子類順序初始化。只執行一次static
修飾代碼塊。在類加載的時候,按照類定義的順序執行。也就是父類順序執行static塊—>子類順序執行static塊。只執行一次
static方法就是沒有this的方法。在static方法內部不能調用非靜態方法,反過來是可以的。而且可以在沒有創建任何對象的前提下,僅僅通過類本身來調用static方法。這實際上正是static方法的主要用途。 ——Java編程思想
讀完上面的引言後,這裏需要強調一點,在非靜態方法中,是可以通過this
來調用static
變量、方法的。
static
常見修飾
1 static
修飾方法
static
方法一般稱作靜態方法,由於靜態方法不依賴於任何對象就可以進行訪問,因此對於靜態方法來說,是沒有this
的,因爲它不依附於任何對象,既然都沒有對象,就談不上this
了。並且由於這個特性,在靜態方法中不能訪問類的非靜態成員變量和非靜態成員方法,因爲非靜態成員方法/變量都是須依賴具體的對象才能夠被調用。
雖然在靜態方法中不能訪問非靜態成員方法和非靜態成員變量,但是在非靜態成員方法中是可以訪問靜態成員方法/變量的。
class MyObject{
private static String str1 = "staticProperty";
private String str2 = "property";
public MyObject(){
}
public void print1(){
System.out.println(str1); // 可以訪問靜態變量
System.out.println(str2);
print2(); // 可以訪問靜態方法
}
public static void print2(){
System.out.println(str1);
System.out.println(str2); // 編譯出錯,不能訪問非靜態變量
print1(); // 編譯出錯,不能訪問非靜態方法
}
}
在上面的代碼中,由於print2
方法是獨立於對象存在的,可以直接用過類名調用。
因此,如果說想在不創建對象的情況下調用某個方法,就可以將這個方法設置爲static。我們最常見的static
方法就是main
方法,至於爲什麼main
方法必須是static
的,現在就很清楚了。因爲程序在執行main方法的時候沒有創建任何對象,因此只有通過類名來訪問。
2 static
修飾成員變量
static
變量也稱作靜態變量,靜態變量和非靜態變量的區別是:
靜態變量被所有的對象所共享,在內存中只有一個副本,它當且僅當在類初次加載時會被初始化。而非靜態變量是對象所擁有的,在創建對象的時候被初始化,存在多個副本,各個對象擁有的副本互不影響。
static
成員變量的初始化順序按照定義的順序進行初始化。
3 static
修飾代碼塊
比如JDBC
中通過靜態塊加載資源
static{
/**
* 6.解析資源文件
*/
//6.1 獲取資源文件解析器對象
ResourceBundle bundle=ResourceBundle.getBundle("DBOptions");
//6.2從資源獲取數據,填充四項變量
driver=bundle.getString("DRIVER");
url=bundle.getString("URL");
userName=bundle.getString("USERNAME");
password=bundle.getString("PASSWORD");
Class.forName(driver);
}
static
塊可以置於類中的任何地方,類中可以有多個static
塊。在類初次被加載的時候,會按照static
塊的順序來執行每個static
塊,並且只會執行一次。
4 static
修飾類
static
修飾類只有一種情況,那就是這個類屬於靜態內部類,接觸過Android
開發的話可能遇見過很多這樣的靜態內部類,如WindowManager.LayoutParams
類,LayoutParams
就是WindowManager
類下的靜態內部類,它的源碼如下所示:
public interface WindowManager extends ViewManager {
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
//內部實現
}
}
static
誤區
1 static
關鍵字會改變類中成員的訪問權限嗎?
有些初學的朋友會將Java
中的static
與C/C++
中的static
關鍵字的功能混淆了。在這裏只需要記住一點:與C/C++
中的static
不同,Java
中的static
關鍵字不會影響到變量或者方法的作用域。在Java
中能夠影響到訪問權限的只有private、public、protected
(包括包訪問權限)這幾個關鍵字。看下面的例子就明白了:
public class Main{
public static void main(String[] args){
System.out.println(Person.name);
System.out.println(Person.age); // 編譯出錯,字段Person.age不可見
}
}
class Person{
public static String name = "小曾";
private static int age = 18;
}
提示錯誤Person.age
不可見,這說明static
關鍵字並不會改變變量和方法的訪問權限。
2 能通過this
訪問靜態成員變量嗎?
本問題呼應開篇,雖然對於靜態方法來說沒有this
,那麼在非靜態方法中能夠通過this
訪問靜態成員變量嗎?先看下面的一個例子,這段代碼輸出的結果是什麼?
public class Main {
static int value = 33;
public static void main(String[] args) throws Exception{
new Main().printValue();
}
private void printValue(){
int value = 3;
System.out.println(this.value);
}
}
// 打印結果:33
這裏面主要考察對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 下面這段代碼的輸出結果是什麼?
public class Test extends Base{
static{
System.out.println("test static");
}
public Test(){
System.out.println("test constructor");
}
public static void main(String[] args) {
new Test();
}
}
class Base{
static{
System.out.println("base static");
}
public Base(){
System.out.println("base constructor");
}
}
// 打印結果:
base static
test static
base constructor
test constructor
至於爲什麼是這個結果,我們先不討論,先來想一下這段代碼具體的執行過程,在執行開始,先要尋找到main
方法,因爲main
方法是程序的入口,但是在執行main
方法之前,必須先加載Test
類,而在加載Test
類的時候發現Test
類繼承自Base
類,因此會轉去先加載Base
類,在加載Base
類的時候,發現有static
塊,便執行了static
塊。在Base
類加載完成之後,便繼續加載Test
類,然後發現Test
類中也有static
塊,便執行static
塊。在加載完所需的類之後,便開始執行main
方法。在main
方法中執行new Test()
的時候會先調用父類的構造器,然後再調用自身的構造器。因此,便出現了上面的輸出結果。
2 這段代碼的輸出結果是什麼?
public class Test {
Person person = new Person("Test");
static{
System.out.println("test static");
}
public Test() {
System.out.println("test constructor");
}
public static void main(String[] args) {
new MyClass();
}
}
class Person{
static{
System.out.println("person static");
}
public Person(String str) {
System.out.println("person "+str);
}
}
class MyClass extends Test {
Person person = new Person("MyClass");
static{
System.out.println("myclass static");
}
public MyClass() {
System.out.println("myclass constructor");
}
}
// 打印結果:
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
首先加載Test
類,因此會執行Test
類中的static
塊。接着執行new MyClass()
,而MyClass
類還沒有被加載,因此需要加載MyClass
類。在加載MyClass
類的時候,發現MyClass
類繼承自Test
類,但是由於Test
類已經被加載了,所以只需要加載MyClass
類,那麼就會執行MyClass
類的中的static
塊。在加載完之後,就通過構造器來生成對象。而在生成對象的時候,必須先初始化父類的成員變量,因此會執行Test
中的Person person = new Person("Test")
,而Person
類還沒有被加載過,因此會先加載Person
類並執行Person
類中的static
塊,接着再執行父類的構造器,完成了父類的初始化,然後就來初始化自身了,因此會接着執行MyClass
中的Person person = new Person("MyClass")
,最後執行MyClass
的構造器。
3 這段代碼的輸出結果是什麼?
public class Test {
static{
System.out.println("test static 1");
}
public static void main(String[] args) {
}
static{
System.out.println("test static 2");
}
}
// 打印結果:
test static 1
test static 2
重要總結
經過以上的講解與案例後,我們總結出一個類啓動後的執行過程
- 加載類:通過
JVM
調用ClassLoader
類中的loaderClass()
方法加載我們要執行的類 - 執行
static
修飾的代碼塊(從上往下執行,並且只執行一次) - 調用構造器
- 成員變量初始化(注意不是在構造函數中初始化)
- 執行構造方法(這時纔是構造函數初始化)
- 對象創建完畢