Java编程规范-缺陷预防

2022-12-02 08:59:28 浏览数 (1)

1编程语言

1.1与 C 语言不同的基础数据结构

在 Java 中,有八种基础数据类型,其中 4 种整形, 2 种浮点类型, 1 种用于表示 Unicode 编码的字符单元的字符类型 char 和 1 种用于表示真假的 boolean 类型,其中一些和 C 差异 较 大,开发人员需要注意这些差异: 1、Java 不支持 unsigned ; 2、在 C 中,布尔值和数字类型是可以转换的,在 Java 中不可以, boolean 和数值类型之间不能进行转换; 3、在 C 中,某些类型在不同系统中,所占空间大小不同,比如 long 类型,在 32 位系统中占 4 字节,在 64 位系统中占 8 字节,但 Java 中 long 类型无论在 32 位系统还是 64 位系统中,都是占 8 字节; 4、在 C 中, char 类型占 1 字节空间,而在 Java 中占 2 字节 ,意义也发生了变化, Java 中的 char 用于存储 Unicode 编码的字符 ;

下面表格中是 Java 的八种基础了数据类型: 数据类型 空间占用 ( 位 ) 最小值 最大值 封装类

代码语言:javascript复制
boolean 	- 	- 	- 	Boolean 
char 	16 	u0000 	uFFFF 	Character 
byte 	8 	-128 	127 	Byte 
short 	16 	-32768 	32767 	Short 
int 	32 	-2^31 	2^31 - 1 	Integer 
long 	64 	-2^63 	2^63 - 1 	Long 
float 	32 	1.4E-45F 	3.4028235E 38F 	Float 
double 	64 	4.9E-324D 	1.7976931348623157E 308F 	Double 

1.2权限控制

遵循最小访问原则,准确的使用成员方法及属性的权限控制修饰符: 1、只被该类内部使用的方法及属性使用 private 修饰; 2、同包中可以使用的方法及属性使用默认属性 ( 不加权限控制修饰符 ) ; 3、同包和继承类都可以使用的方法及属性使用 protected 修饰; 4、对外公开的方法和属性使用 public 修饰;

1.3使用括号明确优先级

使用括号 “()” 明确表达式的运算顺序,避免使用默认优先级。 示例: 【错误用法】

代码语言:javascript复制
if (a == b && c == d) 
a = b << 2 | c 

【正确用法】

代码语言:javascript复制
if ((a == b) && (c == d)) 
a = (b << 2) | c 

1.4避免变量赋值复杂化

1、避免在一个语句中给多个变量赋值; 示例: 【错误用法】

代码语言:javascript复制
i = j = 0; 
a = (b = c   d)   e; 

【正确用法】

代码语言:javascript复制
i = 0; 
j = 0; 
b = c   d; 
a = b   e ; 

2 、变量 、变量 – 、 变量、 – 变量等语句要单独占一行,不要放在其他语句中, for 循环语句除外; 示例: 【错误用法】

代码语言:javascript复制
a = c  ; 

【正确用法】

代码语言:javascript复制
a = c; 
c  ; 

1.5魔鬼数字

1、避免使用不便于理解或不便于整体替换的数字,用有实际意义的常量来标记; 2、不要定义 NUMBER_ZERO 这样没有实际意义的常量; 示例 : 【 错误用法】

代码语言:javascript复制
public static final int NUMBER_ZERO = 0; 
  
for (int i = NUMBER_ZERO; i < 1024 ; i  ) { 
    doSomething(); 
} 

【正确用法】

代码语言:javascript复制
public static final int MAX_USER_BUF_SIZE = 1024; 
  
for (int i = 0; i < MAX_USER_BUF_SIZE ; i  ) { 
    doSomething(); 
} 

1.6字符串操作

字符串操作一律使用工具类实现,如 org.apache.commons.lang3. StringUtils ; 示例 : 【 错误用法】

代码语言:javascript复制
private static final String DEFAULT_NAME = "ZhangSan"; 
  
public void doSomething(String strName) { 
    strName.equals(DEFAULT_NAME); 
    ... 
} 

【正确用法】

代码语言:javascript复制
private static final String DEFAULT_NAME = "ZhangSan"; 
  
public void doSomething(String strName) { 
    StringUtils.equals(strName , DEFAULE_NAME ); 
    ... 
} 

1.7明确数值类型

在为 int 、 long 、 float 、 double 变量或常量赋值时,要明确数值类型; int 值无后缀 long 值后缀为大写字母 ‘L’ ,小写字母 ‘l’ 虽合法,但和数字 ‘1’ 容易混淆,所以 禁止 使用; float 值后缀为 ‘f’ 或 ‘F’ ; double 值后缀为 ‘d’ 或 ‘D’ ; 示例 : 【 错误用法】

代码语言:javascript复制
long l = 6; 
double d = 6.0; 
double d = 6.0f; 

【正确用法】

代码语言:javascript复制
int i = 6; 
long l = 6L; 
float f = 6.0f; 
double d = 6.0d;

1.8volatile 修饰 long 和 double

代码语言:javascript复制
暂不处理

Java 中 long 和 double 类型变量占 8 字节,如果变量 非 volatile 属性, Java 内存模型在读写这两种类型的变量时, 是分两次进行的 , 每次读写 4 字节 。 在多线程环境下,如果多个线程同时操作一个 long 或 double 类型变量,可能会出现这样一种情况:读取线程 A 看到变量的前 4 字节来自写入线程 B ,而后 4 字节来自写入线程 C 。 为避免这样情况,在定义 long 和 double 类型变量时,如果该变量有可能在 32 位系统多线程环境 下使用,且 没有其他同步机制 ,应 增加 volatile 属性 。 volatile 本身不保证 获取和设置操作的 原子性 ,仅仅保持修改的可见性。但是 java 的内存模型保证 声明为 volatile 的 long 和 double 变量的 get 和 set 操作是原子的 。 volatile 虽然可以实现 long 和 double 单词读写操作的原子属性,但也不应被滥用, volatile 属性的变量较普通变量少了一些优化措施。比如不会放到缓存中,每次都从内 存 直接读写,所以对性能会有一些影响。 示例 : 【正确用法】

代码语言:javascript复制
public volatile long cnt = 6 L; 

1.9参数合法性 检查

public 和 protected 方法参数的合法性由方法的调用者负责,方法内部也需做必要的检查,检查不通过抛出异常。 默认属性方法和 private 方法参数的合法性由方法的调用者负责,方法内部可不做检查。 示例: 【正确用法】

代码语言:javascript复制
public UserInfo getUser(String usrName) { 
    if ((null == usrName) || (usrName.isEmpty())) { 
        throw new IllegalArgumentException("invalid user name"); 
    } 
    ... 
} 

1.10类 的静态方法调用

按照此规范处理 使用 类名 调用类的静态方法, 而 不是 使用某个 具体对象 ,以 强调 这个 方法的静态属性,同时避免无谓的编译器解析成本 。 示例 : 【 错误用法】

代码语言:javascript复制
UserManager um = new UserManager(); 
Um.aStaticMethod();

【正确用法】

代码语言:javascript复制
UserManager.aStaticMethod(); 

1.11使用 final 修饰 只读变量

按照此规范处理 如果变量是只读的,要使用final来增加限制 。 示例: 【正确用法】

代码语言:javascript复制
public class User { 
    /** userName 在构造函数中赋值,赋值后不可更改 */ 
    private final String userName ; 
  
    public User(String userName) { 
        this. userName = userName; 
    } 
} 

1.12使用 static 修饰静态变量

按照此规范处理 如果变量在多个实例中可以共用,使用 static 修饰此变量以减少重复分配内存,比如日志工具对象。 示例: 【正确用法】

代码语言:javascript复制
public class DemoTest { 
    private static final Logger log = xxx; 
    ... 
} 

1.13指定序列化ID

按照此规范处理 实现序列化接口的类要指定成员变量 serialVersionUID ,以便实现新老版本之间的 兼容 。 如果不指定 serialVersionUID , Java 编译器会根据类的成员变量和一定的算法生成用来表达对象的 serialVersionUID ,当这个类中内容发生变化时, Java 自动计算出的 serialVersionUID 会发生变量,这样就无法读取之前版本所存储的数据。 一般情况下,建议将 serialVersionUID 指定为 1L ,当此值没有变动时,就可以保护后续版本和之前版本的兼容,如果要强制和之前版本不兼容,此值可以每次增加 1. 示例 : 【正确用法】

代码语言:javascript复制
public class DemoClass implements Serializable { 
    private static final long serialVersionUID = 1L; 
} 

1.14尽量避免让方法返回 null

不强制要求按照此规范处理 1、当 Java 操作空的引用中的方法或属性时,会抛出 NullPointerException 异常,为尽可能避免 NullPointerException 异常的出现,在设计方法时,应当尽可能的避免让方法返回 null 。 示例 : 在下面链式调用中,如果某个方法返回 null ,则会引起程序访问空指针异常

代码语言:javascript复制
initialze(argument).calculate(data).dispatch() 

2、一个好的方法设计要尽量避免返回 null 对象,特别是数组、 String 、 List 、 Set 、 Map ,否则要求每一个调用者都要进行 null 判断,增加了代码复杂度。 示例: 【错误用法】

代码语言:javascript复制
public List<UserInfo> getUserList() { 
    List<UserInfo> userList = null; 
  
    if (condition) { 
        return null; 
    } 
    ... 
    return userList; 
} 

/* 调用处 */ 


if(condition) { 
    userList = getUserList(); 
    if (null != userList) { 
        for (UserInfo user : userList) { 
            statements; 
        } 
    } 
} 

【正确用法】

代码语言:javascript复制
public List<UserInfo> getUserList() { 
    List<UserInfo> userList = new ArrayList<UserInfo>() ; 
  
    if (condition) { 
        return userList; 
    } 
    ... 
    return userList; 
} 
  
/* 调用处 */ 
if (condition) { 
    for (UserInfo user : getUserList()) { 
        statements; 
    } 
} 

1.15路径分隔符和文件分隔符

按照此规范处理 在 Windows 中,路径与路径之间的路径分隔符是 ‘;’ ,而在 Linux 中是 ‘:’ 。 在 Windows 中,路径中的文件分隔符是 ‘’ ,而在 Linux 中是 ‘/’ 。 所以,考虑到代码在不同平台的可移植性,代码中在使用到路径分隔符和文件分隔符的地方,不要写死,必须要使用 File 类的字符串常量 File.path.Separator 和 File.separator , File 类可以根据当前的平台,提供该平台所对应的分隔符。 示例 : 【 错误用法】 /* 错误的文件分隔符使用方式 */

代码语言:javascript复制
String sysPath = "/root"; 
String javaPath = "/java/bin"; 

/* 错误的路径分隔符使用方式 */

代码语言:javascript复制
String path = sysPath   ";"   javaPath; 

【正确用法】

代码语言:javascript复制
String sptor = File.separator; 
String pathSptor = File.pathSep a rator; 

/* 正确文件分隔符使用方式 */

代码语言:javascript复制
String sysPath = sptor   "root"; 
String javaPath = sptor   "java"   sptor   "bin"; 

/* 正确路径分隔符使用方式 */

代码语言:javascript复制
String path = sysPath   pathSptor   javaPath; 

1.16谨慎 使用线程不安全的类

代码语言:javascript复制
根据业务场景处理

在多线程环境下,要考虑线程安全问题。 线程 不安全的类

代码语言:javascript复制
HashMap 
LinkedHashMap 
TreeMap 
ArrayList 
LinkedList 
HashSet 
TreeSet 
LinkedHashSet 
StringBuilder 

线程安全的类:

代码语言:javascript复制
ConcurrentHashMap 
Hashtable 
Vector 
CopyOnWriteArrayList 
CopyOnWriteArraySet 
StringBuffer 

需要注意的是,在使用支持线程安全的类时,要注意性能问题,比如 Hashtable 的性能要比 ConcurrentHashMap 差,还有些类有特殊的使用场景,比如 CopyOnWriteArrayList 和 CopyOnWriteArraySet 是使用在读操作 远远多于 写操作的场景下,用在其他场景时性能会下降很多。 JDK 源码 concurrent 目录 中是 一些支持并发访问的类 。除 此 之 外, Java 还提供了多种线程安全的方式,比如 synchronized , ReentrantLock ,同步包装器 (Collections.synchronizedXXX) 等,根据具体情况由开发设计人员选择适合的线程安全方式。

1.17正则表达式预编译

推荐使用正则表达式的预编译功能,可以有效加快正则匹配速度。 Pattern 要定义为 static final 静态变量, 以避免执行多次预编译。 示例: 【错误用法】 // 没有 使用预编译

代码语言:javascript复制
private void func(...) { 
    if (Pattern.matches(regexRule, content)) { 
        ... 
    } 
} 

// 多次 预编译

代码语言:javascript复制
private void func(...) { 
    Pattern pattern = Pattern.compile(regexRule); 
    Matcher m = pattern.matcher(content); 
    if (m.matches()) { 
        ... 
    } 
} 

【正确用法】

代码语言:javascript复制
private static final Pattern pattern = Pattern.compile(regexRule); 
  
private void func(...) { 
    Matcher m = pattern.matcher(content); 
    if (m.matches()) { 
        ... 
    } 
} 

1.18Math.Random()、nextInt()、nextLong()

Math.random() 这个方法返回是 double 类型,取值的范围 0 ≤ x < 1 ( 能够取到 零值 ,注意除零异常)。 如果想获取整数类型的随机数,不要将 x 放大 10 的若干倍然后取整,直接使用 Random 对象的 nextInt () 或者 nextLong () 方法。

1.19用时统计

获取当前毫秒数,使用 System.getCurrentTimeMillis() ,而不是 new Date().getTime(); 如果需要更精确,使用 System.nanoTime() 的 方法 , 在 JDK8 中,针对 统计时间等场景,推荐 使用 Instant 类。

1.20禁止 isXxx() 与 getXxx() 并存

代码语言:javascript复制
按照此规范处理

禁止在 POJO 类中,同时存在对应属性 xxx 的 isXxx() 和 getXxx() 方法; 框架在调用属性 xxx 的提取方法时,并不能确定哪个方法一定是被优先调用到。

2集合处理

2.1ArrayList 的 subList 结果不能强转为 ArrayList

ArrayList 的 subList 结果不可 强转 成 ArrayList ,否则会抛出 ClassCastException 异常,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList 。 sub List 返回的是 ArrayList 的内部类 Sublist ,并不是 ArrayList ,而是 ArrayList 的一个视图,对 SubList 子 列 表 的 所有操作最终会反映到原列表上。

2.2集合转数组

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

代码语言:javascript复制
List<String> list = new ArrayList<>(2); 
list.add("123"); 
list.add("456"); 
String[] arrays = (String[])list.toArray(); 
最后一行 会 出现 ClassCastException 错误。

【正确用法】

代码语言:javascript复制
List<String> list = new ArrayList<String>(2); 
list.add("guan"); 
list.add("bao"); 
String[] array = new String[list.size()]; 
array = list.toArray(array); 

2.3数组转集合

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

代码语言:javascript复制
String[] str = new String[] {"you", "wu"}; 
List list = Arrays.asList(str); 
list .add("hi"); // 运行时 异常 
str[ 0 ] = "hello" ; // list.get(0) 也会 随之改变 

2.4不要在 foreach 中 remove/add 操作

不要在 foreach 循环里进行元素的 remove/add 操作,会 报 java.util.ConcurrentModificationException 异常。 remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁。 示例: 【错误用法】

代码语言:javascript复制
List<String> list = new ArrayList<String>(); 
list.add("1"); 
list.add("2"); 
for (String item : list) { 
    if (StringUtils.equals(item, "2")) { 
         list.remove(item); 
    } 
} 

注 : 会 报 java.util.ConcurrentModificationException 异常

【正确用法】

代码语言:javascript复制
List<String> list = new ArrayList<String>(); 
list.add("1"); 
list.add("2"); 
Iterator<String> iterator = list.iterator(); 
while (iterator.hasNext()) { 
    String item = iterator.next(); 
   if ( StringUtils.equals(item, "2") ){ 
        iterator.remove(); 
    } 
} 

2.5Comparator

Comparator 要满足如下三个条件,不然 Arrays.sort, Collections.sort 会报 IllegalArgumentException 异常。 三个条件如下 1)x, y 的比较结果和 y, x 的比较结果相反。 2)x > y, y > z, 则 x > z 。 3)x = y ,则 x, z 比较结果和 y, z 比较结果相同。 示例: 【错误用法】 // 下例中没有处理相等的情况,实际使用中可能会出现异常:

代码语言:javascript复制
new Comparator<Student>() { 
     © Override 
     public int compare(Student o1, Student o2) { 
          return o1.getId() > o2.getId() ? 1 : -1; 
     } 
}; 

【正确用法】

代码语言:javascript复制
new Comparator<Student>() { 
     ©Override 
     public int compare(Student o1, Student o2) { 
        if(o1.getId() > o2.getId()) { 
            return 1; 
        } 
        if (o1.getId() < o2.getId()) { 
            return -1; 
        }            
        return 0; 
    } 
}; 

2.6使用entrySet 遍历 Map

如果 代码中需要使用到 map 的 KV , 推荐 使用 entrySet 方式 遍历 Map 集合 KV ,而非 keySet 方式。 keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的 value 。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。 如果是 JDK8 ,推荐使用 Map.foreach 方法。 示例: 【错误用法】

代码语言:javascript复制
Map<String, String> map = new HashMap(); 
map.put("a", "b"); 
for (String key : map.keySet() ) { // 这里遍历 一次 
    String value = map.get(key) ;   // 这里 又 遍历 一次 
    fun c ( key, value ); 
} 

【正确用法】

代码语言:javascript复制
Map<String, String> map = new HashMap(); 
map.put("a", "b"); 
for (Map.Entry<String, String> entry: map.entrySet() ) { 
    String key = entry.getKey(); 
String value = entry.getValue(); 
fun c(key, value); 
} 

/ / JDK8 中,可以采用下面 两种 方式

代码语言:javascript复制
map.forEach((key, value) -> fun c(key, value)); 
  
map.forEach((key, value) -> { 
fun c1(key, value); 
func2(key, value); 
}); 

2.7Map集合能否存储 null

集合类

Key

Value

Super

说明

Hashtable

不允许 null

不允许 null

Dictionary

线程安全

ConcurrentHashMap

不允许 null

不允许 null

AbstractMap

线程安全

TreeMap

不允许 null

允许 null

AbstractMap

非线程安全

HashMap

允许 null

允许 null

AbstractMap

非线程安全

2.8集合去重

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

3并发处理

3.1线程必须通过线程池创建

不允许自行 显示的创建线程,推荐使用 线程池 ThreadPoolExecutor 或 其封装类 Executors 来 完成线程 创建 。 使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者 “过度切换”的问题。 使用 Executors 去创建线程 时,要注意使用场景 ,规避 资源耗尽 的风险, Executors 返回的线程池对象的风险如下:

a 、 对于 FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。

b 、 对于 CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。

3.2多线程中的Timer

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

3.3多线程中的Random

避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 导致的性能下降。 Random 实例包括 java.util.Random 的实例或者 Math.random() 的方式。 在 JDK7 之后,多线程环境中,推荐使用没有这个问题的 ThreadLocalRandom

4代码 结构

4.1异常处理

非运行时异常 是由外界运行环境决定异常抛出条件的异常,例如文件操作,可能受权限、磁盘空间大小的影响而失败,这种异常是程序本身无法 控制 的,需要调用者明确考虑该异常出现时该如何处理。 运行时异常 是程序在运行过程中本身考虑不周导致的异常,例如传入错误的参数等。抛出运行时异常的目的是防止异常扩散,导致定位困难。因此在做异常体系设计时要根据错误的性质合理选择自定义异常的基层关系。 还有一种异常是 Error 继承而来的,这种异常由 Java 虚拟机自己维护,表示发生了致命错误,程序无法继续运行 , 例如内存 耗尽等情况 ,我们自己的程序不应该捕获 , 并且也不应该创造这种类型的异常。 1、异常处理中一般情况下遵循 " 早抛出 , 晚捕获 " 的规则; 2、不要主动 捕获 运行时 异常 RuntimeException ; 3、对于非运行时异常,如果不处理,需要用日志记录; 示例 : 【正确用法】

代码语言:javascript复制
public void doSomething() { 
    try { 
       ... 
    } catch (IOExecption ioe) { 
        l og.error(ioe); 
    } 
} 

4、异常 和返回值不应该混合使用,推荐使用异常机制 ; 示例 : 【 错误用法】

代码语言:javascript复制
public UserInfo getUserInfo(String usrName) { 
    UserInfo userInfo = null; 
  
    if((usrName == null) || usrName.isEmpty() || (usrName.length() > MAX_USER_LEN)) { 
        return null; 
    } 
  
    if(!usrName.startWith( " db_ " )) { 
        t hrow new IllegalArgumentException( " invalid prefix. " ); 
    } 
  
    userInfo = getUserInfoFromFile(usrName); 
    return userInfo; 
} 

【正确用法】

代码语言:javascript复制
public UserInfo getUserInfo(String usrName) { 
    UserInfo userInfo = null; 
  
    If((usrName == null) || usrName.isEmpty() || (usrName.length() > MAX_USER_LEN)) { 
        throw new IllegalArgumentException( " user name is invalid. " ); 
    } 
  
    If(!usrName.startWith( " db_ " )) { 
        throw new IllegalArgumentException( " invalid prefix. " ); 
    } 
  
    userInfo = getUserInfoFromFile(usrName); 
    return userInfo; 
} 

4.2模块间调用

1、模块间共用的函数和常量要放在公共目录,不允许模块各自定义; 2、对外的非常量的实例变量,要封装 getter 和 setter 方法,不允许定义为 public 属性;

4.3覆写

1、覆写父类和接口的方法必须有 @Override 注释,以此可清楚说明此方法是覆写父类的方法,且可以保证覆写父类的方法时不会因为单词写错而造成错误; 2、所有对象必须覆写 toString() 方法,虽然 Object 类实现了 toString() 方法,但返回的信息十分有限,不能让用户信息的了解该类,覆写 toString() 方法时,应该返回该类有意义的便于阅读理解的内容,如果父类的 toString() 方法能够清晰的表达子类的内容,那么子类可以不覆写这个方法; 3、覆写 equals() 方法时要同时覆写 hashCode() 方法,否则可能会导致该类无法在基于散列的集合中正常使用。这样的集合包括 HashMap 、 HashSet 、 Hashtable 等。覆写这两个方法时,应满足的约定条件是:两个对象 equals() 为 true 时, hashCode() 值必须相等。 示例 : 【正确用法】

代码语言:javascript复制
public class DemoNode { 
    private String nodeName; 
    
    public DemoNode(String nodeName) { 
        this.nodeName = nodeName; 
    } 
  
    @Override 
    public String toString () { 
        return "NodeName:"   this.nodeName; 
    } 
  
    @Override 
    public boolean equals (Object obj) { 
        if (this == obj) { 
            return true; 
        } 
        if (obj == obj) { 
            return false; 
        } 
        if (getClass() != obj.getClass()) { 
            return false; 
        } 
        DemoNode other = (DemoNode) obj; 
        if (nodeName == null) { 
            if (other.nodeName != null) { 
                return false; 
            } 
        } else if (!nodeName.equals(other.nodeName)) { 
            return false; 
        } 
        return true; 
    } 
  
    @Override 
    public int hashCode () { 
        final int prime = 31; 
        int result = 1; 
        result = prime   result   ((nodeName == null) ? 0 : nodeName.hashCode()); 
        return result; 
    } 
} 

4.4循环分支

1、for 循环中尽 量避免操 作循环条件; 示例 : 【 错误用法】

代码语言:javascript复制
for (int i = 0; i < MAX_CNT; i  ) { 
    ... 
    if (condition) { 
        i--; 
    } 
} 

2、while 循环条件中尽量避免使用 continue 语 句 ; 示例 : 【 错误用法】

代码语言:javascript复制
while (pstNode(i)) { 
    ... 
    if (condition) { 
        continue; 
    } 
    i  ; 
} 

3、为避免越界等风险, for 循环尽量使用 foreach 形式 ; 示例 : 【 错误用法】

代码语言:javascript复制
int[] cntArray = new int[16]; 
... 
for (int i = 0; i <= cntArray.length ; i  ) { 
    doSomething (cntArray[i]); 
} 

【正确用法】

代码语言:javascript复制
int[] cntArray = new int[16]; 
... 
for (int i : cntArray) { 
    doSomething (i); 
} 

4、在高并发 或循环 场景中,避免使用 “ 等于” 或“不等于” 判断作为中断或退出的条件 ; 如果并发 或循环 控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。 例如 判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止。 示例 : 【 错误用法】

代码语言:javascript复制
for (int i = cnt ; i != 0 ; i -- ) { 
    doSomething (cntArray[i]); 
}

【正确用法】

代码语言:javascript复制
for ( int i = cnt; i >= 0; i-- ) { 
    doSomething (i); 
} 

5资源类

5.1资源 close()

1、优先采用 try-with-resources 方式关闭数据库、 IO 等资源。 try-with-resources 方式可以把资源的关闭动作交给 JVM 来完成,保证不会出现资源泄露的情况,而且代码看起来更加简洁。但使用 try-with-resources 方式时要 注意 资源申请的 顺序 , JVM 在关闭这些资源时是按照与 创建资源时相反的顺序进行关闭的 ; 示例 : 【正确用法】

代码语言:javascript复制
/* 在关闭资源时, JVM 先关闭后面的 out 资源,再关闭前面的 in 资源 */ 
try (InputStream in = new FileInputStream(INPUT_FILE_NAME); 
     CutputStream out = new FileOutPutStream(OUTPUT_FILE_NAME)) { 
    ... 
} catch (IOException ioe) { 
    ... 
} 

2、如果不能采用 try- with -resources 方式,资源的关闭动作必须放在 finally 分支中,如果多个资源需要关闭,需要对每个资源进行 try-catch ,以防止一个资源关闭失败导致其他资源未关闭; 示例 : 【 错误用法】

代码语言:javascript复制
try { 
    ... 
    out.close(); 
    in.close(); 
} catch (IOException ioe) { 
    ... 
} 

【 错误用法】

代码语言:javascript复制
try { 
    ... 
} catch (IOException ioe) { 
    ... 
} finally { 
    try { 
        out.close(); 
        in.close(); 
    } catch (IOException ioe) { 
        ... 
    } 
} 

【正确用法】

代码语言:javascript复制
try { 
    ... 
} catch (IOException ioe) { 
    ... 
} finally { 
    try { 
        out.close(); 
    } catch (IOException ioe) { 
        ... 
    } 
  
    try { 
        in.close(); 
    } catch (IOException ioe) { 
        ... 
    } 
} 

5.2预防资源泄露

1、及时去掉废弃对象的引用; 及时将不再使用的对象从全局结构中摘除,以便于 JVM 在垃圾回收时释放这些内存资源。在错误用法示例中,用户退出时, 没有 将用户对象从全局结构 userMap 中 摘除 ,垃圾回收不会释放这些资源,所以造成了资源泄露。 示例 : 【 错误用法】

代码语言:javascript复制
public void login(String userName) { 
    UserInfo userInfo = userMap.get(userName); 
  
    if (userInfo == null) { 
        /* 为新登录用户申请一个对象空间 */ 
        userInfo = new UserInfo(userName); 
  
        /* 将用户对象放到全局结构中 */ 
        userMap.put(username, userInfo); 
    } 
  
    showUserLogin(userName); 
} 
  
public void quit(String userName) { 
    showUserQuit(userName); 
} 

【正确用法】

代码语言:javascript复制
public void login(String userName) { 
    UserInfo userInfo = userMap.get(userName); 
  
    if(userInfo = null) { 
        /* 为新登录用户申请一个对象空间 */ 
        userInfo = new UserInfo(userName); 
  
        /* 将用户对象放到全局结构中 */ 
        userMap.put(username, userInfo); 
    } 
    showUserLogin(userName); 
} 
  
public void quit(String userName) { 
    showUserQuit(userName); 
  
    /* 用户退出时,从全局结构中摘除用户对象节点 */ 
    userMap.remove(userName); 
} 

2、及时释放系统资源; Java 中的垃圾回收只能够释放 JVM 中对象所占据的内存资源,而一些对象除了在 JVM 中申请内存资源之外,还需要 操作系统 为其提供 其他资源 ,垃圾回收机制对这些操作系统资源是无能为力的,这些资源无法通过 JVM 的垃圾回收机制去释放,需要开发人员主动释放。 这些资源包括流, 文件 、 socket 、 数据库 等相关资源。 示例 : 包括 但不限于下列资源:

代码语言:javascript复制
java.io.FileInputStream 
java.io.FileOutputStream 
java.io.BufferedInputStream 
java.io.BufferedOutputStream 
java.io.BufferedReader 
java.io.BufferedWriter 
java.sql.Connection 
java.sql.Statement 
java.sql.PreparedStatement 
java.sql.CallableStatement 
java.sql.ResultSet 

5.3禁用Finalize()

禁止使用 finalize() 方法。 Java 运行时,不仅不能确定 finalize() 方法何时被执行,甚至不能保证一定会被执行,所以使用 finalize() 方法是不可控的,在程序中应该禁止使用该方法。

6性能优化

6.1字符串拼装

1、大量 字符串的 相加处理 应该使用 StringUtils.join 示例: 【错误用法】

代码语言:javascript复制
    public static final int LOOP_TIMES = 5; 
    String strShow = ""; 
	String[] stringArray ; 
	stringArray[0] = “abc1” ; 
	stringArray[ 1 ] = “abc 2 ” ; 
	stringArray[ 2 ] = “abc 3 ” ; 
	stringArray[ 3 ] = “abc 4 ” ; 
	stringArray[ 4 ] = “abc 5 ” ; 
  
    for (int i = 0; i < LOOP_TIMES; i  ) { 
        strShow  = xxx; 
    } 

【正确用法】

代码语言:javascript复制
String[] stringArray ; 
stringArray[0] = “abc1” ; 
stringArray[ 1 ] = “abc 2 ” ; 
stringArray[ 2 ] = “abc 3 ” ; 
stringArray[ 3 ] = “abc 4 ” ; 
stringArray[ 4 ] = “abc 5 ” ; 
  
String strShow = StringUtils.join (stringArray) ; 

6.2计划好List、Set、Map 的初始容量

在使用 List 、 Set 、 Map 等容器时,如果能大致估算最终的 size , 则尽量 在初始化这些容器时指定 一个初始容量 。否则在元素不断添加到容器的过程中,会因为容器空间不足而扩容,每一次扩容都会 new 出新的对象,废弃之前的容器对象。 初始化容器对象时,指定容器初始 size ,这样就能尽量避免在中间过程中不必要的申请和释放内存资源。 无需担心指定容器 size 后,实际添加过程超过这个 size 的情况,这种情况下,容器还是会自动扩容的。 示例: 【推荐用法】

代码语言:javascript复制
private static final int INIT_SIZE = 10240; 
  
Map<String, String> strMap = new HashMap(INIT_SIZE) 
Set<String> strSet = new HashSet(INIT_SIZE); 
List<String> strList = new ArrayList(INIT_SIZE); 

6.3优先使用具有 Buffer 功能的IO操作流

IO 操作流推荐使用 具有 缓冲机制, 也就是 有 Buffer 功能的类嵌套 IO 操作流,没有 Buffer 功能的 IO 操作流会频繁的进行 IO 操作,效率较低。 示例: 【不推荐直接使用】

代码语言:javascript复制
InputStream input = new FileInputStream("employee.dat"); 

下列是一些不具有 Buffer 功能的类 :

代码语言:javascript复制
InputStream 
CutputStream 
Reader 
Writer 

【正确用法】

代码语言:javascript复制
DataInputStream din = new DataInputStream( 
        new BufferedInputStream( 
        new FileInputStream("employee.dat")
        )
        ); 

下列是一些具有 Buffer 功能的类 :

代码语言:javascript复制
BufferedInputStream 
BufferedOutputStream 
BufferedReader 
BufferedWriter 

6.4不要自己实现JDK API的函数或功能

尽量使用 JDK 自带的 API 函数,不用自己写类似功能的函数。 JDK 自身的函数在性能和可靠性方面一般有更好的实现,大家必须熟练掌握,特别是算法方面的。 示例 1- 复制数组 【正确用法】

代码语言:javascript复制
byte[] srcBuf = new byte[BUF_SIZE]; 
byte[] dstBuf = new byte[BUF_SIZE]; 
... 
System.arraycopy(srcBuf, 0, dstBuf, 0, dstBuf.length); 

示例 2- 集合转数组 【正确用法】

代码语言:javascript复制
Set<Byte> byteSet = new HashSet<>(); 
Byte[] byteArray; 
... 
byteArray = byteSet.toArray(new Byte[byteSet.size()]); 

7调试信息

7.1日志记录

1 . 不要 直接 使用 日志 系统 ( Log4j 、 Logback ) 中 的 API ,更不要直接使用 System.out 与 System.err 进行控制台打印,应该使用 日志 框架 SLF4J 中 的 API 来 输出日志 。使用 门面模式 的 日志框架,有利于 系统维护 和 各个类 的 日志处理 方式 统一 ;

示例: 【 错误用法】

代码语言:javascript复制
public class DemoTest { 
    ... 
    public void funA() { 
        ... 
        System.out.println("xxx"); 
    } 
} 

【正确用法】

代码语言:javascript复制
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
... 
  
public class DemoTest { 
    private static final Logger logger = LoggerFactory.getLogger(Abc.class); 
    ... 
    public void funA() { 
        ... 
        logger.info("xxx"); 
    } 
} 

2 . 日志 文件推荐至少保留 15 天, 放置 有些异常 具备 以 “ 周 ” 发生 的特点 ; 3 . 对 trace/debug/info 级别的日志输出,必须使用条件输出形式 或者 使用占位符的方式。 这是 因为如果在在日志中有字符串拼接动作,即使日志没有打印数据,也会执行 字符串 拼接 操作 ,如果 日志 中有对象,会执行对象的 toString() 方法 ,浪费 了 系统资源 , 执行了上述 操作 ,最终却不需要打印日志。 示例: 【 错误用法】

代码语言:javascript复制
public class DemoTest { 
    ... 
    public void funA() { 
        ... 
        logger.debug( "x Processing trade with id: "   id   " and symbol : "   symbol ); 
    } 
} 

【正确用法】

代码语言:javascript复制
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
... 
  
public class DemoTest { 
    private static final Logger logger = LoggerFactory.getLogger(Abc.class); 
    ... 
    public void funA() { 
        ... 
        // 采用有条件输出方式 
        if (logger.isDebugEnabled()) { 
            logger.debug( " xProcessing trade with id: "   id   " and symbol : "   symbol ); 
        } 
  
        // 采用占位符方式 
        logger.debug("xProcessing trade with id: {} and symbol : {} " , id, symbol ); 
    } 
} 

7.2设置线程名称

新起一个线程时,要设置线程 名称 ,这样 性能 测试时可对线程状态进行监控,异常时也可以知道异常发生在 哪个 线程中。 示例 (不局限于这两种 方法 ) : 【正确用法】

代码语言:javascript复制
NewThread newThread = new NewThread(); 
Thread thread = new Thread(newThread, "newThreadName"); 
thread.start(); 

【正确用法】

代码语言:javascript复制
NewThread newThread = new NewThread(); 
Thread thread = new Thread(newThread); 
thread.setName("newThreadName"); 
thread.start(); 

7.3断言的使用

不要在断言中做业务处理。 断言中的语句仅在开启 -ea(-enableassertions) 选项时才能被执行。 示例: 【错误用法】

代码语言:javascript复制
assert doSomething() : "Failed to do someting." ; 

【正确用法】

代码语言:javascript复制
boolean ret = doSomething(); 
assert ret : "Failed to do something."; 

0 人点赞