JavaにおけるStringの不変性の理解方法

20961 ワード

文書ディレクトリ
  • 問題
  • Stringクラスの宣言
  • finalキーワードの役割
  • Stringの不変性
  • Stringの不変性はどれらの利益があります
  • Stringは本当に絶対に可変ではありませんか
  • に質問
    どうしてみんなStringが可変ではないと言っているのですか?
    Stringクラスの宣言
    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
        
        ...
        ...
       
        public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }
    

    JAvaのString宣言はfinalタイプで、Stringが継承できないことを示しています.Stringのコアストレージ値のvalueはchar[]配列でもfinal修飾を使用しており、value は変更されていないことを示しています.
    finalキーワードの役割
    final修飾変数を使用すると、コンパイラは修正を許可しません.例を挙げます.
        @Test
        public void finalTest(){
           final char[] value = {'a','a','a'};
            char[] value2 = {'b','b','b'};
            value = value2;//cannot assign a value to final variable
        }
    

    上のvalue=value 2の行は間違って報告され、final修飾の変数に値を割り当てることはできません.final修飾を使用する変数の参照は変更できません.ただし、参照を変更できないことは、中の値を変更できないことを意味しません.次のように説明します.
        @Test
        public void finalTest() {
            final char[] value = {'a', 'a', 'a'};
            System.out.println(value);
            value[2] = 'b';//      2    b
            System.out.println(value);
        }
    

    印刷結果:
    aaa
    aab
    

    Stringの不変性
    ここで簡単な例でStringの不変性をテストします
        @Test
        public void testString() {
            String test1 = new String("aaa");
            StringBuilder test2 = new StringBuilder("aaa");
            System.out.println("     test1:" + test1);
            System.out.println("     test2:" + test2.toString());
    
            //       
            String afaddStr = addString(test1);
            StringBuilder afaddStrBuild =  addStringBuilder(test2);
            System.out.println("     test1:" + test1);
            System.out.println("     test2:" + test2.toString());
        }
    
        /**
         *        bbb
         *
         * @param str
         */
        public String addString(String str) {
            str = str + "bbb";
            return str;
        }
    
        /**
         *        bbb
         *
         * @param str
         */
        public StringBuilder addStringBuilder(StringBuilder str) {
            str.append("bbb");
            return str;
        }
    

    結果出力:
         test1:aaa
         test2:aaa
         test1:aaa
         test2:aaabbb
    

    test 1は1回の操作の後も元のままになっていますが、test 2は変更されています.実はStringという可変性は私たちがよく何気なく使用しています.最も一般的な使用シーンはHashSetの値です.次の例を示します.
        @Test
        public void testString2() {
            String key1 = new String("aaa");
            StringBuilder key2 = new StringBuilder("aaa");
            HashSet set = new HashSet<>();
            set.add(key1);
            set.add(key2);
            System.out.println("     set :"+ set.toString());
            /**************      ******************/
            String afaddStr = addString(key1);
            StringBuilder afaddStrBuild =  addStringBuilder(key2);
            /**************      ****end***********/
            System.out.println("     set :"+ set.toString());
        }
        /**
         *        bbb
         *
         * @param str
         */
        public String addString(String str) {
            str = str + "bbb";
            return str;
        }
        /**
         *        bbb
         *
         * @param str
         */
        public StringBuilder addStringBuilder(StringBuilder str) {
            str.append("bbb");
            return str;
        }
    

    StringBuilderストレージを使用すると、何気ないビジネス操作でSetの値が変更され、不要なトラブルを引き起こす可能性があります.
    Stringの不変性にはどのようなメリットがありますか?
  • は、文字列が可変である場合にのみ、文字列プールが実現される.文字列プールの実装は、異なる文字列変数がプール内の同じ文字列を指すため、実行時に多くのheap空間を節約することができる.文字列が可変である場合、String interningは実現できません(String interningとは、異なる文字列に対して1つだけ保存することを意味します.つまり、同じ文字列は複数保存されません).これにより、変数がその値を変更すると、他の値を指す変数の値も一緒に変更されます.
  • 文字列が可変である場合、深刻なセキュリティ問題が発生します.たとえば、データベースのユーザー名、パスワードは文字列で入力されてデータベースの接続を取得したり、socketプログラミングではホスト名やポートが文字列で入力されたりします.文字列は可変であるため、その値は変更できません.そうしないと、ハッカーたちは穴に潜り、文字列が指すオブジェクトの値を変更し、セキュリティ・ホールをもたらすことができます.
  • 文字列は可変であるため、マルチスレッドは安全であり、同じ文字列インスタンスを複数のスレッドで共有することができる.これにより、スレッドセキュリティの問題で同期を使用する必要がなくなります.文字列自体がスレッドで安全です.
  • 文字列は可変であるため、hashcodeは作成時にキャッシュされ、再計算する必要はありません.これにより、文字列はMapのキーとして適切になり、文字列の処理速度は他のキーオブジェクトよりも速くなります.これは、HashMapのキーが文字列を使用することが多いことです.

  • Stringって本当に絶対変わらないのかな
    次の例を示します.
        @Test
        public void testString3() throws IllegalAccessException, NoSuchFieldException {
            String strObj = new String("aaa");
            System.out.println("        :" + strObj);
            System.out.println("      hash :" + strObj.hashCode());
            Field field = strObj.getClass().getDeclaredField("value");
            field.setAccessible(true);
            char[] value = (char[]) field.get(strObj);
            value[2] = 'b';
            System.out.println("        :" + strObj);
            System.out.println("      hash :" + strObj.hashCode());
        }
    

    印刷結果は次のとおりです.
            :aaa
          hash :96321
            :aab
          hash :96321
    

    反射によってStringの値を変更できることを説明します.
    参考:
    https://www.zhihu.com/question/20618891