1、所有的覆写方法,必须加@Override注解
解释:所有的覆写方法,必须加@Override注解。 反例:getObject()与get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。

加上注解能增加代码的可读性,看到标签就知道这是从父类重写的方法,在调用时也将调用重写后的方法。并且使用@Override可以准确判断是否覆盖成功。
注意:
- 子类的访问级别必须高于父类被覆盖方法的访问级别,例如父类是public的而子类是protected的则是错误的。
- 方法被定义为private、static、final则不能被覆盖
- 方法调用时,会先在子类寻找覆盖的方法,再到父类寻找
2、Map/Set的key为自定义对象时,必须重写hashCode和equals
解释:HashMap中,hashCode()方法继承与Object类,hashCode码默认是内存地址。即便有相同含义的两个对象,比较也是不相等的。例如:
Student st1 = new Student("wei","man");
Student st2 = new Student("wei","man");HashCode
HashCode是通过hash函数得来的,指的是在hash表中有对应的位置。
能够大大提高查询效率
3、Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals
错误示范:
if(appRoleMemberVo.getMemberType().equals("3")){
//代码
}正确示范:
if(State.THREE.equals(appRoleMemberVo.getMemberType())){
//代码
}
4、浮点数之间的等值判断,基本数据类型不能用==来比较
float a = 1.0f;
float b = 1.0f;
System.out.println(a);//1.0
System.out.println(b);//1.0
System.out.println(a==b);//true
//精度丢失
float c = 1.0f-0.9f;
float d = 0.9f-0.8f;
System.out.println(c);//0.100000024
System.out.println(d);//0.099999964
System.out.println(c==d);//false输出并不是我们想要的结果(精度丢失),解决办法是使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作。
方法1:
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);// 0.1
BigDecimal y = b.subtract(c);// 0.1
System.out.println(x.equals(y));// true
方法2:
a.compareTo(b) : 返回 -1 表示小于,0 表示 等于, 1表示 大于
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));// 1
5、SimpleDateFormat线程不安全问题
如果我们把SimpleDateFormat定义成static成员变量,那么多个thread之间会共享这个SimpleDateFormat对象, 所以Calendar对象也会共享。
复现错误
public class SimpleDateFormatBugTest2 {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static Date d1 = null;
private static Date d2 = null;
private static Date d3 = null;
static {
try {
d1 = sdf.parse("2020-12-12 12:12:12");
d2 =sdf.parse("2019-11-11 11:11:11");
d3 =sdf.parse("2018-10-10 10:10:10");
} catch (ParseException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
String parse = sdf.format(d1);
System.out.println("当前日期:" + parse);
});
Thread t2 = new Thread(() -> {
String parse = sdf.format(d2);
System.out.println("当前日期:" + parse);
});
Thread t3 = new Thread(() -> {
String parse = sdf.format(d3);
System.out.println("当前日期:" + parse);
});
t1.start();
t2.start();
t3.start();
try {
t1.join();
t2.join();
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用三个线程分别对2020-12-12 12:12:12、2019-11-11 11:11:11、2018-10-10 10:10:10的Date格式转字符串格式的操作
第一次
当前日期:2019-11-11 11:11:11
当前日期:2019-11-11 11:11:11
当前日期:2019-11-11 11:11:11第二次
当前日期:2018-10-10 10:10:10
当前日期:2018-10-10 10:10:10
当前日期:2018-10-10 10:10:10第三次
当前日期:2018-10-11 11:11:11
当前日期:2020-11-11 11:11:11
当前日期:2019-11-11 11:11:11总结:
SimpleDateFormat是线程不安全的
5.1 SimpleDateFormat线程不安全的解决方法
5.1.1 将SimpleDateFormat定义成局部变量
SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
String str1 = "01-Jan-2010";
String str2 = sdf.format(sdf.parse(str1));缺点:每调用一次方法就会创建一个SimpleDateFormat对象,方法结束又要作为垃圾回收。
5.1.2 使用ThreadLocal
// 可以直接设置初始值
private static ThreadLocal<SimpleDateFormat> simpleDateFormat = ThreadLocal.withInitial(() ->
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
);
// 调用get()方法取得值
System.out.println(simpleDateFormat.get().parse(date));
// 移除
simpleDateFormat.remove();为什么用ThreadLocal能解决问题
ThreadLocal即线程本地变量。它用来为每个线程维护一个专属的变量副本,线程对自己的变量副本进行操作时,对其他线程的变量副本没有任何影响。它特别适合解决并发情况下变量共享造成的线程安全性问题。
ThreadLocal的Lambda构造方式:withInitial方法
用于创建一个线程局部变量,变量的初始化值通过调用Supplier的get方法来确定。ThreadLocal会为每个线程提供一份变量,各个线程互不影响。
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); }DEMO(银行存款案例):
