JAVA內部類

一:什麼是內部類?

定義:  內部類是指在一個外部類的內部再定義一個類。內部類作爲外部類的一個成員,並且依附於外部類而存在的。內部類可爲靜態,可用protected和private修飾(而外部類只能使用public和缺省的包訪問權限)。內部類主要有以下幾類:成員內部類、局部內部類、靜態內部類、匿名內部類。

內部類有兩種情況:
(1) 在類中定義一個類(私有內部類,靜態內部類)

(2) 在方法中定義一個類(局部內部類,匿名內部類)

二、爲什麼需要內部類?

主要原因有以下幾點:
  1. 內部類方法可以訪問該類定義所在的作用域的數據,包括私有的數據
  2. 內部類可以對同一個包中的其他類隱藏起來,一般的非內部類,是不允許有 private 與protected權限的。
  3. 當想要定義一個回調函數且不想編寫大量代碼時,使用匿名內部類比較便捷、方便編寫線程代碼
  4. 每個內部類都能獨立的繼承一個接口的實現,所以無論外部類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。內部類使得多繼承的解決方案變得完整。  每個內部類都能獨立地繼承自一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。大家都知道Java只能繼承一個類,它的多重繼承在我們沒有學習內部類之前是用接口來實現的。但使用接口有時候有很多不方便的地方。比如我們實現一個接口就必須實現它裏面的所有方法。而有了內部類就不一樣了。它可以使我們的類繼承多個具體類或抽象類。

二、內部類解析

1、成員內部類

  • 即在一個類中直接定義的內部類, 成員內部類與普通的成員沒什麼區別,可以與普通成員一樣進行修飾和限制。成員內部類不能含有static的變量和方法。
  • 成員內部類可以無條件訪問外部類的所有成員屬性和成員方法(包括private成員和靜態成員)。
  • 在外部類中如果要訪問成員內部類的成員,必須先創建一個成員內部類的對象,再通過指向這個對象的引用來訪問。
  • 由於成員內部類看起來像是外部類的一個成員,所以可以像類的成員一樣擁有多種權限修飾。可以擁有private訪問權限、protected訪問權限、public訪問權限及包訪問權限。如果成員內部類用private修飾,則只能在外部類的內部訪問,如果用public修飾,則任何地方都能訪問;如果用protected修飾,則只能在同一個包下或者繼承外部類的情況下訪問;如果是默認訪問權限,則只能在同一個包下訪問。這一點和外部類有一點不一樣,外部類只能被public和包訪問兩種權限修飾。
package innerclass;

public class OutClassT1 {

    private String outName = "outName";
    private int sameAge = 10;

    /**
     * 不能定義靜態成員、可以訪問外部類的所有成員、內部類和外部類的實例變量可以共存
     */
    class InnerClass {
        public String innerName = "innerName";
        //內部類和外部類的實例變量可以共存
        public int sameAge = 20;
        public void write() {
            InnerClass clazz = new InnerClass();
            //成員內部類可以任意訪問外部類的成員變量
            System.out.println("outName=" + outName);
            //在內部類中訪問內部類自己的變量
            System.out.println("innerName=" + innerName);
            //在內部類中訪問內部類自己的變量也可以用this.變量名
            System.out.println("sameAge=" + this.sameAge);
            //在內部類中訪問外部類中與內部類同名的實例變量用外部類名.this.變量名
            System.out.println("sameAge=" + OutClassT1.this.sameAge);
        }
    }

    /**
     * 外部類非靜態方法獲取內部類實例變量,可以直接new,因爲非靜態方法是屬於外部類實例對象的,調用該方法的時候說明外部類實例對象已經存在了
     * 在外部一般通過這種方法獲取內部類實例的
     */
    public InnerClass getInnerClass() {
        InnerClass innerClass = new InnerClass();
        return innerClass;
    }

    /**
     * 外部類靜態方法訪問內部類的實例變量,必須new外部類實例,再通過外部類的實例變量獲取內部類實例
     * 原因:成員內部類是依附外部類而存在的,也就是說,如果要創建成員內部類的對象,前提是必須存在一個外部類的對象
     */
    public static void callInnerPro() {
        //InnerClass innerClass = new OutClassT1().getInnerClass();
        InnerClass innerClass = new OutClassT1().new InnerClass();
        System.out.println(innerClass.innerName);
    }

    public static void main(String[] args) {
        /**
         * 也可以通過如下兩種方式獲取內部類實例
         * 成員內部類是依附外部類而存在的,也就是說,如果要創建成員內部類的對象,前提是必須存在一個外部類的對象。
         */
        //InnerClass innerClass = new OutClassT1().new InnerClass();
        InnerClass innerClass = new OutClassT1().getInnerClass();
        innerClass.write();
        new OutClassT1().callInnerPro();

    }
}

  • 爲什麼外部類可以創建內部類的對象?並且內部類能夠方便的引用到外部類對象?
我們用JAVA分析器或者用javap -verbose方法分析編譯後的class文件。這裏我們用JAVA分析器,JAVA分析器見最後。

class innerclass.OutClassT1$InnerClass extends java.lang.Object{
    //域
    public java.lang.String innerName;
    public int sameAge;
    final innerclass.OutClassT1 this$0;
    //構造器
     InnerClass(innerclass.OutClassT1);
    //方法
    public void write( );
}
編譯器會默認爲成員內部類添加了一個指向外部類對象的引用。
我們在定義的內部類的構造器是無參構造器,編譯器還是會默認添加一個參數,該參數的類型爲指向外部類對象的一個引用,所以成員內部類中的this$0 指針便指向了外部類對象,因此可以在成員內部類中隨意訪問外部類的成員。從這裏也間接說明了成員內部類是依賴於外部類的,如果沒有創建外部類的對象,則無法對this&0引用進行初始化賦值,也就無法創建成員內部類的對象了。
  • 爲什麼內部類可以引用外部類的私有域?

對OutClassT1進行java解析發現:爲外部類的私有變量都添加了靜態方法:


public class innerclass.OutClassT1 extends java.lang.Object{
    //域
    private java.lang.String outName;
    private int sameAge;
    //構造器
    public OutClassT1( );
    //方法
    public static void main(java.lang.String[]);
    static int access$100(innerclass.OutClassT1);
    static java.lang.String access$000(innerclass.OutClassT1);
    public innerclass.OutClassT1$InnerClass getInnerClass( );
    public static void callInnerPro( );
}

static int access$100(innerclass.OutClassT1);
static java.lang.String access$000(innerclass.OutClassT1);
它將返回值作爲參數傳遞給他的對象域data。這樣內部類InnerClass中的打印語句:System.out.println(outName);
實際上運行的時候調用的是: System.out.println(this$0.access$000(OutClassT1));

總結一下編譯器對類中內部類做的手腳吧:
(1)  在內部類中偷偷摸摸的創建了包可見構造器,從而使外部類獲得了創建權限。
(2)  在外部類中偷偷摸摸的創建了訪問私有變量的靜態方法,從而 使 內部類獲得了訪問權限。
這樣,類中定義的內部類無論私有,公有,靜態都可以被包圍它的外部類所訪問。

2、局部內部類

  1. 在方法中定義的內部類稱爲局部內部類。
  2. 局部內部類就像是方法裏面的一個局部變量一樣,是不能有public、protected、private以及static修飾符的。因爲它不是外圍類的一部分,但是它可以訪問當前代碼塊內的常量,和此外圍類所有的成員。 
  3. 可以定義與外部類同名的變量、局部內部類中不可以定義靜態變量、局部內部類中可以訪問外部類的變量。
  4. 因爲方法中的內部類沒有訪問修飾符, 所以方法內部類對包圍它的方法之外的任何東西都不可見,故:局部內部類只能在定義該內部類的方法內實例化,不可以在此方法外對其實例化。
  5. 局部內部類對象不能使用該內部類所在方法的非final局部變量。
public class OutClassT3 {
    public String outName = "outName";
    private String sameName= "SameName from out";

    public void outMethod(final String outMethodInput) {
        final String outMethodName = "outMethodName";

        /**
         * 局部內部類就像是方法裏面的一個局部變量一樣,是不能有public、protected、private以及static修飾符的
         * 可以定義與外部類同名的變量
         * 局部內部類中不可以定義靜態變量
         * 可以訪問外部類的變量
         * 訪問所在外部類方法的變量,則該變量必須是final類型的
         */
        class InnerClass {
            //可以定義與外部類同名的變量
            public String sameName = "SameName from inner";
            //局部內部類中不可以定義靜態變量
            //public static int age;
            void innerMethod() {
                //可以直接訪問外部類的變量
                System.out.println("outName=" + outName);
                //可以直接訪問自己的變量
                System.out.println("sameName=" + sameName);
                //訪問和內部類變量重名的外部類變量
                System.out.println("sameName=" + OutClassT3.this.sameName);
                //訪問所在外部類方法的變量,則該變量必須是final類型的
                System.out.println("outMethodName=" + outMethodName);
                //訪問所在外部類方法的變量,則該變量必須是final類型的
                System.out.println("outMethodInput=" + outMethodInput);
            }
        }

        /**
         * 局部內部類只能在定義該內部類的方法內實例化,不可以在此方法外對其實例化。
         */
        new InnerClass().innerMethod();

    }

    public static void main(String[] args) {
        OutClassT3 outClassT3 = new OutClassT3();
        outClassT3.outMethod("outMethodInput");
    }
}

3、匿名內部類

簡單地說:匿名內部類就是沒有名字的內部類。

匿名內部類是唯一一種沒有構造器的類。正因爲其沒有構造器,所以匿名內部類的使用範圍非常有限,大部分匿名內部類用於接口回調。一般來說,匿名內部類用於繼承其他類或是實現接口,不需要增加額外的方法,只是對繼承方法的實現或是重寫。

比較常用的場景是:爲組件添加監聽功能、多線程開發。

  • 什麼情況下需要使用匿名內部類?如果滿足下面的一些條件,使用匿名內部類是比較合適的:
  • 只用到類的一個實例。
  • 類在定義後馬上用到。
  • 類非常小(SUN推薦是在4行代碼以下)
  • 給類命名並不會導致你的代碼更容易被理解。
在使用匿名內部類時,要記住以下幾個原則:
  • 匿名內部類不能有構造方法。
  • 匿名內部類不能定義任何靜態成員、方法和類。
  • 匿名內部類不能是public,protected,private,static。
  • 只能創建匿名內部類的一個實例。
  • 一個匿名內部類一定是在new的後面,用其隱含實現一個接口或實現一個類。
  • 因匿名內部類爲局部內部類,所以局部內部類的所有限制都對其生效。

4、靜態內部類(也稱之爲嵌套類)

如果不需要內部類對象與其外圍類對象之間有聯繫,可以將內部類聲明爲static。
普通的內部類對象隱含地保存了一個引用,指向創建它的外圍類對象。然而,當內部類是static的時,靜態內部類是不需要依賴於外部類,這意味着:

  • 要創建嵌套類的對象,並不需要其外圍類的對象。
  • 不能從嵌套類的對象中訪問非靜態的外圍類對象。

生成一個靜態內部類不需要外部類成員:這是靜態內部類和成員內部類的區別。

public class OutClassT3 {
    public String outName = "outName";
    private String sameName= "SameName from out";

    public void outMethod(final String outMethodInput) {
        final String outMethodName = "outMethodName";

        /**
         * 局部內部類就像是方法裏面的一個局部變量一樣,是不能有public、protected、private以及static修飾符的
         * 可以定義與外部類同名的變量
         * 局部內部類中不可以定義靜態變量
         * 可以訪問外部類的變量
         * 訪問所在外部類方法的變量,則該變量必須是final類型的
         */
        class InnerClass {
            //可以定義與外部類同名的變量
            public String sameName = "SameName from inner";
            //局部內部類中不可以定義靜態變量
            //public static int age;
            void innerMethod() {
                //可以直接訪問外部類的變量
                System.out.println("outName=" + outName);
                //可以直接訪問自己的變量
                System.out.println("sameName=" + sameName);
                //訪問和內部類變量重名的外部類變量
                System.out.println("sameName=" + OutClassT3.this.sameName);
                //訪問所在外部類方法的變量,則該變量必須是final類型的
                System.out.println("outMethodName=" + outMethodName);
                //訪問所在外部類方法的變量,則該變量必須是final類型的
                System.out.println("outMethodInput=" + outMethodInput);
            }
        }

        /**
         * 局部內部類只能在定義該內部類的方法內實例化,不可以在此方法外對其實例化。
         */
        new InnerClass().innerMethod();

    }
    public static void main(String[] args) {
        OutClassT3 outClassT3 = new OutClassT3();
        outClassT3.outMethod("outMethodInput");
    } 
}

public class Test {
    public static void main(String[] args) {
        /**
         * 需要通過生成外部類對象來生成內部類實例
         */
        OutClassT2.StaticInnerClass innerClass = new OutClassT2.StaticInnerClass();
        //這裏只能調用public類型的方法
        innerClass.innerMethod();
        //不能調用私有方法,這裏方法的修飾符和平時普通類的修飾符意義一樣
        //innerClass.privatInnerMethod();
        // 調用靜態內部類的靜態方法,直接調用
        OutClassT2.StaticInnerClass.staticInnerMethod();

    }
}

參考:

https://www.cnblogs.com/ITtangtang/p/3980460.html

http://www.cnblogs.com/dolphin0520/p/3811445.html

https://www.cnblogs.com/yanzige/p/5377332.html

http://blog.51cto.com/android/384809

《java編程思想》

附:JAVA分析器代碼:

package tools;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;

public class ClassAnalyzer {
    private static final String tab = "    ";//縮進

    /**
     * 調用該方法,並傳遞class文件名稱即可,比如ClassAnalyzer.analyzer("OutClassT1");//OutClassT1對應的是編譯後生成的class文件名稱
     * @param className
     * @throws ClassNotFoundException
     */
    public static void analyzer(String className) throws ClassNotFoundException {
        Class c = Class.forName(className);
        System.out.print(Modifier.toString(c.getModifiers()));
        System.out.print(" ");
        System.out.print(c.toString());
        Class superC = c.getSuperclass();
        if (superC != null) {
            System.out.print(" extends " + superC.getName());
        }
        System.out.println("{");//類開始括號
        //打印域
        System.out.println(tab + "//域");
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            printField(field);
        }
        //打印構造器
        System.out.println(tab + "//構造器");
        Constructor[] constructors = c.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            printConstructor(constructor);
        }
        //打印方法
        System.out.println(tab + "//方法");
        Method[] methods = c.getDeclaredMethods();
        for (Method method : methods) {
            printMethod(method);
        }
        System.out.println("}");//類結束括號
    }

    //打印域
    private static void printField(Field field) {
        System.out.print(tab);
        System.out.print(Modifier.toString(field.getModifiers()));
        System.out.print(" ");
        Class fieldType = field.getType();
        if (fieldType.isArray()) {
            System.out.print(getArrayTypeName(fieldType));
        } else {
            System.out.print(field.getType().getName());
        }
        System.out.print(" ");
        System.out.print(field.getName());
        System.out.println(";");
    }

    //打印構造器
    private static void printConstructor(Constructor constructor) {
        System.out.print(tab);
        System.out.print(Modifier.toString(constructor.getModifiers()));
        System.out.print(" ");
        System.out.print(constructor.getDeclaringClass().getSimpleName());
        Class[] varTypes = constructor.getParameterTypes();
        System.out.print("(");
        printParameters(varTypes);
        System.out.println(");");
    }

    //打印方法
    private static void printMethod(Method method) {
        System.out.print(tab);
        System.out.print(Modifier.toString(method.getModifiers()));
        System.out.print(" ");
        Class returnType = method.getReturnType();
        if (returnType.isArray()) {
            System.out.print(getArrayTypeName(returnType));
        } else {
            System.out.print(method.getReturnType().getName());
        }
        System.out.print(" ");
        System.out.print(method.getName());
        System.out.print("(");
        Class[] varTypes = method.getParameterTypes();
        printParameters(varTypes);
        System.out.print(")");
        //聲明拋出的異常
        Class[] exceptionType = method.getExceptionTypes();
        if (exceptionType.length != 0) {
            System.out.print(" throws ");
            for (int i = 0; i < exceptionType.length; i++) {
                System.out.print(exceptionType[i].getName());
                if (i < (exceptionType.length - 1)) {
                    System.out.print(",");
                }
            }
        }
        System.out.println(";");
    }

    //打印構造器和方法的參數列表
    private static void printParameters(Class[] varTypes) {
        if (varTypes.length > 0) {
            for (int i = 0; i < varTypes.length; i++) {
                if (varTypes[i].isArray()) {
                    System.out.print(getArrayTypeName(varTypes[i]));
                } else {
                    System.out.print(varTypes[i].getName());
                }
                if (i < (varTypes.length - 1)) {
                    System.out.print(", ");
                }
            }
        } else {
            System.out.print(" ");
        }
    }

    public static String getArrayTypeName(Class type) {
        StringBuffer buffer = new StringBuffer(getArrayType(type).getName());
        int dimension = countArrayDimension(type);
        for (int i = 1; i <= dimension; i++) {
            buffer.append("[]");
        }
        return buffer.toString();
    }

    public static int countArrayDimension(Class type) {
        int dimension = 0;
        if (type.isArray()) {
            Class tempType = type;
            while ((tempType = tempType.getComponentType()) != null) {
                dimension++;
            }
        }
        return dimension;
    }

    public static Class getArrayType(Class type) {
        Class arrayType = null;
        if (type.isArray()) {
            Class tempType = type.getComponentType();
            do {
                arrayType = tempType;
            } while ((tempType = tempType.getComponentType()) != null);
        }
        return arrayType;
    }

    public static void main(String[] args) {
        try {
//            Scanner in = new Scanner(System.in);
//            System.out.print("Input class name:");
//            String className = in.next();
//            in.close();
            System.out.println("args = [" + 11111 + "]");
            String className = "com.wisely.learn.generic.DateInterval";
            analyzer(className);
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章