1. 程式人生 > >內部類與靜態內部類

內部類與靜態內部類

前言

  如果你是一個急性子,沒什麼耐性的人,可以只看下句,自己去品味理解:

  內部類:就是我是你的一部分,我瞭解你,我知道你的全部,沒有你就沒有我。(所以內部類物件是以外部類物件存在為前提的)

  靜態內部類:就是我跟你沒關係,自己可以完全獨立存在,但是我就借你的殼用一下,來隱藏自己。

 

  如果還不知道靜態和普通成員的區別,就先學static吧。

  靜態成員:屬於這個類,資料存放在class檔案中,程式執行之前就已經存進去資料了。

  普通成員:屬於這個類的物件,程式執行後生成堆疊中。

 

  先來看一下官方說法,根據Oracle官方的說法:

  Terminology:

 Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.

  一個稱為靜態巢狀類(靜態內部類),一個稱為內部類。那麼兩者到底有什麼區別呢?很多JDK原始碼用到了靜態內部類。HashMap、ThreadLocal、AQS的sync等。那麼接下來學習一下內部類吧!

 

內部類

  內部類是定義在另外一個類中的類,主要原因有:

  • 內部類方法可以訪問該類定義所在的作用域中的資料,包括私有的資料
  • 內部類可以對同一個包的其他類隱藏

  靜態內部類和非靜態內部類最大的區別是:非靜態內部類編譯後隱式儲存著外部類的引用(就算外部類物件沒用了也GC不掉),但是靜態內部類沒有。

1.1 非靜態內部類

1.1.1 定義

  內部類定義語法格式如下:

class OuterClass {
    ...
    class NestedClass {
        ...
    }
}

  我們直接先看來一個例子吧,在Human類裡定義了一個HumanLeg非靜態內部類,並且在HumanLeg類的例項方法中直接訪問外部類的private訪問許可權的例項變數和類變數。

/**
 * 人類 - 外部類
 *
 * @author GrimMjx
 */
public class Human {

    private static final int eyes = 2;

    private static void count() {
        System.out.println("I can count number");
    }

    private int teeth = 10;

    private void say() {
        System.out.println("Hello world");
    }

    /**
     * 人腿 - 非靜態內部類
     */
    public class HumanLeg {
        private Double length;

        public HumanLeg(Double length) {
            this.length = length;
        }

        public void test() {
            say();
            count();
            System.out.println("I have " + eyes + " eyes");
            System.out.println("I have " + teeth + " teeth");
            System.out.println("My leg has " + length.toString() + "cm long");
        }
    }

    public static void main(String[] args) {
        Human human = new Human();
        HumanLeg humanLeg = human.new HumanLeg(100D);
        humanLeg.test();
    }
}

  執行結果:

Hello world
I can count number
I have 2 eyes
I have 10 teeth
My leg has 100.0cm long

  由此看出,非靜態內部類可以直接訪問外部類的例項變數、類變數、例項方法、類方法。這是因為在非靜態內部類物件裡,儲存了一個它所寄生的外部類物件的引用(非靜態內部類例項必須寄生在外部類例項裡)。也就是說,非靜態內部類物件總有一個隱式引用,指向了建立它的外部類物件。我們來畫一張示意圖來理解一下。

 

  另外,還有一些要注意的點

  • 非靜態內部類的成員只是在非靜態內部類範圍是可知的,並不能被外部類直接使用,如果要訪問非靜態內部類的成員必須顯示建立非靜態內部類物件來呼叫訪問!
  • 根據靜態成員不能訪問非靜態成員的規則,外部類的靜態方法不能訪問非靜態內部類。
  • 非靜態內部類不允許定義靜態成員。如下面例子所示:
/**
 * @author GrimMjx
 */
public class Test {

    class Inner{
        static {
            
        }
        
    }

    // 靜態成員無法訪問非靜態成員
    public static void main(String[] args) {
        new Inner();
    }
}

1.1.2 內部類的特殊語法規則

  如果非靜態內部類方法訪問某個變數,其順序為

  1. 該方法是否有該名字的成員變數 - 直接用該變數名
  2. 內部類中是否有該名字的成員變數 - 使用this.變數名
  3. 外部類中是否有該名字的成員變數 - 使用外部類的類名.this.變數名

  接下來看一個例子:

/**
 * @author GrimMjx
 */
public class Outer {

    private int i = 1;

    public class Inner {
        private int i = 2;

        public void print() {
            int i = 3;
            System.out.println(i);
            System.out.println(this.i);
            System.out.println(Outer.this.i);
        }
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        Inner inner = outer.new Inner();
        inner.print();
    }
}

  執行結果:

3
2
1

 

1.2 靜態內部類

  如果用static來修飾一個內部類,那麼就是靜態內部類。這個內部類屬於外部類本身,但是不屬於外部類的任何物件。因此使用static修飾的內部類稱為靜態內部類。靜態內部類有如下規則:

  • 靜態內部類不能訪問外部類的例項成員,只能訪問外部類的類成員。
  • 外部類可以使用靜態內部類的類名作為呼叫者來訪問靜態內部類的類成員,也可以使用靜態內部類物件訪問其例項成員。
/**
 * 靜態內部類測試外部類。
 *
 * @author GrimMjx
 */
public class StaticInnerTest {
    private int x = 1;
    private static int y = 2;

    public void test(){
        System.out.println(new InnerClass().a);
        System.out.println(InnerClass.b);
    }

    static class InnerClass {
        private int a = 3;
        private static int b = 4;

        public void test(){
            //無法訪問
//            System.out.println(x);
            System.out.println(y);
        }
    }

    public static void main(String[] args) {
        StaticInnerTest staticInnerTest = new StaticInnerTest();
        staticInnerTest.test();

        InnerClass innerClass = new InnerClass();
        innerClass.test();
    }
}