我想获得一个类的所有方法,包括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;
但现在,如果该类实现了一些带有默认方法的接口,而这些方法没有被具体的超类覆盖,那么这些方法将逃脱上述检测。此外,现在有一些关于同名默认方法的规则,这些规则也必须考虑在内。
问题:当前推荐的获取类的所有方法的方法是什么:
"all"最常见的定义应该是可以在类的实例方法内部直接访问的方法,而无需使用super
或类名:
- 包括在类本身中声明的public、protected、package和private方法
- 包括其超类的受保护方法
- 包括其同一包的超类的包方法
- 包括其接口的默认方法(未覆盖/隐藏的方法,请参阅此处和此处)
- 包括具有适当可访问性的静态方法(类和超类)
- 不要包含超类的私有方法
- 不要包括重写的方法
- 不包括隐藏方法(特别是,不包括隐藏的静态方法)
- 不包括合成/桥接方法
- 不要包含Java不允许的方法,即使JVM允许
因此,当两个布尔标志都是false
:时,上述定义适用于以下签名
public Collection<Method> getAllMethods(Class clazz,
boolean includeAllPackageAndPrivateMethodsOfSuperclasses,
boolean includeOverridenAndHidden)
理想的规范答案应该允许这些布尔标志。
即使对于"在Java 8之前"的场景,您的代码片段也是不正确的。但是,收集所有方法并不是一种常见的情况,因为您通常需要与特定上下文相关的方法,例如,您可能想知道对于给定上下文,哪些方法是可访问的,而给定上下文并不包括所有方法,即使您考虑非public
方法。如果您真的想要所有方法,那么您必须记住,private
和static
方法永远不会被重写,而包私有方法只有在同一package
中声明时才会被重写。因此,过滤每个遇到的方法签名是不正确的。
更糟糕的是,方法可能会被不同的修饰符覆盖。后者可以通过保持从实际类开始的想法来解决,并使用Class.getMethods()
来获得包括default
方法在内的所有public
方法,并向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的方法(通常仅限于
public
和protected
) - 或者,您想实际查看某个
class
/package
上下文可访问的方法吗 - 是否应包括
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()));
}
我无法在Android环境中编译Holger的答案,因为MethodType
添加在API 26级中,并且Android Studio支持Java 8语言功能的子集。除此之外,Holger的代码包含了太多的Lambda和流,我认为这些都是人类无法读取的。因此,我决定编写一个可读性更强的代码,适用于任何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
类不包含对其方法的返回类型的任何引用。
但是,当调用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;
}
}