2019獨角獸企業重金招聘Python工程師標準>>>
Java中接口、抽象類與內部類學習
接口與內部類為我們提供了一種將接口與實現分離的更加結構化的方法。
抽象類和抽象方法
抽象方法:僅有聲明而沒有方法體。
抽象類:包含一個或多個抽象方法的類,該類就必須限定為抽象的(否則會報錯)。
abstract void f() { //抽象方法
}
抽象類的特點:
- 抽象類中的方法并不需要所有的方法都是抽象的,只需部分的方法是抽象的即可。
- 如果一個新類從一個抽象類繼承,并想創建該新類的對象,name就必須為基類中所有的抽象方法提供方法定義。否則導出類也是抽象類,并要用abstract修飾該類。
- 抽象類并不能被實例化。
- 抽象類中的抽象方法不能有方法體,但抽象類中的其它非抽象方法必須有方法體。
- 抽象類中也可以沒有抽象方法。
- 抽象類中不應該有private的成員(注意是不應該,而不是不能)。不管是成員變量,還是非抽象方法都不建議用private修飾,抽象方法是禁止使用private修飾。原因就是我們創建抽象類的目的就是要實現代碼復用,方便子類繼承,private修飾的是不能繼承的,同時抽象類不能實例化對象,所以用private修飾的成員毫無用處。
抽象類的中抽象方法只允許用public和默認修飾(JDK1.8之前默認是用protected修飾,但在JDK1.8之后則是默認為default修飾)
接口(???)
interface關鍵字使抽象的概念更向前了一步。interface將產生一個完全抽象的類,沒有提供任何具體實現。
接口的特點:
- 允許創建者確定方法名、參數列表和返回類型。但是沒有任何方法體。接口只提供了形式并沒有提供任何具體實現。
- 接口中可以包含域(如int,String)但是域默認是static、final的。即默認定為public static final類型的。
- 接口中的方法必須是public修飾的,默認為public。
- 接口中的方法不能有方法體。
- 接口中定義的方法都需要有實現類來實現,如果實現類不能實現接口中的所有方法則實現類定義為抽象類。
- 接口中的方法可以為static int method();即defalut static修飾;但是默認修飾方法的還是public abstarct的
- 在接口中只有方法的聲明沒有方法體。
接口與抽象類的使用判斷:
- 若要創建不帶任何方法定義及成員變量的基類,則選擇接口(注意:接口中沒有成員變量,只有public static final修飾的常量,且符號要大寫)
- 若知道某事物應該成為一個基類,則第一選擇應是使它成為一個接口。
完全解耦—接口的應用???(p174)
Java中的多重繼承
可以通過接口實現Java的多繼承,當將一個具體類和多個接口組合到一起時,這個具體類必須放在前面,后面跟著的才是接口(否則編譯器會報錯)。
interface CanFight{void fight();
}
interface CanSwim{void swim();
}
interface CanFly{void fly();
}
class ActionCharacter{public void fight(){}
}
class Hero extends ActionCharacter implements CanFight,CanFly,CanSwim {public void swim() {}public void fly() {}
}
public class Adventrue {private static void t(CanFight x){x.fight(); }private static void u(CanSwim x){x.swim(); }private static void v(CanFly x){x.fly(); }private static void w(ActionCharacter x){x.fight(); }public static void main(String [] args){Hero hero = new Hero();t(hero);u(hero);v(hero);w(hero);}
}
使用接口的核心原因:
- 為了能夠向上轉型為多個基類型(由此帶來的靈活性)
- 防止客戶端程序員創建該類的對象,并確保這僅僅是建立一個接口
通過繼承來擴展接口(???)
適配接口(???)
接口中的域
初始化接口中的域
在接口中定義的域不能是”空final“,但是可以被非常量的表達式初始化。例如:int RANM_INT = Rand.nextInt(10);.因為域是static的,可以在域第一次加載時就初始化,當然這些域不是接口的一部分,他們的值被存在該接口的靜態儲存區域中。
嵌套接口(???)
接口可以嵌套在類或其他接口中。
接口與工廠
內部類(補)
為什么使用內部類
使用內部類最吸引人的原因是:每個內部類都能獨立地繼承一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對于內部類都沒有影響
使用內部類最大的優點就在于它能夠非常好的解決多重繼承的問題,使用內部類還能夠為我們帶來如下特性:
(1)、內部類可以用多個實例,每個實例都有自己的狀態信息,并且與其他外圍對象的信息相互獨。
(2)、在單個外圍類中,可以讓多個內部類以不同的方式實現同一個接口,或者繼承同一個類。
(3)、創建內部類對象的時刻并不依賴于外圍類對象的創建。
(4)、內部類并沒有令人迷惑的“is-a”關系,他就是一個獨立的實體。
(5)、內部類提供了更好的封裝,除了該外圍類,其他類都不能訪問。
內部類分類
成員內部類
public class Outer{private int age = 99;String name = "Coco";public class Inner{String name = "Jayden";public void show(){System.out.println(Outer.this.name);System.out.println(name);System.out.println(age);}}public Inner getInnerClass(){return new Inner();}public static void main(String[] args){Outer o = new Outer();Inner in = o.new Inner();in.show();}}
1.Inner 類定義在 Outer 類的內部,相當于 Outer 類的一個成員變量的位置,Inner 類可以使用任意訪問控制符,
如 public 、 protected 、 private 等
2.Inner 類中定義的 show() 方法可以直接訪問 Outer 類中的數據,而不受訪問控制符的影響,
如直接訪問 Outer 類中的私有屬性age
3.定義了成員內部類后,必須使用外部類對象來創建內部類對象,而不能直接去 new 一個內部類對象,
即:內部類 對象名 = 外部類對象.new 內部類( );
4.編譯上面的程序后,會發現產生了兩個 .class 文件: Outer.class,Outer$Inner.class{}
5.成員內部類中不能存在任何 static 的變量和方法,可以定義常量:
(1).因為非靜態內部類是要依賴于外部類的實例,而靜態變量和方法是不依賴于對象的,僅與類相關,
簡而言之:在加載靜態域時,根本沒有外部類,所在在非靜態內部類中不能定義靜態域或方法,編譯不通過;
非靜態內部類的作用域是實例級別
(2).常量是在編譯器就確定的,放到所謂的常量池了
★★友情提示:
1.外部類是不能直接使用內部類的成員和方法的,可先創建內部類的對象,然后通過內部類的對象來訪問其成員變量和方法;
2.如果外部類和內部類具有相同的成員變量或方法,內部類默認訪問自己的成員變量或方法,如果要訪問外部類的成員變量,
可以使用 this 關鍵字,如:Outer.this.name
靜態內部類
是 static 修飾的內部類
1.靜態內部類不能直接訪問外部類的非靜態成員,但可以通過 new 外部類().成員 的方式訪問
2.如果外部類的靜態成員與內部類的成員名稱相同,可通過“類名.靜態成員”訪問外部類的靜態成員;
如果外部類的靜態成員與內部類的成員名稱不相同,則可通過“成員名”直接調用外部類的靜態成員
3.創建靜態內部類的對象時,不需要外部類的對象,可以直接創建 內部類 對象名 = new 內部類();
public class Outer{private int age = 99;static String name = "Coco";public static class Inner{String name = "Jayden";public void show(){System.out.println(Outer.name);System.out.println(name); }}public static void main(String[] args){Inner i = new Inner();i.show();}}
.方法內部類
訪問僅限于方法內或者該作用域內
(1).局部內部類就像是方法里面的一個局部變量一樣,是不能有 public、protected、private 以及 static 修飾符的
(2).只能訪問方法中定義的 final 類型的局部變量,因為:
當方法被調用運行完畢之后,局部變量就已消亡了。但內部類對象可能還存在,
直到沒有被引用時才會消亡。此時就會出現一種情況,就是內部類要訪問一個不存在的局部變量;
==>使用final修飾符不僅會保持對象的引用不會改變,而且編譯器還會持續維護這個對象在回調方法中的生命周期.
局部內部類并不是直接調用方法傳進來的參數,而是內部類將傳進來的參數通過自己的構造器備份到了自己的內部,
自己內部的方法調用的實際是自己的屬性而不是外部類方法的參數;
防止被篡改數據,而導致內部類得到的值不一致
/* 使用的形參為何要為 final??? 在內部類中的屬性和外部方法的參數兩者從外表上看是同一個東西,但實際上卻不是,所以他們兩者是可以任意變化的, 也就是說在內部類中我對屬性的改變并不會影響到外部的形參,而然這從程序員的角度來看這是不可行的, 畢竟站在程序的角度來看這兩個根本就是同一個,如果內部類該變了,而外部方法的形參卻沒有改變這是難以理解 和不可接受的,所以為了保持參數的一致性,就規定使用 final 來避免形參的不改變 */public class Outer{public void Show(){final int a = 25;int b = 13;class Inner{int c = 2;public void print(){System.out.println("訪問外部類:" + a);System.out.println("訪問內部類:" + c);}}Inner i = new Inner();i.print();}public static void main(String[] args){Outer o = new Outer();o.show();}}
(3).注意:在JDK8版本之中,方法內部類中調用方法中的局部變量,可以不需要修飾為 final,匿名內部類也是一樣的,主要是JDK8之后增加了 Effectively final 功能
http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html
反編譯jdk8編譯之后的class文件,發現內部類引用外部的局部變量都是 final 修飾的
匿名內部類
(1).匿名內部類是直接使用 new 來生成一個對象的引用;
(2).對于匿名內部類的使用它是存在一個缺陷的,就是它僅能被使用一次,創建匿名內部類時它會立即創建一個該類的實例,
該類的定義會立即消失,所以匿名內部類是不能夠被重復使用;
(3).使用匿名內部類時,我們必須是繼承一個類或者實現一個接口,但是兩者不可兼得,同時也只能繼承一個類或者實現一個接口;
(4).匿名內部類中是不能定義構造函數的,匿名內部類中不能存在任何的靜態成員變量和靜態方法;
(5).匿名內部類中不能存在任何的靜態成員變量和靜態方法,匿名內部類不能是抽象的,它必須要實現繼承的類或者實現的接口的所有抽象方法
(6).匿名內部類初始化:使用構造代碼塊!利用構造代碼塊能夠達到為匿名內部類創建一個構造器的效果
public class OuterClass {public InnerClass getInnerClass(final int num,String str2){return new InnerClass(){int number = num + 3;public int getNumber(){return number;}}; /* 注意:分號不能省 */}public static void main(String[] args) {OuterClass out = new OuterClass();InnerClass inner = out.getInnerClass(2, "chenssy");System.out.println(inner.getNumber());}}interface InnerClass {int getNumber();}
內部類
可以將一個類的定義放在另一個類的定義內部,這就是內部類。內部類的使用與其他類沒有什么不同,但是如果想從外部類的非靜態方法之外的任意位置創建某個內部類的對象時,那么必須指明這個對象的類型:OuterClassName.InnerClassName。
public class Parcel2 {class Contents {private int i = 11;public int value() {return i;}}class Destination{private String label;Destination(String whereTo){label = whereTo;}String readLable(){return label;}}public Contents contents(){ return new Contents(); }public Destination destination(String s){ return new Destination(s); }public void ship(String dest){Contents c = new Contents();Destination d = new Destination(dest);System.out.println(d.readLable());}public static void main(String [] args){Parcel2 p = new Parcel2();p.ship("Tasmania");//defing references to inner classParcel2 q = new Parcel2();Parcel2.Contents c = q.contents();Parcel2.Destination d = q.destination("borneo");}
}
內部類標識符
外圍內的名字 + “$” + 內部類的名字.class。如果內部類是匿名的,編譯器會簡單地產生一個數字作為其標識符。如果內部類是嵌套在別的內部類中,只需直接將他們的名字加在其外圍類標識符與“$”后面。
鏈接到外部類
- 當生成一個內部類對象時,此對象與制造它的外圍對象之間就有了一種聯系,所以他就能訪問其外圍對象的所有成員而不需要任何其他條件。內部類擁有其外圍類的所有元素的訪問權。
- 內部類的對象只能在與外圍類的對象相關聯的情況下才能被創建。構建一個內部類時需要一個指向其外圍類對象的引用,如果編譯器找不到這個引用則會報錯。
- 外部類訪問內部類可以通過幾種方式訪問:
方式一:外部類name.內部類name name = 外部類對象.get方法返回內部類new的對象。如 Parcel2.Contents c = q.contents();要求在外部類的contents方法中要返回內部類的new對象。
方式二:內部類實現了外部的一個接口,則可以 interface name = 外部類.get方法返回內部類的對象。都不能直接在外部類中采用new字段去構造對象再訪問,可以用封裝在外部類的方法中再new內部類后用方法返回。
方式三:通過.new去創建
使用.this和.new
- 要生成對外部類的引用,可以再外部類的名字后緊跟”.this“。這樣產生的引用具有正確的類型,且在編譯期就被知曉并檢查,因此沒有任何運行時的開銷。
public class DoThis {void f(){ System.out.println("DoThis.f()"); }public class Inner{public DoThis outer(){ return DoThis.this; } //返回外部類的引用}public Inner inner(){ return new Inner(); }public static void main(String [] args){DoThis dt = new DoThis();DoThis.Inner di = dt.inner();di.outer().f();}
}
- 創建某個內部類的對象,使用“.new”。要創建某個類的對象必須使用外部內的對象來創建該內部類的對象。在擁有外部類對象之前是不可能創建內部類對象的,因為內部類會暗暗地連接到引用它的外部類對象上。但是,如果創建的是嵌套類(靜態內部類),那么就不需要對外部類對象的引用。
public class DoNew {public class Inner{}public static void main(String [] args){DoNew dn = new DoNew();DoNew.Inner di = dn.new Inner();}
}
內部類與向上轉型
當將內部類向上轉型為其基類,尤其是轉型為一個接口時,內部類就發揮很大作用了。因為內部類——某個借口的實現——能夠完全不可見,并且不可用。所以得到得只是指向基類或接口的引用,能夠很方便的隱藏實現細節。
在方法和作用域內的內部類
可以在一個方法里面或者在任意的作用域內定義內部類。
- 實現某個類型的接口,于是可以創建并返回對其的引用。
- 想創建一個類來輔助解決問題,但不希望這個類是公共可用的。
局部內部類
- 在方法的作用域內創建一個完整的類。在方法的作用域外是不能訪問的。
- 在方法執行完畢,并不意味著局部內部類就不可以使用了。
- 可以在同一個子目錄的任意類中對局部內部類使用相同的類標識符,這并不會引起命名沖突。
匿名內部類
將返回值的生成與表示這個返回值的類的定義結合在一起。這個類是沒有名字的。
public class Parcel7 {public Contens contens(){return new Contens() {private int i = 11;public int value() {return i;}};}public static void main(String [] args){Parcel7 p = new Parcel7();Contens c = p.contens();}
}
匿名內部類的基類如果只有帶參構造器,在使用匿名類時可以傳遞合適的參數給基類的構造器即可。
public class Parcel8 {public Wrappping wrappping(int x) {return new Wrappping(x) {public int value() {System.out.println("super.value(): " + super.value());return super.value() * 47;}};}public static void main(String[] args) {Parcel8 p = new Parcel8();Wrappping wrappping = p.wrappping(10);wrappping.value();}
}
在匿名類的末尾的分號,并不是用來標記此內部類結束的,它標識的是表達式的結束,只不過表達式正好包含了匿名內部類。這與別的地方使用的分號是一致的。
在匿名類中定義字段時,還可以初始化。如果匿名內部類希望使用一個在其外部定義的對象,編譯器要求參數引用時final的。這個在jdk以前的版本是這樣,但是在jdk1.8的時候這一規則不再適用。內部類可以訪問外部類中的所有變量無需用final修飾了
public class Parcel9 {public Destination destination( /*final*/ String dest){return new Destination() {private String label = dest;public String readLabel() {return label;}};}public static void main(String [] args){Parcel9 p = new Parcel9();Destination d = p.destination("sdcsd");}
}
匿名內部類既可以擴展類也可以實現接口,但是在實現接口時變不能繼承,繼承時便不能實現接口,而且實現接口時只能實現一個接口。
嵌套類
嵌套類:不需要內部類對象與其外圍類對象有聯系,那么可以將內部類聲明為static。普通的內部類不能有static數據和static字段,也不能包含嵌套類。但是嵌套類可以包含所有的這些東西。
接口中的類
接口中的任何類修飾都是public和static的,可以在接口中的內部類實現其外圍接口。如果想創建某些公共代碼,使得他們都可以被某個接口的的所有不同實現所共用,name使用接口內部的嵌套類會很方便。