12

このコードを実行するとどうなるか説明してもらえますか? 「G'Day Mate.」が出力されることは知っていますが、Reflection は System.out.println をどのようにキャッチするのでしょうか? スタック/ヒープ レベルで何が起こるか? どうもありがとう。

   public static void main(String... args) {
          System.out.println("Hello World");
    }

    static {
        try {
           Field value = String.class.getDeclaredField("value");
           value.setAccessible(true);
           value.set("Hello World", value.get("G'Day Mate."));
        } catch (Exception e) {
          throw new AssertionError(e);
        }
    }
4

5 に答える 5

8

リフレクションは System.out を「キャッチ」しません。もちろん、最も難しい例である文字列を選択しました。これは、Java String クラスが非常に「興味深い」クラスであり、各文字列がオブジェクトではなく、文字列のプールで生成され、それ自体が不変であるためです。

コードが行うことは、Java String クラスで静的に (つまり、実行時間の前に) 文字列 "Hello World" の値を "G`Day Mate." に設定することです。これは、文字列 "Hello World" を使用するたびに、"G`Day Mate." に変更されることを意味します。例:

String h ="Hello World";
System.out.println(h);
>>>G`Day Mate.

例が少し役立つことを願っています。興味深い発言、コード:

public static void main(String[] args){
        String h = "Hello";
        System.out.println(h);
        System.out.println("Hello");
    }
     static {
            try {
               Field value = String.class.getDeclaredField("value");
               value.setAccessible(true);
               value.set("Hello", value.get("G'Day Mate."));
            } catch (Exception e) {
              throw new AssertionError(e);
            }
        }

出力を生成します:

>>>G`Day
>>>G`Day

つまり、マッピングでは空白が何らかの違いを生むことを意味しますが、それが String オブジェクトとリフレクトの機能にどのように影響するかはわかりません。

于 2012-07-28T12:28:08.780 に答える
1

素晴らしい質問...私が理解できたのは

value.set("Hello World", value.get("G'Day Mate."));

文字列メモリ プールの値を置き換え、参照を同じに保ちました。このプログラムHello Worldでは、任意の場所で言及されていることを意味しますG'Day Mate.

C 言語でポインタを使用して変数の内容を変更する場合と似ています。

static{
try {
    Field value = String.class.getDeclaredField("value");
    value.setAccessible(true);
    value.set("Hello World", value.get("G'Day Mate."));
 } catch (Exception e) {
   throw new AssertionError(e);
 }
}
public static void main(String[] args){
    System.out.println("Hello World");
    System.out.println("Hell World");
    System.out.println("Hello orld");
    System.out.println("Hello World");
    String s = "Hello World";
    System.out.println(s);
}

版画

G'Day Mate.
Hell World 
Hello orld
G'Day Mate.
G'Day Mate.

そのためString Pool、文字列は変更されていますが、キーは残っていますHello World

興味深いことに、ステートメントの場合

value.set("Hello World", value.get("G'Day Mate."));

ArrayIndexOutOfBoundException後の文字列の長さは短くなります...文字列にアクセスするたびにスローHello Worldされます。

お役に立てれば!!!

于 2012-07-28T13:29:44.417 に答える
0

java.lang.reflect.Field.set(object, value) の javadoc

指定されたオブジェクト引数のこの Field オブジェクトによって表されるフィールドを、指定された新しい値に設定します。基になるフィールドにプリミティブ型がある場合、新しい値は自動的にアンラップされます。

操作は次のように進行します。

基になるフィールドが static の場合、obj 引数は無視されます。null の場合があります。

それ以外の場合、基になるフィールドはインスタンス フィールドです。指定されたオブジェクト引数が null の場合、メソッドは NullPointerException をスローします。指定されたオブジェクト引数が、基になるフィールドを宣言するクラスまたはインターフェイスのインスタンスでない場合、メソッドは IllegalArgumentException をスローします。

この Field オブジェクトが Java 言語アクセス制御を実施しており、基礎となるフィールドにアクセスできない場合、メソッドは IllegalAccessException をスローします。

基になるフィールドが final である場合、この Field オブジェクトに対して setAccessible(true) が成功し、フィールドが非静的でない限り、メソッドは IllegalAccessException をスローします。このように final フィールドを設定することは、プログラムの他の部分からアクセスできるようになる前に、空白の final フィールドを持つクラスのインスタンスの逆シリアル化または再構築中にのみ意味があります。他のコンテキストで使用すると、プログラムの他の部分がこのフィールドの元の値を使用し続ける場合など、予測できない影響が生じる可能性があります。

基になるフィールドがプリミティブ型の場合、新しい値をプリミティブ型の値に変換するためにアンラップ変換が試行されます。この試みが失敗した場合、メソッドは IllegalArgumentException をスローします。

アンラップの可能性がある後、恒等変換または拡大変換によって新しい値を基になるフィールドの型に変換できない場合、メソッドは IllegalArgumentException をスローします。

基になるフィールドが静的である場合、フィールドを宣言したクラスがまだ初期化されていなければ初期化されます。

フィールドは、ラップされていない可能性のある新しい値に設定されます。

フィールドが obj の型で非表示の場合、フィールドの値は前述の規則に従って設定されます。パラメータ: obj - フィールドを変更するオブジェクト value - 変更する obj のフィールドの新しい値

于 2012-07-28T12:31:12.040 に答える
0

このソース コードは、Java のいくつかの興味深い手法を開きます。一つ一つ調べてみましょう。

まず、コードの流れを理解する必要があります。コードのどの部分が最初に実行されますか?

静的初期化ブロック。なんで?Java 言語仕様 (12.4)を参照してください。

Initialization of a class consists of executing its static initializers and the initializers for static fields (class variables) declared in the class.

そして、それはいつ発生しますか?再びJLS(12.4.1)から:

T is a class and a static method declared by T is invoked.

したがって、 static initiazlier が main メソッドの前に最初に実行されるという結論に達することができます。

現在、これらの 2 つの行はリフレクションを使用しています。

Field value = String.class.getDeclaredField("value");
value.setAccessible(true);

簡単にするために、最初の行を 2 行に分けることができます。

Class<String> c=String.class;
Field value=c.getDeclaredField("value");

1 行目はReflected Class Objectを取得しており、2 行目はクラスのフィールドFieldを表すa を取得しています。valueString

value.setAccessible(true)反映されたクラス オブジェクトが、使用時に Java 言語アクセス チェックを抑制する必要があることを示します (参照)。

問題のNexラインは

value.set("Hello World", value.get("G'Day Mate."));

.set() ドキュメントに飛び込むと、 のset(Object aObject,Object value)バージョンを呼び出していることがわかりますset。は、実際には であるのフィールドの値をvalue.get("G'Day Mate.")返しています。そして、それを呼び出すと、オブジェクトの値フィールドの値がオブジェクトの値フィールドに置き換えられます。"G'Day Mate."valuechar[]set"Hello World""G'Day Mate."

ブロックのstaticコードが説明されています。

主な機能に飛び込みましょう。とてもシンプルです。出力する必要がありますHello, world。しかし、それは出力していG'Day Mateます。なんで?イニシャライザで作成したHello, worldString オブジェクトは、メイン関数で使用しているオブジェクトstaticと同じであるためです。JLSHello, worldに再度相談すると、それが明らかになります

さらに、文字列リテラルは常に String クラスの同じインスタンスを参照します。これは、文字列リテラル (より一般的には、定数式の値である文字列 (§15.28)) が、メソッド String.intern を使用して一意のインスタンスを共有するために「インターン」されているためです。

この答えは、事実をより簡潔に理解するのに役立ちます。

Hello,worldオブジェクトの値を既に に変更しているため、別の値が表示されていますG'Day, Mate

ただし、メイン関数で使用すると、プールにチェックインするのではなくnew String("Hello world")、の新しいインスタンスを直接作成します。StringしたがってHello world、メイン関数はHello world、値を変更した静的初期化子とは異なります。

于 2013-11-17T21:37:24.597 に答える
-4

Oracle (Java) ドキュメントから:

クラスは任意の数の静的初期化ブロックを持つことができ、クラス本体のどこにでも表示できます。ランタイム システムは、静的初期化ブロックがソース コードに現れる順序で呼び出されることを保証します。

ここにリンク全体があります

于 2012-07-28T12:13:55.917 に答える