【Java】内部类

概述

内部类是声明在另一个类的内部的类。本质上,它是一个被"嵌套"在外部类中的类。

当一个事物A的内部,还有一个部分需要一个完整的结构B进行描述,而这个内部的完整的结构B又只为外部事物A提供服务,不在其他地方单独使用,那么整个内部的完整结构B最好使用内部类。总的来说,遵循高内聚、低耦合的面向对象开发原则。

内部类的分类

类型 所在位置 是否依赖外部类对象 特点
成员内部类 类中,方法外 依赖 最常见,可访问外部类成员
静态内部类 类中,方法外,带 static 不依赖 不持有外部类引用,类似外部类的静态成员
局部内部类 方法内 依赖 方法内定义,仅方法内可见
匿名内部类 局部类的一种特殊形式 依赖 用来临时创建某个类或接口的对象(一次性使用)

(静态)成员内部类

如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为静态内部类,否则声明为非静态内部类。

java 复制代码
[修饰符] class 外部类 {
    [其他修饰符] [static] class 内部类 {
    }
}
  • 成员内部类作为类的成员的角色
    • 和外部类不同,内部类还可以声明为privateprotected
    • 可以调用外部类的结构。(⚠️在静态内部类中不能使用外部类的非静态成员)
  • 成员内部类作为类的角色
    • 可以在内部定义属性、方法、构造器等结构;
    • 可以继承父类和实现父接口,和外部类的父类和父接口无关;
    • 可以声明为抽象类 ,因此可以被其它的内部类继承;
    • 可以声明为final的,表示不能被继承;
    • 编译后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)

注意点

  • 外部类访问成员内部类的成员,需要内部类.成员内部类对象.成员的方式;
  • 成员内部类可以直接使用外部类的所有成员,包括私有的数据;

实例化

  • 非静态成员内部类

和外部类对象绑定:内部类对象必须依赖外部类对象。外部类可通过 new Inner() 创建内部类对象(必须有外部类对象前缀)。

java 复制代码
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.show();
  • 静态成员内部类

不依赖外部类对象(不持有外部类实例)。可以访问外部类的静态成员,但不能访问非静态成员。

java 复制代码
Outer.Inner inner = new Outer.Inner();
inner.show();

局部(匿名)内部类

局部内部类是定义在方法内部、代码块内部或构造器内部的类。作用域仅限于该局部范围内,外部无法直接访问。

java 复制代码
// 非匿名
[修饰符] class 外部类 {
    [修饰符] 返回值类型 方法名([形参列表]) {
            [final/abstract] class 内部类 {
    	}
    }    
}
  • 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
    • 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类。
  • 不能使用访问修饰符,因为它是局部变量级别的类型。
  • 作用域仅限于该局部范围内,外部无法直接访问。
  • 可以访问外部类成员,包含静态成员。
  • 可以访问方法中的局部变量,但要求变量为 final 或 effectively final。Java 8 之后局部变量无需显式声明 final,只要它不被重新赋值(effectively final)即可。原因:局部变量属于栈内存,方法结束后销毁,但局部内部类对象可能还活着,因此 JVM 会复制一份局部变量到内部类中,需要保证不变性。
  • 局部内部类可以定义 static 成员。
java 复制代码
public class Outer {
    private String name = "OuterName";
    private static String a = "oa";

    public void doWork() {
        int count = 5; // effectively final

        class Worker {
            static int x = 0;
            public void execute() {
                x = 1;
                System.out.println("访问外部类成员: " + name);
                System.out.println("访问外部类static成员: " + a);
                System.out.println("访问方法局部变量: " + count);
            }
        }

        Worker w = new Worker();
        w.execute();
    }

    public static void main(String[] args) {
        new Outer().doWork();
    }
}

匿名内部类是一种没有名字的局部内部类,通常用来简化代码,尤其在需要临时创建某个类的实例(通常是接口或抽象类)时使用。

java 复制代码
父类/接口 类型 变量 = new 父类/接口() {
    // 重写方法
};

传统方式:需要写一个单独的类文件 / 内部类实现一个接口。

java 复制代码
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Running...");
    }
}

然后使用

java 复制代码
Runnable r = new MyRunnable();

使用匿名内部类,节省代码、逻辑更集中,不用专门创建一个新类。

java 复制代码
Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running...");
    }
};

匿名内部类最常见的用途就是实现接口。

java 复制代码
interface Greeting {
    void sayHello();
}

public class Main {
    public static void main(String[] args) {
        Greeting g = new Greeting() {
            @Override
            public void sayHello() {
                System.out.println("Hello from anonymous class!");
            }
        };

        g.sayHello();
        
        ///// or
        new Greeting() {
            @Override
            public void sayHello() {
                System.out.println("Hello from anonymous class!");
            }
        }.sayHello();
    }
}

匿名内部类的对象作为实参。

java 复制代码
interface A {
	void method();
}

public class Test {
    public static void test(A a) {
    	a.method();
    }
    
    public static void main(String[] args) {
    	test(new A() {
			@Override
			public void method() {
				System.out.println("aaaa");
			}
    	});
    }   
}
java 复制代码
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("线程运行");
    }
}).start();
相关推荐
张人大 Renda Zhang1 小时前
Maven = Java 构建世界的“事实标准”:从 pom.xml 到云原生 CI/CD
xml·java·spring boot·后端·ci/cd·云原生·maven
老鼠只爱大米1 小时前
Java设计模式之装饰器模式详解
java·设计模式·装饰器模式·decorator·java设计模式
0***v7771 小时前
springboot 异步操作
java·spring boot·mybatis
LSL666_1 小时前
7 SpringBoot pom.xml解释
java·spring boot·spring
ps酷教程1 小时前
java泛型反射&mybatis的TypeParameterResolver
java·mybatis
b***59431 小时前
springboot+mybaties项目中扫描不到@mapper注解的解决方法
java·spring boot·mybatis
u***42071 小时前
Spring Boot 实战篇(四):实现用户登录与注册功能
java·spring boot·后端
慕沐.1 小时前
【算法】冒泡排序的原理及实现
java·算法·排序算法
v***8571 小时前
Java进阶-在Ubuntu上部署SpringBoot应用
java·spring boot·ubuntu