我写了一个计算QoQ的方法。
我的想法是迭代所有字段并计算它是长型、整数型、浮点数还是双精度型,并将字段名称和结果设置为地图。
写这段代码很容易,但我发现它太丑了:
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;
}
我只是重复了四次几乎相同的逻辑,我尝试使用类似<T extends Number> void doXXX(T before, T now)
但Number
无法计算的东西。
而 Integer、Long 和其他没有一些通用接口,如NumberEquals
(equals
进行类型检查的默认实现(或Divideable
......
java中也没有宏...
我尝试了一些时间,但还没有解决方案。
所以我想知道有没有办法做抽象并减少这个逻辑。
我建议将问题隔离到一个单独的转换方法中,该方法将Number
作为参数并返回可以统一处理的单个基元类型。例如:
private static int toInt(Number number) {
// domain-specific conversion logic
}
然后可以简化代码,以避免通过单个案例来切换确切的类型:
if(a instanceof Number) {
int beforeNum = toInt((Number)a);
if(beforeNum == 0) {
map.put(f.getName(), ZERO);
continue;
}
// and so on
问题的关键在于,如何详细完成转换将特定于领域(即,将取决于数字的解释方式(。假设数字代表货币,那么乘以 100(或使用的任何小数单位(并使用整数算术可能更安全。在任何情况下,此代码都可以利用Number
上的intValue()
(或其他类似(方法来再次避免切换。举例说明:
private static int toInt(Number number) {
if( number == null ) {
return 0;
}
return ((int) (number.doubleValue() * 100.0)); // Example only
}
这是一个使用 Map 查找类处理程序的解决方案。Long
、Integer
、Double
和Float
都由同一个NumberPercentDifferenceCalculator
类处理。BigInteger
和BigDecimal
都有自己的处理程序。
public class QoQCalculator {
private static final Map<Class<?>, AbstractPercentDifferenceCalculator> HANDLERS;
static {
HANDLERS = new HashMap<>();
NumberPercentDifferenceCalculator npdc = new NumberPercentDifferenceCalculator();
HANDLERS.put(Integer.class, npdc);
HANDLERS.put(Long.class, npdc);
HANDLERS.put(Float.class, npdc);
HANDLERS.put(Double.class, npdc);
BigDecimalPercentDifferenceCalculator bdpc = new BigDecimalPercentDifferenceCalculator();
HANDLERS.put(BigDecimal.class, bdpc);
HANDLERS.put(BigInteger.class, new BigIntegerPercentDifferenceCalculator(bdpc));
}
public static <T> Map<String, String> calculateQoq(final T now, final T before) {
final Field[] declaredFields = now.getClass().getDeclaredFields();
if (declaredFields.length == 0) {
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);
AbstractPercentDifferenceCalculator calculator = HANDLERS.get(a.getClass());
if (calculator != null) {
map.put(f.getName(), calculator.getPercentDifference(f.get(now), a));
} else {
System.out.println("No handler for " + a.getClass());
}
} catch (final Exception e) {
e.printStackTrace(System.out);
}
}
return map;
}
}
需要子类来处理特定类型的公共基类:
abstract class AbstractPercentDifferenceCalculator<T> {
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.##%");
private static final String ZERO = DECIMAL_FORMAT.format(0);
public final String getPercentDifference(Object now, Object before) {
if (returnZeroForNow(cast(now)) || returnZeroForBefore(cast(before))) {
return ZERO;
}
return DECIMAL_FORMAT.format(calculatePercentDifference(cast(now), cast(before)));
}
protected abstract double calculatePercentDifference(T now, T before);
protected abstract T cast(Object o);
protected boolean returnZeroForNow(T now) {
return now == null;
}
protected boolean returnZeroForBefore(T before) {
return before == null;
}
}
以及强制转换为其特定类型的实现,并知道如何计算该类型的百分比差异:
class NumberPercentDifferenceCalculator extends AbstractPercentDifferenceCalculator<Number> {
@Override
protected double calculatePercentDifference(Number now, Number before) {
return (now.doubleValue() - before.doubleValue()) / before.doubleValue();
}
@Override
protected Number cast(Object o) {
return (Number) o;
}
@Override
protected boolean returnZeroForBefore(Number before) {
return super.returnZeroForBefore(before) || before.doubleValue() == 0D;
}
}
class BigDecimalPercentDifferenceCalculator extends AbstractPercentDifferenceCalculator<BigDecimal> {
protected double calculatePercentDifference(BigDecimal now, BigDecimal before) {
return now.subtract(before).divide(before, BigDecimal.ROUND_CEILING).doubleValue();
}
@Override
protected BigDecimal cast(Object o) {
return (BigDecimal) o;
}
@Override
protected boolean returnZeroForBefore(BigDecimal before) {
return super.returnZeroForBefore(before) || before.compareTo(BigDecimal.ZERO) == 0;
}
}
class BigIntegerPercentDifferenceCalculator extends AbstractPercentDifferenceCalculator<BigInteger> {
public final BigDecimalPercentDifferenceCalculator delegate;
public BigIntegerPercentDifferenceCalculator(BigDecimalPercentDifferenceCalculator delegate) {
this.delegate = delegate;
}
@Override
protected BigInteger cast(Object o) {
return (BigInteger) o;
}
@Override
protected double calculatePercentDifference(BigInteger now, BigInteger before) {
return delegate.calculatePercentDifference(new BigDecimal(now), new BigDecimal(before));
}
@Override
protected boolean returnZeroForBefore(BigInteger before) {
return delegate.returnZeroForBefore(new BigDecimal(before));
}
}
下面是一个简单的运行器:
public class QoQRunner {
public static void main(String[] args) {
Holder before = new Holder(1, 2.0, 3L, BigDecimal.valueOf(4.4), BigInteger.valueOf(5));
Holder now = new Holder(10, 12.0, 13L, BigDecimal.valueOf(2.2), BigInteger.valueOf(15));
Map<String, String> diff = QoQCalculator.calculateQoq(now, before);
System.out.println(diff);
}
@Data // from lombok
@AllArgsConstructor // from lombok
public static class Holder {
private final Integer i;
private final Double d;
private final Long l;
private final BigDecimal bd;
private final BigInteger bi;
}
}