Java | 実務レベルの配列出力ユーティリティ

Java Java
スポンサーリンク
/*
 * ArrayUtils.java
 *
 * 実務レベルの配列出力ユーティリティ
 * - プリミティブ配列・オブジェクト配列・多次元配列を安全に文字列化
 * - null 安全
 * - 深さ制限、要素数制限、トランケーション表示
 * - 循環参照検出(Object[] の自己参照を回避)
 * - 簡易な "pretty" 表示(入れ子をインデントして出力)
 *
 * 使い方(コマンドラインで実行可能な main を含む):
 *   java ArrayUtils
 *
 * ライセンス: example (public domain for demo)
 */

import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Map;

public final class ArrayUtils {

    private ArrayUtils() {}

    /**
     * 配列(あるいは配列を含む任意のオブジェクト)を安全に文字列化する。\
     * - null -> "null"
     * - プリミティブ配列 -> Arrays.toString
     * - Object[] -> 再帰的に展開(循環検出あり)
     *
     * @param obj 任意のオブジェクト(配列でなくても obj.toString() を返す)
     * @return 表示用の1行文字列
     */
    public static String safeToString(Object obj) {
        return safeToString(obj, 10, 3); // デフォルト: 要素最大10, 深さ最大3
    }

    /**
     * safeToString のカスタム版
     * @param obj 対象オブジェクト
     * @param maxElements 配列の各次元で表示する最大要素数(超えたら "..." を付ける)
     * @param maxDepth 展開する最大深さ(超えたら "..." を表示)
     */
    public static String safeToString(Object obj, int maxElements, int maxDepth) {
        if (obj == null) return "null";
        StringBuilder sb = new StringBuilder();
        Map<Object, Boolean> seen = new IdentityHashMap<>();
        buildString(obj, sb, 0, maxElements, maxDepth, seen);
        return sb.toString();
    }

    // 内部再帰ヘルパー
    private static void buildString(Object obj, StringBuilder sb, int depth, int maxElements, int maxDepth, Map<Object, Boolean> seen) {
        if (obj == null) {
            sb.append("null");
            return;
        }

        if (!obj.getClass().isArray()) {
            sb.append(obj.toString());
            return;
        }

        // 循環参照チェック
        if (seen.containsKey(obj)) {
            sb.append("[...cyclic...]");
            return;
        }
        seen.put(obj, Boolean.TRUE);

        if (depth >= maxDepth) {
            sb.append("[...]");
            return;
        }

        Class<?> comp = obj.getClass().getComponentType();
        if (comp.isPrimitive()) {
            // 各プリミティブ型ごとに toString を呼ぶ
            if (obj instanceof int[]) sb.append(Arrays.toString((int[]) obj));
            else if (obj instanceof long[]) sb.append(Arrays.toString((long[]) obj));
            else if (obj instanceof short[]) sb.append(Arrays.toString((short[]) obj));
            else if (obj instanceof char[]) sb.append(Arrays.toString((char[]) obj));
            else if (obj instanceof byte[]) sb.append(Arrays.toString((byte[]) obj));
            else if (obj instanceof boolean[]) sb.append(Arrays.toString((boolean[]) obj));
            else if (obj instanceof float[]) sb.append(Arrays.toString((float[]) obj));
            else if (obj instanceof double[]) sb.append(Arrays.toString((double[]) obj));
            else sb.append("[unknown primitive array]");
        } else {
            Object[] arr = (Object[]) obj;
            sb.append('[');
            int len = arr.length;
            int limit = Math.max(0, Math.min(len, maxElements));
            for (int i = 0; i < limit; i++) {
                if (i > 0) sb.append(", ");
                buildString(arr[i], sb, depth + 1, maxElements, maxDepth, seen);
            }
            if (len > limit) sb.append(", ...");
            sb.append(']');
        }

        // 終了時に seen から削除しておく(他の枝で同じ配列が出たら cyclic 検出したい場合は残す)
        seen.remove(obj);
    }

    /**
     * 配列(または任意のオブジェクト)を "pretty" に出力する。\
     * pretty とは入れ子を改行・インデントして見やすく表示すること。\
     * 注意: 実務ではログ出力には長すぎないように適宜 maxElements/maxDepth を調整してください。
     *
     * @param obj 対象
     * @param maxElements 要素のトランケーション閾値
     * @param maxDepth 深さの閾値
     */
    public static String prettyPrint(Object obj, int maxElements, int maxDepth) {
        StringBuilder sb = new StringBuilder();
        Map<Object, Boolean> seen = new IdentityHashMap<>();
        prettyBuild(obj, sb, 0, maxElements, maxDepth, seen);
        return sb.toString();
    }

    private static void prettyBuild(Object obj, StringBuilder sb, int indent, int maxElements, int maxDepth, Map<Object, Boolean> seen) {
        if (obj == null) {
            sb.append("null");
            return;
        }
        if (!obj.getClass().isArray()) {
            sb.append(obj.toString());
            return;
        }

        if (seen.containsKey(obj)) {
            sb.append("[...cyclic...]");
            return;
        }
        seen.put(obj, Boolean.TRUE);

        if (indent / 2 >= maxDepth) {
            sb.append("[...]");
            return;
        }

        Class<?> comp = obj.getClass().getComponentType();
        if (comp.isPrimitive()) {
            // プリミティブは一行で
            sb.append(safeToString(obj, maxElements, maxDepth));
            return;
        }

        Object[] arr = (Object[]) obj;
        sb.append('[');
        if (arr.length > 0) sb.append('\n');
        int limit = Math.max(0, Math.min(arr.length, maxElements));
        for (int i = 0; i < limit; i++) {
            indent(sb, indent + 2);
            prettyBuild(arr[i], sb, indent + 2, maxElements, maxDepth, seen);
            if (i < arr.length - 1) sb.append(',');
            sb.append('\n');
        }
        if (arr.length > limit) {
            indent(sb, indent + 2);
            sb.append("...\n");
        }
        indent(sb, indent);
        sb.append(']');

        seen.remove(obj);
    }

    private static void indent(StringBuilder sb, int n) {
        for (int i = 0; i < n; i++) sb.append(' ');
    }

    // シンプルな main (デモ)
    public static void main(String[] args) {
        // サンプルデータ
        int[] one = {1, 2, 3, 4, 5};
        int[][] two = { {10, 20}, {30, 40} };
        Object[] mixed = { one, new String[]{"A","B"}, "Hello" };

        System.out.println("safeToString(one): " + safeToString(one));
        System.out.println("safeToString(two): " + safeToString(two));
        System.out.println("safeToString(mixed): " + safeToString(mixed));

        System.out.println("\nprettyPrint(mixed, maxElements=2, maxDepth=3):\n" + prettyPrint(mixed, 2, 3));

        // 循環参照の例
        Object[] a = new Object[1];
        a[0] = a; // 自己参照
        System.out.println("\ncyclic safe: " + safeToString(a));
        System.out.println("cyclic pretty: " + prettyPrint(a, 5, 5));
    }
}
Java

Java
スポンサーリンク
シェアする
@lifehackerをフォローする
スポンサーリンク
タイトルとURLをコピーしました