JavaRush /Java Blog /Random-JA /オブジェクトの比較: 練習
articles
レベル 15

オブジェクトの比較: 練習

Random-JA グループに公開済み
これは、オブジェクトの比較に特化した記事の 2 番目です。最初のグループでは、比較の理論的基礎、つまり比較がどのように行われるか、なぜ、どこで使用されるかについて議論しました。この記事では、数値、オブジェクト、特殊なケース、微妙な点、および明白ではない点の比較について直接話します。より正確には、次のことについて話します。
オブジェクトの比較: 練習 - 1
  • 文字列比較: ' ==' とequals
  • 方法String.intern
  • 実際のプリミティブの比較
  • +0.0そして-0.0
  • 意味NaN
  • Java 5.0。==「 」によるメソッドと比較の生成
  • Java 5.0。オートボックス化/ボックス化解除:オブジェクト ラッパーの「 ==」、「>=」、「 」 。<=
  • Java 5.0。enumenum 要素 (型)の比較
それでは始めましょう!

文字列比較: ' ==' とequals

ああ、これらの行は... 最も一般的に使用されるタイプの 1 つであり、多くの問題を引き起こします。原則として、それらについては別の記事があります。ここで比較の問題について触れます。もちろん、 を使用して文字列を比較することもできますequals。さらに、それらは を介し​​て比較されなければなりませんequals。ただし、知っておく価値のある微妙な点もあります。まず第一に、同一の文字列は実際には単一のオブジェクトです。これは、次のコードを実行することで簡単に確認できます。
String str1 = "string";
String str2 = "string";
System.out.println(str1==str2 ? "the same" : "not the same");
結果は「同じ」になります。これは、文字列参照が等しいことを意味します。これは明らかにメモリを節約するためにコンパイラ レベルで行われます。コンパイラは文字列のインスタンスを 1 つ作成し、このインスタンスへのstr1参照を割り当てますstr2。ただし、これはコード内でリテラルとして宣言された文字列にのみ適用されます。断片から文字列を構成する場合、その文字列へのリンクは異なります。確認 - この例:
String str1 = "string";
String str2 = "str";
String str3 = "ing";
System.out.println(str1==(str2+str3) ? "the same" : "not the same");
結果は「同じではありません」になります。コピー コンストラクターを使用して新しいオブジェクトを作成することもできます。
String str1 = "string";
String str2 = new String("string");
System.out.println(str1==str2 ? "the same" : "not the same");
結果も「同じではない」になります。 したがって、文字列は参照比較を通じて比較できる場合があります。しかし、これに頼らない方が良いでしょう。文字列のいわゆる正規表現を取得できる非常に興味深いメソッドの 1 つについて触れたいと思いますString.intern。それについて詳しく話しましょう。

String.intern メソッド

Stringこのクラスが文字列プールをサポートしているという事実から始めましょう。クラス内で定義されている文字列リテラルだけでなく、すべての文字列リテラルがこのプールに追加されます。したがって、このメソッドを使用すると、の観点からはintern既存のプール (メソッドが呼び出されるプール) と等しい文字列をこのプールから取得できます。そのような行がプールに存在しない場合は、既存の行がそこに配置され、その行へのリンクが返されます。したがって、2 つの等しい文字列への参照が異なる場合でも (上の 2 つの例のように)、これらの文字列への呼び出しは同じオブジェクトへの参照を返します。 internequalsintern
String str1 = "string";
String str2 = new String("string");
System.out.println(str1.intern()==str2.intern() ? "the same" : "not the same");
このコード部分を実行した結果は「同じ」になります。 なぜこのようになったのか正確には言えません。このメソッドinternはネイティブなので、正直に言うと、C コードの荒野には入りたくないのです。おそらく、これはメモリ消費とパフォーマンスを最適化するために行われます。いずれにしても、この実装機能について知っておく価値はあります。次の部分に進みましょう。

実際のプリミティブの比較

まず初めに、質問したいと思います。とてもシンプルです。次の合計 – 0.3f + 0.4f は何ですか? なぜ?0.7f?確認しよう:
float f1 = 0.7f;
float f2 = 0.3f + 0.4f;
System.out.println("f1==f2: "+(f1==f2));
結果として?のように?私も。この断片を完了できなかった人のために、結果は次のようになると言います...
f1==f2: false
なぜこれが起こっているのでしょうか?. 別のテストを実行してみましょう。
float f1 = 0.3f;
float f2 = 0.4f;
float f3 = f1 + f2;
float f4 = 0.7f;
System.out.println("f1="+(double)f1);
System.out.println("f2="+(double)f2);
System.out.println("f3="+(double)f3);
System.out.println("f4="+(double)f4);
への変換に注意してくださいdouble。これは、より多くの小数点以下の桁数を出力するために行われます。結果:
f1=0.30000001192092896
f2=0.4000000059604645
f3=0.7000000476837158
f4=0.699999988079071
厳密に言えば、結果は予測可能です。小数部分の表現は有限級数 2-n を使用して実行されるため、任意に選択された数値の正確な表現について話す必要はありません。この例からわかるように、表現精度はfloat小数点以下 7 桁です。 厳密に言えば、表現ではfloat 仮数部に 24 ビットが割り当てられます。 float したがって、 (精度について話しているので次数は考慮せずに) を 使用して表現できる最小絶対数は、 2-24≈6*10-8 です。表現内の値が実際に反映されるのはこのステップですfloat。そして量子化があるので誤差も発生します。 したがって、結論は次のとおりです。表現内の数値は、float一定の精度でのみ比較できます。それらを小数点第 6 位 (10-6) に四捨五入するか、できればそれらの差の 絶対値を確認することをお勧めします。
float f1 = 0.3f;
float f2 = 0.4f;
float f3 = f1 + f2;
float f4 = 0.7f;
System.out.println("|f3-f4|<1e-6: "+( Math.abs(f3-f4) < 1e-6 ));
この場合、次のような有望な結果が得られます。
|f3-f4|<1e-6: true
もちろん、絵柄はタイプと全く同じですdouble。唯一の違いは仮数部に 53 ビットが割り当てられるため、表現精度は 2-53 ≒ 10-16 になります。はい、量子化値ははるかに小さいですが、存在します。そして、残酷な冗談を言うこともあります。ちなみに、JUnitのテストライブラリでは、実数を比較するメソッドにおいて、明示的に精度を指定しています。それらの。比較方法には、数値、何と等しいか、比較の精度という 3 つのパラメータが含まれます。 ところで、程度を示す数値を科学的な形式で書く際の微妙な点について触れておきたいと思います。質問。10-6はどうやって書くのですか?実際にやってみると、80% 以上が 10e-6 と回答しています。ちなみに正解は1e-6です!そして、10e-6 は 10-5 です。私たちはプロジェクトの 1 つで、まったく予期せずにこの熊手を踏みました。彼らは非常に長い間エラーを探し、定数を 20 回調べました。そして、ある日、主に偶然に、定数 10e-3 が出力され、2 つの定数が見つかるまで、誰もその正しさに疑いを持ちませんでした。小数点以下の桁が、予想される 3 桁ではなく、桁数になります。したがって、注意してください。 次へ移りましょう。

+0.0 と -0.0

実数の表現では、最上位ビットに符号が付けられます。他のすべてのビットが 0 の場合はどうなりますか? このような状況では、結果が表現範囲の下限に位置する負の数となる整数とは異なり、最上位ビットのみが 1 に設定された実数も、マイナス符号が付いた場合のみ 0 を意味します。したがって、+0.0 と -0.0 という 2 つのゼロがあります。これらの数値は等しいと考えるべきでしょうか?という論理的な疑問が生じます。仮想マシンはまさにこのように考えます。ただし、これらを操作した結果、異なる値が得られるため、 これらは 2 つの異なる数値です。
float f1 = 0.0f/1.0f;
float f2 = 0.0f/-1.0f;
System.out.println("f1="+f1);
System.out.println("f2="+f2);
System.out.println("f1==f2: "+(f1==f2));
float f3 = 1.0f / f1;
float f4 = 1.0f / f2;
System.out.println("f3="+f3);
System.out.println("f4="+f4);
...そして結果:
f1=0.0
f2=-0.0
f1==f2: true
f3=Infinity
f4=-Infinity
したがって、場合によっては、+0.0 と -0.0 を 2 つの異なる数値として扱うことが合理的です。また、2 つのオブジェクトがあり、その 1 つのフィールドが +0.0 で、もう 1 つのフィールドが -0.0 である場合、これらのオブジェクトは等しくないものと見なすこともできます。仮想マシンとの直接比較で数値が等しくないことがどのように理解できるのかという疑問が生じますtrue。答えはこれです。仮想マシンはこれらの数値が等しいとみなしますが、その表現は依然として異なります。したがって、できることはビューを比較することだけです。そして、それを取得するために、メソッドint Float.floatToIntBits(float)と があり、それぞれとのlong Double.doubleToLongBits(double)形式でビット表現を返します(前の例の続き)。 intlong
int i1 = Float.floatToIntBits(f1);
int i2 = Float.floatToIntBits(f2);
System.out.println("i1 (+0.0):"+ Integer.toBinaryString(i1));
System.out.println("i2 (-0.0):"+ Integer.toBinaryString(i2));
System.out.println("i1==i2: "+(i1 == i2));
結果は次のようになります
i1 (+0.0):0
i2 (-0.0):10000000000000000000000000000000
i1==i2: false
したがって、+0.0 と -0.0 が異なる数値である場合は、ビット表現を通じて実数変数を比較する必要があります。+0.0と-0.0を整理したようです。ただし、-0.0 だけが驚きではありません。というような事もありますが・・・

NaN 値

NaNを意味するNot-a-Number。この値は、0.0 を 0.0 で割る、無限大を無限大で割るなど、誤った数学演算の結果として表示されます。この値の特徴は、それ自体と等しくないことです。それらの。:
float x = 0.0f/0.0f;
System.out.println("x="+x);
System.out.println("x==x: "+(x==x));
...結果は...
x=NaN
x==x: false
オブジェクトを比較すると、どのようにしてこのことが判明するのでしょうか? オブジェクトのフィールドが に等しい場合NaN、比較により が得られますfalse。つまり、オブジェクトは不等であるとみなされることが保証されます。ただし、論理的には、まったく逆のことを望むかもしれません。メソッドを使用すると、目的の結果を得ることができますFloat.isNaN(float)true引数が の場合に戻りますNaN。この場合、ビット表現の比較には依存しません。標準化されていません。おそらくプリミティブについてはこれで十分です。次に、Java バージョン 5.0 以降に現れた微妙な点に移りましょう。そして最初に触れておきたいポイントは、

Java 5.0。==「 」によるメソッドと比較の生成

デザインには製法というパターンがある。場合によっては、コンストラクターを使用するよりも、その使用の方がはるかに有益です。例を挙げてみましょう。オブジェクトシェルについてはよく知っていると思いますBoolean。このクラスは不変であり、値を 2 つだけ含めることができます。つまり、実際には、どのようなニーズにも、2 つのコピーだけで十分です。また、事前に作成して単に返すだけであれば、コンストラクターを使用するよりもはるかに高速になります。そのような方法がありますBoolean: valueOf(boolean)。バージョン1.4から登場しました。同様の生成メソッドが、バージョン 5.0 のByteCharacterShortおよびクラスに導入されました。これらのクラスがロードされると、プリミティブ値の特定の範囲に対応するインスタンスの配列が作成されます。これらの範囲は次のとおりです。 IntegerLong
オブジェクトの比較: 実践 - 2
これは、メソッドを使用するときに、valueOf(...)引数が指定された範囲内にある場合、常に同じオブジェクトが返されることを意味します。おそらくこれにより速度が多少向上するでしょう。しかし同時に、本質的に問題を解決するのが非常に難しい問題も発生します。詳細については、こちらをお読みください。 理論的には、生成メソッドはとクラスvalueOfの両方に追加されています。彼らの説明には、新しいコピーが必要ない場合は、この方法を使用する方がよいと記載されています。速度などが向上する可能性があります。等々。ただし、現在の (Java 5.0) 実装では、このメソッドで新しいインスタンスが作成されます。これを使用しても速度が向上することは保証されません。さらに、値の連続性によりキャッシュを編成できないため、この方法をどのように高速化できるかを想像するのは困難です。整数を除く。つまり、小数部分なしで。FloatDouble

Java 5.0。オートボックス化/ボックス化解除:オブジェクト ラッパーの「 ==」、「>=」、「 」 。<=

操作を最適化するために、生成メソッドとインスタンス キャッシュが整数プリミティブのラッパーに追加されたのではないかと思いますautoboxing/unboxing。それが何なのか思い出させてください。オブジェクトが操作に関与する必要があるが、プリミティブが関与している場合、このプリミティブは自動的にオブジェクト ラッパーにラップされます。これautoboxing。逆も同様です。プリミティブを操作に関与させる必要がある場合は、そこにオブジェクト シェルを置き換えることができ、値はそこから自動的に展開されます。これunboxing当然のことながら、そのような利便性に対して料金を支払う必要があります。自動変換操作により、アプリケーションの速度が多少低下します。ただし、これは現在のトピックとは関係ないので、この質問はやめておきます。 明らかにプリミティブまたはシェルに関連する操作を扱っている限り、すべて問題ありません。「 」の操作はどうなりますか==? Integer内部に同じ値を持つ2 つのオブジェクトがあるとします。それらはどのように比較されるのでしょうか?
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println("i1==i2: "+(i1==i2));
結果:
i1==i2: false

Кто бы сомневался... Сравниваются они How an objectы. А если так:Integer i1 = 1;
Integer i2 = 1;
System.out.println("i1==i2: "+(i1==i2));
結果:
i1==i2: true
これでさらに面白くなりました!-eの場合、autoboxing同じオブジェクトが返されます。ここに罠があります。同じオブジェクトが返されることがわかったら、これが常に当てはまるかどうかを確認する実験を開始します。そして、いくつの値をチェックするのでしょうか? 1つ?十?百?おそらく、ゼロ付近の各方向で 100 に制限されるでしょう。そして私たちはどこでも平等を手に入れることができます。すべてがうまくいっているように思えます。しかし、ここで少し振り返ってください。何が問題かわかりましたか?...はい、オートボックス化中のオブジェクト シェルのインスタンスは生成メソッドを使用して作成されます。これは、次のテストでよくわかります。
public class AutoboxingTest {

    private static final int numbers[] = new int[]{-129,-128,127,128};

    public static void main(String[] args) {
        for (int number : numbers) {
            Integer i1 = number;
            Integer i2 = number;
            System.out.println("number=" + number + ": " + (i1 == i2));
        }
    }
}
結果は次のようになります。
number=-129: false
number=-128: true
number=127: true
number=128: false
キャッシュ範囲 内の値については同一のオブジェクトが返され、範囲外の値については異なるオブジェクトが返されます。したがって、プリミティブではなくアプリケーション シェルのどこかが比較されると、最も恐ろしいエラー、つまりフローティング エラーが発生する可能性があります。コードは、このエラーが表示されない限られた範囲の値でもテストされる可能性が高いためです。しかし、実際の作業では、計算の結果に応じて、表示されたり消えたりします。そのような間違いを見つけるよりも、夢中になるほうが簡単です。したがって、可能な限りオートボクシングを避けることをお勧めします。それだけではありません。小学5年生までの算数を覚えましょう。不等式A>=Bと を考えますА<=BAこの関係とについて何が言えるでしょうかB? 一つだけあることは、それらは等しいということです。同意しますか?私はイエスと思う。テストを実行してみましょう:
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println("i1>=i2: "+(i1>=i2));
System.out.println("i1<=i2: "+(i1<=i2));
System.out.println("i1==i2: "+(i1==i2));
結果:
i1>=i2: true
i1<=i2: true
i1==i2: false
そして、これが私にとって最大の奇妙なことです。このような矛盾が生じるのであれば、なぜこの機能が言語に導入されたのか全く理解できません。一般に、もう一度繰り返しますが、なしで実行できる場合はautoboxing/unboxing、この機会を最大限に活用する価値があります。最後に触れたいトピックは... Java 5.0 です。列挙要素の比較 (enum 型) ご存知のとおり、Java はバージョン 5.0 以降、enum - enumeration などの型を導入しました。デフォルトでは、そのインスタンスには、クラス内のインスタンス宣言に名前とシーケンス番号が含まれています。したがって、発表順序が変わると数字も変わります。ただし、「 そのまま連載する 」の記事でも述べたように、これは問題にはなりません。すべての列挙要素は単一のコピーに存在し、これは仮想マシン レベルで制御されます。したがって、リンクを使用して直接比較できます。* * * 今日はオブジェクト比較の実装の実践的な側面についてはこれで終わりです。おそらく何かが足りないのでしょう。いつものように、あなたのコメントを楽しみにしています!とりあえず、休暇を取らせていただきます。ご清聴ありがとうございました! ソースへのリンク:オブジェクトの比較: 実践
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION