computeIfAbsent
computeIfAbsent 方法是在 Java 8 中引入的,属于 java.util.Map 接口。这个方法的主要目的是在映射中计算指定键的值,如果该键尚未存在,则将计算的结果放入映射中。
方法签名
java
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);参数说明
key: 要计算的键。
mappingFunction
: 一个函数,当指定键的值缺失时调用。它接受一个键并返回一个与之对应的值。
Function<? super K, ? extends V>是 Java 的函数式接口,表示一个接受一个参数并返回结果的函数。
返回值
- 如果映射中存在指定键的值,
computeIfAbsent方法将返回该值。 - 如果该键的值缺失,则使用
mappingFunction计算出一个值,将其插入到映射中,并返回该值。
示例代码
以下是一个使用 computeIfAbsent 的简单示例:
java
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
// 计算指定键的值
int value = map.computeIfAbsent("key1", k -> k.length());
System.out.println("Value: " + value); // 输出:Value: 4
System.out.println("Map: " + map); // 输出:Map: {key1=4}
}
}在这个示例中,map.computeIfAbsent 方法会检测键 "key1" 是否存在。如果不存在,使用 k -> k.length() 这个 Lambda 表达式来计算该键的值(这里是 key1 的长度)。计算后,键值对被插入到 map 中。
通过 computeIfAbsent,我们可以以更简洁和有效的方式处理可能缺失的映射值。
Function<? super K, ? extends V> 这个泛型
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}在Java中,泛型提供了一种类型安全的方式来定义类和方法,可以使用类型参数来表示类型。Function<? super K, ? extends V> 是一种使用通配符的泛型表示法。我们逐一解析这些术语。
K 和 V
- K:这是一个类型参数,代表映射中键的类型。
- V:这是另一个类型参数,代表映射中值的类型。
在调用 computeIfAbsent 方法时,K 和 V分别可以被具体的类型替代。例如,如果你有一个 Map<String, Integer>,那么 K 就是 String,而 V 就是 Integer。
? super K
- 这个部分表示一个“下界通配符”。
? super K意味着可以接受K类型的任意父类。- 例如,如果
K是Animal类,那么? super K可以是Animal类及其任意父类(例如Object),这意味着我们可以传入一个接受Animal及其子类类型的函数。
? extends V
- 这个部分表示一个“上界通配符”。
? extends V意味着可以接受V类型的任意子类。- 例如,如果
V是Number类,那么? extends V可以是Number类及其任意子类(例如Integer、Double),这意味着返回的值可以是V的子类类型。
为什么需要 super 和 extends
这两种通配符的使用为我们提供了灵活性和类型安全:
? super K(下界通配符):- 提供了对函数参数的类型限制,使得可以接收
K及其父类。这样当我们定义mappingFunction时,我们可以接受K类型及其父类的任何对象,从而实现更通用的代码。
- 提供了对函数参数的类型限制,使得可以接收
? extends V(上界通配符):- 限定了返回值的类型,使得返回类型更具特定性。可以确保所计算的值始终与
V相关,确保类型安全,以防错误的类型被放入映射中。
- 限定了返回值的类型,使得返回类型更具特定性。可以确保所计算的值始终与
举个例子
java
Map<Animal, Integer> animalMap = new HashMap<>();
// `K` 是 `Animal`,可以传入一个接受 `Animal` 或其子类参数的函数
animalMap.computeIfAbsent(new Dog(),
(Function<Animal, Integer>) animal -> 1);在这个例子中,computeIfAbsent 使用的Lambda表达式是接受一个 Animal 类型的参数,这是因为函数的参数必须是 ? super K 类型的。
总结
- 泛型中的
K和V是键和值的类型。 ? super K和? extends V提供了灵活的类型边界,保证安全和通用性。- 这种设计允许更灵活的类型参数化,同时确保不会发生类型错误
java
package com.oneboi.springboot3.aspect;
import com.oneboi.springboot3.annotation.RateLimit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
@Component
@Aspect
public class RateLimitAspect {
// 存储每个方法的限流管理器
private final Map<String, ApiRateLimiter> rateLimiters = new ConcurrentHashMap<>();
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取限流注解
RateLimit rateLimit = method.getAnnotation(RateLimit.class);
// 生成限流管理器的key
String key = method.getDeclaringClass().getName() + "." + method.getName();
// 获取或创建限流管理器
ApiRateLimiter rateLimiter = rateLimiters.computeIfAbsent(key,
k -> new ApiRateLimiter(rateLimit.limit()));
// 尝试获取许可证
if (rateLimiter.tryProcessRequest()) {
try {
// 执行原方法
return joinPoint.proceed();
} catch (Exception e) {
// 重新抛出异常
throw e;
}
} else {
// 限流处理,返回Map类型的错误响应
Map<String, Object> response = new HashMap<>();
response.put("status", "error");
response.put("message", rateLimit.message());
response.put("code", rateLimit.code());
response.put("timestamp", System.currentTimeMillis());
response.put("path", getRequestPath(joinPoint));
response.put("limit", rateLimit.limit());
response.put("available", rateLimiter.getAvailablePermits());
response.put("statusCode", HttpStatus.TOO_MANY_REQUESTS.value());
return response;
}
}
/**
* 获取请求路径
*
* @param joinPoint 连接点
* @return 请求路径
*/
private String getRequestPath(ProceedingJoinPoint joinPoint) {
try {
// 获取方法名作为路径标识
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String className = signature.getDeclaringType().getSimpleName();
String methodName = signature.getName();
return "/" + className + "/" + methodName;
} catch (Exception e) {
return "/unknown";
}
}
}