获取类的所有方法(包括 Java 8 的继承缺省方法)的新方法是什么?

2022-09-02 04:33:52

我想获取类的所有方法,包括公共方法、受保护方法、包方法和私有方法,以及继承的方法。

记得:

  • 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;

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

问题:当前推荐的获取类的所有方法的方法是什么:

“all”的最常见定义应该是可以在类的实例方法中直接访问的方法,而无需使用类名或类名:super

  • 包括在类本身中声明的公共方法、受保护方法、包方法和私有方法。
  • 包括其超类的受保护方法。
  • 包括同一包的超类的包方法。
  • 包括其接口的默认方法(未覆盖/隐藏的方法,请参阅此处此处)。
  • 包括具有适当可访问性的静态方法(类和超类)。
  • 不要包含超类的私有方法。
  • 不要包含重写的方法。
  • 不要包含隐藏方法(在特殊情况下,不要包含隐藏的静态方法)。
  • 不要包含合成/桥接方法。
  • 不要包含 Java 不允许的方法,即使 JVM 允许这些方法。

因此,当两个布尔标志都是:false

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

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


答案 1

即使对于“在 Java 8 之前”的场景,您的代码片段也是不正确的。但是收集所有方法并不是通常的情况,因为您通常需要有关特定上下文的方法,例如,您可能想知道给定上下文可以访问哪些方法,即使您考虑非方法,也不包括所有方法。如果你真的想要所有的方法,你必须记住,方法永远不会被重写,包私有方法只有在在同一个中声明时才会被重写。因此,筛选每个遇到的方法签名是不正确的。publicprivatestaticpackage

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

顺便说一句,嵌套线性搜索循环从来都不是一个好主意。你很快就会得到一个二次或更糟糕的复杂性。

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

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()));
}

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

  • 您是否正在寻找构成API的方法(通常也是唯一的)?publicprotected
  • 或者你想真正看到某个/上下文可以访问的方法?classpackage
  • 是否应包括方法?static
  • 是否应包括合成/桥接方法?
  • 等。

以下是适合您更具体要求的修订方法:

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

    Predicate<Method> include = m -> !m.isBridge() && !m.isSynthetic() &&
         Character.isJavaIdentifierStart(m.getName().charAt(0))
      && m.getName().chars().skip(1).allMatch(Character::isJavaIdentifierPart);

    Set<Method> methods = new LinkedHashSet<>();
    Collections.addAll(methods, clazz.getMethods());
    methods.removeIf(include.negate());
    Stream.of(clazz.getDeclaredMethods()).filter(include).forEach(methods::add);

    final int access=Modifier.PUBLIC|Modifier.PROTECTED|Modifier.PRIVATE;

    Package p = clazz.getPackage();
    if(!includeAllPackageAndPrivateMethodsOfSuperclasses) {
        int pass = includeOverridenAndHidden?
            Modifier.PUBLIC|Modifier.PROTECTED: Modifier.PROTECTED;
        include = include.and(m -> { int mod = m.getModifiers();
            return (mod&pass)!=0
                || (mod&access)==0 && m.getDeclaringClass().getPackage()==p;
        });
    }
    if(!includeOverridenAndHidden) {
        Map<Object,Set<Package>> types = new HashMap<>();
        final Set<Package> pkgIndependent = Collections.emptySet();
        for(Method m: methods) {
            int acc=m.getModifiers()&access;
            if(acc==Modifier.PRIVATE) continue;
            if(acc!=0) types.put(methodKey(m), pkgIndependent);
            else types.computeIfAbsent(methodKey(m),x->new HashSet<>()).add(p);
        }
        include = include.and(m -> { int acc = m.getModifiers()&access;
            return acc!=0? acc==Modifier.PRIVATE
                    || types.putIfAbsent(methodKey(m), pkgIndependent)==null:
                noPkgOverride(m, types, pkgIndependent);
        });
    }
    for(clazz=clazz.getSuperclass(); clazz!=null; clazz=clazz.getSuperclass())
        Stream.of(clazz.getDeclaredMethods()).filter(include).forEach(methods::add);
    return methods;
}
static boolean noPkgOverride(
        Method m, Map<Object,Set<Package>> types, Set<Package> pkgIndependent) {
    Set<Package> pkg = types.computeIfAbsent(methodKey(m), key -> new HashSet<>());
    return pkg!=pkgIndependent && pkg.add(m.getDeclaringClass().getPackage());
}
private static Object methodKey(Method m) {
    return Arrays.asList(m.getName(),
        MethodType.methodType(m.getReturnType(), m.getParameterTypes()));
}

答案 2

我无法在Android环境中编译Holger的答案,因为它是在API级别26中添加的,并且Android Studio支持Java 8语言功能的子集。除此之外,Holger的代码包含许多lambda和流,我认为这些是人类不可读的。所以我决定编写一个在任何Java环境中都工作的更具可读性的代码。但这不是一个理想的解决方案,因为我没有包括标志。MethodType

下面的片段的工作原理与您调用 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

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

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

推荐