获取类的所有方法的新方法是什么,包括Java 8的inheritance默认方法?
我想获取类的所有方法,包括public,protected,package和private方法,以及包括inheritance的方法。
记得:
-
Class.getDeclaredMethods()
获取public,protected,package和private方法, 但不包括inheritance的方法。 -
Class.getMethods
获取inheritance的方法, 但只获取公共方法。
在Java 8之前,我们可以做一些事情:
Collection found = new ArrayList(); 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
类或类名:
- 包括在类本身中声明的public,protected,package和private方法。
- 包括其超类的受保护方法。
- 包括同一包的超类的包方法。
- 包括其接口的默认方法(未覆盖/隐藏的方法,请参见此处和此处 )。
- 包含具有适当可访问性的静态方法(类和超类)。
- 不要包含私有的超类方法。
- 不要包含重写方法。
- 不要包含隐藏方法(特别是,不包括隐藏的静态方法)。
- 不包括合成/桥接方法。
- 不包括Java不允许的方法,即使JVM允许它们也是如此。
因此,当两个布尔标志都为false
时,上面的定义符合以下签名:
public Collection getAllMethods(Class clazz, boolean includeAllPackageAndPrivateMethodsOfSuperclasses, boolean includeOverridenAndHidden)
理想的规范答案应该允许这些布尔标志。
即使对于“之前的Java 8”场景,您的代码段也不正确。 但是收集所有方法并不是一种常见的方案,因为您通常需要关于某个上下文的方法,例如,您可能想知道哪些方法对于给定的上下文是可访问的,哪些方法不包括所有方法,即使您考虑非- public
方法。 如果您真的想要所有方法,则必须记住private
和static
方法永远不会被覆盖,并且只有在同一个package
声明时才会覆盖包私有方法。 因此,过滤每个遇到的方法签名是不正确的。
更糟糕的是,方法可能会被不同的修饰符覆盖。 后者可以通过保持想法从实际类开始并使用Class.getMethods()
获取所有public
方法(包括default
方法)并遍历java.lang.Object
的超类层次结构来解决,因此已经遇到的覆盖具有最少的限制访问修饰符。
作为旁注,嵌套线性搜索循环永远不是一个好主意。 你很快就会遇到二次或更复杂的问题。
您可以使用以下方法收集方法:
public static Set getAllMethods(Class> cl) { Set methods=new LinkedHashSet<>(); Collections.addAll(methods, cl.getMethods()); Map
但正如所说的,可能是它不适合你想做的任何事情。 您应首先问自己以下问题:
- 您是否正在寻找构成API的方法(通常是
public
并且仅protected
)? - 或者您是否希望实际看到某些
class
/package
上下文可访问的方法? - 是否应包括
static
方法? - 是否应包括合成/桥接方法?
- 等等
以下是适用于您更具体要求的修订方法:
public static Collection getAllMethods(Class clazz, boolean includeAllPackageAndPrivateMethodsOfSuperclasses, boolean includeOverridenAndHidden) { Predicate include = m -> !m.isBridge() && !m.isSynthetic() && Character.isJavaIdentifierStart(m.getName().charAt(0)) && m.getName().chars().skip(1).allMatch(Character::isJavaIdentifierPart); Set 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
我无法在Android环境中编译Holger的答案,因为在API级别26中添加了MethodType
,并且Android Studio支持Java 8语言function的子集。 除此之外,Holger的代码包含了很多lambdas和stream,我认为那些人类不可读。 所以我决定编写一个更易读的代码,可以在任何Java环境中运行。 但它并不是一个理想的解决方案,因为我没有包含旗帜。
片段下方的工作方式与调用getAllMethods(clazz, false, false)
private static Collection getAllMethods(Class> target) { Class> clazz = target; Collection 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 allMethods = new ArrayList<>(methodSignatures.size()); for(MethodSignature methodSignature : methodSignatures) { allMethods.add(methodSignature.getMethod()); } return allMethods; } private static void addIfAbsentAndNonSynthetic(Collection collection, Method method) { MethodSignature methodSignature = new MethodSignature(method); if(!method.isSynthetic() && !collection.contains(methodSignature)) { collection.add(methodSignature); } }
方法声明的两个组件包括方法签名:方法的名称和参数类型。 在区分方法时编译器不考虑返回类型,因此即使它们具有不同的返回类型,也不能声明具有相同签名的两个方法。 因此, MethodSignature
类不包含对其方法的返回类型的任何引用。
但是当您调用getDeclaredMethods
或getMethods
时,可以获得具有相同名称和参数类型但具有不同返回类型的多个声明的方法。 这意味着编译器创建了一个合成方法,称为桥接方法。 要解决此问题,请在方法上调用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; } }