小白學Java:內部類

小白學Java:內部類

內部類是封裝的一種形式,是定義在類或接口中的類。編程

內部類的分類

成員內部類

即定義的內部類做爲外部類的一個普通成員(非static),就像下面這樣:設計模式

public class Outer {
    class Inner{
        private String id = "夏天";

        public String getId() {
            return id;
        }
    }

    public Inner returnInner(){
        return new Inner();
    }
    public void show(){
        Inner in = new Inner();
        System.out.println(in.id);
    }
}

咱們經過以上一個簡單的示例,能夠得出如下幾點:ide

  • Inner類就是內部類,它的定義在Outer類的內部。
  • Outer類中的returnInner方法返回一個Inner類型的對象
  • Outer類中的show方法經過咱們熟悉的方式建立了Inner示例並訪問了其私有屬性。

能夠看到,咱們像使用正常類同樣使用內部類,但實際上,內部類有許多奧妙,值得咱們去學習。至於內部類的用處,咱們暫且不談,先學習它的語法也不遲。咱們在另一個類中再試着建立一下這個Inner對象吧:學習

class OuterTest{
    public static void main(String[] args) {
        //!false:Inner in = new Inner();
        Outer o = new Outer();
        o.show();
        Outer.Inner in = o.returnInner();
        //!false: can't access --System.out.println(in.id);
        System.out.println(in.getId());
    }
}

哦呦,有意思了,咱們在另外一個類OuterTest中再次測試咱們以前定義的內部類,結果出現了很是明顯的變化,咱們陷入了沉思:測試

  • 咱們不可以像以前同樣,用Inner in = new Inner();建立內部類實例。
  • 不要緊,咱們能夠經過Outer對象的returnInner方法,來建立一個實例,成功!
  • 須要注意的是:咱們若是須要一個內部類類型的變量指向這個實例,咱們須要明確指明類型爲:Outer.Inner,即外部類名.內部類名
  • 好啦,獲得的內部類對象,咱們試着直接去訪問它的私有屬性!失敗!
  • 那就老老實實地經過getId方法訪問吧,成功!

說到這,咱們大概就能猜想到:內部類的存在能夠很好地隱藏一部分具備聯繫代碼,實現了那句話:我想讓你看到的東西你隨便看,不想讓你看的東西你想看,門都沒有。this

連接到外部類

其實咱們以前在分析ArrayList源碼的時候,曾經接觸過內部類。咱們在學習迭代器設計模式的時候,也曾領略過內部類帶了的奧妙之處。下面我經過《Java編程思想》上:經過一個內部類實現迭代器模式的簡單案例作相應的分析與學習:
首先呢,定義一個「選擇器」接口:設計

interface Selector {
    boolean end();//判斷是否到達終點
    void next();//移到下一個元素
    Object current();//訪問當前元素
}

而後,定義一個序列類Sequence:指針

public class Sequence {
    private Object[] items;
    private int next = 0;
    //構造器
    public Sequence(int size) {
        items = new Object[size];
    }
    public void add(Object x) {
        if (next < items.length) {
            items[next++] = x;
        }
    }
    //該內部類能夠訪問外部類全部成員(包括私有成員)
    private class SequenceSelector implements Selector {
        private int i = 0;
        @Override
        public boolean end() {
            return i == items.length;
        }
        @Override
        public void next() {
            if (i < items.length) {
                i++;
            }
        }
        @Override
        public Object current() {
            return items[i];
        }
    }
    //向上轉型爲接口,隱藏實現的細節
    public Selector selector() {
        return new SequenceSelector();
    }
}
  • 內部類SequenceSelector以private修飾,實現了Selector接口,提供了方法的具體實現。
  • 內部類訪問外部類的私有成員items,能夠得出結論:內部類自動擁有對其外部類全部成員的訪問權。

當內部類是非static時,當外部類對象建立了一個內部類對象時,內部類對象會產生一個指向外部類的對象的引用,因此非static內部類能夠看到外部類的一切。code

  • 外部類Sequenceselector方法返回了一個內部類實例,意思就是用接口類型接收實現類的實例,實現向上轉型,既隱藏了實現細節,又利於擴展。

咱們看一下具體的測試方法:

public static void main(String[] args) {
        Sequence sq = new Sequence(10);
        for (int i = 0; i < 10; i++) {
            sq.add(Integer.toString(i));
        }
        //產生咱們設計的選擇器
        Selector sl = sq.selector();

        while (!sl.end()) {
            System.out.print(sl.current() + " ");
            sl.next();
        }
    }
  • 隱藏實現細節:使用Sequence序列存儲對象時,不須要關心內部迭代的具體實現,用就完事了,這正是內部類配合迭代器設計模式體現的高度隱藏。
  • 利於擴展:咱們若是要設計一個反向迭代,能夠在Sequence內部再定義一個內部類,並提供Selector接口的實現細節,及其利於擴展,妙啊。

.new和.this

咱們稍微修改一下最初的Outer:

public class Outer {
    String id = "喬巴";
    class Inner{
        private String id = "夏天";

        public String getId() {
            return id;
        }
        public String getOuterId(){
            return Outer.this.id;
        }
        public Outer returnOuter(){
            return Outer.this;
        }
    }
    public static void main(String[] args) {
        Outer o = new Outer();
        System.out.println(o.new Inner().getId());//夏天
        System.out.println(o.new Inner().getOuterId());//喬巴
    }
}
  • 在內部類Inner體內添加了returnOuter的引用,return Outer.this;,即外部類名.this
  • 咱們能夠發現,內部類內外具備同名的屬性,咱們在內部類中,不加任何修飾的狀況下默認調用內部類裏的屬性,咱們能夠經過引用的形式訪問外部類的id屬性,即Outer.this.id

咱們來測試一波:

public static void main(String[] args) {
        Outer.Inner oi = new Outer().new Inner();
        System.out.println(oi.getId());//夏天
        Outer o = oi.returnOuter();
        System.out.println(o.id);//喬巴
    }
  • 外部類產生內部類對象的方法已經被咱們刪除了,這時咱們若是想要經過外部類對象建立一個內部類對象:Outer.Inner oi = new Outer().new Inner();,即在外部類對象後面用.new 內部類構造器

咱們對內部類指向外部類對象的引用進行更加深刻的理解與體會,咱們會發現,上面的代碼在編譯以後,會產生兩個字節碼文件:Outer$Inner.classOuter.class。咱們對Outer$Inner.class進行反編譯:
136jn1.png
確實,內部類在建立的過程當中,依靠外部類對象,並且會產生一個指向外部類對象的引用

局部內部類

方法做用域內部類

即在方法做用域內建立一個完整的類。

public class Outer {
    public TestOuter test(final String s){
        class Inner implements TestOuter{
            @Override
            public void testM() {
                //!false: s+="g";
                System.out.println(s);
            }
        }
        return new Inner();
    }
    public static void main(String[] args) {
        Outer o = new Outer();
        o.test("天喬巴夏").testM();//天喬巴夏
    }
}
interface TestOuter{
    void testM();
}

須要注意兩點:

  • 此時Inner類是test方法的一部分,Outer不能在該方法以外訪問Inner。
  • 方法傳入的參數s和方法內自己的局部變量都須要以final修飾,不能被改變!!!

JDK1.8以後能夠不用final顯式修飾傳入參數和局部變量,但其自己仍是至關於final修飾的,不可改變。咱們去掉final,進行反編譯:
136OXR.png

任意做用域內的內部類

能夠將內部類定義在任意的做用域內:

public class Outer {
    public void test(final String s,final int value){
        final int a = value;
        if(value>2){
            class Inner{
                public void testM() {
                    //!false: s+="g";
                    //!false: a+=1;
                    System.out.println(s+", "+a);
                }
            }
            Inner in = new Inner();
            in.testM();
        }
        //!false:Inner i = new Inner();
    }
    public static void main(String[] args) {
        Outer o = new Outer();
        o.test("天喬巴夏",3);
    }
}

一樣須要注意的是:

  • 內部類定義在if條件代碼塊中,並不意味着建立該內部類有相應的條件。內部類一開始就會被建立,if條件只決定能不能用裏頭的東西。
  • 如上所示,if做用域以外,編譯器就不認識內部類了,由於它藏起來了。

靜態內部類

即用static修飾的成員內部類,歸屬於類,即它不存在指向外部類的引用

public class Outer {
    static int a = 5;
    int b = 6;
    static class Inner{
        static int value;
        public void show(){
            //!false System.out.println(b);
            System.out.println(a);
        }
    }
}
class OuterTest {
    public static void main(String[] args) {
        Outer.Inner oi = new Outer.Inner();
        oi.show();
    }
}

須要注意的是:

  • 靜態內部類也能夠定義非靜態的成員屬性和方法。
  • 靜態內部類對象的建立不依靠外部類的對象,能夠直接經過:new Outer.Inner()建立內部類對象。
  • 靜態內部類中能夠包含靜態屬性和方法,而除了靜態內部類以外,即咱們上面所說的全部的內部類內部都不能有(可是能夠有靜態常量static final修飾)。
  • 靜態內部類不能訪問非靜態的外部類成員。
  • 最後,咱們反編譯驗證一下:

136Lc9.png

匿名內部類

這個類型的內部類,看着名字就怪怪的,咱們先看看一段違反咱們認知的代碼:

public class Outer {
    public InterfaceInner inner(){
    //建立一個實現InterfaceInner接口的是實現類對象
        return new InterfaceInner() {
            @Override
            public void show() {
                System.out.println("Outer.show");
            }
        };
    }
    public static void main(String[] args) {
        Outer o = new Outer();
        o.inner().show();
    }
}
interface InterfaceInner{
    void show();
}

真的很是奇怪,乍一看,InterfaceInner是個接口,而Outer類的inner方法怎麼出現了new InterfaceInner()的字眼呢?接口不是不能建立實例對象的麼?

確實,這就是匿名內部類的一個使用,其實inner方法返回的是實現了接口方法的實現類對象,咱們能夠看到分號結尾,表明一個完整的表達式,只不過表達式包含着接口實現,有點長罷了。因此上面匿名內部類的語法其實就是下面這種形式的簡化形式:

public class Outer {   
    class Inner implements InterfaceInner{
        @Override
        public void show(){
            System.out.println("Outer.show");
        }
    }
    public InterfaceInner inner(){ 
        return new Inner();  
    }
    public static void main(String[] args) {
        Outer o = new Outer();
        o.inner().show();
    }
}
interface InterfaceInner{
    void show();
}

不只僅是接口,普通的類也能夠被看成「接口」來使用:

public class Outer {
    public OuterTest outerTest(int value) {
        //參數傳給匿名類的基類構造器
        return new OuterTest(value) {
            
            @Override
            public int getValue() {
                return super.getValue() * 10;
            }
        };
    }
    public static void main(String[] args) {
        Outer o = new Outer();
        System.out.println(o.outerTest(10).getValue());//100
    }
}
class OuterTest {
    public int value;
    OuterTest(int value) {
        this.value = value;
    }
    public int getValue() {
        return value;
    }
}

須要注意的是:

  • 匿名類既能夠擴展類,也能夠實現接口,固然抽象類就再也不贅述了,普通類均可以,抽象類就更能夠了。但不能同時作這兩件事,且每次最多實現一個接口。
  • 匿名內部類沒有名字,因此自身沒有構造器。
  • 針對類而言,上述匿名內部類的語法就代表:建立一個繼承OuterTest類的子類實例。因此能夠在匿名內部類定義中調用父類方法與父類構造器。
  • 傳入的參數傳遞給構造器,沒有在類中直接使用,能夠不用在參數前加final。

內部類的繼承

內部類能夠被繼承,可是和咱們普通的類繼承有些出處。具體來看一下:

public class Outer {
    class Inner{
        private int value = 100;
        Inner(){
        }
        Inner(int value){
            this.value = value;
        }
        public void f(){
            System.out.println("Inner.f "+value);
        }
    }
}
class TestOuter extends Outer.Inner{
    TestOuter(Outer o){
        o.super();
    }
    TestOuter(Outer o,int value){
        o.super(value);
    }

    public static void main(String[] args) {
        Outer o = new Outer();
        TestOuter tt = new TestOuter(o);
        TestOuter t = new TestOuter(o,10);
        tt.f();
        t.f();
    }
}

咱們能夠發現的是:

  • 一個類繼承內部類的形式:class A extends Outer.Inner{}
  • 內部類的構造器必須連接到指向外部類對象的引用上,o.super();,即都須要傳入外部類對象做爲參數。

內部類有啥用

能夠看到的一點就是,內部類內部的實現細節能夠被很好地進行封裝。並且Java中存在接口的多實現,雖然必定程度上彌補了Java「不支持多繼承」的特色,但內部類的存在使其更加優秀,能夠看看下面這個例子:

//假設A、B是兩個接口
class First implements A{
    B makeB(){
        return new B() {
        };
    }
}

這是一個經過匿名內部類實現接口功能的簡單的例子。對於接口而言,咱們徹底能夠經過下面這樣進行,由於Java中一個類能夠實現多個接口:

class First implements A,B{
}

可是除了接口以外,像普通的類,像抽象類,均可以定義獨立的內部類去單獨繼承並實現,使用內部類使「多重繼承」更加完善


因爲後面的許多內容尚未涉及到,學習到,因此總結的比較淺顯,並無作特別深刻,特別真實的場景模擬,以後有時間會再作系統性的總結。若是有敘述錯誤的地方,還望評論區批評指針,共同進步。
參考:
《Java 編程思想》
https://stackoverflow.com/questions/70324/java-inner-class-and-static-nested-class?r=SearchResults

相關文章
相關標籤/搜索