15

リフレクションのクラスとメソッド、およびクラスローダーなどは、操作するクラスのいわゆる「バイナリ」名を必要とします。

問題は、完全修飾名、つまりソースコードで使用する名前しかない場合、どのようにしてバイナリ名を取得するかということです。

例えば:

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

可能ですが、不器用な解決策は、もうスローされなくなるか、置き換えるものがなくなるまで、1つずつ後方.から置き換えることです。$Class.forName()ClassNotFoundException.

より良い/よく知られている/標準的な解決策はありますか?APIドキュメントでを調べましたが、Class使用CLassLoaderできるjava.lang.reflectものは見つかりませんでした。

4

3 に答える 3

13

これで、正規名から完全修飾名(FQN)を取得したいようです。それは単純な名前からの作業とは異なるので、2番目の答えを追加します。

正規の名前の競合が発生した場合、Sunjavacコマンドはクラスをコンパイルしません。ただし、別々にコンパイルすることで、同じ正規名を持つ2つの異なるクラスを取得できます。

例:

ファイル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

したがって、2つのクラスの正規名は同じですが、FQNが異なります。2つのクラスのFQNと正規名が同じであっても、異なるクラスローダーを介してロードされる場合は異なる可能性があります。

あなたの問題を解決するために、私はあなたがとることができるいくつかの方法を見ます。

最初に、ネストの量が最も少なく、したがってFQNの「$」の数が最も少ないクラスと一致するように指定できます。更新Sunjavacはこれとは正反対のことを行い、最もネストされたクラスと一致することがわかりました。

次に、考えられるすべてのFQNをテストし、複数ある場合は例外をスローできます。

第3に、唯一の一意のマッピングはFQNを使用することであり、指定されたクラスローダー内でのみ行われることを受け入れ、アプリケーションを適切に再処理します。スレッドコンテキストクラスローダーをデフォルトのクラスローダーとして使用すると便利だと思います。

于 2012-11-12T12:14:08.353 に答える
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

ご覧のとおり、3つのローカルクラスはすべて同じ単純な名前です。

于 2012-11-11T20:14:52.303 に答える
1

正規の名前が一意のクラスを指定するのは安全だと思います。上記のように、javacでは、単一のコンパイル単位から同じ正規名で2つのクラスを作成することはできません。2つのコンパイルがある場合、どのクラスをロードするかについて問題が発生する可能性がありますが、その時点で、ライブラリのパッケージ名がパッケージ名と衝突することを心配します。これは、悪意のある人を除いてすべて回避されます。

このため、そのシナリオに遭遇しないと想定するのが安全だと思います。それらの線に沿って、興味のある人のために、OPの提案($sをsに反転)を実装し、その正規名のクラスが見つからない場合、または2つ以上のクラスが見つかった場合は.単にaをスローします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に、スローされた例外を自動ブレークするように指示したことがある場合は、この種の動作が当然のことであることがわかります。

于 2015-01-03T23:23:45.627 に答える