1.內部類的定義和性質
??內部類,顧名思義,就是在一個類里面定義一個類,但是內部類的一個特殊之處在于,它能夠獲取到其外部類對象的所有成員,包括private字段。
??這時因為當外部類對象在創建一個內部類對象的時候,編譯器又開始悄悄咪咪的把外部類對象的引用傳入給內部類對象,于是內部類就可以通過這個外部類對象的引用對外部類的所有成員進行訪問。在內部類中,可以通過外部類類名.this的方式獲取到外部類對象的引用,見下面的例子。
??基于這個原因,在創建內部類對象的時候,不能使用外部類類名進行創建,而必須使用外部類的對象來進行創建。下面是栗子:
public class Outer{
public class Inner{
public Outer getOuter(){
//可以在內部類中通過外部類類名.this獲取到外部類的引用
return Outer.this;
}
}
public static void main(string[] args){
//編譯不通過,不能通過外部類類名創建內部類,因為Inner非靜態
//Inner inner = new Outer.Inner();
//編譯通過,通過外部類對象創建內部類
Outer outer = new Outer();
Inner inner = outer.new Inner();
}
}
??這個時候你是不是隱約感覺到了什么不對!在java基礎整理1當中我們提過,static關鍵字可以使類的成員不再與單個具體對象相互關聯,而是與類本身關聯,那么我們如果用static關鍵字對內部類進行修飾會怎么樣呢?答案是在創建一個static的內部類時,你并不需要一個外部類對象,這時我們稱這個內部類為嵌套類(或靜態內部類),下文中static內部類我將一律以嵌套類(或靜態內部類)對其進行稱呼,而內部類則指代非static的內部類
2.匿名內部類
??在匿名內部類之前,我們首先了解一下在方法和作用域中的內部類
??在方法中定義的內部類其實很好理解,即在方法體當中,內部類可以訪問,如下
//在方法中定義的內部類
public class InnerClassInMethod {
public interface Destination{
String readLabel();
}
//一個返回Destination的方法
public Destination methodA(String s){
//在其中定義一個內部類, 該內部類僅可在methodA(String s)的方法體中訪問
class InnerDestination implements Destination{
private String label;
private InnerDestination(String dest){
label = dest;
}
public String readLabel(){
return label;
}
}
return new InnerDestination(s);
}
}
??在作用域中定義的內部類與在方法中定義類似,在定義內部類的整個作用于當中,內部類都是可以訪問的
//在作用域中定義的內部類
public class InnerClassInField {
public interface Destination{
String readLabel();
}
//一個返回Destination的方法
public void methodB(Boolean b){
if(b){
//在其中定義一個內部類, 該內部類僅可在methodA(String s)的方法體中訪問
class InnerDestination implements Destination{
private String label;
private InnerDestination(String whereTo){
label = whereTo;
}
public String readLabel(){
return label;
}
}
//可以在if()的作用域中對內部類進行調用
InnerDestination innerDestination = new InnerDestination("在作用域中可以調用");
}
//在這里就不能對內部類進行調用
//InnerDestination innerDestination = new InnerDestination("在作用域外則不能調用");
}
}
匿名內部類則是以上情況的簡化形式,當匿名內部類需要使用一個從外部定義的對象,那么這個對象必須聲明為final的,否則會出現編譯時錯誤消息,下面我們將InnerClassInMethod.java改變為匿名內部類的
//InnerClassInMethod.java的匿名內部類形式
public class AnonymousClassInMethod {
public interface Destination{
String readLabel();
}
//一個返回Destination的方法
public Destination methodA(final String s){
//使用匿名內部類返回Destination對象,因為需要使用外部String對象s,因此s被定義為final
return new Destination() {
private String label = s;
@Override
public String readLabel() {
return label;
}
};
}
}
3.嵌套類(靜態內部類)
??前面我們在第1部分中最后有提到,在創建內部類時,外部類對象會將自己的引用傳給內部類持有。但是當我們用static關鍵字修飾內部類時,內部類對象就不會與外部類的對象相聯系,即不再持有外部類對象的引用,這時該內部類我們稱之為嵌套類。
??嵌套類有幾個性質:
- 創建嵌套類時不需要外部類的對象
- 嵌套類不能訪問外部類的非靜態對象
- 嵌套類可以包含static數據和字段,而普通的內部類不能擁有這些
因為嵌套類不具有對外部類對象的引用,以上性質就比較好理解了
4.為什么要用內部類(重點!!!)
??由于java的繼承機制使得子類只能具有一個父類,這個限制會使得一些編程問題變得難以解決
??但是內部類的存在使得這個問題變得容易起來。首先我們思考一下內部類的特點,往往在我們的編程當中,外部類可以繼承某個類或某個接口,內部類也可以獨立的繼承某個類或某個接口,而同時由于內部類能夠訪問其外部類的對象,因此內部類能夠訪問自己所繼承的以及其外部類對象所繼承的所有方法,于是該內部類可以看做繼承了兩個類。因此實際上,內部類是一種多重繼承的實現方式。
以下介紹兩種內部類的應用場景
a. 回調 + “鉤子”
/** 一個帶有increment()方法的接口*/
interface Incrementable{
void increment();
}
/** 第一個回調類,實現Incrementable接口 */
class Callee1 implements Incrementable{
private int i = 0;
@Override
public void increment() {
i++;
System.out.println(i);
}
}
/**一個同樣含有increment()但做著不同工作的類*/
class OtherOptionClass{
public void increment(){
System.out.println("other operation");
}
}
/**
* 第二個回調類
* 若因為某些原因必須繼承OtherOptionClass,但又想實現Incrementable
* 由于兩者都具有increment()方法,則可以通過內部類來實現
*/
class Callee2 extends OtherOptionClass{
private int i = 0;
/** 繼承并覆蓋OtherOptionClass中的increment方法 */
@Override
public void increment(){
super.increment();
i++;
System.out.println(i);
}
/** 定義內部類,內部類實現Incrementable接口 */
private class Closure implements Incrementable{
@Override
public void increment() {
/** 調用外部類的方法 */
Callee2.this.increment();
}
}
/** 定義一個方法以獲取到Closure對象 */
Incrementable getCallbackReference(){
return new Closure();
}
}
/** 調用者類,需要通過傳入Incrementable對象來構建實例,在適當的時候調用其方法,即回調 */
class Caller{
Incrementable callBackReference;
Caller(Incrementable cbr){
callBackReference = cbr;
}
/** 在方法中執行回調 */
void go(){
callBackReference.increment();
}
}
/** 主類 */
public class Callbacks {
public static void main(String[] args) {
/** 創建兩個被回調對象 */
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
/** 構建兩個調用者對象,c1傳入自身,c2傳入自己的內部類*/
Caller caller1 = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbackReference());
/** 在方法中執行回調 */
caller1.go();
caller1.go();
caller2.go();
caller2.go();
}
}
/** Output:
1 ->caller1.go();
2 ->caller1.go();
other operation ->caller2.go();
1
other operation ->caller2.go();
2
*/
如上例,構建Callee對象,將其作為構造參數傳入Caller對象中,這樣可以讓Caller對象在恰當的時候決定操作Callee類,這就是程序設計時常用到的回調。
在這個例子當中,定義了兩個回調類,第二個回調類需要繼承一個類和一個接口,但是它們具有不同作用但具有相同方法標簽的increment()方法,這個時候,我們可以考慮使用內部類對接口進行繼承,并在接口方法實現時調用外部類Callee2的increment()方法,從而達成了外部類Callee2對類和接口的increment()同時進行了繼承。
在本例中內部類Closure僅可通過Callee2獲取,通過它可以轉發調用Callee2對象的increment()方法,這被稱為鉤子,另外,在該類的外部獲取到內部類Closure,也僅能通過它調用Callee2對象的increment(),因此這個是個安全的鉤子。
b. 內部類與控制框架
控制框架如控制一個溫室的運行:控制燈、水、溫度的開關,響鈴等操作。
- 首先可以定義一個模板事件類Event,在這個類中具有一個抽象方法action(); (應用模板模式)
- 然后搭建框架抽象類Controller,該類維護一個Event列表,可以添加刪除Event,并通過run()方法逐條執行Event。
- 實現具體的框架類GreenHouseControls,具有私有變量light、water、temperate等,并定義多個內部類如LightOn、LightOff等繼承自Event,實現各自的action()方法 (應用命令模式,不同的Event自行決定如果和實現)
- 實現一個main()方法,創建GreenHouseControls實例,操作之~
這個例子雖然也可以不用內部類實現,例如可以單獨定義多個操作類繼承自Event,然后在構造方法中傳入具有light、water、temperate等的GreenHouseControls對象,通過這個對象引用修改其參數。
但是如果使用內部類,就可以將這些所有的細節全部封裝起來,此外內部類能夠很容易訪問外部類GreenHouseControls的變量
該例子詳情請見《java編程思想第四版》p207 - p211