​ 背八股的时候,没怎么见到过关于Java内部类的知识。有几次在牛客刷题的时候刷到了,发现自己很多都不清楚,因此本文应运而生。本文将从什么是内部类、内部类的种类、怎么使用内部类等方向进行讲解,希望能够帮到你!

一、什么是内部类?

​ 简单的来说,就是“定义在另一个类中的类”。这只是最基础的说法,其实定义在接口中的另一个类,也可以称为内部类,这将在后面讲解。前面主要提及的便是在类中的内部类。

二、内部类的分类

​ 按照是否被 static 修饰,或者说,是否需要一个指向外部类的引用,可以分为 “普通的”内部类和嵌套类。当然,这个普通的是我自己命名的,它泛指,一般的内部类(不被 static 修饰)。

​ 对于,“是否需要一个指向外部类的引用”的这个引用是什么。它是这样的:当创建一个内部类时,这个内部类的对象中会隐含一个链接,指向用于创建该对象的外围对象。通过该链接,无需任何特殊条件,内部类对象就可以访问外围对象的成员。

​ 如果再细分,那么内部类大致可以分为四种:

​ 1、成员内部类

​ 2、局部内部类

​ 3、匿名内部类

​ 4、嵌套类(静态内部类)

​ 其中,嵌套类其实是可以划分到成员内部类中的,这里为了讲解的方便,特意划分出来。

三、成员内部类

3.1 创建成员内部类

public class Outer {
    private int num1 = 10;
    private static int num2 = 100;

    private int getNum1() {
        return this.num1;
    }

    class Inner {
        private int num1 = 12;
        public void f1() {
            System.out.println("Outer的私有成员变量 num1: " + Outer.this.num1);
            System.out.println("Inner的私有成员变量 num1: " + num1);
            System.out.println("Outer的静态成员变量 num2: " + num2);
            System.out.println("调用Outer的方法:" + getNum1());
        }
    }

    public static void main(String[] args) {
        // 创建内部类实例
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();

        inner.f1();

    }
}

​ 如上面的代码所示,这是一个简单的成员内部类的实例。可以看到,内部类可以“无条件”的访问外部类的成员和方法,包括私有的。

3.2 外部类的引用

​ 如果我们想要访问外部类的成员或方法,我们就需要一个指向外部类的引用,这个引用可以用 外部类的名称.this来生成。

​ 当然,如果内部类没有这个字段或方法,你也可以直接使用外部类的变量名或方法名来使用。不过还是建议加上 外部类名称.this 的方式,这样更容易明白调用的是谁的成员。

3.3 创建内部类实例

​ 想要创建内部类的实例,就必须先创建外部类的实例。然后通过 外部类实例.new来创建内部类实例。

​ 如果你直接 Outer.Inner inner = new Inner(); 那么,编译器会报 “找不到类Inner”。

image-20230930105951207.png

​ 当然,你也不能 Outer.Inner inner = new Outer.Inner();,编译器会提示你需要一个 Outer的实例。

image-20230930110021592.png

​ 同时,对于普通的类,我们知道:只能给他们声明为 public 或 包访问权限(即不写),无法被声明为 private 和 protected的。但,内部类不一样,它可以被四种访问修饰符所修饰。

image-20230930110700069.png

四、局部内部类

4.1 什么是局部内部类?

​ 即在一个方法内或者任何一个作用域内。

4.2 创建局部内部类

4.2.1 方法内
public class Outer {
    private int num1 = 10;
    private static int num2 = 100;

    private int getNum1() {
        return this.num1;
    }

    public void test() {
        class Inner {
            private int num1 = 12;
            public void f1() {
                System.out.println("Outer的私有成员变量 num1: " + Outer.this.num1);
                System.out.println("Inner的私有成员变量 num1: " + num1);
                System.out.println("Outer的静态成员变量 num2: " + num2);
                System.out.println("调用Outer的方法:" + getNum1());
            }
        }
        Inner inner = new Inner();
        inner.f1();
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.test();
    }
}
4.2.2 作用域内
public class Outer {
    private int num1 = 10;
    private static int num2 = 100;

    private int getNum1() {
        return this.num1;
    }

    public void test(boolean flag) {
        if (flag) {
            class Inner {
                private int num1 = 12;
                public void f1() {
                    System.out.println("Outer的私有成员变量 num1: " + Outer.this.num1);
                    System.out.println("Inner的私有成员变量 num1: " + num1);
                    System.out.println("Outer的静态成员变量 num2: " + num2);
                    System.out.println("调用Outer的方法:" + getNum1());
                }
            }
            Inner inner = new Inner();
            inner.f1();
        }
//        Inner inner = new Inner();
//        inner.f1();
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.test(true);
    }
}

​ 值得注意的是:局部内部类的有效范围,就是这个作用域的范围。譬如,如果把上面的代码的注释去掉,就会报错,因为已经超过定义其的作用域之外了。

五、匿名内部类

5.1 什么是匿名内部类?

​ 顾名思义,即没有名字的内部类。

5.2 匿名内部类示例

public class Animal {
    private int num1 = 10;
    private static int num2 = 100;

    private int getNum1() {
        return this.num1;
    }
    public Dog running() {
        return new Dog(){
            @Override
            public void run() {
                System.out.println("*********");
                super.run();
                System.out.println("Animal的私有成员变量 num1: " + num1);
                System.out.println("Animal的静态成员变量 num2: " + num2);
                System.out.println("调用Animal的方法:" + getNum1());
            }
        };
    }

    public static void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = animal.running();
        dog.run();
    }
}

Dog类

public class Dog {
    public void run(){
        System.out.println("小狗 run run run");
    }
}

​ 除此之外,我们也可以把匿名内部类运用在方法的参数上,也可以直接在方法内部直接 new。

public class Animal {
    public void test2(Thread thread) {
        System.out.println("test2*****");
    }

    public static void main(String[] args) {
        Animal animal = new Animal();
        new Thread() {
            @Override
            public void run() {
                super.run();
            }
        }.start();
        
        animal.test2(new Thread() {
            @Override
            public void run() {
                super.run();
            }
        });
    }
}

六、嵌套类(静态内部类)

6.1 什么是嵌套类?

​ 如果不需要内部类对象和外部类对象之间的连接,可以将内部类设置为 static 的。如果一个内部类被设置为嵌套类,那么就意味着:

​ 1、不需要一个外部类对象来创建嵌套类对象;

​ 2、无法从嵌套类对象内部访问非 static 的外部类对象。

​ 对于普通的内部类,其内部不能有 static 数据、static 字段,也不能有嵌套类。但是嵌套类可以拥有这些。

image-20230930115252698.png

6.2 创建嵌套类

public class Outer {
    private int num1 = 10;
    private static int num2 = 100;

    private int getNum1() {
        return this.num1;
    }

    static class Inner {
        private int num1 = 12;
        private static int num2 = 666;
        public void f1() {
//            System.out.println("Outer的私有成员变量 num1: " + Outer.this.num1);
            System.out.println("Inner的私有成员变量 num1: " + num1);
            System.out.println("Outer的静态成员变量 num2: " + num2);
//            System.out.println("调用Outer的方法:" + getNum1());
        }
    }
}

6.3 对于非嵌套类是否能有静态成员

public class Outer {
    class Inner {
        private int num1 = 12;
        static int num2 = 666;
        public static void f2() {
            System.out.println("我是Inner的静态方法 f2()");
        }
    }
    public static void main(String[] args) {
        int a = Outer.Inner.num2;
        Outer.Inner.f2();
        System.out.println(a);
    }
}

​ 实测貌似是可以的?这里的Java版本是 JDK17。

6.4 接口中的类

​ 嵌套类也可以是接口的一部分,当类被放到接口中,都会被加上 public 和 static。因此接口中的类都是嵌套类,也就是说,类放在接口中,这是将其本身放到了这个接口的“命名空间”中。你甚至可以在嵌套类中实现这个接口。

public interface ClassInInterface {
    void say();
    class Test implements ClassInInterface{
        @Override
        public void say() {
            System.out.println("我是接口中的类");
        }
    }

    public static void main(String[] args) {
        new Test().say();
    }
}

七、常见问题

7.1 为什么需要内部类?

​ 每一个内部类都可以独立地继承一个实现。外部类是否已经继承了某个实现,对于内部类来说是没有限制的。通过这一点,我们可以利用内部类来实现“多继承”。

7.2 继承内部类

​ 需要在继承类的构造方法中传入一个外部类的实例,然后调用外部实例的构造方法。

public class A extends Outer.Inner{
    A(Outer outer) {
        outer.super();
    }
}

class Outer {
    class Inner {
    }
}

7.3 内部类的重写?

​ 你不能通过继承外部类的方式,然后“重写”内部类中的方法。你通过继承“重写”的内部类和被继承类的内部类,其实是两个独立的实体——他们分别存在不同的命名空间中。如果你想重写,你必须显示的在内部类也 extends 被继承类的内部类。

7.4 内部类标识符

​ 外部类名称+ $ + 内部类名称。

​ 如果内部类是匿名的,那么编译器会以数字作为内部类的标识符。

天行健,君子以自强不息