Javaの別のクラスからプライベートフィールドの値を読み取る方法は?


482

サードパーティに設計が不十分なクラスがあり、JARそのプライベートクラスの1つにアクセスする必要があるフィールドのます。たとえば、プライベートフィールドを選択する必要があるのはなぜですか?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

リフレクションを使用して値を取得するにはどうすればよいstuffIWantですか?

回答:


668

プライベートフィールドにアクセスするには、クラスの宣言済みフィールドからフィールドを取得して、アクセス可能にする必要があります。

Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException

EDIT:としてでコメントされていaperkinsにアクセスし、値を取得するとして、それを設定すると投げることができ、両方のフィールドにアクセスする、Exception唯一ものの、Sをチェックし、あなたがに留意する必要がある例外が上にコメントしています。

NoSuchFieldExceptionあなたが宣言したフィールドに対応していませんでした名前でフィールドを求めた場合にスローされます。

obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException

IllegalAccessExceptionそれはプライベートであり、逃し経由でアクセス可能にされていない場合、フィールドには、例えば(アクセスできなかった場合にスローされますf.setAccessible(true)行を。

RuntimeExceptionスローされる可能性があり、SのいずれかですSecurityException(JVMのであれば、S SecurityManagerは、フィールドのアクセシビリティを変更することはできません)、またはIllegalArgumentExceptionあなたがいないフィールドのクラスの型のオブジェクトのフィールドを試してみて、アクセスすると、(S)

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type

4
例外コメントについて説明していただけますか?コードは実行されますが、例外がスローされますか?またはコードが例外をスローする可能性がありますか?
Nir Levy

1
@Nir -いいえ-すべての可能性のコードは、(SecurityManagerができるようになりますデフォルトとして、フィールドのアクセシビリティを変更することが)ちゃんと動作します-しかし、あなたがする必要はありハンドルが(どちらかそれらをキャッチたりするそれらを宣言するチェック例外を再スロー)。答えを少し変更しました。あなたが遊んで何が起こるかを見るために小さなテストケースを記述することが良いかもしれません
oxbow_lakes

3
申し訳ありませんが、これは私に混乱を招く答えです。おそらく、典型的な例外処理の例を示しています。クラスが正しく接続されていない場合、例外が発生するようです。コードサンプルでは、​​対応するllnesで例外がスローされるように見えます。
ラファイエット2016

2
getDeclaredField()が親クラスで定義されている場合、フィールドは見つかりません- getDeclaredField()一致が見つかる(その後を呼び出すことができるsetAccessible(true))か、に到達するまで、親クラスの階層全体を反復して、それぞれを呼び出す必要がありますObject
ルークハッチソン

1
@legendセキュリティマネージャーをインストールして、そのようなアクセスを禁止できます。Java 9以降、このようなアクセスはモジュールの境界を越えて機能することは想定されていません(モジュールが明示的に開かれている場合を除く)。ただし、新しいルールの適用に関する移行段階があります。その上、Reflectionなどの追加の作業が必要なことは、常に非privateフィールドに違いをもたらしました。偶発的なアクセスを防ぎます。
Holger

159

FieldUtilsApache commons-lang3から試してください:

FieldUtils.readField(object, fieldName, true);

76
commons-lang3のいくつかのメソッドを組み合わせることで、世界のほとんどの問題を解決できると確信しています。
Cameron

@yegor javaがこれを許可する理由 なぜJavaはプライベートメンバーへのアクセスを許可するのですか?
Asif Mushtaq

1
@ yegor256 C#とC ++のプライベートメンバーにもアクセスできます。じゃあ?すべての言語作成者は弱いですか?
Asif Mushtaq

3
@UnKnown javaはしません。Java言語とJava仮想マシンとしてのJavaの2つがあります。後者はバイトコードで動作します。そして、それを操作するライブラリがあります。したがって、Java言語では、スコープ外のプライベートフィールドを使用したり、最終参照を変更したりすることはできません。しかし、ツールを使用してそれを行うことは可能です。これはたまたま標準ライブラリの中にあります。
evgenii

3
@UnKnownの理由の1つは、DIやORM、XML / JSONシリアライザなどのインフラストラクチャフレームワークだけがフィールドにアクセスできるようにする「フレンド」アクセスがJavaにないことです。このようなフレームワークは、オブジェクトの内部状態を正しくシリアル化または初期化するためにオブジェクトフィールドにアクセスする必要がありますが、ビジネスロジックに適切なコンパイル時のカプセル化を適用する必要がある場合があります。
Ivan Gammel 2016

26

リフレクションは、問題を解決する唯一の方法ではありません(クラス/コンポーネントのプライベートな機能/動作にアクセスすることです)

別の解決策は、.jarからクラスを抽出し、(たとえば)JodeまたはJadを使用して逆コンパイルし、フィールドを変更して(またはアクセサを追加して)、元の.jarに対して再コンパイルすることです。次に、新しい.classを.jarクラスパスのの前に置くか、に再度挿入し.jarます。(jarユーティリティを使用すると、既存の.jarを抽出して再挿入できます)

以下に述べるように、これは単にフィールドにアクセス/変更するのではなく、プライベート状態にアクセス/変更するというより広い問題を解決します。

.jarもちろん、これは署名されていないことが必要です。


36
この方法は、単純なフィールドではかなり苦痛です。
Valentin Rocher、

1
同意しません。フィールドへのアクセスだけでなく、フィールドへのアクセスが十分でないことが判明した場合は、必要に応じてクラスを変更することもできます。
ブライアンアグニュー

4
次に、それをもう一度行う必要があります。jarが更新を取得し、存在しないフィールドにリフレクションを使用するとどうなりますか?それはまったく同じ問題です。あなたはそれを管理する必要があります。
Brian Agnew

4
これが与えられた投票数に驚いているa)実用的な代替案として強調表示されているb)フィールドの可視性を変更するだけでは不十分なシナリオに対応している
Brian Agnew

2
@BrianAgnew多分それは単なる意味論ですが、(リフレクションを使用してプライベートフィールドを読み取る)質問に固執する場合、リフレクションを使用しないことはすぐに自己矛盾です。しかし、私はあなたがフィールドへのアクセスを提供することに同意します...しかし、フィールドはもはや非公開ではないので、再び質問の「非公開フィールドの読み取り」に固執しません。他の角度から見ると、.jarの変更は場合によっては機能しない可能性があり(署名付きjar)、jarが更新されるたびに実行する必要があり、クラスパスを注意深く操作する必要があります(これは、アプリケーションコンテナーなど)
レミモーリン2014

18

まだ言及されていないもう1つのオプション:Groovyを使用します。Groovyでは、言語の設計の副作用としてプライベートインスタンス変数にアクセスできます。フィールドにゲッターがあるかどうかに関係なく、

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()

4
OPは特にJavaを求めていました
Jochen

3
今日の多くのJavaプロジェクトにはGroovyが組み込まれています。プロジェクトがSpringのグルーヴィーなDSLを使用することで十分であり、たとえばクラスパスにグルービーを配置することができます。この場合、この回答は役立ちます。OPには直接回答しませんが、多くの訪問者にとって有益です。
アミールアビリ

10

Javaリフレクションを使用するprivate/publicと、あるクラスのすべてのフィールドとメソッドに別のクラスにアクセスできます。ただし 、セクションの欠点のOracle ドキュメントに従って彼らはそれを推奨:

「リフレクションを使用すると、プライベートフィールドやメソッドへのアクセスなど、非リフレクトコードでは違法となる操作をコードで実行できるため、リフレクションを使用すると、予期しない副作用が発生し、コードが機能しなくなり、移植性が損なわれる場合があります。リフレクティブコード抽象化に違反しているため、プラットフォームのアップグレードにより動作が変わる可能性があります」

以下は、リフレクションの基本的な概念を示すためのコードスナップです。

Reflection1.java

public class Reflection1{

    private int i = 10;

    public void methoda()
    {

        System.out.println("method1");
    }
    public void methodb()
    {

        System.out.println("method2");
    }
    public void methodc()
    {

        System.out.println("method3");
    }

}

Reflection2.java

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class Reflection2{

    public static void main(String ar[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        Method[] mthd = Reflection1.class.getMethods(); // for axis the methods 

        Field[] fld = Reflection1.class.getDeclaredFields();  // for axis the fields  

        // Loop for get all the methods in class
        for(Method mthd1:mthd)
        {

            System.out.println("method :"+mthd1.getName());
            System.out.println("parametes :"+mthd1.getReturnType());
        }

        // Loop for get all the Field in class
        for(Field fld1:fld)
        {
            fld1.setAccessible(true);
            System.out.println("field :"+fld1.getName());
            System.out.println("type :"+fld1.getType());
            System.out.println("value :"+fld1.getInt(new Reflaction1()));
        }
    }

}

それがお役に立てば幸いです。


5

oxbow_lakesが言及しているように、リフレクションを使用してアクセス制限を回避できます(SecurityManagerで許可されている場合)。

とは言っても、このクラスの設計があまりにも悪く、そのようなハッカーに頼るようになっている場合は、別の方法を探す必要があります。確かに、この小さなハックによって数時間は節約できるかもしれませんが、将来、どれくらいの費用がかかりますか?


3
私は実際より幸運です。このコードを使用してデータを抽出しているだけです。その後、それをごみ箱に戻します。
フランク・クルーガー、

2
その場合は、ハックしてください。:-)
ローレンスゴンサルベス

3
これは質問に対する答えを提供しません。批評したり、著者に説明を求める場合は、投稿の下にコメントを残してください。
Mureinik 2014

@Mureinik-「反射を使用できます」という言葉で質問に答えます。例やそれ以上の説明や方法はありませんが、それは答えです。気に入らない場合は反対票を投じてください。
ArtOfWarfare 2017年

4

Soot Java最適化フレームワークを使用して、バイトコードを直接変更します。 http://www.sable.mcgill.ca/soot/

すすは完全にJavaで書かれており、新しいJavaバージョンで動作します。


3

次のことを行う必要があります。

private static Field getField(Class<?> cls, String fieldName) {
    for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
        try {
            final Field field = c.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        } catch (final NoSuchFieldException e) {
            // Try parent
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "Cannot access field " + cls.getName() + "." + fieldName, e);
        }
    }
    throw new IllegalArgumentException(
            "Cannot find field " + cls.getName() + "." + fieldName);
}

2

Springを使用する場合、ReflectionTestUtilsは、最小限の労力でここで役立ついくつかの便利なツールを提供します。「単体および統合テストシナリオで使用するため」と説明されています。ReflectionUtilsという名前の同様のクラスもありますが、これは「内部使用のみを目的としています」と説明されています。これが何を意味するかについては、この回答を参照してください。

投稿された例に対処するには:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");

1
SpringでUtilsクラスを使用する場合は、もちろん、以下の場合を除いて、実際には非テストクラス(docs.spring.io/spring-framework/docs/current/javadoc-api/org/…)を使用する必要があります。ユニットテストに実際に使用しています。
同期

おそらく、そのクラスは「内部使用のみを目的とする」と記述されていますが。これに関する回答に情報を追加しました。(私の例のようにこの例を使用ReflectionTestUtilsし続けましたが、テスト状況でこの種のことを実行する必要があっただけです。)
Steve Chambers

1

リフレクションに関する追加の注記:いくつかの特殊なケースで、同じ名前のクラスが異なるパッケージに複数存在する場合、トップアンサーで使用されているリフレクションがオブジェクトから正しいクラスを選択できない場合があることを確認しました。したがって、オブジェクトのpackage.classが何であるかがわかっている場合は、次のようにプライベートフィールドの値にアクセスすることをお勧めします。

org.deeplearning4j.nn.layers.BaseOutputLayer ll = (org.deeplearning4j.nn.layers.BaseOutputLayer) model.getLayer(0);
Field f = Class.forName("org.deeplearning4j.nn.layers.BaseOutputLayer").getDeclaredField("solver");
f.setAccessible(true);
Solver s = (Solver) f.get(ll);

(これは私にとってうまくいかなかったクラスの例です)


1

マニフォールドの @JailBreakを使用して、直接型保証されたJavaリフレクションを実行できます。

@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;

public class Foo {
    private String stuffIWant;
}

@JailBreakの階層fooFooのすべてのメンバーに直接アクセスできるように、コンパイラーのローカル変数のロックを解除します。

同様に、jailbreak()拡張メソッドを1回限りの使用に使用できます。

foo.jailbreak().stuffIWant = "123";

jailbreak()メソッドを通じて、Fooの階層内の任意のメンバーにアクセスできます。

どちらの場合も、コンパイラーはフィールドアクセスをタイプセーフにパブリックフィールドのように解決しますが、マニフォールドは内部で効率的なリフレクションコードを生成します。

マニホールドの詳細をご覧ください。


1

XrayInterfaceツールを使用すると、非常に簡単です。不足しているゲッター/セッターを定義するだけです。

interface BetterDesigned {
  Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}

そして、あなたの貧弱に設計されたプロジェクトをX線で撮る:

IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());

内部的には、これは反射に依存しています。


0

ケースの問題を回避してみてください。データを設定/取得したいカラスは、独自のクラスの1つです。

ただ、作成public setter(Field f, Object value)してpublic Object getter(Field f)いるため。これらのメンバー関数内で独自にセキュリティチェックを行うこともできます。たとえば、セッターの場合:

class myClassName {
    private String aString;

    public set(Field field, Object value) {
        // (A) do some checkings here  for security

        // (B) set the value
        field.set(this, value);
    }
}

もちろん、ここでフィールド値を設定java.lang.reflect.FieldするsString前にfor を見つける必要があります。

この手法は、一般的なResultSet-to-and-from-model-mapperで使用します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.