java之內部類詳解

一杯涼茶發表於2016-12-03

      序言

        有位小同學要我寫一篇這個的總結,我說那好吧,那就動手寫總結一下這個內部類的知識,感覺這個在面試中也會經常遇到,內部類、反射、集合、IO流、異常、多執行緒、泛型這些重要的基礎知識大家都比較容易記不住。大概是自己平常用的比較少,所以經常性的會忘記,現在通過博文的方式記錄下來,以後忘記可以回過頭來自己看。

                                            --WH

 

一、什麼是內部類

        顧名思義,內部類就是在一個類的內部在定義一個類,比如,A類中定義一個B類,那麼B類相對A類來說就稱為內部類,而A類相對B類來說就是外部類了。

        在一個類中有很多屬性,比如、成員變數,成員方法,區域性變數,靜態方法等(這些區別應該知道把,如果不知道的應該先把這幾個弄清楚,然後在繼續往下看,對,說的就是你),那麼這樣一比較,B類在A類中的不同位置就可以被分為了不同的內部類了。總共有四種,

          1、成員內部類

          2、區域性內部類

          3、靜態內部類

          4、匿名內部類

        現在看的一臉懵逼沒關係,接下來我會一一講解這四種內部類和用法及區別

 

二、成員內部類

   1、什麼是成員內部類?

      例如:  Outer類中有一個私有的成員變數id、還有一個out方法。 在內部類Inner中只有一個in方法   (註明:寫的類中沒有具體意義,就是為了說明問題而寫的)      

//在A類中申明瞭一個B類,此B類就在A的內部,並且在成員變數的位置上,所以就稱為成員內部類
public class Outer {
    private int id;
    public void out(){
        System.out.println("這是外部類方法");
    }
    
    class Inner{        
        public void in(){
            System.out.println("這是內部類方法");
        }
    }
}

 

  2、如何例項化成員內部類呢?

public class Outer {
    private int id;
    public void out(){
        System.out.println("這是外部類方法");
    }
    
    class Inner{        
        public void in(){
            System.out.println("這是內部類方法");
        }
    }
}

----------------------------------------------------------------
public class Test{
        public static void main(String[] args) {
        //例項化成員內部類分兩步
        //1、例項化外部類
        Outer outObject = new Outer();
        //2、通過外部類呼叫內部類
        Outer.Inner inObject = outObject.new Inner();
     //測試,呼叫內部類中的方法
     inObject.in();//列印:這是內部類方法
    } }

    分析:這個由外部類建立內部類物件不難理解把,想想如果你要使用一個類中方法或者屬性,你就必須要先有該類的一個物件,同理,一個類在另一個類的內部,那麼想要使用這個內部類,就必須先要有外部類的一個例項物件,然後在通過該物件去使用內部類。 

  3、成員內部類能做哪些事情?

      ·訪問外部類的所有屬性(這裡的屬性包括私有的成員變數,方法),例子大多數還是上面的內容,稍微改變一點點

public class Outer {
    private int id;
    public void out(){
        System.out.println("這是外部類方法");
    }
    
    class Inner{    
        public void in(){
            System.out.println("這是內部類方法");
        }
     
     //內部類訪問外部類私有的成員變數
     public void useId(){
       System.out.println(id+3);。
     }

     //內部類訪問外部類的方法 
     public void useOut(){
       out(); 
     }  } }
--------------------------------------------------
public class Test{
        public static void main(String[] args) {
        //例項化成員內部類分兩步
        //1、例項化外部類
        Outer outObject = new Outer();
        //2、通過外部類呼叫內部類
        Outer.Inner inObject = outObject.new Inner();
     //測試
     inObject.useId();//列印3,因為id初始化值為0,0+3就為3,其中在內部類就使用了外部類的私有成員變數id。
     inObject.useOut();//列印:這是外部類方法
    } }
 

      如果內部類中的變數名和外部類的成員變數名一樣,那麼如何會怎麼樣呢?

public class Outer {
    private int id;//預設初始化0
    public void out(){
        System.out.println("這是外部類方法");
    }
    
    class Inner{    
        private int id=8;    //這個id跟外部類的屬性id名稱一樣。
        public void in(){            
            System.out.println("這是內部類方法");
        }
        
        public void test(){
            System.out.println(id);//輸出8,內部類中的變數會暫時將外部類的成員變數給隱藏
            //如何呼叫外部類的成員變數呢?通過Outer.this,想要知道為什麼能通過這個來呼叫,就得明白下面這個原理
            //想例項化內部類物件,就必須通過外部類物件,當外部類物件來new出內部類物件時,會
            //把自己(外部類物件)的引用傳到了內部類中,所以內部類就可以通過Outer.this來訪問外部類的屬性和方法,到這裡,你也就可以知道為什麼內部類可以訪問外部類的屬性和方法,這裡由於有兩個相同的
屬性名稱,所以需要顯示的用Outer.this來呼叫外部類的屬性,平常如果屬性名不重複,那麼我們在內部類中呼叫外部類的屬性和方法時,前面就隱式的呼叫了Outer.this。
System.out.println(Outer.this.id);//輸出外部類的屬性id。也就是輸出0 } } }

 

    藉助成員內部類,來總結內部類(包括4種內部類)的通用用法:

          1、要想訪問內部類中的內容,必須通過外部類物件來例項化內部類。

          2、能夠訪問外部類所有的屬性和方法,原理就是在通過外部類物件例項化內部類物件時,外部類物件把自己的引用傳進了內部類,使內部類可以用通過Outer.this去呼叫外部類的屬性和方法,一般都是隱式呼叫了,但是當內部類中有屬性或者方法名和外部類中的屬性或方法名相同的時候,就需要通過顯式呼叫Outer.this了。

          

    4、成員內部類的面試題

//要求:使用已知的變數,在控制檯輸出30,20,10。
 class Outer { public int num = 10; class Inner { public int num = 20; public void show() { int num = 30; System.out.println(?); System.out.println(??); System.out.println(???); } } }
class InnerClassTest { public static void main(String[] args) { Outer.Inner oi = new Outer().new Inner(); oi.show(); } }

    答案:num、this.num、Outer.this.num

    解析:這題你如何明白了上面總結中的第二點,那麼非常簡單,考察的就是1、區域性變數 2、this,和3、Outer.this,也就是內部類訪問外部類屬性方法的原理。這考察三個東西,

       1、在一個方法中,使用直接使用變數名,肯定使用的是區域性變數,因為會把大的成員變數給隱藏掉,這題中,也就是說show方法中的num會將內部類中的成員變數num隱藏掉,而內部類中的成員變數num又會把外部類中的成員變數num隱藏掉,所以通過直接輸出num,會是show方法中的區域性變數的值30.   

       2、使用this.num呼叫,其this代表的是呼叫該方法的物件,呼叫show方法的是oi,oi也就是內部類物件,所以oi.num也就標識內部類的成員變數num的值20

       3、Outer.this.num,呼叫的外部類中的成員變數num的值也就是10,這個如果不清楚就看上面總結中的第二點,就是那個原理。

 

三、區域性內部類

    1、什麼是區域性內部類,跟區域性變數一樣,在一個方法中定義的,那麼就是區域性內部類了。

    例如

public class Outer {
    private int id;
  //在method01方法中有一個Inner內部類,這個內部類就稱為區域性內部類
public void method01(){class Inner{ public void in(){ System.out.println("這是區域性內部類"); } } } }

     2、區域性內部類一般的作用跟在成員內部類中總結的差不多,但是有兩個要注意的地方,

            1、在區域性內部類中,如果要訪問區域性變數,那麼該區域性變數要用final修飾。看例子 

              為什麼需要使用final?

                final修飾變數:變為常量,會在常量池中放著,

                逆向思維想這個問題,如果不實用final修飾,當區域性內部類被例項化後,方法彈棧,區域性變數隨著跟著消失,這個時候區域性內部類物件在想去呼叫該區域性變數,就會報錯,因為該區域性變數已經沒了,當區域性變數用fanal修飾後,就會將其加入常量池中,即使方法彈棧了,該區域性變數還在常量池中呆著,區域性內部類也就是夠呼叫。所以區域性內部類想要呼叫區域性變數時,需要使用final修飾,不使用,編譯度通不過。

public class Outer {
    private int id;
    public void method01(){
        final int cid = 3;    //這個就是區域性變數cid。要讓區域性內部類使用,就得變為final並且賦值,如果不使用final修飾,就會報錯
        class Inner{
            //內部類的第一個方法
            public void in(){    
                System.out.println("這是區域性內部類");
            }
            //內部類中的使用區域性變數cid的方法
            public void useCid(){    
                System.out.println(cid);
            }
        }
        
    }
}

 

            2、區域性內部類不能通過外部類物件直接例項化,而是在方法中例項化出自己來,然後通過內部類物件呼叫自己類中的方法。看下面例子就知道如何用了

public class Outer {
    private int id;
    
    public void out(){
        System.out.println("外部類方法");
    }
    public void method01(){
        class Inner{
            public void in(){    
                System.out.println("這是區域性內部類");
            }
        }
//關鍵在這裡,如需要在method01方法中自己建立內部類例項,然後呼叫內部類中的方法,等待外部類呼叫method01方法,就可以執行到內部類中的方法了。 Inner In
= new Inner(); In.in(); } }

 

      總結:區域性內部類就只要注意那亮點就行了

          1、在區域性內部類中,如果要訪問區域性變數,那麼該區域性變數要用final修飾

          2、如何呼叫區域性內部類方法

 

 

四、靜態內部類

      根據名字就知道,使用static修飾的內部類,就叫做靜態內部類了。     

      回顧static的用法:一般只修飾變數和方法,平常不可以修飾類,但是內部類卻可以被static修飾。

          1、static修飾成員變數:整個類的例項共享靜態變數

          2、static修飾方法:靜態方法,只能夠訪問用static修飾的屬性或方法,而非靜態方法可以訪問static修飾的方法或屬性

          3、被static修飾了的成員變數和方法能直接被類名呼叫。

          4、static不能修飾區域性變數,切記,不要搞混淆了,static平常就用來修飾成員變數和方法。

      注意:

        1、我們上面說的內部類能夠呼叫外部類的方法和屬性,在靜態內部類中就行了,因為靜態內部類沒有了指向外部類物件的引用。除非外部類中的方法或者屬性也是靜態的。這就回歸到了static關鍵字的用法。

        2、靜態內部類能夠直接被外部類給例項化,不需要使用外部類物件

              Outer.Inner inner = new Outer.Inner();

        3、靜態內部類中可以什麼靜態方法和靜態變數,但是非靜態內部類中就不可以申明靜態方法和靜態變數

 

 

 

五、匿名內部類      

      這個在四種內部類中,使用的是最多的,在以後遇到的程式中,大多數出現的也就是它。

      什麼是匿名物件?如果一個物件只要使用一次,那麼我們就是需要new Object().method()。 就可以了,而不需要給這個例項儲存到該型別變數中去。這就是匿名物件。例如 

public class Test {
    public static void main(String[] args) {
        //講new出來的Apple例項賦給apple變數儲存起來,但是我們只需要用一次,就可以這樣寫
        Apple apple = new Apple();
        apple.eat();
        //這種就叫做匿名物件的使用,不把例項儲存到變數中。
        new Apple().eat();
    }
}
class Apple{
    public void eat(){
        System.out.println("我要被吃了");
    }
}

      

      匿名內部類跟匿名物件是一個道理,

        匿名物件:我只需要用一次,那麼我就不用宣告一個該型別變數來儲存物件了,

        匿名內部類:我也只需要用一次,那我就不需要在類中先定義一個內部類,而是等待需要用的時候,我就在臨時實現這個內部類,因為用次數少,可能就這一次,那麼這樣寫內部類,更方便。不然先寫出一個內部類的全部實現來,然後就呼叫它一次,豈不是用完之後就一直將其放在那,那就沒必要那樣。

      匿名內部類是如何個格式呢?

        前提:存在一個類或者介面  
                為什麼呢?
                例如:A介面  new A(){};  必須實現A介面中所有抽象方法,實現完之後,new A(){} 這個整體就是A的一個匿名實現類物件,肯定可以呼叫自己類中的方法
                    不一定是介面,可以是抽象類,也可以是完整的類,如果是抽象類的話,跟介面一樣,實現抽象的方法,如果是完整的類,就可以重寫自己所需要的方法,然後在呼叫,

舉個例子,看看不使用匿名內部類和使用了匿名內部類的區別

   不實用匿名內部類,真麻煩啊。

public class Test {
    public static void main(String[] args) {
    //如果我們需要使用介面中的方法,我們就需要走3步,1、實現介面 2、建立實現介面類的例項物件 3、通過物件呼叫方法
      //第二步      
     Test02 test = new Test02();
      //第三步
        test.method();
    }
}

//介面Test1
interface Test01{
    public void method();
}
//第一步、實現Test01介面
class Test02 implements Test01{

    @Override
    public void method() {
        System.out.println("實現了Test介面的方法");
    }
    
}

    使用了匿名內部類。

public class Test {
    public static void main(String[] args) {
      //如果我們需要使用介面中的方法,我們只需要走一步,就是使用匿名內部類,直接將其類的物件建立出來。     new Test1(){
        public void method(){
          System.out.println("實現了Test介面的方法");
        }
      }.method();
} }
interface Test1{ public void method(); }

      解析:其實只要明白一點,new Test1(){實現介面中方法的程式碼};  Test1(){...}這個的作用就是將介面給實現了,只不過這裡實現該介面的是一個匿名類,也就是說這個類沒名字,只能使用這一次,我們知道了這是一個類, 將其new出來,就能獲得一個實現了Test1介面的類的例項物件,通過該例項物件,就能呼叫該類中的方法了,因為其匿名類是在一個類中實現的,所以叫其匿名內部類,不要糾結為什麼Test1(){...}就相當於實現了Test1介面,這其中的原理等足夠強大了,在去學習,不要鑽牛角尖,這裡就僅僅是需要知道他的作用是什麼,做了些什麼東西就行。

 

匿名內部類的面試題: 

* A:面試題
* 
        按照要求,補齊程式碼
        interface Inter { void show(); }
        class Outer { //補齊程式碼 }
        class OuterDemo {
            public static void main(String[] args) {
                  Outer.method().show();
              }
        }
        要求在控制檯輸出”HelloWorld”

    答案:

* A:面試題
*
        按照要求,補齊程式碼
        interface Inter { void show(); }
        class Outer {
      //補齊程式碼
      public static Inter method(){
        return new Inter(){
          void show(){
            System.out.println("HelloWorld");
          }
        };
      }
     }
        class OuterDemo {
            public static void main(String[] args) {
                  Outer.method().show();
              }
        }
        要求在控制檯輸出”HelloWorld”

      

      解析:這個題目挺有趣的,考了很多東西,一開始只能說我自己度懵逼了,是一個區域性匿名內部類。  通過看主方法中的呼叫,Outer.method(),能直接用類名呼叫方法,那麼肯定該方法就是一個static方法,然後又直接呼叫了show()方法,說明這個method方法有一個返回值,其返回值型別就是實現該介面類的型別,因為只有介面中有show()這個方法。所以在method中就是一個匿名內部類。

              

 

相關文章