获取 class 的所有方法(包括继承的 Java 8 的默认方法)的新方法是什么?
What is the new way of getting all methods of a class, including inherited default methods of Java 8?
我想获取class的所有方法,包括public、protected、package和private方法,包括继承的方法。
记住:
Class.getDeclaredMethods()
获得 public、受保护、打包和私有
方法,但 不包括继承的方法。
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;
但是现在,如果 class 实现了一些带有 默认方法 的接口,这些方法没有被具体的 superclasses 覆盖,这些方法将逃脱以上检测。此外,现在有关于同名默认方法的规则,这些规则也必须考虑在内。
问题:目前推荐的获取class的所有方法的方法是什么:
"all"最常见的定义应该是在class的实例方法内部可以直接访问的方法,而不需要使用super
或class 姓名:
- 包括 public、受保护、包和私有方法在 class 本身中声明。
- 包括其超classes 的受保护方法。
- 包括其超class相同包的包方法。
- 包括其接口的默认方法(那些不是 overridden/hidden,参见 here and here)。
- 包括具有适当可访问性的静态方法(class 和 superclasses)。
- 不包含 superclasses 的私有方法。
- 不要包含重写的方法。
- 不包含隐藏方法(特别是不包含隐藏的静态方法)。
- 不包括 synthetic/bridge 方法。
- 不要包含 Java 不允许的方法,即使 JVM 允许它们。
因此,当两个布尔标志均为 false
时,上述定义符合以下签名:
public Collection<Method> getAllMethods(Class clazz,
boolean includeAllPackageAndPrivateMethodsOfSuperclasses,
boolean includeOverridenAndHidden)
理想的、规范的答案应该允许这些布尔标志。
即使对于“Java 8 之前”的场景,您的代码片段也不正确。但是收集 all 方法并不是一种常见的情况,因为您通常需要关于特定上下文的方法,例如您可能想知道对于给定的上下文哪些方法是 可访问的,即使您考虑了非 public
方法,它也不包括所有方法。如果你真的想要 all 方法,你必须记住 private
和 static
方法永远不会被覆盖,包私有方法只有在声明时才被覆盖相同 package
。所以过滤每一个遇到的方法签名是不正确的。
更糟糕的是,方法可能会被不同的修饰符覆盖。后者可以通过保持从实际class开始的想法来解决,并使用Class.getMethods()
获取所有public
方法包括default
方法并遍历superclass 层次结构指向 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
个方法?
- 是否应包含 synthetic/bridge 方法?
- 等等
这是根据您更具体的要求修改后的方法:
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);
}
}
方法声明的两个组成部分包括方法签名:方法名称和参数类型。编译器在区分方法时不考虑 return 类型,因此即使它们具有不同的 return 类型,也不能声明具有相同签名的两个方法。所以 MethodSignature
class 不持有任何对其方法的 return 类型的引用。
但是当您调用 getDeclaredMethods
或 getMethods
时,可能会得到多个具有相同名称和参数类型但不同 return 类型的已声明方法。这意味着编译器创建了一个合成方法,称为桥接方法。要解决此问题,请在该方法上调用 method.isSynthetic()
,如果它 return 为真,则跳过它。由于它是一种合成方法,因此会有一个具有相同签名但不同 return 类型的非合成方法。
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;
}
}
我想获取class的所有方法,包括public、protected、package和private方法,包括继承的方法。
记住:
Class.getDeclaredMethods()
获得 public、受保护、打包和私有 方法,但 不包括继承的方法。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;
但是现在,如果 class 实现了一些带有 默认方法 的接口,这些方法没有被具体的 superclasses 覆盖,这些方法将逃脱以上检测。此外,现在有关于同名默认方法的规则,这些规则也必须考虑在内。
问题:目前推荐的获取class的所有方法的方法是什么:
"all"最常见的定义应该是在class的实例方法内部可以直接访问的方法,而不需要使用super
或class 姓名:
- 包括 public、受保护、包和私有方法在 class 本身中声明。
- 包括其超classes 的受保护方法。
- 包括其超class相同包的包方法。
- 包括其接口的默认方法(那些不是 overridden/hidden,参见 here and here)。
- 包括具有适当可访问性的静态方法(class 和 superclasses)。
- 不包含 superclasses 的私有方法。
- 不要包含重写的方法。
- 不包含隐藏方法(特别是不包含隐藏的静态方法)。
- 不包括 synthetic/bridge 方法。
- 不要包含 Java 不允许的方法,即使 JVM 允许它们。
因此,当两个布尔标志均为 false
时,上述定义符合以下签名:
public Collection<Method> getAllMethods(Class clazz,
boolean includeAllPackageAndPrivateMethodsOfSuperclasses,
boolean includeOverridenAndHidden)
理想的、规范的答案应该允许这些布尔标志。
即使对于“Java 8 之前”的场景,您的代码片段也不正确。但是收集 all 方法并不是一种常见的情况,因为您通常需要关于特定上下文的方法,例如您可能想知道对于给定的上下文哪些方法是 可访问的,即使您考虑了非 public
方法,它也不包括所有方法。如果你真的想要 all 方法,你必须记住 private
和 static
方法永远不会被覆盖,包私有方法只有在声明时才被覆盖相同 package
。所以过滤每一个遇到的方法签名是不正确的。
更糟糕的是,方法可能会被不同的修饰符覆盖。后者可以通过保持从实际class开始的想法来解决,并使用Class.getMethods()
获取所有public
方法包括default
方法并遍历superclass 层次结构指向 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
个方法? - 是否应包含 synthetic/bridge 方法?
- 等等
这是根据您更具体的要求修改后的方法:
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);
}
}
方法声明的两个组成部分包括方法签名:方法名称和参数类型。编译器在区分方法时不考虑 return 类型,因此即使它们具有不同的 return 类型,也不能声明具有相同签名的两个方法。所以 MethodSignature
class 不持有任何对其方法的 return 类型的引用。
但是当您调用 getDeclaredMethods
或 getMethods
时,可能会得到多个具有相同名称和参数类型但不同 return 类型的已声明方法。这意味着编译器创建了一个合成方法,称为桥接方法。要解决此问题,请在该方法上调用 method.isSynthetic()
,如果它 return 为真,则跳过它。由于它是一种合成方法,因此会有一个具有相同签名但不同 return 类型的非合成方法。
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;
}
}