掘金 后端 ( ) • 2024-04-23 11:02

内部类是一种嵌套在另一个类(称为外部类)内部的类定义。

  • 内部类可以访问外部类的所有成员(包括私有成员),同时也可以拥有自己的成员变量、方法、构造器以及嵌套类。
  • 内部类的使用增强了封装性,简化了代码组织,尤其在处理事件监听、回调函数、模块化设计等方面非常有用。

内部类的种类

  • 成员内部类(Member Inner Class):
    • 定义在外部类的成员位置,可以具有任意访问修饰符(publicprotectedprivate 或默认访问权限)。
    • 可以直接访问外部类的所有成员(包括私有成员)。
    • 外部类要访问内部类的成员,需要通过内部类的实例来访问。
  • 静态内部类(Static Nested Class):
    • 使用 static 关键字修饰,不依赖于外部类的实例,可以直接使用外部类的静态成员,无需创建外部类对象。
    • 不能直接访问外部类的非静态成员,但可以通过外部类实例来访问。
  • 局部内部类(Local Inner Class):
    • 定义在外部类的方法或代码块内部。
    • 只能在定义它的方法或代码块中被访问。
    • 可以访问外部类的所有成员,以及定义它的方法或代码块内的局部变量(前提是这些局部变量必须被声明为 final)。
  • 匿名内部类(Anonymous Inner Class):
    • 将内部类的概念进一步简化,没有类名,直接在创建对象时定义并实例化。
    • 由于没有类名,它不能独立存在,只能与创建它的实例关联。

内部类的特点

  • 封装性:内部类增强了面向对象的封装性,可以把一些实现细节封装在内部类中,隐藏在外部类之内,限制了其他对象的直接访问。
  • 代码组织:内部类使得相关的类可以紧密地封装在一起,提高了代码的可读性和可维护性。
  • 访问特权:内部类可以直接访问外部类的所有成员,包括私有成员,这在设计回调机制、事件监听器等场景下非常有用。
  • 生命周期相关性:某些内部类(非静态内部类)的实例与外部类的实例之间存在紧密的生命周期联系,它们共享同一个外部类实例。

成员内部类

成员内部类(Member Inner Class)是Java中内部类的一种,它定义在另一个类(外部类)的成员位置,可以具有任意访问修饰符(publicprotectedprivate 或默认访问权限)。

特点

  • 访问外部类成员
    • 成员内部类可以直接访问外部类的所有成员,包括私有成员(字段、方法和嵌套类)。这意味着内部类可以访问外部类的私有数据和受保护的功能,这有助于实现更紧密的封装和更复杂的逻辑。
    • 外部类访问内部类的成员则需要通过内部类的实例来访问,如同访问其他类的对象一样。
  • 生命周期依赖
    • 成员内部类的实例与外部类的实例之间存在依赖关系。创建一个成员内部类的实例时,必须先有一个外部类的实例存在。也就是说,不能独立创建一个成员内部类的对象,除非它被嵌套在外部类的一个方法或代码块中,且该方法或代码块正在被外部类的一个实例调用。
  • 访问修饰符
    • 成员内部类可以有任意访问修饰符,如 publicprotectedprivate 或默认(包访问权限)。访问修饰符决定了其他类能否访问这个内部类。
  • 不能有静态成员
    • 成员内部类不能有静态字段、静态方法或静态初始化块,因为它本身依赖于外部类的实例。然而,它仍然可以使用外部类的静态成员。
  • 编译后的类文件
    • 编译成员内部类时,编译器会生成两个独立的类文件:一个是外部类的 .class 文件,另一个是内部类的 .class 文件,其名称格式通常是 外部类名$内部类名.class

使用方式

  1. 定义

    • 在外部类的成员位置(不在任何方法或代码块内部)使用 class 关键字定义内部类,可以加上访问修饰符。
    public class OuterClass {
        private String outerData;
    	// 定义一个成员内部类
        class InnerClass {
            // ...
        }
    }
    
  2. 创建实例

    • 在外部类的方法或代码块中,通过 new 关键字创建内部类的实例,需要先有一个外部类的实例。
    OuterClass outer = new OuterClass();
    OuterClass.InnerClass inner = outer.new InnerClass();
    
  3. 访问内部类成员

    • 外部类通过内部类实例访问其成员,如同访问其他类的对象一样。
    inner.someMethod();
    
  4. 内部类访问外部类成员

    • 内部类可以直接访问外部类的所有成员,包括私有成员。
    class InnerClass {
        void accessOuter() {
            System.out.println("外部类的成员变量outerData: " + outerData);
        }
    }
    

应用场景

  • 封装复杂逻辑:当某个类的实现细节过于复杂,或者需要对外部类进行深度定制时,可以使用成员内部类来封装这部分逻辑,保持外部类的简洁性。
  • 事件监听器:在处理GUI事件、网络事件等场景中,经常需要创建事件监听器类。将监听器类定义为成员内部类,可以方便地访问外部类的状态和方法。
  • 回调函数:在多线程、异步处理等需要回调的场景中,成员内部类可以作为回调接口的实现类,直接访问外部类的状态,简化代码。
  • 模块化设计:当某个类的功能可以自然地划分为几个逻辑相关的部分时,可以使用成员内部类来组织这些部分,增强代码的模块化和可读性。

代码示例

public class MemberInternalClass {
    private String outerData;
    
    public MemberInternalClass(String outerData) {
        this.outerData = outerData;
    }
	
    // 创建一个成员内部类
    class InnerClass {
        void accessOuter() {
            System.out.println("外部成员变量outerData: " + outerData);
        }
    }

    public void demonstrateInnerClass() {
        // 调用成员内部类
        InnerClass inner = new InnerClass();
        inner.accessOuter();
    }
}

public static void main(String[] args) {
    MemberInternalClass memberInternalClass = new MemberInternalClass("Hello Java!");
    // 通过成员方法调用成员内部类
    memberInternalClass.demonstrateInnerClass();// 输出: 外部成员变量outerData: Hello Java!
    // 直接访问成员内部类
    memberInternalClass.new InnerClass().accessOuter();// 输出: 外部成员变量outerData: Hello Java!
}

静态内部类

静态内部类(Static Nested Class),也被称为静态嵌套类,是一种特殊的内部类,其声明时使用了 static 关键字。

特点

  • 独立于外部类实例
    • 静态内部类不依赖于外部类的实例,可以独立创建其对象。这意味着无需先创建外部类实例就能创建静态内部类的实例。
  • 访问外部类成员
    • 静态内部类只能访问外部类的静态成员(包括静态字段、静态方法和嵌套静态类),不能直接访问外部类的实例成员(非静态字段和非静态方法)。若需要访问实例成员,通常需要通过传递外部类实例作为参数或在内部类中持有外部类实例引用。
  • 静态成员
    • 静态内部类可以拥有自己的静态成员(静态字段、静态方法和静态初始化块),这些静态成员遵循普通类中静态成员的规则。
  • 编译后的类文件
    • 同成员内部类一样,静态内部类也会生成单独的 .class 文件,文件名通常为 外部类名$静态内部类名.class
  • 访问修饰符
    • 静态内部类可以使用任意访问修饰符(publicprotectedprivate 或默认包访问权限),控制其他类对其的访问。

使用方式

  1. 定义

    • 在外部类的成员位置,使用 static class 关键字定义静态内部类。
    public class OuterClass {
        // 创建静态内部类
        static class StaticInnerClass {
            // ...
        }
    }
    
  2. 创建实例

    • 与创建普通类对象类似,直接使用 new 关键字创建静态内部类的实例,无需外部类实例。
    OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
    
  3. 访问内部类成员

    • 通过静态内部类的实例访问其成员。
    inner.someMethod();
    
  4. 内部类访问外部类成员

    • 静态内部类通过外部类的类名访问其静态成员。
    class StaticInnerClass {
        void accessOuter() {
            System.out.println("外部静态常量: " + OuterClass.outerStaticData);
        }
    }
    

应用场景

  • 工具类或辅助类:当需要定义一组仅与某个外部类相关的工具方法或常量时,可以使用静态内部类来封装这些功能,避免污染外部类或创建不必要的顶级类。
  • 单例模式:静态内部类可以用于实现线程安全的懒汉式单例模式,利用类加载机制确保单例的唯一性。
  • 封闭数据结构:静态内部类可以用来封装外部类的数据结构,提供安全的访问接口,防止外部直接修改数据。
  • 枚举类的替代:在不支持枚举的早期Java版本中,静态内部类常被用来模拟枚举的行为,每个内部类实例代表一个枚举值。

代码示例

public class OuterClass {
    private static String staticData = "Static Data";

    // 定义静态内部类
    static class StaticNestedClass {
        void accessStatic() {
            System.out.println("静态成员变量: " + staticData);
        }
    }
}

public static void main(String[] args) {
    // 调用静态内部类
    OuterClass.StaticNestedClass inner = new OuterClass.StaticNestedClass();
    inner.accessStatic();
}

局部内部类

局部内部类是一种特殊的内部类,它被定义在一个方法、代码块或其他局部作用域内。这种设计允许将类的定义限制在非常特定的范围中,只在需要的地方创建和使用。

特点

  • 访问权限与可见性: 局部内部类对外部不可见,即不能从外部类的其他方法或外部其他类中直接访问。它只能在其定义的局部作用域内被创建和使用。由于其局限性,局部内部类没有访问修饰符(如 publicprivate 等)。
  • 访问外部资源: 尽管作用域有限,局部内部类却可以访问其所在作用域内的所有局部变量,前提是这些变量必须是 final 或实际上 final(即在局部内部类的生命周期内其值不会改变)。此外,局部内部类还可以访问外部类的所有成员(包括私有成员),这一点与非局部内部类一致。
  • 生命周期与垃圾回收: 局部内部类对象的生命周期与其依赖的局部变量密切相关。如果一个局部内部类对象引用了其外部方法的局部变量,那么只要这个局部内部类对象是可达的,所引用的局部变量就不会被垃圾回收,即使该方法已经执行完毕。这是为了确保内部类对象能够正确访问到这些变量的值。

使用方式

局部内部类定义在外部类的一个方法、循环、条件语句等局部作用域内。它的声明和实现与其他内部类一样,包含类的属性、方法、构造器等组件,但其作用域仅限于定义它的局部区域

public class OuterClass {
    public void someMethod() {
        // 局部内部类定义在此处
        class LocalInnerClass {
            // 属性、方法、构造器等...
        }
        
        // 在此作用域内可以创建和使用 LocalInnerClass 的实例
        LocalInnerClass localInstance = new LocalInnerClass();
    }
}

应用场景

局部内部类通常用于解决一些特定的编程问题,如:

  • 临时性的类需求:当某个功能逻辑仅在特定方法中需要,并且该功能需要封装成类,但又无需在整个类层次结构中公开时,可以使用局部内部类。
  • 事件处理回调:在实现事件驱动模型时,有时需要为特定事件创建一个匿名或局部内部类的监听器,这些监听器只在注册事件时创建,并在事件触发时调用其方法。
  • 异常处理:有时为了简化异常处理逻辑,可以定义一个局部内部类来封装特定类型的异常及其处理逻辑。
  • 适配器模式:在需要快速创建一个满足特定接口的适配器对象时,局部内部类可以提供简洁的实现方式。

代码示例

public class Demo03_LocalInnerClass {

    private String sharedField = "外部类定义";
    public void processData() {
        final String importantValue = "外部类方法的变量"; // 必须为 final 或 effectively final

        // 定义局部内部类
        class DataProcessor {
            void doProcessing() {
                System.out.println("外部类方法中的局部变量: " + importantValue);
                // 可以访问外部类成员
                System.out.println("外部类成员变量: " + Demo03_LocalInnerClass.this.sharedField);
            }
        }

        // 创建并使用局部内部类实例
        DataProcessor processor = new DataProcessor();
        processor.doProcessing();
    }
    public static void main(String[] args) {
        // 调用非静态方法,间接调用局部内部类
        Demo03_LocalInnerClass method = new Demo03_LocalInnerClass();
        method.processData();
    }
}

匿名内部类

匿名内部类(Anonymous Inner Class)是Java中一种特殊的内部类,它没有名字,即在定义时不需要使用 class 关键字为其命名。

  • 匿名内部类主要用于简化代码,特别是在只需要使用一次某个类的实例,且该类的实现相对简单的情况下。
  • 匿名内部类通常用于实现接口或继承抽象类,并在创建该类实例的同时定义其实现。

特点

  • 无名称
    • 匿名内部类没有名称,定义时直接省略了类名。因此,无法在其他地方再次引用或创建该类的实例。
  • 继承或实现
    • 匿名内部类必须继承一个父类(通常为抽象类)或实现一个接口。这是匿名内部类存在的主要目的,即在创建对象时同时提供具体的实现。
  • 一次性使用
    • 由于匿名内部类没有名称,所以它通常伴随着 new 关键字一起使用,创建并初始化一个该类的实例。这种“创建即使用”的特性使得匿名内部类适用于那些只需一次性使用的场景。
  • 访问外部资源
    • 匿名内部类可以访问其外部类的所有成员(包括私有成员),以及其所在方法的 final 或 effectively final 局部变量。

使用方式

  1. 定义

    • 使用 new 关键字后跟父类或接口的名称,然后直接定义类体(包含方法、属性等),不需要 class 关键字和类名。
    new SomeInterfaceOrAbstractClass() {
        // 类体,包含方法、属性等
    };
    
  2. 实现方法

    • 如果继承抽象类或实现接口,需要在类体中提供所继承或实现方法的具体实现。
    new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("Button clicked!");
        }
    };
    
  3. 创建实例

    • 匿名内部类的实例是在定义时直接创建的,无需额外的 new 操作。创建的实例可以直接赋值给相应类型的变量或作为参数传递。
    ActionListener listener = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("Button clicked!");
        }
    };
    
    button.addActionListener(listener); // 作为参数传递给方法
    

应用场景

  • 事件监听器:在处理GUI事件、网络事件等场景中,匿名内部类常用于快速创建事件监听器的实例,提供事件触发时的处理逻辑。
  • Lambda表达式的前身:在Java 8之前,匿名内部类是实现函数式编程风格的主要手段,如集合排序、流操作等。现在许多这样的场景已被Lambda表达式取代。
  • 一次性使用的策略或算法:当某个策略或算法只需使用一次,且实现较为简单时,可以用匿名内部类实现,避免创建多余的命名类。

代码示例

创建了一个简单的Java Swing GUI应用程序,包含一个按钮。

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Demo04_AnonymousInnerClass {
    public static void main(String[] args) {
        // 创建一个JFrame实例
        JFrame frame = new JFrame("匿名内部类 - 示例");

        // 创建一个JButton实例
        JButton button = new JButton("快点我 !");

        // 使用匿名内部类创建并初始化ActionListener实例
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(frame, "你点到我了!", "Message", JOptionPane.INFORMATION_MESSAGE);
            }
        });

        // 添加按钮到frame并设置关闭操作
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(button);
        frame.pack();
        frame.setVisible(true);
    }
}