Java 內部類

1 、說點閒話

  由於我的緣由,布衣博主的技術博文大半年來一直沒時間更新(WHAT ? 這是啥理由),恍恍惚惚間,一年又是頭,仍是得跳出來,給本身一個交代。java

  編程日久,項目開發中最經常使用的技能大概就是 Ctrl+C  再 Ctrl+V 了,實實在在的代碼搬運工。這很正常,人愈來愈墮,能用現成有的東西毫不會本身造;尤爲上班這種食人俸祿的差事,要講究效率和產出,拷貝修改,雖然低級,卻彷佛是最高效的作法。不過久而久之的,從碼農自身來看,對於本身能力的提高,技能體系的構建,確定是弊大於利的。因此,有時間仍是要沉下來,看書,鑽研,學習新東西,與時同步。最近在看《Java 編程思想(第四版)》這本書,對博主這種對Java初通皮毛的人來講,能同時兼顧基礎和深度,受益頗多,也是對本身已有的Java知識進行了比較系統的梳理。固然,技術最重要的在於交流分享,因此,讀書和實踐過程當中布衣博主也會有一些本身的心得體會的分享出來,算是拋磚引玉吧。這篇先講講Java內部類相關的東西。都是結合書本內化而來的,有知識點,也有布衣博主本身的料。程序員

二、認識內部類

  內部類,顧名思義,就是一個定義在已有的Java類內部的類,就像下面這樣:編程

public class Outer {  //外部類
    class Inner{    //內部類
        public void test(){
            System.out.println("內部類方法");
        }
    }
}

  而後呢?沒有而後了,就先這樣簡潔直觀的認識它吧,相信我,內部類原本就很簡單,別搞得太複雜了。緩存

3 、內部類對象建立和基本使用

  內部類既然是個Java類,天然是能夠建立對象的。固然,這話不許確,往深了說,內部類也能夠是抽象的,這確定是沒法建立對象的——不過你若是要這樣死究的話,我以爲就有點太囿於語法了,對實際運用幫助不大。簡單點的掌握內部類常見的普通狀況:普通內部類、靜態內部類以及匿名內部類足夠了。下面分別介紹——框架

   3.1 普通內部類

  普通狀況,或者說最典型的狀況,就是一個Java類嵌在另外一個Java類中,造成了內、外的格局;外部類就是咱們普通的類,內部類也是普通的類,特性都知足Java類的特性,沒什麼特別的。惟一要說的,就是內部類對象的建立依賴於外部類對象。這很好理解,若是在外部類都沒有建立對象的基礎上,內部類建立的對象依附於哪一個實體呢?也能夠說是皮之不存毛將焉附?因此,內部類的對象建立是這樣的:
ide

public class InnerTest {
    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.test(); //有了內部類對象你就能夠像正常的類對象同樣進行方法調用的騷操做了。
    }
}

  不過,一般狀況下,也不會這樣去建立內部類對象,基於Java封裝的特性,內部類做爲一種封裝的體現,一般會對調用者隱藏實現細節,而經過外部類的方法來返回內部類的對象實例。普通內部類雖然和外部類是兩個類,可是由於處於一個類的內部,仍是有些不同的特性——它能夠訪問外部類的因此成員,包括私有的字段、方法等,這是由於內部類中隱含的持有對外部類的引用,經過這個引用,就能調用外部類的全部成員了。這有點相似於局部和全局的關係,內部類處於局部,可以直接調用做爲全局的外部類的其它成員。說到這裏,有個誤區須要注意。常常看網上說法就說內部類對象能夠訪問外部類的成員,好像我建立的內部類對象能夠直接調用外部類的成員同樣,博主也差點就信了,代碼嘗試,大靠一聲,怎麼能訪問的?因此,我以爲,技術人員對文字的理解仍是有些不夠嚴謹的地方,包括書本上的說法,或由於翻譯的緣由,一些表達上並非那麼貼切的,這個只能本身多去思辨實踐了。這裏,咱們先給外部類定義方法、字段等來完善一下:學習

 

public class Outer {
    private String str;

    public void buyi() {
    System.out.println("外部類方法"); }
class Inner { public void test() { buyi(); // str = "陳本布衣"; // System.out.println("內部類方法"); } } }
public class InnerTest {
    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.test(); 
        inner.str;       //
        buyi.buyi();     //
    }
}

  上面代碼中,① ②纔是上文中說的,內部類能夠訪問外部類的全部成員的狀況,③和④ 你會發現,是沒法編譯經過的。也就是說,正確的理解應該是內部類的內部能夠經過隱含的外部類的引用去訪問外部類的全部成員,而不是內部類對象能夠訪問外部類的成員,這是有本質區別的,固然你也不能拿着內部類對象的引用去訪問外部類的成員,要搞清楚,這是畢竟是兩個類,類的封裝性決定了,一個類的對象是不能去訪問另外一個類對象的非靜態成員的。測試

  以上所見都是看起來很正常的內部類,比較普通,其實內部類還有很複雜的狀況,好比定義在方法內部、代碼塊內部的局部內部類,這裏就不深究了。我以爲,做爲基礎性掌握,沒必要太追求全面了,先入門,用精,當你技術積累夠了,不少東西也就通了。優化

   3.2 靜態內部類

  Java中的靜態,指全局的,好比靜態方法、成員變量等,若是訪問權限容許,你在任何地方都能都直接使用。未了解內部類以前,不知道你有沒有想過,類可不能夠也是靜態呢?是的,類也能夠是靜態的,不過必須是內部類才行。靜態內部類,也叫嵌套類,Java中標準類庫中就有不少現成的應用,好比整形的包裝器中用靜態內部類來緩存[-128,127]之間的整數。嵌套類由於是靜態的,很天然的要從靜態方面去考慮與普通內部類的區別。這裏用博主的理解來闡述一遍:this

  ① 相似於靜態方法中你不能使用this關鍵字,於是嵌套類就失去了普通內部類中那個隱含對外部類的引用this,結果就是你不能在嵌套類中隨意訪問外部類的非靜態成員了;

  ② 靜態屬性決定了,嵌套類更加獨立於外部類,要建立嵌套類的對象徹底不依賴外部類的對象實體;

  ③ 靜態屬性決定了,它會在外部類加載的時候初始化。。。等等,博主差寫高興了,想固然的東西到底靠不靠譜呢?實踐是永遠是檢驗真理的惟一標準,上代碼——

public class Outer {
    Outer() {
        System.out.println("默認構造器");
    }

    static {
        System.out.println("外部靜態塊");
    }

    static void outerstatic() {
        System.out.println("外部靜態方法");
    }

    static class Inner {
        public static  String str = "陳本布衣";

        static {
            System.out.println("嵌套類靜態代碼塊");
        }

        static void innerstatic() {
            System.out.println("嵌套類靜態方法");
        }

        public void test() {
            System.out.println("嵌套類非靜態方法");
        }
    }
}
public class InnerTest {
    public static void main(String[] args) {
        Outer outer = new Outer();                       //
        Outer.Inner inner = new Outer.Inner();           // 
        Outer.Inner.innerstatic();                       // 
        String s = Outer.Inner.str;                      // 
    }
}

  稍微有點Java基礎的都清楚,靜態成員是一個類中最早被初始化的部分,因此,若是咱們只經過 ① 建立外部類的對象,那麼Outer類中的靜態代碼塊確定會執行,控制檯有相應的打印,那靜態內部類會不會也被初始化呢? 測試結果是不會,這時候靜態類有點像靜態方法,你主動叫它,它就靜靜的待在哪裏,不爲所動! 單獨執行 ② 你會發現,外部類靜態塊沒有初始化,也便是靜態內部類獨立於外部類,它的初始化不會對外部類有任何影響;執行 ③ ④ 一樣的,你會發現,只有在要使用類的內部屬性的時候,代碼塊纔會初始化,一樣的初始化對外部類的初始化沒有產生影響,就像外部類徹底不知道內部類在幹什麼同樣。

  這個時候咱們再去看包裝器中關於緩存的靜態內部類的使用就會更加透徹一些,以Integer包裝器爲例:

 
// 源碼中的靜態內部類
 private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }


// 源碼中的裝箱操做
   public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
   }

  代碼中,只是經過靜態內部類定義了緩存值的默認範圍,在你的程序中,若是你沒有主動的執行裝箱或自動的執行裝箱轉換,靜態內部類是不會加載進內存的,對內存沒有任何佔用;只有當你的代碼有了裝箱操做,纔會對靜態內部類產生調用,纔會將該類加載進內存。從這個方面去想,Java的設計者經過對於代碼的編寫的優化是比我等菜鳥要高深那麼一點點點點。。。。

   3.3  匿名內部類

  繼續顧名思義,匿名內部類,就是沒有類名的內部類。缺乏了類名,也就意味着沒有構造器,那怎麼建立對象呢?只能直接new 它的類實體,能夠這麼說,匿名內部類是伴隨着類定義的同時就必須被實例化的。咱們經過匿名內部類來建立線程就是很好的例子:

public class ThreadTest {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {

            }
        });
        thread.start();
    }
}

  (PS : 對Java8的Lambda表達式比較熟悉的老司機可能會像下面這樣去寫,看起來代碼簡潔逼格也挺高,不過說實話,這樣寫代碼我看了內心是有句mmp不知當講不當講的,項目組的維護和溝通成本都很高。只能說,對於新技術仍是不要盲目的跟風吧。)

public class ThreadTest {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
        });
        thread.start();
    }
}

  線程的建立應該是咱們比較常見的匿名內部類的應用了。不過上訴代碼你若是不用匿名內部類,也能夠先實現了接口,經過接口的實現再來建立對象,可是,何須舍近道而繞遠路呢?這裏也能夠看出匿名內部類特色了,它幫咱們省去了單獨實現接口再來建立對象的不少冗餘的步驟。因此你該知道,匿名內部類本質上就是一種代碼上的減省,實際上它仍是在遵循着Java實現(繼承)後再建立對象的語法邏輯的,不信看下面的代碼:

public class Model {
    private int i;

    public Model(int i) {
        this.i = i;
    }

    public int value() {
        return i;
    }

    @Override
    public String toString() {
        return "Model{" +
                "i=" + i +
                '}';
    }
}
View Code
public class Demo {
    public Model getModel(int var){
        return new Model(var){
            public int value(){
                System.out.println("方法調用");
                return super.value()*20;
            }
        };
    }

    @Test
    public void test(){
        Model model = getModel(3);
        System.out.println(model);       //此時value的值仍是 3 ,由於匿名類中的方法還沒被調用
        System.out.println(model.value()); // 此時value被賦值爲 60
    }
}

  這段段代碼說明兩點:① 經過匿名內部類的方式返回Model對象的時候是有繼承體系的,也即匿名實體是繼承了Model類的,否則實體中的沒法使用super調用父類方法;② 不僅是接口的實現能夠用匿名內部類,普通類的繼承也是能夠的,只要知足繼承體系便可。

4 、我的叨叨

  內部類本人平時開發中用得也很少,單純的從功能實現來說,漫長的碼農生涯中徹底不用內部類也徹底無礙,可是Java的設計者們爲何還要搞這套語法呢?追蹤Java標準類庫的一些源碼你會發現,平時經常使用的容器類、整形包裝器等都有大量使用內部類的場景;而平時引入的第三方類庫、框架中的源碼也有不少使用內部類的。沒有對比就沒有傷害,當你翻看本身的代碼老是一坨一坨拼湊感極強,源碼框架中的代碼老是那麼的優雅時,你還能說什麼呢,還不能讓你對代碼編寫質量,對內部類這種語法結構的使用多一份心思嗎?尤爲是在沒有代碼審查制度對代碼質量也沒有嚴格要求的公司,程序員的在代碼上的任意發揮簡直是程序bug之母,因此,像布衣博主同樣,雖然對內部類在代碼編寫上的妙用,還體會得不夠深入,但優化代碼時,嘗試着往源碼的思路靠,樣子走,總仍是有些益處的。

相關文章
相關標籤/搜索