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

后端 未结 2 1570
清歌不尽
清歌不尽 2021-02-07 02:21

I want to get all methods of a class, including public, protected, package and private methods, and including inherited methods.

Remember:

  • Class.ge
2条回答
  •  暗喜
    暗喜 (楼主)
    2021-02-07 02:26

    Even for the “Before Java 8” scenario, your code snippet isn’t correct. But collecting all methods isn’t a usual scenario though, as you normally need methods regarding a certain context, e.g. you might want to know which methods are accessible for a given context, which doesn’t include all methods, even if you consider non-public methods. If you really want all methods, you have to recall that private and static methods are never overridden and package-private methods are only overridden when being declared within the same package. So it’s not correct to filter every encountered method signature.

    What makes matters worse is that methods might get overridden with different modifiers. The latter can be solved by keeping the idea to start at the actual class and use Class.getMethods() to get all public method including default methods and traverse the superclass hierarchy towards java.lang.Object so the already encountered overrides have the least restrictive access modifiers.

    As a side note, nesting linear search loops is never a good idea. You’ll soon end up with a quadratic or worse complexity.

    You may collect methods using the following method:

    public static Set getAllMethods(Class cl) {
        Set methods=new LinkedHashSet<>();
        Collections.addAll(methods, cl.getMethods());
        Map> types=new HashMap<>();
        final Set 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 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()));
    }
    

    But as said, it might be the case that it isn’t suitable for whatever you want to do. You should ask yourself the following questions first:

    • Are you looking for methods that make up the API (that’s usually public and protected only)?
    • Or do you want to actually see methods accessible for a certain class/package context?
    • Shall static methods be included?
    • Shall synthetic/bridge methods be included?
    • etc.

    Here is the revised method adapted to your more specific request:

    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> types = new HashMap<>();
            final Set 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> types, Set pkgIndependent) {
        Set 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()));
    }
    

提交回复
热议问题