说说内部类

前言

说起Java当中的类和C++中的类有什么区别,我第一个要说的就是Java的类中,竟然还可以写一个类,而且这个类还可以是成员变量。简单的来说,Java当中的内部类分为一下的三种。

  • 内部成员类。(通常将内部成员类简称为内部类)
  • 本地内部类
  • 匿名内部类

这里我们说的内部类指的是非静态的内部类。简单的说也就是内部类是作为外部类的一个成员变量而存在的,必须要存在外部类的对象才能够访问到内部类。如果没有外部类的存在,内部类作为一个成员变量自然也不会存在。由于这种依赖与矛盾关系,在内部类中我们不可以声明静态成员。(静态成员包括静态变量,静态方法,静态成员类和嵌套接口。)

不过有一点比较特殊的是,在内部类中我们可以声明static final的变量,原因是在于编译器对于final变量做了一个特殊的处理。final变量会被直接写入字节码中,而不会生成变量的符号引用。

内部类的使用

public class InnerClassDemo1 {
    private int aaa = 3;
    private static final int bbb = 4;

    public static void main(String[] args) {
        InnerClassDemo1 in = new InnerClassDemo1();
        InnerClassDemo1.Inner inner = in.new Inner();
        inner.func();
        // out: Hello World \n class com.sher.innerclass.InnerClassDemo1$Inner

        InnerClassDemo1.Inner.InnerInner innerInner =
                new InnerClassDemo1().new Inner().new InnerInner();
        innerInner.func();
        // out: Hello World Again \n class com.sher.innerclass.InnerClassDemo1$Inner$InnerInner
    }

    public class Inner {
        public static final String a = "Hello World";
//        public static int a = 3; // No static properties but static final.

        void func(){
            System.out.println(a);
            System.out.println(aaa);
            System.out.println(bbb);
            System.out.println(this.getClass());
        }

        public class InnerInner {
            public static final String b = "Hello World Again";

            void func(){
                System.out.println(b);
                System.out.println(this.getClass());
            }
        }
    }
}

上面我演示的是一个内部类,但是内部类当中还有着一个内部类,这个内部类依旧是合法的。我们可以将内部类直接当作是这个类的一个成员变量。一个比较特殊的一点是我们如何创建一个内部类的i变量。首先是这个内部类的类型是外部类.内部类。我们新建一个内部类的方式是,外部类对象.new 内部类()。这个格式或许有一点儿奇怪,不过仔细想想确实非常的正常。内部类是依赖于外部类说存在的,那么内部类的类型上肯定是要有外部类。如果没有的话,不同的类拥有相同名字的内部类,难道说这这两个内部类是同一个类吗?二来就是内部类的创建方式。既然内部类是依赖成员变量,那么肯定是依赖于外部类的对象而存在的。不同的外部类拥有不同的成员变量,这也是理所应当的事。

上面已经说过了内部类中是不可以有静态成员的,除了static final变量。

还有要说的就是在内部类中是否可以访问到外部类的变量。由上面可见,无论是成员变量还是静态变量都是可以访问的。比较内部类本身也是外部类的一个成员变量。

现在我们在来观察一下,这个内部类的名字是不是就是我们给他的名字呢?看结果显然不是,看来和我们上次说的那个匿名内部类是一样的。系统也是默认的生成了一个类。也是使用了$这个符号。不过那么我们是否可以使用一下的方式来新建内部类的对象呢?

InnerClassDemo1$Inner$InnerInner a = new InnerClassDemo1$Inner$InnerInner();

运行了一下试试,果然是不行的。不过我也没期待这种方式是可行的。这也不是python私有变量的那种自欺欺人的私有。怎么可能通过这种方式就可以访问呢。

静态内部类

上面说的都是内部成员类,作为对比来说,可以来简单说说静态内部类。

public class InnerClassDemo2 {
    private int aaa = 3;
    private static int bbb = 4;
    private static final String ccc = "Outer";

    public static void main(String[] args) {
        StaticInner static1 = new StaticInner();
        static1.func();

        A.Go();
    }

    public static class StaticInner{
        public static final String a = "Hello World";
        static int b = 3;
        int c = 4;

        void func(){
            System.out.println(a);
//            System.out.println(aaa);
            System.out.println(InnerClassDemo2.bbb);
            System.out.println(ccc);
        }

    }
}

class A {
    public static void Go() {
        InnerClassDemo2.StaticInner inner = new InnerClassDemo2.StaticInner();
        inner.func();
    }
}

上面演示的是静态内部类。内部成员类是对象的一个成员,那么静态内部类就是类的一个静态成员。静态内部类不需要依赖类的对象而存在,所以创建静态内部类的时候也不需要使用对象.new xx()的方式,而是使用new 外部类.静态内部类()的方式。在外部类中的时候,我们可以省略外部类.,这个用法和静态变量是一样的。

对与外部类的访问权限,也是和静态成员是一样的。他可以访问带类的静态属性(包括private),但是不可以访问到非静态的属性,正如静态方法不可以访问非静态属性一样。

比较一下这两个类的话,我们可以看到这个静态内部类更向是一个真正的类,这个类中的数据基本上什么都是可以有的。(因为静态成员类是不依赖对象的。)

还有一个比较重要的东西没说,之前我们谈论单例设计模式的时候,我们使用了一种方法就是使用静态内部类来实现的。我们在来看一下实现的代码。

public class SingletonTest03 {
    public static void main(String[] args) {

        Singleton03 singleton1 = Singleton03.getInstance();
        Singleton03 singleton2 = Singleton03.getInstance();
        System.out.println("singleton1:" + singleton1.hashCode());
        System.out.println("singleton2:" + singleton2.hashCode());
    }
}

class Singleton03{
    private Singleton03() {}

    // 使用静态内部类
    private static class SingletonInstance {
        private static final Singleton03 INSTANCE = new Singleton03();
    }

    public static Singleton03 getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

因为静态内部类还有一个特点就是静态内部类中的静态数据只有被类第一次被使用到的时候创建。而且这种创建是线程安全的,这都是由JVM来保证的。至于为什么是这样的,这要学到JVM类的加载机制之后才能更深层次的理解之内的原因了吧。

内部类与外部类的联系

上面简答的扯了一扯内部类(通常不加任何说明的内部类就是指内部成员类)与静态内部类的关系,以及他们之间的区别。下面我们想要讨论一下的是,内部类的外部类之前的关系。

上面我们以及了解到了,其实在我们写了一个内部类的之后,Java替我们生成的类并不是我们所谓的内部类的名字。而是外部类$内部类这样的一个名字。其实内部类也是一个正常的类,不过这个类是如何和绑定他的外部类建立联系的呢?

对于内部类来说,编译器会默认生成一个final成员变量,变量的类型是外部类的类型,用来指向外部类的对象。

public class Inner{
    final Outer this$0;

    public Inner(Outer t){
        this$0 = t;
        super();
    }
}

其中引用名称this$0是编译器自动生成的,在创建内部类的对象的时候,系统会自动将外部类的的对象(outer)作为参数自动传入内部类的构造器中。

Outer.Inner inner = outer.new Inner(outer);

其实,与此同时,也会将新创建的内部类的对象(this)作为第一个参数隐式传递。假如创建玩inner对象之后,返回的引用是x,那么代码可以解析如下。

Outer.Inner inner = outer.new Inner(x, outer);

从上面可以看出来,内部类和外部类的绑定是通过this$0这个引用的。不过这些代码并不是真实存在的。这些都是我们用来描述编译器私下完成的工作的。

本地内部类

上面基本上已经说完了最重要的一个内部类——内部成员类。下面就来简单的介绍一个下一个内部类——本地内部类

和内部成员类不一样的是,这种类并不是作为类的一个成员变量,从结构上类似一个局部变量,所以说也就没有了所谓的访问权限了。

  • 在本地类中,可以访问其所声明的方法,构造器或初始化块所属类的所有的成员,
  • 如果要是在静态方法或者静态初始化块中声明的,则需要一个对象的引用来访问类的实例成员。
  • 对于局部变量或方法(构造器)中声明的形式参数,本地类只可以访问final修饰的局部变量或参数。

这个类是不用和外部类进行绑定的,所以和上面的内部成员类不一样,不存在一个final引用和外部类绑定。

其实这种我感觉用处并不大,稍微了解一下就行了。

匿名内部类

其实之前在谈论接口的时候,我们已经提及了这个匿名内部类了。使用匿名内部类的情况其实也就那么一种。

new A(){
    xxx;
}

这里的A就是一个接口,生成了一个匿名的实现了这个接口类的对象。这种写法主要是使用与这个类或者对象只会使用一次。不过Java 8之后,出来乐意lambda表达式可以替代这个匿名内部类,唉~匿名内部类是真的惨!

总结

上面是对Java中内部类的一个简单的介绍。仔细想想看Java中为什么有这么多的内部类呢?其实只是因为这些类在类中的位置不一样。(除了匿名内部类)。他们有些是作为类的实例成员,有些是作为类的静态成员,有些还是作为类中的一种局部变量。其实说白了,作为什么成员,这个类就具有那样性质的功能。从这个方面去理解内部类就一点也不难理解了。


一枚小菜鸡