How to get the binary name of a java class, if one has only the fully qualified name?
反射类和方法以及类加载器等需要使用所谓的类的"二进制"名称。
问题是,如果一个人只有完全限定的名称,即在源代码中使用的名称,那么如何获得二进制名称。
例如:
1 2 3 4 5 | package frege; public static class RT { .... public static class X { .... } } |
类的完全限定名为
1 | Class.forName("frege.RT$X") |
而不是
1 | Class.forName("frege.RT.X") // fails with ClassNotFoundException |
因为
一个可能但笨拙的解决方案是从后到后逐个地用
有没有更好/知名/标准的解决方案?我查看了
现在听起来您想要从规范名称中获取完全限定名(FQN)。因为这与简单的名字不同,我将添加第二个答案。
如果出现规范名称冲突,sun javac命令将不会编译类。但是,通过单独编译,仍然可以得到两个具有相同规范名称的不同类。
一个例子:
文件src1comstack est.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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()); } } |
文件src2comstack estexamplecowhoof.java
1 2 3 | package com.stack.Test.Example.Cow; public class Hoof { } |
然后编译和执行:
1 2 3 4 5 6 7 | 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 |
产生输出:
1 2 3 4 5 6 7 | 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中的"$"数最少。更新结果是sun javac与之完全相反,并将类与嵌套最多的类匹配。
其次,您可以测试所有可能的FQN,如果有多个FQN,则抛出异常。
第三,接受唯一的映射是使用FQN的,然后只在指定的类加载器内,并适当地重新使用应用程序。我发现使用线程上下文类加载器作为默认类加载器很方便。
一个简单的名称省略了很多信息,并且有可能有许多具有相同简单名称的类。这可能使这成为不可能。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 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()); } } } |
这将产生输出:
1 2 3 4 5 6 7 8 9 |
如您所见,这三个本地类都有相同的简单名称。
我认为规范名称指定唯一类是一个安全的赌注。如上所述,javac不允许您在一个编译单元中创建两个具有相同规范名称的类。如果您有两个编译,那么您可能会在加载哪个类时遇到麻烦,但是在这一点上,我更担心库的包名与您的包名相冲突,这是除了恶意程序之外所有人都可以避免的。
基于这个原因,我认为假设你不会遇到这种情况是一个安全的赌注。沿着这些思路,对于那些感兴趣的人,我执行了OP的建议(将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | /** * 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 * discussed here. */ 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(); } |
"预期的"流会碰到