java類的基本組成
java作為一門面向對象的語言, 類和對象是最重要的概念之一,下面,就讓我們來看看java中類的基本結構是怎樣的:
一個簡單的java類主要可由以下幾個部分(要素)組成:
1.實例變量
2.構造函數
3.更改器方法
4.訪問器方法
例如:
class Myclass { field1... // 聲明實例變量(實例域) field2... // 聲明實例變量(實例域) constructor.. // 構造函數 getMethod1.. // 訪問器方法1 getMethod2.. // 訪問器方法2 setMethod1... // 更改器方法1 setMethod2... // 更改器方法2 }
也許你對java類的運用早已了熟於心了,但對結構的細化和分析有助於你進一步的了解
讓我們看一下下面這個實例
Myclass.java:
public class Myclass { private String name; // 聲明實例變量(實例域) public Myclass () { // 構造函數 name = "尚無名字"; } public String getName () { // 訪問器方法 return name; } public void setName (String str) { // 更改器方法 name = str; } }
Test.java:
public class Test { public static void main(String args []) { Myclass instance = new Myclass(); System.out.println(instance.getName()); // 輸 出當前的實例變量name的值 instance.setName("彭湖灣"); // 修改name的值 System.out.println(instance.getName()); // 再次輸 出實例變量name的值 } }
結果:
尚無名字
彭湖灣
關於構造函數有幾點要注意:
1. 構造函數和類同名
2. 它沒有返回值
3. 可以有多個構造函數,允許實現構造函數重載(下面會講)
【注意】沒有返回值意味着你不能在構造函數里寫return XXX,或者是為方法加上類型如public String Myclass() (具體點說,如果你在構造器里面return一個值會直接導致報錯error,而如果你通過public String Myclass的方式加上類型,那么這就不再是個構造函數,而是個普通方法)
函數重載
函數重載: 對類中的一組同名函數, 根據函數實參的參數類型和參數個數的不同,決定調用哪一個對應的函數,這個過程,叫做函數的重載:
根據參數類型不同進行重載
people.java:
public class People { public void print (String str) { System.out.println("我調用了參數類型為String的方法!"); System.out.println(str); } public void print (int number) { System.out.println("我調用了參數類型為int的方法!"); System.out.println(number); } }
Test.java:
public class Test { public static void main(String args []) { People people1 = new People(); people1.print("字符串"); } }
輸出:
我調用了參數類型為String的方法!
字符串
如果將Test.java中的語句換成:
People people1 = new People(); people1.print(1);
結果
我調用了參數類型為int的方法! 1
根據參數個數不同進行重載
People.java:
public class People { public void print (String str) { System.out.println("我調用了一個參數的方法!"); } public void print (String str1, String str2) { System.out.println("我調用了兩個參數的方法!"); } }
Test.java:
public class Test { public static void main(String args []) { People people1 = new People(); people1.print("參數1", "參數2"); } }
輸出:
我調用了兩個參數的方法!
如果將Test.java中的語句換成:
public static void main(String args []) { People people1 = new People(); people1.print("參數"); }
輸出:
我調用了一個參數的方法!
【注意】函數重載只和參數類型和參數個數有關,和返回值類型無關!! 例如public void XXX()和public String XXX()不構成重載! (這也當然會報錯,因為兩個函數重復了)同時重載也和參數的命名無關, public void XXX(String str1)和 public void XXX(String str2)是同一個函數,這和函數重載就更沒有毛線關系了
構造函數重載
和函數重載的特性一致,但有一個有趣的一點要注意: 當你沒有寫入構造函數的時候,系統會默認提供一個無參的構造函數給你, 所以就算沒有顯式地寫type 實例 = new type()也是成立的
但是! 當你寫了一個有參數的構造函數,但又沒有寫無參數的構造函數時,type 實例 = new type()就會報錯了!(因為默認的無參構造函數已經被你寫的有參數的構造函數給取代了嘛~~~~)
實例變量的默認值,和初始化實例變量
如果你聲明了一個實例變量,但沒有初始化它,那么這個變量會取得一個默認值,默認值依類型決定:
這是每個基本類型對應的默認值
boolean false char '/uoooo'(null) byte (byte)0 short (short)0 int 0 long 0 (L) float 0.0 (f) double 0.0 (d)
對於引用類型,一律初始化為null
例子:
Default.java:
public class Default { private int number; private float f; private boolean bool; private char c; private byte b; public void printDefaultValues () { System.out.println("int的默認值:" + number); System.out.println("float的默認值:" + f); System.out.println("boolean的默認值:" + bool); System.out.println("char的默認值:" + c); System.out.println("byte的默認值:" + b); } }
Test.java:
public class Test { public static void main(String args []) { Default default1 = new Default(); default1.printDefaultValues(); } }
輸出
int的默認值:0 float的默認值:0.0 boolean的默認值:false char的默認值:
一般情況下,最好對一個實例變量進行初始化,去覆蓋掉默認值
public class Default { private int number = 1; }
訪問私有實例變量
私有變量的訪問方式我分成兩種: 類內訪問和實例訪問
(實際上兩者概念上有交叉,但為了方便說明我將兩者分開了)
類內訪問:在定義一個類的時候,在類內部訪問私有變量
實例訪問: 創建對應類的一個對象,通過對象訪問
實例訪問
創建的對象不能直接訪問私有實例變量,但能通過公有的訪問器方法訪問:
例如:
People.java:
public class People { private String name; public People (String aName) { name = aName; } public String getName () { return name; } }
在Test.java中,如果我們試圖直接通過People的實例對象訪問name私有變量:
public class Test { public static void main(String args []) { People people1 = new People("彭湖灣"); System.out.println(people1.name); // 直接通過People的實例對象訪問name私有變量 } }
編譯器會友好地提示:The field People.name is not visible,並在運行時候報error,這告訴我們不能不能通過people1.name直接訪問私有實例變量name
但我們的訪問器方法getName是定義為public的呀,所以我們能通過getName方法訪問
public class Test { public static void main(String args []) { People people1 = new People("彭湖灣"); System.out.println(people1.getName()); } }
運行后輸出:
彭湖灣
私有的變量對於類實例來說本是“不可見”的,但公有的訪問器方法卻有權讓它暴露出來。
類內訪問
在類定義的代碼里,我們可以自由地訪問私有實例變量,不過有一點要注意: 私有實例變量的最高訪問權限是類,而不僅僅是單個對象(也就是說同一個類定義的不同的對象能夠對各自的私有實例變量“互訪”)
例如,在下我們通過 equalName方法判斷People類的兩個實例對象的name變量值是否相等
public class People { private String name; public People (String aName) { name = aName; } public boolean equalName (People other) { String otherName = other.name; // 訪問另一個對象的私有的name變量 return name.equals(otherName); } }
在Test.java中:
public class Test { public static void main(String args []) { People people1 = new People("彭湖灣"); People people2 = new People("XXX"); People people3 = new People("彭湖灣"); System.out.println(people1.equalName(people2)); System.out.println(people1.equalName(people3)); } }
輸出結果:
false // 說明people1和people2的name值不相等 true // 說明people1和people3的name值相等
在equalName方法中,我們在方法參數中聲明了同一個people類的另外一個實例對象other,並通過other.name直接取得它的私有實例變量並在方法中使用。運行是成功的,這意為着私有實例變量並不是為單個對象“所私有”,而是為整個類所“私有”。 這個類下的所有對象,都擁有對同一類下某個對象的私有實例變量的直接的訪問權限
當然,不同類的對象, 就沒有這種直接的訪問權限了
實例變量和局部變量“交鋒”
在方法中, 可以直接通過名稱訪問實例變量(隱式訪問),也可以通過this去顯式訪問
事實上,以下兩種方式效果是相同的
public class People { private String name; public People (String aName) { name = aName; // 隱式訪問實例變量 } }
public class People { private String name; public People (String aName) { this.name = aName; // 顯式訪問實例變量 } }
當局部變量和實例變量同名的時候,局部變量會覆蓋同名的實例變量,讓我們看一個例子:
public class People { private String name ="實例name變量"; public People (String name) { System.out.println("輸出:" + name); //在構造函數中這樣做有些怪異,但為了展示請不要介意 } }
在People類中,有一個實例變量name, 同時在構造函數中將通過參數的形式引入一個同名的局部變量
name,這個時候,我們通過name訪問到的是局部變量name,而不是隱式訪問的實例變量name:
請看Test.java中的測試:
public class Test { public static void main(String args []) { People people1 = new People("局部name變量"); } }
運行后打印
輸出:局部name變量
打印的是“局部name變量”而不是"實例name變量", 這代表, 同名的局部變量覆蓋了對應的實例變量
嗯嗯,正因如此就會產生一些問題了:我們可能常常希望能夠在構造函數中通過參數引入的局部變量的值初始化實例變量,但因為同名覆蓋的問題卻會帶來一些煩惱:
// 無效的寫法! public class People { private String name public People (String name) { name = name; // 兩個都取局部變量this,當然無效了 } }
解決辦法有兩種:
1. 為局部變量取另外一個名稱(為了語意的理解可以在同名變量前加上一個字母)
public class People { private String name public People (String aName) { name = aName; } }
2. 使用this顯式訪問實例變量,這樣就可以“繞過”局部變量的覆蓋
public class People { private String name; public People (String name) { this.name = name; } public String getName () { return this.name; } }
小小地總結一下這一小節:
1. 在方法中, 可以直接通過名稱訪問實例變量(隱式訪問),也可以通過this去顯式訪問實例變量
2. 當局部變量和實例變量同名的時候,局部變量會覆蓋同名的實例變量
3. 對2中造成的沖突問題,可從兩個方向解決:
3.1 為局部變量取另外一個名稱
3.2 使用this顯式訪問實例變量
靜態變量和靜態方法
當對一個實例變量加以static修飾符的時候,它就變成了一個靜態變量。(“靜態”這個詞可能並不太能讓你推測出它的意義和用法)
例如:
public static int peopleTotal = 0; // 這里設為public只是為了演示
靜態變量是隸屬於類的,而不是隸屬於對象,也就是說,靜態變量和實例變量是恰好對立的兩種變量,前者屬於類,后者屬於類創建的實例對象。
對於實例變量:每創建一個對象,就會創建一份類內所有實例變量的拷貝
對於靜態變量: 無論創建多少個對象, 靜態變量從始至終都只有一份,為所有對象“共享”
示例(訪問靜態變量):
People.java:
public class People { private String name; public static int peopleTotal = 0; // 這里設為public只是為了演示 public People (String name) { this.name = name; peopleTotal++; // 每次調用構造函數時候,使得類的peopleTotal靜態變量加1 } }
Test.java:
public class Test { public static void main(String args []) { // 創建三個People對象 People people1 = new People("one"); People people2 = new People("tow"); People people3 = new People("three"); System.out.print(People.peopleTotal); // 輸出此時的peopleTotal } }
結果: 輸出3
我們需要注意兩點
第一點,我們最終是通過People.peopleTotal去訪問的peopleTotal, 這進一步證明了它是屬於類的,而不屬於對象
第二點,最后結果輸出了3, 這說明:peopleTotal靜態變量“從始至終都只有一個”, 連續的三次實例化而調用的構造函數, 使它經歷了從0到1,1到2和2到3的過程(反之,如果peopeTotal是實例變量,那么因為每個對象都對這個有個拷貝,則最終輸出的應該是1(0+1=1))
通過靜態方法訪問靜態變量
如果一個方法僅僅用到靜態變量的話,那么這個方法應該作為一個靜態方法使用而不是實例方法,也就是說, 它要加上static修飾符,例如:
public static int getPeopleTotal
讓我們對上述的例子稍加改造:
People.java
public class People { private String name; private static int peopleTotal = 0; // 和上面的例子不同,這里的靜態變量是私有的 public People (String name) { this.name = name; peopleTotal++; // 每次調用構造函數時候,使得類的peopleTotal靜態變量加1 } public static int getPeopleTotal () { return peopleTotal; // 通過靜態方法訪問私有靜態變量 } }
Test.java:
public class Test { public static void main(String args []) { People people1 = new People("one"); People people2 = new People("tow"); People people3 = new People("three"); System.out.print(People.getPeopleTotal()); // 輸出peopleTotal } }
結果 輸出3
靜態方法的各種性質和靜態變量類似,它也是隸屬於類而不是對象
上面我們都在強調一點,靜態變量, 靜態方法隸屬於類而不是對象,那么你可能會因此問幾個問題:
1.對象能夠訪問靜態變量和靜態方法嗎? (靜態變量/方法是否一定要由類調用?)
2.類內定義的實例方法能訪問靜態變量嗎? (類內的靜態變量是否一定要由靜態方法調用?)
下面我將對這兩個問題一一解答:
對象能夠訪問靜態變量和靜態方法嗎?
可以!
實際上,你可以用對象訪問靜態變量或方法,但你最好不要這樣做,因為這容易造成混淆,具體一點說是混淆我們對“靜態”的認知,實際上和對象毫無關系的靜態變量用對象來調用,會造成我們在理解上的一種矛盾,這降低了程序的可讀性。 用類名調用靜態方法才是建議的操作
// 雖然能達到相同效果但不要這么做!! Test.java public class Test { public static void main(String args []) { People people1 = new People("one"); People people2 = new People("tow"); People people3 = new People("three"); System.out.print(people1.getPeopleTotal()); // 用people1對象調用了靜態方法 } }
類內定義的實例方法能訪問靜態變量嗎?
(類內的靜態變量是否一定要由靜態方法調用?)
可以!
答案當然是可以的,但請注意,如果一個方法僅僅只使用到靜態變量(例如我們這個例子),那它應該作為一個靜態方法,而不是實例方法,原因和上面相同,這容易混淆我們對於靜態變量的認知
// 雖然能達到相同效果但不要這么做!! People.java: public class People { private String name; private static int peopleTotal = 0; public People (String name) { this.name = name; peopleTotal++; // 每次調用構造函數時候,使得類的peopleTotal靜態變量加1 } public int getPeopleTotal () { return peopleTotal; // 通過實例方法訪問私有靜態變量 } }
Test.java:
public class Test { public static void main(String args []) { People people1 = new People("one"); People people2 = new People("tow"); People people3 = new People("three"); System.out.print(people1.getPeopleTotal()); // 用people1對象調用了實例方法!! } }
【注意】上面說法的前提“一個方法僅僅只使用到靜態變量”,如果一個方法不僅僅用到靜態變量,情況就不一樣了
main方法
我想每一個寫java的筒子們應該都很熟悉的一段代碼是public static void main(String args []){ ....}
1.在Java中,main()方法是Java應用程序的入口方法
2. java規范要求必須寫成public static void main(String 字符串 []){ ....}的形式
除了字符串數組名稱可以任意取,static,void和參數一律不可缺少
例如我如果省略static就會導致這一段報錯:
String args [] 的作用
這個字符串數組是用來接收命令行輸入參數的,命令行的參數之間用空格隔開
例子:
public class TestMain { public static void main(String args[]){ System.out.println("打印main方法中的輸入參數!"); for(int i=0;i<args.length;i++){ System.out.println(args[i]); } } }
假設要執行的這個java文件的路徑是D:\Study\basetest\src, 則:
D:\Study\basetest\src>javac TestMain.java D:\Study\basetest\src>java TestMain 1 2 3 打印main方法中的輸入參數! 1 2 3
[完]
參考資料:
《java核心技術 卷1》—— Cay S. Horstmann, Gary Cornell
《Java中的main()方法詳解》 —— 熔岩 http://lavasoft.blog.51cto.com/62575/53263
