リストまたはセットに特定の条件に一致するオブジェクトが少なくとも1つ含まれているかどうかを確認する必要があるのは非常に一般的なことのようですが、以前の検索と読み取りでは、満足できるベストプラクティスやデザインパターンを見つけられませんでした。私が考えている状況では、実行するテストが問題のクラスのequalsメソッドに厳密に基づいていないため、「contains」を使用するだけでは不十分です。
私が見たいくつかのこと:
_// for-each with break
private boolean doFoosHaveGoodQuality(List<Foo> candidates) {
boolean foundIt = false;
for (Foo item: candidates) {
if (<< item passes some test >>) {
fountIt = true;
break;
}
}
return foundIt;
}
// while-do with index counter
private boolean doFoosHaveGoodQuality(List<Foo> candidates) {
boolean foundIt = false;
int index = 0;
while(!fountIt && index < candidates.size() - 1) {
Foo item = candidates.get(index++);
if (<< item passes some test >>) {
fountIt = true;
}
}
return foundIt;
}
_
これらは両方とも、仕事を完了し、成功が見つかるとすぐに終了します。希望する項目をリストの早い段階で見つけることができれば、時間を節約できます。ただし、どちらも誤った結果を取得するためにリスト全体をループする必要があります
状況によっては、元の_List<Foo>
_が同時に生成(つまり、データベースから取得)されて、同時に_Map<FooKey, Foo>
_が生成されるというテクニックもあります。この手法では、FooKey
は、「望ましい品質」を形成するFoo
の属性のみを保持する追加の(多くの場合、内部)クラスであり、それに基づく等号およびハッシュコードメソッドをオーバーライドします。目的のFooが存在するかどうかを判断する必要がある場合は、正確にnew FooKey()
インスタンスを作成し、マップ上でhasKey()
を呼び出します。前もって余分な労力が必要ですが、必要なときにすばやく簡単にできます。
指をかけられない。それぞれのアイデアは機能しますが、それぞれが少しハックで非効率的だと感じます。
アルゴリズム的には、for-eachおよびwhile-with-indexingバージョンはlinear searchを実装します。これは、小さなコレクションの場合や、このようなチェックをたまにしか行わない場合には問題ありませんが、プログラムの重要な構成要素である場合は、線形検索とは根本的に異なる何かを行うことによってこれらのクエリを高速化する専用のマッピング構造が必要です。例には、バイナリ検索ツリー(一貫した順序付けを定義する必要がある場合)とハッシュテーブル(一貫したハッシュ関数を定義する必要がある場合)が含まれます。ほとんどの述語ではこれは取るに足らないことであり、他の述語ではいくつかの追加コード(FooKey
を定義する)であり、他の述語では難しいか完全に実用的ではありません。これは、検索基準に応じた判断の呼びかけです。
線形検索を決定した場合、それを実装するためのいくつかのオプションがあります(適切な実装がまだない場合は、そうすることをお勧めします)。 for-eachバリアントは非常に一般的で、IMHOのエレガントです。イテラブルで機能します。具体的なコレクションで裏打ちする必要もありません(Java 8のストリームを参照)。一方、インデックス付きのwhileループは非常に制限されています。ランダムアクセスを実行できないコンテナ(リンクされたリストなど)の場合は攻撃的に遅く、インデックスを作成できない場合はまったく機能しません。 neverこれをお勧めします。おそらく他にもバリエーションがありますが、さらに特殊化されています。線形検索を実装する場合は、反復可能オブジェクトに実装してください。
もう1つの問題は、述語の指定方法です。あなたはできます本質的に同じループを何度も何度も繰り返しますが、条件だけが異なります。しかし、これはエラーが発生しやすく、冗長で、非効率的です。このループのコピーはoneのみで、条件はパラメーターとして渡されます。言語(バージョン)でこれを実行できる場合は、ファーストクラスの関数/ラムダを使用します。それ以外の場合は、インターフェースと匿名クラスを使用してエミュレートできます。これを実行すると、Map
sは魅力の一部を失います。map.get(whatIWant)
に対して長いループではなく、list.find(whatIWant)
になります。もちろん、それらにはまだ大規模なコレクションのパフォーマンス上の利点があります。
補足:ループは少し単純にすることができます。たとえば、for-eachループは次のように記述します。
private boolean doFoosHaveGoodQuality(List<Foo> candidates) {
for (Foo item: candidates) {
if (<< item passes some test >>) {
return true;
}
}
return false;
}
どちらの提案も不必要に冗長であり、入力タイプが具体的すぎます。 Java 8より前は、次のように記述します。
private boolean doFoosHaveGoodQuality(Iterable<Foo> candidates) {
for (Foo item: candidates) {
if (<< item passes some test >>) return true;
}
return false;
}
Java 8では、これらの退屈なループは不要です:
private boolean doFoosHaveGoodQuality(Collection<Foo> foos) {
return foos.stream().anyMatch(foo -> ... some test ... );
}
問題に適切な解決策がない場合は、別の問題を解決してください。
コレクションを反復処理する代わりに、要素が追加または削除されるたびに必要な情報を追跡する別のクラスでそのコレクションをラップします。 Javaが提供するコレクションインターフェースが、あなたが満たす必要のあるすべてのニーズを満たしていると想定することを間違えないでください。
基本的なケースでは、新しいクラスには2つのオブジェクトがあります。元のリストと、気になるプロパティの新しいセットフィールドです。もちろん、これは少数のプロパティのみをクエリすることを前提としています。
つまり、内部コンテナでの変更操作を許可する必要がありますnever。 Java.util.Collectionsには、さまざまなコレクションの変更不可能なビューを返すユーティリティ関数がいくつかあり、呼び出し元の移植を容易にしますが、たとえばイテレータを介しては、一部の呼び出し元が使用する機能であり、実装する必要があります。