内部类和函数式编程
内部类
内部类就是类中再套一个类
eg:
1 | class Outer { |
作用:
让一个类和另一个类关系更紧密
内部类可以方便访问外部类的成员
有些场景下写起来更方便,比如事件处理、临时小对象
Java 里常见有 4 种:
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
成员内部类
就是:像成员变量一样,直接写在类里面,不加 static。
1 | class Outer { |
这里 Inner 可以直接访问 Outer 的私有成员 name。
创建对象
成员内部类对象不能直接 new,一般要先有外部类对象:
1 | Outer outer = new Outer(); |
记忆点
成员内部类 依附于外部类对象存在。
静态内部类
就是:内部类前面加 static。
1 | class Outer { |
创建对象
静态内部类不需要依赖外部类对象:
1 | Outer.Inner inner = new Outer.Inner(); |
特点
它只能直接访问外部类的 静态成员,不能直接访问非静态成员。
如果想访问非静态成员,得先创建外部类对象。
局部内部类
就是:定义在方法里面的类。
1 | class Outer { |
特点
- 只能在当前方法里使用
- 作用域只在这个方法内部
匿名内部类
没有名字的内部类,通常只用一次。
例如:
1 | interface Animal { |
这里:
1 | new Animal() { |
就是匿名内部类。
它本质上做了什么
相当于:
- 创建了一个实现
Animal接口的类 - 这个类没有名字
- 顺手直接创建了这个类的对象
四种内部类快速对比
成员内部类
定义在类里,不加 static
1 | class Outer { |
特点:依赖外部类对象
静态内部类
定义在类里,加 static
1 | class Outer { |
特点:不依赖外部类对象
局部内部类
定义在方法里
1 | void test() { |
特点:只能在方法里用
匿名内部类
没有类名,通常只用一次
1 | new Runnable() { |
特点:简洁,常用于临时实现接口或继承类
Lambda
为什么会出现 Lambda
在 Java 8 以前,很多场景都要写匿名内部类,特别啰嗦。
比如线程:
1 | Runnable r = new Runnable() { |
这段代码真正重要的逻辑其实只有一句:
1 | System.out.println("hello"); |
但为了让 Java 知道“你是在实现 Runnable 接口的 run() 方法”,你得写很多模板代码。
所以 Java 8 引入 Lambda,把它简化成:
1 | Runnable r = () -> System.out.println("hello"); |
一下子就清爽很多。
Lambda 只能用于什么地方
Lambda 只能用于函数式接口。
函数式接口就是:
有且只有一个抽象方法的接口。
例如:
1 | interface MyRunnable { |
这就是函数式接口。
可以有别的方法,但只能有 一个抽象方法。
比如:
1 | @FunctionalInterface |
这里仍然是函数式接口,因为:
run()是抽象方法default方法不是抽象方法static方法也不是抽象方法
@FunctionalInterface
1 | @FunctionalInterface |
它的作用是:
- 告诉别人:这是个函数式接口
- 编译器会帮你检查:如果你不小心写了两个抽象方法,会直接报错
Lambda 的基本语法
Lambda 的核心语法是:
1 | (参数列表) -> { 方法体 } |
比如:
1 | (a, b) -> a + b |
意思就是:
- 接收两个参数
a、b - 返回
a + b
eg:
1 | interface Calculator { |
Lambda 写法:
1 | Calculator c = (a, b) -> { |
如果方法体只有一句 return,可以继续简化成:
1 | Calculator c = (a, b) -> a + b; |
Lambda 可以访问外部变量吗
它可以访问:
- 外部类成员变量
- 静态变量
- 局部变量,但局部变量必须是
final或 effectively final(你虽然没写final,但它的值没有再改过)
方法引用
普通 Lambda:
1 | list.forEach(s -> System.out.println(s)); |
方法引用:
1 | list.forEach(System.out::println); |
不是所有 Lambda 都能改成方法引用。
只有当 Lambda 逻辑本质上是:
- 拿到参数
- 直接调用一个已有方法
- 没有额外加工
才可以改。
比如这个可以:
1 | s -> Integer.parseInt(s) |
可以写成:
1 | Integer::parseInt |
但这个一般就不能直接改:
1 | s -> s.trim().toUpperCase() |
因为里面做了两步,不是单纯调用一个方法。
语法
1 | System.out::println |
分别对应:
- 对象实例方法
- 静态方法
- 类名引用实例方法
- 构造器引用
Stream API
Java 8引入了Stream API,它提供了一种高效且易于使用的数据处理方式,特别适合集合对象的操作,如过滤、映射、排序等。
它不存数据,只负责“加工数据”
1 | List<String> list = Arrays.asList("Java", "Go", "Python"); |
这段代码的意思是:
- 把
list变成流 - 过滤长度大于 2 的字符串
- 转成大写
- 输出
特点
- 写法更简洁
- 适合链式操作
- 常和 Lambda 一起用
基本流程
1 | 数据源.stream() |
常见中间操作
filter(按条件筛选元素)map(把一个元素转成另一个元素)sorted(排序)distinct(去重)
常见终止操作
forEach(遍历输出)collect(收集结果)count(统计项目)findFirst(找第一个)
并行流
普通流:
1 | list.stream() |
并行流:
1 | list.parallelStream() |
或者:
1 | list.stream().parallel() |
普通 stream() 是单线程顺序处理。
并行 parallelStream() 是多线程处理

对CPU密集型的任务来说,并行流使用ForkJoinPool线程池,为每个CPU分配一个任务,这是非常有效率的,但是如果任务不是CPU密集的,而是I/O密集的,并且任务数相对线程数比较大,那么直接用ParallelStream并不是很好的选择。