0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

2つのオブジェクトのフィールド値を全て比較する

Last updated at Posted at 2024-06-19

備忘録です。

DeepComparator.java

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.HashSet;
import java.util.stream.StreamSupport;

public class DeepComparator {

    public static boolean deepEquals(Object obj1, Object obj2) {
        return deepEquals(obj1, obj2, "", new IdentityHashMap<>());
    }

    private static boolean deepEquals(Object obj1, Object obj2, String path, Map<IdentityKey, String> visited) {
        if (obj1 == obj2) {
            return true;
        }
        if (obj1 == null || obj2 == null) {
            System.out.println("Null comparison failed at: " + path + " - " + (obj1 == null ? "obj1 is null" : "obj2 is null"));
            return false;
        }
        if (obj1.getClass() != obj2.getClass()) {
            System.out.println("Class comparison failed at: " + path + " - " + obj1.getClass().getName() + " vs " + obj2.getClass().getName());
            return false;
        }

        IdentityKey key1 = new IdentityKey(obj1);
        IdentityKey key2 = new IdentityKey(obj2);
        if (visited.containsKey(key1) || visited.containsKey(key2)) {
            System.out.println("Circular reference detected at: " + path);
            return true;
        }

        visited.put(key1, path);
        visited.put(key2, path);

        Class<?> clazz = obj1.getClass();
        while (clazz != null) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                try {
                    Object value1 = field.get(obj1);
                    Object value2 = field.get(obj2);
                    String currentPath = path.isEmpty() ? field.getName() : path + "." + field.getName();
                    if (!compareValues(value1, value2, currentPath, visited)) {
                        System.out.println("Field comparison failed at: " + currentPath);
                        return false;
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    return false;
                }
            }
            clazz = clazz.getSuperclass();
        }
        return true;
    }

    private static boolean compareValues(Object value1, Object value2, String path, Map<IdentityKey, String> visited) {
        if (value1 == value2) {
            return true;
        }
        if (value1 == null || value2 == null) {
            System.out.println("Null comparison failed at: " + path + " - " + (value1 == null ? "value1 is null" : "value2 is null"));
            return false;
        }
        if (value1.getClass().isArray() && value2.getClass().isArray()) {
            if (value1.getClass().getComponentType().isPrimitive()) {
                return primitiveArrayEquals(value1, value2, path);
            } else {
                return objectArrayEquals(value1, value2, path, visited);
            }
        } else if (value1 instanceof Iterable<?> && value2 instanceof Iterable<?>) {
            if (!deepEqualsIterable((Iterable<?>) value1, (Iterable<?>) value2, path, visited)) {
                System.out.println("Iterable comparison failed at: " + path);
                return false;
            }
        } else if (value1 instanceof Object && value2 instanceof Object) {
            if (Objects.equals(value1, value2)) {
                
                return true;
            }
            if (!deepEquals(value1, value2, path, visited)) {
                System.out.println("Object comparison failed at: " + path + " - " + value1 + " vs " + value2);
                return false;
            }
        } else {
            if (!Objects.equals(value1, value2)) {
                System.out.println("Value comparison failed at: " + path + " - " + value1 + " vs " + value2);
                return false;
            }
        }
        return true;
    }

    private static boolean primitiveArrayEquals(Object array1, Object array2, String path) {
        if (array1.getClass() != array2.getClass()) {
            return false;
        }

        int length1 = Array.getLength(array1);
        int length2 = Array.getLength(array2);

        if (length1 != length2) {
            System.out.println("Array length comparison failed at: " + path + " - " + length1 + " vs " + length2);
            return false;
        }

        for (int i = 0; i < length1; i++) {
            Object element1 = Array.get(array1, i);
            Object element2 = Array.get(array2, i);
            if (!Objects.equals(element1, element2)) {
                System.out.println("Array element comparison failed at: " + path + "[" + i + "] - " + element1 + " vs " + element2);
                return false;
            }
        }

        return true;
    }

    private static boolean objectArrayEquals(Object array1, Object array2, String path, Map<IdentityKey, String> visited) {
        Object[] objArray1 = (Object[]) array1;
        Object[] objArray2 = (Object[]) array2;

        if (objArray1.length != objArray2.length) {
            System.out.println("Array length comparison failed at: " + path + " - " + objArray1.length + " vs " + objArray2.length);
            return false;
        }

        for (int i = 0; i < objArray1.length; i++) {
            if (!deepEquals(objArray1[i], objArray2[i], path + "[" + i + "]", visited)) {
                System.out.println("Array element comparison failed at: " + path + "[" + i + "]");
                return false;
            }
        }

        return true;
    }

    private static boolean deepEqualsIterable(Iterable<?> iterable1, Iterable<?> iterable2, String path, Map<IdentityKey, String> visited) {
        if (iterable1 == iterable2) {
            return true;
        }
        if (iterable1 == null || iterable2 == null) {
            System.out.println("Null iterable comparison failed at: " + path + " - " + (iterable1 == null ? "iterable1 is null" : "iterable2 is null"));
            return false;
        }
        return deepEquals(toArray(iterable1), toArray(iterable2), path, visited);
    }

    private static Object[] toArray(Iterable<?> iterable) {
        return StreamSupport.stream(iterable.spliterator(), false)
                .toArray();
    }

    private static class IdentityKey {
        private final Object obj;

        IdentityKey(Object obj) {
            this.obj = obj;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            IdentityKey that = (IdentityKey) o;
            return obj == that.obj;
        }

        @Override
        public int hashCode() {
            return System.identityHashCode(obj);
        }
    }
}

Matcherを使う場合

DeepEqualsMatcher.java

import org.hamcrest.Description;
import org.hamcrest.TypeSafeDiagnosingMatcher;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.StreamSupport;

public class DeepEqualsMatcher extends TypeSafeDiagnosingMatcher<Object> {

    private final Object expected;

    public DeepEqualsMatcher(Object expected) {
        this.expected = expected;
    }

    @Override
    protected boolean matchesSafely(Object actual, Description mismatchDescription) {
        return deepEquals(expected, actual, "", new IdentityHashMap<>(), mismatchDescription);
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("an object deeply equal to ").appendValue(expected);
    }

    private boolean deepEquals(Object obj1, Object obj2, String path, Map<IdentityKey, String> visited, Description mismatchDescription) {
        if (obj1 == obj2) {
            return true;
        }
        if (obj1 == null || obj2 == null) {
            mismatchDescription.appendText("Null comparison failed at: " + path + " - " + (obj1 == null ? "obj1 is null" : "obj2 is null"));
            return false;
        }
        if (obj1.getClass() != obj2.getClass()) {
            mismatchDescription.appendText("Class comparison failed at: " + path + " - " + obj1.getClass().getName() + " vs " + obj2.getClass().getName());
            return false;
        }

        IdentityKey key1 = new IdentityKey(obj1);
        IdentityKey key2 = new IdentityKey(obj2);
        if (visited.containsKey(key1) || visited.containsKey(key2)) {
            return true;
        }

        visited.put(key1, path);
        visited.put(key2, path);

        Class<?> clazz = obj1.getClass();
        while (clazz != null) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                try {
                    Object value1 = field.get(obj1);
                    Object value2 = field.get(obj2);
                    String currentPath = path.isEmpty() ? field.getName() : path + "." + field.getName();
                    if (!compareValues(value1, value2, currentPath, visited, mismatchDescription)) {
                        mismatchDescription.appendText("Field comparison failed at: " + currentPath);
                        return false;
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    return false;
                }
            }
            clazz = clazz.getSuperclass();
        }
        return true;
    }

    private boolean compareValues(Object value1, Object value2, String path, Map<IdentityKey, String> visited, Description mismatchDescription) {
        if (value1 == value2) {
            return true;
        }
        if (value1 == null || value2 == null) {
            mismatchDescription.appendText("Null comparison failed at: " + path + " - " + (value1 == null ? "value1 is null" : "value2 is null"));
            return false;
        }
        if (value1.getClass().isArray() && value2.getClass().isArray()) {
            if (value1.getClass().getComponentType().isPrimitive()) {
                return primitiveArrayEquals(value1, value2, path, mismatchDescription);
            } else {
                return objectArrayEquals(value1, value2, path, visited, mismatchDescription);
            }
        } else if (value1 instanceof Iterable<?> && value2 instanceof Iterable<?>) {
            if (!deepEqualsIterable((Iterable<?>) value1, (Iterable<?>) value2, path, visited, mismatchDescription)) {
                mismatchDescription.appendText("Iterable comparison failed at: " + path);
                return false;
            }
        } else if (value1 instanceof Object && value2 instanceof Object) {
            if (Objects.equals(value1, value2)) {
                
                return true;
            }
            if (!deepEquals(value1, value2, path, visited, mismatchDescription)) {
                mismatchDescription.appendText("Object comparison failed at: " + path + " - " + value1 + " vs " + value2);
                return false;
            }
        } else {
            if (!Objects.equals(value1, value2)) {
                mismatchDescription.appendText("Value comparison failed at: " + path + " - " + value1 + " vs " + value2);
                return false;
            }
        }
        return true;
    }

    private boolean primitiveArrayEquals(Object array1, Object array2, String path, Description mismatchDescription) {
        if (array1.getClass() != array2.getClass()) {
            return false;
        }

        int length1 = Array.getLength(array1);
        int length2 = Array.getLength(array2);

        if (length1 != length2) {
            mismatchDescription.appendText("Array length comparison failed at: " + path + " - " + length1 + " vs " + length2);
            return false;
        }

        for (int i = 0; i < length1; i++) {
            Object element1 = Array.get(array1, i);
            Object element2 = Array.get(array2, i);
            if (!Objects.equals(element1, element2)) {
                mismatchDescription.appendText("Array element comparison failed at: " + path + "[" + i + "] - " + element1 + " vs " + element2);
                return false;
            }
        }

        return true;
    }

    private boolean objectArrayEquals(Object array1, Object array2, String path, Map<IdentityKey, String> visited, Description mismatchDescription) {
        Object[] objArray1 = (Object[]) array1;
        Object[] objArray2 = (Object[]) array2;

        if (objArray1.length != objArray2.length) {
            mismatchDescription.appendText("Array length comparison failed at: " + path + " - " + objArray1.length + " vs " + objArray2.length);
            return false;
        }

        for (int i = 0; i < objArray1.length; i++) {
            if (!deepEquals(objArray1[i], objArray2[i], path + "[" + i + "]", visited, mismatchDescription)) {
                mismatchDescription.appendText("Array element comparison failed at: " + path + "[" + i + "]");
                return false;
            }
        }

        return true;
    }

    private boolean deepEqualsIterable(Iterable<?> iterable1, Iterable<?> iterable2, String path, Map<IdentityKey, String> visited, Description mismatchDescription) {
        if (iterable1 == iterable2) {
            return true;
        }
        if (iterable1 == null || iterable2 == null) {
            mismatchDescription.appendText("Null iterable comparison failed at: " + path + " - " + (iterable1 == null ? "iterable1 is null" : "iterable2 is null"));
            return false;
        }
        return deepEquals(toArray(iterable1), toArray(iterable2), path, visited, mismatchDescription);
    }

    private Object[] toArray(Iterable<?> iterable) {
        return StreamSupport.stream(iterable.spliterator(), false)
                .toArray();
    }

    private static class IdentityKey {
        private final Object obj;

        IdentityKey(Object obj) {
            this.obj = obj;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            IdentityKey that = (IdentityKey) o;
            return obj == that.obj;
        }

        @Override
        public int hashCode() {
            return System.identityHashCode(obj);
        }
    }

    public static DeepEqualsMatcher deepEqualsTo(Object expected) {
        return new DeepEqualsMatcher(expected);
    }
}
0
1
1

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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?