Stringが不変であることの実害
メソッドは新しい文字列を返すだけです。戻り値を捨てるミスと、StringBuilderの使いどころを扱います。
なぜエラーが出ないのか
出力: java
(エラーなし)- javacは何も報告しません。文法として正しいためです
出力: java- 実際の挙動 — 期待した結果と食い違っている箇所
見つけ方- エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます
このエラーが出る典型パターン
パターン1
1 public class Main { 2 public static void main(String[] args) { 3 String s = "java"; 4 s.toUpperCase(); ^ 5 System.out.println(s); 6 } 7 }
出力: java期待: JAVA (コンパイルは通っています)
Stringは不変です。toUpperCaseは新しい文字列を返すだけで、元の変数は変わりません。
直し方: (空) を s = にします。
広告
広告スロット(未設定)
パターン2
1 public class Main { 2 public static void main(String[] args) { 3 String s = "a,b,c"; 4 System.out.println(s.replace(",", "-") ); ^ 5 } 6 }
出力: a-b-c(replaceの戻り値を使えば正しく動きます)
replaceも新しい文字列を返します。戻り値を使わなければ何も起きません。
直し方: (空) を (空) にします。
パターン3
1 public class Main { 2 public static void main(String[] args) { 3 String sb = new StringBuilder(); ^ 4 for (int i = 0; i < 3; i++) sb.append(i); 5 System.out.println(sb); 6 } 7 }
Main.java:3: error: incompatible types: StringBuilder cannot be converted to String
ループで文字列を組み立てるならStringBuilderです。String連結は毎回新しい実体を作るため遅くなります。
直し方: String を StringBuilder にします。
パターン4
1 public class Main { 2 public static void main(String[] args) { 3 String s = " java "; 4 s.trim(); ^ 5 System.out.println("[" + s + "]"); 6 } 7 }
出力: [ java ]期待: [java] (コンパイルは通っています)
trimも新しい文字列を返すだけです。戻り値を変数に代入し直さないと元の変数は変わりません。
直し方: s.trim(); を s = s.trim(); にします。
パターン5
1 public class Main { 2 public static void main(String[] args) { 3 String s = "java"; 4 s.concat("script"); ^ 5 System.out.println(s); 6 } 7 }
出力: java期待: javascript (コンパイルは通っています)
concatも新しい文字列を返すだけで、元の変数sは書き換わりません。
直し方: s.concat("script"); を s = s.concat("script"); にします。
よくある誤解
「変数の中身を書き換えている」わけではありません。新しい文字列を作って、参照を差し替えているだけです。
実務での勘所
文字列の結合をfor文の中でa += b;のように繰り返すと、javacの最適化(1つの文の中でのStringBuilder自動生成)が効きません。ループの1周ごとに新しいStringBuilderが作られては捨てられるため、繰り返し回数が増えるほど無駄なオブジェクト生成が積み重なります。ループの外でStringBuilderを1つだけ作り、ループの中ではappendだけを呼ぶようにすると、この無駄がなくなります。