LoginSignup
0
0

More than 3 years have passed since last update.

【Java】1対NのListをMapに変換

Last updated at Posted at 2019-05-28

はじめに

例えば、DBから親子の2テーブルをjoinして抽出した結果をキー毎に処理をしたいとき。
Listのまま処理してもいいけどキーごとにグルーピングした方が見た目きれいですよね?(個人的には可読性が上がるので好きです。性能的に問題が出た場合は仕方ないですが。)
今回はいったん横並びのListで一括取得した後、Map(キー:親テーブル、値:子テーブル)に変換してみます。

前提

ER

部署マスタ

  • 部署id
  • 部署名

社員マスタ

  • 社員id
  • 社員名
  • 部署id(FK)

これを内部結合で取得するとこうなりますね。 ↓

select_all.sql
select *
from 部署マスタ
inner join 社員マスタ using 部署id ;

取得結果のイメージ(List<結果>) ↓

部署 社員
部署1 社員1
部署1 社員2
部署2 社員3
部署2 社員4
部署2

これを部署をキー、社員を値とした『Map<部署, List<社員>>』に変換することが今回の目的です。

実装

環境

  • Java8
  • Spring boot
  • Lombok

作ったもの

  • DemoService:メイン処理
  • DbMapper:実際はMybatisなどのORマッパー。
  • DbMapperImpl:テスト用に実装したDbMapper。
  • Result:select_all.sqlの結果を格納する
  • Department:部署テーブル
  • Employee:社員テーブル

Serviceクラス

一応、Java7バージョンのlist2Map7メソッドと、Java8(StreamAPI)のlist2Map8メソッド2つ作成しました。
どちらも結果は同じはず。
やっぱりStreamAPIでの実装はコード量も少なく見やすい。
ビジネスロジックと関係ない実装はあまりしたくないですね。
頑張ればlist2Map8ももっと見やすくなるかも?でもServiceクラス以外に可読性の低いコードが増えそうですね。

DemoService.java
package com.example.demo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class DemoService {

    @Autowired
    DbMapper mapper;

    public void execute(String[] args) {
        log.info("### START ###");
        // select from DB
        List<Result> list = mapper.select();
        // 抽出した中身
        list.forEach(System.out::println);
        // List ⇒ LinkedHashMap
        Map<Department, List<Employee>> map = list2Map8(list);
        System.out.println();
        // 変換結果を出力
        for (Map.Entry<Department, List<Employee>> entry : map.entrySet()) {
            System.out.println("Key:" + entry.getKey());
            entry.getValue().forEach(e -> System.out.println("    Value:" + e));
        }
        log.info("### END ###");
    }

    /**
     * List⇒Map変換(java7Ver).
     * 
     * @param list
     * @return
     */
    private Map<Department, List<Employee>> list2Map7(List<Result> list) {
        if (list.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<Department, List<Employee>> map = new LinkedHashMap<>();
        // 前回のキー
        Department prevDep = null;
        List<Employee> tempList = null;
        for (Result result : list) {
            Department dep = result.getDepartment();
            // キーが変わったら
            if (!dep.equals(prevDep)) {
                // 値リストを初期化
                tempList = new ArrayList<>();
                // 値が初期化された状態のマップに追加(mapに参照をセット)
                map.put(dep, tempList);
                // 今回のキーを前回キーとする
                prevDep = dep;
            }
            // mapを参照しているtempListに値を追加
            tempList.add(result.getEmployee());
        }
        return map;
    }

    /**
     * List⇒Map変換(java8Ver).
     * 
     * @param list
     * @return
     */
    private Map<Department, List<Employee>> list2Map8(List<Result> list) {
        // List(値)⇒Map(キー、値)に変換
        Map<Department, List<Employee>> ret = list.stream()
                .collect(Collectors.groupingBy(Result::getDepartment,
                LinkedHashMap::new, Collectors.mapping(Result::getEmployee, Collectors.toList())));
        return ret;
    }
}

取得順を保持するためLinkedHashMapを使用しています。
HashMapでは保持されません。
(コメントいただいて、変換メソッド改良しました。ありがとうございます!)

DbMapperインタフェース

実際はMybatisとかORマッパーを使う感じ。

DbMapper.java
package com.example.demo;

import java.util.List;
import org.springframework.stereotype.Component;

@Component
public interface DbMapper {
    List<Result> select();
}

DbMapperImplクラス

デバッグ用データを返す。

DbMapperImpl.java
package com.example.demo;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Component;

@Component
public class DbMapperImpl implements DbMapper {
    @Override
    public List<Result> select() {
        List<Result> list = new ArrayList<>();
        list.add(getResult(1, 101));
        list.add(getResult(1, 102));
        list.add(getResult(2, 203));
        list.add(getResult(3, 304));
        list.add(getResult(3, 305));
        list.add(getResult(3, 306));
        return list;
    }

    private Result getResult(int did, int eid) {
        Department department = new Department();
        department.setDepartmentId(did);
        department.setDepartmentName("システム" + did + "課");

        Employee employee = new Employee();
        employee.setEmployeeId(eid);
        employee.setName("山田 " + eid + "郎");
        employee.setDepartmentId(department.getDepartmentId());

        Result result = new Result(department, employee);
        return result;
    }
}

Resultクラス

DBからのSELECT結果がまず格納されるオブジェクト。

Result.java
package com.example.demo;

import lombok.Data;

@Data
public class Result {
    private Department department;
    private Employee employee;

    public Result() {
    }

    public Result(Department department, Employee employee) {
        this.department = department;
        this.employee = employee;
    }
}

Departmentクラス

部署マスタ。
Lombokの@Dataでhashcodeとequalsをオーバーライドしています。社員マスタも同様。
状況によりますが実際にこれがテーブルの1レコードを表す場合、オーバーライドするフィールドはPKだけでいいですね。その場合は@Dataは使用せずに自前で作成。LinkedHashMap(HashMap)使うのでオーバーライドは必須。

Department.java
package com.example.demo;

import lombok.Data;

@Data
public class Department {
    private Integer departmentId;
    private String departmentName;
}

Employeeクラス

社員マスタ。

Employee.java
package com.example.demo;

import lombok.Data;

@Data
public class Employee {
    private Integer employeeId;
    private String name;
    private Integer departmentId;
}

実行結果

ちゃんとキー(部署)ごとのマップに変換されていますね。

実行結果
Result(department=Department(departmentId=1, departmentName=システム1課), employee=Employee(employeeId=101, name=山田 101郎, departmentId=1))
Result(department=Department(departmentId=1, departmentName=システム1課), employee=Employee(employeeId=102, name=山田 102郎, departmentId=1))
Result(department=Department(departmentId=2, departmentName=システム2課), employee=Employee(employeeId=203, name=山田 203郎, departmentId=2))
Result(department=Department(departmentId=3, departmentName=システム3課), employee=Employee(employeeId=304, name=山田 304郎, departmentId=3))
Result(department=Department(departmentId=3, departmentName=システム3課), employee=Employee(employeeId=305, name=山田 305郎, departmentId=3))
Result(department=Department(departmentId=3, departmentName=システム3課), employee=Employee(employeeId=306, name=山田 306郎, departmentId=3))

Key:Department(departmentId=1, departmentName=システム1課)
    Value:Employee(employeeId=101, name=山田 101郎, departmentId=1)
    Value:Employee(employeeId=102, name=山田 102郎, departmentId=1)
Key:Department(departmentId=2, departmentName=システム2課)
    Value:Employee(employeeId=203, name=山田 203郎, departmentId=2)
Key:Department(departmentId=3, departmentName=システム3課)
    Value:Employee(employeeId=304, name=山田 304郎, departmentId=3)
    Value:Employee(employeeId=305, name=山田 305郎, departmentId=3)
    Value:Employee(employeeId=306, name=山田 306郎, departmentId=3)

あとがき

Lombok便利ですよね。

0
0
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0