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();
        }
    }
}

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