在之前的代碼講解中,我們將一類事物封裝成一個類,再去創建一個相應的對象去調用方法完成相應的操作這是非常方便的,但是在之前的代碼我們發現,對象不僅僅能調用類中的方法,還可以訪問到該類中的成員變量並進行賦值,在點座標中只要是int類型的值我們都取值,這是因爲座標的涵蓋數值是非常廣的,那麼如果一個類的成員變量是其他含義,那麼對值的隨意改變就是不合理的了,比如如果一個成員變量是爲年齡(age),那麼年齡的取值範圍是從0歲到100歲的(假設最大爲100),那麼我們如果將age的成員變量的修飾權限還定義爲public的話此時是不符合常理的,這樣會在後續的代碼在運行的過程中 會偏離預期結果 甚至會出錯,而且後期也會很難維護。有人可能會說在賦值的時候加上一個判斷,這樣就可以避免了,這是一個好的想法,但是如果在主函數中加上判斷,這樣的面向對象是沒有任何意義的,因爲我們面向對象的思想是調用方便,儘量減少在主函數中的操作,那麼我麼應該在哪兒加呢?
訪問器、修改器
這裏應該將成員變量進行私有化修飾,這樣外界就不會訪問到了,避免了對數據域的直接修改,那稱 爲數據域封裝,麼如何可以進行合理的修改呢?爲了能夠訪問私有數據域,可以提供一個 get 方法返回數據域的值。爲了能夠更新一個數據域,可以提供一個 set 方法給數據域設置新值。get 方法也被稱 爲訪問器(accessor), 而 set 方法稱爲修改器(mutator),這樣在修改器中加上判斷條件是很合理的,那麼具體怎麼實現呢?聲明如下:
public 返回值類型 get+成員變量名首字母大寫(){ //訪問器一定有返回值
return this.+該成員變量;
}
public void set+成員變量名首字母大寫(該成員變量數據類型 該成員變量名){ //修改器一定有參數的傳遞
(在賦值之前可以加判斷,判斷修改的值是否合理)
this.+該成員變量 = 傳進來的成員變量;
}
既然修改成員變量的值是通過對象來調用的,那麼對象是如何創建的呢?
構造函數
我們知道在定義一個實體類之後,在主類或者其他的類中,當然在當前類中都是可以創建該類的對象的,而且我們知道使用new關鍵字就可以創建一個對象出來,其實在程序中,對象的創建是和構造函數相關的,如下面的代碼
public class Animal{ //實體動物類
private int age;
private String name;
public void setAge(int age){ //成員變量年齡的修改器
if(age<0){
age=0;
}else{
this.age = age;
}
public int getAge(){ //成員變量年齡的訪問器
return this.age;
}
public void setName(String name){ //成員變量名字的修改器
this.age = age;
}
public String getName(){ //成員變量名字的訪問器
return this.name;
}
public void sleep(){
System.out.println(age+"歲的"+name+"正安靜的閉眼睡覺");
}
}
public class Demo01{ //主類
public static void main(String[] args){
Animal dog = new Animal(); //創建一個對象
dog.age = 8; //給年齡賦值
dog.name = "歡歡"; //給姓名賦值
dog.sleep();
}
}
我們發現實體類中,除了兩個成員變量之外就只有一個普通的sleep方法,剛說創建對象是和實體類的構造函數有關,但是我們並沒有看到構造函數,那麼對象是怎麼被創建出來的呢?
如果類中沒有定義任何構造函數的話 會有一個隱藏的無參構造函數,
那麼什麼是構造函數呢?構造方法是一種特殊的方法。它們有以下三個特殊性:
1.構造方法必須具備和所在類相同的名字。 (如上面代碼的構造函數爲:Animal(){ })
2.構造方法沒有返回值類型,甚至連 void 也沒有。
3.構造方法是在創建一個對象使用 new 操作符時調用的。構造方法的作用是初始化對象。
由此可以得出,沒有聲明任何構造函數時,他會有一個默認的不帶參數的構造函數,那麼如果聲明一個其他的構造函數並且是帶參數的,那麼默認的不帶參數的函數就不存在了,所以我們在創建對象時進行的初始化其實調用的是相應的構造函數,並在這些構造函數是要明確寫出來的,並不會默認構造,如上代碼我們可以優化:
public class Animal{ //實體動物類
private int age;
private String name;
Animal(){ } //無參構造函數
Animal(int age){ //帶有年齡參數初始化的構造函數
this.age = age;
}
Animal(String name){ //帶有名字參數初始化的構造函數
this.name = name;
}
Animal(int age,String name){ //帶有年齡和名字參數初始化的構造函數
this.age = age;
this.name = name;
}
public void setAge(int age){ //成員變量年齡的修改器
if(age<0){
age=0;
}else{
this.age = age;
}
public int getAge(){ //成員變量年齡的訪問器
return this.age;
}
public void setName(String name){ //成員變量名字的修改器
this.age = age;
}
public String getName(){ //成員變量名字的訪問器
return this.name;
}
public void sleep(){
System.out.println(age+"歲的"+name+"正安靜的閉眼睡覺");
}
}
public class Demo01{ //主類
public static void main(String[] args){
Animal dog1 = new Animal(); //創建一個內容爲null的對象
dog1.age = 8;
dog1.name = "歡歡";
dog1.sleep();
Animal dog2 = new Animal(7,"旺旺"); //創建一個對象,並將其姓名和名字進行初始化
dog2.sleep();
}
}
在上面的代碼中我們創建了不同的構造函數,用於不同狀態的對象的初始化,但是上面的代碼還是不夠簡化,很多都是this.+成員變量在進行賦值的時初始化,這裏我麼可以再將代碼進行簡化。我們知道this關鍵字指代的是當前的對象或是成員變量,用於區分與傳進來的參數。這裏我們可以藉助this關鍵字來進行構造函數的調用:
public class Animal{ //實體動物類
private int age;
private String name;
Animal(){ //無參構造函數初始化爲空,調用有兩個參數的構造函數進行空初始化
this(0,null);
}
Animal(int age){ //調用有兩個參數的構造函數進行年齡初始化,名字初始化爲空
this(age,null);
}
Animal(String name){ //調用有兩個參數的構造函數進行名字初始化,年齡初始化爲空
this(0,name);
}
Animal(int age,String name){
this.age = age;
this.name = name;
}
public void setAge(int age){ //成員變量年齡的修改器
if(age<0){
age=0;
}else{
this.age = age;
}
public int getAge(){ //成員變量年齡的訪問器
return this.age;
}
public void setName(String name){ //成員變量名字的修改器
this.age = age;
}
public String getName(){ //成員變量名字的訪問器
return this.name;
}
public void sleep(){
System.out.println(age+"歲的"+name+"正安靜的閉眼睡覺");
}
}
public class Demo01{ //主類
public static void main(String[] args){
Animal dog1 = new Animal();
dog1.age = 8;
dog1.name = "歡歡";
dog1.sleep();
Animal dog2 = new Animal(7,"旺旺");
dog2.sleep();
}
}
構造函數調用構造函數是可以,但是不能產生回調 只能一條線走到底
因此我們可以總結一下構造函數與成員函數的區別:
構造函數沒有返回值,成員函數可有可無
構造函數只有在創建對象時調用 ,成員函數在對象創建之後由對象調用
構造函數可以調用構造函數,無非是構造函數中某段代碼的封裝 不應該作爲對象的特有行爲 需要priva
構造函數可以調用成員函數,但成員函數不能調用構造函數
對象創建流程
那麼有了構造函數之後,來說一下對象的創建流程:
1.javac 編譯源代碼 生成若干個字節碼
2.java 將相關的字節碼文件加載進JVM中的方法區
3.JVM在方法區中找main主函數 加載進棧內存中運行
4.主函數進棧
5.創建對象的流程
5.1 在堆內存中開闢空間並分配地址
5.2 對成員變量進行【默認初始化】
5.3 相應的構造函數進棧
5.4 先對成員變量進行【顯式初始化】
5.5 再執行構造函數的代碼【針對性初始化】
5.6 構造函數出棧 對象創建完畢 返回地址
重載
在上面的代碼我們看到,我們聲明瞭很多的構造函數,它們的函數名都和類名相同只是參數的不同,但是在創建對象時,它們卻是無歧義的,我們稱這樣的函數叫重載函數(這裏又見面啦,在介紹函數時就已經提到過了),所以不僅成員函數有重載,構造函數也一樣有重載。
Animal(){ } //無參構造函數
Animal(int age){ //帶有年齡參數初始化的構造函數
this.age = age;
}
Animal(String name){ //帶有名字參數初始化的構造函數
this.name = name;
}
Animal(int age,String name){ //帶有年齡和名字參數初始化的構造函數
this.age = age;
this.name = name;
}