/*
* 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
VBA
Excel VBA | 実務で超使う「離れたセル × 自動化パターン」
