Java | Java 標準ライブラリ:String クラスの特性

Java Java
スポンサーリンク

String クラスは「特別扱い」されているクラス

String は、Java で文字列を扱うためのクラスです。
でも、ただのクラスではなく「言語レベルで特別扱い」されている存在です。

ダブルクォーテーション "..." で書いたものは全部 String
import しなくても最初から使える
ほとんどのプログラムで必ず登場する

なので、String の特性をしっかり理解しておくと、
コードの読みやすさ・バグの少なさ・性能の面でかなり差が付きます。

ここでは、初心者のうちに押さえておくべきポイントに絞って、
例と一緒に丁寧に解説していきます。


特性1:String は「イミュータブル(不変)」である(最重要)

一度作ったら中身は絶対に変わらない

String の一番大事な特性は「不変(immutable)」です。
一度作られた String の中身(文字列)は、絶対に変わりません。

コードで確かめてみます。

String s = "hello";
s.toUpperCase();
System.out.println(s);   // 何が出る?
Java

toUpperCase() を呼んだので "HELLO" になると思うかもしれませんが、実際の出力は

hello

です。

toUpperCase() は、元の String を書き換えるのではなく、
「大文字にした新しい String を返す」メソッドだからです。

正しい使い方はこうです。

String s = "hello";
String upper = s.toUpperCase();
System.out.println(upper);  // HELLO
System.out.println(s);      // hello(元はそのまま)
Java

この「変更系メソッドに見えても、全部“新しい String を返す”だけ」という性質は、
すべての String メソッドに共通です。

replace
substring
concat
trim

など、全部「元の s は変えず、新しいインスタンスを返す」です。

なぜ不変だとうれしいのか(少し深掘り)

不変であることには、たくさんのメリットがあります。

スレッドセーフ
複数のスレッドから同じ String を読んでも、安全に使える。
「途中で誰かに中身を書き換えられる心配がない」。

hashCode の再計算が不要
equals / hashCode の振る舞いが安定しているので、
HashMap<String, ...>HashSet<String> でキーに使いやすい。

参照を共有しても安全
同じ String インスタンスをいろんな場所で共有しても、
誰かが中身を変えて他に影響を与える、ということが起きない。

この「不変である」という特性を理解しておくだけで、
String の扱い方に迷わなくなります。


特性2:文字列リテラルと String プール

同じ文字列リテラルは実は「共有されている」

次のコードを見てください。

String a = "hello";
String b = "hello";

System.out.println(a == b);        // true になる
System.out.println(a.equals(b));   // true
Java

==true になっているのがポイントです。
== は「同じインスタンスか?」を見る演算子なので、
ab同じオブジェクトを参照しています。

Java には「String プール(intern プール)」という仕組みがあり、
同じ文字列リテラル "hello" は一度だけ作って、後は共有します。

これは「メモリの節約」や「比較の高速化」のための最適化です。

new String(…) を使うと話が変わる

一方で、こう書くとどうでしょう。

String a = "hello";
String b = new String("hello");

System.out.println(a == b);        // false
System.out.println(a.equals(b));   // true
Java

new String("hello") は、文字列プールとは別に新しいインスタンスを作ります。
だから == では false になります。

初心者がよくハマるポイントですが、
「文字列の等価比較には == ではなく equals を使う」が鉄則になります。


特性3:== と equals の違い(ここもかなり重要)

String の比較でやってはいけないこと

やってはいけない比較の例です。

String s1 = new String("abc");
String s2 = new String("abc");

if (s1 == s2) {
    System.out.println("同じ");
} else {
    System.out.println("違う");
}
Java

出力はほぼ確実に

違う

です。
== は「同じインスタンスか?」を見るので、
new String した時点で別オブジェクトです。

正しくは equals で比較する

文字列の内容(値)を比較したいなら、equals を使います。

if (s1.equals(s2)) {
    System.out.println("同じ文字列です");
}
Java

そして、null 安全に書きたいときは、
「null にならない側」から equals を呼ぶのが定番です。

String s = null;

if ("abc".equals(s)) {
    System.out.println("一致");
}
Java

これなら、s が null でも NullPointerException になりません。


特性4:文字列連結とパフォーマンス(StringBuilder の話)

文字列連結の基本と落とし穴

Java では + で文字列を連結できます。

String message = "Hello, " + name + " さん";
Java

これはシンプルで読みやすいです。
小さなコードではこれで十分です。

ただし、ループの中で大量に + 連結をすると、
毎回新しい String が作られるため、パフォーマンスが悪くなります。

例えば:

String s = "";
for (int i = 0; i < 10000; i++) {
    s = s + i;
}
Java

この書き方は、
毎回 s + i で新しい String を生成する → 古いものは捨てる
という動作を何千回も繰り返します。

大量連結は StringBuilder を使う

大量連結をする場合は、StringBuilder を使うのが定番です。

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append(i);
}
String result = sb.toString();
Java

StringBuilder は可変のバッファを持っていて、
中身を書き換えながら文字列を構築できます。

最後に toString()String に変換します。

初心者のうちは

ちょっとした連結 → + でOK
ループなどで大量連結 → StringBuilder を使う

という感覚を持っておけば十分です。


特性5:length と char 型、substring の基本

length は「文字数」、charAt は「1文字」

String.length() は、文字列の長さ(文字数)を返します。

String s = "abc";
System.out.println(s.length()); // 3
Java

1文字取り出したいときは charAt を使います。

char c = s.charAt(0);          // 'a'
Java

配列と同じで、0 から始まるインデックスです。

部分文字列を取り出す substring

substring で一部だけ取り出せます。

String s = "abcdef";
String sub1 = s.substring(2);      // "cdef"(2番目以降)
String sub2 = s.substring(1, 4);   // "bcd"(1〜3番目)
Java

substring(beginIndex, endIndex)
beginIndex 番目から、endIndex - 1 番目まで
というイメージです。


特性6:String は配列や Map のキーとして「超優秀」

不変で hashCode が安定している

String は不変なので、一度作ったら hashCode が変わりません。
そのため、HashMap<String, ...>HashSet<String> のキーに非常によく使われます。

例えば:

Map<String, Integer> scores = new HashMap<>();
scores.put("taro", 80);
scores.put("hanako", 95);

System.out.println(scores.get("taro"));   // 80
Java

String の equals / hashCode は Java 本体側でしっかり実装されているので、
自分で何も考えずにキーに使って大丈夫です。

自作クラスでこれをやろうとすると、
equals / hashCode 契約を守る必要が出てきますが、
String はそのあたりを全部面倒見てくれています。


ちょっとだけ:文字コード(エンコーディング)の話

String は「文字」であって「バイト列」ではない

String は「文字列」を表すクラスであって、
「バイト列」ではありません。

ファイルやネットワークに送るときは、
String をバイト列に変換する必要があります。

String s = "こんにちは";
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
Java

逆に、バイト列から文字列にするには

String s2 = new String(bytes, StandardCharsets.UTF_8);
Java

のように、エンコーディング(UTF-8 など)を明示するのが大事です。

ここは少しレベルが上がりますが、
「画面に出す文字列(String)」と「通信やファイルのバイト列」は別物
という意識だけ持っておくと、後々混乱しにくくなります。


まとめ:String の特性をどう活かすか

初心者として、まず意識しておきたい String の特性はこのあたりです。

イミュータブル(不変)なので、変更系メソッドは全部「新しい String を返す」
== ではなく equals で中身を比較する(特にリテラルと new の違いに注意)
大量連結では StringBuilder を使う
不変で hashCode が安定しているので、Map / Set のキーに向いている

「文字列は当たり前に使うもの」だからこそ、
ここをちゃんと抑えておくと、他のクラスの理解もスムーズになります。

タイトルとURLをコピーしました