Java 编程规范

1. OOP规约

避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可

所有的覆写方法,必须加@Override注解:如果在抽象类中对方法签名进行修改,其实现类会马上编译报错

相同参数类型,相同业务含义,才可以使用Java可变参数,避免使用Object:尽量不使用可变参数

外部正在调用或二方库依赖的接口,不允许修改方法签名,避免对接口调用方法产生影响。接口过时必须加@Deprecated 注解,并清晰的说明采用的新接口或者新服务是什么?

不能使用过时的类或方法?

Object的equals方法容易抛空指针异常,应使用常量或者确定有值的对象来调用equals;推荐使用java.util.Objects#equals()(JDK7引入的工具类)

所有相同类型的包装对象之间值的比较,全部使用equals方法比较。说明:对于Integer var = ? 在-128至127范围内的赋值,Integer对象实在IntegerCache.cache产生,回复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但这个区间外的所有数据,都会在堆上产生,并不会复用已有对象。

关于基本数据类型与包装类型的使用标准:所有POJO类属性必须使用包装类型;RPC方法的返回值和参数必须使用包装类型;所有的局部变量使用基本数据类型

定义DO/DTO/VO等POJO类时,不要设定任何属性默认值

序列化类新增属性时,不要修改serialVersionUID字段,避免反序列化失败;如果完全不兼容升级,避免反序列化混乱,那么要修改serialVersionUID

构造方法中禁止加入业务逻辑,如果有初始化逻辑,放在init中

POJO类必须写toString方法。说明:方法执行抛出异常时,可以直接调用OPJO的toString()打印其属性值,便于排查问题

使用索引访问String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查

循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展;说明:反编译处理的字节码文件显示每次循环都会出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费

final可以声明类,成员变量,方法及本地变量。说明:不允许被继承的类如String类;不允许修改引用的域对象如POJO类的域变量;不允许被重写的方法POJO类的setter方法;不允许运行过程中重新赋值的局部变量;避免上下文重复使用一个变量,使用final描述可以强制定义一个变量,方便更好的重构

慎用objcet的clone方法来拷贝对象。说明:对象clone方法默认浅拷贝,若想实现深拷贝需要重写clone方法实现属性对象的拷贝

类成员与方法访问控制从严。说明:过于宽泛的访问范围,不利于模块解耦

2. 集合处理

关于hashCode和equals的处理的规则:只要重写equals,就必须重写hashCode;因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法;如果自定义对象作为Map的键,那必须重写hashCode和equals:String重写了hashCode和equals方法,所以可用String对象作为key使用

ArrayList 的subList结果不可强制转成ArrayList,否则会抛出ClassCastException异常。说明:subList返回的是ArrayList的内部类subList,并不是ArrayList,而是ArrayList的一个视图,对于subList子列表的所有操作最终会反映到原列表

在subList场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历,增加,删除均产生ConcurrentModificationException异常

使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大小就是list.size()。说明:使用toArray带参方法,入参分配的数组空间不够大时,toArray方法内部将重新分配内存空间,并返回新数组地址,如果数组元素大于实际所需,下标位[list.size()]的数组元素将被置为null,其他数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致

使用工具类Array.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportOperationException异常。说明:asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组

泛型通配符 <? extends T>来接收返回的数据,此写法的泛型集合不能使用add方法,而<? super T>不能使用get方法,作为接口调用赋值时易出错。说明:PECS(Producer Extends Consumer Super)原则:频繁往外读取内容的,适合用上界Extends;经常往里插入的,适合用下界Super

不要在foreach循环里进行元素的remove/add操作。remove元素使用Iterator方式,如果是并发操作,需要对Iterator对象加锁

在JDK7版本及以上,Comparator需要满足三个条件,不然Arrays.sort Collections.sort 会报IlleagalArgumentException异常。说明:x,y的比较结果和y,x的比较结果相反;x>y,y>z则x>z;x=y,则x,z比较结果和y,z比较结果相同;不处理相等的情况,会报异常

集合初始化时,指定集合初始值大小;HashMap使用HashMap(int initialCapacity)初始化,正例:initialCapacity = (需要存储的元素个数/负载因子) + 1。注意负载因子(即loaderfactor)默认为0.75,如果暂时无法确定初始值大小,设置为16

使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。JDK8使用Map.foreach方法

注意Map类集合K/V能不能存储null值的情况

合理利用集合的有序性(sort)和稳定性(order);稳定:集合每次遍历的元素次序一定;有序:遍历结果按某种比较规则一次排列;说明:ArrayList是order/unsort;HashMap是unorder/unsort;TreeSet是order/sort

利用Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List的contains方法进行遍历,对比,去重操作。

3. 并发处理

获取单例对象需要保证线程安全,其中的方法也要保证线程安全。说明:资源驱动类,工具类,单例工厂类都需要注意。

创建线程或线程池时请指定有意义的线程名称,方便出错时回溯

线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗玩内存或者过度切换的问题

线程池不允许使用Executors创建,而是通过ThreadPoolExecutor的方式,这样的处理方式明确线程池的运行规则,规避资源耗尽的风险

SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或使用DateUtils工具类。

高并发时,同步调用应该考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,不锁方法体;能用对象锁,就不用类锁。说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法

对多个资源,数据库表,对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。说明:线程一需要对表A B C一次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须时A B C,否则可能会出现死锁

并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据。说明:如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次

多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其他任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。

使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法;线程执行代码注意catch异常,确保countDown方法可以执行,避免主线程无法执行至await方法,直到超时才返回结果。说明:子线程抛出异常堆栈,不能再主线程try-catch到。

避免Random示例被多线程使用,虽然共享该实例时线程安全的,但会因竞争同一seed导致性能下降。Random实例包括java.util.Random的实例或者Math.random()的方式。在JDK7之后,直接使用API ThreadLocalRandom

并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优化问题隐患。推荐问题解决方案中较为简单的一种:将目标属性声明为volatile型

volatile解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但如果多线,同样无法解决线程安全问题。如果是count++操作,使用如下类实现:AtomicInteger count = new AtomicInteger(); count.addAndGet(1);如果是JDK8,推荐使用LongAdder对象,比AtomicLong性能更好(减少乐观锁的重试次数)

在容量不够进行resize时由于高并发可能出现死链,导致CPU飙升,在开发过程中,可以使用其他数据结构或加锁避免此风险

ThreadLocal无法解决共享对象的更新问题,ThreadLocal对象建议使用static修饰。这个变量是针对一个县城内所有操作共有的,所以设置为静态变量,所有此类实例共享此静态变量,也就是说在第一次被使用是装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量

4. 控制语句

在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后

在 if/else/for/while/do语句中必须使用大括号。及时只有一行代码,避免使用单行的形式

除常用方法(getXxx/isXxx),不要再条件判断中执行其他复杂语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。

循环体中的语句要考量性能,定义对象,变量,获取数据库连接等操作不在循环体中操作

进行参数校验:调用频次低的方法;执行时间开销很大的方法;需要极高稳定性和可用性的方法;对外提供的开放接口,不论是RPC/API/HTTP接口;敏感权限入口;不需要进行参数校验:极有可能被循环调用的方法。但必须在方法说明里注明外部参数检查要求;底层调用频度比较高的方法:DAO的参数校验可以省略;被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查

5. 其他

正则表达式在使用时,利用其预编译功能,可以有效加快正则匹配速度。不要再方法体内定义:Pattern pattern = Pattern.compile(规则)

后台输送给页面的变量必须加 ! v a r 中 间 的 感 叹 号 。 说 明 : 如 果 v a r = n u l l , 或 者 不 存 在 , 那 么 !{var} 中间的感叹号。说明:如果var=null,或者不存在,那么 !varvar=null{var}会直接显示在页面上

注意Math.random()的返回类型时double,注意取值范围[0,1],要排除0异常;如果想取整数类型的随机数,不要将x方法10然后取整,直接使用Random对象的nextInt或者nextLong方法

获取当前毫秒数System.currentTimeMillis()。而不是new Date().getTime()。

6. 异常处理

Java类库中定义的一类RuntimeException可以通过预先检查进行规避,而不应该通过catch处理,比如:IndexOutOfBoundsException,NullPointException等等。

异常不要用来做流程控制,条件控制

对大段代码进行try-catch是不负责的表现。catch时请分清稳定代码和非稳定代码

捕获异常是为了处理,如果不处理,就抛给调用者。最外层的业务使用者必须处理异常

有try块放到的事务代码中,cathc异常后,如果需要回滚事务,一定注意手动回滚

finally块必须对资源对象,流对象进行关闭,有异常也要做try-catch

捕获异常与抛异常必须完全匹配,或者捕获异常是抛异常的父类

方法的返回值可以为null。调用方需要进行null判断放置NPE问题

注意NPE问题产生的场景:返回类型为基本数据类型,return包装数据类型的对象时,自动拆箱有可能产生NPE;数据库的查询结果可能为null;集合里的元素即使isNotEmpty,取出的数据元素也可能为null;远程调用返回对象时,一律要求进行空指针判断,防止NPE;对于Session中获取的数据,建议NPE检查;级联调用obj.getA().getB().getC(),一连串调用,易产生NPE;JDK8中使用Optional防止NPE问题

定义时区分unchecked/checked异常,应使用业务含义的自定义异常。DAOException / ServiceException

在代码中使用抛异常还是返回错误码,对于公司外的http/api开放接口必须使用错误码,而应用内部推荐异常抛出;跨应用间RPC调用优先考虑使用Result方式,封装isSuccess()方法,错误码,错误简短信息

7. 日志规约

应用中不可直接使用日志系统(Log4j Logback)中的API,而应该依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护各个类的日志处理方式统一

日志文件推荐至少保存15天,因为有些异常具备以“周”为频次发生的特点

应用中的扩展日志(如打点 临时监控 访问日志)命名方式:appName_logType_logName.log;日志类型:stats/desc/monitor/visit;日志描述:

对于trace/debug/info级别的日志输出,必须使用条件输出形式或者使用占位符的方式

避免重复打印日志,浪费磁盘空间,在log4j.xml中设置additivity=false

异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,通过throws上抛

谨慎记录日志。生产环境禁止输出debug日志;有选择的输出info日志;如果使用warn来记录刚上线的业务行为信息,一定要注意日志输出量的问题。

可以使用warn日志级别记录用户输入参数错误的情况。

日志级别:debug info warning error fatal

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值