Java
GSON
reflection

ぼくのかんがえたさいきょうのequalsメソッド(Java)

JUnitでBean同士の比較をしたくて作ってしまいました。

※equalsメソッドをこれで実装すると無限ループになってしまうバグを修正しました。
※throwsすると呼び出し側がメンドクサイので、内部でcatchへ修正しました。

  • java.lang配下のクラスの場合 ⇒ ネイティブのequalsメソッドを呼び出す。equalsメソッド未実装だったらtoString同士を比較
  • java.lang配下じゃない、かつ配列の場合 ⇒ 長さ、各要素のチェック
  • java.lang配下じゃない、かつ配列じゃない場合 ⇒ フィールドの値をチェック
/**
 * 便利なequalsメソッド
 * equalsメソッドが実装されてなくてもOK.スーパークラスのフィールドもチェックするよ.
 * @param a
 * @param b
 * @return
 */
@SuppressWarnings("unchecked")
public static boolean reflectEquals( Object a, Object b ){
        try{
        if (a == b) {
            return true;
        }
        if (a == null || b == null ) {
            return a == b;
        }
        if (a.getClass() != b.getClass()) {
            return false;
        }
        boolean useBuildinEqualsFlg = a.getClass().getName().startsWith("java.lang");
        if( useBuildinEqualsFlg ){
            try{
                Method equalsMethod = a.getClass().getDeclaredMethod("equals", Object.class);
                boolean eqFlg = Boolean.class.cast( equalsMethod.invoke(a, b) );
                return eqFlg;
            }catch( NoSuchMethodException e ) {
                String aStr = a.toString();
                String bStr = b.toString();
                boolean eqFlg = aStr.equals(bStr);
                return eqFlg;
            }
        }else{
            if( a instanceof List ) {
                a = ((List<Object>) a).toArray();
                b = ((List<Object>) b).toArray();
            }
            if( a.getClass().isArray() ) {
                int aLen = Array.getLength(a);
                int bLen = Array.getLength(b);
                if( aLen != bLen ) {
                    return false;
                }
                for( int i = 0;i<aLen;i++) {
                    Object aElement= Array.get(a, i);
                    Object bElement= Array.get(b, i);
                    if( !reflectEquals( aElement, bElement ) ) {
                        return false;
                    }
                }
                return true;
            }
            List<Field> fields = getAllFields(a.getClass());
            for( Field field : fields ) {
                field.setAccessible(true);
                Object aFieldObj = field.get(a);
                Object bFieldObj = field.get(b);
                boolean eqFlg = reflectEquals( aFieldObj, bFieldObj);
                if( !eqFlg ) {
                    return false;
                }
            }
            return true;
        }
    }catch(IllegalAccessException | IllegalArgumentException | SecurityException | InvocationTargetException e ){
        throw new RuntimeException(e);
    }
}

/**
 * スーパークラスも遡って全てのフィールドを取得する
 * @param clazz
 * @return
 */
private static List<Field> getAllFields(Class<?> clazz){
    List<Field> allFields = new ArrayList<>();
    if( clazz != Object.class ) {
        Field[] fields = clazz.getDeclaredFields();
        allFields.addAll( Arrays.asList(fields) );
        allFields.addAll( getAllFields(clazz.getSuperclass()) );
    }
    return allFields;
}

↓こうやって呼び出す。

reflectEquals(obj1, obj2)

と力作作った後にこれだけ↓で済むことに気づいた…

Gson gson = new GsonBuilder().setPrettyPrinting().create();
gson.toJson(obj1).equals( gson.toJson(obj2) );