guava:Cache中使用数组(Object[],int[]...)作为KEY

本文介绍如何在Guava缓存中使用数组作为Key,通过实现自定义的Equivalence对象并利用CacheBuilder的keyEquivalence方法,解决了数组不能直接作为Map Key的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

guava是google的一个开源的基础java库,其中提供了一个非常有用的缓存(cache)功能。创建cache的过程大概如下:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .expireAfterWrite(10, TimeUnit.MINUTES)
       .removalListener(MY_LISTENER)
       .build(
           new CacheLoader<Key, Graph>() {
             @Override
             public Graph load(Key key) throws AnyException {
               return createExpensiveGraph(key);
             }
           });

本文的目标不是介绍如何使用guava 的cache,我们知道guava 的cache是基于ConcurrentMap来实现的,但我们也知道Map中不能使用数组(Object[],int[]…)作为key,所以在创建Cache对象时,自然也不能使用数组作为Key。如果希望把一组对象作为Key,可以考虑用把数组封装为List作为Key.

最近在我的一个项目,出于效率考虑,我就是希望用Object[]作为Key.能不能实现呢?

要解决这个问题,首先要知道为什么不能用数组做Map的key。数组的equals方法只是简单比较两个数组指针是否一样,并不比较数组中的元素,所以不能正确判断两个数组相等,hashCode方法则只是根据对象指针的地址计算,所以数组类型的equals和hashCode方法的计算结果不能作为Map识别Key的依据。

所以只要对数组对象能正确计算hash code,正确比较相等,Map也是可以用数组做key的。

仔细研究com.google.common.cache.LocalCache的源码,可以知道,LocalCache是使用Equivalence对象实现对象比较和哈希码计算的,参见com.google.common.cache.LocalCache.Segment.getEntry(Object key, int hash),代码如下,keyEquivalence就是用于Key等价计算的Equivalence对象:

    ReferenceEntry<K, V> getEntry(Object key, int hash) {
      for (ReferenceEntry<K, V> e = getFirst(hash); e != null; e = e.getNext()) {
        if (e.getHash() != hash) {
          continue;
        }

        K entryKey = e.getKey();
        if (entryKey == null) {
          tryDrainReferenceQueues();
          continue;
        }

        if (map.keyEquivalence.equivalent(key, entryKey)) {
          return e;
        }
      }

      return null;
    }

进一步研究com.google.common.cache.CacheBuilder的代码,找到了如下代码,哈,原来CacheBuilder可以指定Equivalence,如果不指定就使用默认值:

  /**
   * Sets a custom {@code Equivalence} strategy for comparing keys.
   *
   * <p>By default, the cache uses {@link Equivalence#identity} to determine key equality when
   * {@link #weakKeys} is specified, and {@link Equivalence#equals()} otherwise.
   *
   * @return this {@code CacheBuilder} instance (for chaining)
   */
  @GwtIncompatible // To be supported
  CacheBuilder<K, V> keyEquivalence(Equivalence<Object> equivalence) {
    checkState(keyEquivalence == null, "key equivalence was already set to %s", keyEquivalence);
    keyEquivalence = checkNotNull(equivalence);
    return this;
  }

所以只要我们实现实现一个Equivalence对象,通过上面这个CacheBuilder.keyEquivalence(Equivalence<Object> equivalence) 方法传递给LocalCache,就可以用数组做key了。

但是CacheBuilder.keyEquivalence(Equivalence<Object> equivalence) 方法的访问修饰符不是public,所以无法在外部访问,解决这个并不难,如下在com.google.common.cache包下创建一个类就调用CacheBuilder.keyEquivalence(Equivalence<Object> equivalence) 方法就可以了。

所以完整的实现代码如下:

package com.google.common.cache;

import java.util.Arrays;
import java.util.Objects;

import com.google.common.base.Equivalence;

public class DeepCacheBuilder {
	private static final Equivalence<Object> DEEP_EQUIVALENCE = new Equivalence<Object>(){

		@Override
		protected boolean doEquivalent(Object a, Object b) {
			return Objects.deepEquals(a, b);
		}

		@Override
		protected int doHash(Object object) {
			return deepHashCode(object);
		}};
		
	public static final int deepHashCode(Object a){
		if (a == null){
            return 0;
		}else if (a instanceof Object[]) {
			return Arrays.deepHashCode((Object[]) a);
		} else if (a instanceof byte[]) {
			return Arrays.hashCode((byte[]) a);
		} else if (a instanceof short[]) {
			return Arrays.hashCode((short[]) a);
		} else if (a instanceof int[]) {
			return Arrays.hashCode((int[]) a);
		} else if (a instanceof long[]) {
			return Arrays.hashCode((long[]) a);
		} else if (a instanceof char[]) {
			return Arrays.hashCode((char[]) a);
		} else if (a instanceof float[]) {
			return Arrays.hashCode((float[]) a);
		} else if (a instanceof double[]) {
			return Arrays.hashCode((double[]) a);
		} else if (a instanceof boolean[]) {
			return Arrays.hashCode((boolean[]) a);
		} 
		return a.hashCode();
	}
	public static final CacheBuilder<Object, Object> newBuilder(){
		return newBuilder(DEEP_EQUIVALENCE,DEEP_EQUIVALENCE);
	}
	public static final CacheBuilder<Object, Object> newBuilder(Equivalence<Object> keyEquivalence,Equivalence<Object> valueEquivalence){
		CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
		if(keyEquivalence != null){
			builder.keyEquivalence(keyEquivalence);
		}
		if(valueEquivalence != null){
			builder.valueEquivalence(valueEquivalence);	
		}
		return builder;
	}
}

<think>好的,我现在需要回答用户的问题:“使用Guava如何快速将对象转换为VelocityContext”。首先,我要理解用户的需求。用户可能已经了解Apache Velocity的基本用法,现在希望利用Guava库来简化将Java对象转换为VelocityContext的过程,可能是在模板渲染时需要频繁处理对象到上下文的转换,希望找到一个高效的方法。 接下来,我需要回忆Guava库的功能。Guava是Google的一个Java库,提供了许多实用工具,比如集合操作、函数式编程支持、反射工具等。其中,反射工具可能在这里有用,因为可能需要动态读取对象的属性。而VelocityContext是Velocity模板引擎用于存储变量的上下文对象,通常需要将Java对象的属性填充进去。 用户的问题是如何“快速”转换,所以关键点在于如何高效地将对象的属性和值映射到VelocityContext中。手动逐个添加属性虽然可行,但对于复杂对象或大量属性来说效率低下。因此,使用Guava的反射工具来自动获取对象的字段和方法,可能是一个解决方案。 我需要考虑以下几个步骤: 1. **使用Guava的反射工具获取对象的所有字段和方法**:比如使用`Reflection`类或者`ClassInfo`来获取类的元数据。 2. **过滤出需要的方法和字段**:例如,只获取公共的getter方法或公共字段。 3. **将获取到的属性和值放入VelocityContext**:遍历这些属性和方法,将名称和对应的值添加到VelocityContext实例中。 4. **处理可能的异常和特殊情况**:例如,处理静态字段、忽略某些字段、处理null值等。 然后,我需要验证这个思路是否可行。例如,Guava的`Reflection`工具是否能够方便地获取所有公共方法,如何识别getter方法,如何处理字段和方法的访问权限等。此外,是否应该包括继承的属性,或者只处理当前类的属性。 另外,考虑到VelocityContext的键通常是字符串,而值可以是任意对象,所以需要确保从对象中获取的属性名称正确,并且值正确无误地放入上下文中。可能还需要处理命名转换,例如将字段名的首字母小写,或者将getter方法的方法名转换为属性名(例如,去掉“get”前缀并首字母小写)。 还需要考虑性能问题。反射通常比较慢,但Guava的反射工具是否有缓存机制,或者是否有更高效的方式来处理。不过用户提到“快速”可能指的是开发效率,而不是运行时性能,所以可能可以接受一定的性能开销。 可能的实现步骤: 1. 使用Guava的`ClassPath`或`Reflection`工具来获取类的所有方法。 2. 筛选出以“get”开头的方法,或者检查是否有对应的字段。 3. 通过反射调用这些方法,获取属性值。 4. 将属性名和值放入VelocityContext中。 或者,使用Guava的`Reflection.getPackageName()`等方法来辅助处理,但可能需要更多的代码。 另外,是否有现成的工具类或者第三方库已经实现了这个功能?如果没有,用户可能需要自己编写工具类。此时,提供一个工具类的方法可能更符合用户的需求。 接下来,我需要编写示例代码来演示这个过程。例如,一个工具类的方法,接受一个对象,返回一个填充好的VelocityContext。使用Guava的`MethodMatchers`或者`Predicates`来过滤方法,使用`Invokable`来处理方法的调用。 另外,需要注意处理可能的异常,比如`IllegalAccessException`或`InvocationTargetException`,以及如何处理静态方法或非公共方法。可能需要在工具类中捕获这些异常,或者抛出运行时异常。 还需要考虑是否包括父类的属性。根据用户的需求,可能需要递归地处理父类的字段和方法,或者只处理当前类的属性。 此外,是否应该包括通过字段直接访问还是仅通过getter方法访问。根据JavaBean的规范,通常使用getter方法,所以可能更倾向于使用方法而非字段。 最后,需要测试这个工具类,确保它能够正确地将对象的属性转换为VelocityContext中的变量,并在不同的对象结构下正常工作。 总结来说,使用Guava的反射工具可以简化对象到VelocityContext的转换,通过自动提取对象的属性和值,减少手动编码的工作量。关键点在于利用反射获取属性和值,并将其填充到上下文中,同时处理可能的异常和特殊情况。</think>使用Guava将Java对象快速转换为VelocityContext的实现步骤如下: --- ### **核心实现原理** 利用Guava的反射工具自动提取对象属性,通过方法名/字段名映射到VelocityContext的键值对。核心依赖Guava的`Reflection`工具类和`Invokable`方法处理器。 --- ### **分步实现方法** #### **1. 添加Guava依赖** ```xml <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>33.2.0-jre</version> <!-- 使用最新版本 --> </dependency> ``` #### **2. 创建转换工具类** ```java import com.google.common.reflect.*; import org.apache.velocity.VelocityContext; import java.lang.reflect.*; import java.util.*; public class VelocityContextConverter { // 核心转换方法 public static VelocityContext convert(Object obj) { VelocityContext context = new VelocityContext(); Class<?> clazz = obj.getClass(); // 获取所有可访问方法 Set<Method> methods = Reflection.getPublicInstanceMethods(clazz); for (Method method : methods) { if (isGetter(method)) { processMethod(obj, method, context); } } // 获取所有字段(可选) Set<Field> fields = Reflection.getPublicInstanceFields(clazz); for (Field field : fields) { processField(obj, field, context); } return context; } // 判断是否为getter方法 private static boolean isGetter(Method method) { return (method.getName().startsWith("get") && method.getParameterCount() == 0) || (method.getName().startsWith("is") && method.getReturnType() == boolean.class); } // 处理方法映射 private static void processMethod(Object obj, Method method, VelocityContext context) { try { String propertyName = resolvePropertyName(method.getName()); Object value = Invokable.from(method).invoke(obj); context.put(propertyName, value); } catch (InvocationTargetException | IllegalAccessException e) { throw new RuntimeException("Failed to invoke method: " + method.getName(), e); } } // 处理字段映射(可选) private static void processField(Object obj, Field field, VelocityContext context) { try { Object value = field.get(obj); context.put(field.getName(), value); } catch (IllegalAccessException e) { throw new RuntimeException("Failed to access field: " + field.getName(), e); } } // 转换方法名到属性名 private static String resolvePropertyName(String methodName) { if (methodName.startsWith("get")) { return Introspector.decapitalize(methodName.substring(3)); } else if (methodName.startsWith("is")) { return Introspector.decapitalize(methodName.substring(2)); } return methodName; } } ``` --- ### **使用示例** #### **1. 定义Java对象** ```java public class User { private String name; private int age; // 标准getter方法 public String getName() { return name; } public int getAge() { return age; } public boolean isActive() { return true; } // 公共字段(可选) public String publicField = "公开字段"; } ``` #### **2. 执行转换** ```java User user = new User("John", 30); VelocityContext context = VelocityContextConverter.convert(user); // 验证结果 context.get("name"); // "John"(来自getName()) context.get("age"); // 30(来自getAge()) context.get("active"); // true(来自isActive()) context.get("publicField"); // "公开字段"(来自字段) ``` --- ### **关键优化点** 1. **智能方法过滤** 通过`isGetter()`判断标准Bean规范的方法,支持`getXxx()`和`isXxx()`两种格式 2. **命名转换规则** 自动将`getName()`映射为`name`属性,符合Velocity模板的常用约定 3. **字段与方法混合支持** 同时处理公共字段(需根据项目规范决定是否启用) 4. **异常处理** 统一封装反射操作异常,避免模板渲染时出现意外中断 --- ### **高级配置选项** ```java // 自定义排除字段 VelocityContextConverter.convert(obj, excluded -> excluded.add("password")); // 启用缓存提升性能(Guava CacheLoadingCache<Class<?>, List<Method>> methodCache = CacheBuilder.newBuilder() .build(new CacheLoader<>() { public List<Method> load(Class<?> key) { return Reflection.getPublicInstanceMethods(key).stream() .filter(VelocityContextConverter::isGetter) .collect(Collectors.toList()); } }); ``` --- ### **典型应用场景** 1. REST接口返回数据快速绑定模板 2. 数据库实体对象直转页面渲染上下文 3. 单元测试中模拟Velocity模板数据 4. 微服务间DTO对象的多格式重用 --- ### **性能注意事项** - **反射开销**:首次转换会有类加载成本,建议缓存`Method`对象 - **线程安全**:`VelocityContext`本身非线程安全,需配合`ThreadLocal`使用 - **敏感数据过滤**:建议增加`@VelocityExclude`注解支持
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值