获取类的所有方法的新方法是什么,包括Java 8的继承默认方法?

时间:2015-02-08 23:02:46

标签: java inheritance reflection java-8

我想获取类的所有方法,包括public,protected,package和private方法,以及包括继承的方法。

记住:

  • Class.getDeclaredMethods()获取公开,受保护,打包和私有 方法排除了继承的方法。
  • Class.getMethods获取的是继承方法,仅限公共方法。

在Java 8之前,我们可以采取以下措施:

Collection<Method> found = new ArrayList<Method>();
while (clazz != null) {
    for (Method m1 : clazz.getDeclaredMethods()) {
        boolean overridden = false;

        for (Method m2 : found) {
            if (m2.getName().equals(m1.getName())
              && Arrays.deepEquals(m1.getParameterTypes(), m2
                  .getParameterTypes())) {
            overridden = true;
            break;
            }
        }
        if (!overridden) found.add(m1);
    }

    clazz = clazz.getSuperclass();
}
return found;

但是现在,如果类使用默认方法实现一些未被具体超类覆盖的接口,这些方法将逃脱上述检测。此外,现在有关于具有相同名称的默认方法的规则,也必须考虑这些规则。

问题:目前推荐的获取课程所有方法的方法是什么:

&#34; all&#34;的最常见定义应该是可以在类的实例方法中直接访问的方法,而不使用super或类名:

  • 包括在类本身中声明的public,protected,package和private方法。
  • 包括其超类的受保护方法。
  • 包含同一包的超类的包方法。
  • 包含其界面的默认方法(未覆盖/隐藏的方法,请参阅herehere)。
  • 包含具有适当辅助功能的静态方法(类和超类)。
  • 不要包含私有的超类方法。
  • 不要包含被覆盖的方法。
  • 不要包含隐藏的方法(特别是,不要包含隐藏的静态方法)。
  • 不包括合成/桥接方法。
  • 不包括Java不允许的方法,即使JVM允许这些方法。

因此,当两个布尔标志都是false时,上面的定义符合以下签名:

public Collection<Method> getAllMethods(Class clazz,
                               boolean includeAllPackageAndPrivateMethodsOfSuperclasses,
                               boolean includeOverridenAndHidden)

理想的规范答案应该允许这些布尔标记。

2 个答案:

答案 0 :(得分:13)

即使对于“之前的Java 8”场景,您的代码段也不正确。但是收集所有方法并不是一种常见的方案,因为您通常需要关于特定上下文的方法,例如:您可能想知道给定上下文哪些方法可访问,即使您考虑非public方法,也不包括所有方法。如果您真的想要所有方法,则必须记住,privatestatic方法永远不会被覆盖,并且只有在同一个{{}中声明时,才会覆盖package-private方法。 1}}。因此,过滤每个遇到的方法签名都是不正确的。

更糟糕的是,方法可能会被不同的修饰符覆盖。后者可以通过将想法保持在实际类开始并使用package获取所有Class.getMethods()方法(包括public方法)并遍历超类层次结构default来解决已经遇到的覆盖具有限制最少的访问修饰符。

作为旁注,嵌套线性搜索循环绝不是一个好主意。你很快就会遇到二次或更复杂的情况。

您可以使用以下方法收集方法:

java.lang.Object

但正如所说的那样,它可能不适合你想做的任何事情。您应首先问自己以下问题:

  • 您是否正在寻找构成API的方法(通常只有public static Set<Method> getAllMethods(Class<?> cl) { Set<Method> methods=new LinkedHashSet<>(); Collections.addAll(methods, cl.getMethods()); Map<Object,Set<Package>> types=new HashMap<>(); final Set<Package> pkgIndependent = Collections.emptySet(); for(Method m: methods) types.put(methodKey(m), pkgIndependent); for(Class<?> current=cl; current!=null; current=current.getSuperclass()) { for(Method m: current.getDeclaredMethods()) { final int mod = m.getModifiers(), access=Modifier.PUBLIC|Modifier.PROTECTED|Modifier.PRIVATE; if(!Modifier.isStatic(mod)) switch(mod&access) { case Modifier.PUBLIC: continue; default: Set<Package> pkg= types.computeIfAbsent(methodKey(m), key -> new HashSet<>()); if(pkg!=pkgIndependent && pkg.add(current.getPackage())) break; else continue; case Modifier.PROTECTED: if(types.putIfAbsent(methodKey(m), pkgIndependent)!=null) continue; // otherwise fall-through case Modifier.PRIVATE: } methods.add(m); } } return methods; } private static Object methodKey(Method m) { return Arrays.asList(m.getName(), MethodType.methodType(m.getReturnType(), m.getParameterTypes())); } public)?
  • 或者您是否希望实际看到某个protected / class上下文可以访问的方法?
  • 是否应包含package个方法?
  • 是否应包括合成/桥接方法?

以下是适用于您更具体请求的修订方法:

static

答案 1 :(得分:1)

我无法在Android环境中编译Holger的答案,因为在API级别26中添加了MethodType并且Android Studio支持Java 8语言功能的子集。除此之外,Holger的代码包含了很多lambdas和stream,我认为那些人类不可读。所以我决定编写一个更易读的代码,可以在任何Java环境中运行。但由于我没有包含旗帜,因此它不是一个理想的解决方案。

下面的代码段工作方式与调用getAllMethods(clazz, false, false)

相同
private static Collection<Method> getAllMethods(Class<?> target) {
    Class<?> clazz = target;
    Collection<MethodSignature> methodSignatures = new ArrayList<>();
    for(Method method : clazz.getDeclaredMethods()) {
        addIfAbsentAndNonSynthetic(methodSignatures, method);
    }
    for(Method method : clazz.getMethods()) {
        addIfAbsentAndNonSynthetic(methodSignatures, method);
    }
    Package pkg = clazz.getPackage();
    clazz = clazz.getSuperclass();
    while(clazz != null) {
        for(Method method : clazz.getDeclaredMethods()) {
            int modifier = method.getModifiers();
            if(Modifier.isPrivate(modifier)) {
                continue;
            }
            if(Modifier.isPublic(modifier) || Modifier.isProtected(modifier)) {
                addIfAbsentAndNonSynthetic(methodSignatures, method);
            }
            else if((pkg != null && pkg.equals(clazz.getPackage())) || (pkg == null
                    && clazz.getPackage() == null)) {
                addIfAbsentAndNonSynthetic(methodSignatures, method);
            }
        }
        clazz = clazz.getSuperclass();
    }
    Collection<Method> allMethods = new ArrayList<>(methodSignatures.size());
    for(MethodSignature methodSignature : methodSignatures) {
        allMethods.add(methodSignature.getMethod());
    }
    return allMethods;
}

private static void addIfAbsentAndNonSynthetic(Collection<MethodSignature> collection,
        Method method) {
    MethodSignature methodSignature = new MethodSignature(method);
    if(!method.isSynthetic() && !collection.contains(methodSignature)) {
        collection.add(methodSignature);
    }
}

方法声明的两个组件包括方法签名:方法的名称和参数类型。在区分方法时,编译器不考虑返回类型,因此即使它们具有不同的返回类型,也不能使用相同的签名声明两个方法。所以MethodSignature类没有对它的方法的返回类型的任何引用。

但是当你调用getDeclaredMethodsgetMethods时,可以获得具有相同名称和参数类型但具有不同返回类型的多个声明方法。这意味着编译器创建了一个合成方法,称为桥接方法。要解决此问题,请在方法上调用method.isSynthetic(),如果返回true则跳过它。由于它是一种合成方法,因此将存在具有相同签名但返回类型不同的非合成方法。

public class MethodSignature {
    private final Method mMethod;
    private final String mName;
    private final Class<?>[] mParameterTypes;

    public MethodSignature(Method method) {
        mMethod = method;
        mName = mMethod.getName();
        mParameterTypes = mMethod.getParameterTypes();
    }

    public Method getMethod() {
        return mMethod;
    }

    public String getName() {
        return mName;
    }

    public Class<?>[] getParameterTypes() {
        return mParameterTypes;
    }

    @Override
    public boolean equals(Object object) {
        if(this == object) {
            return true;
        }
        if(object == null) {
            return false;
        }
        if(!getClass().equals(object.getClass())) {
            return false;
        }
        MethodSignature obj = (MethodSignature) object;
        if(hashCode() != obj.hashCode()) {
            return false;
        }
        return mName.equals(obj.getName()) && Arrays
                .equals(mParameterTypes, obj.getParameterTypes());
    }

    @Override
    public int hashCode() {
        int hash = 11;
        hash = 37 * hash + Objects.hash(mName, Arrays.hashCode(mParameterTypes));
        return hash;
    }
}