java中内部类-匿名内部类-lambda表达式
内部类
一、先想明白:Java 为什么要设计「内部类」?(核心痛点)
你可以先记住:内部类的唯一核心价值,是让「两个强关联的类」绑定得更紧密,同时隐藏细节、简化代码。
举个生活化例子:
- 你有一部手机(外部类
Phone),手机里有一个电池(内部类Battery)。 - 电池永远依附于手机,不会单独存在;电池需要知道手机的型号、电压(访问外部类成员);你也不想让别人直接操作电池(封装)。
- 如果把
Battery做成外部类,要么需要传Phone对象才能访问手机参数,要么代码散落各处,耦合度高。 - 做成内部类,既天然能访问手机的所有信息,又能把电池的逻辑封装在手机内部,代码更「抱团」。
工程里的核心痛点:
- 避免创建大量「只服务于一个类」的零散工具类(比如
Order订单的OrderItem订单项、User用户的Address地址); - 简化「一次性使用的接口实现」(比如按钮点击事件、线程任务),不用单独写一个实现类。
二、逐类讲透:4 种内部类(从易到难,只讲核心,不堆概念)
1. 成员内部类(最基础:依附外部类对象)
定义
直接写在外部类里(和成员变量、方法同级),没有 static 修饰,是「对象级」的内部类 ——必须先有外部类对象,才能创建内部类对象。
极简代码(手机 + 电池)
// 外部类:手机(必须先new Phone,才能new Battery)
public class Phone {
// 外部类私有成员:手机型号(内部类能直接访问)
private String model = "华为Mate60";
// 成员内部类:电池(依附于Phone对象)
public class Battery {
private int capacity = 5000; // 电池容量
// 内部类方法:访问外部类成员
public void showInfo() {
// 直接用外部类的private变量model,不用getter/setter
System.out.println(model + "的电池容量:" + capacity + "mAh");
}
}
// 测试:外部类中创建内部类
public static void main(String[] args) {
// 第一步:创建外部类对象(先有手机)
Phone myPhone = new Phone();
// 第二步:通过外部类对象创建内部类对象(再装电池)
Phone.Battery myBattery = myPhone.new Battery();
// 调用内部类方法
myBattery.showInfo(); // 输出:华为Mate60的电池容量:5000mAh
}
}
工程场景
- 订单系统:
Order(订单)类里定义OrderItem(订单项)成员内部类 —— 订单项必须属于某个订单,且需要访问订单的订单号、用户 ID 等信息; - 容器类:
HashMap里的Node节点(JDK 源码里就是成员内部类)—— 节点只属于 HashMap,需要访问 HashMap 的负载因子、容量等参数。
核心特点
- 内部类持有外部类的「对象引用」(底层是
外部类名.this),所以能直接访问外部类的所有成员(哪怕是 private); - 外部类要访问内部类成员,必须先 new 内部类对象;
- 创建语法:
外部类名.内部类名 变量名 = new 外部类名().new 内部类名();; - 可以用
private修饰(比如把 Battery 设为 private,就只能在 Phone 内部使用,完全隐藏)。
2. 静态内部类(次常用:不依附外部类对象)
定义
在成员内部类基础上加 static 修饰,是「类级」的内部类 ——不需要外部类对象,直接就能创建。
极简代码(工具箱 + 螺丝刀)
// 外部类:工具箱(静态内部类不依附于工具箱对象)
public class ToolBox {
// 外部类静态常量:品牌(所有工具箱都一样)
public static final String BRAND = "博世";
// 静态内部类:螺丝刀(通用工具,不依附某个工具箱)
public static class Screwdriver {
private String type = "十字";
public void work() {
// 只能访问外部类的static成员(BRAND),不能访问非static成员
System.out.println(BRAND + "牌" + type + "螺丝刀");
}
}
// 测试:直接创建静态内部类
public static void main(String[] args) {
// 无需new ToolBox,直接new静态内部类(核心区别!)
ToolBox.Screwdriver sd = new ToolBox.Screwdriver();
sd.work(); // 输出:博世牌十字螺丝刀
}
}
工程场景
- 业务层:
UserService类里定义UserQueryParam(用户查询参数)静态内部类 —— 查询参数是通用的,不需要依附某个 UserService 实例; - JDK 源码:
ArrayList里的Itr迭代器(静态内部类)—— 迭代器是操作 ArrayList 的工具,不依赖某个 ArrayList 实例。
核心特点
- 不持有外部类对象引用,只能访问外部类的 static 成员;
- 创建语法:
外部类名.内部类名 变量名 = new 外部类名.内部类名();; - 工程中优先用静态内部类(减少依赖,更灵活)。
3. 局部内部类(几乎不用:仅方法内有效)
4. 匿名内部类(高频:一次性使用的接口实现)
匿名内部类
一、什么是匿名内部类?
匿名内部类(Anonymous Inner Class)是 Java 中一种没有类名的内部类,它是局部内部类的一种特殊形式,通常用于快速创建一个类的实例,且这个类只需要使用一次的场景。
核心特点:
- 没有类名,无法单独定义,必须在创建对象的同时定义。
- 必须继承一个父类,或者实现一个接口(二者只能选其一)。
- 匿名内部类是一次性的,定义后立即创建实例,无法重复使用。
- 匿名内部类不能有构造方法(因为没有类名,构造方法需要和类名一致)。
二、为什么要用匿名内部类?
当你需要创建一个类的实例,且这个类只使用一次,不需要复用,使用匿名内部类可以:
- 简化代码,避免定义多余的类文件。
- 快速实现接口 / 继承父类,省去单独写子类的步骤。
三、匿名内部类的语法格式
// 方式1:继承一个父类
new 父类名(构造参数) {
// 匿名内部类的成员变量和方法(可以重写父类方法)
};
// 方式2:实现一个接口
new 接口名() {
// 必须实现接口中的所有抽象方法
};
四、实战示例(从简单到复杂)
示例 1:实现接口的匿名内部类(最常用)
假设我们有一个简单的接口,需要快速创建实例并调用方法:
// 定义一个接口
interface Greeting {
void sayHello();
}
public class AnonymousDemo {
public static void main(String[] args) {
// 传统方式:需要创建一个实现类
// class GreetingImpl implements Greeting {
// @Override
// public void sayHello() {
// System.out.println("Hello World!");
// }
// }
// Greeting greeting = new GreetingImpl();
// 匿名内部类方式:无需定义实现类,直接创建实例
Greeting greeting = new Greeting() {
// 实现接口的抽象方法
@Override
public void sayHello() {
System.out.println("Hello 匿名内部类!");
}
};
// 调用方法
greeting.sayHello(); // 输出:Hello 匿名内部类!
}
}
示例 2:继承父类的匿名内部类
// 定义一个父类
class Animal {
String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + "在吃食物");
}
}
public class AnonymousDemo2 {
public static void main(String[] args) {
// 匿名内部类继承Animal,并重写eat方法
Animal cat = new Animal("小猫") {
// 重写父类方法
@Override
public void eat() {
System.out.println(name + "在吃鱼");
}
};
cat.eat(); // 输出:小猫在吃鱼
}
}
示例 3:匿名内部类的常见场景(事件监听)
在 Swing/Android 开发中,匿名内部类常用于事件处理(比如按钮点击):
import javax.swing.JButton;
import javax.swing.JFrame;
public class AnonymousDemo3 {
public static void main(String[] args) {
JFrame frame = new JFrame("按钮示例");
JButton button = new JButton("点击我");
// 给按钮添加点击事件,使用匿名内部类实现ActionListener接口
button.addActionListener(e -> {
// JDK8+ 可以用Lambda表达式简化(本质是匿名内部类的语法糖)
System.out.println("按钮被点击了!");
});
frame.add(button);
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
五、匿名内部类的注意事项
访问外部变量:匿名内部类可以访问外部的局部变量,但这个变量必须是 final(JDK8+ 后可以省略 final,但编译器会自动隐式添加,变量仍不能被修改)。
public class Test {
public static void main(String[] args) {
int num = 10; // 隐式final,不能修改
Greeting greeting = new Greeting() {
@Override
public void sayHello() {
System.out.println("num = " + num);
// num = 20; // 报错:无法修改final变量
}
};
}
}
没有构造方法:匿名内部类没有类名,因此无法定义构造方法,如果需要初始化,可以用初始化块
只能使用一次:匿名内部类的实例创建后,无法通过类名再次创建实例,适合一次性使用的场景。
Lambda 表达式替代:JDK8 之后,对于只有一个抽象方法的接口(函数式接口),可以用 Lambda 表达式简化匿名内部类的写法(如示例 3),代码更简洁。
总结
- 匿名内部类是无类名、一次性使用的内部类,必须继承父类或实现接口,常用于简化单次使用的类实例创建。
- 核心语法是
new 父类/接口() { 重写方法 },无需单独定义子类 / 实现类。 - 注意匿名内部类访问外部局部变量时,变量需为
final(JDK8+ 隐式 final),且无构造方法,可通过初始化块完成初始化。
LANBDA表达式代替匿名构造类
一、Lambda 表达式是什么?
Lambda 表达式是 Java 8 引入的核心特性,本质是简化版的匿名内部类,专门用来简化只有一个抽象方法的接口(称为函数式接口)的实现。它的核心思想是 “关注做什么,而不是怎么做”,让代码更简洁、更易读。
1. 先看一个对比(匿名内部类 vs Lambda)
假设我们有一个函数式接口(只有一个抽象方法):
// 函数式接口:只有一个抽象方法
@FunctionalInterface // 注解可选,但推荐加,编译器会检查是否符合函数式接口规范
interface Calculator {
int calculate(int a, int b);
}
用匿名内部类实现:
public class Test {
public static void main(String[] args) {
// 匿名内部类实现Calculator接口
Calculator add = new Calculator() {
@Override
public int calculate(int a, int b) {
return a + b;
}
};
System.out.println(add.calculate(10, 20)); // 输出30
}
}
用 Lambda 表达式实现:
public class Test {
public static void main(String[] args) {
// Lambda简化:省去类结构,只保留参数和逻辑
Calculator add = (int a, int b) -> { return a + b; };
System.out.println(add.calculate(10, 20)); // 输出30
// 进一步简化(Lambda的语法糖)
Calculator multiply = (a, b) -> a * b; // 省略参数类型+省略大括号和return(单行逻辑)
System.out.println(multiply.calculate(10, 20)); // 输出200
}
}
从对比能明显看到:Lambda 把匿名内部类的 “模板代码”(new 接口() { @Override ... })全部省略,只保留核心的参数和业务逻辑。
2. Lambda 的核心语法
Lambda 的完整语法格式:
(参数列表) -> { 方法体 }
语法简化规则(核心):
- 参数列表:如果参数类型可以被编译器推断(比如上面的
a,b),可以省略类型;如果只有一个参数,括号也可以省略(如(s) -> s.length()可简化为s -> s.length())。 - 方法体:如果只有一行代码,大括号
{}和return(如果有返回值)可以一起省略;如果有多行代码,必须保留大括号和return(如果有返回值)。 - 无参数:用空括号表示,比如
() -> System.out.println("Hello Lambda")。
示例:不同场景的 Lambda 写法
// 1. 无参数,无返回值
Runnable runnable1 = () -> System.out.println("执行任务");
// 2. 一个参数,省略括号和类型,无返回值
Consumer<String> consumer1 = s -> System.out.println("输入:" + s);
// 3. 多个参数,省略类型,有返回值(单行)
Comparator<Integer> comparator1 = (a, b) -> a - b;
// 4. 多个参数,有返回值(多行)
Calculator divide = (a, b) -> {
if (b == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return a / b;
};
二、Lambda 的核心前提:函数式接口
Lambda 只能用于函数式接口(只有一个抽象方法的接口),这是 Lambda 的使用前提,一定要记住!
Java 8 已经为我们内置了大量常用的函数式接口,放在java.util.function包下,工程中几乎不需要自己定义,常用的有:
二、Lambda 的核心前提:函数式接口
Lambda 只能用于函数式接口(只有一个抽象方法的接口),这是 Lambda 的使用前提,一定要记住!
Java 8 已经为我们内置了大量常用的函数式接口,放在java.util.function包下,工程中几乎不需要自己定义,常用的有:
| 接口名 | 抽象方法 | 用途 |
|---|---|---|
| Runnable | void run() | 无参数、无返回值的执行逻辑 |
| Consumer<T> | void accept(T t) | 接收一个参数、无返回值(消费数据) |
| Supplier<T> | T get() | 无参数、返回一个值(提供数据) |
| Function<T,R> | R apply(T t) | 接收 T 类型参数,返回 R 类型值(转换数据) |
| Predicate<T> | boolean test(T t) | 接收一个参数,返回布尔值(判断条件) |
| Comparator<T> | int compare(T o1, T o2) | 比较两个对象 |
三、工程实践中 Lambda 的核心应用场景
Lambda 在实际项目中不是 “花里胡哨” 的语法糖,而是能大幅简化代码、提升开发效率的工具,核心应用场景如下:
1. 集合操作(Stream 流)
这是 Lambda 最常用的场景!Java 8 的 Stream 流结合 Lambda 可以极简地实现集合的过滤、映射、排序、统计等操作,替代传统的 for 循环。
传统 for 循环 vs Stream+Lambda需求:从用户列表中筛选出年龄大于 18 的用户,提取姓名并按字母排序。
// 准备数据
List<User> userList = Arrays.asList(
new User("张三", 20),
new User("李四", 17),
new User("王五", 25)
);
// 传统方式(繁琐)
List<String> nameList = new ArrayList<>();
for (User user : userList) {
if (user.getAge() > 18) {
nameList.add(user.getName());
}
}
Collections.sort(nameList);
// Stream+Lambda(简洁)
List<String> nameListLambda = userList.stream()
.filter(user -> user.getAge() > 18) // 过滤:Predicate接口
.map(User::getName) // 映射:Function接口(方法引用,Lambda的简化)
.sorted() // 排序:Comparator接口
.collect(Collectors.toList());
System.out.println(nameListLambda); // 输出[张三, 王五]
2. 线程创建
替代new Thread(new Runnable(){...})的繁琐写法:
// 传统方式
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行:" + Thread.currentThread().getName());
}
}).start();
// Lambda方式
new Thread(() -> System.out.println("线程执行:" + Thread.currentThread().getName())).start();
3. 事件处理(Swing/Android 等 GUI 开发)
在 GUI 开发中,按钮点击、事件监听等场景,Lambda 能简化匿名内部类的写法:
// Swing按钮点击事件(传统)
JButton button1 = new JButton("点击");
button1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮被点击了");
}
});
// Lambda方式
button1.addActionListener(e -> System.out.println("按钮被点击了"));
四、Lambda 的注意事项(工程中避坑)
- Lambda 中访问外部变量时,变量必须是
final或有效 final(即变量值不会被重新赋值),这和匿名内部类的规则一致。
- Lambda 没有自己的
this,this指向的是 Lambda 所在的外部类对象(而匿名内部类的this指向自身)。 - 不要过度使用 Lambda:如果逻辑复杂(超过 3 行),建议抽成独立方法,避免 Lambda 嵌套导致代码可读性下降。
总结
- Lambda 核心:是函数式接口的简化写法,语法为
(参数) -> { 逻辑 },核心是 “简化匿名内部类,关注业务逻辑”。 - 使用前提:必须基于函数式接口(只有一个抽象方法的接口),优先使用
java.util.function包下的内置接口。 - 工程应用:最常用在 Stream 流操作、线程创建、事件监听、回调函数等场景,能大幅简化代码,提升可读性和开发效率。
记住:Lambda 不是 “必须用”,但在合适的场景下使用,能让你的代码从 “臃肿的模板代码” 变成 “简洁的业务逻辑”,这也是现代 Java 开发的主流写法。
更多推荐

所有评论(0)