54601


javaの初心者です。  
以下のようなデータを保持したい場合、どのようにデータを保持し、追加していくのがいいのでしょうか。  
追加の際には、「name」と「user」が一つずつ処理され、変数に貯めたいです。  
name、userともにString型になります。  
nameやuserに関しては可変長です。  
nameが重複した際にはすでに存在しているnameのuserとして追加したいです。

※複数のnameに対して、userが所属することもある  
※name内でuserは一意になる

[code]

    {
      name_1  : [user_1,user_2,user_4....user_n],
      name_2 : [user_1, user_5....user_n],
      ... : [user_1, user_10, ....user_n ],
      name_n : [user_3, user_5, ....user_n ]
    }
    
[/code]

データの蓄積のイメージ(例)  
1\. {}  
2\. {name_1 : [user_1]}  
3\. {name_1 : [user_1,user_2]}  
4\. {name_1 : [user_1,user_2], name_3 : [user_4]}

ご教示いただけますと幸いです。


CC BY-SA 4.0
2019-04-29T07:48:27.470
54560
2019-04-30T08:03:04.493
2019-04-30T05:27:17.947
30673
30673
question
java
javaのデータの扱い(Map?)について


`user` がコレクションの中で一意であることを保証するためには、`Set`を使用するのが良いでしょう。  
また、その`Set`が`name`毎に1つ存在することを表すのには`Map`を使用します。

そのため、基本的に`Map<String, Set<String>>`という型を使用することになるでしょう。

一番簡単な方法としては、この型をそのまま使うことです。

[code]

    public class Main {
    
        public static void main(final String[] args) {
            final Map<String, SortedSet<String>> map = new HashMap<>();
    
            {
                final SortedSet<String> name1Users = new TreeSet<>();
                name1Users.add("user_1");
                map.put("name_1",  name1Users);
            }
    
            {
                final SortedSet<String> users = map.get("name_1");
                users.add("user_2");
    
                map.get("name_1").forEach(System.out::println);
            }
    
            {
                // "name_3" という key の value がなかった時(value が nullだった時)に、
                // 第二引数で初期値を設定し、その値を返す
                final SortedSet<String> users = map.computeIfAbsent("name_3", k -> new TreeSet<>());
                users.add("user_4");
    
                map.get("name_3").forEach(System.out::println);
            }
        }
    }
    
[/code]

ただし、この方法だと`Map`や`Set`のメソッドを直接実行することになるため、各処理で何をやっているのかがわかりづらい場合があります。

2つ目の方法は、このコレクション型をラップした(メンバーに持つ)クラスを定義することです。

※このようなクラスを「ファーストクラスコレクション」と呼びます

[code]

    class UserContainer {
    
        private final Map<String, SortedSet<String>> userMap = new HashMap<>();
    
        public SortedSet<String> getUsers(String name) {
            return userMap.getOrDefault(name, new TreeSet<>());
        }
    
        public void addUser(String name, String user) {
            SortedSet<String> users = userMap.computeIfAbsent(name, k -> new TreeSet<>());
    
            users.add(user);
        }
    
        /**
         * user が所属している name の一覧を返す
         */
        public List<String> getNames(String user) {
            return userMap
                    .entrySet()
                    .stream()
                    .filter(entry -> entry.getValue().contains(user))
                    .map(Map.Entry::getKey)
                    .sorted()
                    .collect(Collectors.toList());
        }
    }
    
[/code]

こちらのメリットは

  1. メソッド名に意味の分かりやすい名前を付けられる
  2. 上記のように`getNames()`のような処理を定義できる  
(毎回このような処理を使う側で書く必要がない)

等が挙げられます。

* * *

  * `user` がソートされるものとして `SortedSet`(`TreeSet`)を使用していますが、ソートされている必要がなければ、`Set`(`HashSet`)を使用してください。
  * Java SE 8以降を前提としてstreamを使用していますので、わからない場合は調べたり、質問したりしてください。


CC BY-SA 4.0
2019-04-30T07:40:38.973
54601
2019-04-30T08:03:04.493
2019-04-30T08:03:04.493
3068
3068
54560
answer