很多项目的祖传代码总会有一些比较调皮的问题,比如上百行的if-else或者switch-case,比如成百上千的无用对象的创建,比如一看就头疼的代码,反正就是这样那样的问题,造成无法维护和扩展,可读性差,甚至想打一套军体拳。如果你不想被后人打死,那么在开发和维护的时候,有些地方需要注意一下。
如果有疑问,建议改行;
使用entrySet,不要用keySet,因为keySet其实是遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key对应的value,entrySet是遍历一次就把key和value放入entry中,效率更高;
java8推荐使用forEach:
Map<String, String> map = new HashMap<>();
map.put("1", "sb");
map.forEach((k, v) -> {
System.out.println(k);
System.out.println(v);
});
如果需要创建比如Integer之类的包装对象,用valueOf方法,不要用new,Integer会在[-128,127]之间对值进行缓存,避免对象分配,区间之外才会new;
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
就是下面这样的骚操作,我还是真见过。
List<String> list = new ArrayList<>();
// 其他代码...
list = getList();
我也不知道为什么要new,例中list引用指向其他对象后,原来创建的对象成了无谓的开销;
所以只能通过iterator方式迭代操作;
关闭的方法有很多,一定记得关掉就对了;
用String做字符串拼接会造成内存浪费,反编译出的字节码文件显示每次循环都会new一个StringBuilder,然后进行append操作,最后toString返回String对象,所以不要在循环体内 str += “sb”;
List<String> list = new ArrayList<>();
//...bla_bla_bla
List<String> list1 = list.clone();
想像上面这样拿到对象副本分开操作,这是在骗自己,你会发现副本对象add/remove操作的时候,原对象也会跟着add/remove,也就是改变是相互影响的,并没有达到创建副本的目的;
要实现深克隆要么实现Cloneable接口重写clone方法,但是如果对象里面引用对象,层数过多就得每个引用的对象都操作一次;要么将对象序列化成流,再把流反序列化成对象;
懒汉单例和饿汉单例都不科学,懒汉有线程安全问题,加锁又影响性能;饿汉在类装载时就初始化,没有达到懒加载的效果;
推荐使用登记式单例或者枚举,其中枚举能解决序列化和反射创建单例对象的问题,即其他方式通过反射创建的两个对象仍然不同,有被攻击的风险;
登记式单例:
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举单例:
public enum SingletonEnum {
INSTANCE;
public SingletonEnum getInstance() {
return INSTANCE;
}
}
即在创建ArrayList或者HashMap的时候,如果已知元素数量,就传入构造方法,避免由于频繁扩容带来的开销;
jdk1.7中,HashMap在多线程环境下线程不安全的原因就是扩容可能造成链表死循环;
Map<String, String> map = new HashMap<>(16);
经常能见别人的代码一个方法内几百行代码,可读性很差,如果你的方法一个屏幕都装不下,那要考虑把代码抽取出来;
不要一言不合就全捕获Exception,具体异常单独捕获,而且方便问题的定位排查,业务要处理好异常捕获后的工作,到底是抛出到上一级还是做其他处理,都会影响代码质量,但是千万不要试图用异常去控制业务流程走向;
有两点关于异常的操作提醒:
这个是阿里开发手册的规定,要通过ThreadPoolExecutor的方式,简单的说,就是必须指定线程池构造参数策略,避免引起OOM异常;还有,记得shutdown;
下面这篇文章讲过ThreadLocal与内存泄漏问题:
https://blog.csdn.net/unclecoco/article/details/103176609
对于String类型值的判断,是否不为null或者不为空串,建议使用StringUtils的isBlank或者isEmpty(两者区别是对换行符、回车符和空格的判断,isEmpty判断空格是false);判断String和某个字符串是否相等,养成常量写前面的习惯:“str”.equals(str),避免空指针异常;
foo(1),return 1,写盲人代码呢?看看HashMap,容量,装载因子这些都用了常量去定义;在增删改的时候,建议定义一个枚举去解释特定数字比如-1, 0 ,1等,然后返回枚举值,这样代码容易看懂。
public enum InsertStatus {
INSERT_FAILED(0),
INSERT_SUCCESS(1),
INSERT_DUPLICATEKEY(-2),
INSERT_UNKNOWNERROR(-1);
private int insertCode;
InsertStatus(int insertCode) {
this.insertCode = insertCode;
}
public void setInsertCode(int insertCode) {
this.insertCode = insertCode;
}
public int getInsertCode() {
return insertCode;
}
}
大对象如果没有及时被垃圾回收,就有可能造成OOM,如果当前线程由于某些原因没有结束,甚至会影响其他线程申请内存;
引申一道面试题:如果一条线程OOM,其他线程还能否继续工作?
答: 如果不做异常捕获,当前异常线程OOM后,线程结束,GC会释放资源,其他线程能正常工作;如果做了异常捕获,catch后不结束,线程未异常退出,引用对象不释放,其他线程则无法工作。
这在祖传代码里面最常见了,多一个场景就多一个if或case,谁也不愿意重构,大家一起开心。太多if-else其实也并不慢,只是难看而已。
解决方案:
String methodName = map.get(scene);
Class clz = Class.forName("com.xxx.xxx.Bean");
Method method = claz.getMethod(methodName);
method.invoke(clz.newInstance());
结论:遇到这种人大家一起孤立他,就反射那执行效率(不绝对),过程中创建对象既浪费时间又浪费了空间,在业务场景中不适合用反射来写业务代码,为什么提出来,单纯是因为我优化时这样干过而已;
public interface SceneService {
public void doSomething();
}
设计所有场景类,每个场景就是一个策略;除了实现SceneService接口,还有InitializingBean接口,在bean完成注册后会执行重写的afterPropertiesSet方法,在这个方法里,我们把场景bean注册到工厂里面,这个工厂就可以根据传入的类型取出相应的场景对象;
@Service
public class Scene1 implements SceneService, InitializingBean {
@Override
public void doSomething() {
// doSomething when scene1...
}
@Override
public void afterPropertiesSet() throws Exception {
StrategyFactory .register("scene1",this);
}
}
@Service
public class Scene2 implements SceneService, InitializingBean {
@Override
public void doSomething() {
// doSomething when scene2...
}
@Override
public void afterPropertiesSet() throws Exception {
StrategyFactory.register("scene2",this);
}
}
创建工厂类,上面说了工厂在项目启动时就存储了场景实例;工厂主要维护一个map,提供注册和获取的方法;
public class StrategyFactory {
private static Map<String, SceneService> map = new ConcurrentHashMap<>();
public static SceneService getScene(String type){
return map.get(type);
}
public static void register(String type, SceneService sceneService){
map.put(userType,sceneService);
}
}
应用:
SceneService sceneService = StrategyFactory.getScene(type);
sceneService.doSomething();
单一职责原则,里氏替换原则,组合/聚合复用原则,依赖倒转原则,开闭原则,接口隔离原则,迪米特法则(高内聚,低耦合);