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

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

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

int[] -> int 

生产

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

或者我可能想要找到一个方法,它接受一个字符串,并替换字符,并将字符替换为。 所以我搜索一个匹配的方法:

 String, char, char -> String 

生产

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

理想情况下,我喜欢Java等效于Haskell的Hoogle ,它支持按类型签名搜索function。

我希望该工具:

  • 忽略参数的顺序
  • 包括接受’更宽’类型作为参数的方法(例如超类)
  • 包含返回’narrower’类型作为返回值的方法(例如子类)
  • 将’self’值作为实例方法的参数处理(例如’String – > int’将包含String#hashCode)

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

解决方案评论

  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 methods = findMethods(returnType, arguments); for (Method method : methods) System.out.println("\t" + method); System.out.println(); } private final List 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 findMethods(Class returnType, Class... arguments) { Set methods = new LinkedHashSet(); 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 findInstanceMethods(Class returnType, Class... arguments) { List matches = new LinkedList(); 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 findStaticMethods(Class returnType, Class... arguments) { List matches = new LinkedList(); 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[]> { /** * Creates a permutation list * @param list the list to be permutated */ public Permutations(Class[] list) { permutate(new LinkedList>(Arrays.asList(list)), new LinkedList>()); } // ugly, there is better ways of doing this... private void permutate(List> tail, List> choosen) { if (tail.isEmpty()) { add(choosen.toArray(new Class[0])); return; } ListIterator> it = tail.listIterator(); while (it.hasNext()) { Class current = it.next(); choosen.add(current); it.remove(); permutate(new LinkedList>(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 loadClasses( String jarFile, String... allowedPackages) throws IOException, ClassNotFoundException { List klasses = new LinkedList(); JarFile file = new JarFile(jarFile); try { Enumeration 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; } } 

我刚刚在Eclipse(Helios)上试过这个,它支持这个。 按Ctrl-H,转到Java选项卡,选择搜索 – >方法单选按钮,限制到 – >声明。 在搜索字符串中输入*(int, int) int ,它将返回许多带有两个整数并返回int的方法结果。

非常好的问题,虽然我不明白为什么你需要这样的工具。 我很遗憾地说,但似乎实现这样的工具比写这篇文章花费的时间更少。 这是我刚刚实现的代码。 花了182秒。 它是一个静态方法,它接受类,返回类型和参数,并返回与签名匹配的类的所有方法。

 import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class ClassUtil { public static Method[] getMethodsBySignature(Class clazz, Class returnType, Class... args) { List result = new ArrayList(); for (Method m : clazz.getDeclaredMethods()) { if (m.getReturnType().equals(returnType)) { Class[] params = m.getParameterTypes(); if (Arrays.equals(params, args)) { result.add(m); } } } return result.toArray(new Method[result.size()]); } } 

您可以再花5到10分钟来实现打开jar的方法,遍历条目,调用Class.forName()然后调用我的方法。 而已!