こんにちは、私の名前はイエスです。
一般的な面接では、候補者の「基盤」がしっかりしているかどうか、オペレーティングシステムレベルのもの、Java言語のもの、そして...
最近、私は候補者を調べるためにJava言語の基本的な質問をしました。
リフレクションなしで、2つのStringオブジェクトの実際の値を交換するメソッドを実装することは可能ですか?
String yesA = "a";
String yesB = "b";
//能否实现这个 swap 方法
// 让yesA=b,yesB=a?
swap(yesA, yesB);
この質問を過小評価しないでください。実際、いくつかの点を調べることができます。
- yesAとyesBが何であるかを明確にする
- Javaは値を渡すだけです
- 文字列は不変のクラスです
- 文字列定数プール
- インターンの理解
- JVMメモリの分割と変更
上記の点に基づいて、実際に配布できるインタビューの質問はたくさんありますが、今日はこの記事に飛び込むのではなく、上記の点を要約するだけです。
明確な答えが必要です。このメソッドは実装できません。
タイトルの意味によると、次のスワップメソッドの実装を簡単に記述できると思います。
void swap(String yesA, String yesB){
String temp = yesA;
yesA = yesB;
yesB = temp;
}
まず、String yesA = "a";
このます。
つまり、変数yesAは、オブジェクトaを見つけることができる参照のみを格納するため、yesAがaであるかのように見えます。実際、yesAは「アドレス」を格納し、Javaがオブジェクトを検出することを理解できます。このアドレスを介して。
したがって、yesAによって格納された値はaではなく、参照(同様に、yesB)であることがわかります。
次に、Javaには値渡しのみがあると聞いています。つまり、メソッドを呼び出すと、Javaは変数yesAの値をメソッドで定義されたyesAに渡します(yesBについても同じことが言えます)。 )、値を渡すだけです。
上記によると、yesAが参照を格納していることはすでにわかっているので、swap方法
内部のyesAとyesBが参照であることがわかります。
次に、swapメソッドを呼び出し、yesAとyesBの値(つまり、その参照)をスワップします
すみません、スワップは私の外のyesAとyesBと関係がありますか?明らかに、それは問題ではありません。
したがって、最終的には、yesAの外側はまだaを指し、yesBはまだbを指します。
信じられない場合は、コード実行の結果を見てみましょう。
これで、 Javaは値渡しのみであることがわかりました。
これを見て、一部の学生は、intについてはどうでしょうか、intはオブジェクトではなく、参照はありません。実際、同じです。Javaには値の転送しかないことを覚えておいてください。
それを実行して調べてみましょう:
明らかに、intは正常に交換できません。理由は同じです。
yesAとyesBの外では、格納されている値は1と2です(ここには参照がなく、ヒープにオブジェクトがなく、値はスタックに直接割り当てられます)。
swapを呼び出す場合、渡される値は1と2であり、過去にコピーをコピーしたと理解できます。
したがって、スワップ内のyesAとyesBは実際にはコピーであり、それらの値も1と2であり、コピーが交換されます。これはマスターと関係がありますか?
どうやらそうではありません。
SF映画にクローンがあるように、クローンが死んだ場合、本当のマスターは死ぬのでしょうか?
しません。
Javaは値渡しのみであることに注意してください。
このインタビューの質問に戻ると、Stringが不変のクラスであることを知っておく必要があります。
では、不変クラスとは何ですか?
私は前の記事でそれを言いました、ここで私は引用します:
不変クラスとは、オブジェクトの値を変更できないことを意味します。たとえば、Stringは一般的な不変クラスです。Stringオブジェクトを作成した後は、オブジェクトを変更できません。
変更できないため、s + = "a";を実行するなどのメソッドは、実際には新しいStringオブジェクトを返します。古いsが指すオブジェクトは変更されませんが、sの参照は新しいオブジェクトを指します。
下の写真から明らかなはずです。
図に示すように、参照を返すために新しいオブジェクトが実際に作成されるたびに、前のオブジェクトの値は変更されないため、文字列が頻繁にスプライスされるシーンでは、スプライシングに+を使用しないとよく言われます。これにより、頻繁にスプライシングが発生します。オブジェクトを作成し、パフォーマンスに影響を与えます。
そして一般的に、Stringが不変のクラスであると言うとき、インタビュアーは通常次のように尋ねます。
不変クラスの利点は何ですか?
さあ、私もあなたのために答えを用意しています:
このオブジェクトは変更できないことがわかっているため、主な利点は安全性です。また、マルチスレッド環境ではスレッドセーフです(考えてみれば、参照するオブジェクトは不変の値であるため、誰も変更できません。それから、それは常に変更されず、他のスレッドがそれを休ませて移動します、あなたはそれを自信を持って使うことができます)。
次に、定数プールを使用すると、メモリスペースを節約でき、取得効率も高くなります(文字列オブジェクトが定数プールにすでに存在する場合は、新しいオブジェクトを作成する必要はなく、直接返すだけです)。
だからここに言及された文字列。
たとえば、String yesA = "a"
この、yesAはヒープ内のオブジェクトaを指す参照であり、より具体的には、実際にはヒープ内の文字列定数プール内のオブジェクトaを指していることがわかります。
文字列定数プールにすでにaがある場合は、その参照が直接返され、aがない場合は、オブジェクトが作成されてから、その参照が返されます。
これは、リテラル形式の文字列の作成と呼ばれます。
もう1つは直接new String
です。たとえば、次のようになります。
String yesA = new String("a")
この方法は同じではありません。最初に、リテラル「a」がここに表示されるため、文字列定数プールにaがあるかどうかを判断し、aがない場合は、aを作成してから、ヒープ内にオブジェクトaを作成します。 memory、returnヒープメモリオブジェクトaへの参照。つまり、戻り値は文字列定数プールにありません。
次の実験から上記のステートメントを検証できます。リテラルを作成することによって返される参照は同じですが、同じでnew String
はありません。
この時点で、文字列リテラルの作成と文字列の作成の違いを明確に理解する必要がありnew String
ます。
これについて言えば、それはしばしば面接の質問を伴います、それは
intern
次のコードの出力値は何だと思いますか?あなたはそれについて考えることができます
String yesA = "aaabbb";
String yesB = new String("aaa") + new String("bbb");
String yesC = yesB.intern();
System.out.println(yesA == yesB);
System.out.println(yesA == yesC);
OK、答えは次のとおりです。
最初の出力がfalseであり、1つは文字列定数への参照であり、もう1つはヒープ内にあることは間違いありません(実際、まだ出入り口があります。以下を参照してください)。
2番目の出力は、主にこのインターンメソッドのために当てはまります。
internメソッドの機能は、yesB参照が指す値が文字列定数にあるかどうかを判断することです。ない場合は、文字列定数プールに新しいaaabbbオブジェクトを作成し、その参照を返します。ある場合は、参照を直接返します。 。
この例では、yesAは最初にリテラルを介して定義されるため、yesCが定義されると、文字列定数プールにすでにaaabbbオブジェクトが存在します(equals()メソッドを使用してオブジェクトがあるかどうかを判別します)。したがって、定数プール内の参照直接返されるため、yesA == yesC
これで終わりだと思いましたか?
上記のコードの順序を変更してみましょう。
String yesB = new String("aaa") + new String("bbb");
String yesC = yesB.intern();
String yesA = "aaabbb"; // 这里换了
System.out.println(yesA == yesB);
System.out.println(yesA == yesC);
yesAの定義をyesCに入れると、結果は次のように変わります。
あなたは少し混乱していますか?奇妙なことに、上記のロジックによると、ああすべきではありません。
実際、最初に文字列定数プールを描画したときは、それをヒープに描画し、JDK 1.8の観点から話していたので、文字列定数プールはヒープにあると言い続けました。
JDK 1.6では、文字列定数プールは永続世代に配置され、JDK 1.7以降では、ヒープに移動されました。
この領域の変化は、インターンの戻り値の変化につながります。
この認知的前提の下で、変更された順序の後のコードがどのように実行されるかを見てみましょう。
String yesB = new String("aaa") + new String("bbb");
この時点で、新しいaaabbbオブジェクトがヒープ内に作成され(aaaとbbbのオブジェクトについての説明は無視されます)、リテラルaaabbbが表示されないため、文字列定数プールには作成されません。
String yesC = yesB.intern();
この時点で、aaabbbオブジェクトは文字列定数プール内に作成されますか?
ここに重要なポイントがあります。
JDK 1.6では、文字列定数プールは永続世代に配置されるため、新しいオブジェクトを作成して定数プールに配置する必要があります。
ただし、JDK 1.7以降、文字列定数プールはヒープに配置され、ヒープにはすでに新しいaaabbbオブジェクトが含まれているため、リソースを浪費する必要はなく、別のオブジェクトを格納する必要もありません。参照をヒープに直接格納するだけです。したがって、yesCは、yesBと同じ参照を格納します。
String yesA = "aaabbb";
同様に、1.7でyesAによって取得された参照は、yesCおよびyesBと一致しており、どちらもヒープ内のaaabbbオブジェクトを指しています。
- 最終的な答えは本当です
1.7以降、文字列オブジェクトがすでにヒープに存在する場合、internを呼び出すと、文字列定数プールに新しいオブジェクトが作成されず、参照を直接保存して戻ります。
このインタビューの質問が哀れなものであるかどうかを確認します。別のJDKバージョンから回答する必要があります。そうでない場合は間違っていますが、インタビュアーはバージョンを通知しません。
実は面接の質問はこんな感じで、質問が投げかけられて直接答えられるようです。直接答えると間違っています。まず前提を述べてから、それが正しいように、それに答えてください。
やっと
ご覧のとおり、このような小さな基本的な質問は、非常に多くのトピックにつながる可能性があり、JVMメモリの分割などにも拡張できます。
これは実際には基礎のテストであり、人が学んだ知識は関連しているのでリンクされているかどうかもわかります。ポイントを与えると、それが表面に広がる可能性があります。知識のシステムになることができます。
はい、少しから百万ドットまで、次の記事でお会いしましょう〜
フォローしてください〜