如何按类型签名搜索 Java API 方法?

是否有任何开源工具支持按参数类型和返回类型集搜索 Java 方法?

例如,假设我正在寻找一种方法来为整数数组生成哈希代码。我搜索一个采用int[]参数并返回int的方法:

int[] -> int

屈服

java.util.Arrays#hashCode(int[])
...

或者我可能想找到一个方法,它需要一个字符串,和字符来替换,以及要替换它的字符。所以我搜索一个匹配的方法:

String, char, char -> String

屈服

java.lang.String#replace(char, char)
...

理想情况下,我想要一个与Haskell的Hoogle等效的Java,它支持通过类型签名搜索函数。

我希望该工具能够:

  • 忽略参数的顺序
  • 包括接受“更广泛”类型作为参数的方法(例如超类)
  • 包括返回“较窄”类型作为返回值的方法(例如子类)
  • 将'self'值视为实例方法的参数(例如,'String -> int'将包括String#hashCode)

我知道许多IDE支持搜索采用或返回给定类型的方法,但尚未看到通过参数类型返回类型的组合来缩小搜索范围的工具。


答案 1

解决方案注释:

  1. 忽略顺序,测试所有参数的排列,直到找到给定函数的匹配项
  2. 基元类型是可互换的(例如Integer.class = Integer.TYPE)
  3. 检查更广泛的参数
  4. 返回类型可以更窄
  5. Self 被视为方法的第一个参数findMethod

这是程序的输出:

int[] -> Integer
    public native int java.lang.Object.hashCode()
    public static native int java.lang.reflect.Array.getLength(java.lang.Object) throws java.lang.IllegalArgumentException
    public static int java.util.Arrays.hashCode(int[])
    public static native int java.lang.System.identityHashCode(java.lang.Object)

String, Character, Character -> String
    public java.lang.String java.lang.String.replace(char,char)

String -> Integer
    public int java.lang.String.hashCode()
    public int java.lang.String.length()
    public static native int java.lang.reflect.Array.getLength(java.lang.Object) throws java.lang.IllegalArgumentException
    public static java.lang.Integer java.lang.Integer.decode(java.lang.String) throws java.lang.NumberFormatException
    public static java.lang.Integer java.lang.Integer.valueOf(java.lang.String) throws java.lang.NumberFormatException
    public static int java.lang.Integer.parseInt(java.lang.String) throws java.lang.NumberFormatException
    public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String)
    public static native int java.lang.System.identityHashCode(java.lang.Object)

List -> Void
    public abstract void java.util.List.clear()
    public static void java.util.concurrent.locks.LockSupport.park(java.lang.Object)
    public static void java.util.Collections.reverse(java.util.List)
    public static void java.util.Collections.shuffle(java.util.List)
    public static void java.util.Collections.sort(java.util.List)

代码:

public class MethodMatcher {

    public static void main(String... args) throws Exception {

        // where to load some classes from (could be a list of classes to 
        // search from)..
        //        String pathToJar = "/usr/lib/jvm/java-6-sun-1.6.0.22/jre/lib/rt.jar";
        String pathToJar = "C:\\Program Files\\Java\\jdk1.6.0_20\\jre\\lib\\rt.jar";

        MethodMatcher m = new MethodMatcher(pathToJar, 
                "java.io", "java.lang", "java.math", "java.net", 
                "java.nio", "java.text", "java.util");

        // print some examples
        m.printExampleSearch(Integer.class, new int[0].getClass());
        m.printExampleSearch(String.class, String.class, Character.class, Character.class);
        m.printExampleSearch(Integer.class, String.class);
        m.printExampleSearch(Void.class, List.class);
    }

    public void printExampleSearch(Class<?> returnType, Class<?>... arguments) {

        for (int i = 0; i < arguments.length; i++)
            System.out.print((i == 0 ? "":", ") + arguments[i].getSimpleName());

        System.out.println(" -> " + returnType.getSimpleName());

        Set<Method> methods = findMethods(returnType, arguments);

        for (Method method : methods)
            System.out.println("\t" + method);

        System.out.println();
    }



    private final List<MethodFinder> klasses;

    public MethodMatcher(String jarFile, String... allowedPackages) 
    throws IOException, ClassNotFoundException {

        klasses = loadClasses(jarFile, allowedPackages);
    }

    /**
     * Finds a set of methods
     * @param returnType the return type
     * @param arguments the arguments (in any order)
     * @return a set of methods
     */
    public Set<Method> findMethods(Class<?> returnType,
            Class<?>... arguments) {

        Set<Method> methods = new LinkedHashSet<Method>();

        if (arguments.length > 0) {
            MethodFinder instance = new MethodFinder(arguments[0]);

            Class<?>[] rest = new Class<?>[arguments.length - 1];
            System.arraycopy(arguments, 1, rest, 0, rest.length);

            methods.addAll(instance.findInstanceMethods(returnType, rest));
        }
        else {
            for (MethodFinder k : klasses)
                methods.addAll(k.findInstanceMethods(returnType, arguments));
        }

        for (MethodFinder k : klasses)
            methods.addAll(k.findStaticMethods(returnType, arguments));

        return methods;
    }

    /**
     * A method finder class
     */
    static class MethodFinder {

        public final Class<?> klass;

        /**
         * Constructs the method finder (doh)
         * @param klass the class
         */
        public MethodFinder(Class<?> klass) {
            this.klass = klass;
        }

        /**
         * Finds instance method matches
         * @param returnType the return type
         * @param arguments the arguments (in any order)
         * @return
         */
        public List<Method> findInstanceMethods(Class<?> returnType, 
                Class<?>... arguments) {

            List<Method> matches = new LinkedList<Method>();

            for (Method method : klass.getMethods()) {
                if ((method.getModifiers() & Modifier.STATIC) == 0) 
                    if (testMethod(method, returnType, arguments))
                        matches.add(method);
            }

            return matches;        
        }

        /**
         * Finds static method matches
         * @param returnType the return type
         * @param arguments the arguments (in any order)
         * @return
         */
        public List<Method> findStaticMethods(Class<?> returnType,
                Class<?>... arguments) {

            List<Method> matches = new LinkedList<Method>();

            for (Method method : klass.getMethods()) 
                if ((method.getModifiers() & Modifier.STATIC) != 0) 
                    if (testMethod(method, returnType, arguments))
                        matches.add(method);

            return matches;        
        }

        /**
         * Tests a method if it is a match
         * @param method the method to test
         * @param returnType the return type
         * @param arguments the arguments (in any order)
         * @return true if it matches
         */
        private boolean testMethod(Method method, 
                Class<?> returnType, 
                Class<?>... arguments) {

            boolean returnTypeIsOk = false;
            for (Class<?> ic : getInterchangable(returnType))
                if (ic.isAssignableFrom(method.getReturnType()))
                    returnTypeIsOk = true;

            if (!returnTypeIsOk)
                return false;

            Class<?>[] methodArguments = method.getParameterTypes();

            if (methodArguments.length != arguments.length)
                return false;

            if (methodArguments.length == 0) {
                return true;
            }
            else {
                Permutations permutations = new Permutations(arguments);

                outer: for (Class<?>[] permutation : permutations) {
                    for (int i = 0; i < methodArguments.length; i++) {

                        boolean canAssign = false;
                        for (Class<?> ic : getInterchangable(permutation[i])) 
                            if (methodArguments[i].isAssignableFrom(ic))
                                canAssign = true;

                        if (!canAssign)
                            continue outer;
                    }
                    return true;
                }

                return false;
            }
        }

        /**
         * Returns the autoboxing types
         * @param type the type to autobox :)
         * @return a list of types that it could be
         */
        private static Class<?>[] getInterchangable(Class<?> type) {

            if (type == Boolean.class || type == Boolean.TYPE)
                return new Class<?>[] { Boolean.class, Boolean.TYPE };
            if (type == Character.class || type == Character.TYPE)
                return new Class<?>[] { Character.class, Character.TYPE };
            if (type == Short.class || type == Short.TYPE)
                return new Class<?>[] { Short.class, Short.TYPE };
            if (type == Integer.class || type == Integer.TYPE)
                return new Class<?>[] { Integer.class, Integer.TYPE };
            if (type == Float.class || type == Float.TYPE)
                return new Class<?>[] { Float.class, Float.TYPE };
            if (type == Double.class || type == Double.TYPE)
                return new Class<?>[] { Double.class, Double.TYPE };
            if (type == Void.class || type == Void.TYPE)
                return new Class<?>[] { Void.class, Void.TYPE };

            return new Class<?>[] { type };
        }


        /**
         * Creates a permutation list of all different combinations
         */
        @SuppressWarnings("serial")
        private class Permutations extends LinkedList<Class<?>[]> {

            /**
             * Creates a permutation list
             * @param list the list to be permutated
             */
            public Permutations(Class<?>[] list) {
                permutate(new LinkedList<Class<?>>(Arrays.asList(list)),
                        new LinkedList<Class<?>>());
            }

            // ugly, there is better ways of doing this...
            private void permutate(List<Class<?>> tail, List<Class<?>> choosen) {

                if (tail.isEmpty()) {
                    add(choosen.toArray(new Class<?>[0]));
                    return;
                }

                ListIterator<Class<?>> it = tail.listIterator();

                while (it.hasNext()) {

                    Class<?> current = it.next();

                    choosen.add(current);
                    it.remove();

                    permutate(new LinkedList<Class<?>>(tail), choosen);

                    choosen.remove(current);
                    it.add(current);
                }
            }
        }
    }

    /**
     * A hack to read some classes from some allowed packages
     * @param jarFile the jar file to read from
     * @param allowedPackages the allowed packages
     * @return a list of MethodFinders
     * @throws IOException
     * @throws ClassNotFoundException
     */
    private static List<MethodFinder> loadClasses(
            String jarFile, 
            String... allowedPackages) throws IOException, ClassNotFoundException {

        List<MethodFinder> klasses = new LinkedList<MethodFinder>();

        JarFile file = new JarFile(jarFile);
        try {
            Enumeration<JarEntry> enumerator = file.entries();

            while (enumerator.hasMoreElements()) {

                String name = enumerator.nextElement().getName();

                if (!name.endsWith(".class")) 
                    continue;

                name = name.substring(0, name.length() - 6).replace('/', '.');

                boolean allowed = false;
                for (String pkg : allowedPackages)
                    allowed |= name.startsWith(pkg);

                if (allowed)
                    klasses.add(new MethodFinder(Class.forName(name)));
            }
        } 
        finally {
            if (file != null)
                file.close();
        }

        return klasses;
    }
}

答案 2

我刚刚在Eclipse(Helios)上尝试了这个,它支持这个。按 Ctrl-H,转到 Java 选项卡,选择“搜索 ->方法”单选按钮,“限制为 ->声明”。在搜索字符串中,输入,它将返回许多方法的结果,这些方法采用两个 int 并返回一个 int。*(int, int) int