構造方法,this,super,final,static
1構造方法
一個Person類,屬性都被private了,外界無法直接訪問屬性,必須對外提供相應的set和get方法。但如果需要在建立物件的同時明確物件的屬性值,就需要構造方法了。
1.1定義
構建創造時用的方法,即就是物件建立時要執行的方法。
格式:
修飾符 構造方法名(引數列表)
{
}
1.2特點
1)構造方法沒有返回值型別。也不需要寫返回值。因為它是為構建物件的,物件建立完,方法就執行結束。
2)構造方法沒有具體的返回值。
3)構造方法名稱必須和類名保持一致。
這就可以解釋了,建立物件時,要加一個擴號,這不就是在呼叫方法嘛,
Person p=new Person();
例:
public class Person { private String name; private int age; public Person(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
public class Test { public static void main(String[] args) { Person p2=new Person("張三",18); System.out.println(p2.getName()); System.out.println(p2.getAge()); } }
1.3記憶體圖
步驟解釋:
1 main方法進棧
2在堆中開記憶體,賦初值
3有參構造方法進棧
4傳參,賦值
5有參構造彈棧
6地址賦值給p2
7 getname進棧,通過地址找到name,列印
8 getage進棧,通過地址找到age,列印
1.4預設構造方法
編譯器在編譯java檔案時,如果發現類中沒有構造方法,會自動新增一個空參構造。
但如果類中只要有一個構造方法,則不會自動新增空參構造了。
如果寫了有參構造方法,那麼new物件時,再用空參的就會報錯。
所以如果寫了有參構造,最好把空參構造也加上。因為使用空參構造的情況更多。有參構造用的比較少,一般測試時用。
當描述的事物在建立其物件時就要明確屬性的值,就用有參的
若建立物件時不需要明確具體的資料,可以不用寫構造方法,因為會自動加預設的空參構造方法。
還是上面的例子,因為Person定義了有參,所以test再呼叫空參就會報錯:
1.5注意的細節
1)一個類中可以有多個構造方法,(空參、部分成員變數是引數、全部成員變數都是引數)這其實是方法過載。
2)構造方法可以被private修飾,這樣其他程式無法建立該類的物件了。
如果類比較重要,就可以這樣,構造方法用private修飾,不讓建立物件。
其他方法可以用static修飾,那麼就可以直接用類.方法,這樣呼叫了。
例如System類,就是這樣的,
在eclipse中,在類上面,ctrl+點選該類,可以檢視原始碼:
1.6構造方法和一般方法區別
構造方法在物件建立時就執行了,而且只執行一次。
一般方法是在物件建立後,需要使用時才被物件呼叫,並可以被多次呼叫。
物件在建立之後需要修改和訪問相應的屬性值時,在這時只能通過set..或者get..方法來操作。
所以構造方法可以不寫,get set必須寫
例:
class Person { void Person() { } }
class PersonDemo { public static void main(String[] args) { Person p = new Person(); } }
這段程式碼的Person前面有返回值,所以不是構造方法,那麼就會自動新增一個空參構造。所以程式碼是可以執行的,可以new物件的。但是不建議這樣寫,可以看到在eclipse中出現警告:
2 this關鍵字
之前用this可以解決成員變數和區域性變數同名的問題。
this代表的是物件,哪個物件呼叫了this所在的方法,this就代表哪個物件。
this還可以用於在本類中構造方法之間的呼叫。
2.1格式:
this(引數列表);
注意:只能構造方法裡用this(),普通方法裡不能用this()。
例:
public class Person { private String name; private int age; public Person(){ //呼叫本類構造方法 this("李小華",19); } public Person(String name,int age){ this.name=name; this.age=age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
public class Test { public static void main(String[] args) { Person p1=new Person(); System.out.println(p1.getName()); System.out.println(p1.getAge()); Person p2=new Person("張小美",18); System.out.println(p2.getName()); System.out.println(p2.getAge()); } }
這裡的this("李小華",19);
相當於:
但是,this必須寫在第一行,如果這樣,是不行的
原因是初始化動作要最先執行。
2.2記憶體圖
步驟解釋:
1main進棧,看到new,堆中開記憶體,屬性賦初值
2空參進棧
3找第一個引數是name,age的構造方法,進棧,並把引數傳過去
4賦值
5有參構造彈棧,空參構造彈棧
6地址賦給p
7getName進棧,列印,getAge進棧,列印
例:判斷兩個人是否是同齡人
(之前程式碼)
3 super關鍵字
3.1可以用於子父類中構造方法的呼叫
例:
public class Person { public Person(){ System.out.println("這是父類構造方法"); } }
public class Student extends Person{ public Student(){ System.out.println("這是子類構造方法"); } }
public class Test { public static void main(String[] args) { Student s=new Student(); } }
為什麼會這樣呢?因為在建立子類物件時,父類的構造方法會先執行,
子類中所有構造方法的第一行有預設的隱式super();語句。
格式:
呼叫本類中的構造方法
this(實參列表);
呼叫父類中的空引數構造方法
super();
呼叫父類中的有引數構造方法
super(實參列表);
3.2 但是,如果父類定義了有參構造,那麼子類則必須呼叫super(引數),必須手動寫上。
因為父類不會自動建立空參構造了,所以子類的super()就找不到了。
public class Person { public Person(String name){ System.out.println("這是父類構造方法"); } }
改成這樣就可以了:
所以一個類只要定義了有參構造,一定再寫一個空參構造,因為有可能被繼承,不寫會導致子類報錯。
3.3為什麼子類物件建立都要訪問父類中的構造方法
例:
public class Person { int a=1; public Person(String name){ a=5; System.out.println("這是父類構造方法"); } }
public class Student extends Person{ public Student(){ super("張三"); System.out.println("這是子類構造方法"); } }
public class Test { public static void main(String[] args) { Student s=new Student(); System.out.println(s.a); } }
這裡Student是Person的子類,因為Person中在構造方法改變了成員變數的值,所以如果Student不訪問Person的構造方法,那麼就得不到Person中真正的成員變數的值了,也就不是真的繼承了。
總結:
子類會繼承父類中的內容,所以子類在初始化時,必須先到父類中去執行父類的初始化動作。這樣,才可以使用父類中的內容。
子父類是會一級一級的呼叫的,最終是object。
3.4如果有this()
如果子類的構造方法第一行寫了this呼叫了本類其他構造方法,那麼super呼叫父類的語句就沒有了。因為this()或者super(),只能定義在構造方法的第一行,因為初始化動作要先執行。
例:
public class Animal { public Animal(int age){ } }
這裡寫了this(),那麼意思就是呼叫的下面的那個有參構造,而這裡面有super,所以,上面就不能再寫super了,會報錯。
3.5總結:
1)如果有this();那麼就不會有super();了。不寫也不會自動加了。
2)構造方法只要直接或間接可以呼叫父類構造方法就可以。
3)this和super都是隻能在構造方法中使用
在eclipse中也可以有構造方法的快捷方式:
右鍵--source--Generate Constructor using Fields
可以點任意個,一個不選,就是空參構造。
可以看到,點出來後,第一行就是super();
3.6 super練習
描述學生和工人這兩個類,將他們的共性name和age抽取出來存放在父類中,並提供相應的get和set方法,同時需要在建立學生和工人物件就必須明確姓名和年齡。
public class Person { private String name; private int age; public Person(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
public class Student extends Person{ public Student(String name, int age) { super(name, age); } }
public class Worker extends Person{ public Worker(String name, int age) { super(name, age); } }
public class Test { public static void main(String[] args) { Student s=new Student("小紅帽",18); System.out.println(s.getName()); System.out.println(s.getAge()); Worker w=new Worker("大灰",81); System.out.println(w.getName()); System.out.println(w.getAge()); } }
super通常是這麼用的,父類的構造方法既可以給自己的物件初始化,也可以給自己的子類物件初始化。
總結:
this的三個作用:
解決成員變數和區域性變數同名問題
代表本類物件,呼叫本類中的變數和方法
構造方法之間的呼叫
super的兩個作用:
代表父類物件,呼叫父類的變數和方法
子父類中構造方法的呼叫
4 final關鍵字
有些類在描述完之後,不想被繼承,或者有些類中的部分方法功能是固定的,不想讓子類重寫。這時需要使用關鍵字final,final的意思為最終,不可變。final是個修飾符,可以用來修飾類,類的成員,以及區域性變數。不能修飾構造方法。
4.1 final修飾類
final修飾類不可以被繼承,但是可以繼承其他類(也叫太監類)
例:
public class Person { } public final class Father extends Person{ }
這裡的father就不能被繼承了。
4.2 final修飾方法
final修飾的方法不可以被子類重寫,但父類中沒有被final修飾方法,子類重寫後可以加final。(也叫太監方法)
例:
public final class Father{ public final void method1(){ } public void method2(){ } }
public class Son extends Father{ public final void method2(){ } }
這裡son可以重寫method2,不能重寫method1
4.3 Final修飾區域性變數
1)區域性變數是基本資料型別(稱為常量):一旦賦值,終身不變
2)區域性變數是引用資料型別:一旦賦值一個地址,終身不變。但是地址內的物件屬性值可以修改。
4.4 Final修飾成員變數
必須手動賦值或構造方法賦值。因為成員變數有預設值null,不賦值就終身是null了。
只要在分配地址之前(也就是建立物件之前)賦值就可以。
例:
這樣才可以:
或這樣:
public class Person { final String name2; public Person(String name2){ this.name2=name2; } }
5 static關鍵字
被static修飾的成員變數屬於類,不屬於這個類的某個物件。
被static修飾後就共享了(有一個改變,就全變了)。
5.1例:
public class Student { private String name; private String schoolname; public Student() { } public Student(String name, String schoolname) { this.name = name; this.schoolname = schoolname; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSchoolname() { return schoolname; } public void setSchoolname(String schoolname) { this.schoolname = schoolname; } }
public class Test { public static void main(String[] args) { Student s1=new Student("小紅","清華大學"); Student s2=new Student("小明","清華大學"); Student s3=new Student("小亮","清華大學"); s2.setSchoolname("北京大學"); System.out.println(s1.getSchoolname()); System.out.println(s2.getSchoolname()); System.out.println(s3.getSchoolname()); } }
說明schoolname是屬於每一個物件的
改:private static String schoolname;
這時schoolname是共享的了,是屬於Student類的了。
5.2記憶體圖
(資料共享區也叫 靜態區)
記憶體圖步驟說明:
1 test.class進共享區,掃描到main,student.class,也進入
2因為main是static,所以main進靜態區
3掃描student.class,有static的schoolname,就進靜態區,並賦值初始值null
4 main方法進棧
5 有new,就在堆中開記憶體,找到屬性 name賦值null(這時不會再找schoolname了,因為已經在靜態區了)
6 分配地址
7 構造方法進棧,傳值:小紅,清華大學
8 this.name,找到地址,然後找到name,賦值小紅
9 根據地址去靜態區找schoolname(這裡應該用類.變數才對,但是用物件.變數還是能找到的)靜態區的schoolname被改值為清華大學
10構造方法彈棧
11 地址賦值給s1
12 s2的new,s3的new,過程是一樣的...
13 執行到s2.setSchoolname("北京大學")這一步時,靜態區schoolname被改成北京大學
14 後面System.out.println訪問的就全是北京大學了
5.3 Static特點
1)屬於類,不屬於物件
如果一個物件將static成員變數值進行了修改,其他物件的也跟著變,就是共享一個。
2)通過類名直接呼叫,不建議用物件呼叫(會有警告)
這樣就可以:
5.4注意的地方
1)不能用this和super呼叫(因為this代表本類物件,super代表父類物件,但是被static後已經不屬於物件了)
2)靜態內容是優先於物件存在,只能訪問靜態
3)同一個類中,靜態成員只能訪問靜態成員,不能訪問非靜態
簡單記:
非靜態可以調靜態
靜態不能調非靜態
因為static是進共享區的,所以可以理解為先有static show2,後有age,所以在show2中找不到age。
這也解釋了,一個類中有main方法時,其他方法必須加static,才可以呼叫。
這裡的methods();其實寫全了,是Test.methods();
如果方法不加static,就得new一個test物件,來呼叫(因為物件能呼叫變數和方法)
5.5特殊的地方
1)main方法是特殊的,main方法為靜態方法僅僅為程式執行入口,它不屬於任何一個物件,可以定義在任意類中。main是虛擬機器呼叫。
2)多型中也有特殊:
多型呼叫方法中,編譯看左邊,父類有,編譯成功,父類沒有,編譯失敗
執行靜態方法,執行父類中的靜態方法,
執行非靜態方法,執行子類的重寫方法(因為靜態了,就屬於類了)
成員變數,編譯執行全是父類
例:
public class Father { static int i=1; public static void f(){ System.out.println("這是父類靜態方法"); } }
public class Son extends Father{ static int i=2; public static void f(){ System.out.println("這是子類重寫後的靜態方法"); } }
public class Test { public static void main(String[] args) { Father f=new Son(); System.out.println(f.i); f.f(); } }
5.6定義靜態常量
靜態常量一旦賦值,終身不變。可以類.變數名呼叫。
5.6.1定義格式:
public static final 資料型別 變數名 = 值;
(這裡不一定非要Public,也可以私有,但是沒意義,所以都是public)
5.6.2規則
static寫final前面,
變數名全部大寫,多個單詞使用下劃線連線。
例:public static final String SCHOOL_NAME = "北京大學";
Math.PI也是靜態常量
5.6.3介面中的每個成員變數都預設使用 public static final 修飾。
所有介面中的成員變數已是靜態常量,由於介面沒有構造方法,所以必須顯示賦值。可以直接用介面名訪問。(因為用final定義的成員變數必須賦值)