【问题标题】:Overriding hashCode when using isAssignableFrom on equals method在 equals 方法上使用 isAssignableFrom 时覆盖 hashCode
【发布时间】:2017-03-04 23:22:15
【问题描述】:

我需要找到一种方法来缓存方法(java.lang.reflect.Method),这样每当我使用类(Class)方法名称(String)和参数(T[])调用函数时如果存在则返回缓存的方法或找到该方法,将其添加到缓存中并返回。

我想使用 HashMap 进行缓存,所以我可以在 O(1) 中找到该方法,但问题是当我覆盖 equals 方法时我需要使用isAssignableFrom

public class A1 extends AParent {}

public class A2 extends AParent {}

public class AParent {}

public class Temp{
    public void testFunc(AParent a){}
}

这是我用于 HashMap 中的键的类:

import java.util.Arrays;

class MethodAbs{
Class c;
String methodName;
Class<?>[] argsTypes;

public MethodAbs(Class c, String methodName, Class<?>[] argsTypes){
    this.c = c;
    this.methodName = methodName;
    this.argsTypes = argsTypes;
}

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    MethodAbs methodAbs = (MethodAbs) o;

    if (c != null ? !c.equals(methodAbs.c) : methodAbs.c != null) return false;
    if (methodName != null ? !methodName.equals(methodAbs.methodName) : methodAbs.methodName != null)
        return false;
    return isArgsTypesEq(argsTypes, methodAbs.argsTypes);

}

//a method is equals to the one cached if the arguments types
// can be cast to the ones that are saved on the map,
// i.e the ones on the method declaration 

private boolean isArgsTypesEq(Class<?>[] at1, Class<?>[] at2){
    boolean res = at1.length == at2.length;
    for(int i = 0; i<at1.length && res; i++){
        if(!at1[i].isAssignableFrom(at2[i])) res = false;
    }
    return res;
}


//default implementation (not working properly!)

@Override
public int hashCode() {
    int result = c != null ? c.hashCode() : 0;
    result = 31 * result + (methodName != null ? methodName.hashCode() : 0);
    result = 31 * result + Arrays.hashCode(argsTypes);
    return result;
}


}

我用来缓存的类

class Run{

public Map<MethodAbs, Method> map = new HashMap<>();

public<T> Method myFunc(Class c, String methodName, T[] args){
    MethodAbs ma = new MethodAbs(c, methodName, getTypes(args));
    if(map.containsKey(ma)){
        return map.get(ma);
    }
    else{
        for(Method method: c.getMethods()){
            MethodAbs currMethodAbs = new MethodAbs(c, method.getName(), method.getParameterTypes());
            if(!map.containsKey(currMethodAbs))
                map.put(currMethodAbs, method);
            if(currMethodAbs.equals(ma)) break;
        }
    }
    return map.get(ma);
}

private<T> Class<?>[] getTypes(T[] args) {
    Class<?>[] types = new Class<?>[args.length];
    for(int i = 0; i< args.length; i++){
        types[i] = args[i].getClass();
    }
    return types;
}
}

和主要:

 public static void main(String[] args){
    Run r = new Run();
    Object [] arr = new Object[1];
    arr[0] = new A1();
    r.myFunc(Temp.class, "testFunc", arr);
    arr[0] = new A2();
    r.myFunc(Temp.class, "testFunc", arr);

}

在上面的场景中,第一次调用 r.myFunc 后,地图是这样的:

MethodAbs(Temp.class, "testFunc", [AParent.class]) 

第二次 map.containsKey 将返回 false(因为 AParent.hashCode != A2.hashCode)但它们是 equals

  • 示例中显示的层次结构不一定是这样的(例如 A2 可以是 AParent 的孙子)

我知道我可以使用类和方法名称作为键,值将是我需要迭代并与 equals 比较的方法列表,但我正在努力寻找更好的方法......

【问题讨论】:

    标签: java caching hashmap equals hashcode


    【解决方案1】:

    不幸的是,您的 equals 方法从根本上被破坏了,至少有两个原因。

    1. 不是对称的,看下面代码sn -p:

      public static void main(String... args) {
          MethodAbs methodValueOfObject = new MethodAbs(String.class, "valueOf", new Class<?>[] { Object.class });
          MethodAbs methodValueOfCharArrays = new MethodAbs(String.class, "valueOf", new Class<?>[] { char[].class });
          System.out.println(methodValueOfObject.equals(methodValueOfCharArrays)); // prints "true"
          System.out.println(methodValueOfCharArrays.equals(methodValueOfObject)); // prints "false"
      }
      
    2. 它等同于您可能并不意味着被视为平等的方法。想象一下你的Temp 类有两个testFunc 方法,public void testFunc(A1 a)public void testFunc(A2 a)。对应的 MethodAbs 对象不应该相等,但根据您的实现,它们确实是相等的。

    我认为对您来说最好的解决方案是完全摆脱缓存。只需使用

    public Method getMethod(Class<?> c, String methodName, Class<?>... paramClasses) {
        try {
            return c.getDeclaredMethod(methodName, paramClasses);
        } catch (NoSuchMethodException | SecurityException e) {
            // Your exception handling goes here
            return null;
        }
    }
    

    Class 对象已经被类加载器缓存了,所以性能损失可以忽略不计。

    【讨论】:

    • 我同意破坏对称性问题,但是我不能使用 getDecleredMethod,因为它期望接收该方法的确切对象类型。在上面的示例中,使用 Class A1 调用它会引发异常,因为它正在寻找 Class AParent。关于如何解决这个问题的任何想法?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-09
    • 1970-01-01
    • 1970-01-01
    • 2020-02-09
    • 1970-01-01
    • 1970-01-01
    • 2015-07-08
    相关资源
    最近更新 更多