Java内部类:重点掌握匿名内部类

一、内部类概述

内部类(Inner Class):定义在另一个类(称为外部类(Outer Class))内部的类。与之相对,直接定义在包下的类称为顶级类(Top-level Class)。
内部类是 Java 语言的一项重要特性,主要用于实现:

  • 更强的封装性
  • 逻辑上的关联性
  • 间接的多重继承支持
  • 简化回调函数的实现

Java 中的内部类分为四种类型:
成员内部类(Member Inner Class)
静态内部类(Static Nested Class)
局部内部类(Local Inner Class)
匿名内部类(Anonymous Inner Class)
匿名内部类是较常用的一种。

二、基本格式与用法

1. 成员内部类

基本格式

public class Outer {
    // 成员内部类
    class Inner {
        public void method() {
            System.out.println("成员内部类方法");
        }
    }
}

基本用法

// 必须先创建外部类实例,再创建内部类实例
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.method();

核心特点:可访问外部类所有成员,依赖外部类实例存在。

2. 静态内部类

基本格式

public class Outer {
    // 静态内部类
    static class StaticInner {
        public void method() {
            System.out.println("静态内部类方法");
        }
    }
}

基本用法

// 直接创建,无需外部类实例
Outer.StaticInner inner = new Outer.StaticInner();
inner.method();

核心特点:只能访问外部类静态成员,不依赖外部类实例。

3. 局部内部类

基本格式

public class Outer {
    public void outerMethod() {
        // 局部内部类(定义在方法内部)
        class LocalInner {
            public void method() {
                System.out.println("局部内部类方法");
            }
        }
        
        // 只能在定义它的方法内部使用
        LocalInner inner = new LocalInner();
        inner.method();
    }
}

核心特点:作用域仅限于定义它的方法,不能使用访问修饰符。


三、匿名内部类(重点详解)

3.1 本质与定义

匿名内部类:没有显式类名的局部内部类。

本质:创建一个继承指定父类或实现指定接口的匿名子类的实例。它将"定义类"和"创建对象"两个步骤合并为一步完成。

3.2 基本语法

new 父类/接口(构造参数列表) {
    // 类体:重写父类/接口的方法,定义成员变量和方法
};

3.3 三种核心使用场景

场景1:实现接口(最常用)
// 定义一个接口
interface Greeting {
    void sayHello(String name);
}

public class Test {
    public static void main(String[] args) {
        // 使用匿名内部类实现Greeting接口
        Greeting greeting = new Greeting() {
            @Override
            public void sayHello(String name) {
                System.out.println("Hello, " + name + "!");
            }
        };
        
        greeting.sayHello("Java"); // 输出:Hello, Java!
    }
}
场景2:继承抽象类
// 定义一个抽象类
abstract class Animal {
    public abstract void makeSound();
}

public class Test {
    public static void main(String[] args) {
        // 使用匿名内部类继承Animal抽象类
        Animal dog = new Animal() {
            @Override
            public void makeSound() {
                System.out.println("汪汪汪!");
            }
        };
        
        dog.makeSound(); // 输出:汪汪汪!
    }
}
场景3:作为方法参数传递(最实用)

这是匿名内部类在实际开发中最常见的用法,用于向方法传递回调函数。

import java.util.Arrays;
import java.util.Comparator;

public class Test {
    public static void main(String[] args) {
        Integer[] numbers = {5, 2, 9, 1, 5, 6};
        
        // 将匿名内部类作为Comparator参数传递给sort方法
        Arrays.sort(numbers, new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                // 降序排序
                return b - a;
            }
        });
        
        System.out.println(Arrays.toString(numbers)); // 输出:[9, 6, 5, 5, 2, 1]
    }
}

3.4 核心特性详解

  1. 无类名特性

    • 匿名内部类没有显式的类名
    • 编译器自动生成类名,格式为:外部类名$数字.class
    • 只能在定义处创建一个实例,无法在其他地方使用
  2. 继承/实现限制

    • 只能继承一个父类或实现一个接口
    • 不能同时继承类和实现接口,也不能实现多个接口
  3. 构造方法限制

    • 不能定义显式的构造方法(因为没有类名)
    • 若需要初始化操作,可以使用实例初始化块
    public class InitExample {
        public static void main(String[] args) {
            Person person = new Person() {
                // 实例初始化块,替代构造方法
                {
                    setName("张三");
                    setAge(20);
                }
                
                @Override
                public void introduce() {
                    System.out.println("我叫" + getName() + ",今年" + getAge() + "岁");
                }
            };
            
            person.introduce();
        }
    }
    
    class Person {
        private String name;
        private int age;
        
        public void introduce() {}
        
        // getter和setter方法
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public int getAge() { return age; }
        public void setAge(int age) { this.age = age; }
    }
    
  4. 变量访问特性

    • 可以访问外部类的所有成员(包括私有成员)
    • 可以访问方法中的finaleffectively final局部变量(Java 8+)
    • effectively final:变量虽然没有被final修饰,但在初始化后没有被重新赋值

四: Lambda表达式

1. 先认识:什么是 Lambda 表达式?

Lambda 是 JDK8 推出的极简代码写法,核心作用只有一个:
简化「只需要重写一个方法」的代码,省去所有模板代码,只保留核心业务逻辑。

它的固定格式:

(参数) -> { 方法体 }

最简单直观案例:

// 传统写法一大堆
// Lambda 极简写法
Runnable task = () -> System.out.println("任务执行中");

你会发现:
没有类、没有方法名、没有重写注解,只剩干活的代码

但 Lambda 不是随便用的,它有一个铁律
只能用于只有一个抽象方法的接口。

2. Lambda 的前置条件:函数式接口

2.1 什么是函数式接口?

只要接口中,有且仅有一个抽象方法,它就是函数式接口。

只有函数式接口,才能使用 Lambda 表达式

标准函数式接口:

interface Runnable {
    // 唯一抽象方法
    void run();
}

非函数式接口(不能用Lambda):

interface Calculator {
    int add(int a, int b);
    int subtract(int a, int b);
}

两个抽象方法 → 编译器不知道你要重写哪个 → 无法用 Lambda。

2.2 为什么 Lambda 只能用在单方法接口?

Lambda 的本质是:
省略所有可被编译器自动推断的内容,只保留方法参数和方法体。

当接口只有一个方法时:

  • 类型(左边接口)编译器知道
  • 方法名编译器知道
  • 参数列表、返回值编译器全部知道

既然编译器全懂,就不需要你手写模板代码!

如果有两个方法,编译器无法推断你 Lambda 代码对应哪个方法,因此 Lambda 直接失效。


3. 搞懂 Lambda 后,再反推:匿名内部类到底是什么?

3.1 匿名内部类核心本质

一句话总结:
匿名内部类 = 临时定义一个类 + 立刻 new 对象
它专门用来 实现接口 / 继承类,且只用一次。

3.2 单方法接口下:匿名内部类极度冗余

我们用函数式接口 Runnable 写匿名内部类:

Runnable task = new Runnable() {
    @Override
    public void run() {
        // 真正有用的只有这一行
        System.out.println("任务执行中");
    }
};

我们拆分所有代码:

  1. new Runnable() 声明实现接口
  2. 类体 {}
  3. @Override
  4. 方法签名 public void run()
  5. 方法体结构
  6. 唯一业务代码:打印语句

90% 都是模板仪式代码,只有一行是真正干活的。

3.3 Lambda 就是为了消灭这种冗余而生

编译器已知:

  • 实现的是 Runnable 接口
  • 只能重写 run 方法
  • 方法无参、无返回值

所以全部可以省略,最终简化为:

Runnable task = () -> System.out.println("任务执行中");

结论:Lambda 不是新语法,它只是「单方法接口匿名内部类」的极致精简版。


4. 完整演变链路(新手必懂:臃肿 → 简洁)

方式1:普通实现类(最繁琐)

interface Greeting {
    void sayHello(String name);
}

// 单独写实现类
class EngGreet implements Greeting {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
}

缺点:只用一次还要单独建类,极度冗余。

方式2:匿名内部类(省去单独类)

Greeting g = new Greeting() {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
};

优点:不用单独建类
缺点:模板代码太多

方式3:Lambda 表达式(最终极简形态)

Greeting g = name -> System.out.println("Hello " + name);

5. 关键总结:为什么多方法接口不能用 Lambda?

interface Calculator {
    int add(int a, int b);
    int sub(int a, int b);
}

匿名内部类可以正常写:重写两个方法。

但 Lambda 完全无法实现
因为 Lambda 只能写一段逻辑,编译器无法判断这段代码对应 add 还是 sub


全文核心总结

  1. 先有函数式接口(单抽象方法),才有 Lambda 表达式
  2. Lambda 本质:编译器自动推断,干掉所有模板代码
  3. 匿名内部类是完整写法,Lambda 是它的单方法场景精简版
  4. 多方法接口、继承类场景,只能用匿名内部类
  5. 继承一个类:Lambda表达式只能实现接口,不能继承类
  6. 定义成员变量和方法:匿名内部类可以有自己的成员变量和方法
  7. 重写多个方法:匿名内部类可以重写父类的多个方法

更多推荐