how to abstract(reduce) code using reflection to get field(Integer, Long, Float, Double) and do some calculation?

fairjm :

I write a method to calculate QoQ generally.
My idea is to iterate all the fields and calculate if it's long , integer, float or double and set the field name and the result to a map.
It was easy to write this code, but I found it's too ugly :

    public static <T> Map<String, String> calculateQoq(final T now, final T before) {
        final Field[] declaredFields = now.getClass().getDeclaredFields();
        if (ArrayUtils.isEmpty(declaredFields)) {
            return Collections.emptyMap();
        }
        final Map<String, String> map = new HashMap<>(declaredFields.length, 1);
        for (final Field f : now.getClass().getDeclaredFields()) {
            try {
                f.setAccessible(true);
                final Object a = f.get(before);
                if (a instanceof Integer) {
                    final Integer beforeNum = (Integer)a;
                    if (beforeNum == null || beforeNum == 0) {
                        map.put(f.getName(), ZERO);
                        continue;
                    }
                    final Integer nowNum = (Integer) f.get(now);
                    if (nowNum == null) {
                        map.put(f.getName(), ZERO);
                        continue;
                    }
                    map.put(f.getName(), formatTwoFraction((nowNum - beforeNum) * 1.0 / beforeNum));
                } else if (a instanceof Long) {
                    final Long beforeNum = (Long)a;
                    if (beforeNum == null || beforeNum == 0) {
                        map.put(f.getName(), ZERO);
                        continue;
                    }
                    final Long nowNum = (Long) f.get(now);
                    if (nowNum == null) {
                        map.put(f.getName(), ZERO);
                        continue;
                    }
                    map.put(f.getName(), formatTwoFraction((nowNum - beforeNum) * 1.0 / beforeNum));
                } else if (a instanceof Double) {
                    final Double beforeNum = (Double)a;
                    if (beforeNum == null || beforeNum == 0) {
                        map.put(f.getName(), ZERO);
                        continue;
                    }
                    final Double nowNum = (Double) f.get(now);
                    if (nowNum == null) {
                        map.put(f.getName(), ZERO);
                        continue;
                    }
                    map.put(f.getName(), formatTwoFraction((nowNum - beforeNum) / beforeNum));
                } else if (a instanceof Float) {
                    final Float beforeNum = (Float)a;
                    if (beforeNum == null || beforeNum == 0) {
                        map.put(f.getName(), ZERO);
                        continue;
                    }
                    final Float nowNum = (Float) f.get(now);
                    if (nowNum == null) {
                        map.put(f.getName(), ZERO);
                        continue;
                    }
                    map.put(f.getName(), formatTwoFraction((nowNum - beforeNum) / beforeNum));
                }
            } catch (final Exception e) {
                LOG.error("calculateQoq - get field failed - " + f.getName(), e);
            }
        }
        return map;
    }

I just repeat the nearly same logic four times, I try to use something like <T extends Number> void doXXX(T before, T now)
But Number can't be calculated.
And Integer, Long and others don't have some common interface like NumberEquals(the default implementation of equals do type checking) or Divideable...
There is no macros in java too...
I tried some times emmmm but no solution yet.

So I wonder is there any way to do abstraction and reduce this logic.

SDJ :

I would suggest isolating the problem into a separate conversion method that takes Number as argument and returns a single primitive type that can be handled uniformly. For example:

private static int toInt(Number number) {
    // domain-specific conversion logic
}

Then the code can be simplified to avoid switching on the exact type by having a single case:

if(a instanceof Number) {
    int beforeNum = toInt((Number)a);
    if(beforeNum == 0) {
        map.put(f.getName(), ZERO);
        continue;
    }
// and so on

The crux of the matter is that how the conversion gets done in detail will be domain-specific (i.e., will depend on how the numbers are interpreted). Assuming the numbers represent currency, then it might be safer to multiply by 100 (or whatever fractional unit is used), and use integer arithmetic. In any case, this code can leverage the intValue() (or other similar) method on Number to again avoid switching. As an illustration:

private static int toInt(Number number) {
    if( number == null ) {
        return 0;
    }
    return ((int) (number.doubleValue() * 100.0)); // Example only
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=140994&siteId=1