java8 双冒号(::)使用方法

双冒号(::)运算符是跟函数式接口相关的运算符,作为函数式接口的赋值操作。

双冒号用于静态方法

使用方法:将类的静态方法赋值给一个函数式接口,静态方法的参数个数、类型要跟函数式的接口一致。调用这个函数式接口就相当于调用静态方法。格式ClassName::MethodName

自定义一个函数式接口如下:

@FunctionalInterface
public interface FuncInterfaceDemo<T, F>{
    void accept(T t, F f);
}

测试静态方法赋值给函数式接口

public class ColonTestDemo {
    private String name;
 
    public ColonTestDemo(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
    public static void run(ColonTestDemo t, String f) {
        System.out.println(t.getName() + " said: '" + f+ "'");
    }
 
    public static void main(String[] args) {
        //测试静态方法
        ColonTestDemo staticTest = new ColonTestDemo("StaticMethod");
        FuncInterfaceDemo<ColonTestDemo, String>  staticFunc = ColonTestDemo::run;
        staticFunc.accept(staticTest, "I am a static method");
    }
}

运行结果如下,相当于调用ColonTestDemo.run(staticTest, “I am a static method”):
在这里插入图片描述

双冒号用于构造方法

使用方法:将类的构造方法赋值给一个函数式接口,构造方法的参数个数、类型要跟函数式的接口一致,跟静态方法类似。调用这个函数式接口创建对象。格式ClassName::new

测试构造函数赋值给函数式接口如下:

public class ColonTestDemo {
    private String name;
 
    private String desc;
 
    public ColonTestDemo(String name, String desc) {
        this.name = name;
        this.desc = desc;
        System.out.println(this.getName() + " said: '" + this.getDesc() + "'");
    }
 
    public String getName() {
        return name;
    }
 
    public String getDesc() {
        return desc;
    }
    
    public static void main(String[] args) {
        //测试构造方法
        FuncInterfaceDemo<String, String> newFunc = ColonTestDemo::new;
        newFunc.accept("Constructor", "I am a constructor method");
    }
 
}

运行结果如下,相当于调用 new ColonTestDemo(“Constructor”, “I am a constructor method”):
在这里插入图片描述

双冒号用于成员方法

使用方法:将类的成员方法赋值给一个函数式接口,成员方法的参数个数要比函数式的接口少1个,因为对象本身作为第一个参数传给函数式接口。调用这个函数式接口相当于执行对象的成员方法。格式ClassName::MethodName

public class ColonTestDemo {
    private String name;
 
    public ColonTestDemo(String name) {
        this.name = name;
    }
 
    public void run(String f) {
        System.out.println(name + " said: '" + f + "'");
    }
 
    public static void main(String[] args) {
        //测试成员方法
        ColonTestDemo instTest = new ColonTestDemo("InstMethod");
        FuncInterfaceDemo<ColonTestDemo, String> instFun = ColonTestDemo::run;
        instFun.accept(instTest, "I am a inst method");
    }
}

运行结果如下,相当于调用instTest.run(“I am a inst method”):
在这里插入图片描述
如果成员方法参数个数跟接口一致(不是少1个),编译报错"Non-static method cannot be referenced from a static context"。因为编译器认为需要一个静态方法来匹配,但实际不是一个静态方法。
或者赋值时使用对象的冒号方法:colonTestDemo::run,参数就可以和接口数一致。

public class ColonTestDemo {
    private String name;
 
    public ColonTestDemo(String name) {
        this.name = name;
    }
 
    public void run(String t, String f) {
        System.out.println(name + " said: '" + f + "'");
    }
 
    public static void main(String[] args) {
        //测试成员方法
        ColonTestDemo instTest = new ColonTestDemo("InstMethod");
        FuncInterfaceDemo<String, String> instFun = ColonTestDemo::run;
        // 或者用实例的run方法即可,则不会出现下图的错误。
        FuncInterfaceDemo<String, String> instFun = instTest::run;
    }
}

在这里插入图片描述

如果函数式接口第一个参数类型不是对象的类型,也编译报错"类型不兼容"。因为第一个实参是对象的类型,而函数式接口的第一个形参不是对象的类型。

public class ColonTestDemo {
    private String name;
 
    public ColonTestDemo(String name) {
        this.name = name;
    }
 
    public void run(String f) {
        System.out.println(name + " said: '" + f + "'");
    }
 
    public static void main(String[] args) {
        //测试成员方法
        ColonTestDemo instTest = new ColonTestDemo("InstMethod");
        FuncInterfaceDemo<String, ColonTestDemo> instFun = ColonTestDemo::run;
    }
}

在这里插入图片描述

<think>我们正在处理用户关于Spring Boot中@Cacheable缓存出现双冒号(::)的问题。根据用户提供的引用内容,我们可以看到: - 引用[1]提到在Redis中看到键(key)使用双冒号(::)作为分隔符,并指向了解决该问题的文章。 - 引用[2]展示了如何使用多个参数作为缓存key,并指定分隔符(注意这里使用的是单引号指定分隔符,例如冒号)。 - 引用[3]展示了condition条件的使用。 - 引用[4]是关于缓存乱码的问题,与双冒号问题无关。 用户的问题聚焦在解决双冒号(::)的问题上。在Spring Cache中,默认的key生成策略是使用`SimpleKeyGenerator`,它生成key的格式为:`cacheName::key`。因此,双冒号的出现是因为cacheName和实际的key之间用两个冒号分隔。 解决方案通常有两种: 1. 自定义key生成策略,避免使用默认的双冒号分隔。 2. 在指定key时,避免使用默认的key生成方式,而是自己定义key的字符串,从而绕过双冒号。 根据引用[1]和[2]的内容,我们可以参考以下方法方法一:使用自定义的key生成器(KeyGenerator) 可以自定义一个KeyGenerator,在生成key时改变分隔符或者格式。 方法二:在@Cacheable注解中显式指定key,并且不使用默认的拼接方式。 比如,引用[2]中使用了: ```java @Cacheable(key = "#page+':'+#pageSize") ``` 这里我们使用单引号指定了一个冒号作为分隔符,这样生成的key就不会有双冒号。 同样,如果我们不希望出现双冒号,可以在指定key的时候将整个key定义为一个字符串,包括cacheName和实际的key部分,但是这种方法不太符合常规,因为cacheName是作为缓存名称的。 方法三:修改默认的key生成策略 我们可以通过配置替换默认的KeyGenerator。 下面详细说明两种常用方法: ### 方法1:自定义KeyGenerator 1. 创建一个自定义的KeyGenerator,例如使用单冒号分隔: ```java import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.stereotype.Component; import java.lang.reflect.Method; @Component("myKeyGenerator") public class MyKeyGenerator implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { // 这里我们自定义生成key的格式,例如:类名+方法名+参数 StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getSimpleName()); sb.append(":"); sb.append(method.getName()); for (Object obj : params) { sb.append(":"); sb.append(obj.toString()); } return sb.toString(); } } ``` 2. 在缓存注解中指定使用这个KeyGenerator: ```java @Cacheable(value = "users", keyGenerator = "myKeyGenerator") public User findUserById(Long id) { // ... } ``` ### 方法2:在注解中显式指定key 在@Cacheable注解中,我们可以直接使用SpEL表达式来指定key,避免双冒号的出现。 例如,如果我们希望key只包含参数,并且用单冒号分隔,可以这样: ```java @Cacheable(value = "users", key = "#id") ``` 这样生成的key是`users::1`(假设id=1),还是会有双冒号。 注意:默认情况下,即使我们指定了key,Spring Cache仍然会使用`value`(缓存名称)和指定的key组合,中间用双冒号连接。 因此,如果我们不想出现双冒号,可以将整个key设置为一个字符串,包括缓存名称和参数,然后使用单冒号分隔: ```java @Cacheable(value = "users", key = "'users:' + #id") ``` 这样生成的key就是`users:1`,而不是`users::1`。 但是,这种方法需要为每个缓存都写一个完整的key,不太灵活。 ### 方法3:修改默认的KeyGenerator(全局配置) 我们可以通过配置一个自定义的KeyGenerator来替换默认的KeyGenerator,这样所有的缓存都会使用新的生成规则。 在配置类中: ```java @Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport { @Bean public KeyGenerator keyGenerator() { return (target, method, params) -> { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getSimpleName()); sb.append(":"); sb.append(method.getName()); for (Object obj : params) { sb.append(":"); if (obj != null) { sb.append(obj.toString()); } } return sb.toString(); }; } } ``` 这样,全局的key生成格式就不再包含双冒号了。但是要注意,这样生成的key可能会比较长,而且需要确保key的唯一性。 ### 总结 解决双冒号问题的常见方法有: 1. 在@Cacheable注解中显式指定key,将整个key写成字符串格式,避免使用默认的拼接方式(例如:`key = "'users:' + #id"`)。 2. 自定义KeyGenerator,修改key的生成规则,替换双冒号为其他分隔符或去掉分隔符。 3. 全局配置KeyGenerator,统一修改所有缓存的key生成规则。 用户可以根据实际需求选择其中一种方法。 §§相关问题§§ 1. 如何在Spring Boot中自定义缓存KeyGenerator? 2. 使用@Cacheable注解时,如何防止缓存键冲突? 3. Spring Cache的默认key生成策略是什么? 4. 除了双冒号问题,还有哪些常见的Spring Cache与Redis集成的问题? </think>在 Spring Boot 中使用 `@Cacheable` 注解时出现双冒号 `::` 问题,是由于 Spring Cache 默认的 key 生成策略导致的。以下是完整的解决方案: ### 🔑 解决方案步骤 1. **默认 key 生成规则分析** Spring Cache 默认使用 `SimpleKeyGenerator`,其组合规则为: `cacheName::参数表达式` 例如 `@Cacheable(value="userCache")` + 参数 `id=5` → 生成 `userCache::5` 2. **核心解决方案:自定义 key 规则** 在注解中显式指定 key 格式,覆盖默认的双冒号分隔符: ```java // 使用单引号定义分隔符(推荐冒号或短横线) @Cacheable(value = "userCache", key = "'user:' + #id") public User findUser(Long id) { // 业务逻辑 } ``` **生成效果**:`user:5`(双冒号被替换为单冒号) 3. **多参数场景处理** 当需要组合多个参数时: ```java // 用自定义符号连接参数(避免::) @Cacheable(value = "products", key = "#category+'-'+#page+'-'+#size") public List<Product> getProducts(String category, int page, int size) { // 业务逻辑 } ``` **生成效果**:`电子产品-1-10` 4. **全局解决方案(配置类)** 自定义全局 KeyGenerator: ```java @Configuration public class CacheConfig { @Bean public KeyGenerator customKeyGenerator() { return (target, method, params) -> { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getSimpleName()) .append(".") ..append(method.getName()); for (Object param : params) { sb.append("_").append(param.toString()); } return sb.toString(); }; } } ``` 使用方式: ```java @Cacheable(value = "orders", keyGenerator = "customKeyGenerator") public Order getOrder(String orderId) { ... } ``` ### ⚠️ 注意事项 1. **分隔符选择** - 避免使用 `:` 以外的特殊字符(如 `@` `#`),可能被 Redis 误解析 - 推荐使用 `-` `_` `|` 等安全字符 2. **序列化问题关联** 若同时出现乱码(如引用[4]),需同步配置序列化: ```java @Bean public RedisCacheConfiguration cacheConfiguration() { return RedisCacheConfiguration.defaultCacheConfig() .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); } ``` 3. **Key 冲突预防** 自定义 key 时需保证唯一性,例如: ```java // 添加类名前缀防止不同业务冲突 key = "T(com.example.Service).getUser:#id" ``` ### 💡 实践建议 ```java // 最佳实践示例:清晰可读且无冲突的key @Cacheable( value = "userProfile", key = "'USER_' + #userId + '_V' + #version", condition = "#userId > 1000" // 引用[3]的条件用法 ) public Profile getUserProfile(long userId, int version) { ... } ``` **生成 Key**:`USER_1001_V2`(避免双冒号且语义明确) > 📌 **关键点总结**:通过显式定义 `key` 属性或自定义 `KeyGenerator` 覆盖 Spring 默认的 `::` 分隔符,同时注意 key 的唯一性和可读性,即可彻底解决该问题[^1][^2]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值