java面試整理(三)—— 內部類、靜態內部類、成員內部類、局部內部類、匿名內部類

知識總結

內部類

  1. 外部類只能是public或者默認,不可以是protected之類
  2. 內部類是一個編譯是的概念,一旦編譯成功,就會成爲完全不同的兩個類,分別爲outer.class和outer$inner.class類。內部類的成員變量/方法名可以和外部類的相同。
  3. 可以很好的實現隱藏
  4. 可是實現多重繼承
  5. 可以避免修改接口而實現同一個類中兩種同名方法的調用。

靜態內部類

  1. 靜態內部類與非靜態內部類的區別在於:靜態內部類沒有了指向外部的引用。
  2. 在靜態內部類new一個外部類的對象,可以訪問外部的非公開的(private 修飾的)成員變量與方法(jdk1.8測試)
  3. 靜態內部類中可以定義靜態或者非靜態的成員
  4. 在外部類中,創建的靜態內部類對象可以直接訪問對象中的所有成員變量和方法,無論是否是私有的。

非靜態內部類

  1. 非靜態內部類能訪問外部類的一切成員, 包括私有成員。
  2. 外部類雖然不能直接訪問內部類的成員, 但是可以通過內部類的實例訪問內部類的私有成員。
  3. 非靜態內部類可以獲得外部類的引用從而實現回調。
  4. 非靜態內部類不能有靜態成員,因爲內部類需要先創建了外部類,才能創建它自己的對象。但是其可以使用 static final 定義常量

成員內部類

  1. 成員內部類可以直接訪問外部類的所有成員變量、方法,但是外部類如果想要調用成員內部類中所有成員變量或方法則需要成員內部類的對象來獲取
  2. 相同名稱成員變量作用域重合時的調用,使用Outer.this.num調用外部類成員變量,this.num,調用內部類成員變量,num調用方法中的局部變量。若不重合,則直接使用成員變量調用即可。

局部內部類

  1. 局部內部類不能被public、private、static修飾;
  2. 局部內部類中不可定義靜態變量或方法,當訪問外部方法的局部變量(即方法內的變量)時,變量必須是final修飾的的(jdk1.8環境下可以不顯式的加final,但是我們不能對變量進行修改操作,因爲它是默認final型的)。
  3. 在外部類中不能創建局部內部類的實例
  4. 創建局部內部類的實例只能在包含他的方法中;
  5. 外部類不能訪問局部內部類,只能在方法體中訪問局部內部類,且訪問必須在內部類定義之後。

匿名內部類

  1. 爲了免去給內部類命名,或者只想使用一次,就可以選擇使用匿名內部類。
  2. 於匿名內部類沒有自己的名字,所以其在實例化時,必須繼承一個父類(可以是一個抽象類)或者實現一個接口,但是也只能是二者選其一,而不能即繼承父類,又實現接口。而且其繼承和實現都是隱式的,不是顯式的使用extends和implement關鍵字來繼承和實現的。
  3. 由於匿名內部類不能是抽象類,所以它必須要實現它的抽象父類或者接口裏面所有的抽象方法。
  4. 匿名內部類不能定義任何靜態成員、方法。
  5. 局部內部類的所有限制同樣對匿名內部類生效。
  6. 匿名內部類訪問的外部類成員變量或成員方法必須用final修飾;jdk1.8可以不需要顯式的聲明

內部類

又稱之爲嵌套類,是在類中在定義另外一個類。內部類幾乎可以處於一個類內部任何位置,可以與實例變量處於同一級,或處於方法之內,甚至是一個表達式的一部分。

外部類:包含內部類的類

內部類的名字不允許與外部類的名字相同。因爲在外部引用它時必須給出帶有外部類名的完整名稱(外部類名.內部類名)

外部類只能是public或者默認,不可以是protected之類;內部類訪問控制符均可,也可使用abstract或者final修飾。

內部類是一個編譯是的概念,一旦編譯成功,就會成爲完全不同的兩個類,分別爲outer.class和outer$inner.class類。所以內部類的成員變量/方法名可以和外部類的相同。

內部類關係
這裏寫圖片描述

內部類作用

1.內部類可以很好的實現隱藏
2.非靜態內部類擁有外圍類的所有元素的訪問權限 (private修飾也能訪問)
3.可是實現多重繼承 (讓多個內部類分別繼承多個其他類,使外部類可以同時獲取多個其他類的屬性)
4.可以避免修改接口而實現同一個類中兩種同名方法的調用。(外部類繼承,讓內部類實現接口)

靜態內部類

靜態內部類與非靜態內部類的區別在於:靜態內部類沒有了指向外部的引用。靜態內部類不與外部類有關係,即靜態內部類只是將類放在外部類中,但是在虛擬機編譯之後,其是兩個完全不同的兩個類,就和兩個普通類一樣,但又有所不同(可以看下面的),靜態內部類中不能直接調用外部類的成員變量或方法。而非靜態內部類則不同,其和外部類是有聯繫的,其可以直接調用外部類的方法和成員變量,即使外部類使用private修飾的變量或方法也一樣能夠調用。

若想在靜態內部類中調用外部類的成員變量或方法,只能在靜態內部類new一個外部類的對象,就像普通類調用對象一樣。和普通的兩個類不同的是,這種方法,可以訪問外部的非公開的(private 修飾的)成員變量與方法。(jdk1.8測試)

創建靜態內部類對象可以不依靠與外部類對象來實現

outClass.staticInnerClass obj = new outClass.staticInnerClass();

以下皆是在jdk1.8環境下測試

靜態內部類中可以定義靜態或者非靜態的成員
靜態內部類中可以直接訪問外部類的靜態成員變量或者方法,無論是否公開

同理

在外部類中調用靜態內部類,只需要直接new innerClass()即可,
在外部類中,創建的靜態內部類對象可以直接訪問對象中的所有成員變量和方法,無論是否是私有的。

可以參考以下代碼:

public class test {
    private String name = "test";
    private static String na = "ss";

     String test(){
        StaticInerCls inn = new StaticInerCls();
        return inn.name1;
    }

    static class StaticInerCls{
        private String name1 = "innerName";
        public String ss(){
            test t = new test();
            String s = t.name;
            String ss = t.test();
            String sss = na;
            return sss;
        }

    }
}

public class testA {

    public static void main(String[] args) {
        System.out.println(s());
        System.out.println(new test().test());
    }

    public static  String s(){
        test.StaticInerCls s = new test.StaticInerCls();
        return s.ss();
    }

}

非靜態內部類

非靜態內部類纔是真正意義上的內部類,其可以和外部類有直接聯繫。非靜態內部類能訪問外部類的一切成員, 包括私有成員。外部類雖然不能直接訪問內部類的成員, 但是可以通過內部類的實例訪問內部類的私有成員。非靜態內部類可以獲得外部類的引用從而實現回調。而且通過創建非靜態內部類還可已讓java實現真正的多繼承!

非靜態內部類不能有靜態成員,因爲內部類需要先創建了外部類,才能創建它自己的對象。但是其可以使用 static final 定義常量

常量池會維護這些常量。雖然非靜態內部類不能脫離外部類這個上下文實例化,但是常量池使得final變量脫離了類實例化這個條件,編譯期間便可確定。

成員內部類

成員內部類之所以定義爲成員內部類,原因在於其定義的位置和普通外部類的成員變量和成員方法一樣,把該內部類定義爲外部類的一個成員,也即其可成員變量、成員方法是同一級的。形如:

public class OuterCls {
    private String name;
    public String getName(){
        return name;
    }
    class InerCls{
        private String name;
        public String getName(){
            return name;
        }
    }

}

成員內部類可以直接訪問外部類的所有成員變量、方法,但是外部類如果想要調用成員內部類中所有成員變量或方法則需要成員內部類的對象來獲取

創建成員內部類對象:

OuterCls outerCls = new OuterCls();
OuterCls.InerCls inerCls = outerCls.new InerCls();  

成員內部類中可以含有常量

private static final String TYPE = "tes";

內部類訪問外部類成員變量

class Day10_11 {

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

    }
}
class Outer {
    public int num = 10;
    class Inner {
        public int num = 20;
        public void print(){
            int num = 30;
            System.out.println(num);//本方法中的局部變量,
            System.out.println(this.num);//內部類中的成員變量
            System.out.println(Outer.this.num);//外部類中的成員變量,Outer是外部類名
        }
    }
}

以上調用是在相同名稱成員變量作用域重合時的調用,若不重合,則直接使用成員變量調用即可

局部內部類

指內部類定義在方法體內,在局部內部類前不加修飾符public或private,只能在該方法或條件的作用域內才能使用,退出這寫作用域就無法引用。

局部內部類不能被public、private、static修飾;

局部內部類中不可定義靜態變量或方法當訪問外部方法的局部變量(即方法內的變量)時,變量必須是final修飾的的(jdk1.8環境下可以不顯式的加final,但是我們不能對變量進行修改操作,因爲它是默認final型的)。
解析:這是作用域的問題。在方法method執行完成後,局部變量value就失效了,而在new Inner()產生的in對象還存在obj的引用,這樣對象就訪問了一個不存在的變量,是不允許的。iner還存在,在外面和後續調用該局部變量時,這個局部變量可能已經失效了。但爲什麼加上final就可以保證能訪問呢?這裏Java採用了一種copy local variable的方法實現,定義爲final的變量,會拷貝一份存到局部內部類中,後續使用持續維護這個對象在生命週期內,所以可以繼續訪問。

在外部類中不能創建局部內部類的實例
創建局部內部類的實例只能在包含他的方法中;
外部類不能訪問局部內部類,只能在方法體中訪問局部內部類,且訪問必須在內部類定義之後。

匿名內部類

爲了免去給內部類命名,或者只想使用一次,就可以選擇使用匿名內部類。其和局部內部類一樣,也是定義在外部類的方法體內。

其創建方式如下格式:

new 父類構造器(參數列表)|實現接口()    
    {    
     //匿名內部類的類體部分    
    }  

從上面我們可以看出,由於匿名內部類沒有自己的名字,所以其在實例化時,必須繼承一個父類(可以是一個抽象類)或者實現一個接口,但是也只能是二者選其一,而不能即繼承父類,又實現接口。而且其繼承和實現都是隱式的,不是顯式的使用extends和implement關鍵字來繼承和實現的。同時它也是沒有class關鍵字,這是因爲匿名內部類是直接使用new來生成一個對象的引用。當然這個引用是隱式的。

在創建匿名內部類時,由於匿名內部類不能是抽象類,所以它必須要實現它的抽象父類或者接口裏面所有的抽象方法
可以改寫父類中的方法,添加自定義方法。

我們常用的線程的方法一般就是使用匿名內部類,如:

public void countDown(){
        new Thread(){
            @Override
            public void run() {
                ...
            }
        }.start();
    }

匿名內部類的使用它是存在一個缺陷的,就是它僅能被使用一次

匿名內部類不能定義任何靜態成員、方法。
匿名內部類中的方法不能是抽象的,因爲其本身不是抽象類;
匿名內部類爲局部內部類,所以局部內部類的所有限制同樣對匿名內部類生效
匿名內部類訪問的外部類成員變量或成員方法必須用final修飾;jdk1.8可以不需要顯式的聲明

匿名內部類因爲沒有類名,可知匿名內部類不能定義構造器。其會藉助與父類的默認構造器,那麼怎麼來初始化匿名內部類呢?使用構造代碼塊!利用構造代碼塊能夠達到爲匿名內部類創建一個構造器的效果。如:

public class OutClass {  
    public InnerClass getInnerClass(final int age,final String name){  
        return new InnerClass() {  
            int age_ ;  
            String name_;  
            //構造代碼塊完成初始化工作  
            {  
                if(0 < age && age < 200){  
                    age_ = age;  
                    name_ = name;  
                }  
            }  
            public String getName() {  
                return name_;  
            }  

            public int getAge() {  
                return age_;  
            }  
        };  
    }  

    public static void main(String[] args) {  
        OutClass out = new OutClass();  

        InnerClass inner_1 = out.getInnerClass(201, "chenssy");  
        System.out.println(inner_1.getName());  

        InnerClass inner_2 = out.getInnerClass(23, "chenssy");  
        System.out.println(inner_2.getName());  
    }  
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章