如果只有完全限定名称,如何获取 java 类的二进制名称?
Posted
技术标签:
【中文标题】如果只有完全限定名称,如何获取 java 类的二进制名称?【英文标题】:How to get the binary name of a java class, if one has only the fully qualified name? 【发布时间】:2012-10-31 04:18:28 【问题描述】:反射类和方法以及类加载器等需要使用所谓的“二进制”类名称。
问题是,如果只有完全限定名称,即在源代码中使用的名称,如何获得二进制名称。
例如:
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
的 API 文档,但没有找到任何可用的内容。
【问题讨论】:
我不确定forName
方法是否需要所谓的“二进制名称”。文档说它使用完全限定名称。
@EdwinDalorzo 你可以相信我。它找不到具有简单名称的内部类。 (至少 JDK 1.7.0 没有)
这很有趣。我现在不在我的电脑里,但我会尽快尝试一下。这很奇怪,因为 JLS 中 Fully Qualified Name 的定义没有包含“二进制名称”的概念。与所有其他类一样,内部类仅用句点 (.) 表示。 Javadocs 说该方法应该接收一个 fqn。所以这个案例很有趣。
@EdwinDalorzo 你是对的,API 文档显然在这里被破坏了。它实际上说:Given the fully qualified name for a class or interface (in the same format returned by getName)
,它说:If this class object represents a reference type that is not an array type then the binary name of the class is returned, as specified by The Java™ Language Specification.
好吧,Class.getName()
似乎返回了二进制名称。这是一个相当有趣的问题。我还没有找到将您的规范名称转换为二进制名称的方法。我会喜欢这个,因为我希望最终有人能给出答案。
【参考方案1】:
现在听起来您想从规范名称中获取完全限定名称 (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 中的“$”数量最少。 更新事实证明,Sun javac 正好相反,匹配嵌套最多的类。
其次,您可以测试所有可能的 FQN,如果有多个则抛出异常。
第三,接受唯一的唯一映射是与 FQN,然后仅在指定的类加载器中,并适当地重新处理您的应用程序。我发现使用线程上下文类加载器作为默认类加载器很方便。
【讨论】:
感谢您的深入讨论。唉,如果你将第三个程序提供给 javac 会发生什么,其中有new com.stack.Test.Example.Cow.Hoof()
(以及上面的两个程序都在你的类路径中?)。 javac会选择哪一个?
两者都在类路径中,我的 Sun javac 始终使用com.stack.Test$Example$Cow$Hoof
,无论哪个首先出现在类路径中。
我觉得这里值得注意的是,JavaFX 的 fxml 类加载系统非常简单,因为它强制执行 package-names-must-be-lower-case 约定。如果将 fxml 中引用的类放在大写包中,FXML 加载器将找不到它。【参考方案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
如您所见,所有三个本地类都具有相同的简单名称。
【讨论】:
对。但这不是我关心的问题。 (我更新了问题以避免混淆 rgd。“简单”和“完全合格”的名称。)事实上,我只是在谈论嵌套在其他类中的公共类,因此可以在 java 程序中以类似名称命名的公共类x.y.Z
。或者,换种说法:当一个人写foo.bar.Baz.X
时,java编译器如何知道这是否是一个嵌套类?
更不用说,你可以有一个***类 MyStuff 包含一个 Example 内部类,以及一个名为 MyStuff 并包含一个***类 Example 的包,而简单的名称 MyStuff.Example
就可以不区分两者。
@cHao 你是对的,但这也是一个边界情况,类可以在“未命名”包中。我们可以假设我只对命名包中的类感兴趣。
事实证明这样的情况是编译不通的。包和类同名时编译器报错。【参考方案3】:
我认为规范名称指定唯一类是安全的选择。如上所述,javac 不允许您从单个编译单元创建具有相同规范名称的两个类。如果你有 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://***.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 自动中断抛出的任何异常,您就会知道这种行为是课程的标准。
【讨论】:
以上是关于如果只有完全限定名称,如何获取 java 类的二进制名称?的主要内容,如果未能解决你的问题,请参考以下文章