我正在制作一个个人项目,希望这个项目能够成长为一个庞大而可怕的东西......
我想派生一个字符串引用的类,即使它不是完全限定的。我想尽可能地重现JVM的行为。例如,在代码中我可以引用已经导入或在同一包中的不合格的东西。我明白这可能不完全可能,但我想试一试。我目前的代码有以下问题:
ClasspathHelper.forClassLoader()
和ClasspathHelper.forJavaClassPath()
,但我仍然可能会遗漏一些。 更新我正在加入四个不同的类集,这些集合似乎提供了良好的覆盖率。http://docs.oracle.com/javase/7/docs/api/overview-summary.html
似乎是目前为止的最佳选择。显然我也可以编写一个doclet,它将以机器可读的形式输出javadoc。还有一个吗?还有其他问题吗?这是当前的代码:
/**
* Attempts to return the class specified by a string even if the it is not
* fully qualified. It does this by going through all the classes there are.
* Note: You may specify arrays in normal declaration form, e.g. myArray[][].
*
* @param classString The string rep of the class/type
* @return The class I think it refers to
*/
private static Class<?> getClassOfString(String classString) {
classString = convertTypeToCanonicalForm(classString);
Matcher matcher = Pattern.compile("(\\[+(\\w))?((\\w+(\\.\\w+)*);?)?").matcher(classString);
matcher.find();
String arrayPrefix = matcher.group(2);
String className = matcher.group(4);
try {
if (arrayPrefix == null || arrayPrefix.equals("L")) {
String classFound = null;
for (String clazz : getSetOfAllClasses()) {
if (clazz != null) {
if (clazz.matches("(.*\\.)?"+Pattern.quote(className)))
if (classFound == null)
classFound = clazz;
else
throw new RuntimeException("Class name '" + className +
"' is ambiguous: " + classFound + " vs " + clazz);
}
}
if (classFound != null) {
classString = classString.replaceAll(Pattern.quote(className), classFound);
return Class.forName(classString);
}
}
} catch (ClassNotFoundException e) {
throw new RuntimeException();
}
if (arrayPrefix == null)
if (className == "boolean") return(boolean.class); // primitive types
else if (className == "byte") return(byte.class);
else if (className == "char") return(char.class);
else if (className == "double") return(double.class);
else if (className == "float") return(float.class);
else if (className == "int") return(int.class);
else if (className == "long") return(long.class);
else if (className == "short") return(short.class);
else if (className == "void") return(void.class);
// hack for java.* types-- look 'em up
if (className != null) {
String prefixFound = null;
for (String prefix : javaTypes.keySet())
for (String type : javaTypes.get(prefix))
if ((prefix+"."+type).matches(".*" + className))
if (prefixFound == null)
prefixFound = prefix;
else
throw new RuntimeException("Class name '" + className +
"' is ambiguous: java." + prefixFound + "." + className +
" vs java." + prefix + "." + className);
if (prefixFound == null) prefixFound = "util"; // temp hack
classString = classString.replaceAll(Pattern.quote(className),
"java." + prefixFound + "." + className);
}
try {
return Class.forName(classString);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class '" + className +
"' is unknown or somewhere in java.* that I don't know about");
}
}
/**
* Converts the type from standard declaration form to canonical internal form
* if necessary. Practically this just means doing array translation.
*
* @param type
* @return
*/
private static String convertTypeToCanonicalForm(String type) {
Matcher matcher = Pattern.compile("^(\\w+(\\.\\w+)*)((\\[\\])+)$").matcher(type);
if (matcher.find()) {
String typeTemp = matcher.group(1);
if (typeTemp.equals("boolean")) typeTemp = "Z"; // primitive typeTemps
else if (typeTemp.equals("byte")) typeTemp = "B";
else if (typeTemp.equals("char")) typeTemp = "C";
else if (typeTemp.equals("double")) typeTemp = "D";
else if (typeTemp.equals("float")) typeTemp = "F";
else if (typeTemp.equals("int")) typeTemp = "I";
else if (typeTemp.equals("long")) typeTemp = "J";
else if (typeTemp.equals("short")) typeTemp = "S";
else typeTemp = "L" + typeTemp + ";";
matcher = Pattern.compile("\\[\\]").matcher(matcher.group(3));
while (matcher.find())
typeTemp = "[" + typeTemp;
type = typeTemp;
}
return type;
}
/**
* List of package classes for each prefix in the java.* domain
*/
@SuppressWarnings("serial")
static final Map<String, List<String>> javaTypes = new HashMap<String , List<String>>() {{
put("lang", Arrays.asList(new String[]{"Boolean","Byte","Character","Class","Double",
"Float","Integer","Long","Short","String","Void"}));
// rest of java.* goes here
}};
/**
* Gets and stores a set of all the classes we can find. Missing the java.* domain.
* Uses the Google Reflection library.
*
* @return The class set
*/
static Set<String> classStringSet = null;
private static Set<String> getSetOfAllClasses() {
if (classStringSet == null) {
List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());
classLoadersList.add(ClassLoader.getSystemClassLoader());
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new SubTypesScanner(false), new ResourcesScanner())
.setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0]))));
classStringSet = reflections.getStore().getSubTypesOf(Object.class.getName());
reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new SubTypesScanner(false), new ResourcesScanner())
.setUrls(ClasspathHelper.forJavaClassPath()));
classStringSet.addAll(reflections.getStore().getSubTypesOf(Object.class.getName()));
}
return classStringSet;
}
为什么我这样做?那一定会很好玩!你会看到。
答案 0 :(得分:1)
很慢。它会生成所有类的列表,并在其中查找匹配项。在我的机器上大约需要6秒钟。
实现可能遍历所有目录,以及classpath / bootclasspath上所有归档的索引。 (虽然6秒似乎过多了......除非你的课程中有大量的课程。)
如果每次需要6秒,您应该考虑在合适的数据结构中缓存一组类名。
显然这也意味着我可以有重复的麻烦。有没有办法按照Java用来消除歧义的方式对列表进行排序?
Java没有消除名称的歧义。如果发生冲突,则表示编译错误。
(实际上,我过于简单了。该语言有规则来管理名称的处理,但这些规则在很大程度上取决于源代码中的 import 语句。你有类似的东西吗?在您的用例中导入语句?)
我当然缺少java。*。这是因为这些类由原始类加载器加载并且可以进行硬编码。因此它们不可用?
您应该能够使用getSystemClassLoader()