オブジェクトのリストを取得し、オブジェクトに含まれる値に基づいてそれらをマップにグループ化する必要があることがよくあります。例えば。国別のユーザーとグループのリストを取得します。
このための私のコードは通常次のようになります。
Map<String, List<User>> usersByCountry = new HashMap<String, List<User>>();
for(User user : listOfUsers) {
if(usersByCountry.containsKey(user.getCountry())) {
//Add to existing list
usersByCountry.get(user.getCountry()).add(user);
} else {
//Create new list
List<User> users = new ArrayList<User>(1);
users.add(user);
usersByCountry.put(user.getCountry(), users);
}
}
しかし、私はこれが厄介であり、一部の教祖はより良いアプローチを持っていると考えることを余儀なくされます。これまでのところ、最も近いのは Google CollectionsのMultiMap です。
標準的なアプローチはありますか?
ありがとう!
Java 8では、 Map#computeIfAbsent()
を使用できます。
_Map<String, List<User>> usersByCountry = new HashMap<>();
for (User user : listOfUsers) {
usersByCountry.computeIfAbsent(user.getCountry(), k -> new ArrayList<>()).add(user);
}
_
または、Stream APIの Collectors#groupingBy()
を使用して、List
からMap
に直接移動します。
_Map<String, List<User>> usersByCountry = listOfUsers.stream().collect(Collectors.groupingBy(User::getCountry));
_
Java 7以下では、次のものが得られます:
_Map<String, List<User>> usersByCountry = new HashMap<>();
for (User user : listOfUsers) {
List<User> users = usersByCountry.get(user.getCountry());
if (users == null) {
users = new ArrayList<>();
usersByCountry.put(user.getCountry(), users);
}
users.add(user);
}
_
Commons Collections には LazyMap
がありますが、パラメータ化されていません。 Guava にはLazyMap
やLazyList
のようなものはありませんが、このように Multimap
を使用できますin 以下の多剤の回答 。
グアバの Multimap
は実際にこれに最も適したデータ構造であり、実際には Multimaps.index(Iterable<V>, Function<? super V,K>)
ユーティリティメソッドがあります。あなたが望む: Iterable<V>
(これは List<V>
である)を取り、 Function<? super V, K>
を適用するMultimap<K,V>
のキーを取得します。
ドキュメントの例を次に示します。
例えば、
List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde"); Function<String, Integer> stringLengthFunction = ...; Multimap<Integer, String> index = Multimaps.index(badGuys, stringLengthFunction); System.out.println(index);
プリント
{4=[Inky], 5=[Pinky, Pinky, Clyde], 6=[Blinky]}
あなたの場合、あなたはFunction<User,String> userCountryFunction = ...
を書くでしょう。
これを何度も行うようですので、テンプレートクラスを作成しました
public abstract class ListGroupBy<K, T> {
public Map<K, List<T>> map(List<T> list) {
Map<K, List<T> > map = new HashMap<K, List<T> >();
for (T t : list) {
K key = groupBy(t);
List<T> innerList = map.containsKey(key) ? map.get(key) : new ArrayList<T>();
innerList.add(t);
map.put(key, innerList);
}
return map;
}
protected abstract K groupBy(T t);
}
GroupByの実装を提供するだけです
あなたの場合
String groupBy(User u){return user.getCountry();}
コレクション値のマップを処理しなければならないとき、私はほとんど常にクラスに小さなputIntoListMap()静的ユーティリティメソッドを書くことになります。複数のクラスで必要な場合は、そのメソッドをユーティリティクラスにスローします。そのような静的メソッド呼び出しは少しいですが、毎回コードを入力するよりもずっときれいです。アプリでマルチマップがかなり中心的な役割を果たしていない限り、別の依存関係を取り込むことはおそらく価値がありません。
lambdaj を使用すると、次のように1行のコードで結果を取得できます。
Group<User> usersByCountry = group(listOfUsers, by(on(User.class).getCountry()));
Lambdajは、非常に読みやすいドメイン固有の言語でコレクションを操作するためのその他の多くの機能も提供します。
GCライブラリの LinkedHashMultimap で正確なニーズが満たされているようです。依存関係に対応できる場合、すべてのコードは次のようになります。
SetMultimap<String,User> countryToUserMap = LinkedHashMultimap.create();
// .. other stuff, then whenever you need it:
countryToUserMap.put(user.getCountry(), user);
挿入順序は維持され(リストで行っているように見えるすべてについて)、重複は排除されます。もちろん、必要に応じてプレーンハッシュベースのセットまたはツリーセットに切り替えることができます(またはリストですが、それは必要なものではないようです)。ユーザーのいない国を要求したり、全員がポニーを取得したりすると、空のコレクションが返されます。つまり、APIをチェックしてください。それはあなたのために多くのことをするので、依存関係はそれの価値があるかもしれません。
要素を追加するクリーンで読みやすい方法は次のとおりです。
String country = user.getCountry();
Set<User> users
if (users.containsKey(country))
{
users = usersByCountry.get(user.getCountry());
}
else
{
users = new HashSet<User>();
usersByCountry.put(country, users);
}
users.add(user);
containsKey
とget
の呼び出しは、単にget
を呼び出してnull
の結果をテストするよりも遅くないことに注意してください。
Map<String, List<User>> usersByCountry = new HashMap<String, List<User>>();
for(User user : listOfUsers) {
List<User> users = usersByCountry.get(user.getCountry());
if (users == null) {
usersByCountry.put(user.getCountry(), users = new ArrayList<User>());
}
users.add(user);
}