Javaのcontainsメソッド完全ガイド|String・List・Setの使い方と注意点

1. Javaのcontainsを正しく使うための全体像

【結論】containsは「含まれているか」をbooleanで返す便利な判定です。対象がStringかコレクションかで意味と注意点が変わります。

Javaで「入力に禁止語があるか」「リストに指定値が登録済みか」を調べるとき、最初に候補になるのがcontainsです。ただし、部分一致、完全一致、正規表現、大小文字の扱いを混同すると、期待と異なる結果になります。この記事ではString.containsCollection.containsを分けて、実行・テスト・デバッグまでつながる形で整理します。

1.1 containsが返す値

【結論】一致すればtrue、一致しなければfalseです。見つかった位置や件数は返さないため、必要なら別のAPIを選びます。

String.containsは文字列の一部分を探します。List.containsSet.containsは、要素が存在するかを調べます。同名でも、比較対象が異なることを先に確認しましょう。

String.containsの部分一致とList.containsの要素一致を比較した図

2. String.containsで部分一致を判定する

【結論】String.containsは連続した文字列が含まれるかを判定します。大文字・小文字と空白も比較対象です。

String message = "Java development starts here";

boolean hasJava = message.contains("Java");
boolean hasPython = message.contains("Python");

System.out.println(hasJava);   // true
System.out.println(hasPython); // false

2.1 indexOfとの使い分け

【結論】真偽だけが必要ならcontains、位置も必要ならindexOfが読みやすい選択です。

int position = message.indexOf("development");
if (position >= 0) {
    System.out.println("開始位置: " + position);
}

indexOfは見つからないと-1を返します。存在確認だけのコードで>= 0を毎回書くより、意図が明確なcontainsを使うと条件式を読み違えにくくなります。

2.2 大文字・小文字を区別しない判定

【結論】containsに大小文字を無視する機能はありません。文字列を正規化して比較します。

import java.util.Locale;

boolean containsJavaIgnoreCase = message
        .toLowerCase(Locale.ROOT)
        .contains("java");

Locale.ROOTを指定すると、実行環境の既定ロケールに比較結果が左右されにくくなります。ユーザー向け検索では、全角・半角やUnicode正規化まで必要になる場合があるため、要件に合わせて設計してください。

3. null・空文字・正規表現で起きる失敗

【結論】空文字は常に含まれると判定され、nullは例外になります。正規表現はcontainsでは解釈されません。

3.1 nullを安全に扱う

【結論】値がnullになり得るなら、入力境界で検証するか、明示的に条件分岐します。

String keyword = null;

if (keyword != null && message.contains(keyword)) {
    System.out.println("見つかりました");
}

次のコードは実行時に失敗します。

message.contains(null);

例外の一例です。

Exception in thread "main" java.lang.NullPointerException
    at java.base/java.lang.String.contains(String.java:...)

3.2 空文字と正規表現の違い

【結論】"abc".contains("")trueです。また"\d+"は数字パターンではなく文字として探されます。

数字を含むかを調べるなど、正規表現が必要な場合はPatternをコンパイルして使います。

import java.util.regex.Pattern;

Pattern digit = Pattern.compile("\\d+");
boolean hasDigit = digit.matcher("item42").find();

String.matchesは文字列全体がパターンに合うかを判定します。部分一致が必要ならMatcher.find()が適しています。

4. ListとSetのcontainsはequalsで比較する

【結論】コレクションのcontainsは、基本的に要素のequalsで存在を確認します。独自クラスでは実装漏れが原因になりがちです。

import java.util.List;

List<String> languages = List.of("Java", "Kotlin", "Scala");
System.out.println(languages.contains("Java")); // true

4.1 独自クラスで一致しない理由

【結論】別インスタンスでも同じ値として扱いたいなら、equalshashCodeを一緒に実装します。

Javaのrecordは値ベースのequalshashCodeを自動生成するため、単純なデータを表す用途では安全な選択肢です。

record User(String id, String name) {}

var users = List.of(new User("u-1", "Aki"));
System.out.println(users.contains(new User("u-1", "Aki"))); // true

4.2 ListとSetの選び方

【結論】順番や重複が必要ならList、存在確認を多用し重複を避けたいならSetを検討します。

ArrayList.containsは先頭から比較するため、要素数に応じて時間が増えます。HashSet.containsは適切なhashCodeが実装されている場合、通常は高速に存在確認できます。ただし、順序保証やメモリ使用量の要件も比較してください。

5. コンパイル・実行・テストで確認する

【結論】小さな再現コードをJDKで動かし、境界値をJUnitで固定すると、containsの誤用を早期に見つけられます。

public class ContainsExample {
    public static void main(String[] args) {
        String text = "error: connection timeout";
        System.out.println(text.contains("timeout"));
    }
}
javac ContainsExample.java
java ContainsExample

JDKのインストール状況は次で確認できます。

java -version
javac -version

javacが見つからない場合、JREだけではコンパイルできません。使用しているJDKディストリビューションとPATH設定を確認してください。

5.1 MavenとGradleでJUnitを実行する

【結論】既存プロジェクトではビルドツール経由でテストを実行し、依存関係のバージョンはプロジェクトの設定に合わせます。

mvn test
./gradlew test

JUnit 5のテスト例です。

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;

class ContainsTest {
    @Test
    void checksExpectedSubstrings() {
        String text = "Java 21";
        assertTrue(text.contains("Java"));
        assertFalse(text.contains("java"));
    }
}

6. 期待と違うときのデバッグ手順

【結論】値を引用符付きで出力し、null・空白・大文字小文字・入力文字コードを順に確かめます。

System.out.printf("text=[%s], length=%d%n", text, text.length());
System.out.printf("keyword=[%s], length=%d%n", keyword, keyword.length());

デバッガでは、containsを呼ぶ直前にブレークポイントを置き、textkeywordの実際の値を確認します。外部ファイルを読むコードでは、文字コードを明示します。

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

String text = Files.readString(Path.of("input.txt"), StandardCharsets.UTF_8);

文字化けしている入力に対してcontainsを実行しても、判定ロジックの問題とは限りません。読み込み時点のバイト列と指定した文字コードを確認するのが近道です。

7. よくある質問

【結論】containsは用途を絞れば単純です。対象の型と比較規則を確認すると、多くのトラブルを防げます。

7.1 String.containsは大文字・小文字を区別しますか?

【結論】はい。String.containsは文字列をそのまま比較するため、Javaとjavaは一致しません。必要ならLocale.ROOTを指定して両方を小文字化してから比較します。

7.2 containsにnullを渡すとどうなりますか?

【結論】String.contains(null)はNullPointerExceptionになります。値がnullになり得るなら、比較前にnullを扱う方針を決めます。

7.3 containsは正規表現を使えますか?

【結論】使えません。正規表現で条件を表したい場合はPatternまたはString.matchesを使います。matchesは文字列全体を判定する点に注意してください。

7.4 List.containsの比較方法は何ですか?

【結論】List.containsは要素のequalsで比較します。独自クラスでは、同一性ではなく値で比較したい場合にequalsとhashCodeを整合させて実装します。

7.5 部分一致で検索するときの文字コードは意識すべきですか?

【結論】Stringの比較自体はUnicodeの文字列に対して行われます。ただし、ファイルやHTTP入力を誤った文字コードで読み込むと、比較前の文字列が壊れるため入出力時のCharset指定が重要です。

7.6 大量データでcontainsが遅いときはどうしますか?

【結論】同じ条件で繰り返し調べるなら、ListではなくHashSetを検討します。順序が必要か、重複を許すかも合わせて選びます。

8. まとめ

【結論】部分一致にはString.contains、要素の存在確認にはコレクションのcontainsを使い分けます。null、大小文字、equals、性能を要件に沿って確認しましょう。

真偽だけで十分か、位置や正規表現が必要かを最初に決めると、API選択が明確になります。まず小さなテストで境界値を確認し、実際の入力・実行環境で再現できる状態を保つことが、安全なJava開発につながります。