long或者 Long初始赋值时,必须使用大写的 L,不能是小写的 l,小写容易跟数字1混淆,造成误解。
正例:
Long a = 2L;
反例:
Long a = 2l;
不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
正例:
ArrayList list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
System.out.println(list);//[A, B, C]
Iterator iterator = list.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if(next.equals("B")){
iterator.remove();
}
}
System.out.println(list);//[A, C]
反例:
ArrayList list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("R");
list.add("T");
list.add("C");
System.out.println(list);//[A, B, C]
for (String str : list) {
if(str.equals("B")){
list.remove(str);
}
}
3.在iflelse/forlwhileldo语句中必须使用大括号,即使只有一行代码。
正例:
if (condition){
statements;
}
反例:
if (condition) statements;
4.在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
正例:
private static final Pattern pattern = Pattern.compile(".*:(.*)://.*/(.*)\\?.*$");
反例:
public void t(){
Pattern pattern = Pattern.compile(".*:(.*)://.*/(.*)\\?.*$");
}
5.多钱程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。
解释:
Timer运行多个TimeTask时底层公用一个队列它会错误的认为整个 Timer 线程都会取消。
Timer 是内部是单一线程,而 ScheduledThreadPoolExecutor 内部是个线程池,所以可以支持多个任务并发执行。
正例:
private final ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new ImprovedTimer.DaemonThreadFactory());
private ScheduledFuture> improvedTimerFuture = null;
public void schedule(Runnable command, long initialDelay, long period){
// initialDelay 毫秒后开始执行任务,以后每隔 period 毫秒执行一次
// schedule方法被用来延迟指定时间来执行某个指定任务。
// 如果你需要周期性重复执行定时任务可以使用scheduleAtFixedRate或者scheduleWithFixedDelay方法,它们不同的是前者以固定频率执行,后者以相对固定频率执行。
// 不管任务执行耗时是否大于间隔时间,scheduleAtFixedRate和scheduleWithFixedDelay都不会导致同一个任务并发地被执行。
// 唯一不同的是scheduleWithFixedDelay是当前一个任务结束的时刻,开始结算间隔时间,如0秒开始执行第一次任务,任务耗时5秒,任务间隔时间3秒,那么第二次任务执行的时间是在第8秒开始。
improvedTimerFuture = executorService.scheduleAtFixedRate(command, initialDelay, period, TimeUnit.MILLISECONDS);
}
/**
* 守护线程工厂类,用于生产后台运行线程
*/
private static final class DaemonThreadFactory implements ThreadFactory {
private AtomicInteger atoInteger = new AtomicInteger(0);
@Override
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable);
thread.setName("schedule-pool-Thread-" + atoInteger.getAndIncrement());
thread.setDaemon(true);
return thread;
}
}
反例:
public static void main(String[] args) throws Exception {
Timer timer = new Timer();
Thread.sleep(10000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("haha");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 1000, 1000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("heihei");
}
}, 1000,1000);
}
6.所有的包装类对象之间值的比较,全部使用equals方法比较。
正例:
a.equals(b)
反例:
a==b
7.所有的覆写方法,必须加@Override注解。
8.线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式能更加明确线程池的运行规则,规避资源耗尽的风险。
解释:
Java通过Executors提供四种线程池,分别为∶
1)newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
2)newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。
3)newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
4)newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。
Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
正例:
@Bean
public Executor FileCopyExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心线程数,一次处理100个
taskExecutor.setCorePoolSize(fileCorePoolSize);
taskExecutor.setMaxPoolSize(2 *fileCorePoolSize);
taskExecutor.setQueueCapacity(fileQueueCapacity);
taskExecutor.setThreadNamePrefix("file-");
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
反例:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
9.获取当前毫秒数:System.currentTimeMillis();而不是new Date().getTime();
解释:
new Date().getTime()其实最终还是调用了System.currentTimeMillis(),那还不如直接就使用System.currentTimeMillis()。
因为new Date()包含了非常多的时间信息,直接使用System.currentTimeMillis(),效率更高。
10.避免用Apache Beanutils进行属性的copy.
主要还是性能问题
1000次 |
10000次 |
100000次 |
1000000次 |
|
apache BeanUtils |
906毫秒 |
807毫秒 |
1892毫秒 |
11049毫秒 |
apache PropertyUtils |
17毫秒 |
96毫秒 |
648毫秒 |
5896毫秒 |
spring cglib BeanCopier |
0毫秒 |
1毫秒 |
3毫秒 |
10毫秒 |
spring copyProperties |
87毫秒 |
90毫秒 |
123毫秒 |
482毫秒 |
性能走势 --> spring cglib BeanCopier 优于 spring copyProperties 优于 apache PropertyUtils 优于 apache BeanUtils
11.避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
解释:
增加jvm成本,增加无谓的内存消耗
通过对象支持调用静态变量和静态方法,最终都会在编译后转换为类直接调用静态变量或静态方法。
正例:
String str = TestConst.a;
反例:
TestConst tc = new TestConst();
String str = tc.a;
12.ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常。
解释:
1)SubList 继承 AbstractList ,所以具有List接口的所有方法。
2)SubList 是ArrayList 的一个内部类。SubList并没有重新创建一个List,而是直接引用原有的List,只不过对原来List做截取而已。
3)ArrayList 也是继承AbstractList,但是 SubList 和 ArrayList 没有继承关系,所以不能强转换。
正例:
List list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
List subList1 = list.subList(1, 3);
反例:
List list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
ArrayList subList1 = (ArrayList)list.subList(1, 3);
13.Map/Set的key为自定义对象时,必须重写hashCode和equals.
解释:
Map/Set首先将自定义对象的hashcode值与集合中已经存在的数据的hashcode值比较,不相等则直接添加,如果相等再进一步判断他们的equals是否相等,不相等则添加。
如果不重写自定义对象类的hashcode和equals方法,则他们的equals方法不相同;同样他们的hashcode值也大概率不相等
14.Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
15.POJO类中的任何布尔类型的变量,都不要加is,否则部分框架解析会引起序列化错误
解释:
类中的属性用is开头会导致对象序列化后产生意想不到的结果。所以在平时编码的过程中,我们不能在类变量中用is开头如: “isSuccess”,这样容易造成程序错误
16.SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUltis工具类.
17.不能使用过时的类或方法。
18.不能在finally块中使用return,finally块中的return返回后方法结束执行,不会再执行try块中的return语句。
解释:
try块中的return值会先保存起来,然后执行完finally中的代码后,才会把try块中的return值返回,所以finally中的代码逻辑是不会影响try块中的return值的。
但如果在finally中使用return了就会导致try块中的代码得不到执行而无法返回正确的结果
19.使用工具类Arays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/removelclear方法会抛出Unsupported()
解释:
asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。
反例:
String[] str = new String[]{"1","2"};
List strings = Arrays.asList(str);
strings.add("3");//java.lang.UnsupportedOperationException
--------------------------------------------------------------------------------
String[] str = new String[]{"1","2"};
List strings = Arrays.asList(str);
str[0]="3";//strings 内容会同步被改掉
20.使用集合转数组的方法,必须使用集合的toArray(Tarray),传入的是类型完全一样的数组,大小就是list size()
正例:
List list = new ArrayList();
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);
21.创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。创建线程池的时候请使用带ThreadFactory的构造函数,并且提供自定义ThreadFactory实现或者使用第三方实现。
22.在subList场景中,高度注意对原列表的修改,会导致子列表的遍历、增加、删除均产生ConcurentModificationException异常。
反例:
List list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
List subList1 = list.subList(1, 3);
list.remove(2);
for (Integer integer : subList1) {
}
23.在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内都必须包含一个default语句并且放在最后,即使它什么代码也没有。
24.对于Service和DAO类,基于SOA的理念,暴露出来的服务一定是接口,内部的实现类用Impl的后缀与接口区别
25.常量命名应该全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长
26.异常类命名使用Exception结尾
27.必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用try-finally块进行回收。
正例:
ThreadLocal objectThreadLocal = new ThreadLocal();
objectThreadLocal.set(1);
try {
// 业务逻辑
} finally {
objectThreadLocal.remove();
}
28.所有的枚举类型字段必须要有注释,说明每个数据项的用途。
正例:
public enum AppExceptionEnum implements ExceptionInterface {
APP_CODE_OR_SECRET_ERROR("应用编码有误或密钥错误"),
APP_CODE_NOT_EXIST("应用编码不存在"),
;
private String message;
AppExceptionEnum(String message) {
this.message = message;
}
}
29.所有编程相关的命名均不能以下划线或美元符号开始
反例:
private void $test(){
System.out.println("命名不能以美元符号开始")
}
private void _test(){
System.out.println("命名不能以下划线开始")
}
30.抽象类命名使用Abstract或Base开头
31.方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase,必须遵从驼峰形式
32.日期格式化字符串[%s]使用错误,应注意使用小写y'表示当天所在的年,大写Y代表week in which year。
解释:
日期格式化时,yyyy表示当天所在的年,而大写的YYYY代表是week in which year(JDK7之后引入的概念),
意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的YYYY就是下一年。
33.浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals来判断。
正例:
BigDecimal aa = new BigDecimal("1.0");
BigDecimal bb = new BigDecimal("0.9");
BigDecimal cc = new BigDecimal("0.8");
BigDecimal x = aa.subtract(bb);
BigDecimal y = bb.subtract(cc);
x.equals(y);
反例:
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
a==b
34.线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
解释:
如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
如有解释不当处,欢迎各位大佬指正