java中內部類種類較多,語法比較複雜,用法也不盡相同。
概括下來,可以分類為以下五種內部類。
內部類嵌套內部類局部內部類接口內部類匿名內部類本篇文章只對實際項目開發中用的較多的,普通內部類與匿名內部類做一定介紹。其他三種若有興趣請自行通過谷歌或書籍進行了解。
內部類
首先通過一個簡單的小示例,來看看內部類的語法吧。
import java.util.HashMap;public class Parcell { private HashMap<String, String> testMap = new HashMap<String, String>(); class Contents { // 返回一個外部類的引用. public Parcell ParcellRef = Parcell.this; } class Destination { public void putSomethingInMap() { testMap.put("hello", "world"); System.out.println(testMap.get("hello")); } } public Destination to() { return new Destination(); } public Contents contents() { return new Contents(); } public void ship(String dest) { Contents c = new Contents(); Destination d = new Destination(); } public static void main(String[] args) { Parcell p = new Parcell(); Parcell.Contents c = p.contents(); Parcell.Destination d = p.to(); d.putSomethingInMap(); Parcell.Contents c1 = p.new Contents(); }}內部類的語法和相信大家都很熟悉。
在這裡我再自作主張的為大家概括一下
普通內部類持有一個指向外部類的引用。要創建普通內部類,一定要先創建外部類。普通內部類就像人體的心臟一樣,能夠隨意訪問外部類的任意成員變量。在內部類中可以通過「外部類類名.this」的方式返回一個指向外部類實例的引用.如Parcell.this在外部類的static方法中若要創建內部類對象,則需要通過「外部類類名.new XXX()」的方式來創建。普通內部類中不能擁有靜態成員變量。靜態內部類中可以擁有靜態成員變量。也可以擁有非靜態成員變量。但是靜態內部類不能訪問外部類中非靜態的成員變量。而普通內部類可以訪問外部類的靜態成員變量。為什麼static方法中需要p.new XXX()的方式而非static方法中我們直接new 內部類名 就可以創建一個對象了呢?如果你有這樣的疑問請再看看第一條,一定可以想明白的。
普通內部類的語法大致就是這樣了。
那麼,回到我們主題。
內部類到底有什麼用?我們在實際項目中,應該如何使用內部類呢?
內部類的用處或者說用法,歸根結底,主要就是一點:
: 「內部類,主要是設計出來用來解決java中所『缺少』的,多重繼承的概念的。」
什麼?難道java不是靠接口來實現多重繼承的嗎?我認為,這種說法對,也不對。下面我們來看這樣一個例子。
你正在參與一項代號X的星際飛船項目。
宇航局的人希望飛船上的綜合機器人能夠完成兩個工作。一是作為嚮導,能夠供人們查閱信息。二是作為修理機器人,完成一些簡單的飛船日常維護工作。
作為軟體工程師,你被要求編寫一段代碼來實現這兩個功能。
好消息是,嚮導部分的功能與飛船修理維護的功能,已經由你的同事們完成了!太好了,我只需要調用他們提供給我們的接口就大功告成了!
壞消息是,同事們編寫的嚮導功能與飛船修理功能的接口,竟然都叫做work!
這可傷腦筋了,應該怎麼辦呢?
public class Guider { public void work(String name) { System.out.println("歡迎光臨" + name + ",請查閱飛船信息"); }}-------------------------------------------------------------------public class Repairer { public void work (String name) { System.out.println("你好" + name + ",開始準備對飛船進行維護."); }}-------------------------------------------------------------------public class SpacecraftRobot extends Guider { public void doGuidWork(String name) { // 調用guider的work方法 work(name); } public void doRepairWork(String name) { // 返回內部類引用,調用內部類實例的work方法。 new repairerRobot().doRepairWork(name); } public class repairerRobot extends Repairer { public void doRepairWork(String name) { work(name); } }}太棒了。通過使用內部類與「多重繼承」,我們實現了這個功能。現在這個綜合機器人能夠正常工作了!
對於用戶來說,只需要走到機器人面前,告訴機器人你想要doGuidWork還是doRepairWork,它就能夠幫你幹活兒了。內部類的代碼對用戶,對外界徹底隱藏了起來,用戶唯一能夠獲得的信息就是這兩個方法而已。
匿名內部類
綜合機器人原型機試做成功後,新的工作來了!
我們需要對原型機進行量產。以滿足每艘星際飛船的需要。
現在我們要編寫一間生產綜合機器人的工廠。每當我們訪問一次工廠,就能夠從工廠中提取出一臺嶄新的綜合機器人。聰明的你想到了用工廠設計模式來解決這個問題!但是由於有了內部類,所以我們的工廠,稍稍顯得有點不同
// 機器人工廠接口。通過getSpaceCraftRobot方法對外提供機器人public interface SpaceCraftRobotFactory { SpacecraftRobot getSpaceCraftRobot();}-------------------------------------------------------------------public class ProduceSpaceCraftRobot { // 再也不用顯示的創建工廠類的對象了! private ProduceSpaceCraftRobot() { } // 通過匿名內部類,創建工廠對象!將工廠封裝到了內部。不對外界暴露 public static SpaceCraftRobotFactory produceRobot = new SpaceCraftRobotFactory () { public SpacecraftRobot getSpaceCraftRobot() { return new SpacecraftRobot(); } };}-------------------------------------------------------------------// 客戶public class Consumer { public static void main(String[] args) { // 客戶來提取機器人了. SpacecraftRobot x1 = ProduceSpaceCraftRobot.produceRobot.getSpaceCraftRobot(); x1.doGuidWork("lch"); x1.doRepairWork("lch"); }}通過創建匿名內部類,我們使傳統的工廠設計模式優雅了許多!再也不用在外部編寫new xxxFactory()這樣醜陋,多餘的代碼了。
現在的工廠被匿名內部類隱藏了起來。客戶只需要關心有沒有拿到稱心如意的機器人。不應該,不需要關心工廠的名字,也不需要知道工廠是幹嘛的。
真棒,你順利完成了宇航局交給你的任務。
恭喜你,你是這個星球的英雄。
Java中的閉包,閉包與內部類的關係
作為一個程式設計師,即使你從來沒有使用過,你也應該聽說過閉包與回調。
要從java,特別是j2ee的方向入手去講解閉包與回調,會比較困難。
所以我們首先從python來入手,來了解閉包與回調到底是什麼。
python是一門優秀的解釋性語言。你應該掌握他。
下面我們來看一看,標準的回調,在Python中是什麼樣子的。
#定義一個返回sum函數的名叫base_sum函數的函數def base_sum(a,b): #在base_sum中定義一個sum()函數用來計算base_sum(a,b)的形參之合 def sum(): #返回a+b的值 return a + b #返回定義的sum函數 return sum#調用base_sum返回函數sum(),可以理解為返回了一個函數指針return_method = base_sum(1,2)#列印出返回的函數對象print(return_method)#通過指針回調函數對象,返回a與b的合print(return_method())----------<function base_sum.<locals>.sum at 0x1018f3c80>3對於java程式設計師來說,在一個函數中定義另外一個函數也許會比較燒腦。
你可以試著這樣去理解他:
「首先你需要了解的是,函數也需要佔據內存空間,所以函數在內存中也是有地址的。在c語言中,函數名就代表這個函數的地址。
如果你有過c語言的編程經驗,你就應該知道在一個函數中,返回一個指針是一件很容易的事情。
所以,對於以上這段python代碼,你可以嘗試把它理解為:
base_sum()函數中定義了一個指向sum()函數的指針,並且這個指針作為base_sum()的返回值。」
好了,現在我們根據上面的例子,來「定義一下閉包」。
: 調用外部函數,返回一個持有外部函數變量,參數引用的內部函數對象的程序結構,我們就稱它為「閉包」。
遺憾的是,java中沒有為我們顯示的提供指針供我們操作,也沒有提供類似python,javascrpit中的函數定義的語法,那麼我們應該如何實現閉包呢?
不妨還是通過綜合機器人來解答這個疑問吧。這一次,讓我們稍稍修改一下綜合機器人的代碼如下:
public class SpacecraftRobot extends Guider { // 外部類的成員變量 private String name; public SpacecraftRobot(String name) { this.name = name; } public class repairerRobot extends Repairer { public void doRepairWork() { // 內部類持有外部類的引用,訪問外部類的成員變量name。 work(name); } } public void doGuidWork() { // 調用guider的work方法 work(name); } public void doRepairWork() { // 返回一個持有外部類變量引用的內部類的對象,然後調用這個對象,實現具體的業務邏輯. new repairerRobot().doRepairWork(); }}通過對java內部類的合理利用,我們「模擬」出了一個閉包的程序結構。
該程序通過調用外部類對象,從而返回了一個持有外部類對象變量引用的內部類對象。當我們再次調用內部類對象的某個方法時,我們實現了具體的業務邏輯。
總結
內部類通常用來解決「多重繼承」的問題。當你希望隱藏一個類的實現,減少工程中.java文件數量,或者這個類不想被擴展時,你可以通過匿名內部類來創建一個類的對象。java雖然無法直接在語法層面上支持閉包,但是可以通過內部類來模擬一個閉包的程序結構。