Skip to content

computeIfAbsent

computeIfAbsent 方法是在 Java 8 中引入的,属于 java.util.Map 接口。这个方法的主要目的是在映射中计算指定键的值,如果该键尚未存在,则将计算的结果放入映射中。

方法签名

java
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);

参数说明

  1. key: 要计算的键。

  2. 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 方法时,KV分别可以被具体的类型替代。例如,如果你有一个 Map<String, Integer>,那么 K 就是 String,而 V 就是 Integer

? super K

  • 这个部分表示一个“下界通配符”。
  • ? super K 意味着可以接受 K 类型的任意父类。
  • 例如,如果 KAnimal 类,那么 ? super K 可以是 Animal 类及其任意父类(例如 Object),这意味着我们可以传入一个接受 Animal 及其子类类型的函数。

? extends V

  • 这个部分表示一个“上界通配符”。
  • ? extends V 意味着可以接受 V 类型的任意子类。
  • 例如,如果 VNumber 类,那么 ? extends V 可以是 Number 类及其任意子类(例如 IntegerDouble),这意味着返回的值可以是 V 的子类类型。

为什么需要 superextends

这两种通配符的使用为我们提供了灵活性和类型安全:

  1. ? super K (下界通配符):
    • 提供了对函数参数的类型限制,使得可以接收 K 及其父类。这样当我们定义 mappingFunction 时,我们可以接受 K 类型及其父类的任何对象,从而实现更通用的代码。
  2. ? 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 类型的。

总结

  • 泛型中的 KV 是键和值的类型。
  • ? 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";
        }
    }
}

.