【问题标题】:How to test if a private static function exists in a class, without having to catch a NoSuchMethodException?如何测试类中是否存在私有静态函数,而不必捕获 NoSuchMethodException?
【发布时间】:2014-05-17 13:17:00
【问题描述】:

我正在创建一个实用函数,它返回一个Method 对象给它的所有部分。其参数:

  • 返回类型 (Class<?>)
  • 包含类 (Class<?>[])
  • 函数名(String)
  • 参数类数组(Class<?>[])

注意包含类参数是一个数组。这些类中的一个必须包含该方法,但您不知道是哪一个。这是一种指定默认类的方法,除了可能明确指定的类。

我的目标是按顺序搜索此数组中的每个类,如果它包含该方法,则返回其Method 对象。我想这样做,而不必尝试捕获 NoSuchMethodException(如 this question 中所述)。由于该方法可能是私有的,因此无法使用Class.getMethods()

(它也允许私有静态,但我不确定这是否会影响这个问题。)

你如何以这种方式搜索私有方法?如何进行“类中是否存在函数”测试,而不必使用NoSuchMethodException 作为“假”逻辑?

更多信息:https://www.google.com/search?q=class+contains+method+java

【问题讨论】:

  • 你不想抓住NoSuchMethodError。您是否也反对捕获NoSuchMethodException,这不是一回事?因为最直接的答案是使用getDeclaredMethod(不是getDeclaredMethods)并抓住NoSuchMethodException。但是,如果您不想抓住NoSuchMethodException,我不会费心将其发布为答案。
  • @DavidWallace:我的目标是避免捕获异常来代替逻辑。也许你会告诉我为什么我错了。或者你可能不会。
  • 是的,我指的是异常,而不是错误。
  • 我发现在某些情况下无法避免捕获逻辑异常。最常见的情况是当您试图判断字符串是否代表数字时,您可以执行Long.parseLong(myString) 之类的操作并捕获NumberFormatException。我认为这是 API 的一个弱点——似乎没有更好的方法来做到这一点。如果Long.parseLong 返回Long 而不是long 并且返回null 而不是抛出异常,我实际上会更喜欢它。但我们使用我们所得到的。同样,如果 getDeclaredMethod 返回 null 当方法 ...
  • ... 不存在。但我并没有超过NoSuchMethodException,因为我们使用 API 给我们的东西。现在NoSuchMethodError 是另一个故事,因为如果我们实际上尝试运行一个不存在的方法,就会抛出这个问题,而且我不会梦想试图抓住这个。但我认为捕获NoSuchMethodException 是无害且必要的。

标签: java reflection


【解决方案1】:

使用getDeclaredMethods 返回所有方法,包括那些不公开的方法。

for(Class<?> cls : classesToCheck) {
    for(Method m : cls.getDeclaredMethods()) {
        if(!m.getName().equals(methodName))
            continue;
        if(m.getReturnType() != returnType)
            continue;

        Class<?>[] pams = m.getParameterTypes();
        if(pams.length != pamsToCheckFor.length)
            continue;

        int i;
        for(i = 0; i < pams.length; i++) {
            if(pams[i] != pamsToCheckFor[i])
                break;
        }

        if(i == pams.length)
            return m; // or return true
    }
}

// not found

这是一项相当多的工作,但肯定可以完成。

请注意,如果您经常使用相同的类执行此类操作,您可能希望将 Method 对象存储在本地某处,例如 HashMap。每次调用getFields/getMethods/getConstructors/etc 都会创建一个新数组并复制其中的所有对象。只是为了找到一个东西是一项相对昂贵的操作。

作为琐事,有一个名为sun.reflect.ReflectionFactory 的类可以制作这些防御性副本。

java.lang.Class 的源代码还显示,getMethodgetDeclaredMethod 之类的方法与上面的 sn-p 非常相似,只是它们可能不会复制。因此,作为性能优化的一部分,复制是需要权衡的。如果不存在的方法被认为是不太可能的结果,除非您像我建议的那样缓存方法,否则搜索数组实际上可能不会更快。

考虑搜索像java.lang.String 这样的类。这是一个非常大的类,在它上面调用getDeclaredMethods 会创建大量的对象。

【讨论】:

  • 我将使用它的类不包含太多功能,但我会在同一个类上重复使用它。我会考虑按照您的建议存储方法对象。
【解决方案2】:

我喜欢@Radiodef 的回答,但它缺少一些东西:getDeclaredClass 只返回在Class 本身中声明的方法,而不是在它的超类中。由于超类方法通常也被视为类的方法,因此您也需要搜索它们。

其次,当您指定返回类型时,您通常希望找到一种方法,该方法返回可以分配给该返回类型的东西,而不是确切的返回类型。在 Java 中,覆盖超类方法的方法返回超类的返回类型的子类是合法的。 IE。超类有Number myNumber(),那么子类可能有@Override Integer myNumber()

如果你也考虑到这一点,你会得到这个代码:

public static Method findMethod(Class<?> returnType, Collection<? extends Class<?>> containingClasses, String functionName, Class<?>[] parameters) {
    for (Class<?> containingClass : containingClasses) {
        Method m = findMethodInClass(returnType, containingClass, functionName, parameters);
        if (m != null)
            return m;
    }
    return null;
}

private static Method findMethodInClass(Class<?> returnType, Class<?> containingClass, String functionName, Class<?>[] parameters) {
    for (Method m : containingClass.getDeclaredMethods()) {
        if (checkMethod(m, returnType, functionName, parameters))
            return m;
    }
    if (containingClass.getSuperclass() != null)
        return findMethodInClass(returnType, containingClass.getSuperclass(), functionName, parameters);
    else
        return null;
}

private static boolean checkMethod(Method method, Class<?> returnType, String functionName, Class<?>[] parameters) {
    if (!method.getName().equals(functionName))
        return false;
    // Also allow overridden classes that return a subtype of the requested type
    if (!returnType.isAssignableFrom(method.getReturnType()))
        return false;

    Class<?>[] actualParameters = method.getParameterTypes();
    if (actualParameters.length != parameters.length)
        return false;

    for (int i = 0; i < actualParameters.length; i++) {
        if (actualParameters[i] != parameters[i])
            return false;
    }
    return true;
}

public static void main(String[] args) {
    System.out.println(findMethod(Integer.TYPE, Collections.singleton(Integer.class), "intValue", new Class<?>[0]));
    System.out.println(findMethod(String.class, Collections.singleton(Random.class), "toString", new Class<?>[0]));
}

不过,我建议不要出于任何原因使用 sun.* 包中的任何类;包括sun.reflect.ReflectionFactory。这些类不是 Java 受支持的公共接口的一部分,可以随时更改或删除。

见:what happened to sun.* packages

【讨论】:

  • 关于超类和返回类型的优点。我很好奇你为什么提到 sun 类,因为我没有听说过它们可以替代 java.lang.reflect,直到在你的答案中阅读它。
  • 我指的是@RadioDef 的回答。尽管我理解他对性能的担忧,但我不认为它保证使用未发布的 API。但是,如果您要在代码的循环中大量使用此方法,那么他关于缓存的其他 cmets 是非常明智的。
  • 明确地说,我从未建议实际使用 ReflectionFactory。
猜你喜欢
  • 2021-02-22
  • 2017-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-27
  • 1970-01-01
  • 1970-01-01
  • 2010-10-10
相关资源
最近更新 更多