簡體   English   中英

獲取類的所有方法的新方法是什么,包括Java 8的繼承默認方法?

[英]What is the new way of getting all methods of a class, including inherited default methods of Java 8?

我想獲取類的所有方法,包括public,protected,package和private方法,以及包括繼承的方法。

記得:

  • Class.getDeclaredMethods()獲取public,protected,package和private方法, 不包括繼承的方法。
  • 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方法。 如果您真的想要所有方法,則必須記住, privatestatic方法永遠不會被覆蓋,並且只有在同一個package聲明時才會覆蓋包私有方法。 因此,過濾每個遇到的方法簽名是不正確的。

更糟糕的是,方法可能會被不同的修飾符覆蓋。 后者可以通過保持想法從實際類開始並使用Class.getMethods()來獲取所有public方法(包括default方法)並遍歷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的答案,因為在API級別26中添加了MethodType ,並且Android Studio支持Java 8語言功能的子集。 除此之外,Holger的代碼包含了很多lambdas和stream,我認為那些人類不可讀。 所以我決定編寫一個更易讀的代碼,可以在任何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類不包含對其方法的返回類型的任何引用。

但是當您調用getDeclaredMethodsgetMethods時,可以獲得具有相同名稱和參數類型但具有不同返回類型的多個聲明方法。 這意味着編譯器創建了一個合成方法,稱為橋接方法。 要解決此問題,請在方法上調用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;
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM