反射类和方法以及类加载器等需要的类所谓的“二进制”的名字一起工作。
现在的问题是,怎么开始的二进制名称,如果一个只有完全合格的名称,即一个将在源代码中使用的名称。
例如:
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
了,或者没有更多的.
取代。
有没有更好的/众所周知/标准的解决方案? 我看着在API文档Class
, CLassLoader
和java.lang.reflect
,但没有发现任何有用。
现在听起来像你想从规范名称完全限定域名(FQN)。 因为这是从一个简单的名称不同的工作,我会添加第二个答案。
太阳javac命令不会编一个班,如果名称不规范冲突会导致。 然而,通过单独编译你仍然可以得到两个不同的类具有相同的规范名称。
一个例子:
文件SRC1 \ COM \栈\ 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 \栈\测试\例子\牛\ 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
因此,两个类具有相同的规范名称,但不同的FQNs。 即使两个类具有相同的FQN和相同的规范名称,如果它们通过不同的类加载器加载他们仍然可以是不同的。
要解决您的问题,我看到几个前进的道路,你可以采取。
首先,你可以指定你的类嵌套最少的,因此在FQN数最少的S“$”匹配。 更新原来的Sun javac还这样做的完全相反的和类最筑巢相匹配。
其次,你可以测试所有可能的FQNs并抛出一个异常,如果有不止一个。
三,接受的是只有唯一的映射与FQN则只能在指定的类加载和返工适当你的程序。 我觉得方便使用线程上下文类加载器作为默认的类加载器。
一个简单的名称省略了大量的信息,并有可能有很多类具有相同的简单名称。 这可能使这不可能的。 例如:
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
正如你所看到的,所有的三个本地类具有相同简单名称。
我认为它是一个安全的赌注,规范名称指定唯一的类。 正如上面提到的javac不会让你从withen单一的编译单元创建两个类具有相同的规范名称。 如果你有2个编译,那么你可以进入你关于加载的类的麻烦,但在这一点上我会更担心库的包名称与您的包名,这是所有,但恶意避免碰撞。
出于这个原因,我认为它是一个安全的赌注,假设你不会遇到那种情况下。 沿着这一思路,对于那些有兴趣谁,我实现了OP的建议(翻转$
s到.
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();
}
它仍然惹恼了我很大的“预期”的流动是打的catch(NoClass) continue
代码,但如果你曾经告诉Eclipse或的IntelliJ上抛出的异常自动断线,你就会知道这种行为是意料之中的事。
文章来源: How to get the binary name of a java class, if one has only the fully qualified name?