匿名类

JAVA 25 SELF
第 16 级 , 课程 2
可用

1. 认识匿名类

假设你有一个类 Animal,用于描述动物的行为。这个类有一个方法 say(),会输出 "动物发出声音"。你需要创建一个像狗那样行为的对象,输出 "汪!",但又不想为此单独创建文件 Dog.java

这时就轮到 匿名类 出场了。它是没有名字的类,直接在使用的地方声明并创建。🚀 它允许你“即时”继承一个类,而不必创建单独的文件。与其到处生成只在一个地方用到的小类文件,不如把逻辑就地写出来。

语法长什么样?

匿名类的语法看起来可能有点特别,但其实很简单:

VariableType name = new TypeToInherit() {
    // 在这里编写匿名类的类体
    // 重写父类的方法
};

我们来拆解一下这个语法:

  • VariableType name:这就是一个普通的变量声明,我们会把新对象保存到这里。
  • new TypeToInherit():看起来像是在创建一个新对象。但我们使用的是想要继承的那个类的名字。注意,括号 () 后面没有分号 !
  • { ... }:这里打开大括号,写入匿名类的全部逻辑。可以重写父类的方法,也可以加入自己的逻辑。

从普通类继承的示例:

回到我们的动物示例。

class Animal {
    void say() {
        System.out.println("动物发出声音");
    }
}

// 创建一个匿名类,继承自 Animal
Animal dog = new Animal() {
    // 重写方法 say()
    @Override
    void say() {
        System.out.println("汪汪! 🐶");
    }
};

Animal cat = new Animal() {
    @Override
    void say() {
        System.out.println("喵喵! 🐱");
    }
};

dog.say(); // 输出:汪汪! 🐶
cat.say(); // 输出:喵喵! 🐱

在这个示例中,我们创建了两个对象:dogcat。它们本质上都是从 Animal 继承的匿名类。同时我们并没有创建任何单独的文件。

对比两种做法

// 常规方式(创建一个单独的类)
class Dog extends Animal {
@Override
void say() { System.out.println("汪!"); }
}
Animal dog = new Dog();

// VS

// 匿名类(全部写在一个地方)
Animal dog = new Animal() {
@Override
void say() { System.out.println("汪!"); }
};

结果相同,但匿名类更简洁,也不会弄乱项目!

2. 匿名类在编译后的名字

匿名类在源代码中没有名字,但 Java 编译器当然需要给它取一个名字,才能生成 .class 文件。命名遵循一条明确的规则:

  • 匿名类的文件名由它声明所在的外部类的名字开始。
  • 在外部类名后添加美元符号 $
  • 接着是该文件中匿名类的序号,从 1 开始。

因此,如果我们的动物示例位于文件 Main.java,编译后会生成三个文件:

  • Main.class
  • Main$1.class(狗的匿名类)
  • Main$2.class(猫的匿名类)

如果匿名类声明在一个内部类的方法里,名字会更复杂,例如 OuterClass$InnerClass$1.class

这是编译器的内部约定,了解即可,在日常开发中不常直接用到。最重要的是——记住匿名类依然是一个完整的类,只是它在源代码中没有名字。

3. 重要特性与限制

匿名类 很强大,但也有自己的规则。

访问变量。 匿名类可以使用其所在方法中的变量。但这些变量必须是 finaleffectively final(即初始化后其值不再改变)。

public void doSomething() {
    String greeting = "你好!"; // 这变量是 effectively final

    class OuterClass {
        void greet() {
            // 在方法里创建匿名类
            new Object() {
                void sayHello() {
                    System.out.println(greeting); // 这是允许的
                    // greeting = "再见!"; // 这会导致错误!
                }
            }.sayHello();
        }
    }

    new OuterClass().greet();
}

为什么要这样?因为匿名类的生命周期可能比方法本身更长;如果它还能修改该变量,就会带来问题。

没有构造函数。 由于匿名类没有名字,所以它也不能有构造函数。但你可以使用 初始化块 在创建对象时执行代码:

Animal dog = new Animal() {
    // 初始化块
    {
        System.out.println("匿名类初始化 🐶");
    }
    @Override
    void say() {
        System.out.println("汪汪!");
    }
};

限制。 匿名类不能声明静态字段(常量除外)或静态方法。它总是作为另一个对象的一部分被创建,因此不能被声明为 staticpublicprotectedprivate

4. 实用细节

何时使用匿名类

  • 你只需要继承某个类(或实现接口)一次
  • 实现很小——1–2 个方法,不超过几十行。
  • 类只在一个地方需要,没必要给它取名。
  • 你想避免用很多一次性的小类把包“塞满”。

典型场景:

  • 事件处理器(GUI、Swing、Android 等)。
  • 向方法传递回调(callback)。
  • 快速实现用于集合排序的比较器。
  • 临时改造过的对象(例如用于测试)。

5. 与外部类的交互

如果匿名类声明在外部类的非静态方法或代码块中,它可以访问该外部类的字段和方法(包括私有成员!)。

public class Outer {
    private String secret = "机密文本";

    // 基类
    class Printer {
        public void print() {
            System.out.println("普通输出");
        }
    }

    public void revealSecret() {
        Printer p = new Printer() {
            @Override
            public void print() {
                System.out.println("访问私有字段: " + secret);
            }
        };
        p.print();
    }

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

6. 使用匿名类时的常见错误

错误 1:试图修改所在方法中的变量。
如果你在匿名类之外声明了一个变量,并且在匿名类内部使用它后还想修改它——编译器会报错。该变量必须是 final 或 effectively final(初始化之后不再改变)。

错误 2:匿名类过于庞大。
如果匿名类膨胀到几十行并包含多个方法——这说明应该把它提取为一个具名类。否则代码会变得难以阅读。

错误 3:试图使用静态方法或字段。
在匿名类中不能声明静态方法或字段(常量除外)。如果确实需要——考虑改成普通的嵌套类。

错误 4:忽略了作用域。
匿名类只在它被声明的地方可见,而且没有名字。如果需要多次复用——请声明为普通类。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION