1. 程式人生 > >物件記憶體模型[2]內部類

物件記憶體模型[2]內部類

【參考連結】

內部類分為靜態內部類和非靜態內部類兩類,其中非靜態內部類又可分為非匿名內部類和匿名內部類

一共是3種

靜態內部類 vs 非靜態內部類

換個角度來理解

如果要讓我們自己來為一個類設計內部類,並且其可以包含static成員變數成員方法,我們會怎麼設計?

1、  內部類的static成員方法,只能訪問外部類的static成員變數成員方法

2、  內部類的非static成員方法,可以先建立一個外部類的物件,通過這個物件訪問其非static成員變數成員方法,當然也可以訪問static的

但是Java並沒有採用這種設計,而是做了拆分,分為兩種內部類

如果要實現第1種需求,可以為這個類定義一個靜態內部類,並且將其增強為可以直接建立靜態內部類的物件,其非static方法也可以訪問外部類的static成員變數成員方法

如果要實現第2種需求,可以為這個類定義一個非靜態內部類。

如果需要同時實現這兩個需求,這可以在這個類中定義兩個內部類。

為了區分這兩種內部類,要求在定義靜態內部類的時候類名前加static修飾符。

因此,Java中的靜態內部類和非靜態內部類的區別如下:

1、 靜態內部類可以獨立建立物件,非靜態內部類必須依託於外部類物件建立

2、 靜態內部類中可以有static成員,也可以有非static成員,但是非static成員方法只能訪問外部類中的static成員。非靜態內部類不能有static成員。

不過非靜態內部類可以有staticfinal型別常量哈

因為如前所述,static final在被其他類引用的時候,並不是被引用而是直接在該類中自動生成了一條新的常量項。

靜態內部類

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

package com.shadowfaxghh.test;

publicclass OutterClass {
    
    
publicfloat om1=100f;
    
    
publicstaticvoid omethod(){
    }
    
    
publicstaticclass InnerClass{
        
publicstaticfloat im1=100f;
        
        

publicfloat im2=200f;
        
        
publicstaticvoid imethod1(){
            OutterClass.omethod();
        }
        
//      public void imethod2(){
//          OutterClass.this.om1;
//      }
    }
}

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12

package com.shadowfaxghh.test;

publicclass Main {

    
publicstaticvoid main(String[] args) {

        
//建立一個靜態內部類的物件
        OutterClass.InnerClass i=new OutterClass.InnerClass();
    }

}

上述靜態內部類中不能訪問外部類的非靜態成員

非靜態內部類

非靜態內部類的物件需要依託於外部類的物件而存在,也就是說

要建立非靜態內部類的物件,必須先建立一個外部類的物件。(也說明了建立外部類的物件的時候,並不會自動建立內部類的物件)

所以,在非靜態內部類的方法中可以,可以自由訪問外部類的成員變數和成員方法,無視訪問控制修飾符。可以通過如下方式來訪問

外部類.this.成員變數、外部類.this.成員方法

非靜態內部類可以用public、protected、private、包訪問控制修飾符修飾,這會影響建立內部類物件的地點和方式

如下程式碼所示,OutterClass內部有InnerClass1、InnerClass2兩個非靜態內部類

InnerClass1因為是public的所以可以在OutterClass外部建立,InnerClass2因為是private的所以只能在OutterClass內部建立

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

package com.shadowfaxghh.test;

publicclass OutterClass {
    
    
privatefloat om1=100f;
    
    
publicvoid omethod1(){
        
//建立內部類的物件
        InnerClass2 i2=new InnerClass2();
        i2.imethod();
    }
    
    
publicclass InnerClass1{
        
publicvoid imethod(){
            System.out.println(OutterClass.
this.om1);
        }
    }
    
    
privateclass InnerClass2{
        
publicvoid imethod(){
            System.out.println(OutterClass.
this.om1);
        }
    }

}

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

package com.shadowfaxghh.test;

import com.shadowfaxghh.test.OutterClass.InnerClass1;

publicclass Main {

    
publicstaticvoid main(String[] args) {
        
//需要先建立外部類物件
        OutterClass o=new OutterClass();
        
        
//public型別的內部類,在建立了外部類物件的情況下,可以使用這種方式建立物件
        InnerClass1 i1 = o.new InnerClass1();
        i1.imethod();
        
        o.omethod1();
        
//      InnerClass1 i2 = o.new InnerClass2();
    }

}

上述main()方法中的InnerClass1 i2 = o.new InnerClass2();會編譯異常

為什麼在非靜態內部類中可以訪問外部類物件的成員呢?

這是因為,在編譯的時候,會為外部類、內部類生成獨立的.class檔案

並且會修改非靜態內部類的建構函式,為其新增一個外部類的形參,儲存在內部類自動新增的一個成員變數中。內部類的物件通過這個成員變數訪問外部類的物件

如上的程式碼,生成了如下.class檔案,其中顯然OutterClass$InnerClass1.class就是內部類InnerClass1的。

可以看到其擁有一個外部類型別的名為this$0的成員變數

會在建立物件的時候賦值(從位元組碼看來還呼叫了外部類的getClass()方法,但是卻並未使用返回的Class物件,不解)

 


訪問外部類物件的成員變數也是通過這個成員變數

 

所以,在外部類的靜態方法中,也不能建立其內部類的物件

因為建立內部類物件時要求傳遞一個外部類物件,而此時呼叫靜態方法時可能並未建立外部類物件

非匿名內部類 vs 匿名內部類

非靜態內部類也可以繼承類或實現介面

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

package com.shadowfaxghh.test;

publicclass OutterClass {
    
    
private OnClickListener listener;
    
    
publicvoid setOnClickListener(OnClickListener l){
        
this.listener=l;
    }
    
    
publicvoid omethod1(){
        Thread t=
new InnerClass1();
        t.start();
    }
    
    
publicvoid omethod2(){
        setOnClickListener(
new InnerClass2());
    }
    
    
privateclass InnerClass1 extends Thread{

        @Override
        
publicvoid run() {
            
        }
    }
    
    
privateclass InnerClass2 implements OnClickListener{

        @Override
        
publicvoid onClick() {
            
        }
        
    }

}

上述程式碼還可以簡寫成如下方式,建立物件時不定義內部類的名稱,直接重寫其方法,此即為匿名內部類

 Java Code 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

package com.shadowfaxghh.test;

publicclass OutterClass {
    
    
private OnClickListener listener;
    
    
publicvoid setOnClickListener(OnClickListener l){
        
this.listener=l;
    }
    
    
publicvoid omethod1(){
        
        
new Thread(){
            @Override
            
publicvoid run() {
                
            }
        }.start();
    }
    
    
publicvoid omethod2(){
        setOnClickListener(
new OnClickListener(){
            @Override
            
publicvoid onClick() {
                
            }
        });
    }
}

並且還可以在重寫的方法中,訪問外部方法的區域性變數,但是要求這些區域性變數是final的。

(在JDK7.0及之前,只要是訪問就會要求該變數宣告final,在JDK7.0之後,僅訪問不會提示,當修改的時候才會)

(注意這裡是訪問外部方法的區域性變數才限制,訪問外部類的成員變數是沒有限制的)

 Java Code 

1
2
3
4
5
6
7
8
9
10
11

publicvoid omethod1(float a){
        
        
float b=100f;
        
        
new Thread(){
            @Override
            
publicvoid run() {
                System.out.println(
"a="+a+" b="+b);
            }
        }.start();
    }

Compiler compliance level: 1.7

Compiler compliance level: 1.8

這是因為

雖然是匿名,但是仍然會生成自己的.class檔案

以上述程式碼中的new Thread()為例,生成了一個內部類,繼承自Thread,自動添加了一個所在外部類型別的成員變數

 

以上述程式碼中的newOnClickListener為例,生成了一個內部類,繼承自Object,實現了OnClickListener介面,也自動添加了一個所在外部類型別的成員變數

 

 

 

而對外部方法的區域性變數的訪問,也是通過為這個匿名類自動新增兩個成員變數,在建立物件的時候賦值這兩個成員變數

 

而對如下程式碼而言,子執行緒main()方法的執行,有可能在b=200f;之前,也有可能是在b=200f;之後,這樣就會導致執行run()方法時,資料的不一致性。

所以要求在匿名內部類中訪問外部方法中的區域性變數時,其必須為final的。