1. 程式人生 > 實用技巧 >Java基礎之:OOP——內部類

Java基礎之:OOP——內部類

Java基礎之:OOP——內部類

在一個類A中又完整的聲明瞭另一個類B,那麼類A我們就稱為外部類,類B就稱為內部類。

內部類最大的特點就是可以直接訪問私有屬性,並且可以體現類與類之間的包含關係。

內部類的分類:

  1. 建立在外部類的方法內部:

    • 區域性內部類

    • 匿名內部類(實際也可以看作是,匿名區域性內部類)

  2. 建立在外部類的屬性位置:

    • 成員內部類

    • 靜態內部類(實際也可以看作是,靜態成員內部類)

區域性內部類

區域性內部類是定義在外部類的方法體中的類,具體使用方法與使用細節如下:

  • 1.可以直接訪問外部類的所有成員,包含私有的

  • 2.不能新增訪問修飾符,因為它的地位就是一個區域性變數。區域性變數是不能使用修飾符的,但是可以使用final 修飾,因為區域性變數也可以使用final

  • 3.作用域僅僅在定義它的方法或程式碼塊中,並且遵循前向引用原則

  • 4.外部類---訪問------>區域性內部類,建立物件呼叫

  • 5.外部其他類---不能訪問----->區域性內部類 (因為 區域性內部類地位是一個區域性變數),這點實際上是可以訪問的,不過要用到多型的性質。看下面思考題

  • 6.如果外部類和內部類的成員重名時,內部類訪問的話,預設遵循就近原則,如果想訪問外部類的成員,則可以使用 (外部類名.this.成員)去訪問

package class_inner;
/**
 * 
 *  1.可以直接訪問外部類的所有成員,包含私有的
 *  2.不能新增訪問修飾符,因為它的地位就是一個區域性變數。區域性變數是不能使用修飾符的。
 *      但是可以使用final 修飾,因為區域性變數也可以使用final 
 *  3.作用域僅僅在定義它的方法或程式碼塊中,並且遵循前向引用原則
 *  4.外部類---訪問------>區域性內部類,建立物件呼叫
 *  5.外部其他類---不能訪問----->區域性內部類 (因為 區域性內部類地位是一個區域性變數)
 *      這點實際上是可以訪問的,不過要用到多型的性質。看下面思考題。
 *  6.如果外部類和內部類的成員重名時,內部類訪問的話,預設遵循就近原則
 *      如果想訪問外部類的成員,則可以使用 (外部類名.this.成員)去訪問 
 */
public class LocalInnerDetail {
​
    public static void main(String[] args) {
        //細節4:建立Outer物件,呼叫say()方法,即呼叫了Inner區域性內部類
        new Outer().say();
    }
}
​
​
class Outer{
    private int a;
    
    public void say() {
        //細節3:此時Inner類還沒有執行到,不滿足向前引用原則,所以不能建立物件
//      Inner inner = new Inner();
        
        class Inner{
            private int b;
            public void innerShow() {
                //細節1:可以直接訪問外部類的所有成員,包含私有的
                System.out.println(a);
            }
        }
        
        //細節2:可以使用fanal修飾,因為區域性內部類,地位就相當於區域性變數
        final class InnerA{
            private int a;
            public void innerShow() {
                //細節6:就近原則 ,訪問到內部類的屬性a
                System.out.println(a);
                //細節6:訪問外部成員屬性,外部類名.this.成員
                System.out.println(Outer.this.a);
            }
        }
        
        Inner inner = new Inner();
        inner.innerShow();
    }
    
    //細節3:
//  Inner i = new Inner(); //報錯:Inner cannot be resolved to a type
    
}

關於細節2的補充說明:

區域性內部類中只能訪問使用final修飾的區域性變數(並且在同一個作用域內):

jdk7 final必須手動新增! jdk8 final是隱式加入,不用手動新增。

原因:防止區域性變數訪問範圍擴大!

匿名內部類(重要!!!)

匿名內部類也可以看作是匿名區域性內部類,這種內部類在實際開發中經常用到,所以比較重要。

匿名內部類的使用方式以及使用理解如下:

package class_inner.Anonymous;
​
public class AnonymousInner {
​
    public static void main(String[] args) {
        new XX().m1();  //輸出:AnonymousInner......
    }
}
​
class XX{
​
    public void m1() {
        /*
         * 對於a而言,編譯型別為A
         * 執行型別為:new 後面一直到 ";" 前面這一部分程式碼
         *  就像是一個類的宣告  但這個類並沒有名字,宣告出來之後直接通過new產生一個物件返回給A介面的引用a
         * a的執行型別就是一個匿名內部類
         */
        A a  = new A() {
            @Override
            public void show() {
                System.out.println("AnonymousInner......");
            }
        }; 
        
        /*
         * 對於show方法呼叫而言,它會先看編譯型別A介面中有沒有show方法,如果有編譯器通過。
         * 然後在執行時,通過動態繫結找到a的執行型別即匿名內部類中實現了的show方法,再執行。
         */
        a.show();
        
        //還可以使用匿名物件來建立匿名內部類,我們之前會使用匿名物件呼叫方法,例如: new A().show
        new A() {
            @Override
            public void show() {
                System.out.println("Other_AnonymousInner......");
            }
        }.show();
    }
}
​
//不僅可以使用介面,也可以使用父類建立匿名內部類
interface A{
    public void show();
    
}

匿名內部類的使用細節與區域性內部類,基本相同。但由於匿名內部類的使用方式讓我們看起來很"奇特",所以在這裡把兩種呼叫方式列出來做一個對比:

package class_inner.Anonymous;
​
public class AnonymousInner {
​
    public static void main(String[] args) {
        
        //可以使用匿名內部類,作為引數傳入方法中,呼叫順序於區域性內部類相同
        say(new B(){
            public void show() {
                System.out.println("interface_B.....");
            }
        });
        
        //列印匿名內部類的執行型別,可以看到是沒有名字的。
        System.out.println(new B() {
            @Override
            public void show() {
            }
        }.getClass());
        
        System.out.println(new Father("jack").getClass());   //匿名物件
        System.out.println(new Father("jack") {}.getClass());//匿名內部類
        
        //使用匿名內部類列印輸出資訊
        Father f = new Father("jack") {
            @Override
            public void show() {
                System.out.println("AnonymousExtends_Class_Father_name:" + getName());
            }
        };
        f.show();
        
    }
    
    public static void say(B b) {
        b.show();
    } 
}
​
​
interface B{
    public void show();
}
​
class Father{
    private String name;
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public Father(String name) {
        super();
        this.name = name;
    }
    
    public void show() {
        System.out.println("Class_Father:" + name);
    }
}

程式輸出:

interface_B.....

class class_inner.Anonymous.AnonymousInnerDetail$2

class class_inner.Anonymous.Father

class class_inner.Anonymous.AnonymousInnerDetail$3

AnonymousExtends_Class_Father_name:jack

匿名內部類的應用案例

有一個鈴聲介面Bell,裡面有個ring方法。 有一個手機類Cellphone,具有鬧鐘功能alarmclock,引數是Bell型別 測試手機類的鬧鐘功能,通過匿名內部類(物件)作為引數,列印:懶豬起床了 再傳入另一個匿名內部類(物件),列印:小夥伴上課了

package class_inner.Anonymous;

public class AnonymousInnerClassWork {
​
    public static void main(String[] args) {
        
        //方式一:匿名物件呼叫方法
        new Cellphone().alarmclock(new Bell(){
            @Override
            public void ring() {
                System.out.println("懶豬起床了");
            }
        }); 
        
        //方式二:實名物件呼叫方法
        Cellphone cellphone = new Cellphone();
        cellphone.alarmclock(new Bell() {
            @Override
            public void ring() {
                System.out.println("小夥伴上課了");
            }
        });
        
    }
}
​
class Cellphone {
    public void alarmclock(Bell b) {
        b.ring();
    }
}
​
interface Bell{
    public void ring();
}

  


成員內部類

成員內部類是宣告在外部類的成員屬性位置上的類,具體使用如下:

package class_inner;
​
import class_inner.OuterM.InnerM;
​
public class MemberInner {
​
    public static void main(String[] args) {
        
        //外部其他類---訪問---->成員內部類:
        //方式一:匿名物件,訪問成員內部類,呼叫方法
        new OuterM().new InnerM().show2();
        
        //方式二:
        OuterM outerM = new OuterM();
        //如果想要接收InnerM物件,必須要引包
        //import class_inner.OuterM.InnerM;
        InnerM i = outerM.gerInnnerM(); 
        i.show();
        
        //方式三:
        InnerM i2 = new OuterM().gerInnnerM();  
        i2.show2();
    }
​
}
class OuterM{
    private String name = "小范";
    private int age = 20;
    //可以使用所有的訪問修飾符來修飾成員內部類
    public  class InnerM{
        private String name = "小黃";
        public void show() {
            //可以訪問外部類所有屬性與方法,因為成員內部類本質地位就是成員屬性
            //如果外部類和內部類的成員重名時,如果想訪問外部類的成員,則可以使用(外部類名.this.成員)
            System.out.println("name:" + OuterM.this.name + "  age: " + age);
        }
        public void show2() {
            System.out.println("name:" + name + "  age: " + age);
        }
    }
    
    public InnerM gerInnnerM() {
        return new InnerM();
    }
}

程式輸出:

name:小黃 age: 20

name:小范 age: 20

name:小黃 age: 20

靜態內部類

靜態內部類又可能看作是靜態成員內部類,因為它就是成員內部類加上了static修飾。

具體使用如下:

package class_inner;
​
import class_inner.OuterM.InnerM;
​
public class MemberInner {
​
    public static void main(String[] args) {
        
        //外部其他類---訪問---->成員內部類:
        //方式一:匿名物件,訪問成員內部類,呼叫方法
        new OuterM().new InnerM().show2();
        
        //方式二:
        OuterM outerM = new OuterM();
        //如果想要接收InnerM物件,必須要引包
        //import class_inner.OuterM.InnerM;
        InnerM i = outerM.gerInnnerM(); 
        i.show();
        
        //方式三:
        InnerM i2 = new OuterM().gerInnnerM();  
        i2.show2();
    }
​
}
class OuterM{
    private String name = "小范";
    private int age = 20;
    //可以使用所有的訪問修飾符來修飾成員內部類
    public  class InnerM{
        private String name = "小黃";
        public void show() {
            //可以訪問外部類所有屬性與方法,因為成員內部類本質地位就是成員屬性
            //如果外部類和內部類的成員重名時,如果想訪問外部類的成員,則可以使用(外部類名.this.成員)
            System.out.println("name:" + OuterM.this.name + "  age: " + age);
        }
        public void show2() {
            System.out.println("name:" + name + "  age: " + age);
        }
    }
    
    public InnerM gerInnnerM() {
        return new InnerM();
    }
}

程式輸出:

name:小范

OuterS_phone:123

InnerS_Sphone:321

特別注意:

  1. 使用靜態內部類,只需要使用類名呼叫即可,就不會生成OuterS外部類的物件

  2. 出現重名的屬性時,只需要 (外部類名.屬性)就可以訪問

  3. 靜態內部類只能訪問外部靜態的內容

思考題

在區域性內部類的細節5中提到外部其他類不能訪問區域性內部類,但可以通過多型的特性將其實現。

具體方式是:

  1. 定義一個區域性內部類可以訪問到的介面或父類

  2. 讓區域性內部類繼承此介面或父類

  3. 在外部其他可以使用介面或父類的類中,就可以通過多型的方式使用到區域性內部類。

  4. 這種方式主要就是使用了多型的動態繫結機制

package test;
​
public class Test {
    public static void main(String[] args) {
        A a = new A();
        System.out.println("a--hashCode= " + a.hashCode());
        
        A a2 = a.say();
        a2.show();
        
//      X x = a.say();
//      x.show();
        
        
    }
}
​
interface X{
    public void show();
}
​
class A{
    public void show() {
        
    }
    private int n1 = 10;
    private int n3 = 330;
​
    public A /* X */ say() {
        int n2 = 20;
        
        class InnerA extends A /* implements X */{
            int n3 = 30;
            
            public void show() {
                System.out.println("A--n3=" + A.this.n3 + "\tInnerA--n3= " + n3);
                
                System.out.println("A--hashCode=" + A.this.hashCode());
            }
        }
        InnerA innerA = new InnerA();
        innerA.show();
        return innerA;
    }
}

  

程式輸出:

a--hashCode= 366712642

A--n3=330 InnerA--n3= 30

A--hashCode=366712642

A--n3=330 InnerA--n3= 30

A--hashCode=366712642

說明

在程式中可以使用介面 X 將區域性內部類"接出來",也可以使用父類 A 將區域性內部類"接出來"。

這種思路需要靈活應用動態繫結機制,考慮區域性內部類的執行型別問題。