Javaプログラミングにおいて、文字列の操作は避けて通れない非常に重要な要素です。

ユーザー入力の加工、ログデータの解析、ファイルパスの抽出など、さまざまな場面で「文字列の一部を取り出す」という処理が必要になります。

Javaでこれを実現するための最も基本的かつ強力なツールが substring メソッドです。

本記事では、Javaの substring メソッドについて、基本的な使い方から、引数の指定方法、注意すべき例外処理、さらにはパフォーマンス面での内部挙動まで、プロフェッショナルな視点で詳しく解説します。

この記事を読めば、文字列切り出しに関する迷いは完全になくなるでしょう。

substringメソッドとは

Javaの String クラスに用意されている substring メソッドは、元の文字列から指定した範囲の部分文字列(substring)を抽出して新しい文字列オブジェクトを生成するためのメソッドです。

Javaの文字列は「イミュータブル(不変)」であるため、元の文字列を直接書き換えるのではなく、切り出した後の新しい文字列インスタンスを戻り値として返すという性質を持っています。

substringの2つのオーバーロード

substring メソッドには、主に以下の2つの書き方があります。

  1. substring(int beginIndex):開始位置のみを指定する
  2. substring(int beginIndex, int endIndex):開始位置と終了位置を指定する

これらを使い分けることで、文字列の先頭を削ったり、特定の中間部分だけを抜き出したりすることが可能になります。

substringの基本的な使い方

まずは、それぞれのシグネチャに基づいた具体的なコード例を見ていきましょう。

開始位置のみを指定する場合

substring(int beginIndex) は、引数で渡したインデックスから、文字列の最後までを切り出します。

Javaのインデックスは「0」から始まることに注意してください。

Java
public class SubstringExample1 {
    public static void main(String[] args) {
        String text = "JavaProgramming";

        // インデックス4('P'の位置)から最後までを切り出す
        String result = text.substring(4);

        System.out.println("元の文字列: " + text);
        System.out.println("切り出し結果: " + result);
    }
}
実行結果
元の文字列: JavaProgramming
切り出し結果: Programming

この例では、0番目が ‘J’、1番目が ‘a’、2番目が ‘v’、3番目が ‘a’ と数え、4番目の ‘P’ 以降が抽出されています。

開始位置と終了位置を指定する場合

substring(int beginIndex, int endIndex) を使用すると、より細かな範囲指定が可能です。

ここで最も重要なルールは、「終了インデックス(endIndex)に指定した位置の文字は含まれない」という点です。

数学的な表現を使えば、[beginIndex, endIndex) 、つまり「開始位置以上、終了位置未満」の範囲が抽出されます。

Java
public class SubstringExample2 {
    public static void main(String[] args) {
        String text = "Welcome to Java World";

        // インデックス11('J')から、15('a'の次の空白)の前までを切り出す
        // つまり 11, 12, 13, 14 番目の文字を取得する
        String result = text.substring(11, 15);

        System.out.println("抽出された単語: " + result);
    }
}
実行結果
抽出された単語: Java

このように、第2引数には「欲しい文字の最後のインデックス + 1」を指定するのが基本です。

切り出す文字数は 「endIndex – beginIndex」 という計算式で求めることができます。

上記の例では 15 - 11 = 4 文字が抽出されています。

インデックスの考え方と注意点

substring を使いこなすためには、Javaにおけるインデックスの数え方を正確に把握しておく必要があります。

0から始まるインデックス

Javaの文字列は内部的に文字の配列として管理されており、最初の文字のインデックスは必ず 0 です。

インデックス01234
文字Hello

例えば、”Hello” という文字列に対して substring(1, 3) を実行すると、1番目の ‘e’ から始まり、3番目の ‘l’ の手前までが対象となるため、結果は “el” になります。

負の数や範囲外の指定による例外

substring メソッドを使用する際、最も頻繁に遭遇するエラーが StringIndexOutOfBoundsException です。

この例外は、以下のような不正なインデックスを指定した場合に発生します。

  • beginIndex が負の値である。
  • endIndex が文字列の長さ(length())を超えている。
  • beginIndexendIndex よりも大きい。
Java
String str = "Hello";
// StringIndexOutOfBoundsExceptionが発生する例
str.substring(-1);       // 負の値は不可
str.substring(0, 10);    // 文字列の長さを超えている
str.substring(3, 1);     // 開始位置が終了位置より大きい

実務では、動的にインデックスを計算する場合にこれらのエラーが起きやすいため、事前に if 文などで範囲チェックを行うか、Math.min()Math.max() を活用して安全に値を制御することが推奨されます。

実践的な活用シーン

substring は単体で使うよりも、他のメソッドと組み合わせて使うことで真価を発揮します。

特定の文字を基準に切り出す(indexOfとの組み合わせ)

例えば、メールアドレスからドメイン部分だけを抽出したり、ファイルパスから拡張子を取得したりする場合、indexOflastIndexOf と組み合わせるのが定石です。

Java
public class PracticalExample {
    public static void main(String[] args) {
        String fileName = "report_2024_final.pdf";

        // ドット '.' の位置を探す
        int dotIndex = fileName.lastIndexOf(".");

        if (dotIndex != -1) {
            // ドットの次の位置から最後までを切り出す
            String extension = fileName.substring(dotIndex + 1);
            System.out.println("拡張子: " + extension);
        }
    }
}
実行結果
拡張子: pdf

lastIndexOf を使うことで、ファイル名の中に複数のドットが含まれていても、最も後ろにあるドット(拡張子の区切り)を正確に特定できます。

文字列の前後をトリミングして特定の長さに収める

Webサイトの記事故障などで、長い説明文の冒頭100文字だけを表示し、残りを「…」とするような処理も substring で実装可能です。

Java
public class TruncateExample {
    public static void main(String[] args) {
        String longText = "Javaは1995年にサン・マイクロシステムズによって公開されたプログラミング言語です。オブジェクト指向を特徴とし、現在でも多くの企業システムで利用されています。";
        int limit = 20;

        String summary;
        if (longText.length() > limit) {
            summary = longText.substring(0, limit) + "...";
        } else {
            summary = longText;
        }

        System.out.println("要約: " + summary);
    }
}
実行結果
要約: Javaは1995年にサン・マイクロシステムズ...

substringのパフォーマンスとメモリ管理の変遷

Javaエンジニアとして知っておくべき重要な知識に、substring の内部実装の変化があります。

過去の仕様(Java 6以前)とメモリリークのリスク

Java 6以前の substring は、元の文字列の内部文字配列(char[])を共有していました。

切り出された文字列は、元の配列への参照と「どの位置から何文字分か」というオフセット情報だけを持っていたのです。

この仕組みは、メモリコピーが発生しないため高速であるというメリットがありましたが、一方で「巨大な文字列から数文字だけ切り出した場合、元の巨大な文字列のメモリが解放されない」というメモリリークに近い問題を引き起こしていました。

現在の仕様(Java 7 Update 6以降)

現在のJavaでは、この問題に対処するため、substring が実行されるたびに内部で文字配列の新しいコピーを作成するように変更されました。

これにより、元の文字列が不要になればガベージコレクション(GC)によって正しくメモリが回収されます。

大量の文字列を扱うループ処理などで substring を多用する場合は、以前の仕様よりもメモリ割り当てのオーバーヘッドがわずかに増えていることを意識しておく必要がありますが、基本的には現在の安全な仕様が望ましいとされています。

サロゲートペア(絵文字など)を扱う際の注意

現代のアプリケーション開発において無視できないのが、絵文字や特殊な漢字などの「サロゲートペア」です。

Javaの String は内部的に UTF-16 を使用しており、通常の文字は 2バイト(1つの char)で表現されますが、絵文字などは 4バイト(2つの char)を消費します。

substring メソッドは char単位(インデックス単位)で処理を行うため、サロゲートペアの途中で切り出してしまうと、文字化けや不正なデータが発生する恐れがあります。

Java
public class EmojiExample {
    public static void main(String[] args) {
        // 絵文字を含む文字列(この絵文字は2チャ分消費する)
        String text = "Java☕"; 
        
        // "Java" は4文字、"☕" はサロゲートペアで2インデックス分
        System.out.println("文字列の長さ: " + text.length());

        // 誤って絵文字の途中で切ってしまう可能性
        // text.substring(0, 5); // 実行環境により結果が異なるが、不完全な文字になる危険がある
    }
}

サロゲートペアを安全に扱う必要がある場合は、codePointAt メソッドを使用したり、Java 8以降の codePoints() ストリームを活用して「コードポイント単位」で処理を行うなどの工夫が必要です。

substringの代替手段と関連メソッド

状況によっては、substring 以外のメソッドを使ったほうがコードが読みやすくなる場合があります。

String.split()

特定の区切り文字(カンマやスペースなど)で分割したい場合は、substring で位置を計算するよりも split() を使うのが一般的です。

Java
String csv = "apple,banana,orange";
String[] fruits = csv.split(","); // カンマで分割して配列にする

String.replace() / replaceAll()

文字列の一部を置換したいだけであれば、切り出しと結合を繰り返すのではなく、直接置換メソッドを使用しましょう。

StringBuilder.substring()

大量の文字列結合を伴う処理の中で切り出しを行う場合、StringBuilder クラスにも substring メソッドが存在します。

使い方は String クラスのものと同じですが、可変オブジェクトを扱っている際に便利です。

まとめ

Javaの substring メソッドは、文字列操作の基本中の基本でありながら、インデックスの指定方法やメモリの挙動、サロゲートペアへの対応など、深く理解すべきポイントが多いメソッドです。

基本ルール

開始位置(beginIndex)は含み、終了位置(endIndex)は含まない。

インデックス

0から始まり、範囲外指定は StringIndexOutOfBoundsException を投げる。

活用法

indexOf と組み合わせて動的な切り出しを行うのが効果的。

注意点

Java 7以降は新しいメモリコピーを作成する仕様。

サロゲートペアの分断には注意が必要。

これらの知識を整理して活用することで、バグが少なくメンテナンス性の高い文字列処理コードを書くことができるようになります。

日々のコーディングにおいて、まずは「開始位置と終了位置の関係性」を正しく意識することから始めてみてください。