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

标签 java inheritance reflection java-8

我想获取一个类的所有方法,包括public、protected、package和private方法,也包括继承的方法。

记住:

  • Class.getDeclaredMethods() 获取 public、protected、package 和 private 方法,不包括继承的方法。
  • Class.getMethods 获取继承方法,仅限公共(public)方法。

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

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

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

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

  • 包括在类本身中声明的公共(public)、 protected 、包和私有(private)方法。
  • 包括其父类(super class)的 protected 方法。
  • 包括同一包的父类(super class)的包方法。
  • 包括其接口(interface)的默认方法(那些未被覆盖/隐藏的方法,参见 herehere)。
  • 包括具有适当可访问性的静态方法(类和父类(super class))。
  • 不要包含父类(super class)的私有(private)方法。
  • 不要包含重写的方法。
  • 不包含隐藏方法(特别是不包含隐藏静态方法)。
  • 不包括合成/桥接方法。
  • 不要包含 Java 不允许的方法,即使 JVM 允许它们也是如此。

因此,当两个 boolean 标志均为 false 时,上述定义符合以下签名:

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

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

最佳答案

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

更糟糕的是,方法可能会被不同的修饰符覆盖。后者可以通过保持从实际类开始的想法来解决,并使用 Class.getMethods() 获取所有 public 方法,包括 default方法并将父类(super class)层次结构遍历到 java.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)?
  • 或者您是否想实际查看特定/上下文可访问的方法?
  • 是否应包含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()));
}

关于java - 获取类的所有方法的新方法是什么,包括 Java 8 的继承默认方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28400408/

相关文章:

Java:将文本打印到控制台或文本文件的优雅方式

javascript - 了解 JS 中的原型(prototype)继承方法

java - 使用反射动态创建和分配对象

Java : loop on all the classes in the classpath

.net - 如何确定 MemberInfo 实例的可访问性?

java - 在改造 1.9.0 中调用 api 时出错

java - 如何创建列表中的数据并将其插入到 Hibernate 查询中

java - 尝试创建一种在 Android Java 中格式化十六进制字符串的方法

C++ 基本继承概念

python - 在继承的情况下为 kwargs 输入 Mypy