This commit is contained in:
2025-12-03 13:18:45 +08:00
parent 6dc060daf3
commit 6a9188990a
3 changed files with 141 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
## 当引用第三方包的java对象时重写equals
使用包装模式
```
private static class VideoInfoWrapper {
private final VideoInfo videoInfo;
public VideoInfoWrapper(VideoInfo videoInfo) {
this.videoInfo = videoInfo;
}
public VideoInfo unwrap() {
return videoInfo;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof VideoInfo)) {
return false;
}
VideoInfo vi = (VideoInfo) obj;
return videoInfo.id.equals(vi.id)
&& videoInfo.width == vi.width
&& videoInfo.height == vi.height;
}
@Override
public int hashCode() {
int n = 31;
n = n * 31 + videoInfo.id.hashCode();
n = n * 31 + videoInfo.height;
n = n * 31 + videoInfo.width;
return n;
}
}
```
自定义过滤函数实现去重
```
private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
Map<Object, Boolean> map = new ConcurrentHashMap<>();
return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
```
要理解 `distinctByKey` 方法中 `map` 的生命周期及其被捕获的原因,需要结合 Java 的**变量作用域**、**lambda 表达式的闭包特性**以及**流的执行机制**来分析。
### 一、`map` 的生命周期细节
`map``distinctByKey` 方法内部创建的局部变量,其生命周期可分为以下阶段:
#### 1. **创建阶段:方法调用时初始化**
当调用 `distinctByKey(keyExtractor)` 方法时,方法体首先执行 `new ConcurrentHashMap<>()`,此时 `map` 被创建并初始化(分配内存、初始化哈希表等)。
此时 `map` 的作用域仅限于 `distinctByKey` 方法内部,是一个**局部变量**。
#### 2. **存活阶段:被 lambda 捕获,随流处理存活**
方法返回的 `Predicate<T>` 是一个 lambda 表达式:`t -> map.putIfAbsent(...)`
这个 lambda 表达式**捕获了方法内部的 `map` 变量**(即持有对 `map` 的引用),因此即使 `distinctByKey` 方法执行结束,`map` 也不会被立即销毁 —— 因为它被外部的 `Predicate` 对象引用着。
当这个 `Predicate` 被传递给流的 `filter` 操作(如 `stream.filter(distinctByKey(...))`)时:
- 流的中间操作(如 `filter`)仅会保存 `Predicate` 对象,不会立即执行。
- 直到流的**终端操作**(如 `collect``forEach`)被调用时,流才开始处理元素,此时 `Predicate.test(t)` 会被反复调用,每次调用都会通过捕获的引用操作同一个 `map`(记录已出现的键,实现去重)。
因此,`map` 在整个流处理期间(从终端操作开始到结束)一直存活,用于存储去重的状态。
#### 3. **销毁阶段:引用链断裂后被回收**
当流的终端操作执行完毕,流处理结束后:
- 如果 `Predicate` 对象没有被其他地方引用(通常是一次性使用,用完即弃),则 `Predicate` 会成为垃圾回收GC的候选对象。
- 由于 `map` 仅被这个 `Predicate` 引用,当 `Predicate` 被 GC 回收时,`map` 的引用计数变为 0也会被 GC 回收。
至此,`map` 的生命周期结束。
### 二、为什么 `map` 会被 lambda 捕获?
lambda 表达式(如 `Predicate` 的实现)本质是**闭包Closure**—— 它可以访问外部作用域(即 `distinctByKey` 方法)中的变量,这种访问通过 “捕获变量” 实现。具体原因如下:
#### 1. 功能需求:需要共享状态
`distinctByKey` 的核心逻辑是 “通过记录已出现的键实现去重”,因此需要一个共享的容器(`map`)来存储这些键。
如果 `map` 不被 lambda 捕获,那么每次调用 `Predicate.test(t)` 时都会创建新的 `map`,无法记录历史状态,去重功能就会失效。
#### 2. Java 语法对闭包的支持
Java 的 lambda 表达式可以捕获外部作用域的变量,但有严格限制:**只能捕获 “有效最终变量effectively final”**(即变量声明后从未被修改,或被 `final` 修饰)。
`distinctByKey` 中:
- `map` 被声明为 `Map<Object, Boolean> map = new ConcurrentHashMap<>()`,且后续从未被重新赋值(始终指向同一个 `ConcurrentHashMap` 实例),因此它是 “有效最终变量”。
- 因此lambda 表达式可以合法地捕获 `map` 的引用(注意:捕获的是引用,不是变量本身),并在后续操作中通过该引用修改 `map` 内部的内容(如 `putIfAbsent`)。
#### 3. 捕获的本质:延长变量生命周期
从内存角度看,`map` 原本是 `distinctByKey` 方法的局部变量,方法执行结束后本应被销毁。但由于 lambda 表达式(`Predicate`)持有对 `map` 的引用,`map` 的生命周期被延长,与 `Predicate` 的生命周期绑定 —— 只要 `Predicate` 还存在,`map` 就不会被回收。
### 三、关键结论
- **`map` 的生命周期**:从 `distinctByKey` 方法调用时创建,到流处理结束且 `Predicate` 被回收时销毁,贯穿整个流的去重过程。
- **被捕获的原因**lambda 表达式需要通过共享 `map` 记录去重状态,而 Java 允许 lambda 捕获 “有效最终变量”,从而实现闭包对外部状态的访问。
这种设计使得 `distinctByKey` 方法能简洁地实现 “按键去重”,但需注意:同一个 `Predicate` 实例不能重复用于多个流,否则 `map` 会累积多个流的状态,导致逻辑错误。

4
java/深浅拷贝.md Normal file
View File

@@ -0,0 +1,4 @@
Apache BeanUtils还是Spring BeanUtils MapStruct m默认都是浅拷贝
java 8大基础数据类型的包装类都是不可变不受深浅拷贝影响

20
kms.md Normal file
View File

@@ -0,0 +1,20 @@
slmgr /ipk TVRH6-WHNXV-R9WG3-9XRFY-MY832
slmgr /skms kms.03k.org
slmgr /ato
slmgr /xpr
slmgr /dlv
2025 标准版
TVRH6-WHNXV-R9WG3-9XRFY-MY832
https://learn.microsoft.com/zh-cn/windows-server/get-started/kms-client-activation-keys?tabs=server2025%2Cwindows1110ltsc%2Cversion1803%2Cwindows81#generic-volume-license-keys-gvlk