同窗們,這一次講座,咱們講一下Inner Class內部類。
咱們平時寫的程序是由一個個類構成的,這些類之間是相互獨立的關係。咱們說過,這種思路源自對現實世界的模擬,拉近了「問題空間」和「解決空間」。所以簡化了系統的設計。
而Inner class 內部類是指一個類是另外一個類的內部成員,定義在某個類的內部的,對外可能可見也可能不可見。java
基本形式仍是蠻簡單的,咱們看一個例子:編程
public class OuterClass {
private String outerName;
public void display(){
System.out.println("OuterClass display...");
System.out.println(outerName);
}
public class InnerClass{
private String innerName;
InnerClass(){
innerName = "inner class";
}
public void display(){
System.out.println("InnerClass display...");
System.out.println(innerName);
}
}
}
如上面的代碼所示,這樣在OuterClass裏面就定義了一個InnerClass類。使用的時候能夠這麼用:安全
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
outerClass.display();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.display();
}
從代碼能夠看出,內部類跟一個普通屬性同樣使用,只要在外部類建立好的狀況下,就能夠去使用,能夠明顯看出這種被包含的關係。
並且,外部的程序能這麼用的一個前提是內部類聲明爲public,這樣纔會爲外部程序所見,這個跟普通屬性也是同樣的。固然,這個public不是約束包含的那個外部類的,不管把內部類聲明爲public仍是private,對包含它的外部類都是可見的。閉包
外部類和內部類的這種包含關係,決定了互相能夠調用。代碼以下(OuterClass.java):異步
public class OuterClass {
private String outerName;
public OuterClass(){
outerName="OurClass Default Name";
}
public void display(){
System.out.println("OuterClass display...");
System.out.println(outerName);
}
public void displayInner(){
InnerClass innerClass=new InnerClass();
innerClass.display();
}
private class InnerClass{
private String innerName;
InnerClass(){
outerName="outer class new name";
innerName = "inner class default name";
}
public void displayOuter(){
System.out.println(outerName);
}
public void display(){
System.out.println("InnerClass display...");
System.out.println(innerName);
}
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
outerClass.display();
outerClass.displayInner();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.display();
innerClass.displayOuter();
}
}
咱們在OuterClass裏面用下面兩行建立了內部類並調用方法:ide
InnerClass innerClass=new InnerClass();
innerClass.display();
而在內部類裏面,咱們直接使用了外部內的屬性。這一段演示了外部類內部類的互操做。編譯器會作一個處理,對上面的內部類方法:函數式編程
public void displayOuter(){
System.out.println(outerName);
}
編譯以後的代碼會變成:函數
OuterClass.this.outerName。
好奇的人通常會去bin目錄下看一眼編譯以後的結果,生成了兩個class文件,OuterClass.class和OuterClassthis
除了上述的基本型內部類,內部類還能夠定義成靜態的,或者在一個方法體內進行定義,如:spa
void method1() {
class InnerClass {
public void print() {
System.out.println("method inner class.");
}
}
InnerClass inner = new InnerClass();
inner.print();
}
這個簡單,這裏再也不完整舉例。下面舉一個靜態內部類的例子,代碼以下(Employee.java):
public class Employee{
public String empName;
public Company company;
public Employee(String empName){
this.empName = empName;
}
public static class Company{
public String compName;
public String compRegion;
public Company(String compName,String compRegion){
this.compName = compName;
this.compRegion = compRegion;
}
}
public static void main(String[] args) {
Company company = new Employee.Company("Sun/Oracle", "China");
Employee alice = new Employee("Alice");
Employee bob = new Employee("Bob");
alice.company = company;
bob.company = company;
}
}
上面的Employee裏面包含一個靜態內部類Company,這樣跟一個普通類相似了,外部程序能夠直接建立這個內部類。並且多個外部類employee能夠共享同一個內部類company。要注意的是,這個時候,外部類能夠訪問內部類,而內部類訪問不了外部類。大家能夠本身動手試一下,在Employee中增長一個test(),而後試着在Company中調用,會有編譯錯誤:
Cannot make a static reference to the non-static method test() from the type Employee。
有了內部類的這些基礎知識,下面咱們要討論進階一點的內容了。
有一種頗有用的場合時匿名內部類。它看起來就是一個常規的內部類,可是不顯式起名,所以叫匿名類。這個場景,定義和實例化是寫在一塊兒同時的。當咱們須要實現一個接口或者抽象類的時候,咱們常常這麼用。
咱們看一個監聽器響應事件觸發的例子。代碼以下:
先定義一個Listener接口:
public interface ClickListener {
void onClick();
}
再定義一個Button類,裏面包含一個Listener匿名內部類,代碼以下(Button.java):
public class Button {
public void click(){
new ClickListener(){
public void onClick(){
System.out.println("click ...");
}
}.onClick();
}
public static void main(String[] args) {
Button button=new Button();
button.click();
}
}
仔細看看上面的代碼,按照常規,咱們應該在click()方法中定義這個內部類,而後new一個實例,再調用方法。而使用匿名內部類,咱們用一句話一鼓作氣:
new ClickListener(){
public void onClick(){
System.out.println("click ...");
}
}.onClick();
之因此這麼用,是由於其實咱們只是想實現onClick()方法,至於這個類叫什麼名字,咱們並不關心,因此就匿名了。這種需求在些事件響應程序式會常常用到。這個匿名能夠當作一種簡寫,對編譯器來說,他仍是規規矩矩地生成了一個內部類的class文件ButtonInnerClass.class。爲何須要內部類?用兩個外部類不是同樣的嗎?從程序功能上來講,確實是同樣的。我我的理解的是,採用內部類技術,隱藏細節和內部結構,封裝性更好,讓程序結構更加合理優雅。在現實世界裏,一個事物內部都由不少部件組成,每一個部件也還可能包含子部件,這些子部件不須要暴露出來。內部類的思想就是借鑑了這個現實世界,概念的同一性讓它很好理解。這種思想更貼近現實世界,「問題空間」與「解決空間」更接近了。
除了上述的基本型內部類,內部類還能夠定義成靜態的,或者在一個方法體內進行定義,如:¨G6G這個簡單,這裏再也不完整舉例。下面舉一個靜態內部類的例子,代碼以下(Employee.java):¨G7G上面的Employee裏面包含一個靜態內部類Company,這樣跟一個普通類相似了,外部程序能夠直接建立這個內部類。並且多個外部類employee能夠共享同一個內部類company。要注意的是,這個時候,外部類能夠訪問內部類,而內部類訪問不了外部類。大家能夠本身動手試一下,在Employee中增長一個test(),而後試着在Company中調用,會有編譯錯誤:¨G8G有了內部類的這些基礎知識,下面咱們要討論進階一點的內容了。有一種頗有用的場合時匿名內部類。它看起來就是一個常規的內部類,可是不顯式起名,所以叫匿名類。這個場景,定義和實例化是寫在一塊兒同時的。當咱們須要實現一個接口或者抽象類的時候,咱們常常這麼用。
咱們看一個監聽器響應事件觸發的例子。代碼以下:先定義一個Listener接口:¨G9G再定義一個Button類,裏面包含一個Listener匿名內部類,代碼以下(Button.java):¨G10G仔細看看上面的代碼,按照常規,咱們應該在click()方法中定義這個內部類,而後new一個實例,再調用方法。而使用匿名內部類,咱們用一句話一鼓作氣:¨G11G之因此這麼用,是由於其實咱們只是想實現onClick()方法,至於這個類叫什麼名字,咱們並不關心,因此就匿名了。這種需求在些事件響應程序式會常常用到。這個匿名能夠當作一種簡寫,對編譯器來說,他仍是規規矩矩地生成了一個內部類的class文件Button1.class。
咱們要的是這個方法,而接口裏面也只有着一個方法,這種場景叫函數式接口,Java8以後能夠用函數式編程進一步簡化上面的代碼:
public void click(){
ClickListener listener = ()->{System.out.println("click ...");};
listener.onClick();
}
函數式接口與匿名類是不一樣的實現方式。編譯器並不會生成一個內部類class文件。
把匿名類當成方法的參數是常見的使用方式。
咱們寫一個程序,一個事件處理器響應事件的發生,響應完以後發一個消息通知。
先定義一個發消息的接口:
public interface IMessenger {
void sendMessage(String string);
}
再定義事件處理器,代碼以下(Handler.java):
public class Handler {
public void handleEvent(IMessenger messenger) {
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
messenger.sendMessage("SUCCESS.");
}
}).start();
}
}
這個處理器的主體方法是handleEvent(IMessenger),異步處理事件花費兩秒鐘,以後發消息。
下面咱們使用這個事件處理器,代碼以下(HandlerTest.java):
public class HandlerTest {
public static void main(String[] args) {
Handler handler = new Handler();
handler.handleEvent(new IMessenger() {
public void sendMessage(String msg) {
System.out.println("event handled. " + msg);
}
});
System.out.println("------event handling test--------");
}
}
注意上面代碼的主體部分:
handler.handleEvent(new IMessenger() {
public void sendMessage(String msg) {
System.out.println("event handled. " + msg);
}
});
這裏就是生成了一個實現IMessenger接口的匿名類,而後傳給handleEvent()方法做爲參數使用。天然,編譯器會自動生成一個匿名類class文件。
再一次說起函數式編程,由於這個IMessenger是一個只有惟一一個方法的接口,因此此處咱們能夠再次用函數式編程簡化代碼:
public static void main(String[] args) {
Handler handler = new Handler();
handler.handleEvent((msg) ->{System.out.println("event handled. " + msg);});
System.out.println("------event handling test--------");
}
匿名內部類來自外部閉包環境的自由變量必須是final的。這一點讓人費解,不少人在編譯器提示錯誤的時候就自動修改一下,並不深究。這個與Java對Closure閉包的實現有關,咱們來初步探究一下。這是比進階更加高級的課題了。
閉包是包含自由變量的函數,這些變量是在定義函數的環境中定義的,而函數自己也當成一個參數進行傳遞和返回,返回後,這個自由變量還同函數一同存在。在JavaScript和別的語言中流行。
你們也一直但願Java實現閉包。Java是經過內部類實現的,可是實現的不完整。
先看現象。咱們在上面的HandlerTest程序的主體部分增長一個自由變量,代碼以下:
int i = 0;
test.handleEvent(new IMessenger() {
public void sendMessage(String msg) {
System.out.println("event handled. " + msg);
int j=0;
j = i+j;
}
});
在JDK7以前,會有編譯錯誤:Local variable i defined in an enclosing scope must be final。這個錯誤出如今j=i+j;這一行。必須讓外面的這個自由變量i定義成final。(JDK8以後不會出錯。可是實際上仍是有這個限制,你試着把上面的代碼修改一下,給i進行一個賦值就會看出來了。)
把i定義成final以後,意味着咱們在匿名類中不能給自由變量i賦值了。
這兒在類裏面定義了內部類,而內部類又引用了外部的自由變量,這就構成了典型的閉包。Java並無徹底實現閉包,在生成匿名類的時候,它把外部自由變量i的value傳入,其實是一個拷貝,所以內部其實不能修改外部的自由變量了。Java團隊此處又偷了一個懶,乾脆就規定外部的這個自由變量爲final。
咱們能夠反編譯這個類,看看編譯器處理以後的代碼,爲:
public void sendMessage(String msg) {
System.out.println("event handled. " + msg);
int j = 0;
j = this.val$i + j;
}
注意編譯器把j=i+j;變成了j = this.val$i + j;,只是一個外部i的拷貝。對於普通的內部類,爲何又不用呢?由於普通的內部類有構造函數,在構造函數過程當中把外部類傳進來。可是匿名內部類是沒有構造函數的。Java這樣處理,引發了多年的爭論。我我的仍是比較贊同Java團隊的作法的,這樣雖然限制不少,可是代碼安全,而且預留了之後徹底支持閉包的可能性。之後我會有專題講解閉包,此次講解只是就着匿名內部類帶一下。大家看到了,Inner Classs起步於一個簡單的模型,基本內容很簡單,可是一步步深刻,就會牽扯出高級的話題。這就有點像推倒多米諾骨牌,第一下只是很小的一個動做,後面卻又大動做等着。不少技術都是這樣的,越研討越深,不斷髮現新挑戰,這也是一種樂趣。正如咱們探索未知的山川,層巒疊嶂,接應不暇,這山望見那山高,前路永遠有精彩的風景。也正如咱們進入桃園洞口,剛開頭武陵人只是緣溪捕魚,到了半道,忽逢桃花林,復前行,林盡水源卻得一山,從口入,行數十步,豁然開朗,得此桃源仙境。學問上這種探索的樂趣,王國維先生曾言:衆裏尋他千百度,慕然回首,那人卻在,燈火闌珊處。咦!微斯人,吾誰與歸