基本數據類型與對象
????????Java是面向對象的語言,所以光有基本數據類型是不夠的萍肆,對象才是主角≌陀欤基本數據類型的存在塘揣,一方面是向計算機本身物理結構的妥協,另一方面是為了構造對象宿崭。
????????編程語言所面對的問題和事物是復雜的亲铡,多維度的,需要用一個組合體來囊括方方面面,各個角度奖蔓。比如描述一個人赞草,他有姓名,年齡吆鹤,性別厨疙,住址,身高疑务,體重等等沾凄,這么多信息,不是哪個基本數據類型可以表示的知允∪鲶埃可以理解為,每個基本數據類型都是一維的廊镜,只有把他們組合起來牙肝,才能表示多維的事物。
????????基本數據類型嗤朴,說穿了其實就兩類:數字和文字配椭。除了char類型表示文字,其他的類型可以視為數字雹姊。在人類的自然語言中股缸,其實描述事物不外乎也就兩個手段:文字和數字——文字描述“質”,數字描述“量”吱雏。
????????對象則可以包含二者,它是現實中的事物在計算機中的映射敦姻。但是對象可以映射的不僅僅是具體事物,還可以是抽象的事物歧杏,比如一個查詢镰惦。只要這個事物可以被描述,或者可度量犬绒。對象在計算機中代表了世界的一切旺入,當編程語言可以在計算機中描述一切時,就可以模擬它們的運行凯力。
類與對象
????????類與對象的關系茵瘾,就是基本數據類型與其值之間的關系,比如
int a = 5咐鹤;
????????變量a的類型是int拗秘,值是5。類型代表一類事物祈惶,int代表著整數雕旨,5只是整數中的一個而已扮匠,還有很多其他的整數值。類奸腺,是新創(chuàng)造出來的類型餐禁,也代表一類事物。一個該類型的對象突照,也只是類型中所有值中的一個而已帮非。同一類型的對象之間,他們的屬性個數和名稱必然一樣讹蘑,但是每個屬性的值可能不一樣末盔。屬性的個數及名稱決定了“質”,而屬性的具體值決定了“量”座慰,也可以稱為狀態(tài)陨舱。
????????從衍生的角度看,創(chuàng)建了類版仔,然后由這個模板批量制造對象游盲;從歸納的角度,存在一批相似的對象蛮粮,然后將其抽象成了類益缎,代表這批對象公共的、共同的特征然想。
語法
類
????????類主要由兩部分構成:屬性和方法莺奔。屬性代表著狀態(tài),即這個類是什么变泄;而方法代表行為令哟,即這個類能做什么。還是先舉例:
class Apple {
float weight;
void grow() {
weight += 1.0;
}
}
????????Apple類只有一個屬性weight妨蛹,表示重量屏富;只有一個方法grow,表示生長蛙卤。創(chuàng)建Apple的對象后狠半,可以通過點號運算符訪問屬性和方法:訪問屬性可以獲取該屬性的值;訪問方法可以改變或者獲取相應的屬性值——大部分方法都是在操縱某些屬性表窘,當然也不全是典予。
構造方法與創(chuàng)建對象
????????有了類之后要如何創(chuàng)建對象呢甜滨?對于基本類型可以這樣:
int a = 4乐严;
????????但是對于復合類型,并沒有像4這樣的字面值可供使用衣摩,于是Java提供了new + 構造方法的方式來創(chuàng)建對象昂验,如下:
Apple a = new Apple();
????????很像對普通方法的調用捂敌,除了必須用new關鍵字來配合。那么構造方法哪兒來的呢既琴?類中定義的:
class Apple {
int weight;
Apple() {
weight = 10;
System.out.println(“weight:” + weight);
}
}
????????除了不能有返回值以外占婉,其他地方似乎沒有區(qū)別,不過構造方法的名稱必須和類名保持一致甫恩。構造方法主要是用來初始化屬性值的逆济,這樣可以讓對象在創(chuàng)建時就擁有指定的狀態(tài),當然也可以在其中做一些其他的工作磺箕,跟普通方法一樣奖慌。有了構造方法之后,創(chuàng)建對象時松靡,必須按照構造方法指定的方式來創(chuàng)建對象简僧。上面的構造方法沒帶參數,所以給weight屬性賦了一個固定值雕欺;但實際上構造方法可以帶參數岛马,用調用構造方法時傳入的值來初始化各個屬性,這樣在創(chuàng)建對象時就可以指定任意的狀態(tài)屠列。
class Apple {
int weight;
Apple(int w) {
weight = w;
}
}
Apple a = new Apple(2);
????????那么如果不在類中定義構造方法啦逆,是不是就無法創(chuàng)建對象了呢?理論上是這樣的脸哀,如果最后編譯成的類class文件中不含有構造方法蹦浦,那么這個類就無法創(chuàng)建對象了。但是撞蜂,從源代碼到class文件有個編譯的過程盲镶,所以編譯器是可以在其中做一些手腳,比如檢查是否存在構造方法蝌诡,沒有的話就自動幫你寫一個默認的構造方法溉贿。所謂默認的構造方法,其實是就是空參且沒有方法體的構造方法浦旱。但是如果存在構造方法宇色,就不管了。比如上例颁湖,類中有且只有一個Apple(int w)方法宣蠕,那么下面的代碼將會報錯:
Apple a = new Apple();
????????因為此時的class文件中沒有了空參的構造方法。所以甥捺,如果要使用空參的構造方法來創(chuàng)建對象抢蚀,要么不在類中寫任何構造方法,讓編譯器替你做镰禾;要么手寫一個空參的構造方法——無論類中有沒有其他重載的帶參數的構造方法皿曲。
方法重載
????????Java提供了方法重載機制唱逢,即:一個類中可以存在同名的方法,只要它們的方法簽名不同屋休。所謂方法簽名包括了方法名稱和方法參數列表坞古,不包含返回值;參數列表包括參數的個數劫樟、類型和排序痪枫,不包含參數名稱。如下:
void grow(int叠艳,float听怕,int);
int grow(float虑绵, int尿瞭, int);
float grow(int翅睛,float)声搁;
void grow(int, long)捕发;
????????以上四個方法是可以共存于一個類中的疏旨,它們僅僅是名字一樣。當然第二種重載的方式是不推薦的扎酷,它們僅僅是參數排序不同檐涝,個數和類型都是一樣的,這樣及其容易引起混淆法挨。所以谁榜,重載中的“重”,僅僅指的是名字凡纳。老實說窃植,不太理解為啥要搞這么個重載機制,多個方法共用一個名字能帶來什么好處荐糜?我絞盡腦汁想到了兩個理由:
- 起名字確實是個費力的活兒巷怜,如果經常玩游戲或者生孩子你會懂的。暴氏。延塑。。
- 如果需要重載的方法是構造方法答渔。
????????單說第二條关带,為什么需要多個不同的構造方法?理想狀態(tài)下研儒,只需要一個全參數的構造方法即可豫缨,足夠初始化類的所有屬性。但是有些情況下我們想忽略其中的一些屬性端朵,即只傳部分參數好芭,其他的保持默認值即可。這樣以來冲呢,每次調用全參數的構造方法就會麻煩又啰嗦舍败。由于創(chuàng)建對象時只能按照構造方法規(guī)定的方式來,所以我們需要另外一個構造方法敬拓;而構造方法又必須和類名保持一致邻薯,于是我們需要多個同名且參數列表不同構造方法。這時只能引入重載機制乘凸,將方法簽名(方法名加上參數列表)作為每個方法的唯一標識(被jvm識別)厕诡,也就是方法可以重名了,重名的方法不會引起混淆营勤,如下:
class Apple {
int weight;
Apple(int w) {
weight = w;
}
Apple() { }
}
????????看起來灵嫌,重載機制是為了使多個構造方法并存而發(fā)明的。雖然對普通方法也是有效葛作,但更像是無心插柳寿羞。
this關鍵字
????????我將this理解為一個占位符,代表將來的赂蠢、尚未創(chuàng)建出來的對象本身绪穆。因為在寫代碼時,寫的是類本身虱岂,是個模版玖院,而非具體的對象。在創(chuàng)建對象時第岖,操作系統(tǒng)會為每個對象開辟存儲空間司恳,但僅僅是用來存放具體的屬性值,存儲空間的大小按照類中定義的屬性分配绍傲,空間不能共享扔傅,因為每個對象的狀態(tài)不同;但是方法卻可以共享烫饼,因為方法的邏輯是通用的猎塞,沒必要為每個對象單獨存儲一份方法代碼,于是方法的代碼存儲在公共的空間中——也就是類本身的空間杠纵。于是荠耽,對象需要存儲空間,也需要存儲空間比藻,但它們存儲的內容不一樣:類的空間主要存儲方法铝量,而對象的空間主要存儲屬性倘屹。
以grow方法為例:
void grow() {
weight += 1.0;
}
????????grow方法要對weight屬性操作,注意這是類中的代碼慢叨,是公共的纽匙,不是某個對象特有的。那么如何保證賦值給了正確的對象拍谐?畢竟Apple類會有很多個對象烛缔。所以就需要一個對象的指針,來保證方法是對正確的對象操作的轩拨,這個指針践瓷,就是this。上面提到方法是通過對象來調用的:
apple.grow();
????????這看起來像是apple對象單獨存儲了一份grow方法的代碼亡蓉,grow方法屬于這個對象晕翠。但其實方法是公共的,對象的存儲空間中并沒有方法代碼砍濒,通過對象來調用方法似乎有點說不通崖面。實際上,這只是為了向grow方法傳遞apple對象的地址(或者叫指針)而已梯影,編譯器會將這行代碼編譯成類似下面的代碼(注意這里只是為了理解本質巫员,編譯器可能不是這么做的,但道理相通):
grow(apple);
????????等等甲棍,類中的grow方法是空參的简识,這里強行插入一個Apple類型的參數合適嗎?當然不合適感猛!但是grow方法只是表面上空參七扰,實際上它長這樣:
void grow(Apple this);
????????這樣就說得通了。類中的所有方法(構造方法應該不在此列)都有一個隱式的參數陪白,而且排在第一位颈走,就是this。它可能實現為一個普通的屬性變量咱士,類型就是當前類立由,創(chuàng)建對象時會自動將這個變量賦值為當前對象的地址。構造方法應該是沒有這個隱式參數的序厉,畢竟調用構造方法時對象還不沒創(chuàng)建出來锐膜,沒辦法給構造方法傳參——語法上構造方法也不能在對象上調用。this指針是隱式的弛房,編譯器和jvm會自動處理道盏,我們直接拿來用就可以了。在類中聲明方法時也沒有必要把它顯式寫進參數列表(不合法);在方法中調用另一個方法時荷逞,也沒有必要在被調用方法名前面加上”this.”(雖然合法)媒咳,只是知道調用方法時有這么個機制就行。說完隱式种远,那么顯式的this怎么用涩澡?用來干嘛?
- 類中引用屬性院促,防止重名
class Apple {
int weight;
Apple (int weight) {
this.weight = weight;
}
}
????????當方法尤其是構造方法的參數中有與屬性同名的參數,且在方法體中操作了同名的屬性(這種情況是怎么發(fā)生的呢斧抱?)常拓,則需要在屬性前面飲用this關鍵字,防止混淆辉浦。雖然寫代碼時對象還沒創(chuàng)建出來弄抬,但是當jvm執(zhí)行到這段代碼時,this已經被賦值——相當與提前占位了宪郊。this在構造方法中使用也是合法的掂恕,這意味著執(zhí)行構造方法的方法體時,對象已經創(chuàng)建出來了弛槐,構造方法的方法體只是在初始化對象的屬性懊亡。
- 構造方法之間相互調用
class Apple {
int weight;
Apple(int weight) {
this.weight = weight;
}
Apple() {
this(4.5);
}
}
????????普通的重載方法之間可以直接調用,但是構造方法卻不可以乎串,像這樣是會報錯的:
Apple() {
Apple(4.5);
}
????????這時只能用this( )來代替對構造方法的調用店枣。還需要注意的是,this()的調用只能發(fā)生在構造方法內叹誉,普通方法內部不能調用鸯两;而且必須是方法體的第一條語句。如下的語句會報錯:
Apple() {
int a = 9;
this(4.5);
}
????????上面三個規(guī)則是為什么呢长豁?
初始化
????????對象的初始化指的是其屬性被賦值的過程钧唐。這個過程有三個節(jié)點:第一個是對象剛被分配空間時,這個節(jié)點可以形象的理解為“清零”匠襟,即將這塊內存區(qū)域以前使用的痕跡抹去钝侠,換成屬性對應類型的默認值,比如int類型賦值0酸舍,引用類型賦值null机错。這個賦值過程是jvm自動執(zhí)行的,無需人工干預父腕。這個特點是Java語言的一大賣點弱匪,C++就不會做這種“多余”的動作。這么做的好處是更安全,對象的初始化狀態(tài)不是一堆亂起八糟的“前任”值萧诫,而是經過清理的斥难。
????????需要注意的是,這個清理動作只發(fā)生在對象創(chuàng)建的時刻帘饶,或者說只對屬性變量有效哑诊;對于局部變量,也就是方法體內臨時定義的變量及刻,jvm不會有這個動作镀裤,所以必須手動初始化——給變量賦值,否則無法通過編譯缴饭。(這又是為啥呢暑劝?一個猜測是對象的初始化發(fā)生在堆上,jvm可以干預屬性變量的賦值颗搂;而局部變量在棧上担猛,jvm干預不了)
第二個節(jié)點就是在聲明變量的同時賦值,如下:
class Apple {
int weight = 3;
}
????????這樣的屬性值是寫死的丢氢,或者說硬編碼的傅联,缺乏靈活性:每個new出來的對象,其weight屬性都是3疚察。當然某些情況下需要這樣做蒸走。
????????第三個節(jié)點就是構造方法了,在構造方法內給屬性賦值貌嫡,可以是固定值载碌,類似于第二個節(jié)點;也可以是參數值衅枫,更靈活嫁艇。
????????至此,初始化過程就結束了弦撩,構造方法是最后的機會步咪。再往后對屬性值的改變就不屬于初始化了,比如各種setter方法益楼。有啥子區(qū)別嗎猾漫?我反正沒看出來。Spring給對象的屬性賦值時感凤,基本靠的是setter方法悯周,當然構造方法也是可以的。
static關鍵字
????????上面提到類的存儲空間中主要保存了方法代碼陪竿,而屬性保存在對象的存儲空間中禽翼,這兩個存儲空間是分開的。而實際上類的存儲空間中也可以保存屬性值,只要將屬性用static關鍵字修飾闰挡。
????????static代表靜態(tài)的锐墙,Java引入這個機制是為向所謂的“全局變量”妥協。被static修飾的變量长酗,可以被每個該類的對象訪問溪北,可以看成是共享的;由于存儲在類空間夺脾,所以可以直接通過類名訪問之拨,如下:
class Apple {
static String name = “apple”;
}
????????然后就可以這樣訪問了:
Apple.name;
????????從實現的角度,static變量跟對象無關咧叭,它存放在類空間蚀乔;但是從語義的角度,static變量用來表達所有對象都共有的屬性佳簸,比如上例中的name屬性乙墙,每個Apple對象或許weight屬性不同颖变,但是name都是一致的生均。一旦name發(fā)生了變化,所有對象的這個屬性也會跟著變化腥刹;從這個角度理解马胧,static變量的確是全局的。
????????static還可以用來修飾方法衔峰,表示這個方法跟具體對象無關佩脊,不必再通過對象名來訪問了(當然,static方法不會有隱式的this指針)垫卤。static方法表示那些可以直接可以直接以類名來訪問的方法威彰,畢竟創(chuàng)建對象是需要花費時間和空間的。static方法當然也可以通過對象來訪問穴肘,只是需要先創(chuàng)建一個對象歇盼,不如直接使用類名省事。
????????static還有一個重要用途是static塊评抚,當然它還是跟對象無關(static跟對象絕緣)豹缀,而是表示在加載類的時候需要做的事,如下:
class Apple {
static {
System.out.println(“init”);
}
}
????????類加載是發(fā)生在創(chuàng)建對象之前的慨代。與此對應邢笙,也可以創(chuàng)建沒有static修飾的塊,表示在創(chuàng)建對象時要做的事侍匙,如下:
class Apple {
{
System.out.println(“init”);
}
}
????????可以手動驗證下是否在構造方法執(zhí)行之前氮惯。
main方法
????????先思考一個問題:java的代碼是如何運轉起來的晴氨?類中的確定義了很多變量和方法,問題是誰來調用它們呢鬼悠?總不會自動執(zhí)行吧扩劝?你會說是在另一個類中被調用的,可另一個類又是被誰調用的铛纬?這樣調用來調用去厌均,最終的調用者是誰?是某個固定的類嗎告唆?這些問題的其實可以歸結為:java程序執(zhí)行的起點在哪兒棺弊?
????????在C或著C++中,都會有一個main函數擒悬,操作系統(tǒng)以這個函數為入口模她,順序執(zhí)行其中的代碼。Java也提供了這樣的機制——main方法懂牧,舉個例子:
public static void main(String[] args) {
Apple apple = new Apple(4);
apple.grow(4);
System.out.println(apple.weight);
}
????????Java程序都是從這里啟動并執(zhí)行的侈净。與C/C++不同的是,Java程序是由jvm來執(zhí)行的僧凤,而不是直接由操作系統(tǒng)來執(zhí)行畜侦。說到底,Java是解釋型的語言躯保,由jvm一行行解釋執(zhí)行java編譯后的字節(jié)碼旋膳;C/C++則直接編譯成了二進制的系統(tǒng)指令,操作系統(tǒng)可以直接拿來執(zhí)行途事。Java語言不是自舉的验懊,它無法自己執(zhí)行自己,需要依賴外部力量——jvm一般是由C++編寫實現的尸变。jvm本身也是運行在操作系統(tǒng)中的程序义图,main方法是jvm程序與java程序之間的接口,像是約定好的暗號召烂,在命令行輸入以下命令:
java Apple
????????jvm啟動碱工,尋找并加載Apple類,檢查類中是否有入口——main方法骑晶,而不是別的其他方法痛垛,然后開始執(zhí)行其中的代碼。
????????所以桶蛔,java程序的啟動工作都放在這個方法里面匙头,比如創(chuàng)建一個對象并執(zhí)行其中某個方法。main方法與其他普通方法的唯一區(qū)別在于:它是唯一可以被jvm識別的方法仔雷。當然蹂析,main方法也可以被其他類調用舔示,此時它就是個普通靜態(tài)方法。
????????main方法可以出現在任何類中电抚,只要你想將它作為程序的起點和入口惕稻;但每個jvm進程每次只能啟動一個入口,如果項目中還有其他的類也有main方法蝙叛,那么需要再啟動一個jvm進程來執(zhí)行它——兩個進程之間是互不干擾的俺祠。