java上下轉型與多態

如果你對於java中的上下轉型,多態有些困惑或者感覺難以記住的話,相信這篇博客一定會讓你的理解更加深入。
(下面從邏輯方面進行闡述,真實內存到底如何存放數據,請勿以此爲依據。)

1.引用類型與實例類型

2.上轉型

3.下轉型

4.字段影響

5.方法影響

6.什麼情況下才能進行上下轉型

7.多態

8.總結

先放兩張圖,然後開始慢慢說起,圖在後面會用到。
在這裏插入圖片描述
在這裏插入圖片描述

1.引用類型與實例類型

爲什麼要先說這個呢?
我們知道,java中的引用類型與實例對象類型是不一樣的。

S s = new S();

有一個一個類S,S s 是在內存中新建了一個S類引用類型對象s,接着 new S() 是在內存中新建了一個S類實例對象, = 將這個實例對象賦給引用類型對象s,也就是s現在指向了這個實例對象。

S s1 = s;

現在我們又新建了一個S類引用對象s1,將s賦給s1,這個又是什麼意思呢,跟我們想象中的拷貝不一樣,內存中的S類實例對象只有一個,這行代碼是將 s指向的對象賦給了新建的引用類型s1,也就是說,現在s1跟s都指向了這個實例對象。

做個小小的實驗驗證一下:

public class S {

	int age = 1;
	
	public static void main(String[] args) {
		
		S s = new S();
		System.out.println("s.age:"+s.age);
		S s1 = s;
		s1.age = 2;
		System.out.println("s.age:"+s.age); 
		
	}
	
}

輸出:

s.age:1
s.age:2		

可以看到,我們並沒有直接改變s的age屬性,但是值卻發生了改變,說明兩個引用類型確實指向的是一個實例對象。

在上面的基礎上現在可以說說上下轉型的邏輯了。

現在有兩個類,F類和S類,S類繼承於F類。

2.上轉型

S s = new S();
F f = s;

這個就是上轉型,它的邏輯是什麼呢?

我們先新建了S類引用類型對象s,並指向一個S類實例對象。第二行代碼:新建了一個S基類F類引用類型對象f,並將它指向引用類型s指向的S類實例對象。


3.下轉型

S s = new S();
F f = s;
S s1 = (S)f;

這個是下轉型。

前面兩行於=與上面一樣,重點是第三行,又新建了一個S類引用類型對象s1,並將它指向引用類型f指向的S類實例對象。

上面只是介紹了上下轉型的大致流程,你應該還是很疑惑,那麼到底它對於類中字段,方法會帶來什麼影響?以及爲什麼使用上下轉型?什麼時候才能進行上下轉型?通過在內存中的執行我們可以慢慢看到答案

我們可以想象引用類型對象在內存中包含很多個指針,每個指針分別指向實例對象中的一個字段或方法,那麼指針到底有多少個?進行不同的上下轉型操作時,指針的是怎麼改變指向的內容的呢?


4.字段影響

先放代碼:

public class F {
	
	int age = 1;
	String name = "F";

}

public class S extends F {

	int age = 2;
	String id = "123";
	
	public static void main(String[] args) {
	
		F f = new F();
		
		S s = new S();
		System.out.println("age:"+s.age+" id:"+s.id+" name:"+s.name);
		
		f = s;	
		
//		System.out.println("id:"+f.id);		這行代碼出錯,說明上轉型後並不能調用id屬性
		System.out.println("age:"+f.age+" name:"+f.name);	
		
		S s1 = (S)f;
		System.out.println("age:"+s1.age+" id:"+s1.id+" name:"+s1.name);
		//下轉型後,age值再次改變,說明在S實例對象內存區存在兩個“age”,
	}
	
}

是不是感覺很難理解,我們需要配合上面的第一張內存圖來理解

在這裏插入圖片描述

F f = new F();

f引用類型中包含兩個字段指針:age和name,分別指向了實例對象的age字段和name字段

S s = new S();

由於S類繼承了F,所以它的實例對象字段區會存在F類的字段age(F)和name,然後還會存在自己新增的兩個字段age(S)和id,共4個字段。然而,S類引用類型s中只有三個指針,這是因爲S新增的age字段和F類字段同名,所以,關鍵的是,要看清這個age字段指針指向的是實例對象中的age(S),也就是子類新增的age字段。

f = s;

現在將基類F的引用類型對象f指向s指向的實例對象,f中有兩個字段指針,name字段指針指向name字段很容易理解,本來就是繼承基類的屬性,而age字段指針呢,指向的是age(F).

S s1 = (S)f;

這一句就很好理解了,新建了S類引用類型對象,指向f指向的S類型實例對象,跟上圖中間的指向是一樣的嘛,也合乎常理。

小結:

基類的引用類型中的字段指針與基類實例對象中的字段個數是相同的。

基類的子類,實例對象的字段,不管與基類是否同名,其個數永遠等於 基類字段個數+子類字段個數,而子類的引用類型中的字段指針,如果碰到同名的字段,指向的都是子類新增的字段。

上轉型時,基類字段指針遇到同名字段,指向的是基類創建的字段,其他都一一對應。

下轉型時,子類字段指針遇到同名字段,再次指向子類新增的字段,其他也一一對應。


5.方法影響

public class F {

	public void eat() {
		System.out.println("F-eat");
	}
	
	public void drink() {
		System.out.println("F-drink");
	}
}

public class S extends F{

	@Override
	public void eat() {
		System.out.println("S-eat");
	}
	
	public void walk() {
		System.out.println("S-walk");
	}
	
	public static void main(String[] args) {
				
		F f = new F();
		S s = new S();
		
		f = s;
		f.eat();
		f.drink();
//		f.walk();	代碼錯誤,說明上轉型不能調用原子類非重寫方法
		
	}
	
}

方法與字段流程相似,不在細緻分析,我們只找比較關鍵的分析。
在這裏插入圖片描述
與字段不同的是,子類如果新增方法與基類同名,在內存中此新增方法將會覆蓋掉原來基類創建的方法,也可以說基類的方法在子類對象的內存中不再存在。
上轉型時,基類方法指針在子類實例對象中只能找到子類覆蓋後的方法,因此只能指向這個被覆蓋的方法,因此在調用時會出現多態後面再詳細闡述。
下轉型時,所以方法指針都指回原來一一對應的方法,沒用什麼特殊的地方。

小結:

基類的引用類型中的方法指針與基類實例對象中的方法個數是相同的。

基類的子類,實例對象的方法,其個數等於 基類方法個數+子類與基類不同名方法個數,子類對象如果方法與基類方法同名,會將其覆蓋掉,因此子類的引用類型中的方法指針與子類實例對象中的方法個數是相同的,一一對應。

上下轉型時,方法指針與方法都是一一對應的。


6.什麼情況下才能進行上下轉型

A a = new B();

對於上轉型,只要引用類型對象a是實例對象b的祖先類,就可以進行上轉型,很容易理解,基類中的指針數少於子類中的指針數,且每個指針都能在其中找到名字一一對應的。

A a = (A)b;

對於下轉型,只有引用類型A是b所指向的實例化對象的類型的基類或者相同,就可以進行下轉型與上面原理相似。


上下轉型的本質原則都是基類引用類型對象能夠指向子類實例類型對象 !!!!!!!!


7.多態

到了這裏就要說一說多態了,上面其實已經涵蓋了多態,我們之所以單獨拿出來講講,是因爲它的用途實在是廣泛。

public class F {

	public void speak() {
		System.out.println("我是father");
	}
	
}
public class S1 extends F {

	public void speak() {
		System.out.println("我是son-1");
	}
	
}
public class S2 extends F {

	public void speak() {
		System.out.println("我是son-2");
	}
	
}
public class Test {

	public static void main(String[] args) {
		
		F f = new F();
		S1 s1 = new S1();
		S2 s2 = new S2();
		
		f = s1;
		f.speak();
		f = s2;
		f.speak();
		
	}
	
}

輸出:

我是son-1
我是son-2

同一個基類引用類型對象f,在指向不同子類實例對象時,執行同一個函數,會出現不同的結果,這就是多態。


經過上面我們的分析,你現在還看不懂多態嗎?

8.總結

我們從內存的角度,對執行過程特殊的地方來進行一次總結。

1.基類建立:

指針與實例對象中字段和方法一一對應。

2.子類建立:

新增字段(包括重名)都會建立內存,字段指針除了與基類同名的,其他也會新增,字段指針指向的時候一一對應,遇到內存中存在兩個相同名字的字段時,指向子類新增的那個。

新增方法(除了重名)都會建立內存,重名的方法,新增方法會覆蓋原來方法,方法指針除了與基類同名的,其他也會新增,方法指針指向的時候一一對應。

3.上轉型時:

基類字段指針一一指向對應名字字段,當遇到存在兩個相同名字的字段時,指向基類創建的那個。

基類方法指針一一指向對應名字方法。

4.下轉型時:

子類字段指針對應名字一一指向子類對象字段,遇到內存中存在兩個相同名字的字段時,指向子類新增的那個。

子類方法指針一一指向對應名字方法。

對於字段:可調用自己或者父類以上創建的字段,哪種類型引用對象調用就指向那種類型創建的字段。

對於方法:可調用自己或者父類以上創建的字段,不管哪種引用類型對象調用,根據實例對象類型重寫後的方法執行(未被重寫就按原來的執行)。


現在你還有什麼不理解的地方嗎?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章