【java】Static解讀和public static void main(String[] args)詳細分析
首先問一個問題:
public static void main(String[] args)程式碼代表了什麼含義?以及思考為什麼java程式一開始就會進入main()函式,其實c、c++也是如此?後面一個問題有static的基礎就非常容易。(後一個問題的答案提前公佈:用static修飾的程式碼塊表示靜態程式碼塊,當Java虛擬機器(JVM)載入類時,就會執行該程式碼塊)
現在回到第一個問題:
下面分別解釋這些關鍵字的作用:
(1)public關鍵字,這個好理解,宣告主函式為public就是告訴其他的類可以訪問這個函式。
(2)static關鍵字,告知編譯器main函式是一個靜態函式。也就是說main函式中的程式碼是儲存在靜態儲存區的,即當定義了類以後這段程式碼就已經存在了。如果main()方法沒有使用static修飾符,那麼編譯不會出錯,但是如果你試圖執行該程式將會報錯,提示main()方法不存在。因為包含main()的類並沒有例項化(即沒有這個類的物件),所以其main()方法也不會存。而使用static修飾符則表示該方法是靜態的,不需要例項化即可使用。
(3)void關鍵字表明main()的返回值是無型別。
(4)引數String[] args
public class Testargs { public static void main(String[] args) { String str = new String(); for (int i = 0; i < args.length; i++) { System.out.println(args[i]); str += args[i]; } System.out.println(str); } }
如果不使用編譯器,使用命令列進行編譯。你可以在準備編譯class的時候傳入abc,那麼上面程式執行的結果就是abc,也就是引數args的主要作用是為程式使用者在命令列狀態下與程式互動提供了一種手段。
另外一種作用就是像正常函式方法一樣呼叫其函式,但是需要注意的一點就是static宣告的變數或者方法,是類方法或者說是類變數,不需要例項化就可以直接使用
具體實現步驟:
首先宣告一個類A:
public classA { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { System.out.println(args[i]); } } }
接著再寫一個B類:
public classB {
public static void main(String[] args)
{
//A c = new A();
String[] b = {"111","222","333"};
//c.main(b); //輸出111 222 333
//因為main()函式是靜態函式,因此不需要例項化就可以使用
A.main(b);//輸出結果和上面的是一樣的
}
}
這個沒有很常用,只是作為了解就可以了
static 修飾符能夠與變數、方法一起使用,表示是“靜態”的。
靜態變數和靜態方法能夠通過類名來訪問,不需要建立一個類的物件來訪問該類的靜態成員,所以static修飾的成員又稱作類變數和類方法。靜態變數與例項變數不同,例項變數總是通過物件來訪問,因為它們的值在物件和物件之間有所不同。
請看下面的例子:
public class Demo {
static int i = 10;
int j;
Demo() {
this.j = 20;
}
public static void main(String[] args) {
System.out.println("類變數 i=" + Demo.i);
Demo obj = new Demo();
System.out.println("例項變數 j=" + obj.j);
}
}
執行結果:
類變數 i=10
例項變數 j=20
static 的記憶體分配
靜態變數屬於類,不屬於任何獨立的物件,所以無需建立類的例項就可以訪問靜態變數。之所以會產生這樣的結果,是因為編譯器只為整個類建立了一個靜態變數的副本,也就是隻分配一個記憶體空間,雖然有多個例項,但這些例項共享該記憶體。例項變數則不同,每建立一個物件,都會分配一次記憶體空間,不同變數的記憶體相互獨立,互不影響,改變 a 物件的例項變數不會影響 b 物件。請看下面的程式碼:
- public classDemo {
- static int i;
- int j;
- public static void main(String[] args) {
- Demoobj1 = new Demo();
- obj1.i = 10;
- obj1.j = 20;
- Demoobj2 = new Demo();
- System.out.println("obj1.i=" + obj1.i + ", obj1.j=" + obj1.j);
- System.out.println("obj2.i=" + obj2.i + ", obj2.j=" + obj2.j);
- }
- }
obj1.i=10, obj1.j=20
obj2.i=10, obj2.j=0
注意:靜態變數雖然也可以通過物件來訪問,但是不被提倡,編譯器也會產生警告。
上面的程式碼中,i 是靜態變數,通過 obj1 改變 i 的值,會影響到 obj2;j 是例項變數,通過 obj1 改變 j 的值,不會影響到 obj2。這是因為 obj1.i 和 obj2.i 指向同一個記憶體空間,而 obj1.j 和 obj2.j 指向不同的記憶體空間,請看下圖:
圖1 靜態變數記憶體分配
注意:static 的變數是在類裝載的時候就會被初始化。也就是說,只要類被裝載,不管你是否使用了這個static 變數,它都會被初始化。
小結:類變數(class variables)用關鍵字 static 修飾,在類載入的時候,分配類變數的記憶體,以後再生成類的例項物件時,將共享這塊記憶體(類變數),任何一個物件對類變數的修改,都會影響其它物件。外部有兩種訪問方式:通過物件來訪問或通過類名來訪問。
靜態方法
靜態方法是一種不能向物件實施操作的方法。例如,Math 類的 pow() 方法就是一個靜態方法,語法為 Math.pow(x, a),用來計算 x 的 a 次冪,在使用時無需建立任何 Math 物件。因為靜態方法不能操作物件,所以不能在靜態方法中訪問例項變數,只能訪問自身類的靜態變數。
以下情形可以使用靜態方法:
- 一個方法不需要訪問物件狀態,其所需引數都是通過顯式引數提供(例如 Math.pow())。
- 一個方法只需要訪問類的靜態變數。
讀者肯定注意到,main() 也是一個靜態方法,不對任何物件進行操作。實際上,在程式啟動時還沒有任何物件,main() 方法是程式的入口,將被執行並建立程式所需的物件。
關於靜態變數和靜態方法的總結:
- 一個類的靜態方法只能訪問靜態變數;
- 一個類的靜態方法不能夠直接呼叫非靜態方法;
- 如訪問控制權限允許,靜態變數和靜態方法也可以通過物件來訪問,但是不被推薦;
- 靜態方法中不存在當前物件,因而不能使用 this,當然也不能使用 super;
- 靜態方法不能被非靜態方法覆蓋;
- 構造方法不允許宣告為 static 的;
- 區域性變數不能使用static修飾。
靜態方法舉例:
- public classDemo {
- static int sum(int x, int y){
- return x + y;
- }
- public static void main(String[] args) {
- int sum = Demo.sum(10, 10);
- System.out.println("10+10=" + sum);
- }
- }
10+10=20
static 方法不需它所屬的類的任何例項就會被呼叫,因此沒有 this 值,不能訪問例項變數,否則會引起編譯錯誤。
注意:例項變數只能通過物件來訪問,不能通過類訪問。
關於在靜態方法中間接呼叫非靜態方法示例:
package com.imooc.testargs;
public class StaticInvokeNonStaticMethod {
private String name;
public StaticInvokeNonStaticMethod(String name) {
this.name = name;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
StaticInvokeNonStaticMethod s = new StaticInvokeNonStaticMethod(
"zhangsan");
StaticInvokeNonStaticMethod s2 = new StaticInvokeNonStaticMethod(
"wangwu");
StaticInvokeNonStaticMethod.ask(s);
StaticInvokeNonStaticMethod.ask(s2);
//s.ask(s2); // 物件呼叫靜態方法的方式不推薦使用
}
/**
* 通過傳入一個例項物件的方法引用,達到靜態方法內部呼叫非靜態方法
* */
public static void ask(StaticInvokeNonStaticMethod s) {
System.out.println("靜態方法內部呼叫非靜態方法");
//System.out.println(s); //這是在類中訪問類物件,不是例項變數
//Cannot make a static reference to the non-static field name, 說明不能在靜態方法中訪問例項變數
//System.out.println(name);
s.say();
}
public void say() {
System.out.println("hello my name is " + name);
}
}
靜態初始器(靜態塊)
塊是由大括號包圍的一段程式碼。靜態初始器(Static Initializer)是一個存在於類中、方法外面的靜態塊。靜態初始器僅僅在類裝載的時候(第一次使用類的時候)執行一次,往往用來初始化靜態變數。示例程式碼:
- public classDemo {
- public static int i;
- static{
- i = 10;
- System.out.println("Now in static block.");
- }
- public void test() {
- System.out.println("test method: i=" + i);
- }
- public static void main(String[] args) {
- System.out.println("Demo.i=" + Demo.i);
- new Demo().test();
- }
- }
Now in static block.
Demo.i=10
test method: i=10
靜態匯入
靜態匯入是 Java 5 的新增特性,用來匯入類的靜態變數和靜態方法。一般我們匯入類都這樣寫:
- import packageName.className; // 匯入某個特定的類
- import packageName.*; // 匯入包中的所有類
而靜態匯入可以這樣寫:
- import static packageName.className.methonName; // 匯入某個特定的靜態方法
- import static packageName.className.*; // 匯入類中的所有靜態成員
匯入後,可以在當前類中直接用方法名呼叫靜態方法,不必再用 className.methodName 來訪問。
對於使用頻繁的靜態變數和靜態方法,可以將其靜態匯入。靜態匯入的好處是可以簡化一些操作,例如輸出語句 System.out.println(); 中的 out 就是 System 類的靜態變數,可以通過 import static java.lang.System.*; 將其匯入,下次直接呼叫 out.println() 就可以了。
請看下面的程式碼:
- import static java.lang.System.*;
- import static java.lang.Math.random;
- public classDemo {
- public static void main(String[] args) {
- out.println("產生的一個隨機數:" + random());
- }
- }
產生的一個隨機數:0.05800891549018705