java内部类

一般内部非静态类

class Outer {
    public Integer data = 1;
    class Inner{
        void print() {
            System.out.println(data);
        }
    }
}

public static void main(String[] args) {
    Outer outer = new Outer();
    Outer.Inner inner = outer.new Inner();  // 注意内部类 new 的方式

    inner.print();  // 1

    outer.data = 2;

    inner.print();  // 2

}

由此可见,一般内部类与外部类共享变量,并且可以对变量进行修改

方法内部类

class Outer {
    public Integer data = 1;

    public void fun(){

        Integer funDate = 1;

        class Inner{
            void print() {
                System.out.println(data);
                System.out.println(funDate);
            }
        }

        new Inner().print();
    }

}

public static void main(String[] args) {
    Outer outer = new Outer();
    outer.fun();  // 输出 1 和 1

}

由此可见,方法内部类也是共享外部变量的,但是,对于方法内部的局部变量是不能做修改操作的,它可以不为final,但至少要保证不会修改它,否则编译就会报错

事实上,如果将两个对象打印出来的话,会发现不管是内部类还是外部类,不管是全局变量还是局部变量,它们其实引用的都是同一个对象

所以对于局部变量的不可变要求或许只是java语言规范,并发jvm规范。至于为什么这么做,之前看到过一个说法说是,内部类会将外部类的变量复制一份,如果外部类变量做了修改,则内部类变量就不能保持一致了。但显然从上面结果看,貌似只是复制了引用,对象实体仍然没有复制,并且实例变量确实还是可以修改的,从这些方面讲,貌似变量复制论还是有点问题。具体什么原因有待商榷

内部类逃逸

方法内部的成员变量可以通过return逃逸,那如果是方法内部类呢?

class Outer {

    public Object fun(){

        class Inner{
        }

        return new Inner();
    }

}

public static void main(String[] args) {
    Outer outer = new Outer();
    Object fun = outer.fun();

    System.out.println(fun);  // org.example.Outer$1Inner@34a245ab
}

注意到仍然可以成功打印出来,与普通的内部类相比,它的$符号后面多了一个数字。

事实上,不管是哪里定义的内部类,它都是一个单独的class,也会有单独的class文件,故,这里虽然是定义在方法内部的,但是该类一旦加载初始化后也是存在于元空间的,所以使用上并不会有什么问题

如何实例化内部类?

一般的内部非静态类只需要外部实例对象使用new关键字即可创建,见第一个代码示例

而方法内部的内部类创建方式有点曲折,并不能直接创建,但可以通过反射创建

class Outer {

    public Object fun(){

        class Inner{
        }

        return new Inner();
    }

}

public static void main(String[] args) throws Exception {
    Outer outer = new Outer();
    Object fun = outer.fun();

    Object o = fun.getClass().getConstructor().newInstance();  // 这样创建会报 <init>() 方法找不到
}

上述代码看似没问题,但运行起来会报找不到构造器方法错误

有没有可能是没有显示定义构造器的原因呢?

不是,即使你定义了一个空参构造器仍然会报这个错。

事实上,这里有一个坑,虽然看似你定义的是一个无参的构造方法,实际上,对于所有的内部类,jvm会自动地在其参数列表第一个位置加上一个外部类的参数

所以正确的反射创建方式应该是

getClass().getConstructor(Outer.class).newInstance(outer);

通过反编译class文件也能看出来,在内部类中其实是有一个外部类实例传进去的

但是并没有使用该参数,看来其并非字节码层面的操作,而是jvm内部的处理逻辑了

Leave a Comment