独り言

プログラミングの講師をしています。新人研修で扱う技術の解説と個人の技術メモ、技術書の紹介など

【Java】入れ子構造のデータの表示

例えば、メインカテゴリを一覧で表示するときに、そのメインカテゴリに対するサブカテゴリも入れ子にして一覧で表示したいような場合があります。
例えば以下のような表示をしたい時。

  • メイン1
    • サブ1
    • サブ2
    • サブ3
  • メイン2
    • サブ1
    • サブ2
    • サブ3

このような表示は多くのWebアプリケーションで見られるデータの構造です。
例えば、4択問題による学習アプリを作成する場合に、問題の一覧を表示させながら、それぞれの問題に対する選択肢を表示する、のような場合にも適用することができます。

DBから取得したデータを使ってJavaを使ったWebアプリケーションでこのような表示をしたい場合を想定します。
メインとサブは別テーブルでデータが保持されていて、外部キー参照で紐づいているイメージです。

メイン

id name
1 メイン1
2 メイン2

サブ

id name parent_id
1 サブ1 1
2 サブ2 1
3 サブ3 1
4 サブ1 2
5 サブ2 2
6 サブ3 2

Javaを使用してこのような表示をする場合、おそらくメインカテゴリ情報を保持するクラスとサブカテゴリ情報を保持するクラス(それぞれJavaBeansのクラス)を用意することになるでしょう。
個人的に、すぐに思いつく方法としては大きく3つ。

  1. SQLでgroup by, max関数, case式, string_agg関数などを駆使して、メインカテゴリごとに1レコードとなるようにデータを取得する。
  2. メインとサブのテーブルを結合してまとめてデータを取得し、画面側(VIEW側)で条件分岐の構文を使用して制御する。
  3. メインのJavaBeansクラスに、サブのクラスをListなどで保持し、画面側ではシンプルな2重ループになるようにデータを作成する。

1の方法は、SQLが少し複雑になりますが、Javaのプログラムと画面側のソースは比較的シンプルになりそうです。
ただし、サブカテゴリとして表示したいものの数が決まっている場合にしか有効ではなく、上記の例で言えば、サブ4が登場した時にソースコードを修正する必要が出てきて、拡張性に乏しいです。

2の方法は、SQLJavaのプログラムもシンプルになります。データ保持用のJavaBeansのクラスで、メインとサブ両方のデータを保持するようにフィールド(プロパティ)を保持しておく必要があります。
画面側で条件分岐を駆使して表示をコントロールすることになりますが、サブ側のループ処理で、メイン側のidとサブ側のparent_idが同じ場合のみ表示するようにすればシンプルに実装できそうです。

3の方法は、SQLも画面もシンプルに実装できますが、Javaの方でデータ保持用のインスタンスを色々と変換する作業が必要になります。

3の方法

ここでは3の方法についてのサンプルコードを示します。
話をシンプルにするために、DBへの接続は省略します。
また、表示も標準出力を利用したコンソールへの出力にしています。

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

public class Category {
    
    private int id;
    private String name;
    // サブ用
    private SubCategory subCategory;
    private List<SubCategory> subList;

    public Category() {

    }
    public Category(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public Category(int id, String name, int subId, String subName) {
        this.id = id;
        this.name = name;
        this.subCategory = new SubCategory(subId, subName, id);
    }
    
    public String getName() {
        return this.name;
    }

    public SubCategory getSubCategory() {
        return this.subCategory;
    }

    public List<SubCategory> getSubList() {
        return this.subList;
    }

    public void setSubLust(List<SubCategory> list) {
        this.subList = list;
    }

    public void initSubList() {
        this.subList = new ArrayList<>();
    }

    public void addSubCategory(SubCategory sub) {
        this.subList.add(sub);
    }

    // ListのCntainsで存在チェックしたときに、idとnameが同じなら同じと見なすようにする
    @Override
    public boolean equals(Object obj) {
        if(obj != null && obj instanceof Category) {
            Category c = (Category)obj;
            if(this.id == c.id && (this.name != null && this.name.equals(c.getName()))) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
}
public class SubCategory {

    private int id;
    private String name;
    private int parentId;

    public SubCategory() {
    }

    public SubCategory(int id, String name, int parentId) {
        this.id = id;
        this.name = name;
        this.parentId = parentId;
    }

    public String getName() {
        return this.name;
    }

    // その他のアクセッサは省略
    
}
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Category> list = new ArrayList<>();
        list.add(new Category(1, "Main1", 1, "sub1"));
        list.add(new Category(1, "Main1", 2, "sub2"));
        list.add(new Category(1, "Main1", 3, "sub3"));
        list.add(new Category(2, "Main2", 4, "sub4"));
        list.add(new Category(2, "Main2", 5, "sub5"));
        list.add(new Category(2, "Main2", 6, "sub6"));

        //  リストへの詰め替え。
        List<Category> newList = new ArrayList<>();
        for(Category c : list) {
            if(!newList.contains(c)) {
                newList.add(c);
                newList.get(newList.indexOf(c)).initSubList();
            }
            newList.get(newList.indexOf(c)).addSubCategory(c.getSubCategory());
        }
        
        // 出力
        for(Category c : newList) {
            System.out.println("main : " + c.getName());
            for(SubCategory s : c.getSubList()) {
                System.out.println("sub : " + s.getName());
            }
        }

    }
}

結果

main : Main1
sub : sub1
sub : sub2
sub : sub3
main : Main2
sub : sub4
sub : sub5
sub : sub6