**一、接口**
- 接口的理解
?? Java接口是一系列方法的聲明,是一些方法特征的集合,一個接口只有方法的特征沒有方法的實現;
?? 也就是說,接口自身自提供方法的基本聲明,而不提供方法體;接口中聲明的方法只能被實現該接口的子類所具體實現。
?? 接口是Java中另一種非常重要的結構。因為Java不支持多繼承,某種程度來說這也造成了一定的局限性。
?? 所以接口允許多實現的特點彌補了類不能多繼承的缺點。通常通過繼承和接口的雙重設計,可以既保持類的數據安全也變相實現了多繼承。
- 接口的特點
1. 使用關鍵字"interface"聲明一個接口;使用關鍵字"implements"聲明一個類實現一個接口。
1. 與類的權限限制相同,接口的也只能被聲明為“public”或默認修飾符。
1. 接口當中聲明的變量被自動的設置為public、static、final,也就是說在接口聲明的變量實際都會被隱式的提升為"公有的靜態常量"
1. 接口中聲明的方法都是抽象的,并且都被自動的設置為public。
1. 接口自身不能被構造實例化,但可以通過實現該接口的類進行實例化。
1. 實現接口的類如果不是抽象類,那么該類就必須對接口中的方法進行實現。
1. 接口與接口之間也可以實現繼承關系。子接口除擁有父接口的所有方法聲明外,還可以定義新的方法聲明。
~~~
package com.tsr.j2seoverstudy.interface_demo;
//訪問修飾符只能為public或默認訪問修飾符
public interface InterfaceDemo {
// 會被隱式的提升為:public static final int VAR = 50;
int VAR = 50;
// public void methodDemo();
void methodDemo();//方法必須是抽象的
}
class Test implements InterfaceDemo{
public static void main(String[] args) {
InterfaceDemo in = new InterfaceDemo();//compile exception
InterfaceDemo in = new Test();//但可以聲明接口類型的變量,并通過實現該接口的類來進行實例化
}
@Override
public void methodDemo() {
}
}??
~~~
? 根據接口的特點,實際上我們可以看到:接口實際上更像是在聲明一種規范,相當于實現定義了程序的一種框架。例如,作為一種遙控汽車的設計者。你可能需要提供一些規范給這些遙控汽車的生產廠商們,讓他們按照你的設計規范來生產遙控汽車。
~~~
package com.tsr.j2seoverstudy.interface_demo;
public interface Moveable {
void turnLeft();
void turnRight();
void stop();
}
~~~
?? 這是你提供的讓玩具汽車具備可移動性(moveable)的接口,生產廠商必須按照該接口的規范進行實現,才能然小汽車成功的move起來。
- 接口與抽象類的區別
1. 一個類可以實現多個接口,但只能繼承一個抽象類。
1. 抽象類中可以存在非抽象方法,但接口中聲明的方法必須是抽象的。
1. 抽象類中的方法可以是任何的訪問權限,但接口中的方法都是public權限的。
1. 抽象類中可以存在自己定義的任何類型的實例域(變量等),但接口中的域都是公有、靜態、最終的。
?? 而抽象類和接口最大的相同之處,可能就在于:都是對其體系中的對象,不斷的進行向上抽取而來的共有特性。它們都可以用于多態的實現。
**二、內部類**
1、什么是內部類?
顧名思義,內部類就指定義在另一個類的內部當中的類。我們知道一個Java所編寫的基本體現形式就是一個類,而一個類的結構通常是由域(靜態域、實例域)和方法構成的。而有時候一個類中還有另一種構成部分,就是內部類。
2、為什么使用內部類?
關于這點,《Java2學習指南》中這樣說:你是一個OO程序猿,因此知道為了代碼的重用性和靈活性(可擴展性),需要將類保持足夠的專用性。也就是說,一個類只應該具有該類對象需要執行的代碼;而任何其它操作,都應該放在更適合這些工作的其它的類當中。但是!有時候會出現當前類當中需要的某個操作,應當放在另一個單獨的特殊類中更為合適,因為要保持類足夠的專用性;但不巧的是,這些操作又與當前的類有著密切聯系(例如會使用到當前類當中的成員(包括私有成員)等等)。正是這一類的情況,促使了內部類的誕生。
而更具體的來說,之所以使用內部類的原因,通常主要在于:
- 在內部類當中可以訪問該類定義所在的作用域當中的任何數據,包括私有數據。(這是因為內部類會隱式的持有所在外部類的對象引用:“外部類名.this”)
~~~
package com.tsr.j2seoverstudy.base;
public class Outer {
private int num = 5;
private class Inner {
void printOuterNum() {
/*
* 1.驗證了內部類可以訪問其定義所在的作用域當中的任何屬性,包括被聲明為私有的屬性。
* 2.之所以內部類能訪問外部類的實例屬性,是因為其隱式的持有了外部類的對象:外部類類名.this
*/
System.out.println(num);
System.out.println(Outer.this.num);
}
}
}
~~~
可以看到上面例子中雖然外部類“Outer”中的變量“num”被聲明為私有的,但定義在“Outer”當中的內部類仍然可以訪問到該成員變量。
- 內部類能夠針對于存在同一個包下的其他類,將自身隱藏起來。簡單的來說,該好處就是帶來更完善的類的封裝性。
~~~
package com.tsr.j2seoverstudy.base;
class Outer {
private int num = 5;
// 暴露給別人的方法接口
public void exposeMethod() {
Inner in = new Inner();
System.out.println(in.doSomeThingSecret());
}
private class Inner {
int doSomeThingSecret() {
// 封裝一些你不想暴露給其它人任何細節的方法
System.out.println("隱蔽的方法,叼!");
return num;
}
}
}
/*
* 程序輸出結果為:
* 隱蔽的方法,叼!
* 5
*/
public class Test{
public static void main(String[] args) {
Outer out = new Outer();
out.exposeMethod();
}
}
~~~
通過該例子我們可以看到通過內部類實現帶來的嚴謹的封裝性。我們通過內部類“Inner”的方法“doSomeThingSecret”完成了一系列“秘密的操作”。
但我們提供給它人使用時,暴露給使用者的細節只有外部類當中的一個方法“exposeMethod”。這就很好的達到了我們的目的,隱藏不想讓別人知道的操作。
我們知道通常來講,類的訪問權限只能被修飾為public 或 默認的。這就意味著即使我們選擇相對較小的訪問權限:默認的包訪問權限。
那么我們定義的該類當中的實現細節,也會暴露給位于同一個包中的其它類。
而內部類允許被聲明為pirvate。這意味著:其它類甚至連我們定義了這樣的一個類都不知道,就更不用提該類當中的實現細節了。
- 通過匿名內部類能夠更為便捷的定義回調函數。以Java中的多線程機制為例:
如果不使用內部類,那么我們的實現方式就應該如同:
~~~
package com.tsr.j2seoverstudy.base;
public class InnerDemo {
public static void main(String[] args) {
Assignment assignment = new Assignment();
Thread t = new Thread(assignment);
t.start();
}
}
class Assignment implements Runnable{
@Override
public void run() {
System.out.println("線程任務");
}
}
~~~
而通過匿名內部類,我們可以將實現簡化為:
~~~
package com.tsr.j2seoverstudy.base;
public class InnerDemo {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程任務");
}
});
t.start();
}
}
~~~
與我在之前的回顧中說到的一樣,“匿名”其實很好理解,直接的理解就是沒有名字。Java中的標示符就是名字。
所以,在第一種實現方式里,定義線程任務類的類名標示符“Assignment ”就是該線程任務類的類名。
而當我們將線程的任務通過第二種方式實現時,我們發現該其直接被作為參數傳遞給Thread類的構造函數當中。
沒有相關的類名標示符,那么這個線程任務類就是沒有名字的,所以被稱為“匿名”。
注:第三種使用方式應該是實際開發中最常用的。前兩種情況我個人在工作里很少用到,但很多知名的書中都介紹了,所以不妨也作為一種了解,總會有用得上的時候。
**創建內部類對象的方式**
在上面我們說過了,內部類當中是隱式的持有一個其所屬外部類的對象引用的。
由此就不能想象,一個內部類的對象創建肯定是依賴于其所屬的外部類的。
換句話說,要像創建使用一個內部類的對象,前提是必須先獲取到其所屬外部類的對象。
內部類的創建方式大概也就是兩種情況:
- 在其所屬外部類當中,創建該內部類對象:這種情況與平常創建對象的方式沒有任何不同,也就是ClassName clazz = new ClassName();這樣的方式。
- 在其所屬外部類之外的類中創建內部類對象:因為我們說過了內部類對象的創建依賴于其所屬外部類,所以這是的創建方式為:Outer.Inner in = new Outer().new Inner()。
我們還是通過一道以前看見過的面試題,來更直觀的了解內部類的對象創建:
~~~
/*
* 題目:
* 1. public class Outer{
* 2. public void someOuterMethod() {
* 3. // Line 3
* 4. }
* 5. public class Inner{}
* 6. public static void main( String[] args ) {
* 7. Outer o = new Outer();
* 8. // Line 8
* 9. }
* 10. }
*
* Which instantiates an instance of Inner?
* A. new Inner(); // At line 3
* B. new Inner(); // At line 8
* C. new o.Inner(); // At line 8
* D. new Outer.Inner(); // At line 8
*/
~~~
其實很簡單,只要牢記我們上面說的兩種創建情況就OK了。歸納來講:在創建內部類對象之前,必須先構造其外部類的對象。
所以當在外部類中創建內部類對象,因為外部類自身持有對象引用:this。所以可以直接創建內部類。
而在外部類之外創建內部類對象,則就需要先new Outer()創建得到外部類對象,再創建內部類對象。
由此我們來分別看該題目當中的4個答案:
- A答案放在程序的第三行。是在其所屬外部類當中的實例方法中創建內部類對象,因為實例方法持有外部類對象引用this,所以可以直接創建。則A答案合法。
- B答案放在程序的第八行。雖然是在內部類本身創建內部類對象,但因為代碼是位于靜態方法當中,所以并不持有其外部類對象。所以B是非法的。
- C答案放在程序的第八行。代碼雖然位于靜態方法中,但因為之前已經創建了外部類對象“o”,所以再通過“o”創建內部類對象的方式是行得通的的。但要注意的是這種方式的正確使用形式應當是“o.Inner()”而非“new o.Inner()”。所以C也是非法的。
- D答案放在程序的第八行。乍看之下,十分完美。但要注意的是其使用的是“new Outer.”,而非通過new關鍵字調用類構造器創建對象的正確方式“new Outer().”。所以D自然也是非法的。
由此可以得出結論,合法的內部類實例聲明方式只有:A。
**局部內部類**
局部內部類是內部類之中一種稍顯特殊的使用方式。顧名思義,與“局部變量”相同,也就是被定義在方法或代碼塊當中的內部類。
關于局部內部類的使用,我覺得需要掌握的主要只有三點:
第一、與其它的局部成員一樣,局部類的有效范圍被限定在包含的代碼塊中,一旦超出該范圍,該局部內部類就不能被訪問了。
第二、局部內部類不能被訪問修飾符修飾,也就是說不能使用private、protected、public任一修飾符。因為作用域已經被限定在了當前所屬的局部塊中。
第三、這通常也是使用局部內部類的最常見原因。你可能也注意到了,普通的內部類雖然能訪問任何其所屬外部類的成員;但其所屬外部類定義的方法當中的局部變量是訪問不到的,使用局部內部類就可以解決這一問題。但必須謹記的是:被局部內部類訪問的變量必須被修飾為final。
~~~
public class PartInnerDemo {
int num_1 = 10;
public void method(){
final int num_2 = 5;
class PartInner{
private void InnerMethod(){
System.out.println(num_1+num_2);
}
}
}
}
~~~
**
**
**靜態內部類(嵌套類)**
靜態內部類可以說是內部類當中的一朵奇葩。開個玩笑,之所以這樣說是因為靜態內部類是比較特殊的一種內部類。
它的特性更像是一種嵌套類,而非內部類。因為我們前面說過了一般內部類當中,都會隱式的持有一個其所屬外部類的對象引用。而靜態內部類則不會。
除此之外,在任何非靜態內部類當中,都不能存在靜態數據。所以,如果想在內部類中聲明靜態數據,那么這個內部類也必須被聲明為靜態的。
當然,靜態類中除了靜態數據,也可以聲明實例數據。不同之處在于:
如果要在之外使用靜態內部類當中的靜態數據,那么可以直接通過該內部類的:類名.靜態成員名的方式。
而如果要只用該靜態內部類當中的實例成員,那么就必須如同其他非靜態內部類一樣,先創建該內部類的對象。
但同時需要注意,靜態內部類的對象創建與一般的內部類又有所不同,因為我們知道靜態內部類自身是不持有外部類的對象引用的,所以它不依賴于外部類的對象。簡單的說,我們可以認為靜態內部類自身也就是所屬外部類的一個靜態成員,所以其對象創建的方式為:Outer.Inner in = new Outer.Inner();
~~~
public class StaticInner {
void test() {
int num_1 = Inner.num_1;
//
Inner in = new Inner();
int num = in.num;
}
private static class Inner {
int num = 5;
static int num_1 = 10;
}
?? public static void main(String[] args) {
??? ?StaticInner.Inner in = new StaticInner.Inner();
?}
}
~~~
說到這里,想起另外一個問題。這個問題在初學Java時一直沒能想清楚原因:
~~~
public class Test {
public static void main(String[] args) {
class Inner{
String name;
Inner(String s){
name = s;
}
}
Inner o = new Inner("test");
}
}
~~~
我們在前面說過,所有非靜態的內部類的實例化工作,都依賴于其外部類的對象實例化。
但在這段代碼中,我們對于定義的局部內部類“Inner”的對象創建卻沒有依賴于其所在外部類。
這應當是因為:因為該局部內部類被定義在一個靜態方法中,靜態方法中不會持有其所屬的類的對象引用this。
也就是說,定義在靜態方法當中的局部內部類遭受與靜態方法同樣的限制:不能訪問任何其所屬外部類的非靜態成員。
而內部類之所以持有其外部類對象引用的目的在于:可以訪問其所屬外部類的所有實例成員。
那么既然現在我已經被限制成不能訪問實例成員了,自然也就不必依賴于外部類的對象了。
**匿名內部類**
關于匿名內部類,其實在上面說為什么使用內部類的三種原因時,已經說過了。
匿名內部類的定義格式通常為:
~~~
new SuperType(constuction parameters){
//inner class method and data
}
~~~
對于匿名內部類,簡單來說就是一種內部類的簡寫形式。
而必須注意的是,對于匿名內部類的使用,其前提是:該內部類必須是繼承于一個外部類或者實現一個外部接口。
正如我們在上面說到的關于Java多線程。之所以能使用匿名內部類,是因為我們定義的匿名內部類實現了Runnable接口。
- 前言
- 第一個專欄《重走J2SE之路》,你是否和我有一樣的困擾?
- 磨刀不誤砍材工 - 環境搭建(為什么要配置環境變量)
- 磨刀不誤砍材工 - Java的基礎語言要素(定義良好的標示符)
- 磨刀不誤砍材工 - Java的基礎語言要素(關鍵字)
- 磨刀不誤砍材工 - Java的基礎語言要素(注釋-生成你自己的API說明文檔)
- 磨刀不誤砍材工 - Java的基礎語言要素(從變量/常量切入,看8種基本數據類型)
- 磨刀不誤砍材工 - Java的基礎語言要素(運算符和表達式的應用)
- 磨刀不誤砍材工 - Java的基礎語言要素(語句-深入理解)
- 磨刀不誤砍材工 - Java的基礎語言要素(數組)
- 換一個視角看事務 - 用"Java語言"寫"作文"
- 牛刀小試 - 淺析Java的繼承與動態綁定
- 牛刀小試 - 詳解Java中的接口與內部類的使用
- 牛刀小試 - 趣談Java中的異常處理
- 牛刀小試 - 詳解Java多線程
- 牛刀小試 - 淺析Java集合框架的使用
- 牛刀小試 - Java泛型程序設計
- 牛刀小試 - 詳細總結Java-IO流的使用