Java進階---對象與內存控制(三)
3、父子實例的內存控制
在正是開始之前,先介紹一下面向對象的三大特徵,有助於我們理解文章:
> 封裝:屬性可用來描述同一類事物的共同特徵,行爲可以描述同一類事物都可做的操作,而封裝就是要把屬於同一類事物的共性(包括共同的屬性和行爲)歸到同一個類中,以方便使用。
> 繼承,一個類如果擁有另一個封裝類的所有屬性和行爲,且擁有區別於其他類屬性或方法,那麼可以讓這個類繼承這一個封裝的類,這樣我們就不用在這個類中定義封裝類中所有的屬性和行爲卻擁有了這些屬性和行爲,從而使代碼複用,達到簡化代碼的效果。
> 多態:站在抽象的層面上去實施一個統一的行爲,到個體(具體)的層面上時,這個統一的行爲會因爲個體(具體)的形態特徵而實施自己的特徵行爲。父類層擁有統一行爲,這個行爲實際上是對此父類的多個子類所重寫的該行爲的抽象,即此行爲可以代表其所有子類的行爲;子類層重寫的父類的行爲擁有自己區別於其父類的其他子類的特徵。這就是多態機制的體現。
1)、繼承成員變量和繼承方法的區別
我們都知道,當訪問權限允許的情況下,子類可以直接訪問父類中定義的成員變量和方法。但是這裏是哪種繼承方式呢?下面我們通過一個例子來分析討論:
class Persion{
String name = "小武";
public void display(){
System.out.println("Persion中的display():" +this.name);
}
}
class Man extendsPersion{
String name = "靈靈";
@Override
public void display() {
System.out.println("Man中的display():" +this.name);
}
}
public classtest01 extends Persion{
public static void main(String[] args) {
Persion p = new Persion();
System.out.println("p.name"+ p.name);
p.display();
Man man = new Man();
System.out.println("man.name"+ man.name);
man.display();
Persion pm = new Man();
System.out.println("pm.name:"+ pm.name);
pm.display();
Persion pm2 = man;
System.out.println("pm2.name"+ pm2.name);
pm2.display();
}
}
輸出結果:
下面分析程序執行過程。前四行輸出大家都明白,從第五行開始講起:
A. Persionpm = new Man();雖然我們聲明瞭一個Persion對象,但始終要牢記此時初始化的應該是一個Man對象,此時Persion當中的name應該是Persion.name=”小武”,所以pm.name=”小武”,輸出第五行;此時Persion當中的display()(即pm.display())被Man重寫,所以應該稱爲Man.display(),所以輸出的是”靈靈”,輸出第六行;
B. Persionpm2 = man;man變量是對Man對象的引用,pm2變量是對Persion對象的引用,他們都指向同一塊內存空間。但是pm2.name仍然是Persion對象中的name,pm2.display()卻是Man.display(),輸出第七八行;
2)、內存中子類的實例
下面再介紹一種一段情況,通過分析變量所引用的Java對象在內存中的分配,可以更好地理解Java的內存分配機制:
class Persion{
String name = "小武";
}
class Man extendsPersion{
String name = "靈靈";
}
public classtest01 extends Man{
String name = "小武靈靈";
public static void main(String[] args) {
test01 t = new test01();
Man m = t;
Persion p = t;
System.out.println("test01.name:"+ t.name);
System.out.println("Man.name"+ m.name);
System.out.println("Persion.name"+ p.name);
}
}
輸出結果爲:
這意味着t、m、p這3個變量所引用的Java對象(test01)所引用的Java對象擁有3個name實例變量,也就是說需要3塊內存存儲它們。
這個test01對象不僅存儲了它自身的name實例變量,還需要存儲從Man、Persion兩個父類那裏繼承到的name實例變量,但這3個name實例變量在底層是有區別的,分別是test01.name、Man.name、Persion.name。
下面我麼將程序改一下,看眼一下對super的瞭解:
class Persion{
String name = "小武";
int number = 4100;
}
class Man extendsPersion{
String name = super.name;
int number = 9160;
}
public classtest01 extends Man{
String name = "小武靈靈";
public static void main(String[] args) {
test01 t = new test01();
}
public test01() {
System.out.println("super.name:"+ super.name);
System.out.println("super.number:"+ super.number);
}
}
程序輸出結果爲”小武”和”9160”;
程序分析:輸出小武可能沒有問題,但是輸出9160證明了一點,那就是super.number中的super代表引用該test01對象的關聯的對象Man,即該對象的第一級繼承對象。
所以,使用super應注意以下兩個方面:
> 子類方法不能直接使用returnsuper;,但使用return this;返回調用該方法的對象是允許的;
> 程序不允許直接把super當成變量使用,例如判斷super和一個變量是夠引用同一個Java對象時不能使用super == a。
下面再給一個使用super的程序,我給出輸出結果,大家自己分析吧:
class Persion{
String name = "小武";
int number = 4100;
}
class Man {
String name = "小武";
public Man getMan() {
return this;
}
public void output(){
System.out.println("Man中的output方法");
}
}
public classtest01 extends Man{
String name = "靈靈";
public void output() {
System.out.println("test01中的output方法");
}
public void getSuperVoid(){
super.output();
}
public Man getSuper(){
return super.getMan();
}
public static void main(String[] args) {
test01 t = new test01();
Man m = t.getSuper();
System.out.println("t和m所引用的對象是否相同:" + (t == m));
System.out.println("訪問t所引用的name實例變量:" + t.name);
System.out.println("訪問m所引用的name實例對象:" + m.name);
t.output();
m.output();
t.getSuperVoid();
}
}
輸出結果:
總結父、子對象在內存中的存儲規則:當程序創建一個子類對象時,系統不僅會爲該類中定義的實例變量分配內存,也會爲其父類中定義的所有實例變量分配內存,即使子類定義了與父類同名的實例變量。如果子類裏定義了與父類中已有變量同名的變量,那麼子類中定義的變量會隱藏父類中定義的變量併爲其分配內存控件,而非覆蓋父類中的變量。爲了在子類方法中訪問父類中定義的、被隱藏的實例變量,或者爲了在子類方法中調用父類中被其重寫的方法,可以通過super.作爲限定來修飾這些實例變量和實例方法,例如上面的程序中調用Man中的output方法,使用super.output或者((Man)t).output();
3)、父、子類的類變量
上面講了父、子類的實例在內存中的分配,其實父、子類的類變量基本與其類似。所不同的是類變量屬於類本身,而實例變量則屬於Java對象;類變量在類初始化階段完成初始化,而實例變量在對象初始化階段完成初始化。
下面是一個使用super.作爲限定來訪問父類中定義的類變量:
class Man {
static String name = "小武";
}
public classtest01 extends Man{
static String name = "靈靈";
public static void main(String[] args) {
test01 t = new test01();
t.output();
}
public void output(){
System.out.println("訪問test01類的name變量:" + name);
System.out.println("訪問test01的父類的name變量:" + Man.name);
System.out.println("通過super.訪問test01的父類的name變量:"+ super.name);
}
}
輸出結果中的name依次爲靈靈、小武、小武。
總結訪問父類的類變量的方法有三種:
>使用父類的類名作爲主調來訪問類變量;
>創建父類的對象的引用來訪問類變量;
>使用super.作爲限定來訪問類變量。