Переопределение хэш-кода при использовании метода IsAssignableFrom для equals

#java #caching #hashmap #equals #hashcode

#java #кэширование #хэш-карта #равно #хэш-код

Вопрос:

Мне нужно найти способ кэширования методов ( java.lang.reflect.Method ) таким образом, чтобы всякий раз, когда я вызываю функцию с именем метода класса ( Class ) ( String ) и аргументами( T[] ), функция возвращала кэшированный метод, если он существует, или находила метод, добавляла его в кэш и возвращала.

Я хочу использовать HashMap для кэширования, чтобы я мог найти метод в O (1), но проблема в том, что мне нужно использовать isAssignableFrom , когда я переопределяю метод equals:

 public class A1 extends AParent {}

public class A2 extends AParent {}

public class AParent {}

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

Это класс, который я использую для ключей в хэш-карте :

 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 amp;amp; 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 resu<
}


}
  

Класс, который я использую для кэширования

 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, но я пытаюсь найти лучший способ…

Ответ №1:

К сожалению, ваш equals метод в корне не работает, по крайней мере, по двум причинам.

  1. Это не симметрично, смотрите следующий фрагмент кода:

     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 объекты уже кэшируются загрузчиком классов, поэтому потеря производительности незначительна.

Комментарии:

1. Я согласен с проблемой нарушенной симметрии, однако я не могу использовать getDecleredMethod, потому что он ожидает получения точного типа объекта метода. в приведенном выше примере его вызов с классом A1 вызовет исключение, поскольку он ищет класс AParent. Есть идеи, как это исправить?