反射类和方法以及类加载器等需要使用所谓的“二进制”类名称。
问题是,如果一个人只有一个完全限定的名称,即一个人在源代码中使用的名称,如何获得二进制名称。
例如:
package frege;
public static class RT {
....
public static class X { .... }
}
该类的完全限定名称为frege.RT.X
。然而,要获得类对象,需要编写:
Class.forName("frege.RT$X")
而不是
Class.forName("frege.RT.X") // fails with ClassNotFoundException
因为X
恰好是frege.RT
的内部类。
一个可能但又笨拙的解决方案是将.
从$
逐个替换为Class.forName()
,直到ClassNotFoundException
不再抛出.
或那里不再需要Class
替换。
有没有更好/众所周知/标准的解决方案?我查看了CLassLoader
,java.lang.reflect
和{{1}}的API文档,但没有找到任何可用的文档。
答案 0 :(得分:12)
现在听起来你想从规范名称中获取完全限定名称(FQN)。由于这与使用简单名称不同,我将添加第二个答案。
如果会导致规范名称冲突,Sun javac命令将不会编译类。但是,通过单独编译,您仍然可以获得具有相同规范名称的两个不同的类。
一个例子:
文件src1 \ com \ stack \ Test.java
package com.stack;
public class Test {
public static class Example {
public static class Cow {
public static class Hoof {
}
}
}
public static void main(String[] args) throws Exception {
Class<?> cl1 = Class.forName("com.stack.Test$Example$Cow$Hoof");
Class<?> cl2 = Class.forName("com.stack.Test.Example.Cow.Hoof");
System.out.println(cl1.getName());
System.out.println(cl1.getSimpleName());
System.out.println(cl1.getCanonicalName());
System.out.println();
System.out.println(cl2.getName());
System.out.println(cl2.getSimpleName());
System.out.println(cl2.getCanonicalName());
}
}
文件src2 \ com \ stack \ Test \ Example \ Cow \ Hoof.java
package com.stack.Test.Example.Cow;
public class Hoof { }
然后编译并执行:
set CLASSPATH=
mkdir bin1 bin2
javac -d bin1 -sourcepath src1 src1\com\stack\Test.java
javac -d bin2 -sourcepath src2 src2\com\stack\Test\Example\Cow\Hoof.java
set CLASSPATH=bin1;bin2
java com.stack.Test
制作输出:
com.stack.Test$Example$Cow$Hoof
Hoof
com.stack.Test.Example.Cow.Hoof
com.stack.Test.Example.Cow.Hoof
Hoof
com.stack.Test.Example.Cow.Hoof
因此,两个类具有相同的规范名称但具有不同的FQN。即使两个类具有相同的FQN和相同的规范名称,如果它们通过不同的类加载器加载,它们仍然可以是不同的。
要解决您的问题,我会看到您可以采取的几种方法。
首先,您可以指定您匹配具有最少嵌套量的类,因此在FQN中匹配最少数量的'$'。 UPDATE 事实证明,Sun javac与此完全相反,并且匹配具有最多嵌套的类。
其次,您可以测试所有可能的FQN,并在有多个FQN时抛出异常。
第三,接受唯一的唯一映射是使用FQN然后仅在指定的类加载器中并适当地重新使用应用程序。我发现使用线程上下文类加载器作为默认的类加载器很方便。
答案 1 :(得分:2)
一个简单的名称省略了很多信息,并且可以有许多具有相同简单名称的类。这可能会使这变得不可能。例如:
package stack;
/**
*
* @author Simon Greatrix
*/
public class TestLocal {
public Object getObject1() {
class Thing {
public String toString() {
return "I am a Thing";
}
}
return new Thing();
}
public Object getObject2() {
class Thing {
public String toString() {
return "I am another Thing";
}
}
return new Thing();
}
public Object getObject3() {
class Thing {
public String toString() {
return "I am a rather different Thing";
}
}
return new Thing();
}
/**
* @param args
*/
public static void main(String[] args) {
TestLocal test = new TestLocal();
Object[] objects = new Object[] {
test.getObject1(),
test.getObject2(),
test.getObject3()
};
for(Object o : objects) {
System.out.println("Object : "+o);
System.out.println("Simple Name : "+o.getClass().getSimpleName());
System.out.println("Name : "+o.getClass().getName());
}
}
}
这会产生输出:
Object : I am a Thing
Simple Name : Thing
Name : stack.TestLocal$1Thing
Object : I am another Thing
Simple Name : Thing
Name : stack.TestLocal$2Thing
Object : I am a rather different Thing
Simple Name : Thing
Name : stack.TestLocal$3Thing
如您所见,所有三个本地类都具有相同的简单名称。
答案 2 :(得分:1)
我认为可以肯定的是,规范名称指定了唯一的类。如上所述,javac不允许您使用单个编译单元创建具有相同规范名称的两个类。如果你有2个编辑,那么你可能会遇到关于你加载哪个类的麻烦,但是在那一点上我更担心一个库的包名与你的包名冲突,这是所有人都可以避免的。恶意的。
出于这个原因,我认为假设你不会遇到那种情况是一个安全的赌注。顺便提一下,对于那些感兴趣的人,我实施了OP的建议(将$
翻到.
s),并简单地抛出一个ClassNotFoundException
。没有找到任何具有该规范名称的类,或者如果它找到两个或更多具有该名称的类。
/**
* Returns the single class at the specified canonical name, or throws a {@link java.lang.ClassNotFoundException}.
*
* <p>Read about the issues of fully-qualified class paths vs the canonical name string
* <a href="http://stackoverflow.com/questions/13331902/how-to-get-the-binary-name-of-a-java-class-if-one-has-only-the-fully-qualified">discussed here</a>.
*/
public static <TStaticallyNeeded> Class<TStaticallyNeeded> classForCanonicalName(String canonicalName)
throws ClassNotFoundException {
if (canonicalName == null) { throw new IllegalArgumentException("canonicalName"); }
int lastDotIndex = canonicalName.length();
boolean hasMoreDots = true;
String attemptedClassName = canonicalName;
Set<Class> resolvedClasses = new HashSet<>();
while (hasMoreDots) try {
Class resolvedClass = Class.forName(attemptedClassName);
resolvedClasses.add(resolvedClass);
}
catch (ClassNotFoundException e) {
continue;
}
finally {
if(hasMoreDots){
lastDotIndex = attemptedClassName.lastIndexOf('.');
attemptedClassName = new StringBuilder(attemptedClassName)
.replace(lastDotIndex, lastDotIndex + 1, "$")
.toString();
hasMoreDots = attemptedClassName.contains(".");
}
}
if (resolvedClasses.isEmpty()) {
throw new ClassNotFoundException(canonicalName);
}
if (resolvedClasses.size() >= 2) {
StringBuilder builder = new StringBuilder();
for (Class clazz : resolvedClasses) {
builder.append("'").append(clazz.getName()).append("'");
builder.append(" in ");
builder.append("'").append(
clazz.getProtectionDomain().getCodeSource() != null
? clazz.getProtectionDomain().getCodeSource().getLocation()
: "<unknown code source>"
).append("'");
builder.append(System.lineSeparator());
}
builder.replace(builder.length() - System.lineSeparator().length(), builder.length(), "");
throw new ClassNotFoundException(
"found multiple classes with the same canonical names:" + System.lineSeparator() +
builder.toString()
);
}
return resolvedClasses.iterator().next();
}
它仍然让我非常恼火,预计&#34; flow是命中catch(NoClass) continue
代码,但是如果你曾经告诉eclipse或者intelJ对任何抛出的异常进行自动中断,你就会知道这种行为与课程相同。