
Javaのcontainsとは?containsで「含む」をチェックする方法
String、配列、ListやSet、Mapに、文字列や値、インスタンスが「含まれているか」の確認は、プログラムでは頻繁に行います。
そして、必要な時に限って「えーっと、どうやってやるんだっけ?」となったりしませんか? どうしても、ついつい忘れてしまうと思います。
でも、もう大丈夫です。この記事では、みんながちょっと忘れがちな「何かが何かに含まれているかの調べ方」を、分かりやすくお伝えします!!
※この記事のサンプルは、Java 10の環境で動作確認しています
目次
1.containsは「含んでいるか?」の共通キーワード
プログラミングでは、何かが何かを含んでいるか…の確認を良く行います。
例えば、文字列に“ERROR”が含まれていればエラー処理をする、数値の配列が異常値を含んでいればエラーとする、などです。
Javaで「含んでいるか?」を知る時には、“contains”という単語を含むメソッドを呼び出して判断します。判断結果は、trueなら含んでいる、falseなら含んでいない、で共通しています。
以下では、String、配列、ListやSetなどのCollection、Mapが何かを含んでいるかを調べる方法をお伝えします。
2.Stringが含んでいるか?
2-1.String.containsで探す
String.containsは、文字列が引数の文字列を含んでいるか確認するメソッドです。含んでいるならtrue、含んでいなければfalseを戻します。
public boolean contains(CharSequence s) パラメータ: s - 検索するシーケンス 戻り値: この文字列がsを含む場合はtrue。そうでない場合はfalse
String s = "ABCDあいうえお"; if (s.contains("CD")) { System.out.println("含まれています"); }
2-2.String.indexOfで探す
String.containsの代わりに、文字列が最初に出現する位置を戻すindexOfで探してもいいでしょう。
indexOfの戻り値は、文字列中に、探している文字列が出現する位置の数値です(0始まり)。戻り値が-1でなければ、探している文字列を含んでいるということです。
public int indexOf(String str) パラメータ: str - 検索対象の部分文字列。 戻り値: 指定された部分文字列が最初に出現する位置のインデックス。そのような出現箇所がない場合は-1。
String s = "ABCDあいうえお"; int index = s.indexOf("CD"); if (index != -1) { System.out.println("含まれています"); }
indexOfを使うと、文字列を抽出(substring)したい場合などに便利です。また、indexOfには開始位置を指定できるものや、文字列の後ろから探すものもあります。
このindexOfは、以下の記事でも詳しく説明していますので、ぜひ参考にしてください。
関連記事2-3.String.matchesで探す
String.matchesを使うと「正規表現」での検索ができます。ですので、String.matchesは「含んでいるか」にも使えたりします。
正規表現と呼ばれる機能を使うと、文字列の検索を効率化できます。正規表現は少し難しいですが、一度覚えればプログラムの効率化に抜群の効果がありますし、他のプログラミング言語でも大体同じように使えるので一石二鳥です。
それに、containsやindexOfは決まった文字列しか探せません。例えば「“abc”の後ろに数字0~9のどれかがある」という確認を一度にできません。検索したい文字列で何度もメソッド呼び出しを繰り返すしかなく、プログラムが長くなりがちです。でも、正規表現ならあっさりとできます。
public boolean matches(String regex) パラメータ: regex - この文字列との一致を判定する正規表現 戻り値: この文字列が指定された正規表現と一致する場合にだけ、trueが返される
String s = "ABCDあいうえお"; // 文字列全体のどこかに"CD"または"うえ"のどちらがが含まれていればtrue if (s.matches(".*(CD|うえ).*")) { System.out.println("含まれています"); } // .*を前後に入れないとマッチしない。 // 以下では、文字列全体が"CD"か"うえ"の時にだけマッチするため。 if (s.matches("(CD|うえ)")) { System.out.println("含まれています"); }
要注意なのは、「含んでいるか」とするなら、正規表現の最初と最後に“.*”を付ける必要があることです。String.matchesは文字列全体とマッチングするので、「文字列全体のどこかにある」という意味にしなければならないからです。
これだと少し不便なので、シンプルに「含んでいる」を確認したいなら、Javaの正規表現に関するクラス(Pattern、Matcher)の使い方を覚える必要があります。それについては後述します。
3.配列が含んでいるか?
3-1.for文で探す
最も基本的なやり方で、配列全体をループして値を探します。比較する条件は自分で書くので、等しい、以上、以下、業務ロジックに則った値、いろいろできます。
String[] arr = {"AB", "CD", "EF", "GH"}; boolean contains = false; for (String s : arr) { if (s.equals("CD")) { contains = true; break; } } if (contains) { System.out.println("含まれています"); }
とてもお手軽ですが、配列の要素が多くなればなるほど、確認に時間がかかります。場合によっては、後ろから探したり、途中から探したりするなどの工夫が必要になるかもしれません。
3-2.Arrays.asListでListに変換して探す
もっとシンプルに、配列が特定の値やインスタンスと等しいものを持つか確認するだけでいいなら、List.containsを使ってもいいでしょう。
List.containsを使うと、List中に「含まれているか」の確認ができます。ですので、Arrays.asListで配列からListを作れば、含まれているかの確認が簡単にできます。
boolean contains(Object o) パラメータ: o - このリスト内にあるかどうかが判定される要素 戻り値: 指定された要素がこのリスト内にある場合はtrue
String[] arr = {"AB", "CD", "EF", "GH"}; List<String> l = Arrays.asList(arr); if (l.contains("CD")) { System.out.println("含まれています"); }
なお、List.containsは、List上の各要素へequalsを呼び出して、探しているものと等しいかどうかを判断します。これには注意が必要です。
つまり、自分で作ったクラスの場合は、equalsをオーバーライドしていないと、List.containsでの判断が出来ないということです。標準APIにあるクラス(String、Integerなど)はきちんとequalsが作られているので問題ありません。
4.Collection(List/Set/Queue/Stack)が含んでいるか?
4-1.Collection.containsで探す
List/Set/Queue/StackなどのCollectionにはcontainsというメソッドがあり、指定されたインスタンスを含んでいるかが分かります。
boolean contains(Object o) パラメータ: o - このコレクション内にあるかどうかが判定される要素 戻り値: 指定された要素がこのコレクション内にある場合はtrue
では、List/Set/Queue/Stackのcontainsを使ってみましょう。
List<String> l = Arrays.asList("AB", "CD", "EF", "GH"); Set<String> s = new HashSet<>(l); Queue<String> q = new ArrayDeque<>(l); Stack<String> st = new Stack<>(); l.forEach(e -> st.push(e)); if (l.contains("CD")) { System.out.println("Listに含まれています"); } if (s.contains("CD")) { System.out.println("Setに含まれています"); } if (q.contains("CD")) { System.out.println("Queueに含まれています"); } if (st.contains("CD")) { System.out.println("Stackに含まれています"); }
4-1-1.大事なのはequalsがちゃんと作られているか
Collection.containsで重要なのは、Collectionに入れてあるクラスでequalsがちゃんと作られているかです。
Collectionの中に持っているインスタンスと、containsで指定されたインスタンスが同じかはObject.equalsの結果で判定されます。ですので、equalsがしっかりとオーバーライドされていなければcontainsは動きません。
import java.util.ArrayList; import java.util.List; class NotOverrideEquals { int i; NotOverrideEquals(int i) { this.i = i; } // このようにequalsが実装されていれば、想定通りに動く // public boolean equals(Object obj) { // return this.i == ((NotOverrideEquals)obj).i; //} public static void main(String[] args) { List<NotOverrideEquals> l = new ArrayList<>(); l.add(new NotOverrideEquals(1)); l.add(new NotOverrideEquals(2)); l.add(new NotOverrideEquals(3)); // equalsをオーバーライドしていないので、「同じ」インスタンスだと判断できない boolean b = l.contains(new NotOverrideEquals(2)); System.out.println(b); // → false } }
Javaの標準APIのクラスなら大抵は大丈夫ですが、自作のクラスを使う場合は、きちんとequalsをオーバーライドしているか確認しましょう。
4-2.Collection.containsAllで探す
Collectionが、調べたいすべてのインスタンスを含むか確認したいなら、一度に調べるためのメソッドcontainsAllが使えます。containsAllを使えば、プログラムがより分かりやすくなります。
全てのインスタンスが含むか、ループをしてcontainsで調べてもいいのですが、そのようなプログラムを自分で書く必要はないのです。
boolean containsAll(Collection<?> c) パラメータ: c - このコレクションにあるかどうかを調べるコレクション 戻り値: 指定されたコレクションのすべての要素がこのコレクション内にある場合はtrue
調べたいインスタンスを持っているCollection(ListなどでOKです)を作り、containsAllを呼び出しましょう。
List<String> v = Arrays.asList("AB", "GH"); // 調べたい値 List<String> l = Arrays.asList("AB", "CD", "EF", "GH"); Set<String> s = new HashSet<>(l); Queue<String> q = new ArrayDeque<>(l); Stack<String> st = new Stack<>(); l.forEach(e -> st.push(e)); if (l.containsAll(v)) { System.out.println("Listに全て含まれています"); } if (s.containsAll(v)) { System.out.println("Setに全て含まれています"); } if (q.containsAll(v)) { System.out.println("Queueに全て含まれています"); } if (st.containsAll(v)) { System.out.println("Stackに全て含まれています"); }
5.Mapが含んでいるか?
Mapは、キーと値をペアにして持つもので、プログラムでは大変良く使います。
Mapが何かを含むか確認したい時は、キーが含むか、値が含むかの二つのパターンがあります。
なお、Mapを使う上での前提条件ではありますが、キーとしては最低限Object.equalsをきちんと実装しているクラスでなければなりません。ものによっては、Object.hashCodeもきちんと実装している必要があります(HashMapを使う場合など)。
5-1.Map.containsKeyでキーを探す
Map.containsKeyで、Mapのキーにその値を含んでいるかを確認できます。
boolean containsKey(Object key) パラメータ: key - このマップ内にあるかどうかが判定されるキー 戻り値: 指定されたキーのマッピングがこのマップに含まれている場合はtrue
Map<String, String> m = new HashMap<>(); m.put("AB", "あ"); m.put("CD", "い"); m.put("EF", "う"); m.put("GH", "え"); m.put("IJ", "あ"); if (m.containsKey("CD")) { System.out.println("含まれています"); }
5-2.Map.containsValueで値を探す
Map.containsKeyで、Mapの値がその値を含んでいるかを確認できます。
boolean containsValue(Object value) パラメータ: value - このマップにあるかどうかが判定される値 戻り値: このマップが1つまたは複数のキーを指定された値にマッピングしている場合はtrue
Map<String, String> m = new HashMap<>(); m.put("AB", "あ"); m.put("CD", "い"); m.put("EF", "う"); m.put("GH", "え"); m.put("IJ", "あ"); if (m.containsValue("あ")) { System.out.println("含まれています"); }
5-3.Mapのキーか値に複数の値を含んでいるか一度に探す
Mapのキーか値に複数の値が含まれるか一度に探したい場合、ズバリそのものなメソッドはないので、SetやListに変換してからcontainsAllをするのが速いでしょう。
MapのキーをSetにするにはMap.keySetを、値をCollectionにするにはMap.valuesを使います。それぞれの結果に対して、containsAllをしてあげれば良いのです。
List<String> v1 = Arrays.asList("CD", "GH"); List<String> v2 = Arrays.asList("あ", "う"); Map<String, String> m = new HashMap<>(); m.put("AB", "あ"); m.put("CD", "い"); m.put("EF", "う"); m.put("GH", "え"); m.put("IJ", "あ"); Set<String> keySet = m.keySet(); Collection<String> valueList = m.values(); if (keySet.containsAll(v1)) { System.out.println("キーに全て含まれています"); } if (valueList.containsAll(v2)) { System.out.println("値に全て含まれています"); }
6.【応用】便利・高速な検索の仕方
6-1.文字列を正規表現で探す
java.util.regex.Matcherのfindを使うと、正規表現にマッチする文字列を「含んでいるか」を確認できます。Matcherは文字列が正規表現にマッチするか調べるクラスです。
Matcherを使うには、java.util.regex.Patternと組み合わせ、以下のようにします。Patternは、どういう正規表現かを表すクラスです。
- java.util.PatternのインスタンスをPattern.compileで作る
- 検索元の文字列をPattern.matcherで指定して、Matcherのインスタンスを作る
- 作ったMatcherでfindやmatchesを実行する
少々手順は多いですが、その分Javaでの正規表現のパワーをフルに使えます。それに、Patternは作ったものの使いまわしが出来ますので、調べなければならない文字列がたくさんある場合に便利です。
String s = "ABCDあいうえお"; Pattern p = Pattern.compile("CD|うえ"); // "CD"または"うえ"のどちらか Matcher m = p.matcher(s); // "CD"または"うえ"のどちらがが含まれていればtrue if (m.find()) { System.out.println("含まれています"); }
Javaでの正規表現の使い方や活用方法は、java.util.PatternのJavadocや、以下の記事も参考にしてみてください。
関連記事6-2.配列・CollectionからStreamで探す
Streamの終端操作anyMatchを使うと、Streamが条件を満たすものを含むか確認できます。Streamの中に、引数のPredicate.testがtrueを戻すものが1つでもある場合はtrueが戻り、そうでなければfalseです。
下記はシンプルな例なので、Collection.containよりも面倒に思えかもしれません。ですが、複雑な条件での抽出をする場合は、こちらはループの処理がいらないので比較する処理に集中できますね。
String[] arr = {"AB", "CD", "EF", "GH"}; boolean b = Arrays.stream(arr).anyMatch(e -> "CD".equals(e)); System.out.println(b); // → true
List<String> l = Arrays.asList("AB", "CD", "EF", "GH"); boolean b = l.stream().anyMatch(e -> "CD".equals(e)); System.out.println(b); // → true
条件を満たすものが一つもないか確認したい場合は、noneMatchを使います。
String[] arr = {"AB", "CD", "EF", "GH"}; boolean b = Arrays.stream(arr).noneMatch(e -> "XX".equals(e)); System.out.println(b); // → true
List<String> l = Arrays.asList("AB", "CD", "EF", "GH"); boolean b = l.stream().noneMatch(e -> "XX".equals(e)); System.out.println(b); // → true
6-3.Arrays.binarySearchで高速に探す
とても大きなサイズの配列の中から探さなければならないなら、java.util.ArraysのbinarySearchを活用できる場合があります。二分探索(バイナリサーチ)のアルゴリズムにより、高速に検索できます。
ただし、以下のArrays.binarySearchを使うには条件があります。
- 配列の要素があらかじめソートされていなければならない
- (Object版のみ)Comparableなクラスの配列でなければならない
public static int binarySearch(Object[] a, Object key) パラメータ: a - 検索される配列 key - 検索される値 戻り値: 配列内に検索キーがある場合は検索キーのインデックス。それ以外の場合は(-(挿入ポイント) - 1)。 挿入時点は、そのキーが配列に挿入される時点として定義される。つまり、そのキーよりも大きな最初の要素のインデックス。配列内のすべての要素が指定されたキーよりも小さい場合はa.length。 これにより、キーが見つかった場合にのみ戻り値が>= 0になることが保証される。
公式Javadocの日本語訳は少々分かりづらいのですが、私たちの使い方なら、戻り値が0以上ならその値が配列中にある、と分かっていれば十分です。
String[] arr = {"CD", "AB", "GH", "EF"}; Arrays.sort(arr); if (Arrays.binarySearch(arr, "CD") >= 0) { System.out.println("含まれています"); }
なお、Arrays.binarySearchには、Comparatorを使うものもあります。こちらなら、Comparableなクラスではない配列も扱えます。この場合でも、binarySearchを呼び出す前にソートが必要なのは変わりません。
public static <T> int binarySearch(T[] a, T key, Comparator<? super T> c) パラメータ: a - 検索される配列 key - 検索される値 c - 配列が順序付けされるコンパレータ。 null値は、要素の自然順序付けが使用されることを示す。 戻り値: ※Comparable版と同じなので省略
String[] arr = { "CD", "AB", "GH", "EF" }; Comparator<String> c = new Comparator<>() { public int compare(String str1, String str2) { return str1.compareTo(str2); // インスタンス同士の大小比較の結果を戻す処理を自分で書く } }; Arrays.sort(arr, c); if (Arrays.binarySearch(arr, "CD", c) >= 0) { System.out.println("含まれています"); }
Arrays.binarySearchにはintなどのプリミティブ型版もあります。例えば、intの配列では以下のように使います。
int[] arr = {5, 2, 3, 1, 4}; Arrays.sort(arr); if (Arrays.binarySearch(arr, 3) >= 0) { System.out.println("含まれています"); }
ちなみに、Arrays.binarySearchは配列内の検索開始位置・終了位置を指定できるものもあります。必要ならこれらも使いましょう。
public static int binarySearch(Object[] a, int fromIndex, int toIndex, Object key) public static <T> int binarySearch(T[] a, in fromIndex, int toIndex, T key, Comparator<? super T> c) パラメータ: fromIndex - 検索される最初の要素(これを含む)のインデックス toIndex - 検索される最後の要素(これを含まない)のインデックス
6-4.Collections.binarySearchで高速に探す
配列と同じように、値をたくさん持っているListをcontainsで調べるのは時間がかかります。containsは律儀に先頭から全て調べるからで、もし後ろの方にあったなら、それだけ時間が無駄になります。
配列にはそのためのArrays.binarySearchがあるように、ListにもCollections.binarySearchがあります。
Collections.binarySearchを使う時のルールも、Arrays.binarySearchと同じです。
- Listの要素があらかじめソートされていなければならない
- Comparableなクラスを格納するListでなければならない
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) パラメータ: list - 検索されるリスト。 key - 検索されるキー。 戻り値: リスト内に検索キーがある場合は検索キーのインデックス。それ以外の場合は(-(挿入ポイント) - 1)。 挿入時点は、そのキーがリストに挿入される時点として定義される。つまり、そのキーよりも大きい最初の要素のインデックス。リスト内のすべての要素が指定されたキーよりも小さい場合はlist.size()。 これにより、キーが見つかった場合にのみ戻り値が>= 0になることが保証される。
List<String> l = Arrays.asList("CD", "AB", "GH", "EF"); Collections.sort(l); if (Collections.binarySearch(l, "CD") >= 0) { System.out.println("含まれています"); }
Collections.binarySearchには、Comparatorを使うものも用意されています。こちらの場合は、Listに格納されているクラスはComparableでなくても大丈夫です。
public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c) パラメータ: list - 検索されるリスト。 key - 検索されるキー。 c - リストが順序付けされるコンパレータ。 null値は、要素の自然順序付けが使用されることを示す。 戻り値: ※Comparable版と同じなので省略
List<String> l = Arrays.asList("CD", "AB", "GH", "EF"); Comparator<String> c = new Comparator<>() { public int compare(String str1, String str2) { return str1.compareTo(str2); // インスタンス同士の大小比較の結果を戻す処理を自分で書く } }; Collections.sort(l, c); if (Collections.binarySearch(l, "CD", c) >= 0) { System.out.println("含まれています"); }
7.まとめ
この記事では、String、配列、Collection(List/Set等)、Mapについて、「含むかどうか」を確認する方法をお伝えしてきました。
基本的には、containsという名前かそれに近い名前のメソッドがあって、それぞれのクラスでの確認処理が行われるケースが多いです。
なお、この記事のサンプルでは、Javaの標準APIだけを用いました。Apache Commons CollectionsやLang、GoogleのGuavaなどの有名なライブラリでは、もっといろいろなことが簡単にできますので、興味があればチャレンジしてみましょう!
コメント