Java基礎10--多態--內部類

10-1,接口的應用

小例子:電腦USB接口的實現

interface USB { //暴露的規則
	public void open();
	public void close();
}
class BookPC {
	public static void main(String[] args) {
		useUSB(new UPan());
		useUSB(new UMouse());
	}
	public static void useUSB(USB u) {
		if(u != null) {
			u.open();
			u.close();
		}
	}
}
//實現規則
//這些設備和電腦的耦合性降低了。
class UPan implements USB {
	public void open() {
		System.out.println("UPan open...");
	}
	public void close() {
		System.out.println("UPan close...");
	}
}
class UMouse implements USB {
	public void open() {
		System.out.println("mouse open...");
	}
	public void close() {
		System.out.println("mouse close...");
	}
}

 

10-2,多態-概述

1,對象的多態性(多種形態):

class 動物{...}
class 貓 extends 動物 {...}
class 狗 extends 動物 {...}

之前我們創建 貓 對象的時候,用的方法是:

貓 mao = new 貓();

有了多態之後,創建 貓 對象可以這麼創建:

動物 dw = new 貓();

多態的創建方式,可以看出,一個對象有兩種形態。即貓這個事物既具備着貓的形態,又具備着動物的形態,這就是對象的多態性。

簡單的說,就是一個對象對應着不同的類型。

 

多態在代碼中的體現:父類或者接口的引用指向其子類的對象。

 

10-3,多態-好處

好處是:提高了代碼的擴展性,前期定義的代碼可以使用後期的內容。

舉例說明:

abstract class Animal {
	abstract void eat();
}
class Dog extends Animal {
	//實現父類中的抽象方法,實現動物的共性功能:吃飯
	void eat() {
		System.out.println("啃骨頭");
	}
	//定義自己的方法,只有狗才具備的功能:看家
	void lookHome() {
		System.out.println("看家");
	}
}
class Cat extends Animal {
	void eat() {
		System.out.println("吃魚");
	}
	void catchMouse() {
		System.out.println("抓老鼠");
	}
}
class Pig extends Animal {
	void eat() {
		System.out.println("飼料");
	}
	void gongDi {
		System.out.println("拱地");
	}
}
class DuoTaiDemo {
	public static void main(String[] args) {
		Cat c = new Cat();
		Dog d = new Dog();
		method(new Pig()); //Animal a = new Pig();
		method(c);
		method(d);
	}
	public static void method(Animal a) {
		a.eat();
	}
}

Cat,Dog,Pig都繼承了Animal,是Animal的子類,Animal的變量a指向了創建的Pig對象,調用Pig中的eat()方法,實現了多態性。

 

10-4,多態-弊端&前提

1,多態的弊端:

    前期定義的內容不能使用(調用)後期子類的特有內容。

2,多態的前提:

(1)必須有關係,繼承或實現,Animal a = new Cat();Cat要繼承Animal。

(2)要有覆蓋,不然方法不能用。Cat中的方法要重寫Animal中的方法。

弊端:參考10-3的代碼:

如:

Cat c = new Cat();
method(c);
...
public static void method(Animal a) {
	a.eat();
	a.catchMouse();
}

創建Cat對象,把c傳給method方法,Animal的a指向c,因爲在Animal中沒有定義catchMouse方法,所以會報錯,只能調用Animal中定義過的並且被覆蓋實現的方法。也就是不能調用Cat類中的特有內容catchMouse方法。

 

10-5,多態-轉型:

1,Animal a = new Cat();

a.eat();

這是自動類型提升,貓對象被提升爲動物類型。但是特點功能無法訪問。

其作用就是限制對特有功能的訪問。

專業講:向上轉型,向上造型,上溯造型。

2,Cat c = (cat)a;

如果還想用具體動物貓的特有功能,可以將該對象進行向下轉型。

向下轉型的目的是爲了使用子類中的特有方法。

Cat c = (Cat)a;//強制轉換爲Cat類型

c.eat();

c.catchMouse();

3,注意:對子轉型,自始至終都是子類對象在做着類型的變化。

Animal a1 = new Pig();

Cat c1 = (Cat)a1;//ClassCastException

類型轉換失敗,子類間不能轉換。

 

10-6,多態-類型判斷-instanceof

public static void method(Animal a) {
	a.eat();
	if(a instanceof Cat) { //判斷a是不是Cat類型
		Cat c = (Cat)a;
		c.catchMouse();
	} else if (a instanceof Dog) {
		Dog d = (Dog)a;
		d.lookHome();
	}
}

instanceof:用於判斷對象的具體類型。只能用於引用數據類型判斷。

通常在向下轉型前用於健壯性的判斷。

向下造型時通常用instanceof判斷一下,提高健壯性,若傳入的不是相同類型,在轉型時會報錯。若把Cat改爲Animal,則下面些什麼都沒有用,因爲所有的對象都繼承於Animal,都屬於Animal類型。

 

10-7,多態-成員變量

1,多態時,成員變量的特點:

編譯時,參考引用類型變量所屬的類中是否有調用的成員變量,有則編譯通過,沒有則編譯失敗。

運行時,參考引用型變量所屬的類中是否有調用的成員變量,並運行該所屬類中的成員變量。

簡單說:編譯和運行都參考等號左邊。

class Fu {
	int num = 3;
}
class Zi extends Fu {
	int num = 4;
}
class DuoTaiDemo {
	public static void main(String[] args) {
		Fu f = new Zi();
		System.out.println(f.num);
	}
}

結果:3

左邊爲父類,就看父類中的num,若沒有,則編譯失敗,若爲Zi f = new Zi();則打印Zi中的num,此時Zi類中不定義num打印爲3,因爲繼承了父類的num。

開發是不會出現此類情況,因爲父類中定義的東西直接拿過來用就可以,不用再在子類中定義。

 

10-8,多態-成員函數

1,成員函數(非靜態)

編譯時,參考引用型變量所屬的類中是否有調用的函數,有則編譯通過,沒有則編譯失敗。

運行時,參考的是對象所屬的類中是否有調用的函數。

簡單說:編譯看等號左邊,運行看等號右邊。

class Fu {
	void show() {
		System.out.println("fu show...");
	}
}
class Zi extends Fu {
	void show() {
		System.out.println("zi show...");
	}
}
class DuoTaiDemo {
	public static void main(String[] args) {
		Fu f = new Zi();
		f.show();
	}
}

f爲引用變量,Zi爲對象,在堆中開闢空間,引用變量f指向Zi對象的地址,調用show方法時,show方法進棧,帶有this引用,指向Zi對象的地址,在Zi對象中找有沒有show方法,若有,則執行Zi中的show方法,若沒有,則在super指向的父類中找show方法,有則執行父類中的show方法,若沒有則編譯失敗。

 

10-9,多態-靜態函數

靜態函數:

編譯時,參考引用變量所屬的類中是否有調用的靜態方法。

運行時,參考引用變量所屬的類中是否有調用的靜態方法。

簡單說:編譯和運行都看等號左邊。

其實對於靜態方法是不需要有對象的,直接用類名調用即可。

class Fu {
	static void method() {
		System.out.println("fu static method");
	}
}
class Zi extends Fu {
	static void method() {
		System.out.println("zi static method");
	}
}
class DuoTaiDemo {
	public static void main(String[] args) {
		Fu f = new Zi();
		f.method(); //打印fu static method
		Fu.method();
		Zi.method();
	}
}

靜態方法加載進靜態方法區。Static修飾的函數不受對象控制,不需要創建對象直接用類名調用即可。

Fu f是創建了一個Fu類對象的引用,若用f調用靜態方法,則直接執行Fu類靜態方法區中的method函數,靜態方法中沒有this。


10-10,內部類-概述

1,內部類也生成.class文件,文件名格式爲:外部類名$內部類名.class。

2,內部類訪問特點:

(1)內部類可以直接訪問外部類中的成員,包括私有的。

(2)外部類要訪問內部類,必須建立內部的對象。

內部類一般用於類的設計。

3,何時使用內部類?

分析事物時,發現該事物描述中還有事物,而且這個事物還在訪問被描述事物的內容,這時就將這個內部的事物定義成內部類來描述。

例如:

class Outer {
	private int num = 3;
	class Inner { //內部類
		void show() {
			System.out.println("show run..." + num);
		}
	}
	public void method() {
		Inner in = new Inner(); //在外部類中定義內部類的對象,訪問內部類中的內容
		in.show();
	}
}
class InnerClassDemo {
	public static void main(String[] args) {
		Outer out = new Outer();
		out.method();
	}
}

 

10-11,內部類-修飾符

1,內部類可以被修飾符修飾,如private ,public ,static等。

2,例如:

class Outer {
	private static int num = 31; //靜態函數訪問靜態成員
	class Inner { //內部類
		void show() {
			System.out.println("show run ... " + num);
		}
		/*
		static void function() {
			System.out.println("function run ... " + num);
		}
		*/
	}
	public void method() {
		Inner in = new Inner();
		in.show();
	}
}
class InnerClassDemo {
	public static void main(String[] args) {
		Outer out = new Outer();
		out.method();
		//直接訪問外部類中的內部類中的成員。
		Outer.Inner in = new Outer().new Inner();
		in.show();
		//如果內部類是靜態的,相當於一個外部類
		Outer.Inner in = new Outer.Inner(); //外部類一加載,該靜態內部類就存在了。
		in.show();
		//如果內部類是靜態的,成員是靜態的
		Outer.Inner.function();//靜態直接用類名調用
	}
}

10-12,內部類-細節

細節1:

class Outer {
	int num = 3;
	class Inner {
		int num = 4;
		void show() {
			int num = 5;
			System.out.println(num);//打印5
		}
	}
	void method() {
		new Inner.show();
	}
}
class InnerClassDemo {
	public static void main(String[] args) {
		new Outer().method();
	}
}

若內部類的show方法打印this.num,則這個this指代的是Inner類的對象,打印4;

若內部類的show方法打印Outer.this.num,則指代了Outer類的對象,打印3。

 

細節2:

爲什麼內部類能直接訪問外部類中的成員呢?

因爲內部類持有了外部類的引用。格式爲:外部類名.this,如:Outer.this.num,指的是Outer中的num。

 

10-13,內部類-局部內部類

內部類可以存放在局部位置上。

內部類在局部位置上只能訪問局部中被final修飾的局部變量。

例如:

class Outer {
	int num = 3;
	Object method(final int y) {
		final int x = 9;
		class Inner { //局部內部類,在method方法中
			void show() {
				//局部內部類訪問局部變量,局部變量必須被final修飾。
				System.out.println("show..." + y);
			}
		}
		Object in = new Inner();
		return in;
	}
}
class InnerClassDemo {
	public static void main(String[] args) {
		Outer out = new Outer();
		Object obj = out.method();
	}
}

10-14,匿名內部類-概述

匿名內部類就是內部類的簡寫格式。

必須有的前提是:

    內部類必須繼承或實現一個外部類或接口。

匿名內部類:

    其實就是一個匿名子類對象。

格式:

    new父類 or 接口() { 子類內容 }

 

例如:

abstract class Demo {
	abstract void show();
}
class Outer {
	int num = 4;
	/* 非匿名方法
	class Inner extends Demo {
		void show() {
			System.out.println("show ... " + num);
		}
	}
	*/
	public void method() {
		//new Inner().show(); //對應上面非匿名方法,
		new Demo { //匿名內部類,繼承了外部類Demo
		//new了一個匿名子類對象並調用show方法,子類中重寫Demo的抽象方法
			void show() {
				System.out.println("show ..." + num);
			}
		}.show();
	}
}
class InnerClassDemo {
	public static void main(String[] args) {
		new Outer().method();//new 一個Outer對象並調用其method方法。
	}
}

10-15,匿名內部類-應用

使用場景:

當函數參數是接口類型時,而且接口中的方法不超過三個。

可以用匿名內部類作爲實際參數進行傳遞。

class InnerClassDemo {
	class Inner {}
	public static void main(String[] args) {
		/* 接口類型參數傳遞,在傳遞時直接實現方法
		show(new Inter(){
			public void show1() {
				System.out.println("show1...");
			}
			public void show2() {
				System.out.println("show2...");
			}
		});
		*/
		new Inner();
	}
	public void method() {
		new Inner();
	}
	public static void show(Inter in) {//接口形參
		in.show1();
		in.show2();
	}
}
interface Inter {
	void show1();
	void show2();
}
class Outer {
	/* 非匿名方式
	class Inner implements Inter {
		public void show1() {
			System.out.println("show1...");
		}
		public void show2() {
			System.out.println("show2...");
		}
	}
	*/
	public void method() {
		Inner in = new Inner();
		in.show1();
		in.show2();
		//給匿名內部類起個名字,用該名字調用裏面的show1,show2方法。
		Inter in = new Inter() {
			public void show1() {
				System.out.println("show1...");
			}
			public void show2() {
				System.out.println("show2...");
			}
		};
		in.show1();
		in.show2();
	}
}

若匿名內部類中只有一個方法,可以這麼調用:

new Inter() {
	public void show() {
		System.out.println("show run ... ");
	}
}.show();

 

10-16,匿名內部類-細節

class Outer {
	void method() {
		//前面的Object表示父類對象,後面的Object表示子類對象。
		//這裏是多態,把new Object向上轉型爲Object型,編譯時看等號左邊,
		//因爲Object類中沒有show方法,所以編譯失敗。
		Object obj = new Object() {
			public void show() {
				System.out.println("show run ... ");
			}
		};
		obj.show(); // 報錯,找不到show方法。
	}
}
class InnerClassDemo {
	public static void main(String[] args) {
		new Outer().method();
	}
}

報錯原因:因爲匿名內部類這個子類對象被向上轉型爲了Object類型,這樣就不能再使用子類的特有方法了。

 

10-17,對象的初始化過程:

代碼示例:

class Fu {
	int num = 9;
	{ // 構造代碼塊
		System.out.println("Fu"); //第一步:打印 Fu
	}
	Fu() {
		super();
		//顯示初始化
		//構造代碼塊初始化
		show();
	}
	void show() {
		//這個show會被子類的show覆蓋
		System.out.println("fu show " + num); //第二步:打印Zi類中的show:Zi show 0
	}
}
class Zi extends Fu {
	int num = 8;
	{
		System.out.println("Zi"); //第三步:打印 Zi
	}
	Zi() {
		super();
		//顯示初始化
		//構造代碼塊初始化
		show();
	}
	void show() {
		System.out.println("Zi show " + num); //第四步:打印Zi show 8
	}
}
public class Demo {
	public static void main(String[] args) {
		new Zi();
	}
}

步驟:

(1)Demo類加載進方法區,Demo的構造函數加載進方法區。

(2)main方法加載進靜態方法區,main進棧。

(3)new Zi()在堆中創建子類對象,在這個內存塊中開闢兩塊空間,分別爲Zi的num = 0,Fu的num = 0。

(4)Fu類先加載進方法區,Zi類後加載進方法區。

(5)new Zi();時調用Zi的構造函數,Zi中的super調用Fu的構造函數,Fu()先執行super這裏super調用的是Object,然後執行成員變量的顯示初始化,然後執行本類中構造代碼塊的初始化,這時輸出Fu,然後再執行show方法,由於Zi繼承了Fu,並且重寫了Fu中的show方法,所以這類輸出Zi類中的show方法,輸出Zi show 0。因爲Zi類還未進行顯示初始化,所以輸出默認初始值0。

(6)Zi()中的super()運行完畢,進行Zi類成員變量的顯示初始化,這時Zi中的num=8,再進行Zi類構造代碼塊的初始化,輸出Zi,再執行show方法,這時因爲已經進行了顯示初始化,所以輸出Zi show 8。

(7)main方法彈棧,運行結束。


發佈了152 篇原創文章 · 獲贊 109 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章