二十一、Java内部类

在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类叫做内部类。一般来说,内部类分为成员内部类、局部内部类、匿名内部类和静态内部类。

内部类是一种实现多效果的方式。

21.1 成员内部类

成员内部类的位置类似于成员变量,定义在类中方法外:

/**
 * @author QHJ
 * @date 2022/9/19  13:46
 * @description: 成员内部类
 */
public class ConstructorInnerClass {
    String name = "外部类";
    
    class ConstructorInnerClass2{
        String name = "成员内部类";
    }
}

内部类 ConstructorInnerClass2 就好像 ConstructorInnerClass 的一个成员,成员内部类可以无限制访问外部类的所有成员属性和成员方法(包括私有成员和静态成员)

/**
 * @author QHJ
 * @date 2022/9/19  13:46
 * @description: 成员内部类
 */
public class ConstructorInnerClass {
    String name = "外部类";
    boolean flag = true;
    private int age = 22;
    static double money = 100;

    class ConstructorInnerClass2{
        String name = "成员内部类";

        public void print(){
            System.out.println(flag);
            System.out.println(age);
            System.out.println(money);
        }
    }
}

不过需要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果需要访问外部类的同名成员,需要使用 外部类.this.成员变量外部类.this.成员方法进行访问:
在这里插入图片描述

内部类可以随心所欲地访问外部类的成员,但外部类想要访问内部类的成员就不那么容易了。必须先创建一个成员内部类的对象,再通过这个对象来访问:

/**
 * @author QHJ
 * @date 2022/9/19  13:46
 * @description: 成员内部类
 */
public class ConstructorInnerClass {
    String name = "外部类";
    boolean flag = true;
    private int age = 22;
    static double money = 100;

    class ConstructorInnerClass2{
        String name = "成员内部类";

        public void print(){
            System.out.println(flag);
            System.out.println(age);
            System.out.println(money);
        }
    }

    public static void main(String[] args) {
        ConstructorInnerClass constructorInnerClass = new ConstructorInnerClass();
        // 先创建内部类的对象
        ConstructorInnerClass2 constructorInnerClass2 = constructorInnerClass.new ConstructorInnerClass2();
        // 访问内部类的方法
        constructorInnerClass2.print();
    }
}

这也就意味着,如果想要在静态方法中访问成员内部类,就必须先得创建一个外部类的对象,因为内部类是依附于外部类的。

但是这种创建内部类的方式在实际开发中并不常用,因为内部类和外部类紧紧地绑定在一起,使用起来非常不便。

内部类可以拥有 private 访问权限、protected 访问权限、public 访问权限及包访问权限。比如上面的例子,如果成员内部类 ConstructorInnerClass2 用 private 修饰,则只能在外部类的内部访问,如果用 public 修饰,则任何地方都能访问;如果用 protected 修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public 和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。

21.2 局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,所有局部内部类的生命周期仅限于作用域内:

/**
 * @author QHJ
 * @date 2022/9/19  14:09
 * @description: 局部内部类
 */
public class LocalInnerClass {

    public void print() {

        class LocalInnerClass2 {
            private int age = 22;

            public void print2() {
                System.out.println(age);
            }
        }

        LocalInnerClass2 localInnerClass2 = new LocalInnerClass2();
        localInnerClass2.print2();
    }

    public static void main(String[] args) {
        LocalInnerClass localInnerClass = new LocalInnerClass();
        localInnerClass.print();
    }
}

局部内部类就好像一个局部变量一样,它是不能被权限修饰符修饰的,比如 publicprotectedprivatestatic等:
在这里插入图片描述

21.3 匿名内部类

匿名内部类是平常用的最多的,尤其是启动多线程的时候会经常用到,并且 IDE 也会帮我们自动生成:

/**
 * @author QHJ
 * @date 2022/9/19  14:25
 * @description: 匿名内部类
 */
public class AnonymousInnerClass {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
        t.start();
    }
}

匿名内部类就好像是一个方法的参数一样,用完就没了,以至于我们都不需要为它专门写一个构造方法,它的名字也是由系统自动命名的。观察一下编译后的字节码文件也可以发现,匿名内部类连名字都没有,而是直接借用的外部类,然后 $1 就搞定了。

匿名内部类是唯一一种没有构造方法的类,不允许我们为其编写构造方法,因为它就像是直接通过 new 关键字创建出来的一个对象。

匿名内部类的作用主要是用来继承其他类或者实现接口,并不需要增加额外的方法,方便对继承的方法进行实现或者重写。

21.4 静态内部类

静态内部类和成员内部类类似,只是多了一个static关键字:

/**
 * @author QHJ
 * @date 2022/9/19  14:43
 * @description: 静态内部类
 */
public class StaticInnerClass {
    static int age = 22;
    double money;

    static class StaticInnerClass2 {
        public void print() {
            // 只能访问静态变量
            System.out.println(age);
        }
    }
}

由于 static关键字的存在,静态内部类是不允许访问外部类中的非静态变量和方法的:
JavaSE基础之(二十一)Java内部类-小白菜博客
静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非 static 成员变量或者方法。因为在没有外部类的情况下,可以创建静态内部类的对象,如果允许访问类的非静态成员就会产生矛盾,因为外部类的非静态成员是必须依附于具体的对象的。

21.5 总结

为什么要使用内部类?

在《Think in java》中有这样一句话:

使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。

使用内部类还能够为我们带来如下特性(摘自《Think in java》):

1、内部类可以使用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立。
2、在单个外部类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
3、创建内部类对象的时刻并不依赖于外部类对象的创建。
4、内部类并没有令人迷惑的 “is-a” 关系,他就是一个独立的实体。
5、内部类提供了更好的封装,除了该外围类,其他类都不能访问。