Java 入門

Java 文字列

文字列(Strings)は、テキストデータを表現し、処理するために使用されます。Javaにおいて、必ず覚えておくべき重要なポイントが一つあります。それは、文字列はオブジェクト(Objects)であり、プリミティブ型(Primitive data types)ではないということです。

本章では、Javaにおける文字列の扱い方について、その作成方法から主要なオペレーション、そして非常に重要な内部構造の特性までを網羅的に探求していきます。

1. 文字列の作成

Javaにおいて、文字列は String クラスのオブジェクトです。文字列オブジェクトを作成するには、いくつかの異なる方法があります。

1.1 文字列リテラル (String Literals)

最も一般的で推奨される作成方法は、「文字列リテラル」を使用することです。これは、文字列をダブルクォーテーション "" で直接囲む方法です。

String message = "Hello, World!"; // 文字列リテラルを作成
System.out.println(message);

内部構造の解説:文字列コンスタントプール

リテラルを使用して文字列を作成すると、Javaはメモリ内の特殊な領域である「文字列コンスタントプール」をチェックします。もしプール内に全く同じ内容の文字列が既に存在する場合、Javaは新しいメモリを確保せず、既存のオブジェクトを再利用します。プールに存在しない場合にのみ、新しい文字列オブジェクトが作成されます。

String str1 = "Java";
String str2 = "Java"; // str2 は str1 が指しているのと同じオブジェクトを直接参照する

// == はメモリ上のアドレス(参照)を比較する
// これらはプール内の同じオブジェクトを指しているため、結果は true となる
System.out.println(str1 == str2); // 出力: true

1.2 new キーワードの使用

通常のオブジェクトと同様に、new キーワードを使って文字列を作成することも可能です。注意点として、この方法では文字列コンスタントプールに同じ内容があっても、常にメモリ(ヒープ)上に新しい文字列オブジェクトが強制的に作成されます。

String str3 = new String("Java");
String str4 = new String("Java"); // str4 は str3 とは無関係の新しいオブジェクトとなる

// メモリ上で全く異なる2つのオブジェクトであるため、アドレスが異なり、結果は false となる
System.out.println(str3 == str4); // 出力: false

1.3 文字配列からの作成 (Character Arrays)

文字配列(char[])を組み立てて文字列にすることができます。

char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String messageFromArray = new String(charArray);
System.out.println(messageFromArray); // 出力: Hello

1.4 バイト配列からの作成 (Byte Arrays)

ファイル処理やネットワーク転送を扱う際、バイト配列(byte[])を文字列に変換し直す必要が頻繁にあります。このプロセスでは、エンコーディング(Encoding)を指定することができます。

byte[] byteArray = {65, 66, 67}; // A, B, C に対応する ASCII コード値
String messageFromBytes = new String(byteArray); // システムのデフォルトエンコーディングを使用
System.out.println(messageFromBytes); // 出力: ABC

// ベストプラクティス:文字化けを防ぐため、UTF-8 エンコーディングを明示的に指定する
String messageFromBytesUTF8 = new String(byteArray, java.nio.charset.StandardCharsets.UTF_8); 
System.out.println(messageFromBytesUTF8); // 出力: ABC

2. 文字列の不変性

Javaの文字列は不変(Immutable)です。これは、一度文字列オブジェクトが作成されると、その内容を後から変更することはできないということを意味します。文字列を修正しているように見える操作は、実際には内部で新しい文字列オブジェクトを作成しています。

String text = "Hello";
text = text + " World"; // ここで全く新しい文字列オブジェクト "Hello World" が作成される
System.out.println(text); // 出力: Hello World

この例では、最初の "Hello" という文字列自体が変更されたわけではありません。+ 演算子による連結操作によってメモリ上に新しい "Hello World" が生成され、text 変数がその新しいオブジェクトを指すように再割り当てされたのです。もし元の "Hello" を参照する変数が他になければ、最終的には Java のガベージコレクタ(Garbage Collector)によってクリーンアップされます。

2.1 不変性がもたらすメリット

  • スレッドセーフ (Thread Safety): 内容が変化しないため、複数のスレッドが同時に同じ文字列を読み取っても、データが改ざんされる心配がありません。
  • メモリの節約 (String Pool Efficiency): 不変であるからこそ、Javaは「文字列コンスタントプール」の仕組みを安心して導入でき、複数の変数が一つのオブジェクトを共有することでメモリを大幅に節約できます。
  • 振る舞いの予測可能性 (Predictability): 文字列の状態が変わらないため、メソッドに渡した文字列がそのメソッド内で密かに変更されるといったリスクを考慮する必要がありません。

3. 主要な文字列オペレーション

String クラスには、テキストを処理するための非常に便利なメソッドが多数用意されています。以下は日常的な開発で多用されるものです。

3.1 長さの取得 (Length)

length() メソッドは、文字列に含まれる文字数を返します。

String myString = "Java is fun";
int length = myString.length();
System.out.println("文字列の長さ: " + length); // 出力: 文字列の長さ: 11 (注意:スペースも1文字としてカウントされる)

3.2 文字列の連結 (Concatenation)

+ 演算子または concat() メソッドを使用して、複数の文字列をつなげることができます。

String firstName = "John";
String lastName = "Doe"; 

String fullName = firstName + " " + lastName; // + 演算子を使用 (最も一般的)
System.out.println(fullName); // 出力: John Doe 

String fullNameConcat = firstName.concat(" ").concat(lastName); // concat() メソッドを使用
System.out.println(fullNameConcat); // 出力: John Doe

3.3 部分文字列の抽出 (Substrings)

substring() メソッドは、元の文字列から一部を抽出するために使用します。主に2つの形式があります。

  • substring(int beginIndex): beginIndex(これを含む)から末尾までを抽出します。
  • substring(int beginIndex, int endIndex): beginIndex(含む)から endIndex(含まない)までを抽出します。
  • 注意:Javaのインデックス(Index)は 0 から始まります。
String message = "Hello, World!"; 

String sub1 = message.substring(7); // インデックス 7 から末尾までを抽出
System.out.println(sub1); // 出力: World! 

String sub2 = message.substring(0, 5); // インデックス 0 から 4 までを抽出 (5は含まない)
System.out.println(sub2); // 出力: Hello

3.4 大文字・小文字の変換 (Case Conversion)

toLowerCase() はすべて小写に、toUpperCase() はすべて大写に変換します。

String text = "Java Programming";
System.out.println("小文字に変換: " + text.toLowerCase()); // 出力: java programming
System.out.println("大文字に変換: " + text.toUpperCase()); // 出力: JAVA PROGRAMMING

3.5 前後の空白削除

trim() メソッドは、文字列の先頭と末尾にあるスペースや改行などの不可視文字を自動的に削除します(途中のスペースは維持されます)。ユーザー入力を処理する際に非常に役立ちます。

String stringWithWhitespace = "   Hello, World!   ";
String trimmedString = stringWithWhitespace.trim();
System.out.println("空白削除後: [" + trimmedString + "]"); // 出力: [Hello, World!]

3.6 文字や部分文字列の置換 (Replacing)

replace() メソッドを使用すると、特定の文字やテキスト断片を新しい内容に置き換えることができます。

String message = "Hello, World!";
String replacedMessage = message.replace("World", "Java");
System.out.println(replacedMessage); // 出力: Hello, Java! 

String replacedChar = message.replace('!', '?');
System.out.println(replacedChar); // 出力: Hello, World?

3.7 文字列の分割 (Splitting)

split() メソッドは、指定したデリミタ(Delimiter:区切り文字)に基づいて、長い文字列を文字列配列(String[])に分割します。

String csvData = "John,Doe,30,New York";
String[] values = csvData.split(","); // カンマで分割

for (String value : values) {
    System.out.println(value);
}
/* 出力:
John
Doe
30
New York
*/

4. 文字列の比較 (Comparing Strings):重要!

文字列を比較する際は、必ず equals() または equalsIgnoreCase() メソッドを使用してください。

  • equals(): 正確に比較し、大文字と小文字を区別します。
  • equalsIgnoreCase(): 大文字と小文字の区別を無視して比較します。

初心者向けの注意点: 文字列の内容を比較するために == を使用してはいけません! == は2つの変数がメモリ上の同じアドレスにあるか(同一オブジェクトか)を比較するのに対し、.equals() はテキスト内容が同じかどうかを比較します。

String str1 = "Java";
String str2 = "java"; 

System.out.println(str1.equals(str2)); // 出力: false (大文字小文字が異なるため)
System.out.println(str1.equalsIgnoreCase(str2)); // 出力: true (区別を無視)

String str3 = new String("Java");
String str4 = "Java"; 

System.out.println(str3.equals(str4)); // 出力: true (内容はどちらも "Java")
System.out.println(str3 == str4);      // 出力: false (警告:一方はヒープオブジェクト、他方はコンスタントプールのため)

5. 空文字または null のチェック

文字列を扱う際は、それが空(長さが 0)であるか、または null(変数がどのオブジェクトも指していない)であるかをチェックすることが頻繁にあります。

String emptyString = "";
String nullString = null; 

System.out.println("emptyString は空ですか? " + emptyString.isEmpty()); // 出力: true

if (nullString == null) {
    System.out.println("nullString の値は null です"); 
}

// 複合防御:NullPointerException (空ポインタ例外) を防ぐ標準的な書き方
if (emptyString != null && !emptyString.isEmpty()) {
    System.out.println("文字列には実際の内容があります");
} else {
    System.out.println("文字列は null か、あるいは空です"); // 出力される
}

6. 文字や部分文字列の検索 (Finding)

indexOf()lastIndexOf() は、ある部分文字列が元の文字列内のどのインデックス位置にあるかを特定するために使用します。見つからない場合は -1 を返します。

  • indexOf(): 左から右へ探し、最初に見つかった位置を返します。
  • lastIndexOf(): 右から左へ探し、最後に見つかった位置を返します。
String text = "This is a test string"; 

int firstIndex = text.indexOf("is");
int lastIndex = text.lastIndexOf("is"); 

System.out.println("'is' が最初に出現するインデックス: " + firstIndex); // 出力: 2 (Th'is' の中)
System.out.println("'is' が最後に出現するインデックス: " + lastIndex);   // 出力: 5 (単独の 'is') 

int notFoundIndex = text.indexOf("xyz");
System.out.println("'xyz' のインデックス: " + notFoundIndex); // 出力: -1 (未検出)

7. 文字列のフォーマット

Java では String.format() メソッドが提供されており、フォーマット指定子(Format specifiers)を使ってエレガントに文字列を連結・整形できます。これは、コードが + 演算子で溢れかえるよりもはるかに読みやすくなります。

String name = "Alice";
int age = 30;
double salary = 50000.0; 

// フォーマット指定子を使用して変数をテキストに埋め込む
String formattedString = String.format("姓名: %s, 年齢: %d, 薪水: %.2f", name, age, salary);
System.out.println(formattedString); // 出力: 姓名: Alice, 年齢: 30, 薪水: 50000.00

主要なフォーマット指定子一覧:

  • %s : String (文字列) を挿入
  • %d : Integer (整数) を挿入
  • %f : Floating-point (浮点数) を挿入
  • %.<数字>f : 指定した小数点以下の桁数を保持する浮点数 (例:%.2f は小数点以下2桁)
  • %c : Character (単一文字) を挿入
  • %b : Boolean (ブーリアン値) を挿入