避坑指南-计算和集合常见错误与接口和抽象类的理解

194 阅读3分钟

BigDecimal

精度丢失

private static void scaleProblem() {
    BigDecimal decimal = new BigDecimal("12.222");
    BigDecimal result = decimal.setScale(2);
    System.out.println(result);
}

执行以上代码将会抛出异常,因为指定的精度不符合要求,如需要舍弃精度,应调用以下方法

BigDecimal result = decimal.setScale(2, BigDecimal.ROUND_HALF_UP);

除不尽

private static void divideProblem() {
    System.out.println(new BigDecimal(30).divide(new BigDecimal(7)));
}

总所周知,上面结果是除不尽的,因此也会抛出异常,同样舍弃精度即可

System.out.println(new BigDecimal(30).divide(new BigDecimal(7), 2, BigDecimal.ROUND_HALF_UP));

精度导致比较结果错误

private static void equalProblem() {

    BigDecimal bd1 = new BigDecimal("0");
    BigDecimal bd2 = new BigDecimal("0.0");

    System.out.println(bd1.equals(bd2));
    System.out.println(bd1.compareTo(bd2) == 0);
}

第一个方法返回结果为:假,因为如果精度不同将会返回假

@Override
public boolean equals(Object x) {
    ...
    if (scale != xDec.scale)
        return false;
   ...
}

第二个方法返回结果为:真,因为数值相等

SimpleDateFormat

时间精度

private static void formatPrecision() throws Exception {

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

    String time_x = "2020-03-01 10:00:00";
    String time = "2020-03";

    System.out.println(sdf.parse(time_x));
    System.out.println(sdf.parse(time));
}

第一个字符串解析成功,第二个字符串解析失败,因此能够解析大于等于它定义的时间精度

线程不安全

private static void threadSafety() {

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>(1000));

    while (true) {
        threadPoolExecutor.execute(() -> {
            String dateString = "2020-03-01 10:00:00";
            try {
                Date parseDate = sdf.parse(dateString);
                String dateString2 = sdf.format(parseDate);
                System.out.println(dateString.equals(dateString2));
            } catch (ParseException ex) {
                ex.printStackTrace();
            }
        });
    }
}

多线程场景下将会导致异常的抛出,原因是不论解析还是格式化方法,都离不开下面的全局变量

protected Calendar calendar;

由于对象引用被共享,并且其内部没有实现线程安全机制,因此它并不是线程安全的对象,解决线程安全的方式有三种:方法局部变量、线程局部变量、同步锁

For

for (Iterator<Integer> l = left.iterator(); l.hasNext(); ) {
    for (Iterator<Integer> r = right.iterator(); r.hasNext(); ) {
        System.out.println(l.next() * r.next());
    }
}

以上代码较传统循环少了迭代索引,但存在外层迭代过早耗尽的问题,改成如下代码即可

for (Iterator<Integer> l = left.iterator(); l.hasNext(); ) {
    Integer tmp = l.next();
    for (Iterator<Integer> r = right.iterator(); r.hasNext(); ) {
        System.out.println(tmp * r.next());
    }
}

但以上代码迭代复杂度还是有点高,我们有更加简单的迭代方法

for (Integer l : left) {
    for (Integer r : right) {
        System.out.println(l * r);
    }
}

Object

默认 Equals 比较内存地址,默认 HashCode 根据对象地址生成整数数值

若需相同的对象在 Set 和 Map 中只存在一个,必须重写 HashCode 方法,并尽量让属性参与到计算中

List

List # indexOf 方法基于 Equals 方法实现比较

Collections.binarySearch 方法基于 CompareTo 方法实现比较

因此上述两个方法的实现应该要同步,否则排序与索引位置可能不一致

Lombok

单字母驼峰

@Data
public class Personal {
    private String iPhone;
    private String name;
    private String userName;
}

序列化字段名称变化

ObjectMapper mapper = new ObjectMapper();
Personal personal = new Personal();
personal.setIPhone("?");
System.out.println(mapper.writeValueAsString(personal));

输出结果

{"name":null,"userName":null,"iphone":"?"}

如果将上述结果修改为以下字符串再解析

{"name":null,"userName":null,"iPhone":"?"}

输出结果

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "iPhone"

父类属性

@Data 注解包含 @EqualsAndHashCode(callSuper = false)注解

因此需要注意子类 Equals 方法默认只会比较当前子类的属性,否则需要手动加上以下注解

@EqualsAndHashCode(callSuper = true

未完待续....