学习Java43天

内部类 (Inner Class)

一、内部类的基本概念

1. 什么是内部类?
  • 定义:在一个类的内部定义的另一个类。

  • 位置 :类的五大成员之一(属性、方法、构造方法、代码块、内部类)。

  • 设计思想 :内部类代表的事物是外部类的一部分,它们之间存在紧密的逻辑关系。内部类单独出现没有意义。

2. 内部类的访问特点
  • 内部类 → 外部类 :可以直接访问外部类的所有成员(包括私有成员)。

  • 外部类 → 内部类 :必须创建内部类对象才能访问内部类的成员。

java

复制代码
public class Outer {
    private String outerField = "外部类私有属性";
    
    // 内部类
    public class Inner {
        public void show() {
            // 内部类可以直接访问外部类的私有成员
            System.out.println("访问外部类属性:" + outerField);
        }
    }
    
    public void test() {
        // 外部类要访问内部类,必须创建对象
        Inner inner = new Inner();
        inner.show();
    }
}

二、内部类的分类

内部类主要有四种类型:

  1. 成员内部类 - 定义在成员位置

  2. 静态内部类 - 用 static 修饰的成员内部类

  3. 局部内部类 - 定义在方法或代码块内部

  4. 匿名内部类 - 没有名字的特殊局部内部类

三、成员内部类

1. 基本特点
  • 位置:定义在类的成员位置(与属性、方法同级)。

  • 修饰符 :可以使用 publicprotectedprivate默认 等访问修饰符。

  • 访问:可以访问外部类的所有成员。

2. 获取成员内部类对象的两种方式

方式一:当内部类被 private 修饰时

java

复制代码
public class Outer {
    private String name = "外部类";
    
    // 私有内部类
    private class Inner {
        public void show() {
            System.out.println("内部类访问:" + name);
        }
    }
    
    // 对外提供内部类对象的方法
    public Inner getInnerInstance() {
        return new Inner();
    }
}

public class Test {
    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner inner = outer.getInnerInstance(); // 通过方法获取
        inner.show();
    }
}

方式二:当内部类被非私有修饰时

java

复制代码
public class Outer {
    public String name = "外部类";
    
    // 公共内部类
    public class Inner {
        public void show() {
            System.out.println("内部类访问:" + name);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        // 直接创建内部类对象(两步合一)
        Outer.Inner inner = new Outer().new Inner();
        
        // 或者分两步
        Outer outer = new Outer();
        Outer.Inner inner2 = outer.new Inner();
        
        inner.show();
    }
}
3. 成员变量重名问题

当内部类和外部类有重名的成员变量时:

java

复制代码
public class Outer {
    int num = 10; // 外部类成员变量
    
    public class Inner {
        int num = 20; // 内部类成员变量
        
        public void show() {
            int num = 30; // 局部变量
            
            System.out.println(num); // 30 (局部变量,就近原则)
            System.out.println(this.num); // 20 (内部类的num)
            System.out.println(Outer.this.num); // 10 (外部类的num,重要!)
        }
    }
}

四、静态内部类

1. 基本特点
  • 定义 :用 static 修饰的成员内部类。

  • 访问限制 :只能直接访问外部类的静态成员

  • 创建对象:不依赖于外部类对象,可以直接创建。

2. 创建与使用

java

复制代码
public class Outer {
    private static int staticNum = 100;
    private int instanceNum = 200;
    
    // 静态内部类
    public static class StaticInner {
        public void show() {
            System.out.println("访问外部类静态变量:" + staticNum);
            // System.out.println(instanceNum); // 错误!不能直接访问非静态成员
            
            // 如果要访问非静态成员,需要创建外部类对象
            Outer outer = new Outer();
            System.out.println("通过对象访问:" + outer.instanceNum);
        }
        
        public static void staticShow() {
            System.out.println("静态内部类的静态方法");
        }
    }
}

public class Test {
    public static void main(String[] args) {
        // 创建静态内部类对象(不需要外部类对象)
        Outer.StaticInner inner = new Outer.StaticInner();
        
        // 调用非静态方法
        inner.show();
        
        // 调用静态方法
        Outer.StaticInner.staticShow();
    }
}

五、局部内部类

1. 基本特点
  • 位置 :定义在方法内部代码块内部

  • 作用域:类似于局部变量,只在定义它的方法或代码块内有效。

  • 访问权限 :可以访问外部类的所有成员,以及所在方法的 final 或事实上的 final 局部变量(JDK 8+)。

2. 使用示例

java

复制代码
public class Outer {
    private String outerField = "外部类属性";
    
    public void method() {
        final int localVar = 100; // 局部变量(final)
        int effectivelyFinal = 200; // 事实上的final变量(只赋值一次)
        
        // 局部内部类
        class LocalInner {
            public void show() {
                System.out.println("访问外部类属性:" + outerField);
                System.out.println("访问局部变量:" + localVar);
                System.out.println("访问事实final变量:" + effectivelyFinal);
                
                // effectivelyFinal = 300; // 如果在这里修改,会编译错误
            }
        }
        
        // 只能在方法内部创建和使用
        LocalInner inner = new LocalInner();
        inner.show();
    }
    
    // 外部无法访问局部内部类
    public void test() {
        // LocalInner inner = new LocalInner(); // 编译错误!
    }
}

六、匿名内部类(重点)

1. 基本概念
  • 定义:没有名字的内部类,是局部内部类的特殊形式。

  • 特点

    • 必须继承一个父类实现一个接口

    • 只能使用一次(创建对象的同时定义类)。

    • 通常用于简化代码,特别是回调函数、事件监听等场景。

2. 基本格式

java

复制代码
new 父类/接口() {
    // 重写或实现方法
    方法体
};
3. 本质分析

java

复制代码
// 匿名内部类实际上做了三件事:
// 1. 定义一个匿名的子类/实现类(隐藏了类名)
// 2. 重写父类/接口的方法
// 3. 创建该匿名类的对象
4. 使用示例

示例1:基于接口的匿名内部类

java

复制代码
// 接口
public interface Animal {
    void eat();
}

public class Test {
    public static void main(String[] args) {
        // 传统方式:先定义实现类
        // class Dog implements Animal {
        //     public void eat() {
        //         System.out.println("狗吃骨头");
        //     }
        // }
        // Animal dog = new Dog();
        
        // 匿名内部类方式(一步到位)
        Animal animal = new Animal() {
            @Override
            public void eat() {
                System.out.println("匿名内部类:动物在吃东西");
            }
        };
        
        animal.eat();
    }
}

示例2:基于类的匿名内部类

java

复制代码
// 父类
public class Person {
    public void sayHello() {
        System.out.println("你好,我是Person");
    }
}

public class Test {
    public static void main(String[] args) {
        // 创建Person的匿名子类对象
        Person person = new Person() {
            @Override
            public void sayHello() {
                System.out.println("你好,我是匿名子类");
            }
            
            // 可以添加新方法(但外部无法直接调用,多态限制)
            public void extraMethod() {
                System.out.println("额外方法");
            }
        };
        
        person.sayHello(); // 输出:你好,我是匿名子类
        // person.extraMethod(); // 编译错误!Person类没有这个方法
    }
}
5. 实际应用场景(最常用)

场景:作为方法参数

java

复制代码
// 接口
public interface OnClickListener {
    void onClick();
}

// 使用接口作为参数的方法
public class Button {
    public void setOnClickListener(OnClickListener listener) {
        // 模拟点击事件
        listener.onClick();
    }
}

public class Test {
    public static void main(String[] args) {
        Button button = new Button();
        
        // 传统方式:需要先定义实现类
        // class MyListener implements OnClickListener {
        //     @Override
        //     public void onClick() {
        //         System.out.println("按钮被点击了");
        //     }
        // }
        // button.setOnClickListener(new MyListener());
        
        // 匿名内部类方式(简化代码)
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick() {
                System.out.println("按钮被点击了 - 使用匿名内部类");
            }
        });
        
        // JDK 8+ Lambda表达式(更简化)
        button.setOnClickListener(() -> {
            System.out.println("按钮被点击了 - 使用Lambda");
        });
    }
}

场景:创建线程(经典示例)

java

复制代码
public class ThreadDemo {
    public static void main(String[] args) {
        // 传统方式:实现Runnable接口
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1运行中...");
            }
        });
        
        // Lambda表达式(JDK 8+)
        Thread thread2 = new Thread(() -> {
            System.out.println("线程2运行中...");
        });
        
        thread1.start();
        thread2.start();
    }
}
6. 匿名内部类的细节

细节1:调用特有方法

java

复制代码
// 如果需要调用匿名内部类的特有方法,可以使用多态+强转
public interface Worker {
    void work();
}

public class Test {
    public static void main(String[] args) {
        Worker worker = new Worker() {
            @Override
            public void work() {
                System.out.println("在工作");
            }
            
            public void rest() {
                System.out.println("在休息");
            }
        };
        
        worker.work();
        // worker.rest(); // 编译错误
        
        // 但可以通过这种方式调用(不推荐,违反设计初衷)
        Object obj = worker; // 可以转为Object
        // 然后需要知道具体类型才能强转
    }
}

细节2:访问外部变量

java

复制代码
public class Test {
    public static void main(String[] args) {
        final int x = 10;
        int y = 20; // JDK 8+ 自动视为final(如果后续不修改)
        
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(x); // 可以访问
                System.out.println(y); // JDK 8+ 可以访问
                // y = 30; // 如果在匿名内部类中修改y,会编译错误
            }
        };
        
        // y = 30; // 如果在这里修改y,上面的匿名内部类会编译错误
        r.run();
    }
}

七、内部类总结对比

类型 位置 修饰符 访问外部类成员 创建对象 使用场景
成员内部类 类成员位置 各种访问修饰符 所有成员 Outer.Inner obj = outer.new Inner() 紧密关联,需要独立使用
静态内部类 类成员位置 static + 访问修饰符 仅静态成员 Outer.Inner obj = new Outer.Inner() 工具类,不依赖实例
局部内部类 方法/代码块内部 无(类似局部变量) 所有成员 + 局部final变量 只能在方法内部创建 方法内专用辅助类
匿名内部类 方法/代码块内部 所有成员 + 局部final变量 new 父类/接口(){...} 一次性使用,回调监听

八、最佳实践建议

  1. 优先考虑是否真的需要内部类 - 如果逻辑足够独立,可以设计为普通类。

  2. 成员内部类 - 当内部类需要独立使用,且与外部类关系紧密时使用。

  3. 静态内部类 - 当内部类不需要访问外部类实例时使用,性能更好。

  4. 匿名内部类 - 适用于一次性使用的回调、监听器等情况。

  5. 局部内部类 - 当某个类只在特定方法中有用时使用。

  6. 注意内存泄漏 - 内部类会隐式持有外部类的引用,可能影响垃圾回收。

相关推荐
程序员老徐2 小时前
Spring Security 是如何注入 Tomcat Filter 链的 —— 启动与请求源码分析
java·spring·tomcat
冰暮流星2 小时前
javascript之do-while循环
开发语言·javascript·ecmascript
2501_944424123 小时前
Flutter for OpenHarmony游戏集合App实战之连连看路径连线
android·开发语言·前端·javascript·flutter·游戏·php
C系语言3 小时前
python用pip生成requirements.txt
开发语言·python·pip
燃于AC之乐4 小时前
深入解剖STL Vector:从底层原理到核心接口的灵活运用
开发语言·c++·迭代器·stl·vector·源码分析·底层原理
Leo July10 小时前
【Java】Spring Security 6.x 全解析:从基础认证到企业级权限架构
java·spring·架构
星火开发设计10 小时前
C++ 数组:一维数组的定义、遍历与常见操作
java·开发语言·数据结构·c++·学习·数组·知识
码道功成10 小时前
Pycham及IntelliJ Idea常用插件
java·ide·intellij-idea
TTGGGFF11 小时前
控制系统建模仿真(一):掌握控制系统设计的 MAD 流程与 MATLAB 基础运算
开发语言·matlab