如何通过类型签名搜索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支持搜索获取或返回给定类型的方法,但尚未看到通过参数类型和返回类型的组合来缩小搜索范围的工具。
解决方案评论
- 忽略顺序 ,测试参数的所有排列直到找到给定函数的匹配
- 原始类型是可互换的(例如
Integer.class
=Integer.TYPE
) - 检查更宽的参数
- 返回类型可以更窄
- 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()然后调用我的方法。 而已!