なぜJavaでStringが可変ではないのか考えたことがありますか?


一度にあまり勉強しないでください.
public final class String implements Serializable, Comparable, CharSequence {
    private final char[] value; //   private final             
    private int hash;
    private static final long serialVersionUID = -6849794470754667710L;
    
    public String() {
        this.value = "".value; 
    }

    public String(String var1) {
        this.value = var1.value;
        this.hash = var1.hash;
    }

    public String(char[] var1) {
        this.value = Arrays.copyOf(var1, var1.length);
    }
    ......
}

回答:
3つの点があります:1)Stringは、最下位でprivate final修飾文字配列valueで文字列を格納します.final修飾子はvalueという参照変数が可変であることを保証し、private修飾子はvalueがクラスプライベートであることを保証し、オブジェクトインスタンスを通じてvalue配列に格納されている文字にアクセスしたり変更したりすることはできません.
注:Stringが可変ではないのはfinalが果たす役割だと言っているところがたくさんありますが、実は厳密ではありません.finalでvalueを変更しなくても、初期化が完了した後、valueという参照変数とvalue[]配列に格納されている値を変更しないことを保証できるので、変更したことがありません.finalはvalueという参照変数が変更できないことを保証しているだけで、value[]配列に格納されている文字が変更できないことは保証できません.privateをpublic修飾に変更すると、Stringクラスのオブジェクトはvalueにアクセスしてvalue[]配列に格納されている文字を変更することができ、Stringはもはや可変ではありません.だからprivateの役割はもっと大きいです.後で通過します 1 は検証します.
2)Stringクラスはvalue[]配列の内容を修正する方法を外部に暴露しておらず、Stringクラス内部の文字列に対する操作と変更はいずれも新しいStringオブジェクトを作成することによって行われ、操作が完了すると新しいStringオブジェクトが返され、元のオブジェクトのvalue[]配列は変更されていない.
注意:Stringクラスが外部に露出した場合、setterメソッドなどのvalue[]配列のメソッドを変更することはできますが、Stringが可変であることは保証されません.後で通過します 2 は検証します.
3)Stringクラスはfinalで修飾されており,Stringクラスがサブクラス継承によって破壊または変更できないことを保証している.
注意:Stringクラスがfinalで修飾されていない場合、つまりStringクラスがクラスに継承されている場合、子クラスは親の元のメソッドまたは属性を変更できます.後で通過します 3 は検証します.
以上の3つの条件が同時に満たされてこそ、Stringクラスを可変クラスにし、Stringクラスにインスタンス化するとその内容を変更できない属性を持たせることができる.
面接問題:Stringクラスはどのようなデータ構造で文字列を格納しますか?上のStringのソースコードから分かるように、Stringクラスは配列のデータ構造で文字列を格納します.
コード1:
private修飾子をpublicに変えたらどうなるか見てみましょう.
//       String ,        String    value     
public final class WhyStringImutable {
   public final char[] value;  //        public 
   
   public WhyStringImutable() {
       this.value = "".toCharArray();
   }
   
   public WhyStringImutable(String str){
       this.value = str.toCharArray(); //           
   }
   
   public char[] getValue(){
       return this.value;
   }
}
public class WhyStringImutableTest {
    public static void main(String[] args) {
        WhyStringImutable str = new WhyStringImutable("abcd");
        System.out.println(" str value      :");
        System.out.println(str.getValue()); //   str          
        System.out.println("----------");
        str.value[1] = 'e'; //         value        
        System.out.println("   str value      :");
        System.out.println(str.getValue()); //   str          
   }
}

出力結果:
 str value      :
abcd
----------
   str value      :
aecd

このようにprivateがpublicに変更されると、Stringはオブジェクトインスタンスを介して保存されたvalue配列にアクセスして変更することができ、Stringの不変性を保証することはできません.
コード2:
外部露出がvalue[]配列の方法、例えばsetter方法を変更できる場合、何が起こるか見てみましょう.
public final class WhyStringImutable {
    private final char[] value;

    public WhyStringImutable() {
        this.value = "".toCharArray();
    }

    public WhyStringImutable(String str){
        this.value = str.toCharArray();
    }
    
    //          value      
    public void setValue(int i, char ch){
        this.value[i] = ch;
    }
    
    public char[] getValue(){
        return this.value;
    }

}
public class WhyStringImutableTest {
    public static void main(String[] args) {
        WhyStringImutable str = new WhyStringImutable("abcd");
        System.out.println(" str value      :");
        System.out.println(str.getValue()); //   str          
        System.out.println("----------");
        str.setValue(1,'e'); //   set         value    
        System.out.println("   str value      :");
        System.out.println(str.getValue()); //   str          
   }
}

出力結果:
 str value      :
abcd
----------
   str value      :
aecd

このようにvalue[]配列の内容を変更できる方法が外部に露出してもStringの不変性は保証されない.
コード3:
WhyStringImutableクラスがfinal修飾を除いて、他のものが変わらなければ、どうなるのでしょうか.
public class WhyStringImutable {
    private final char[] value;
    
    public WhyStringImutable() {
        this.value = "".toCharArray();
    }
    
    public WhyStringImutable(String str){
        this.value = str.toCharArray(); //           
    }
    
    public char[] getValue(){
        return this.value;
    }
}

WhyStringImutableから継承されたサブクラスを書き、元の親の属性を変更して、サブクラス独自の論理を実現します.
public class WhyStringImutableChild extends WhyStringImutable {

    public char[] value; //         public   ,   final 

    public WhyStringImutableChild(String str){
        this.value = str.toCharArray();
    }

    public WhyStringImutableChild() {
        this.value = "".toCharArray();
    }

    @Override
    public char[] getValue() {
        return this.value;
    }
}
public class WhyStringImutableTest {
    public static void main(String[] args) {
        WhyStringImutableChild str = new WhyStringImutableChild("abcd");
        System.out.println(" str value      :");
        System.out.println(str.getValue());
        System.out.println("----------");
        str.value[1] = 's';
        System.out.println("   str value      :");
        System.out.println(str.getValue());
    }
}

実行結果:
 str value      :
abcd
----------
   str value      :
ascd

このことから,Stringクラスがfinalで修飾されていなければ,サブクラス継承によって元の属性を修正することができるので,その不変性を保証することはできない.
まとめ
以上のことから,Stringが可変でないのはJDK設計者が上記の3点を巧みに設計し,Stringクラスが可変であることを保証し,Stringに可変でない属性を持たせたためである.テストはエンジニアがデータ型を構築し、データをカプセル化する功力であり、簡単にfinalで修飾するのではなく、背後の設計思想は私たちが理解し、学ぶ価値がある.
広がる
上の分析から、Stringは確かに可変クラスであることがわかりますが、Stringオブジェクトの値を変えることはできませんか?いいえ、反射によってStringオブジェクトの値を変更できます.
ただし、反射によって対応するStringオブジェクトの値が変更されると、同じ内容のStringオブジェクトを後で作成するときに変化した値が反射されるため、後のコードロジック実行時に「頭がつかめない」現象が発生し、迷いがあり、奇抜な問題が発生しても原因を排除することは難しいので注意してください. 4 でこの問題を検証します.
まず、反射によってStringオブジェクトの内容を変更する方法を見てみましょう.
public class WhyStringImutableTest {
    public static void main(String[] args) {
        String str = new String("123");
        System.out.println("    str:"+str);
        try {
            Field field = String.class.getDeclaredField("value");
            field.setAccessible(true);
            char[] aa = (char[]) field.get(str);
            aa[1] = '1';
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        System.out.println("    str:"+str);
}

印刷結果:
    str:123
    str:113 //   ,   ,str        

コード4:
次に、反射によって対応するStringオブジェクトの値が変更されると、後で同じ内容のStringオブジェクトが作成されると、変更された値が反射されるという問題があることを確認します.
public class WhyStringImutableTest {
    public static void main(String[] args) {
        String str = new String("123");
        System.out.println("    str:"+str);
        try {
            Field field = String.class.getDeclaredField("value");
            field.setAccessible(true);
            char[] aa = (char[]) field.get(str);
            aa[1] = '1';
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        System.out.println("    str:"+str);
        
        String str2 = new String("123");
        System.out.println("str2:"+str2); //      str2      ,    113?
        System.out.println("         :"+(str == str2)); //    str   str2           
        System.out.println("        :"+str.equals(str2)); //    str   str2        
}

実行結果は次のとおりです.
    str:123
    str:113
str2:113 //     123??    113,   str2          。
         :false //    false,                  
        :true   //   true,                 

以上の出力結果から,反射後に同じ内容の文字列オブジェクトを新規作成すると反射修正後の値となり,大きな迷いをもたらすことが分かったので,実際の開発では慎重に行う.