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方法彈棧,運行結束。