如何获得一个Java类的二进制名称,如果只有完全合格的名称?(How to get the bina

2019-08-02 14:36发布

反射类和方法以及类加载器等需要的类所谓的“二进制”的名字一起工作。

现在的问题是,怎么开始的二进制名称,如果一个只有完全合格的名称,即一个将在源代码中使用的名称。

例如:

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文档ClassCLassLoaderjava.lang.reflect ,但没有发现任何有用。

Answer 1:

现在听起来像你想从规范名称完全限定域名(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则只能在指定的类加载和返工适当你的程序。 我觉得方便使用线程上下文类加载器作为默认的类加载器。



Answer 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

正如你所看到的,所有的三个本地类具有相同简单名称。



Answer 3:

我认为它是一个安全的赌注,规范名称指定唯一的类。 正如上面提到的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?