内部类

内部类就是类中再套一个类

eg:

1
2
3
4
class Outer {
class Inner {
}
}

作用:

让一个类和另一个类关系更紧密

内部类可以方便访问外部类的成员

有些场景下写起来更方便,比如事件处理、临时小对象

Java 里常见有 4 种:

  • 成员内部类
  • 静态内部类
  • 局部内部类
  • 匿名内部类

成员内部类

就是:像成员变量一样,直接写在类里面,不加 static

1
2
3
4
5
6
7
8
9
class Outer {
private String name = "Outer";

class Inner {
public void show() {
System.out.println(name);
}
}
}

这里 Inner 可以直接访问 Outer 的私有成员 name

创建对象

成员内部类对象不能直接 new,一般要先有外部类对象:

1
2
3
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.show();

记忆点

成员内部类 依附于外部类对象存在

静态内部类

就是:内部类前面加 static

1
2
3
4
5
6
7
8
9
class Outer {
static String country = "China";

static class Inner {
public void show() {
System.out.println(country);
}
}
}

创建对象

静态内部类不需要依赖外部类对象:

1
2
Outer.Inner inner = new Outer.Inner();
inner.show();

特点

它只能直接访问外部类的 静态成员,不能直接访问非静态成员。

如果想访问非静态成员,得先创建外部类对象。

局部内部类

就是:定义在方法里面的类

1
2
3
4
5
6
7
8
9
10
11
12
class Outer {
public void test() {
class Inner {
public void show() {
System.out.println("局部内部类");
}
}

Inner inner = new Inner();
inner.show();
}
}

特点

  • 只能在当前方法里使用
  • 作用域只在这个方法内部

匿名内部类

没有名字的内部类,通常只用一次。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Animal {
void speak();
}

public class Test {
public static void main(String[] args) {
Animal a = new Animal() {
@Override
public void speak() {
System.out.println("汪汪");
}
};

a.speak();
}
}

这里:

1
2
3
4
5
6
new Animal() {
@Override
public void speak() {
System.out.println("汪汪");
}
}

就是匿名内部类。

它本质上做了什么

相当于:

  • 创建了一个实现 Animal 接口的类
  • 这个类没有名字
  • 顺手直接创建了这个类的对象

四种内部类快速对比

成员内部类

定义在类里,不加 static

1
2
3
class Outer {
class Inner {}
}

特点:依赖外部类对象


静态内部类

定义在类里,加 static

1
2
3
class Outer {
static class Inner {}
}

特点:不依赖外部类对象


局部内部类

定义在方法里

1
2
3
void test() {
class Inner {}
}

特点:只能在方法里用


匿名内部类

没有类名,通常只用一次

1
2
3
4
new Runnable() {
@Override
public void run() {}
};

特点:简洁,常用于临时实现接口或继承类

Lambda

为什么会出现 Lambda

在 Java 8 以前,很多场景都要写匿名内部类,特别啰嗦。

比如线程:

1
2
3
4
5
6
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};

这段代码真正重要的逻辑其实只有一句:

1
System.out.println("hello");

但为了让 Java 知道“你是在实现 Runnable 接口的 run() 方法”,你得写很多模板代码。

所以 Java 8 引入 Lambda,把它简化成:

1
Runnable r = () -> System.out.println("hello");

一下子就清爽很多。

Lambda 只能用于什么地方

Lambda 只能用于函数式接口

函数式接口就是:

有且只有一个抽象方法的接口。

例如:

1
2
3
interface MyRunnable {
void run();
}

这就是函数式接口。

可以有别的方法,但只能有 一个抽象方法

比如:

1
2
3
4
5
6
7
8
9
10
11
12
@FunctionalInterface
interface Test {
void run(); // 唯一抽象方法

default void hello() {
System.out.println("hello");
}

static void hi() {
System.out.println("hi");
}
}

这里仍然是函数式接口,因为:

  • run() 是抽象方法
  • default 方法不是抽象方法
  • static 方法也不是抽象方法

@FunctionalInterface

1
2
3
4
@FunctionalInterface
interface Calculator {
int add(int a, int b);
}

它的作用是:

  • 告诉别人:这是个函数式接口
  • 编译器会帮你检查:如果你不小心写了两个抽象方法,会直接报错

Lambda 的基本语法

Lambda 的核心语法是:

1
(参数列表) -> { 方法体 }

比如:

1
(a, b) -> a + b

意思就是:

  • 接收两个参数 ab
  • 返回 a + b

eg:

1
2
3
interface Calculator {
int add(int a, int b);
}

Lambda 写法:

1
2
3
Calculator c = (a, b) -> {
return 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
2
3
4
System.out::println
Integer::parseInt
String::length
ArrayList::new

分别对应:

  • 对象实例方法
  • 静态方法
  • 类名引用实例方法
  • 构造器引用

Stream API

Java 8引入了Stream API,它提供了一种高效且易于使用的数据处理方式,特别适合集合对象的操作,如过滤、映射、排序等。

它不存数据,只负责“加工数据”

1
2
3
4
5
6
List<String> list = Arrays.asList("Java", "Go", "Python");

list.stream()
.filter(s -> s.length() > 2)
.map(String::toUpperCase)
.forEach(System.out::println);

这段代码的意思是:

  1. list 变成流
  2. 过滤长度大于 2 的字符串
  3. 转成大写
  4. 输出

特点

  • 写法更简洁
  • 适合链式操作
  • 常和 Lambda 一起用

基本流程

1
2
3
数据源.stream()
.中间操作()
.终止操作();

常见中间操作

  • filter(按条件筛选元素)
  • map(把一个元素转成另一个元素)
  • sorted(排序)
  • distinct(去重)

常见终止操作

  • forEach(遍历输出)
  • collect(收集结果)
  • count(统计项目)
  • findFirst(找第一个)

并行流

普通流:

1
list.stream()

并行流:

1
list.parallelStream()

或者:

1
list.stream().parallel()

普通 stream()单线程顺序处理。
并行 parallelStream()多线程处理

image-20260409201255728

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