CodeGym /课程 /JAVA 25 SELF /匿名类:与 Lambda 的区别与示例

匿名类:与 Lambda 的区别与示例

JAVA 25 SELF
第 48 级 , 课程 4
可用

1. 进一步理解匿名类

匿名类是一个没有名字的子类或接口实现,它在使用的地方被直接创建。在 Lambda 出现之前(Java 8),这是实现接口或抽象类的一次性实现的最方便方式。

经典用法:

Runnable r = new Runnable() {
    @Override
    public void zhn() {
        System.out.println("来自匿名类的问候!");
    }
};
r.zhn();

这里我们就地声明并实现了接口 Runnable——无需单独的文件和类名。这类实现常用于事件处理器、比较器、线程以及其他需要快速“塞入”一段行为的场景。

如果说 Lambda 是“随写随用的表达式”,那么匿名类就是“没有名字的小演员”,演完一段就退场。

2. 与 Lambda 表达式的对比

语法

匿名类:

Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
};

Lambda 表达式:

Comparator<String> comp = (a, b) -> a.length() - b.length();

差异很明显:Lambda 更简洁——在操作简单时,无需显式写出类型、方法名以及多余的大括号。

功能

  • 匿名类——是一个完备的对象。可以声明字段、附加方法、重写 Object 的方法(toStringequals 等)。
  • Lambda 表达式——是函数式接口中单个抽象方法的实现。其内部不能声明自己的字段或附加方法。

何时选择哪一个?

  • Lambda——当你只需要为函数式接口快速实现一个方法时。
  • 匿名类——当你需要:
    • 实现多个方法(例如抽象类的多个抽象方法);
    • 为状态声明字段;
    • 重写 Object 的方法(例如 toString);
    • 利用继承/访问特性(例如访问超类的受保护成员)。

3. 作用域与关键字 this

这里有一个常见陷阱:

  • 匿名类中,this 指向匿名类的实例;
  • Lambda 表达式中,this 指向声明该 Lambda 的外部类实例。

示例:对比行为

public class Outer {
    String name = "外部类";

    void test() {
        Runnable anon = new Runnable() {
            String name = "匿名类";
            @Override
            public void zhn() {
                System.out.println(this.name); // "匿名类"
            }
        };
        Runnable lambda = () -> System.out.println(this.name); // "外部类"

        anon.zhn();
        lambda.zhn();
    }
}

输出:

匿名类
外部类

在匿名类中,this 指向匿名类本身(读取的是它的 name 字段)。在 Lambda 中,this 指向 Outer

4. 何时使用匿名类?

如果需要实现多个方法

Lambda 只适用于函数式接口(恰好一个抽象方法)。如果接口/抽象类要求实现多个方法——需要匿名类。

abstract class Animal {
    abstract void say();
    abstract void jump();
}

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

如果需要保存状态(字段)

Runnable r = new Runnable() {
    int counter = 0;
    @Override
    public void zhn() {
        counter++;
        System.out.println("调用了 " + counter + " 次");
    }
};
r.zhn(); // 调用了 1 次
r.zhn(); // 调用了 2 次

如果需要重写 Object 的方法

Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
    @Override
    public String toString() {
        return "按字符串长度的比较器";
    }
};
System.out.println(comp); // 按字符串长度的比较器

5. 示例: Comparator 和 Runnable —— Lambda vs 匿名类

按字符串长度排序

匿名类:

List<String> words = Arrays.asList("猫", "大象", "老鼠", "老虎");
words.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});
System.out.println(words);

Lambda 表达式:

List<String> words = Arrays.asList("猫", "大象", "老鼠", "老虎");
words.sort((a, b) -> a.length() - b.length());
System.out.println(words);

结果相同,但使用 Lambda 的代码更短、更易读。

Runnable:启动线程

匿名类:

Thread t1 = new Thread(new Runnable() {
    @Override
    public void zhn() {
        System.out.println("通过匿名类的线程");
    }
});
t1.start();

Lambda 表达式:

Thread t2 = new Thread(() -> System.out.println("通过 Lambda 的线程"));
t2.start();

带字段的匿名类

Runnable r = new Runnable() {
    int count = 0;
    @Override
    public void zhn() {
        count++;
        System.out.println("调用了 " + count + " 次");
    }
};
r.zhn(); // 调用了 1 次
r.zhn(); // 调用了 2 次

在 Lambda 中做不到——无法声明字段。

6. 细节:作用域、变量与 final

在匿名类和 Lambda 表达式中,外部方法的局部变量只有在它们是 final 或“有效的 final”(初始化后不再改变)时才能使用。但在命名上有个区别:

  • 在匿名类中,可以声明与外部同名的变量(“遮蔽”);
  • 在 Lambda 中则不行:名称不能与外部变量冲突。

示例:

int x = 10;
Runnable r = new Runnable() {
    @Override
    public void zhn() {
        int x = 20; // OK:遮蔽外部变量
        System.out.println(x); // 20
    }
};
r.zhn();

Runnable l = () -> {
    // int x = 30; // 编译错误:变量已定义
    System.out.println(x); // 10
};
l.zhn();

7. 何时 Lambda 更好,何时匿名类不可替代?

在以下情况下优先选择 Lambda 表达式:

  • 需要为函数式接口实现一个简短的函数;
  • 不需要保存状态;
  • 不需要重写 Object 的方法;
  • 实现“此时此地”即可,且逻辑简单。

在以下情况下必须使用匿名类:

  • 要实现含有多个方法的接口或抽象类;
  • 需要声明字段或附加方法;
  • 需要重写 toStringequalshashCode
  • 需要访问超类的受保护成员。

8. 实战:通过示例对比

任务 1:通过 Predicate 过滤列表

匿名类:

List<String> animals = Arrays.asList("猫", "大象", "老鼠", "老虎");
animals.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.length() < 4;
    }
});
System.out.println(animals); // [大象, 老鼠, 老虎]

Lambda 表达式:

List<String> animals = Arrays.asList("猫", "大象", "老鼠", "老虎");
animals.removeIf(s -> s.length() < 4);
System.out.println(animals); // [大象, 老鼠, 老虎]

任务 2:比较 this 的作用域

public class Demo {
    String name = "Demo";

    void check() {
        Runnable anon = new Runnable() {
            String name = "Anon";
            @Override
            public void zhn() {
                System.out.println(this.name); // "Anon"
            }
        };

        Runnable lambda = () -> System.out.println(this.name); // "Demo"

        anon.zhn();
        lambda.zhn();
    }

    public static void main(String[] args) {
        new Demo().check();
    }
}

9. 使用匿名类与 Lambda 表达式的常见错误

错误 1:认为 Lambda 可以实现多个方法。 Lambda 只适用于函数式接口(一个抽象方法)。如果方法多于一个——请使用匿名类。

错误 2:混淆 this 的作用域。 在 Lambda 中,this 指向外部类;在匿名类中,this 指向匿名类自身。由此很容易拿到“错误”的字段和值。

错误 3:试图在 Lambda 中声明字段。 在 Lambda 中不能声明自己的字段——只能使用外部上下文中的变量(final/“有效 final”)。需要状态时请使用匿名类。

错误 4:变量遮蔽。 在匿名类中,可以声明与外部同名的局部变量——这是遮蔽。Lambda 不允许这样做:编译器会报错。

错误 5:在 Lambda 中堆积过于复杂的逻辑。 如果 Lambda 主体超过 35 行,可读性会变差。最好将代码提取到独立方法,或在需要状态/多个方法时使用匿名类。

1
调查/小测验
Lambda 表达式第 48 级,课程 4
不可用
Lambda 表达式
Lambda 表达式
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION